Application Hooks with Laravel Events

Laravel Events is one of those features that is critical to many applications and the applications I am currently working on are no different. When I first started using the Events I found that the setup was very simple to use and everything made sense – fire an event when something happens and setup a listener response that will react. As the docs state, this really is a simple observer implementation. The example in the docs is quite simple and also a very useful example:

Event::listen('user.login', function($user)
    $user->last_login = new DateTime;
$event = Event::fire('user.login', array($user));

This implementation is quite simple, but there are a few things that can be missed if you haven’t worked with a similar system before.

  1. The event listener has to be declared before the event fires. This seems simple, but it caused me a few headaches when working in packages and realizing that everything had to be organized in a specific order so that the events would fire properly.
  2. Data can be passed to the listener closure, but this is not necessary. You could just write the same function without passing $user and use Auth::user() to the same ends.
  3. You can have multiple listeners for a single event and you can also give them a priority to decide in which order they will be triggered. Simply pass a third parameter after the closure with a numeric value (ie: 5). Keep in mind this is priority not order so the higher the number, the earlier it is triggered.

Putting it to Use

Everything above is more or less covered in the documentation and with a basic knowledge of how Events work. The next step I took was to start putting these into practice with a few simple examples that I found very useful.

Event::listen('user.login.failed', function($user)
    Log::warning('Login Failed for User '.$user->username);
    $user->failed_attempts = $user->failed_attempts + 1;
Event::listen('user.logout', function($user)
    // Queue up session clean for this user
    // Remove any unpublished items in database for user

Going Further With Events

After using Events extensively in smaller projects, we began a very large scale project at work that was going to be entirely package-based with a very small framework developed on top of Laravel that allowed us to create a global dashboard layout that had ways for packages to extend it and implement themselves inside it. For our purposes, this had to be more than just a layout – we needed to have one application that could be changed in it’s entirety for a second client without having to change the core code or our existing packages. Laravel and Composer make this very easy on the surface, but the hooks we needed inside the core were the biggest hurdle as to how we were going to implement this structure. Whether or not the following examples are the best way to do this is up for debate, but for our purposes it got the job done and it gave us an easy way to implement our extensibility.

Using Events as Hooks

The system we were building required that the main dashboard could have “hooks” that packages could grab onto and return data. We discussed a number of ways of doing this, but we decided that creating a rigid system for each hook made the most sense. What we ended up with was a well-documented system that allowed our packages to declare dashboard widgets, reporting widgets, CMS block types and necessary config values. Below is an example of one of these implementations.

Dashboard Widget

First thing we do is in our extending package’s service provider register method. We listen for the event widgets.dashboard.create and we return a very specific array of data that allows this package to define a widget. Note that we are returning an array of data from our listener – the Laravel docs don’t talk much about this.

\Event::listen('widgets.dashboard.create', function(){
    return array(
	'size' => 'col-md-4',
	'icon' => 'fa-rub',
	'heading' => 'Weekly Sales',
	'text' => count($sales).' Items',
	'link' => 'dashboard/orders/week',
	'link_text' => 'View Sales',
	'color' => 'info',
	'type' => 'panel'
}, 20);

Now that we have the listener setup, the event fire is pretty self-explanatory. Note that we set the variable $widgets to the response of the fire method – when we do this Laravel returns an array of all the results. Because of this, we can listen 6 times to this event and the $widgets will contain an array of 6 widget arrays for us to work with, in the order defined by our event priority.
The rest of the code is not as relevant to the events package, but we takes those arrays and process them based on the type defined in the array and then pass that data to the view.

$widgets = \Event::fire('widgets.dashboard.create');
$data['widgets'] = array();
foreach($widgets as $w)
    if(method_exists('\Company\Widgets\Widget', 'create_'.$w['type']))
        $data['widgets'][] = call_user_func('\Company\Widgets\Widget::create_'.$w['type'], $w);
Config Variables

Another issue we needed to solve for was allowing one global configuration section to handle configuration for all packages. We used a similar setup to the widgets and then used a simple key-value pair in the database that would handle configuration dynamically based on what values the packages requested be added. We debated countless ways of doing this with passing input types and other data sets, but in the end we decided to allow each package to make their own config views and then pass the view along. We also wanted to ensure that each package could define a sub-config section without having to tell the core that it exists. The routes we used in combination with the controller allowed us to check if any config/{type} existed and, if it exists, display the relevant views.

// Pass the Orders Config View to the Config Builder
\Event::listen('', function()
    return 'Orders::config.orders';
// Pass the Main Config View to the Config Builder
\Event::listen('', function()
    return 'Orders::config.main';
// Are we looking for a specific config page?
// This code checks if we are on /config/$type or just on /config route. 
    $views = \Event::fire('');
    if(count($views) > 0)
        foreach($views as $v)
            // Load each view file into a config array for our main config view
            $data['config'][] = \View::make($v);
// We have a specific type. Check if we have views registered.
    // Fire a dynamic type event to retrieve views for a specific type of config page
    $views = \Event::fire(''.$type);
    // Check if we have more than one view for this config type
    if(count($views) > 0)
        foreach($views as $v)
            $data['config'][] = \View::make($v);
        return \Redirect::to('config')->with('warning', 'Could Not Find That Config Page');

Final Thoughts

The one downfall to this system may end up being our overhead. The basic implementation isn’t very taxing, but as we continue to develop we will be looking for ways to avoid firing events if there won’t be a listener for that request. This could be as simple as only registering config listeners when “config” exists in the current route.

The other issue, as noted above, is that there is likely a better way to do some of this. We chose to do it this way because the Events package was already there and had things like priority and data passing capabilities that we would need and there were few shortcomings from a developer perspective to implementing the system this way. As we continue to work on the application we may move more towards Events or away from it if we find an alternative, but for now it’s meeting our needs.

Deleting Orphan Records in MySQL

Recently I ran into an issue with an application I had developed where I made the mistake of not deleting some child table records when the parent record was deleted. After the app had been in beta for a month or two, I realized that I had thousands of records in the child tables that had no parents and were thus “orphan” records.

After juggling a few ideas in my head, I decided the best approach to this would be to make a temporary table of just the orphan items and then delete from the child table where the ID matches an ID in my orphans table. What does this look like in MySQL?

  SELECT FROM ChildTable
  LEFT OUTER JOIN ParentTable ON ChildTable.RelationKey = ParentTable.RelationKey
  WHERE  ParentTable.RelationKey IS NULL

How Does it Work?
Essentially, the first line of the query makes a new temporary table that we will delete when we are done:

The second line selects the primary key (id) of every record in Child Table.
The third line creates a Left Outer Join with the parent table that used to have records associated with your orphaned records, based on whichever field you use to associate the two tables (RelationKey).
The fourth line specifies that we only want to select the ones where the RelationKey we provide is NULL, meaning it couldn’t find a parent element.

At this point we build the table and we now have a table of all orphaned records.
The last command just runs a DELETE query telling the database to delete any records that exist in both the ChildTable and the new Orphans table. Once that’s done, delete the Orphans table and the operation is done.

Composer with FuelPHP

As I noted in an earlier post about my switch from CodeIgniter to FuelPHP, I have since started moving much of my development from FuelPHP to Laravel. Most recently I have been using Laravel 4 and have become acquainted and infatuated with Composer packages. Composer is bringing the package functionality that PHP needs and PEAR can’t possibly deliver to the current PHP community. Unfortunately, the current version (1.4 as I write this) of FuelPHP doesn’t have Composer baked in yet. The beauty of Composer is that installing it into an application is so simple that this is no longer a barrier.

Note: FuelPHP 2.0, which is major milestone and change for FuelPHP, will supposedly include Composer. There is talk of a 1.x version including Composer in order to bridge the gap between the current releases and the much changed 2.0 release.

Setting Up Composer to Work With FuelPHP 1.x

1) Download Composer. Although obvious, this is a necessary step. I find that command line installation is easiest. Navigate to the root directory of your application and run the following code:

curl -s | php

2) At this point you should have a composer.phar file installed in your application’s root directory. The next step is telling Composer what packages you want to add to your project. Composer uses a file named composer.json, so create this file in the root directory. The most basic example is laid out below, but look for more examples and information in the Composer documentation.

     "config": {
        "vendor-dir": "fuel/app/vendor"
     "require": {
        "monolog/monolog": "1.2.*"

3) Now that we have the composer.phar file and a composer.json file with the packages we need, run the composer installation, which will download the packages and place them in the vendor directory specified in composer.json (in this case the default fuel vendor directory).

php composer.phar install

4) Once the installation has finished (may take some time, depending on the number of packages you have) we can assume that all packages are installed to the fuel/app/vendor directory and now we need to tell FuelPHP to look at this directory for our new packages.
Open your bootstrap file (fuel/app/bootstrap.php) and add the following code after the AutoLoader::register() that is already there:

require APPPATH.'vendor/autoload.php';

At this point you have finished the setup and installation of Composer.
The next step is to read the Composer getting started guide to make sure you’re up to speed with how it works.
The basics you will need to know right off the bat:

  • php composer.phar update – this command will update all your packages to the latest version based on criteria specified in your composer.json file.
  • php composer.phar dump-autoload – run this command whenever you add or remove packages so your autoload.php file can be updated.

FuelPHP – The Switch From CodeIgniter

I’m certainly not the first to make the switch and I’m certain I won’t be the last. For years, CodeIgniter was my framework of choice for PHP applications of almost any size. The URI classes, simple helper integration and MVC structure were easy to use and light enough to stay out of the way during application development. For my most recent project, I needed something more modular, possibly with a full blown HMVC integration.

After extensive research into HMVC options for CodeIgniter, I stumbled upon fuel and the core functions they had built in from the bottom up. After a few days of reading, I was sold on the idea of using fuel for my newest project. It’s been a few weeks now and I can definitely say that this was a decision I will never regret. Rather than going into a discussion of the differences between CodeIgniter and fuelPHP (which has been done before quite well), I will just summarize some of my favourite features.

  1. Built in Modular structure – this was the reason that led to me stumble upon fuelPHP in the first place. Fuel doesn’t force you into using modules or an HMVC file structure, but if you choose to implement them the process is well documented and quite easy to integrate. Once you start creating apps in a modular fashion, it becomes obvious why this structure has clear benefits. I won’t go into the benefits of HMVC, but a quick read around will find various questions and answers on this topic.
  2. ORM in the Core – When reading about the ORM Class in the core of fuel, I had no intentions of using it for my applications; I was always comfortable using models to extend standard SQL queries or using the Active Record class in CodeIgniter. I decided to give the ORM package a chance for one module in the new application and ended up using it for every module so far. The ORM maps a model to each table in the database, establishes the fields of the table and the relationships to other tables.
  3. Complete Flexibility – One of the many benefits of fuel is that there are very few restrictions on how you write code. Classes can be in any file structure you want, you can declare any folder as a “modules” folder, you can easily extend native classes and there are countless other examples of how fuel lets you write code in your own way.
  4. Rockstar Team Running the Show – One thing I often heard about with CodeIgniter was that EllisLab was creating CodeIgniter more to fit it’s own needs than those of the community. The slow process for change was also blamed on restrictions on community development put in place by EllisLab. It was refreshing to read up on fuelPHP and know that not only is the team made up of incredible minds, but community was the focus of development.
  5. PHP 5.3 – Although this may cause issues for some hosting environments (I had to update my own VPS to the latest version manually), the use of PHP 5.3 means that fuelPHP can take advantage of all the latest features that PHP introduced since the times of PHP 4.
  6. Community Enthusiasm – One of the biggest reasons I fell in love with CodeIgniter was the massive community following and development. Libraries and modifications were being developed and released constantly, which was helping to keep the framework moving forward when the core was moving at a slower pace. This is the same reason I have fallen in love with fuel; the community is on fire about fuel. Not only are they excited about it, but they’re helping develop modules, packages and the core at an incredible pace.

There are countless other things I have found myself loving about fuel, but these are the most obvious ones at a general level. At this point I have begun developing all my applications in a modular structure. I have been scouring GitHub for fantastic packages (like Warden) that I can extend to save time and use to learn from other experts.

I feel it is only fair to disclose the fact that much of this love has since passed. Although I still think fuelPHP has some great features and functionality, the community, development speed and brilliance over at Laravel has converted me. See you in #laravel on Freenode.

© 2019 Craig Hooghiem

Theme by Anders NorenUp ↑