Začiatočnícke otázky (Tovarnička vs. Presenter)

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

Zdravím ľudia,
Rád by som sa spýtal na pár veci ohľadom továrničiek a presenterov.
Niekde som sa dočítal, že je lepšie používať formulár ala továrnička napr. registrácia či login ako v presentery ako to teda je? prečo to je lepšie? aké to má výhody?
Momentálne mám Vytvorený SignPresenter kde riešim Registráciu, Prihlasovanie, Obnovenie hesla, Preposlanie aktivácie účtu a odhlasovanie dá sa to používať aj v továrničke spolu alebo pri tovarníčkach to musím rozkúskovať?

Ospravedlňujem sa za hlúpe dotazy no potrebujem si ujasniť čo je lepšie hneď na začiatok.
Za odpoveďe vopred Ďakujem.

Editoval SontoEremo (22. 6. 2014 18:35)

echo
Člen | 134
+
0
-

Zdravím,
obecně se doporučuje single-responsibility princip. Proto by bylo nejlepší kdyby jsi rozdělil SignPresenter na více presenterů: AuthenticationPresenter (login, logout), ResetPasswordPresenter, RegistrationPresenter (registrace, aktivace) Toto má svá omezení.

Pokud potřebuješ vykreslit přihlašovací komponentu ve všech presenterech, pak AuthenticationPresenter ztrácí význam. Uděláš si AuthenticationLoginControl a logiku (chápej jako logiku, která komunikuje s modelem!) nacpeš tam. V BasePresenteru vytvoříš pomocí createComponentAuthenticationControl()… komponentu a vykreslíš v layoutu. Podobně AuthenticationLogoutControl atd.

Ber to jako nástin možností. Není totiž možné říct, abys to dělal tak nebo jinak. Záleží na potřebách aplikace.

David Matějka
Moderator | 6445
+
0
-

@SontoEremo precti si: https://doc.nette.org/…s/form-reuse je tam jak o tovarnach, tak i o formularich jako komponentach, jak radi @echo

SontoEremo
Člen | 341
+
0
-

@echo & @matej21 Vďaka za reakcie :)
Trocha upresním prečo som sa na to pítal.
Všetky spomenuté formuláre chcem použiť iba na jednom mieste nikde inde ich nechcem umiestnovať 2×.
Preto som sa pýtal, či je lepšie továrnička s formulárom alebo formulár v presenter-u,
keďže som chcel mať fajn url v tvare http://www.domena.xx/sign/in atď… tým, že ich rozstrúsim budem mať aj inú tvar url nie? čiže už to bude http://www.domena.xx/ authentication/in atď…

Editoval SontoEremo (22. 6. 2014 19:19)

David Matějka
Moderator | 6445
+
0
-

@SontoEremo v pripade tovarnicek pouze presunes vytvareni onoho formulare pryc z presenteru, aby formular nebobtnal – url tedy zustane naprosto stejna. je to jako bys mel nejakou funkci

function vytvor_prihlasovaci_formular()
{
	$form = new Form();
	$form->addText('email');
	...
	return $form;
}

a tuto funkci pak v createComponent metode zavolal. Pomoci tech zminovanych tovarnicek, ktere jsou jako trida a reigstrovane v DI containeru, je to sofistikovanejsi a cistci.

Komponenty jsou o krapet slozitejsi, ale mnohem flexibilnejsi.

Editoval matej21 (22. 6. 2014 19:35)

SontoEremo
Člen | 341
+
0
-

@matej21 Takže si vytvorím dajme tomu Továrničku s napr. UserRegister.php a vňom

use Nette\Application\UI\Form;

class UserRegister extends Nette\Object
{
    private $database;

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

    public function createComponentSignUpForm()
    {
        $form = new Form;
        ....
        return $form;
    }

    public function signUpFormSuccessSubmited($form)
    {
        ...
    }
}

atď … a to potom predať SignPresenter-u a nakoniec vykresliť v Šablone napr. in.latte ?

echo
Člen | 134
+
-3
-

@SontoEremo To děláš špatně. createComponent ve třídě, která nedědí od Nette\ComponentModel\Container (čili Presenter nebo Control atd. viz Nette.ComponentModel.Container) je k ničemu. Já bych na to ve tvém případě šel takto:

Presenter:

class AuthenticationPresenter extends Presenter
{

	/** @var UserModel @inject */
	public $user;

	public function actionOut()
	{
		$this->user->logout();
	}


	protected function createComponentLoginForm($name)
	{
		$form = new Form($this, $name);
		$form->add...

		if($form->isSuccess()) {
			$this->login($form->values->username, $form->values->password);
		}

		return $form;
	}

	protected function login($username, $password)
	{
		try {
			$this->user->login($username, $password);
		} catch (AuthenticationException $e) {
			$this->flashMessage('Invalid credentials');
		}
	}

}

UserModel by byla fasáda, která by se starala o běžné věci s uživatelem. Měla by přístup do databáze, session, atp. Její metody bych viděl na

login($username, $pass);
logout();
register($username, $pass, array $data);
markAsActive($username);
...

Případně lépe (rozuměj více) objektově:

login(User $user);
logout();
register(User $user);
markAsActive(User $user);
...

Šablonu pak řešit přímočaře:

{control loginForm}
David Matějka
Moderator | 6445
+
0
-

@echo:

  1. do formu nemusis predavat this a name.
  2. nepouzivej zadny ->isSuccess()… ale onSuccess event
  3. tohle myslim @SontoEremo uz umi a ma. tahle diskuze je o refaktoringu toho kodu pryc z presenteru

Editoval matej21 (22. 6. 2014 21:36)

echo
Člen | 134
+
-1
-

matej21 napsal(a):

@echo:

  1. do formu nemusis predavat this a name.
  2. nepouzivej zadny ->isSuccess()… ale onSuccess event
  3. tohle myslim @SontoEremo uz umi a ma. tahle diskuze je o refaktoringu toho kodu pryc z presenteru
  1. jednou jsem měl problém s metodou attached(), tak to tam raději uvádím
  2. když definuju formulář v createComponent..() a používám ho jenom na jednom místě, tak mi to umožní mít obslužnou metodu presenteru jako protected, což se mi líbí; pokud na formulář mám vlastní továrničku, pak s tebou naprosto souhlasím
  3. souhlas
SontoEremo
Člen | 341
+
0
-

@echo & @matej21
Tak po poriadku chalani :) pretože som sa dosť pomýlil takže normálne si vytvorím
AuthenticationPresenter kde budem riešiť logout + login a zas napr. RegistrationPresenter pre registráciu atď…
Šablonu pak řešit přímočaře:

{control loginForm}

Ale kde potom budem definovať Css+Html štruktúru? ako mám teraz napr. login

<div id="signup-page-content">

		{form signInForm class=>"form"}
			<h1 class="block-heading">Prihlásenie</h1>
			<div n:foreach="$form->errors as $error" id="errors" class="alert alert-danger">
			    <strong >{$error}</strong>
			</div>
			<div n:foreach="$flashes as $flash" id="flashes" class="alert alert-danger flash {$flash->type}">
			    <strong >{$flash->message}</strong>
			</div>
			<div class="input-icon mbl">
				<i class="fa fa-user"></i>
				{input username}
			</div>
			<div class="input-icon mbl">
				<i class="fa fa-key"></i>
				{input password}
			</div>
			{input signIn class=>"btn btn-lg btn-red  btn-block"}
		{/form}
		<div class="sign-footer">
			<ul class="list-unstyled">
				<li><a n:href="Sign:reset">Zabudli ste heslo?</a></li>
				<li>Ešte nemáte účet? <a n:href="Sign:up">Vytvorte si ho</a></li>
				<li>Nedostali ste aktivačný email? <a n:href="Sign:resend">Pošlite si ho znova</a></li>
			</ul>
		</div>
	</div>

je o refaktoringu toho kodu pryc z presenteru Tak v presenteru alebo preč z presenteru :)

Editoval SontoEremo (22. 6. 2014 21:51)

David Matějka
Moderator | 6445
+
0
-

@SontoEremo: v pripade pouziti tovarnicky se na zobrazeni nic nemeni – ty jen vezmes ten kod z createComponent* metody a presunes ho do tovarnicky, kterou pak v one createComponent* metode zavolas a vrati ti nakonfigurovany formular.

V pripade formulare jako komponenty (druha cast v prispevku, na ktery jsem odkazoval) je to o malinko komplikovanejsi – tam ono zobrazeni formulare resi samotna komponenta

Editoval matej21 (22. 6. 2014 21:56)

SontoEremo
Člen | 341
+
0
-

@matej21 tebe veľmi moc Ďakujem za rady.
Ale mal by som prosbu mohol by si mi ukázať ako to s tou továrničkou? nejaký rýchli kód ako to všetko vytvoriť pretože som prešiel všetko ale nejak mi to stále neni jasné :)

SontoEremo
Člen | 341
+
0
-

Napr. Vytvorím si továrničku ako ju pomenovať Register… ← čo? čo doň dať? kde uložiť? kam zadať a ako vložiť do presenteru?

David Matějka
Moderator | 6445
+
+1
-

@SontoEremo zkus si to :) kdyz ti budu posilat hotove reseni, tak je to k nicemu. Hezky priklad je v tomto clanku, na ktery uz jsem odkazoval

Proste si vytvoris tridu RegisterationFormFactory, do create metody das inicializaci formulare, vytvoris si tam nejakou processForm metodu, kde se form zpracuje a ulozi. Tuhle factory registrujes jako sluzbu, injectnes do presenteru a v prislusne createComponent* metode zavolas $his->registrationFormFactory->create() … ale to vsechno je v tom clanku, tak si ho precti :)

a jeste jeden prispevek pro inpiraci: https://forum.nette.org/…nebo-tovarna

Editoval matej21 (22. 6. 2014 22:07)

SontoEremo
Člen | 341
+
0
-

@matej kdyz ti budu posilat hotove reseni, tak je to k nicemu.
Pravda pravdúca :) ale teraz k problému takže som sa niečo pokúsil splácať a hádam je to ok
moj components/RegisterFormFactory.php

<?php
namespace App\components;

use Nette\Object;
use Nette\Application\UI\Form;
use Nette\Utils\Html;
use Nette\Security as NS;
use Nette\Mail\Message;
use Nette\Mail\SmtpMailer;
use Nette\Utils;
use Nette\Templating\FileTemplate;
use Nette\Latte\Engine;
use Nette\Templating\Helpers;

class RegisterFormFactory extends Object
{
    private $database;

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

    /**
	 * @var \App\Model\UserRepository
	 * @inject
	 */
	public $userRepository;

    public function createSignUpForm()
    {
        $repository = $this->userRepository;
		$sign_UP_form = new Form();
		$sign_UP_form->addProtection();
		$sign_UP_form->addText('username')
					 ->setAttribute('placeholder', 'Užívateľské meno')
					 ->setAttribute('class', 'form-control')
					 ->addRule(Form::MIN_LENGTH, 'Užívateľské meno musí obsahovať aspoň %d znaky', 4)
					 ->addRule(Form::MAX_LENGTH, 'Užívateľské meno môže obsahovať max. %d znakov', 32)
					 ->addRule(function($input) use($repository)
		             {
		                return !$repository->userexists($input->value);
		             }, 'Toto užívateľské meno je obsadené prosím vyberte si iné!');
		$sign_UP_form->addText('email')
					->setAttribute('placeholder', 'Emailová adresa')
					->setAttribute('class', 'form-control')
					->addRule(Form::EMAIL, 'Zadajte prosím, platnú emailovú adresu!')
					->addRule(function($input) use($repository)
		             {
		                return !$repository->usermailexists($input->value);
		             }, 'Táto emailová adresa je už v systéme zaregistrovaná!');
		$sign_UP_form->addPassword('password')
					->setAttribute('placeholder', 'Heslo')
					->setAttribute('class', 'form-control')
					->addRule(Form::MIN_LENGTH, 'Heslo musí obsahovať aspoň %d znaky', 4)
					->addRule(Form::MAX_LENGTH, 'Heslo môže obsahovať max. %d znakov', 32);
		$sign_UP_form->addPassword('confirm_password')
					->setAttribute('placeholder', 'Potvrďe Heslo')
					->setAttribute('class', 'form-control')
					->addRule(Form::EQUAL, 'Zadané heslá nie sú zhodné!', $sign_UP_form['password']);
		$sign_UP_form->addSubmit('signUp', 'Vytvoriť účet');
        $sign_UP_form->onSuccess[] = $this->processForm;
        return $sign_UP_form;
    }

    public function processForm(Form $sign_UP_form)
    {

    }
}

môj SignPresenter

<?php
namespace App\presenters;

use Nette\Application\UI\Form;

class SignPresenter extends DefaultPresenter {
	/**
	 * @var \App\components\RegisterFormFactory
	 * @inject
	 */
	public $registerFormFactory;

	protected function createComponentRegisterForm() {
        $signUp_Form = $this->registerFormFactory->createSignUpForm();
        return $signUp_Form;
    }

}

môj Config

services:
	- App\components\RegisterFormFactory

A nakoniec up.latte

<div id="signup-page-content">
		{form registerForm}
			<h1 class="block-heading">Registrácia</h1>
			{if $form->hasErrors()}
			<div id="errors" class="alert alert-danger">
			    <ul>
			    	{foreach $form->errors as $error}
			    		<li>{$error}</li>
			    	{/foreach}
			    </ul>
			</div>

			{/if}
			<div n:foreach="$flashes as $flash" id="flashes" class="alert alert-danger flash {$flash->type}">
			    <strong >{$flash->message}</strong>
			</div>
			<div class="input-icon mbl">
				<i class="fa fa-user"></i>
				{input username}
			</div>
			<div class="input-icon mbl">
				<i class="fa fa-envelope"></i>
				{input email}
			</div>
			<div class="input-icon mbl">
				<i class="fa fa-key"></i>
				{input password}
			</div>
			<div class="input-icon mbl">
				<i class="fa fa-shield"></i>
				{input confirm_password}
			</div>
			<span class="help-block mtm mbl text-muted">
				Kliknutím na tlačidlo <strong>"Vytvoriť účet"</strong> súhlasíte so <a href="#">Všeobecnými podmienkami</a> ako aj s <a href="#">Ochranou Osobných údajov</a>. Potvrdzujete, že ste si podmienky a všetky potrebné dokumenty prečítali pred tým ako ste si účet vytvorili.
			</span>
			{input signUp class=>"btn btn-lg btn-red  btn-block"}
		{/form}
		<div class="sign-footer">
			<ul class="list-unstyled">
				<li>Už mám účet <a n:href="Sign:in">chcem sa Prihlásiť</a></li>
			</ul>
		</div>
	</div>
David Matějka
Moderator | 6445
+
0
-

@SontoEremo: skoro dobre :) jen nepouzivej mimo presentery injectovani pomoci anotace inject. to se hodi (jen) do presenteru (je tady uz spoustu diskuzi ohledne toho a nebudu zacinat dalsi proc tomu tak je). takze si ten repozitar vyzadej v konstruktoru, stejne jako to delas s tim databazovym spojenim. Jinak tam na prvni pohled nevidim chybu.

SontoEremo
Člen | 341
+
0
-

hej do constructoru ale ako keď chcem injectovať viac súborov?

private $userRepository;

	public function __construct(\App\Model\UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    private $userManager;

	public function __construct(\App\Model\UserManager $userManager)
    {
    	parent::__construct($userManager);
        $this->userManager = $userManager;
    }

Je hlúposť asi, že? :)

David Matějka
Moderator | 6445
+
0
-

Neinjectujes soubory, ale objekty (instance trid)
Vic konstruktoru mit nemuzes, proste do jednoho konstruktoru pridej vice parametru

protected $userRepository;

protected $userManager;

public function __construct(\App\Model\UserRepository $userRepository, \App\Model\UserManager $userManager)
{
	$this->userRepository = $userRepository;
	$this->userManager = $userManager;
}
SontoEremo
Člen | 341
+
0
-

@matej na prvý krát som to aj tak skúšal ale bez čiarky a skončil som s chybou tak som to zmenil no nevadí Ďakujem za pomoc a nakopnutie tak zas nabudúce :D
Ešte raz díky @matej21

SontoEremo
Člen | 341
+
0
-

Predsa len ešte jedná otázka
v presenteri som uvádzal $template->verifyLink = $this->presenter->link(‚//Sign:verify‘, array(‚token‘ ⇒ $token,));
Teraz by som asi mal namiesto $this->presenter->link() mal používať niečo iné ale čo?

Šaman
Člen | 2666
+
0
-

V presenteru je $this totožné s $this->presenter, takže ten presenter je tam zbytečné psát. Jestli jsi v komponentě, tak se tímto zápisem dostaneš na presenter a dál pracuješ jako uvnitř presenteru. Nebo napíšeš $this->plink(…);. Plink si najde presenter a zavolá nad ním link.

SontoEremo
Člen | 341
+
0
-

@Šaman
Vyhadzuje mi vždy
Call to undefined method App\components\RegisterFormFactory::plink()

Šaman
Člen | 2666
+
0
-
  1. Link, nebo plink bych nepředával šabloně, ale volal makrem. To tu chybu ale nezpůsobuje.
  2. Jaká RegisterFormFactory? Ty si píšeš vlastní tovární třídu? Ta samozřejmě žádný presenter nezná. Obsluhu musíš napsat přímo do komponenty (něco, co dědí od PresenterComponent). Ukázku formuláře v komponentě máš třeba tady.
SontoEremo
Člen | 341
+
0
-

@Šaman ja mám RegisterForm a vňom

<?php
namespace App\components;

use Nette\Object;
use Nette\Application\UI\Form;
use Nette\Utils\Html;
use Nette\Security as NS;
use Nette\Mail\Message;
use Nette\Mail\SmtpMailer;
use Nette\Utils;
use Nette\Templating\FileTemplate;
use Nette\Latte\Engine;
use Nette\Templating\Helpers;

class RegisterFormFactory extends Object
{
	/** @persistent */
    public $backlink = '';

    protected $userRepository;

	protected $userManager;

	public function __construct(\App\Model\UserRepository $userRepository, \App\Model\UserManager $userManager)
	{
	    $this->userRepository = $userRepository;
	    $this->userManager = $userManager;
	}

    public function createSignUpForm()
    {
    	$repository = $this->userRepository;
		$sign_Up = new Form();
		$sign_Up->addProtection();
		$sign_Up->addText('username')
					 ->setAttribute('placeholder', 'Užívateľské meno')
					 ->setAttribute('class', 'form-control')
					 ->addRule(Form::MIN_LENGTH, 'Užívateľské meno musí obsahovať aspoň %d znaky', 4)
					 ->addRule(Form::MAX_LENGTH, 'Užívateľské meno môže obsahovať max. %d znakov', 32)
					 ->addRule(function($input) use($repository)
		             {
		                return !$repository->userexists($input->value);
		             }, 'Toto užívateľské meno je obsadené prosím vyberte si iné!');
		$sign_Up->addText('email')
					->setAttribute('placeholder', 'Emailová adresa')
					->setAttribute('class', 'form-control')
					->addRule(Form::EMAIL, 'Zadajte prosím, platnú emailovú adresu!')
					->addRule(function($input) use($repository)
		             {
		                return !$repository->usermailexists($input->value);
		             }, 'Táto emailová adresa je už v systéme zaregistrovaná!');
		$sign_Up->addPassword('password')
					->setAttribute('placeholder', 'Heslo')
					->setAttribute('class', 'form-control')
					->addRule(Form::MIN_LENGTH, 'Heslo musí obsahovať aspoň %d znaky', 4)
					->addRule(Form::MAX_LENGTH, 'Heslo môže obsahovať max. %d znakov', 32);
		$sign_Up->addPassword('confirm_password')
					->setAttribute('placeholder', 'Potvrďe Heslo')
					->setAttribute('class', 'form-control')
					->addRule(Form::EQUAL, 'Zadané heslá nie sú zhodné!', $sign_Up['password']);
		$sign_Up->addSubmit('signUp', 'Vytvoriť účet');
        $sign_Up->onSuccess[] = $this->signupsuccessForm;
        return $sign_Up;
    }

    public function signupsuccessForm(Form $sign_Up) {
    	$values = $sign_Up->getValues();
        if ($values->password === $values->confirm_password){
            $user = $this->userManager;
            $token = Utils\Random::generate(32);
            $userip = $_SERVER['REMOTE_ADDR'];
            $user->add($values->username, $values->email, $values->password, $token, $userip);
			$template = new FileTemplate(__DIR__ . '/../templates/Activate/email.latte');
			$template->registerFilter(new Engine);
            $template->registerHelperLoader('\Nette\Templating\Helpers::loader');
            $template->user = $user;
            $template->verifyLink = $this->presenter->plink('//Sign:verify', array(
                'token' => $token,
            ));

            $mail = new Message;
            $mail->setFrom('VistmoLabs <vistmolabs@vistmolabs.org>')
                 ->addTo($values['email'])
                 ->setSubject('Aktivácia účtu')
                 ->setHtmlBody($template);

            $mailer = new SmtpMailer(array(
		        'host' => 'smtp.vistmolabs.org',
		        'port' => 465,
		        'username' => 'vistmolabs@vistmolabs.org',
		        'password' => 'WS83ZYF6427g5G9',
		        'secure' => 'ssl',
			));
        	$mailer->send($mail);
            $this->flashMessage(Html::el('div')
             	 ->setHtml('Registrácia prebehla úspešne. Na zadanú Emailovú adresu <strong class="email-strong"><br>' . $values->email . '<br></strong> Vám bol zaslaný aktivačný link, po úspešnej aktivácii sa budete môcť prihlásiť.'));
            $this->redirect('finish');
        } else {
            $signup_form->addError($this->getMessage());
        }
	}
}

problém mi robí táto časť

$template->verifyLink = $this->presenter->plink('//Sign:verify', array(
               'token' => $token,
           ));
Šaman
Člen | 2666
+
0
-

Jj. A když to zakomentujes, tak ti bude zlobit i $this->flashMessage a $this->redirect. Proč? Protože dědíš od Object, který žádně flahmessage ani redirecty nezná. To všechno musíš volat nad presenterem. Na presenter se dostaneš (běžným způsobem) jen z komponenty, která bude k presenteru připojena (až bude připojena, takže ne třeba v konstruktoru). Takže:

  1. potřebuješ komponentu, viz třeba můj odkaz – pak bude fungovat $this->presenter->link/redirect/flashMessage
  2. volitelně na ni buď dopíšes ručně tovární třídu, nebo použiješ generované továrničky
SontoEremo
Člen | 341
+
0
-

@Šaman dík spravil som to podľa tvojho formulára a všetko funguje ok ale chcem sa spýtať v šablone Components/LoginForm/loginForm.latte chcem uviesť link ale vyhadzuje to chybu

<ul class="list-unstyled">
				<li><a n:href="#">Zabudli ste heslo?</a></li>
				<li>Ešte nemáte účet? <a n:href="#">Vytvorte si ho</a></li>
				<li>Nedostali ste aktivačný email? <a n:href="#">Pošlite si ho znova</a></li>
			</ul>

predtým v presenter-y stačili <li><a n:href=„Sign:reset“>Zabudli ste heslo?</a></li> ale tu neviem ako na to.

A ešte sa chcem spítať naozaj je potrebné mať komponentu keď prihlasovací a vlastne všetky formuláre budú len na jednom mieste a nikde inde?

Šaman
Člen | 2666
+
0
-
  1. n:href je zkratka pro makro link. Takže pokud jsi v komponentě a odkazuješ se na presenter, musíš použít href="{plink …}"
  2. Není. Já mám komponenty raději, ale zrovna u takového jednoúčelového formuláře, jako přihlašování je to jedno. Obecně ale presentery dělají moc věcí a jdou to dlouhé třídy, takže se snažím je co nejvíc osekat. Druhým důvodem pro komponenty je snadné znovupoužití.

Editoval Šaman (24. 6. 2014 0:00)