DATABÁZOVÝ MODEL \ PROPOJENÍ S DATÁBÁZÍ

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
HosekPetr
Člen | 31
+
0
-

Dobrý den všem,

jsem začátečník v nette, v MVP i objektovém programování a potřebuji moc pomoct, nenašel jsem nic na fóru ani v dokumentaci, jak by to mělo vypadat.

Snažím se o znovupoužitelnost kódu, abych vše mohl kdykoli a kdekoli použít znovu. Proto jsem si v adresářové struktuře ve složce app vytvořil složku nazvanou components a v ní složku forms, kde budou třídy formů, které budu dále někde využívat, k tomuto postupu mě přimělo několik témat zde na fóru.

Hlavní třída je BaseForm od které budou všechny ostatní třídy dědit a BaseForm bude dědit od Nette\Application\UI\Form.

Níže jsou uvedeny nějaké kódy.

V součané době to hází chybu „Fatal Error Call to a member function getByType() on a non-object“, ta bude jistě hned v BasePresenteru. Řekl bych, že v BaseForm i BasePresenter nemají být ty construkty, i když v authenticatoru to funguje jak má! :(

Poraďte mi prosím, jak udělat databázový model, který budu moct použít uplně všude! V presenterech i v komponentách, které si vytvořím pro znovupoužití. Předpoklad je, že komponenty se budou stavět na všem možném na UI\Control nebo UI\Form.

Perfektní by bylo, kdybych mohl v jakékoli třídě použít např. $this->database->user() a to už by reprezentovalo tabulku v databázi, jde mi o to aby bylo poznat, že jde o spojení s databází. někde jsem viděl něco jako $this->context->user() za použítí továren, ale to se mi moc nelíbí. Navíc se v prezenterech musí použít getServices a jinde to nejde. :(

Všem předem velice děkuji za pomoc!

app/config/config.neon

common:
	parameters:
		database:
			driver: mysql
			host: localhost
			dbname: test
			user: test
			password: test

	php:
		date.timezone: Europe/Prague

	nette:
		session:
			autoStart: smart

		database:
			default:
				dsn: '%database.driver%:host=%database.host%;dbname=%database.dbname%'
				user: %database.user%
				password: %database.password%

	services:
		database: @Nette\Database\Connection

		authenticator: Authenticator( @database )

	factories:

production < common:

development < common:

soubor app/presenters/BasePresenter.php

use Nette\Database\Connection,
	Nette\Application\UI\Presenter;

abstract class BasePresenter extends Presenter
{

	public $database;

	public function __construct(Connection $database)
    	{
        	$this->database = $database;
    	}

	public function handleOdhlaseni()
	{
		$this->getUser()->logout();
		$this->flashMessage('Odhlášení bylo úspěšné.', 'success login');
		$this->redirect('Prihlaseni:');
	}


}

soubor components/forms/BaseForm.php:

use Nette\Application\Connection;
use Nette\Application\UI\Form;

abstract class BaseForm extends Form
{

	public $database;

	public function __construct(Connection $database)
    	{
        	$this->database = $database;
    	}

}

soubor components/forms/SignUpForm.php:

use Nette\Diagnostics\Debugger;
use Nette\Database\Connection;

class SignUpForm extends BaseForm
{

	public function __construct()
	{
		parent::__construct();
		$this->addText('email', 'E-mail:')
			->setRequired('Vyplňte prosím email!')
			->setAttribute('class', 'signUpInput')
			->addRule(self::EMAIL, 'Email nemá správný formát!');
		$this->addPassword('password', 'Heslo:')
			->setRequired('Vyplňte prosím heslo!')
			->setAttribute('class', 'signUpInput')
			->addRule(self::MIN_LENGTH, 'Heslo musí mít alespoň %d znaků!', 6)
			->addRule(self::PATTERN, 'Heslo musí obsahovat číslici!', '.*[0-9].*');
		$this->addPassword('passwordVerify', 'Ověření hesla:')
			->setRequired('Vyplňte prosím ověření hesla!')
			->setAttribute('class', 'signUpInput')
			->addRule(self::MIN_LENGTH, 'Ověření hesla musí mít alespoň %d znaků!', 6)
			->addRule(self::PATTERN, 'Ověření hesla musí obsahovat číslici!', '.*[0-9].*')
			->addRule(self::EQUAL, 'Hesla se neshodují!', $this['password']);
		$this->addText('username', 'Uživatelské jméno:')
			->setRequired('Vyplňte prosím uživatelské jméno!')
			->setAttribute('class', 'signUpInput');
		$this->addSubmit('register', 'Registrovat')
			->setAttribute('class', 'submit');
	}

	public function handleSignUp($form)
	{

		$formValues = $form->getValues();
		$insertValues = array(
			'email' => $formValues['email'],
			'username' => $formValues['username'],
			'password' => $formValues['password']
		);
		$userExist = $this->database->table('user')->where('email', $formValues['email'])->count('*');
		if ($userExist === 0) {
			$this->database->table('user')->insert($insertValues);
			$this->flashMessage('Registrace proběhla úspěšně!', 'success');
		}
		else {
			$this->flashMessage('Uživatel pod tímto emailem již existuje!', 'error');
		}
	}

}

soubor models/Authenticator.php

use Nette\Security as NS,
	Nette\Database\Connection,
	Nette\Object;


class Authenticator extends Object implements NS\IAuthenticator
{
	public $database;

	public function __construct(Connection $database)
    	{
        	$this->database = $database;
    	}

	public function authenticate(array $credentials)
	{
		list($email, $password) = $credentials;
		$row = $this->database->table('user')->where('email', $email)->fetch();

		if (!$row) {
			throw new NS\AuthenticationException("User '$email' not found.", self::IDENTITY_NOT_FOUND);
		}

		if ($row->password !== $this->calculateHash($password)) {
			throw new NS\AuthenticationException("Invalid password.", self::INVALID_CREDENTIAL);
		}

		unset($row->password);
		return new NS\Identity($row->id, null, $row->toArray());
	}

	public function calculateHash($password)
	{
		return $password; // doplnit zabezpečení hesla
	}

}
thorn
Člen | 14
+
0
-

No komponenty jsou tu od toho, aby mohli byt znovupouzitelne a nemelo by se v nich vyuzivat globalniho pristupu (napr. v kterekoliv komponente pristupovat rovnou jako prihlaseny uzivatel, rovnou vyuzivat databaze, atp.) jinak receno, spravny pristup by asi mel byt takovy, ze pokud chces pristupovat ke komponente, melo by to fungovat tak, ze v konstruktoru komponenty vstoupis s napr. databazovym parametrem pro vyber, neco jako construct(\Selection $table,..), a abys mohl v techto komponentach manipulovat s databazi, musis vyuzivat napr. use Nette\Database\Table\Selection;, samotnou komponentu pak jen volas tak, ze v prislusnem presenteru vytvoris novy objekt a vstoupis tam s ni se svoji databazi.

Ale abych odpovedel na tvoji otazku, zrejme to chces globalne resit a mit to tak jednodussi.
Jeste pred rozdelenim QuickStartu na rozdeleni db do faktorek to fungovalo tak, ze sis v config.neon zaregistroval novou sluzbu do casti Services pod database sis dal neco takoveho:

<?php
services:
	model:	Model(@database)
	authenticator: Authenticator( @model::getQuery(user) )
?>

Tim sis zaregistroval model a ve slozce models si vytvoril pristup k databazi v souboru Model.php, vytvoril ses tam nejake metody, ktere pak volali nejake tabulky, a kdekoliv, at uz v presenteru nebo komponente kdyz pouzijes napr.

<?php
use \Model;
?>

a nacteni teto sluzby pomoci (to nize muzes dat do napr do BasePresenteru):

<?php
public function startup() {
	parent::startup();

	$this->model = $this->getService('model');
    }
?>

muzes z vesela pristupovat k DB napriklad timto zpusobem:

<?php
$this->model->getQuery('tableName')->where()->order();
?>

Editoval thorn (17. 3. 2012 17:44)

Michal Vyšinský
Člen | 608
+
0
-

Ahoj,
takže začnu tím, kde osobně vidím největší problémy:

  1. BasePresenter:
    • presenteru NIKDY nepřepisuj contructor. Nikde ani nevidím parent::__construct() – ta aplikace ti podle mého nemůže vůbec jet
  2. Property BasePresenteru database by podle mého měla být private
  3. handleOdhlaseni() – když pominu hrozné míchání angličtiny a češtiny tak menší chybu vidím tady:
$this->flashMessage('Odhlášení bylo úspěšné.', 'success **login**');

Sice se nejedná o chybu, která by zastavila aplikaci, ale je to podle mě nelogické – odhlášení == logout a ne login

  1. $this->redirect('Prihlaseni:'); – ten redirect myslím taky nikdy nebude fungovat – redirectuje se na presenter nebo action ale rozhodně ne pomocí takového stringu. Omlouvám se za klamnou informaci – díky @duke
  2. BaseForm – stejné, jako 1 a 2. Na database si můžeš udělat setter – ale nepředával bych to v constructoru – a opět nevoláš parent::__construct()
  3. SignUpForm – také 1 a 2 (kromě parent::__construct)

Editoval CherryBoss (17. 3. 2012 18:38)

duke
Člen | 650
+
0
-

@CherryBoss Ten redirekt má z hlediska syntaxe v pořádku, 'Prihlaseni:' je ekvivalentní k 'Prihlaseni:default', tj. redirekce na presenter PrihlaseniPresenter s action default.

Michal Vyšinský
Člen | 608
+
0
-

Ok omlouvám se. Nedošlo mi to.

HosekPetr
Člen | 31
+
0
-

Zdravím všechny,

předem všem velice děkuji za odpovědi! Všechno hodlám využít!

@CherryBoss: AJ A ČJ míchám proto, že chci mít adresy kompletně české, ale asi se na to vykašlu a dám to do AJ kompletně, protože i doména bude v AJ.

Ale stále mám problém s modelem!

S funkce startup(), ve které volám $this->getService, lze použít pouze v presenterech a nejde využít například v BaseForm kde používám extends Nette\Application\UI\Form.

Otázka zní… Mám pro BaseForm také pužít extends Nette\Application\UI\Presenter nebo to lze vyřešit vše jinak? V současné době model Database.php využívá přípojení z config.neon že? Nebráním se tomu předělat model a jinak řešit přístup k databázím v projektu!

Moc prosím o rady.

Předem všem děkuji!!!

Soubor BaseForm

use Nette\Application\Connection,
	Nette\Application\UI\Form;

use \Database;

abstract class BaseForm extends Form
{
	/**
	 * Spojení s databází
	 */
	protected $database;

	public function startup()
	{
		parent::startup();
		$this->database = $this->getService('databaseModel');
	}

}

soubor SignUpForm.php

use Nette\Diagnostics\Debugger;
use \Database;

class SignUpForm extends BaseForm
{

	public function __construct()
	{
		parent::__construct();
		$this->addText('email', 'E-mail:')
			->setRequired('Vyplňte prosím email!')
			->setAttribute('class', 'signUpInput')
			->addRule(self::EMAIL, 'Email nemá správný formát!');
		$this->addPassword('password', 'Heslo:')
			->setRequired('Vyplňte prosím heslo!')
			->setAttribute('class', 'signUpInput')
			->addRule(self::MIN_LENGTH, 'Heslo musí mít alespoň %d znaků!', 6)
			->addRule(self::PATTERN, 'Heslo musí obsahovat číslici!', '.*[0-9].*');
		$this->addPassword('passwordVerify', 'Ověření hesla:')
			->setRequired('Vyplňte prosím ověření hesla!')
			->setAttribute('class', 'signUpInput')
			->addRule(self::MIN_LENGTH, 'Ověření hesla musí mít alespoň %d znaků!', 6)
			->addRule(self::PATTERN, 'Ověření hesla musí obsahovat číslici!', '.*[0-9].*')
			->addRule(self::EQUAL, 'Hesla se neshodují!', $this['password']);
		$this->addText('username', 'Uživatelské jméno:')
			->setRequired('Vyplňte prosím uživatelské jméno!')
			->setAttribute('class', 'signUpInput');
		$this->addSubmit('register', 'Registrovat')
			->setAttribute('class', 'submit');
	}

	public function handleSignUp($form)
	{

		$formValues = $form->getValues();
		$insertValues = array(
			'email' => $formValues['email'],
			'username' => $formValues['username'],
			'password' => $formValues['password']
		);
		$userExist = $this->database->user()->where('email', $formValues['email'])->count('*');
		if ($userExist === 0) {
			$this->database->user()->insert($insertValues);
			$this->flashMessage('Registrace proběhla úspěšně!', 'success');
		}
		else {
			$this->flashMessage('Uživatel pod tímto emailem již existuje!', 'error');
		}
		Debugger::barDump($formValues);
		Debugger::barDump($userExist);
	}

}

soubor Database.php

use Nette\Database\Connection,
	Nette\Database\Table\Selection,
	Nette\Object;

class Database extends Object
{
	/**
	 * Spojení s databází
	 */
	public $database;

	public function __construct(Connection $database)
    {
    	$database =
        $this->database = $database;
    }

    public function user() {
    	return $this->database->table('user');
    }

    public function transaction() {
    	return $this->database->table('transaction');
    }
}

soubor config.neon

common:
	parameters:
		database:
			driver: mysql
			host: localhost
			dbname: ledger
			user: root
			password:

	php:
		date.timezone: Europe/Prague

	nette:
		session:
			autoStart: smart

		database:
			default:
				dsn: '%database.driver%:host=%database.host%;dbname=%database.dbname%'
				user: %database.user%
				password: %database.password%

	services:
		database: @Nette\Database\Connection
		databaseModel: Database(@database)
		authenticator: Authenticator( @database )

	factories:

production < common:

development < common: