Robert Basic's blog

Posts tagged 'events'

Events in a Zend Expressive application

by Robert Basic on August 04, 2016.

Three weeks ago I wrote a post on how to utilize Tactician in a Zend Expressive application. Today I want to expand on that post a little by adding the possibility to trigger and listen to events using the Zend EventManager component.

Using events allows our application to respond to different events that occur during a request. For example, when a user registers a new account, our application can trigger an event, UserRegistered, that can let the rest of our application know when a new registration happens. With the help of the EventManager we attach listeners to that event. When the UserRegistered event is triggered, the event manager will invoke all the listeners that are listening to that particular event.

This allows for a better separation of concerns in some cases, because if we take this approach, our code that deals with registering new users doesn’t care any more what happens after that — do we send out a welcoming email to the user, a notification to the site admins, create a log entry somewhere… It just registers a new account, triggers the event and that’s it.

Let’s see some code

As I mentioned earlier, this post expands on my previous post, so all we are going to add is an event that gets triggered when the Ping command is handled, an event listener that listens to that event and wire it all together with the event manager.

Let’s include the Zend EventManager in our project with composer:

$ composer require zendframework/zend-eventmanager

Next we expand the factory for the Ping command handler. We create an EventManager object that we will pass to the Ping command handler, so that we can trigger events:

src/App/CommandHandler/PingFactory.php

diff --git a/src/App/CommandHandler/PingFactory.php b/src/App/CommandHandler/PingFactory.php
index e995d1a..0737631 100644
--- a/src/App/CommandHandler/PingFactory.php
+++ b/src/App/CommandHandler/PingFactory.php
@@ -3,6 +3,7 @@
 namespace App\CommandHandler;
 
 use Interop\Container\ContainerInterface;
+use Zend\EventManager\EventManager;
 
 class PingFactory
 {
@@ -10,6 +11,11 @@ class PingFactory
     {
         $logPath = '/tmp/ping-command.log';
 
-        return new Ping($logPath);
+        $events = new EventManager();
+        $events->setIdentifiers([
+            Ping::class
+        ]);
+
+        return new Ping($logPath, $events);
     }
}

Later on we will use the same event manager to attach event listeners to our events.

In the Ping command handler we use the event manager, that we pass in as a constructor argument from the ping command handler factory, to trigger events:

src/App/CommandHandler/Ping.php

diff --git a/src/App/CommandHandler/Ping.php b/src/App/CommandHandler/Ping.php
index 538e3af..9768738 100644
--- a/src/App/CommandHandler/Ping.php
+++ b/src/App/CommandHandler/Ping.php
@@ -2,15 +2,19 @@
 
 namespace App\CommandHandler;
 
+use Zend\EventManager\EventManagerInterface;
 use App\Command\Ping as PingCommand;
 
 class Ping
 {
     private $logPath;
 
-    public function __construct($logPath)
+    private $events;
+
+    public function __construct($logPath, EventManagerInterface $events)
     {
         $this->logPath = $logPath;
+        $this->events = $events;
     }
 
     public function __invoke(PingCommand $pingCommand)
@@ -18,5 +22,12 @@ class Ping
         $commandTime = $pingCommand->getCommandTime();
 
         file_put_contents($this->logPath, $commandTime . PHP_EOL, FILE_APPEND);
+
+        $params = [
+            'command_time' => $commandTime,
+            'event_time' => time(),
+        ];
+
+        $this->events->trigger('ping_command_handled', $this, $params);
     }
}

The main part is the call to the trigger method on the event manager. The first argument, ping_command_handled, is the event’s name. We will use that event name to attach to it later. The second argument is the target of the event and usually it’s the object instance that triggers the event. Finally with the third argument we can send out optional parameters with the event which we can access in our event listeners.

Even though we have no listeners attached to the event, our application will continue to work perfectly fine, because an event is not required to have any listeners. It doesn’t make much sense to have events without listeners, but it wouldn’t break our application.

Shhh… Listen!

The simplest way to attach a listener to the event would be to tell the event manager to call a callable every time the event is triggered. This would also mean we need to set up any and all dependencies our event listeners have, at the moment of attaching the listener to the event, even though the event can end up not being triggered at all. All that dependency set up can be costly and wasteful.

Zend EventManager comes with lazy listeners that allows to fetch event listeners from a container-interop compatible container. This lets us to set up the dependency graph for an event listener in a factory, which will be invoked by the lazy listener only when the event we are listening to is triggered. If the event is triggered, the lazy listener will fetch the event listener from the container along with it’s dependencies, but if the event is not triggered, nothing will happen. Super useful!

Let’s attach our event listener to the event using the lazy listener:

src/App/CommandHandler/PingFactory.php

diff --git a/src/App/CommandHandler/PingFactory.php b/src/App/CommandHandler/PingFactory.php
index 0737631..5ce9c7e 100644
--- a/src/App/CommandHandler/PingFactory.php
+++ b/src/App/CommandHandler/PingFactory.php
@@ -4,6 +4,7 @@ namespace App\CommandHandler;
 
 use Interop\Container\ContainerInterface;
 use Zend\EventManager\EventManager;
+use Zend\EventManager\LazyListener;
+use App\EventListener\Ping as PingEventListener;
 
 class PingFactory
 {
@@ -16,6 +17,13 @@ class PingFactory
             Ping::class
         ]);
 
+        $lazyListener = new LazyListener([
+            'listener' => PingEventListener::class,
+            'method' => 'onPingCommandHandled',
+        ], $container);
+
+        $events->attach('ping_command_handled', $lazyListener);
+
         return new Ping($logPath, $events);
     }
}

We create a new LazyListener object and as the first argument to it we pass an array with the actual event listener and method that will be called when the event is triggered. The second argument is the container-interop compatible container, which knows how to build our event listener.

After that we attach the lazy listener to the events we are interested in, in this case the event called ping_command_handled. Once that event gets triggered, the lazy listener will get our PingEventListener from the container and call the onPingCommandHandled method on it.

Let’s quickly tell the container how to create our event listener:

config/autoload/dependencies.global.php

diff --git a/config/autoload/dependencies.global.php b/config/autoload/dependencies.global.php
index 794304e..cf47c99 100644
--- a/config/autoload/dependencies.global.php
+++ b/config/autoload/dependencies.global.php
@@ -21,6 +21,7 @@ return [
             Helper\UrlHelper::class => Helper\UrlHelperFactory::class,
             'CommandBus' => App\CommandBusFactory::class,
             App\CommandHandler\Ping::class => App\CommandHandler\PingFactory::class,
+            App\EventListener\Ping::class => App\EventListener\Ping::class,
         ],
     ],
];

And finally our event listener looks something like this:

src/App/EventListener/Ping.php

<?php
namespace App\EventListener;
use Interop\Container\ContainerInterface;
use Zend\EventManager\Event;
class Ping
{
    public function __invoke(ContainerInterface $container)
    {
        // Grab some dependencies from the $container
        // And return self
        return new self();
    }

    public function onPingCommandHandled(Event $event)
    {
        // Do something with the $event here
        $name = $event->getName();
        $target = $event->getTarget();
        $params = $event->getParams();
    }
}

When the container grabs our event listener the __invoke method will be invoked at which point we can grab the event listener’s dependencies from the container. Once the event listener object is created, the onPingCommandHandled method will be called by the event manager when the event is triggered.

Happy hackin’!

Tags: container, event listener, event manager, events, php, zend expressive, zf.
Categories: Development, Programming.

Need help on your PHP projects? Let's talk!

Creating a chat bot with PHP and Dbus

by Robert Basic on January 08, 2012.
Heads-up! You're reading an old post and the information in it is quite probably outdated.

Now that we know how to use DBus to communicate with Pidgin from PHP and how to listen to DBus signals, it’s time to put it all together by creating a simple chat bot! Nothing fancy, just a simple script that runs somewhere on some server and, by using a Pidgin account, can respond to some basic queries we send it.

What did we get?

As we want our script to receive messages from an other account, first we need to listen to the ReceivedImMsg event on the im.pidgin.purple.PurpleInterface interface. The data we get with that event is the ID of receiver’s account, the sender of the message, the actual message and the conversation’s ID (and some flags which we’re not interested in):

<?php
$interface = "im.pidgin.purple.PurpleInterface";
$method = "ReceivedImMsg";
if ($signal->matches($interface, $method)) {
    $data = $signal->getData()->getData();
    $receiver = $data[0];
    $sender = $data[1];
    $message = $data[2];
    $conversation = $data[3];
}

Of course, this is only for this one event, for data associated with other events see Pidgin’s manual.

Who's there?

The event we are listening for will fire for any and all accounts, no matter who is the sender or the receiver of the message. We need to make sure that the receiving and the sending accounts are the correct ones, that the receiver is connected and that the receiver and the sender are contacts, “buddies”:

<?php
if ($receiver == 2034 && $proxy->PurpleAccountIsConnected($receiver)
    && $proxy->PurpleFindBuddy($receiver, $sender) == 3681) {

The numbers 2034 and 3681 are the account IDs for my accounts I used in this example; you’ll need to figure out yours.

Sending a response

Now that we know who’s talking to whom, we can act upon the received message, do something with it, create a response message and send it back! The data we got with the event, has the ID of the conversation between the two accounts. We create a new instant message for that conversation and send it on it’s merry way with our clever response message:

<?php
$im = $proxy->PurpleConvIm($conversation);
$proxy->PurpleConvImSend($im, $responseMessage);

As for what action the script can take upon a new message, is really up to the developer: it can do simple stuff like sending back the current uptime of the server or the current IP, running other tools like usher and sending that result back, or whatever is necessary.

Daemonizing

As this little bot is supposed to run on some server, the easiest way to run it as a “daemon” is to put the script in a background job via nohup:

$ nohup php chat.php &

If needed, creating daemons in PHP can be done too.

And that’s about all what’s needed to create a chat bot. See a complete example here on Github.

As for, is PHP the right tool for creating this kind of thing, I don’t know, maybe, maybe not. What I do know, is that I had fun writing all this and I learned a lot along the way :)

Happy hackin’!

Tags: bot, dbus, events, php, pidgin, signals.
Categories: Development, Programming.

Need help on your PHP projects? Let's talk!

Listening to Dbus signals with PHP

by Robert Basic on December 26, 2011.
Heads-up! You're reading an old post and the information in it is quite probably outdated.

In my previous post I described (tried, at least) how to communicate with Pidgin from PHP, by using the Dbus PHP extension.

The good part is that not can we only call different methods against Pidgin’s libpurple API, we can also listen to different signals on different events, that are sent via Dbus. Some of the events that are signalled are when a chat message is recieved, a friend comes online, a file is sent, or any other from a list of some 110 different events.

The PHP Dbus extension allows us to watch for one exact signal on an interface, or for all signals on an interface. Of course, we can add watches on multiple interfaces at once.

Watching for signals

Once we know the interface and/or the specific signal we’re interested in, we can add a watch on it. This is done by calling the addWatch method on the Dbus object, were the first parameter is the interface, and the second, optional parameter is the exact signal we want to listen to.

<?php

$dbus = new Dbus(Dbus::BUS_SESSION);

// watching for a specific signal
$dbus->addWatch("im.pidgin.purple.PurpleInterface", "ReceivedImMsg");
// or watching on an entire interface
// $dbus->addWatch("im.pidgin.purple.PurpleInterface");
// also can listen to different interfaces at the same time
$dbus->addWatch("org.freedesktop.Hal.Device");

Next, we need a way to actually get these signals when the events occur. For this we are using the waitLoop method of the Dbus class. That method accepts a number as a parameter, which is the number of miliseconds it should wait between requests. If an event happened on the interface we’re watching, it will return the signal, which is a DbusSignal; otherwise we’ll get a null:

<?php
do {
    $signal = $dbus->waitLoop(1000);

    if ($signal instanceof DbusSignal) {
        // even if we watch only for one signal on one interface
        // we still can get rubbish, so making sure this is what we need
        if($signal->matches("im.pidgin.purple.PurpleInterface","ReceivedImMsg")){
            // data is in this weird DbusSet object thingy
            $data = $signal->getData()->getData();
            echo "Got stuff!\n";
        }
    }
} while (true);

Once we got the signal, to make sure that the signal is really the one we’re interested in, we call the matches method on it. The first parameter is the interface and the second is the signal.

Each event has (can have? not sure yet) additional data associated with it. To get to it, for some odd reason, we need to call getData()->getData() on the signal; note that this is in case of listening on libpurple’s interfaces, not sure about others. Experiment. Also, what kind of data is returned, again, depends on the interface and/or the event - some return arrays, some strings.

Have a look at the Github repo for some more examples (the dbus-signals* files).

In the third, and probably last post in this dbus mini-series, I’ll try to build a bot with which I can communicate and issue out commands to.

Happy hackin’!

Tags: dbus, events, listen, php, pidgin, signals, xmpp.
Categories: Development, Programming.

Need help on your PHP projects? Let's talk!

A Zend Framework 2 EventManager use case

by Robert Basic on October 19, 2011.
Heads-up! You're reading an old post and the information in it is quite probably outdated.

With Zend Framework 2 beta 1 released yesterday and some free time to spare, I decided to finally try and tackle one of the “scariest” additions to the Zend Framework - the EventManager component. Now, I won’t go into details about this whole event voodoo, Matthew already did that. Twice.

Basically, this allows us to have one piece of code to trigger an event and to have one or more listeners listening to this event. When the event gets triggered, the listeners are called and then we can do something, like caching or logging. Logging or caching. Caching. Logging…

See, that’s my problem. All the event examples stop at logging and caching. Truly there must be some other example for which this event stuff can be used for. (Yes, I know. The whole dispatch process is now event driven or whatnot in ZF2, but I need event examples in my application, not in the framework.) I don’t claim I found a perfect example for the events, but I tried.

The problem

One of the most “repetitive” places in my code I found is the save method in my models. Pass an array of data to it, possibly do something with that data, validate it, persist it, maybe do some more data mangling, return true/false to the caller. Over and over again, but just with enough difference between different models that there is actually no double code to pull out to an abstract class or some such.

Say, for example, we have a Post of some sort. It has a title and a slug created from the title. Standard stuff, nothing fancy. For the Post to be valid, it needs to have both the title and the slug set.

Now, without the EventManager, the save method could have a similar flow:

  • call the save method, passing in the data array
  • check if the data array has a slug set, if not, create one from the title
  • validate the data array, to make sure both title and slug are properly set
  • save the post

As I said, pretty standard stuff, so I’ll assume you can imagine that piece of code in your head (read, I’m lazy to write it). The problem: the save method is stuffed with data preparing and validation code.

Using the EventManager

This is where, I hope, the EventManager can help. Call the save method and, just before the persist call, trigger a “pre-save” event and then persist the data. Attach two listeners to this “pre-save” event; the first will do the data preparation, the second will do the validation. There, done. Now the save method doesn’t have that unneeded code around it and can be pulled out to an abstract class, all that is in the event listeners.

Let’s see some code:

<?php

// This is the Post object

class Post
{
    protected $events = null;

    public function events()
    {
        if ($this->events === null) {
            $this->events = new Zend\EventManager\EventManager(__CLASS__);

            $this->events->attach('save', array('SlugifyPost', 'slugify'), 100);
            $this->events->attach('save', array('ValidatePost', 'validate'), 90);
        }

        return $this->events;
    }

    // this method can be pulled out to an abstract model class
    // and reuse it for all the models that extend it
    public function save($data)
    {
        $this->events()->prepareArgs($data);
        $response = $this->events()->trigger('save', $this, $data);

        echo 'data saved! ' . json_encode($response->last());
    }
}

I just set a “save” event to be triggered and attached two listeners to that event, the slugify and the validate methods. When the save method gets called, the event is triggered and the EventManager calls our listeners. One fairly important point here is the prepareArgs method call, which prepares the event arguments in such way, that when these arguments (the $data array in this case) are modified in one listener, this modification is reflected for the other listeners, too. If you don’t want to modify the arguments in the listeners, this call can be omitted. As for the rest of the code, it’s explained in Matthew’s posts and in the ZF2 docs.

And here’s how the slugify method modifies the data:

<?php

class SlugifyPost
{
    public function slugify($event)
    {
        $data = $event->getParams();

        $event->setParam('slug', strtolower(str_replace(' ', '-', $data['title'])));

        return $data;
    }
}

Calling the save method itself remains as it was before introducing the EventManager, which means I could add this to my models without changing the API and not break anything! Genius! Theoretically, anyway…

<?php

$post = new Post;

$values = array(
    'title' => 'My post'
);

try {
    $post->save($values);
} catch(\InvalidArgumentException $e) {
    echo $e->getMessage();
}

You can find this complete example on Github.

Thoughts?

So, what do you think? Does this approach makes sense to you? Do tell. I kinda like the idea, but time will tell in the end, as always.

Happy hackin’!

Tags: eventmanager, events, listeners, zend framework, zf, zf2.
Categories: Development, Programming.

Need help on your PHP projects? Let's talk!