Magie okolo symfony/kdyby eventů

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

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á.

  1. Vytvořila by se listener interface.
interface ISignInListener {

	public function listenSignIn(User $user);

}
interface ISignOutListener {

	public function listenSignOut(User $user);

}
  1. Vytvoří se listener/y implementující toto rozhraní
class Listener implements ISignInListener, ISignOutListener {

	public function listenSignIn(User $user) {}

	public function listenSignOut(User $user) {}

}
  1. V konfiguračním souboru by se registrovali pod sekci listeners (do služeb by se nemohli registrovat více níže):
listeners:
	- Listener
  1. 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á.

  1. 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
+
+2
-

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)

Martk
Člen | 661
+
+1
-

@Felix Díky za opověď. Objasnilo mi to problém se symfony/events.

CZechBoY
Člen | 3608
+
0
-

Mně by se líbilo, kdyby zmizla ta magie s callbackem… Zadávat jméno metody je divný. Kdyby to byl callback typu [$this, 'onLoggedIn'] tak by tomu aspoň IDEčko rozumělo.

greeny
Člen | 405
+
0
-

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
+
+1
-

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)