Magie okolo symfony/kdyby eventů
- Martk
- Člen | 661
Potřebuji použit eventy, ale nechce se mi do toho hlavně kvůli magii a nulovému napovídání IDE. Kdybych náhodou chtěl refactorovat kód eventů, tak je velká pravděpodobnost, že se kód rozbije. Další nevýhodou je, že nevím kde se daný event používá a volá. Jaké eventy se v aplikaci používají. Kdyby někdo náhodou znal odpovědi na moje odpovědi, budu rád, když se o to podělí.
Napadla mě taková myšlenka, která by víceméně moje problémy vyřešila, nevím jestli je realizovatelná.
- Vytvořila by se listener interface.
interface ISignInListener {
public function listenSignIn(User $user);
}
interface ISignOutListener {
public function listenSignOut(User $user);
}
- Vytvoří se listener/y implementující toto rozhraní
class Listener implements ISignInListener, ISignOutListener {
public function listenSignIn(User $user) {}
public function listenSignOut(User $user) {}
}
- V konfiguračním souboru by se registrovali pod sekci listeners (do služeb by se nemohli registrovat více níže):
listeners:
- Listener
- Automaticky by se třída Listener zaregistrovala jako služba bez autowire. Důvod je takový: vygenerovala by se automaticky nová třída/y, která by sloužila jako prostředník (odesílatel):
class SignInMediator implements ISignInListener {
/**
* @param ISignInListener[]
*/
public function __construct(array $listeners) {
$this->listeners = $listeners;
}
public function listenSignIn(User $user) {
foreach ($this->listeners as $listener) {
$listener->listenSignIn($user);
}
}
}
// to samé ISignOutListener
Tato třída by byla ovšem autowirovaná.
- Využivání eventů:
class SignIn {
public function __construct(ISignInListener $listener) {
// ...
}
public function login(User $user) {
$this->listener->listenSignIn($user);
}
}
Když budeme uchovávat interface listenerů na jednom místě, tak budeme
mít přehled o všech eventech.
Po rozkliknutí listenerů nám ide napoví, kde se listener využívá a jaké
třídy jej implementují.
Používá se na pozadí magie s mediátorem.
Je tohle realizovatelné, použivatelné nebo mám začít používat symfony events?
- Felix
- Nette Core | 1245
Zdravim.
Kdyby\Events nebo Contributte\EventDispatcher neni zase takova magie, jak se jevi na prvni pohled.
Defacto workflow je takove: v DIC se najdou vsechny sluzby, ktere
implementuji nejaky interface, konkretne
Symfony\Component\EventDispatcher\EventSubscriberInterface
a ten ma
jednu jedinou metodu, getSubscribedEvents
, ktera vraci pole
udalosti, na ktere posloucha.
Ty udalosti mohou byt takrka cokoli, napr. objednavka vytvorena, pri requestu, pri erroru nebo v tem pripade pri prihlaseni uzivatelem.
Napad s interfacem je super, take jsem to tak na zacatku delal. Tedka pouzivam Contributte\EventDispacher, ktery je tak tenky, jak jen to je mozne. Pridava navic jednu zasadni vec, vsechny subscriberi jsou volany az kdyz se vyhodi dany event, tzn lazy-loading.
Napovidani v IDE, jake povidani mas namysli? Normalne se pouziva, ze 1 subscriber posloucha na 1 eventu, ale muze i vice.
use Contributte\EventDispatcher\EventSubscriber;
use Contributte\Events\Bridges\Security\Event\LoggedInEvent;
use Contributte\Events\Bridges\Security\Event\SecurityEvents;
final class LoggedInSubscriber implements EventSubscriber
{
/**
* @return array
*/
public static function getSubscribedEvents()
{
return [SecurityEvents::ON_LOGGED_IN => 'onLoggedIn'];
}
/**
* @param LoggedInEvent $event
* @return void
*/
public function onLoggedIn(LoggedInEvent $event)
{
// do magic
}
}
Takhle muze vypadat jednoduchy subscriber na prihlasovani uzivatelu. Zase tolik kodu to neni, ze? :-)
Jak dohledat kolik je subscriberu v aplikaci je o neco tezsi, ale teoreticky, IDEA ti napovi, kolikrat je pouzita dana konstanta. Nebo muzes vyuzit primo nazev z eventu (https://github.com/…dInEvent.php#L14)
LoggedInEvent::NAME === SecurityEvents::ON_LOGGED_IN
Snad jsem ti odpovedel alespon na cast otazek.
Editoval Felix (8. 8. 2017 7:25)
- greeny
- Člen | 405
Ahoj, kdysi jsem si zkoušel napsat vlastní eventy tak aby se mi osobně s něma pracovalo dobře. Nejspíš to není úplně nejlepší řešení, ale pro inspiraci přikládám aspoň trochu jak jsem zamýšlel, že by se s nimi pracovalo:
<?php
// event
class OrderFinishedEvent implements IEvent // interface slouží pouze k identifikaci třídy jako event, nemá žádné metody
{
public function __construct(Order $order, User $user, $status) {
$this->order = $order;
$this->user = $user;
$this->status = $status;
}
// gettery
}
?>
<?php
// listener
class AnyClass
{
/**
* @EventListener
*/
public function onOrderFinished(OrderFinishedEvent $event) {
// ...
}
}
?>
No a v compile-time (compiler extension) se prošly všechny služby
v kontejneru a všechny jejich public metody, u každé se kontrolovala
existence @EventListener
anotace (ta případně mohla být i nad
třídou, v tom případě se prošly všechny metody a všechny co příjmali
argument typu IEvent, tak se zaregistrovaly). Všechno se to zaregistrovalo do
eventmanageru, kterej měl přístup ke kontejneru a mohl si z něj lazy
vytahovat služby a volat ty eventy. Pracovalo se mi s tím hodně dobře, dalo
by se to udělat i bez těch anotací (projdeš úplně všechno a zjistíš
jaký má ta metoda argumenty, pokud je to IEvent, tak to zaregistruješ)
Dá se tím jednoduše zjistit kde se danej event používá (usage třídy), jednoduše se kontrolujou povinné parametry eventu (konstruktor), přejmenování eventu ti IDE udělá přes refactor, atd.
Případně by se dalo udělat i $event->cancel()
či
nějaké priority eventů.
Editoval greeny (9. 8. 2017 12:29)
- h4kuna
- Backer | 740
Minulý týden, na toto taky vzešel dotaz. Vyřešilo to vlastní rozšíření.
- není potřeba anotace
- není potřeba interface
- není potřeba procházet všechny služby
- není event manager
Vyrobíš si třídu, která v konstruktoru příjme závislosti a v konstruktoru je spojí, tím třída končí. Stačí zaregistrovat jako službu rozšíření. Všechno se to spustí v Container::initialize()
Editoval h4kuna (19. 9. 2017 8:39)