title: ZF2 - How to Listen for Events and Trigger a Service because of it?
body: <p>Have been trying to learn how to implement Services because they get Triggered by a Listener. <a href="https://framework.zend.com/manual/2.4/en/in-depth-guide/services-and-servicemanager.html" rel="nofollow noreferrer">Have</a> <a href="https://framework.zend.com/manual/2.4/en/modules/zend.event-manager.event-manager.html" rel="nofollow noreferrer">been</a> <a href="https://samsonasik.wordpress.com/2013/05/18/zend-framework-2-register-event-listeners-in-configuration-file/" rel="nofollow noreferrer">doing</a> <a href="http://www.michaelgallego.fr/blog/2013/05/12/understanding-the-zend-framework-2-event-manager/" rel="nofollow noreferrer">a</a> <a href="https://samsonasik.wordpress.com/2013/03/30/zend-framework-2-getting-closer-with-eventmanager/" rel="nofollow noreferrer">serious</a> <a href="https://stackoverflow.com/questions/24897470/listeners-automatically-being-attached-when-using-listeners-config-key">lot</a> <a href="http://resoftsol.com/events-the-eventmanager-and-listeneraggregates-in-zend-framework-2/" rel="nofollow noreferrer">of</a> <a href="https://stackoverflow.com/questions/13978748/zf2-how-to-attach-listener-on-the-event-in-the-module-class">reading</a> <a href="https://stackoverflow.com/questions/28524590/zf2-how-to-listen-to-the-dispatch-event-of-a-specific-controller">the</a> <a href="https://stackoverflow.com/questions/30794259/how-to-trigger-an-event-attached-to-the-shared-event-manager-in-zf2">last</a> few days to get it to work, but have been finding it difficult. Thus I'm thinking my understanding of the order of things might be flawed.</p>
  
  <blockquote>
    <p><strong>The use case I'm trying to get to work is the following</strong>:</p>
    
    <p>Just before an Address Entity (with Doctrine, but that's not
    important) gets saved (flushed), a Service must be triggered to check
    if the Coordinates for the Address are set, and if not, create and
    fill a new Coordinates Entity and link it to the Address. The
    Coordinates are to be gotten from Google Maps Geocoding API.</p>
  </blockquote>
  
  <p>Will show below what and how I'm understanding things in the hope I make myself clear. Will do it in steps to show added code in between and tell you what does and doesn't work, as far as I know.</p>
  
  <p>Now, my understanding of all of the information I've gotten the last few days is this:</p>
  
  <p>A Listener has to be registered with ZF2's ServiceManager. The listener "attaches" certain conditions to the (Shared)EventManager. An EventManager is unique to an object, but the SharedEventManager is 'global' in the application. </p>
  
  <p>In the Address module's <code>Module.php</code> class I've added the following function:</p>
  
  <pre><code>/**
   * @param EventInterface $e
   */
  public function onBootstrap(EventInterface $e)
  {
      $eventManager = $e-&gt;getTarget()-&gt;getEventManager();
      $eventManager-&gt;attach(new AddressListener());
  }
  </code></pre>
  
  <p>This gets works, the AddressListener gets triggered.</p>
  
  <p>The AddressListener is as follows:</p>
  
  <pre><code>use Address\Entity\Address;
  use Address\Service\GoogleCoordinatesService;
  use Zend\EventManager\EventManagerInterface;
  use Zend\EventManager\ListenerAggregateInterface;
  use Zend\Stdlib\CallbackHandler;
  
  class AddressListener implements ListenerAggregateInterface
  {
      /**
       * @var CallbackHandler
       */
      protected $listeners;
  
      /**
       * @param EventManagerInterface $events
       */
      public function attach(EventManagerInterface $events)
      {
          $sharedEvents = $events-&gt;getSharedManager();
  
          // Not sure how and what order params should be. The ListenerAggregateInterface docblocks didn't help me a lot with that either, as did the official ZF2 docs. So, been trying a few things...
  
          $this-&gt;listeners[] = $sharedEvents-&gt;attach(GoogleCoordinatesService::class, 'getCoordinates', [$this, 'addressCreated'], 100);
  
          $this-&gt;listeners[] = $sharedEvents-&gt;attach(Address::class, 'entity.preFlush', [GoogleCoordinatesService::class, 'getCoordinates'], 100);
      }
  
      /**
       * @param EventManagerInterface $events
       */
      public function detach(EventManagerInterface $events)
      {
          foreach ($this-&gt;listeners as $index =&gt; $listener) {
              if ($events-&gt;detach($listener)) {
                  unset($this-&gt;listeners[$index]);
              }
          }
      }
  
      public function addressCreated()
      {
          $foo = 'bar'; // This line is here to as debug break. Line is never used...
      }
  }
  </code></pre>
  
  <p>I was expecting a Listener to work as a sort-of stepping stone point to where things get triggered, based on the <code>-&gt;attach()</code> functions in the <code>function attach(...){}</code>. However, this does not seem to work, as nothing gets triggered. Not the <code>addressCreated()</code> function and not the <code>getCoordinates</code> function in the <code>GoogleCoordinatesService</code>. </p>
  
  <p>The code above is supposed to trigger the <code>GoogleCoordinatesService</code> function <code>getCoordinates</code>. The Service has a few requirements though, such as the presence of the EntityManager of Doctrine, the Address Entity it concerns and configuration. </p>
  
  <p>To that effect, I've created the following configuration. </p>
  
  <p>File <code>google.config.php</code> (gets loaded, checked that)</p>
  
  <pre><code>return [
      'google' =&gt; [
          'services' =&gt; [
              'maps' =&gt; [
                  'services' =&gt; [
                      'geocoding' =&gt; [
                          'api_url' =&gt; 'https://maps.googleapis.com/maps/api/geocode/json?',
                          'api_key' =&gt; '',
                          'url_params' =&gt; [
                              'required' =&gt; [
                                  'address',
                              ],
                              'optional' =&gt; [
                                  'key'
                              ],
                          ],
                      ],
                  ],
              ],
          ],
      ],
  ];
  </code></pre>
  
  <p>And in <code>module.config.php</code> I've registered the Service with a Factory</p>
  
  <pre><code>'service_manager' =&gt; [
      'factories' =&gt; [
          GoogleCoordinatesService::class =&gt; GoogleCoordinatesServiceFactory::class,
      ],
  ],
  </code></pre>
  
  <p>The Factory is pretty standard ZF2 stuff, but to paint a complete picture, here is the <code>GoogleCoordinatesServiceFactory.php</code> class. (Removed comments/typehints/etc)</p>
  
  <pre><code>class GoogleCoordinatesServiceFactory implements FactoryInterface
  {
      public function createService(ServiceLocatorInterface $serviceLocator, $options = [])
      {
          $serviceManager = $serviceLocator-&gt;getServiceLocator();
          $entityManager = $serviceManager-&gt;get(EntityManager::class);
          $config = $serviceManager-&gt;get('Config');
  
          if (isset($options) &amp;&amp; isset($options['address'])) {
              $address = $options['address'];
          } else {
              throw new InvalidArgumentException('Must provide an Address Entity.');
          }
  
          return new GoogleCoordinatesService(
              $entityManager,
              $config,
              $address
          );
      }
  }
  </code></pre>
  
  <p>Below is the <code>GoogleCoordinatesService</code> class. However, nothing ever gets triggered to executed in there. As it doesn't even gets called I'm sure the problem lies in the code above, but cannot find out why. From what I've read and tried, I'm expecting that the class itself should get called, via the Factory and the <code>getCoordinates</code> function should be triggered. </p>
  
  <p>So, the class. I've removed a bunch of standard getters/setters, comments, docblocks and typehints to make it shorter.</p>
  
  <pre><code>class GoogleCoordinatesService implements EventManagerAwareInterface
  {
      protected $eventManager;
      protected $entityManager;
      protected $config;
      protected $address;
  
      /**
       * GoogleCoordinatesServices constructor.
       * @param EntityManager $entityManager
       * @param Config|array $config
       * @param Address $address
       * @throws InvalidParamNameException
       */
      public function __construct(EntityManager $entityManager, $config, Address $address)
      {    
          $this-&gt;config = $config;
          $this-&gt;address = $address;
          $this-&gt;entityManager = $entityManager;
      }
  
      public function getCoordinates()
      {
          $url = $this-&gt;getConfig()['api_url'] . 'address=' . $this-&gt;urlFormatAddress($this-&gt;getAddress());
  
          $response = json_decode(file_get_contents($url), true);
  
          if ($response['status'] == 'OK') {
              $coordinates = new Coordinates();
              $coordinates
                  -&gt;setLatitude($response['results'][0]['geometry']['location']['lat'])
                  -&gt;setLongitude($response['results'][0]['geometry']['location']['lng']);
  
              $this-&gt;getEntityManager()-&gt;persist($coordinates);
  
              $this-&gt;getAddress()-&gt;setCoordinates($coordinates);
              $this-&gt;getEntityManager()-&gt;persist($this-&gt;getAddress());
  
              $this-&gt;getEntityManager()-&gt;flush();
  
              $this-&gt;getEventManager()-&gt;trigger(
                  'addressReceivedCoordinates',
                  null,
                  ['address' =&gt; $this-&gt;getAddress()]
              );
          } else {
              // TODO throw/set error/status
          }
      }
  
      public function urlFormatAddress(Address $address)
      {
          $string = // format the address into a string
  
          return urlencode($string);
      }
  
      public function getEventManager()
      {
          if ($this-&gt;eventManager === null) {
              $this-&gt;setEventManager(new EventManager());
          }
  
          return $this-&gt;eventManager;
      }
  
      public function setEventManager(EventManagerInterface $eventManager)
      {
          $eventManager-&gt;addIdentifiers([
              __CLASS__,
              get_called_class()
          ]);
  
          $this-&gt;eventManager = $eventManager;
          return $this;
      }
  
      // Getters/Setters for EntityManager, Config and Address
  }
  </code></pre>
  
  <p>So, that's the setup to handle it when a certain event gets triggered. Now it should, of course, get triggered. For this use case I've setup a trigger in the <code>AbstractActionController</code> of my own (extends ZF2's <code>AbstractActionController</code>). Doing that like so:</p>
  
  <pre><code>if ($form-&gt;isValid()) {
      $entity = $form-&gt;getObject();
      $this-&gt;getEntityManager()-&gt;persist($entity);
  
      try {
          // Trigger preFlush event, pass along Entity. Other Listeners can subscribe to this name.
          $this-&gt;getEventManager()-&gt;trigger(
              'entity.preFlush',
              null,
              [get_class($entity) =&gt; $entity] // key = "Address\Entity\Address" for use case
          );
  
          $this-&gt;getEntityManager()-&gt;flush();
      } catch (\Exception $e) {
          // Error thrown
      }
      // Success stuff, like a trigger "entity.postFlush"
  }
  </code></pre>
  
  <p>So yea. At the moment at a bit of a loss on how to get it working. </p>
  
  <p>Any help would be very much appreciated and would love explanations as to the "why" of it is that a solution works. That would really help me out making more of these services :)</p>