Přejít k navigační liště

Zdroják » PHP » Symfony po krůčkách – Event Dispatcher

Symfony po krůčkách – Event Dispatcher

Články PHP

Dnes se spolu podíváme na EventDispatcher. Jde o komponentu, která dodá tvému kódu flexibilitu. Zároveň je jednou z nejdůležitějších součástek životního cyklu Symfony. Když pochopíš EventDispatcher, budeš zase o kousek blíž k tomu stát se opravdovým mistrem Symfony.

„Cesta dlouhá tisíc mil začíná prvním krokem.“                                                                                                                      Confucius

Hlavní pojmy

Event

…neboli událost. Jde o něco, co může nastat při běhu aplikace. Typickým příkladem je objednávka. Když dojde k odeslání objednávky, tak se zavolá Event. Na Event odeslání objednávky pak může slyšet několik EventSubscriberů.

EventSubscriber

…může poslat e-mail adminovi, přičíst kredity za úspěšný nákup, nebo poslat informační sms do skladu s pokynem k zabalení tvých vánočních dárků.

EventDispatcher

ten se stará o zavolání EventSubscriberů, když nastane určitý Event.

Kde můžeš EventDispatcher najít?

Co ti EventDispatcher umožní?

  • Dostat se na určité místo v kódu bez nutnosti jeho změny
  • Zvýšit flexibilitu a použitelnost tvé aplikace

Jak to aplikovat v kódu?

Symfony\EventDispatcher nainstaluješ pomocí Composeru:

$ composer require symfony/event-dispatcher

Vytvoříš si souborindex.php

<?php

require_once __DIR__ . '/vendor/autoload.php';

$eventDispatcher = new Symfony\Component\EventDispatcher\EventDispatcher;
// dispatchneme event někde v kódu na konci objednávky 
$eventDispatcher->dispatch('order.finish');

A spustíš:

$ php index.php

Dispatchneš Event, ale nic se nestane. Aby se něco stalo, bude potřeba ještě EventSubscriber – ten bude naslouchat na order.finish.

Přidáš tedy EventSubscriber.

<?php

class SendEmailToAdminEventSubscriber implements Symfony\Component\EventDispatcher\EventSubscriberInterface
{
   public $signal = 0;

   public static function getSubscribedEvents()
   {
      // tady budeme poslouchat "order.finish" event
      // a pokud nastane, použijeme metodu sendEmailToAdmin()
       return ['order.finish' => 'sendEmailToAdmin'];
   }

   public function sendEmailToAdmin()
   {
      // náš kód, který pošle e-mail adminovi
       $this->signal = 1;
   }
}

Nakonec přidáš EventSubscriber do EventDispatcheru:

<?php

$sendEmailToAdminEventSubscriber = new SendEmailToAdminEventSubscriber;

$eventDispatcher = new Symfony\Component\EventDispatcher\EventDispatcher;
$eventDispatcher->addSubscriber($sendEmailToAdminEventSubscriber);

var_dump($sendEmailToAdminEventSubscriber->signal);

$eventDispatcher->dispatch('order.finish');

var_dump($sendEmailToAdminEventSubscriber->signal);

A opět spustíš:

$ php index.php
int(0)
int(1)

Teď, když se ti dispatchne order.finish Event, zavolá se každý EventSubcriber, který se k němu zapsal. V něm se zavolá metoda, která je k němu přiřazena. Dojde tak ke změně $signal z 0 na 1.

Pro tip: Metoda getSubscribedEvents() může naslouchat více Eventům, více metodami. Může také určovat jejich pořadí.

Nyní už rozumíš Symfony komponentě EventDispatcher.

Event s argumenty

Při volání události obvykle potřebuješ předat i nějaká data. Například číslo objednávky. Taková třída Event je vlastně pouhý Value object – schránka na data.

<?php

class OrderEvent extends Symfony\Component\EventDispatcher\Event
{
   private $orderId;

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

   public function getOrderId()
   {
       return $this->orderId;
   }
}

Dispatchneš event i s potřebnými daty.

<?php

$orderEvent = new OrderEvent(123);
$eventDispatcher->dispatch('order.finish', $orderEvent);

Rozšíříš EventSubscriber o OrderEvent:

<?php

class SendEmailToAdminEventSubscriber
    implements Symfony\Component\EventDispatcher\EventSubscriberInterface
{
   public $signal = 0;

   public static function getSubscribedEvents()
   {
       return ['order.finish' => 'sendEmailToAdmin'];
   }

   public function sendEmailToAdmin(OrderEvent $orderEvent)
   {
       $this->signal = $orderEvent->getOrderId();
   }
}

A doplníš svůj výsledný kód:

$eventDispatcher = new Symfony\Component\EventDispatcher\EventDispatcher;
$sendEmailToAdminEventSubscriber = new SendEmailToAdminEventSubscriber;
$eventDispatcher->addSubscriber($sendEmailToAdminEventSubscriber);

var_dump($sendEmailToAdminEventSubscriber->signal);

$orderEvent = new OrderEvent(123);
$eventDispatcher->dispatch('order.finish', $orderEvent);

var_dump($sendEmailToAdminEventSubscriber->signal);

Výstup pak vypadá takto:

$  event-dispatcher  php index.php
int(0)
int(123)

 

Jsi zase o krok dál

Teď už:

  • rozumíš základním workflow událostí
  • znáš pojmy Event, EventSubscriber a EventDispatcher
  • víš, k čemu využít vlastní Event objekt
  • …a umíš použít EventDispatcher prakticky ve svém kódu

 

Potřebuješ víc?

Pokud bys potřeboval jít ve vysvětlování do větší hloubky, mrkni na oficiální dokumentaci EventDispatcheru.

Už v Symfony děláš léta a chceš posdílet zkušenosti?

Nebo tě Symfony zatím jen láká a rád by se o něm dozvěděl více? Přijd si pokecat na pravidelné měsíční srazy v Praze a Brně.

Komentáře

Subscribe
Upozornit na
guest
15 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Martin Zeman

Díky za článek!
Jen by mě zajímalo, proč jsi začal zrovna s eventy?

Martin Zeman

Super super, děkuji :)

lenoch

Proč se to nedělá jednodušeji? Např. nějak podobně jako v Nette:

$order->onFinish = function() { sendMailToAdmin(); }
HonzaMarek

1) Nemusím do kódu orderu přidávat onFinish. 2) V momentě dispatchování akce nemusím mít vytvořený objekt $order.

lenoch

Ad 1) No ale musím v kódu orderu zavolat $eventDispatcher->dispatch(‚order.finish‘); ne? Tedy pokud tomu správně rozumím.
Ad 2) V momentě dispatchování snad order vytvořený být musí? Nemusí být vytvořený v momentě subscribování, to ano, v tom je rozdíl.

Já
  1. Ano, stejne jako se v nette vola onFinish…
  2. Callback v nette/object je mozna jednodussi, ale je omezen presne jen na nette/object. V dispatcheru muzete odesilat co chcete kdy chcete, klidne jen hole eventy pred vytvorenim nebo po smazani objednavky…
lenoch
  1. Ano, to byla reakce na Honzu Marka, tím jsem chtěl říct, že v tomhle nevidím rozdíl.
  2. Ano, tady je volaní/nastavování eventů centralizované v nějakém globálním? objektu. Jen nevím jestli eventuální výhody v praxi opodstatní tu větší složitost.

Mě totiž symfony obecně připadá (podobně jako jiné fw) zbytečně složité a komplikované (což může být můj problém), takže jsem na tomhle případu zajímal, co mi ta větší komplexita přinese.

Já
  1. v případě $order rozdíl není – osobně ale nejčastěji (a předpokládám většina programátorů) pracuji s eventama z doctrine nebo přimo ze symfony – tedy z cizího kódu
  2. je to service v di. Callback řeší „události“ objektu, který podědil nette/object, zato EventDispatcher řeší jakékoliv události v aplikaci (a předává v Eventu jakákoliv data). Je to něco jiného, nevím jestli se dá vůbec v nette najít něco srovnatelného…
lenoch

Hm, no tak v případě symfony musí ten objekt odněkud získat objekt EventDispatcher a použít ho, v případě Nette se musí podědit z Nette/Object. Je to trochu jiný typ závislosti, ale zásadní rozdíl v tom aspoň já nevidím. Jakékoliv události lze v Nette obsloužit taky, jakákoliv data lze předat v eventu taky. Ale ok, už toho nechme.

lenoch

Ok, tento důvod chápu – pokud mám spoustu kódu třetích stran, který využívá symfony EventDispatcher, tak pochopitelně musím používat EventDispatcher, když s ním pracuji.

lenoch

Tak je pravda, že Nette řešení využívá určitou magii rozšiřující vlastnosti php, ale přiřazení callbacku do onFinish a pak volání $this->onFinish($parametry) ve třídě mi připadá značně intuitivní a v řadě jazyků (javascript) lze něco podobného udělat implicitně.

Ale budiž, co je čitelné a srozumitelné je subjektivní věc, nechci se přit.

Já osobně vidím největší potenciální výhodu toho symfony systému v tom, (už to někdo zmiňoval), že ta obsluha výjjimek je mimo využívající objekt, který ji pouze odněkud převezme a pak použije a teoreticky mu můžu například dodat i nějakou jinou implementaci EventDispatcheru.

Takže shrnuto vidím to tak, že symfony je víc „decoupled“ a potenciálně o něco flexibilnější, nette je (alespoň pro mě) v tomhle jednodušší a stručnější.

Dvojak

Ja tomu asi moc nerozumim. Objekt subscriberu je do dispatcheru pridavan ve chvili, kdy je event „vyhozen“. Ale v tu chvili preci nemuze programator vedet, jake vsechny subscibery budou v budoucnu event odchytavat a novy programator, ktery subsciber vytvari, pak naopak (pravdepodobne) nemuze upravovat misto, kde byl event dispatchnut.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.

Pocta C64

Za prvopočátek své programátorské kariéry vděčím počítači Commodore 64. Tehdy jsem genialitu návrhu nemohl docenit. Dnes dokážu lehce nahlédnout pod pokličku. Chtěl bych se o to s vámi podělit a vzdát mu hold.