Korišćenje SharedEventManager-a u Zend Framework 2
Autor: Nikola Poša |
Datum:
  • # Frameworks

Bez ikakve sumnje, EventManager  je ključna komponenta u Zend Framework-u 2, koja je zastupljena u praktično svim njegovim aspektima. Klasa koja koristi EventManager može da emituje odgovarajuće događaje (events), koje drugi objekti mogu da “slušaju” sa ciljem da izvrše neku operaciju kada se sâm događaj desi. Podrazumeva se da je čitalac upoznat sa namenom i osnovim konceptima EventManager-a, kao i samog Zend Framework-a, budući da će se ovaj članak baviti isključivo jednim svojstvom te komponente. Preporučujem da pre nego što nastavite, ako već niste, pročitate ovaj članak: An introduction to ZendEventManager, u kojem je dosta jasno objašnjena sama terminologija, kao i neki osnovni principi EventManager-a.

 

Zašto koristiti SharedEventManager?

 

Prilikom korišćenja EventManager-a, a i generalno u toku razvoja određenih event-aware  komponenti u aplikaciji, postojaće slučajevi kada je potrebno dodati listener za određeni event, a da pritom nije dostupna instanca određene EventManager-aware klase, ili bi kreiranje/dohvatanje njegove instance samo zbog potrebe da se prijavi listener za neki njen event bio nepotreban overhead. Takođe, mogu postojati zahtevi za reagovanjem na neki događaj na način da je potrebno targetirati same tipove klasa. Na primer ako želimo da reagujemo na event kojeg emituju Controller-i, Validator-i, Helper-i, bez obzira koja konkretna implementacija tih tipova klasa je u pitanju. Zend Framework ima rešenje u vidu SharedEventManager  koncepta.

 

Konkretan primer upotrebe

 

U našoj aplikaciji postoji sloj mapper-a, pri čemu imamo zahtev da log-ujemo svako čuvanje objekata. Bez obzira da li se radi o korisniku, post-u, komentaru ili bio kom drugom entitetu, treba da logujemo tu akciju perzistiranja bilo kojeg od tih entiteta. Kôd klasa ću maksimalno uprostiti, kako bi se fokusirali na detalje koji se tiču događaja.

Najpre, možemo imati jedan osnovni mapper, u kojem će biti logika zajednička za konkretne mapper-e, između ostalog i ona koja se tiče emitovanja događaja:

namespace Application\Mapper;

use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\EventManager;
use Application\Entity\BaseEntity;

abstract class AbstractMapper implements EventManagerAwareInterface
{
   protected $events;

   public function setEventManager(EventManagerInterface $events)
   {
       $events->setIdentifiers(array(
           'Mapper', //custom identifier
           __CLASS__,
           get_class($this),
       ));

       $this->events = $events;

       return $this;
   }

   public function getEventManager()
   {
       if (null === $this->events) {
           $this->setEventManager(new EventManager());
       }

       return $this->events;
   }

   protected function doSave(BaseEntity $entity)
   {
       $this->getEventManager()->trigger('save.pre', $this, array('entity' => $entity));

       //persistence logic...

       $this->getEventManager()->trigger('save.post', $this, array('entity' => $entity));
   }
}

… a zatim i dve konkretne mapper implementacije:

namespace Application\Mapper;

use Application\Entity\User;

class User extends AbstractMapper
{
   public function save(User $user)
   {
       $this->doSave($user);
   }
}
namespace Application\Mapper;

use Application\Entity\Post;

class Post extends AbstractMapper
{
   public function save(Post $post)
   {
       $this->doSave($post);
   }
}

Dakle imamo jedan osnovni event-aware mapper, koji je u skladu sa određenom ZF2 konvencijom, u kojoj klasa koja emituje događaje compose-uje instancu EventManager-a. Ono što je bitno jeste implementacija setEventManager() metoda, tačnije $events->setIdentifiers() poziv. U tom delu se konfigurišu EventManager instance naših mapper-a, pri čemu ih označavamo odgovarajućim identifikatorima (identifiers), i to onim fiksnim, po našem izboru ("Mapper"), ali i dinamičkim (__CLASS__ , get_class($this) ).

Sada nam preostaje da globalno prijavimo odgovarajući listener za logovanje mapper događaja, jer kao što smo rekli, imamo zahtev za logovanjem akcije perzistiranja entiteta, bez obzira na mapper. To ćemo postići tako što ćemo prijaviti listener uz pomoć  SharedEventManager-a, pri čemu mu prosleđujemo željeni identifikator događaja, a u našem slučaju, to je Mapper identifikator. Za potrebe ovog primera, odlučio sam da tu logiku smestim u samoj Module klasi našeg Application modula:

namespace Application;

use Zend\ModuleManager\Feature\BootstrapListenerInterface;
use Zend\EventManager\EventInterface as Event;
use Zend\Mvc\MvcEvent;
use Zend\Log\Factory as LogFactory;

class Module implements BootstrapListenerInterface
{
   public function onBootstrap(Event $e)
   {
       $app = $e->getParam('application');
       $em = $app->getEventManager();
       $sharedEventManager = $em->getSharedManager();

       $log = LogFactory(array(
           //some config
       ));

       $sharedEventManager->attach('Mapper', 'save.post', function ($e) use ($log) {
           $event = $e->getName();
           $mapperName = get_class($e->getTarget());

           $log->info(sprintf(
               '%s called in %s mapper',
               $event,
               $mapperName,
           ));
       });
   }
}

Hajde da prođemo kroz ovaj kôd… Naša Module klasa reaguje na bootstrap event, sa ciljem da tada podesimo našu logiku za logovanje. Pritom, primetite da SharedEventManager instancu dobavljam putem event manager-a same Application instance. To je sasvim regularno, s obzirom na činjenicu da su SharedEventManager instance deljene između svake EventManager instance u aplikaciji, što je veoma bitna, zapravo ključna odlika celog tog koncepta. Ako pogledate kôd Zend\EventManager\EventManager klase, videćete da ona ima određeno $sharedManager svojstvo, što je zapravo statička Zend\EventManager\SharedEventManager instanca, kojom manipuliše Zend\EventManager\StaticEventManager. Kada neka