Správné použití Nette s Kdyby\Events a různé akce

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

Dobrý den,

s Nette začínám a mám pár nejasností. Mám konkrétní příklad s logováním různých interakcí. Narazil jsem na Kdyby\Events a hrozně se mi to zalíbilo a chtěl bych to používat na většině akcích co se na webu dějí, tedy například – změna hesla, přihlášení, odhlášení, atd.

S přihlašováním a odhlašováním jsem to vyřešil celkem jednoduše. Zaregistroval jsem si UserListener a do něj vložil následující –

class UserListener extends Nette\Object implements Kdyby\Events\Subscriber
{

	/** @var LogRepository */
	protected $logRepository;



	/**
	 * @param LogRepository $logRepository
	 */
	public function injectLogRepository(LogRepository $logRepository)
	{
		$this->logRepository = $logRepository;
	}



	public function getSubscribedEvents()
	{
		return array(
			'Nette\Security\User::onLoggedIn'  => 'userLoggedIn',
			'Nette\Security\User::onLoggedOut' => 'userLoggedOut'
		);
	}



	public function userLoggedIn(User $user)
	{
		$this->logRepository->log('Uživatel se přihlásil.', __METHOD__, $user->getId());
	}



	public function userLoggedOut(User $user)
	{
		$this->logRepository->log('Uživatel se odhlásil.', __METHOD__, $user->getId());
	}

}

A přesně takhle bych to chtěl udělat například když si uživatel změní heslo. Ale jak na to správně? Původně jsem myslel že si prostě vytvořím třídu User která bude dědit od Nette\Object a tam si vložím různé funkce, například funkci changePassowrd a dále tam budu mít proměnou onPasswordChange a na ní napojím UserListener výše. Ale nevím zda je to takto správně a když jsem to zkoušel, tak to akci nezalogovalo.

Děkuji za nakopnutí a omlouvám se za případné nejasnosti v tom co píši. Případně rád dovysvětlím.

Filip Procházka
Moderator | 4668
+
+3
-

Ripper napsal(a):

Narazil jsem na Kdyby\Events a hrozně se mi to zalíbilo a chtěl bych to používat na většině akcích co se na webu dějí…

Děláš mi radost ;)

A přesně takhle bych to chtěl udělat například když si uživatel změní heslo. Ale jak na to správně? Původně jsem myslel že si prostě vytvořím třídu User která bude dědit od Nette\Object a tam si vložím různé funkce, například funkci changePassowrd a dále tam budu mít proměnou onPasswordChange a na ní napojím UserListener výše. Ale nevím zda je to takto správně a když jsem to zkoušel, tak to akci nezalogovalo.

Kdyby\Events fungují tak, že aby události fungovaly, musí třída být registrována v DI Containeru. Není tedy možné dělat události nad entitami (možné to je, ale není to moc sexy).

Správný postup tedy je, událost vytvořit v nějakém usermodelu.

class RegisteredUsers extends Nette\Object
{
	public $onChangePassword = array();

	public function changePassword(MyUser $user, $oldPassword, $newPassword)
	{
		try {
			$user->changePassword($oldPassword, $newPassword);
			$this->onChangePassword($user);
			$this->save($user);

			return TRUE;

		} catch (InvalidPasswordException $e) {
			return FALSE;
		}
	}

	// ...
}

class MyUser extends Nette\Object
{
	private $id;
	private $password;

	public function changePassword($oldPassword, $newPassword)
	{
		if ($this->password !== my_awesome_crypt_func($oldPassword)) {
			throw new InvalidPasswordException("Old password doesn't match");
		}

		$this->password = my_awesome_crypt_func($newPassword);
	}

}

Například takto :) Teď už stačí jen zaregistrovat třídu RegisteredUsers jako službu, doplnit chybějící metodu save a injekci databázového připojení přes konstruktor a valíš :)

Ripper
Člen | 56
+
0
-

@Hosiplan: Tak to je paráda! Už mi to běží jak má, děkuji za pomoc, nemá to chybu :)

dada-amater
Bronze Partner | 52
+
0
-

Ahoj Filipe,

moc se mi to libi. Narazil jsem ale na problemy s dedenim. Pouzivam BaseRepository, ze ktere deni ostatni, napriklad CommentRepository. Asi takto:

class BaseRepository extends Nette\Object
{
	protected $mapper;

	public $onInsert = array();
	public $onUpdate = array();

	public function update(BaseObject $item)
	{
		$result = $this->mapper->update($item);
		$this->onUpdate($item);
		return $result;
	}


	public function insert(BaseObject $item)
	{
		$result = $this->mapper->insert($item);
		$this->onInsert($item);
		return $result;
	}
}

BaseObject je predchudce vsech mych zakladnich objektu (entit), napr. Commentu.
Pak mam CommentListener:

class UserListener extends Nette\Object implements Kdyby\Events\Subscriber
{
    public function getSubscribedEvents()
    {
        return array(
            'CommentRepository::onInsert' => 'commentInserted'
        );
    }

    public function commentInserted(Comment $comment)
    {
        //zpracovani
    }
}

Problem je, ze commentInserted se nikdy nezavola. V DebugBaru pred registrovanim CommentListeneru mam:

0x BaseRepository::onInsert
no system listeners

Po registrovani CommentListeneru stejne, jen pribyde:

0x CommentRepository::onInsert
Kdyby\Events\Subscriber

Pokud kod nepodedim, ale zapisu ho primo do CommentRepository, tak to taky funguje. Dekuji za radu.

Filip Procházka
Moderator | 4668
+
0
-

Když si zapneš panel tak uvidíš jaké eventy jsou registrované a jaké se volají. A z toho jde poznat, že event se vždy volá na classe na které je definovaný. Ve zkratce: je to feature.

Pokud potřebuješ, aby se to volalo zvlášť pro všechny potomky, tak budeš muset ty eventy vyvolávat ručně.

class BaseRepository extends Nette\Object
{
    protected $mapper;

    /** @var Kdyby\Events\EventManager */
    private $evm; // nezapomeň injektovat (konstruktor?)

    public function update(BaseObject $item)
    {
        $result = $this->mapper->update($item);
        $this->evm->dispatchEvent(
            get_class($this) . '::onUpdate',
            new EventArgsList([$this, $item])
        );

        return $result;
    }


    public function insert(BaseObject $item)
    {
        $result = $this->mapper->insert($item);
        $this->evm->dispatchEvent(
            get_class($this) . '::onInsert',
            new EventArgsList([$this, $item])
        );

        return $result;
    }
}
dada-amater
Bronze Partner | 52
+
0
-

@ProchazkaFilip díky!

Tomáš Votruba
Moderator | 1114
+
0
-

Přidal jsem si úspěšně listener na Presenter::onStartup.
Teď bych si chtěl přidat listener do vytváření formuláře. Toto mi ale nefunguje:

Form.php

/** @var [] */
public $onBuild;

public function attached($presenter)
{
	parent::attached($presenter);
	$this->onBuild($this);
}

FormListener.php

class FormListener extends Nette\Object implements Kdyby\Events\Subscriber
{

	public function getSubscribedEvents()
	{
		return ['Zenify\Application\UI\Form::onBuild' => 'onBuild'];
		// zkoušel jsem i: return ['Zenify\Application\UI\Form::onBuild'];
	}


	public function onBuild($form)
	{
		dump($form);
		// neproběhne
	}

}

Mohu poprosit o nakopnutí?

Editoval Tomáš Votruba (22. 2. 2014 15:33)

Filip Procházka
Moderator | 4668
+
0
-
  1. je služba registována v DIC?
  2. vidíš event v events debug panelu ?
  3. co se vygenerovalo do system containeru? vložil se do property Event objekt?
Tomáš Votruba
Moderator | 1114
+
0
-

1. V tom byl háček. Používal jsem event v presenteru a ten bylo potřeba jej registrovat jako službu.

Díky.

sasule
Člen | 18
+
0
-

Ahoj,

prosím o radu, případné nakopnutí:
Mám vytvořenu událost, která reaguje na vytvoření uživatele, jemuž bych pak chtěl poslat email, kde bude link na přihlášení/aktivaci nebo cokoli podobného. A tento link bych chtěl generovat. Email generuji z templatu.

Čili jaký bude lepší přístup? Zapomenout na můj EventListener a volat to na presenteru nebo nějak do EventListeneru dostat (asi) presenter a pomocí něj pak vygenerovat odkaz?

Případně je ještě nějaká jiná možnost, jak odkaz v template vygenerovat?

Osobně se mi moc nelíbí postup níže, protože modelu/fasádě předávám cosi navíc, než by potřebovala:
Presenter

<?php

...
$this->_users->save($user, $this);
...
?>

Model:

<?php
class Users extends \Sasule\Common\BaseModel {

    public $onUserCreated = array();

function save(User $_u, Presenter $_p){
	$this->save($_u);
	$this->onUserCreated($_u, $_p);
}

?>

Díky předem za případné rady :-)

Editoval sasule (1. 4. 2014 12:13)

jiri.pudil
Nette Blogger | 1029
+
0
-

Můžeš použít nějaký jednoduchý generátor URL

Editoval jiri.pudil (1. 4. 2014 12:22)

Filip Procházka
Moderator | 4668
+
0
-

No a nebo si nebudeš komplikovat život a vyžádáš si v konstruktoru toho listeneru třídu Application, z ní si sáhneš na presenter ->getPresenter() a link si vygeneruješ.

Případně já to ještě dělám tak, že šablonu presenteru „ukradnu“.

$template = clone $this->app->getPresenter()->getTemplate();

Ten clone je důležitý, aby jsi neupravoval šablonu pro presenter, ale měl vlastní kopii. Můžeš si pak nastavit soubor se šablonou a máš nakonfigurovaný objekt template, ve kterém už budou fungovat makra na odkazy.

sasule
Člen | 18
+
0
-

Pánové, děkuji moc! Já tušil, že to půjde nějak jednodušeji než to dělám :-)

batko
Člen | 219
+
0
-

Ahoj,

rád bych začal používat Kdyby/Events ale nějak se mi to nedaří rozjet.

  • instal přes composer
  • config.neon
  • i v panelu to mám.

Níže je uveden nástřel servicy která se stará o blog, všechny závisloti jsou tam předány nechcí tady vypisovat kilometorvý kod. Ale nestane se nic.

servica

class BlogService extends \Nette\Object {


    public $onInsert = array();

    public function save($data) {

	$this->onInsert();
	//save do DB
    }

listener

class FooListener extends Nette\Object implements Kdyby\Events\Subscriber {

    public function getSubscribedEvents() {
	return array(
	    'Model\Services\BlogService::onInsert' => 'insert',
	);
    }

    public function insert() {
	dump("test");
    }

}
jiri.pudil
Nette Blogger | 1029
+
0
-

Obě třídy jsou zaregistrované v configu a listener má tag kdyby.subscriber?

batko
Člen | 219
+
0
-

jiri.pudil napsal(a):

Obě třídy jsou zaregistrované v configu a listener má tag kdyby.subscriber?

bylo to tím tagem, omlouvám se, díky moc, už to jede

nyní

	FooListener:
		class: FooListener
		tags: [kdyby.subscriber]
batko
Člen | 219
+
0
-

Ještě malý dotaz. Mám kod uvedený níže. Rád bych kdybych mohl event navěsit na jakoukoliv metodu v service…Ale to co mám nyní mi nefunguje. Jde to nějak?

class BlogService extends \Nette\Object {

    public function getAll() {
	return $this->blogReposiotry->findAll();
    }

a

class FooListener extends Nette\Object implements Kdyby\Events\Subscriber {

    /** @var \Nette\Security\User  */
    public $user;

    public function __construct(\Nette\Security\User $u) {
	$this->user = $u;
    }

    public function getSubscribedEvents() {
	return array(

	    'Model\Services\BlogService::getAll' => 'appStartup',

	);
    }

    public function appStartup() {
	dump("foo");
    }
sasule
Člen | 18
+
0
-

Jestli tomu dobře rozumím, tak bych to viděl asi následovně:

<?php
class BlogService extends \Nette\Object {
    public onGetAll = array();

    public function getAll() {
	$this->onGetAll(...); //sem dáš, co předáš do listeneru.
	return $this->blogReposiotry->findAll();
    }

?>
batko
Člen | 219
+
0
-

sasule napsal(a):

Jestli tomu dobře rozumím, tak bych to viděl asi následovně:

<?php
class BlogService extends \Nette\Object {
    public onGetAll = array();

    public function getAll() {
	$this->onGetAll(...); //sem dáš, co předáš do listeneru.
	return $this->blogReposiotry->findAll();
    }

?>

Ok. Takže prostě vždy musím v tém metodě zavolat onGetAll(…) a něco mu předat.

Mě by stačilo kdyby si to vytáhlo Presenter, ale ok beru to jako cenou radu. Děkuji moc.

Editoval batko (7. 4. 2014 13:55)

David Matějka
Moderator | 6445
+
0
-

@batko: koukni na kdyby/aop, treba to vyresi tvuj problem. ale nezneuzivej to ;)

Jiří Nápravník
Člen | 710
+
0
-

Tak si dovolím tady taky jednu otázku na Kdyby\Events – snad se neztratí:-)

Mám modul pro práci s uživateli, a pak modul, který pracuje s městy. Uživatel může být z nějakého města.

Tak a teď chci smazat město. Ale abych mohl smazat město, chci zjistit nejprve, dza ho nemá nějaký uživatel a případně kolik.

Přijde mi na tohle právě ideální použít Kdyby\Events, na začátku delete metody zavolám událost onPreDeleteCity(). Ale teď nevím, jak to správně nasadit. Napadlo mě jedině, že pokud bude město nasazeno vyhodím si výjimku, která ponese informaci, kolik uživatelů má to dané město a odchytím si to v presenteru a předám do flashMessage.

Ale nevím, je to správně takhle? Nejde to nějak lépe? A je na tohle vůbec dobré použít Kdyby\Events?

akadlec
Člen | 1326
+
0
-

tak to bych řek že je neštastné řešení. Co takhle si prostě udělat před samotným voláním mazání select na zjištění kolik tam čeho je a zda to jde smazat a pak klasicky buď udělat delete a nebo poslat flash zprávičku.

mkoubik
Člen | 728
+
0
-

@akadlec:
Tím zbytečně mixuješ persistenční logiku uživatelů a měst.

akadlec
Člen | 1326
+
0
-

No ale používat na to eventy mi taky nepřipadá zas uplně ok ne?

Jan Suchánek
Člen | 404
+
0
-

@mkoubik: Takže vyhazovat vyjímky?

mkoubik
Člen | 728
+
0
-

Mně event + výjimka připadá nejrozumnější, ale udělej to tak jak to vyhovuje tobě a tvému týmu. Jde o to, aby byl kód dobře srozumitelný a spravovatelný, ne aby to bylo správně.

David Matějka
Moderator | 6445
+
0
-

vyjimku bych na to nepouzival. kdyz uz pouzit eventy, tak bych jako parametr udalosti predal nejaky objekt „Result“ a jednotlive subsribery by tam doplnily potrebne informace

Jiří Nápravník
Člen | 710
+
0
-

akadlec: Udělat kontrolu před samotným smazáním a pak kdyžtak smazat, je samozřejmě nejsnazší. Ale je tam už ten problém, že mazání města řeší něco co nemá… Nemluvě o tom, že v budoucnu bude moci být město i další objekty v systému a volat třeba 10 různých „tabulek“ z jednoho místa aby kontrolovali, zda to jde smazat, mi nepřijde příliš košér. A pokud pak použiju jen modul pro města, tak to nepůjde, protože to budu muset zase smazat…

matej21: vyjimka se mi prave taky moc nelibi, je to „ugly“. To s tím resultem zni zajímave, muzes to nejak rozvest? Příliš nechápu… A píšeš „když už použít eventy“, tak se ti to taky příliš nelíbí asi, jak ybs to řešil tedy ty?

Editoval Jiří Nápravník (8. 4. 2014 13:34)

David Matějka
Moderator | 6445
+
0
-

jak ybs to řešil tedy ty?

Pokud jde fakt pouze o uzivatele, udelal bych si nejakou sluzbu (treba obecnou fasadu nebo klidne konkretne „CityRemover“), ktera by vyzadovala jak sluzbu pro praci s uzivateli tak pro praci s mesty. Pak by to overilo, zda existujou nejaci uzivatele a smazalo/nesmazalo by se mesto.

Pokud by tech „kolizi“ mohlo nastat vice, udelal bych si nejakou tridu Result, treba takhle

class CollisionResult
{
	protected $entries = array();

	public function add($type, $collisions)
	{
		$this->entries[$type] = $collisions;
	}
	public function getEntries()
	{
		return $this->entries;
	}

}

pak kde bych mazal

class CityManager
{

	public function deleteCity($id)
	{
		$result = new CollisionResult();
		$this->onPreDeleteCity($id, $result);
		if(count($result->entries)) {
			...
		} else {
			...
		}
	}
}

ten subscriber na onPreDeleteCity by pak doplnil informace

class DeleteCitySubscriber ....
{
	...
	public function onPreDeleteCity($id, $collisions)
	{
		$found = $this->findUsersWithCity($id);
		if($found) {
			$collision->add('user', $found);
		}
	}
}

eventy sice asi nejsou to pravy orechovy na tohle, ale je to nejsnadnejsi reseni :)

Jan Suchánek
Člen | 404
+
0
-

@Jiří Nápravník: Nebylo by lepší nejprve se pokusit smazat, a až potom volat onErrorEvent?

@matej21: Ten subscriber by vytvářel odpovědi pro flashMessage nebo by se sestavovali až v presenteru?

Editoval jenicek (8. 4. 2014 14:19)

Jiří Nápravník
Člen | 710
+
0
-

matej21: díky, tohle vypadá hodně dobre reseni. Mě naopak na tohle prijdou eventy jako delane, jen je tam mozna trochu nezvyk, ze pouziju evente „pred akci“ nez po akci. diky tomu nebudu muset mit uzce svazane moduly. Co se mi tam fakt nelibilo, tak bylo to znasilneni exceptiony jako jsem mel prvne.

jenicek: myslis odchytit si vyjimku, kterou mi vrati databaze a pak to tam teprve resit tim resenim mateje? no neni to spatny napad, ale moc se spoliham na to, ze mi nekdo treba omylem smaze ten foreign key v databazi a pak se mi smaze to co nema:-)

Jan Suchánek
Člen | 404
+
0
-

@Jiří Nápravník: No nevim to by pak měli řešit ty vychytaný logy co řešíme jinde.

Mazat FK by ti neměl nikdo jinak jsou k ničemu, pokud tedy nepoužívaš MyISAM.

Jak využiješ ten subscriber následně?

EDIT: měl by se vůbec zobrazit button na smazání, když ta akce není povolená?

Editoval jenicek (8. 4. 2014 15:50)

akadlec
Člen | 1326
+
0
-

hmm tak při tomto řešení se mě ty eventy začínají líbit…
Takže všechny tyhle check akce by měly být mimo manager? Dejme tomu že chci smazat produkt z eshopu, ale nemůžu smazat produkt který byl již někdy v minulosti koupen, či jej má někdo aktuálně v košíku (košík v db) takže si udělat preDelete kde se udělá check kolize zda byl někdy produkt objednán a zda jej někdo nemá v košíku? Oboje pokud to dobře chápu v DeleteSubsciberu? a pokud to vrátí nějaký počet entries tak provedu třeba jinou akci?

Jiří Nápravník
Člen | 710
+
0
-

jenicek napsal(a):
EDIT: měl by se vůbec zobrazit button na smazání, když ta akce není povolená?

Neměl, ale 1. neni to prilis rozumne resitelne. Muselo by se jedine v databazi ukladat, kolik ma uziti to mesto a kdyz bude nula tak button vykreslit. Nicmene i tak je tam porad ten problem, ze nekdo muze manipulovat z adresou a da to smazat, takze se to sejne musi resit i na urovni kde se to samotne mazani provadi. Taky by na to šli teoreticky udělat eventy, když se přidá uživatel, zavolá se event, který aktualizuje ten counter uziti. Ale tam je pak zase ten nedostatek, že nepůjde vypsat: TOhle město je přiřazeno u 2 uživatelů a čtyř podniků.

akadlec: tak nemusi byt mimo manager. ale dává mi to smysl a je to doržování Single responsibility. Každá třída odpovídá za své. Může to být v jednom subscriberu (v případě shopu to tady asi i dává smysl). Ale třeba u toho mojeho města bude lepší udělat subscriber samostatný v nějakým tom UserModule, a pak třeba další, který bude checkovat jestli není přiřazen třeba u podniku, tak bude v nějakém CompanyModule

akadlec
Člen | 1326
+
0
-

Takže dejme tomu že mám 2 moduly – uživatele a podinky a pak jeden modul lokality, tak jak uvádíš. Chci smazat lokalitu a předtím než to udělám zavolám onPreDeleteCity a to mě zavolá dva eventsubscribery, jeden v uživatelích a jeden v podnicích a pokud jeden z nich neco nalezne tak mě to tam vyplní. Pochopil jsem dobře?…toto se mi líbím, aspoň trošku odstraním závislosti, co by se mě kryly do jednoho místa.

Jan Suchánek
Člen | 404
+
0
-

@Jiří Nápravník: Ok, myslel jsem nevytvářet button submit a nenavazovat na něj submit, nebo ho nechat disable, pak by to bylo řešitelné i u podvrženého url, ne?

Jiří Nápravník
Člen | 710
+
0
-

akadlec: ano takhle to chápu já, a takhel ot nejspíše udělám, protože tím budu moci udělat ty moduly na sobě nezávislé (resp. usermodul bude locationmodule potrebovat porad, ale ne uz obracene)

jenicek: asi jsme se nepochopili, ja to nemam pres nejaky formular, ale mazu ve vypisu, proste vypsano x mest pod sebou a delete jsou klasicke odkazy (chranene tedy secure routu, takze by to podvrzene url bylo stejne tezke odhadnout). No a proto skryvani tech odkazu by bylo casove naroce a neefektivni, protoze by se per odkaz muselo ptat na pocet v tech ostatnich tabulkach. No anebo tedy pouzit ten predpocitany „counter sloupec“

Jan Suchánek
Člen | 404
+
0
-

@Jiří Nápravník: Ok už chápu, ja na tohle hlídání používám mazací formulář z CD-collection.

Editoval jenicek (8. 4. 2014 16:55)

Filip Procházka
Moderator | 4668
+
0
-

Tak si dovolím tady taky jednu otázku na Kdyby\Events – snad se neztratí:-)

A proč jsi nezaložil vlastní téma? :)


Na použití eventů nevidím nic špatného, jelikož jde o dva různé moduly tak je to možná dokonce ideální.

moc se spoliham na to, ze mi nekdo treba omylem smaze ten foreign key v databazi a pak se mi smaze to co nema:-)

Memyslís že se moc spoléháš na to že budeš mít tabulky v databázi? Nebo že budeš mít kód na hostingu? Tohle je pěkně absurdní obava :)

Přidat si tam vzdálený klíč je výborný nápad který bude fungovat jako poslední záchrana kdybys měl chybu v aplikaci, tak aby si databáze udržela aspoň trochu konzistentní data.

akadlec
Člen | 1326
+
0
-

Hele pánové a jak pak vyřešit flash message? V presenteru/komponentě si zavolám $manager->delete($entity) kde se provede zmíněná kontrola zda je to ok nebo ne, ale co pak dělat když to ok není? Zmíněná výjimka byla označena jako nevhodná, takže co?

Filip Procházka
Moderator | 4668
+
0
-

Vyhazovat výjimky z eventů rozhodně nechceš. Tedy bych do toho eventu klidně předal nějaký validation objekt, na který půjdou přidávat errory. Podobně jako má formulář isValid a addError.

Jan Suchánek
Člen | 404
+
0
-

@akadlec: K těm zprávičkám, už jsem to zmínil, nemohl by se o to starat ten Subscriber?

@Filip Procházka: Ten validaton objekt by mohl být service, tedy předán z neonu?

Editoval jenicek (8. 4. 2014 19:00)

Filip Procházka
Moderator | 4668
+
0
-

Rozhodně ne service, protože pro každé zavolání potřebuješ novou instanci.

Jiří Nápravník
Člen | 710
+
0
-

Filip Procházka napsal(a):

A proč jsi nezaložil vlastní téma? :)

Sem se to celkem hodí a je to takové „support vlákno“ Kdyby\Events na foru:-)

Memyslís že se moc spoléháš na to že budeš mít tabulky v databázi? Nebo že budeš mít kód na hostingu? Tohle je pěkně absurdní obava :)

Přidat si tam vzdálený klíč je výborný nápad který bude fungovat jako poslední záchrana kdybys měl chybu v aplikaci, tak aby si databáze udržela aspoň trochu konzistentní data.

já jsem trochu paranoik no:-D ten cizi klic tam bude, protoze to jede na doctrine, ale furt si rikam, co kdyz prijde nejaky novy matlal, ktery zacne hrabat primo v databazi, ale ok nebudu paranoidni:-)

@jenicek neon ne, videl bych to na nově vytvoreny objekt pres new, ktery pošleš do subscriberu a vytáhneš si co to vratilo.

akadlec
Člen | 1326
+
0
-

@Filip Procházka: nn nemyslel sem vyhazovat výjimky z eventů ale přímo z manageru

class CityManager
{

    public function deleteCity($id)
    {
        $result = new CollisionResult();
        $this->onPreDeleteCity($id, $result);
        if(count($result->entries)) {
		throw new SomeExceptionName('Nemuzes mazat protoze XY');
        } else {
		$this->dao->delete($entity);
        }
    }
}
class CityPresenter
{
	handleDelete($id)
	{
		try {
			$this->manager->deleteCity($id);

		} catch (SomeExceptionName $e) {
			$this->flashMessage('Nemohl sem to smazat protoze Xy');
		} catch (NextSomeExceptionName $e) {
			$this->flashMessage('Nejaky jiny duvod proc to a ono');
		}
	}
}

Editoval akadlec (8. 4. 2014 19:29)

Filip Procházka
Moderator | 4668
+
0
-

Sem se to celkem hodí a je to takové „support vlákno“ Kdyby\Events na foru:-)

Nemám rád support vlákna, v momentě kdy mají víc než dvě stránky je v tom pak bordel ;)

akadlec napsal(a):

@Filip Procházka: nn nemyslel sem vyhazovat výjimky z eventů ale přímo z manageru

Tak to pak jo, na tom není vůbec nic špatného. Já to taky tak dělám.

akadlec
Člen | 1326
+
0
-

Takže je korektní si nadefinovat pár výjimek pro jednotlivé stavy, třeba UsersInUseException, CityNotEmptyException a ty pak zachytávat a podle nich vyhazovat flashky?

Filip Procházka
Moderator | 4668
+
0
-

Samozřejmě :)

Jiří Nápravník
Člen | 710
+
0
-

Btw jak moc ošklivé je, když si budoucí flasmessage hodím, do exception? Zkrátka řekněme ten příklad, že nemůžu smazat město, protože ho mají nastavené dva uživatelé. Jak to pak předat nejlépe uživateli?

  1. vrátím si objekt, který bude mít informaci o počtu těch uživatelů vytáhnu a nastavím flashmessage
  2. vyhodím výjimku, v které bude message Nelze smazat, město mají dva uživatelé. Odchytnu vyjímku a message nastavím na flashMessage

Někde jsem četl, že správně by se exception message neměli přehazovat k uživateli.

Nebo ještě nějak lépe?

Filip Procházka
Moderator | 4668
+
0
-

Rozhodně špatně, to nechceš. Ale co by šlo tak třeba tohle;

use Nette\Reflection\ClassType;

trait RenderableException
{
	public $count;
	public $parameters;
	public $domain = 'front';

	public function renderMessage(Kdyby\Translation\Translator $translator)
	{
		$message = ClassType::from($this)->getShortName() . '.' . $this->code;
		return $this->translator->translate($message, $this->count, $this->parameters, $this->domain);
	}
}

Nadefinuješ si výjimku

class CityRemovalException extends \RuntimeException
{
	const RELATION_RESTRICTION = 1;
	const LOCKED = 2;
	// ...

	use RenderableException;

	public function __construct($message, $code = 0, \Exception $previous = NULL)
	{
		parent::__construct($message, $code, $previous);
		$this->domain = 'city';
	}

	public static function relationRestriction(City $city)
	{
		return new static("Some relation is bound to " . $city->name . " and it cannot be deleted", self::RELATION_RESTRICTION);
	}

	// ...
}

vyhodíš

public function delete($city)
{
	if ($this->isReferenced($city)) {
		throw CityRemovalException::relationRestriction($city);
	}
}

chytneš a informuješ

try {
	$cityManager->remove($city);
	$this->flashMessage("ok");

} catch (CityRemovalException $e) {
	$this->flashMessage($e->renderMessage($this->getTranslator()));
}

Je to jenom koncept, musíš si to doladit :)