← Back to the Blog

Getting to Know Aura PHP - Part I

By Matthew Setter
Getting to Know Aura PHP - Part I

In the world of PHP, you're not hard up for choice for packages and frameworks for just about everything, especially since the advent of Composer and Packagist.
There's Zend Framework (1 & 2), FuelPHP, Laravel (Here is a great article on Developing in Laravel 5), Symfony (1 & 2), Phalcon and a whole lot more.
They all have a lot to offer, depending on your level of expertise with PHP.

However, recently, I was tasked, as part of my regular Education Station column for PHPArch Magazine, to write a 3-part series on Dependency Injection (DI), and the state of it in PHP. During the course of my research, I investigated four of the most notable Dependency Injection Containers (DiC) implementations in PHP, and one of them was Aura.Di.

During the course of my research, I took time out to talk with Aura project founder, and lead contributor Paul M. Jones, about the library. In the process, I discovered an excellent project, one where every component, whilst interoperable with the all the others, is not dependent upon them.

This stands in stark contrast with many of the existing frameworks and packages in PHP today, with some more recent, notable exceptions, such as Zend Framework 2.5. For so many packages, if you want just a component or two, often times you need a number of others, even if you don't use them.

Aura doesn't work this way!

Given that, it's unfortunate that it doesn't seem to get the same amount of coverage and exposure which other PHP frameworks do, such as the aforementioned Zend Framework and Laravel.

Perhaps because it's not as shiny as some of the others, or that it doesn't have a large, visible corporate presence to help it stand out from the crowd. But either way, if you're serious about professional coding practices, and software craftsmanship, this is the framework for you.

If you're yet to hear of it, and want to work with a very well designed set of packages, then today you're in for a treat. I'm going to give you a rapid introduction, to the 2.x implementation, followed by a run-through of a sample application built using the 2.x framework.

Note: The framework component is being phased out in version 3 of the project. But it still provides an excellent way of introducing the project.

In a follow up article, I'll be taking you through some of the changes both already implemented, and set to be implemented in version 3.x of the project. So keep an eye out for that.

What is Aura PHP

The simplest way is to quote the website:

Aura 1.x began as a rewrite of Solar, re-imagined as a library collection with dependency injection instead of a monolithic framework with service location.

The project centers around a collection of high-quality, well-tested, semantically versionedstandards-compliant, independent library packages that can be used in any codebase.

Each library is self-contained and has only the things it needs for its core purpose. None of the library packages depends on any other package. They are decoupled, not only from any particular framework, but also from each other. This means developers can use as much or as little of the project as necessary.

Requirements

Currently there isn't a long list of requirements. The key one is that your web server's running at least PHP 5.3. I'd like to think that this was a minimum, at the very least.

However, if you're running later versions, and I hope you are, it's good to know that Aura's tested against 5.5, 5.6 and HHVM. PHP 7 has also been successfully test against, even though it's still in alpha.

What's On Offer

Aura comes with a range of packages which you would expect from a PHP web project, which include:

  • Autoloading: Compliant with PSR-4
  • Dependency Injection: Complete with constructor and setter injection, lazy-loading and inheritable configuration
  • Dispatching: Creates objects from a factory and invokes methods using named parameters; also provides a trait for invoking closures and object methods with named parameters
  • Filtering: Provides filters to validate and sanitize objects and arrays
  • Routing
  • Sessions: Provides session management functionality, including lazy session starting, session segments, next-request-only "flash" values, and CSRF tools
  • SQL: Provides lazy connections, array quoting, identifier quoting, query profiling, value binding, and convenience methods for common fetch styles, along with object-oriented query builders for MySQL, PostgreSQL, SQLite, and SQLServer; can be used with any database connection library
  • Views: Provides an implementation of the TemplateView and TwoStepView patterns, with support for helpers and for closures as templates, using PHP itself as the templating language
  • Escaping: Provides HTML escapers and helpers, including form input helpers

Have a look at the list of packages to get an overview of what's on offer. If you're keen to know more about any one, follow the link to the respective GitHub repository, and browse the code or peruse the project's README.

A Simple Application

Now that I've given you the big overview, let's get started creating a modest application using the 2.x framework. If you want to peruse the full application, it's available on GitHub. The application's not going to be too special, but take in a series of the components. It's going to have a routing table with three routes:

  1. The Home Page
  2. An About Page
  3. A Data View Page

The home and about pages will be simple text output. The data view page will setup a \PDOconnection to an SQLite3 database, which contains a copy of the Chinook example database, and will submit a SQL query to collate and return the episode's of Lost with the highest grossing sales. When rendered, the page will look like the screenshot below.

Rendered View of Highest Grossing Sales

The Project Structure

Before we dive in to the code, let's first look at the directory structure. It contains 6 key directories, these are:

  • cli
  • config
  • src
  • tests
  • tmp
  • web

Most of these we don't need to be concerned about for the purposes of this example. The key ones, however, are config and web. In config, you'll see five files. Here's what they do:

  • _env.php provides a default environment
  • Common.php provides the default setup which is used across all environments
  • Dev.php, Prod.php, and Test.php implement options specific to those environments

The web directory is similar to the more commonly used /public directory. It contains the bootstrap, or front controller, file, where all requests are routed through.

Looking at Common.php, you'll see a number of existing functions; these being define, modify, modifyLogger, modifyCliDispatcher, modifyWebRouter, and modifyWebDispatcher. These do largely what the names imply.

define sets up the DiC configuration, modify is a utility function for calling any sub-modify functions. modifyLogger sets up logging, modifyCliDispatcher andmodifyWebDispatcher set up the dispatch configuration for cli and web environments respectively.

And modifyWebRouter sets up routing for the web environment. Of these, the only one I'm going to modify is modifyWebRouter.
It already has one route setup, which is defined as:

$dispatcher->setObject('hello', function () use ($di) {
    $response = $di->get('aura/web-kernel:response');
    $response->content->set('Hello World!');
});

What this does is to create a named route, hello, and provide a closure, which receives the DiC, as its handler. Via the DiC it accesses the response object and sets the content of it to be the string Hello World!.

The About Page

Following in that vein, I'm going to add the about page, using the following configuration.

$dispatcher->setObject('about', function () use ($di) {
    // Create a new View object
    $view_factory = new \Aura\View\ViewFactory;
    $view = $view_factory->newInstance();

    // Setup a Two-Step View
    $layout_registry = $view->getLayoutRegistry();
    $layout_registry->set('default', './../src/templates/default.layout.php');

    // Configure the view
    $view_registry = $view->getViewRegistry();
    $view_registry->set('about', './../src/templates/about.php');
    $view->setView('about');
    $view->setLayout('default');

    // Set the view output to be the response body
    $response = $di->get('aura/web-kernel:response');
    $response->content->set($view());
});

This will first setup a new named route, called about, also using a closure as it's handler. Inside the handler, I`ll create a new View object to handle the rendering of the view output, handled with templates. This is a lot simpler and more maintainable than creating the output in code.

You'll see above that there's a layout and view. The reason for this is that I love the two-step view pattern, as it makes it simple to separate reusable elements, such as headers, footers, and sidebars, from action-specific output, such as the about page's content.

I do this by making a call to getLayoutRegistry(), which sets the layout template, linked to the key 'default'. These are named as we may have multiple layout templates. Then I'll add a view template, linked to the key 'about.

With that done I'll tell the View which template to use for the content, and which to use for the layout, by calling setView() and setLayout() respectively.

With all this done, I'll set the response body to be the return result from calling $view as a function. In short, this will set the response body to be the rendered template result.

The View Data Page

The View Data Page is a bit more involved, so will take a bit more explaining. As such, I've broken the code up in to chunks, talking about them a piece at a time.

$dispatcher->setObject('data-view-sales', function () use ($di) {
    $view_factory = new \Aura\View\ViewFactory;
    $view = $view_factory->newInstance();

As before, we've initialised a new view object.

$pdo = new ExtendedPdo(
        'sqlite:./../db/database.sqlite'
    );

    $extended_pdo = new ExtendedPdo($pdo);

Here we've initialised a new ExtendedPdo object, pointing to our SQLite database, which is a PDO extension that provides lazy connections, array quoting, identifier quoting, query profiling etc.

$stm = '
        SELECT t.TrackId, sum(il.unitprice) as "TotalSales", t.Name as "TrackName", g.Name as Genre, a.Title as "AlbumTitle", at.Name as "ArtistName"
        from InvoiceLine il
        inner join track t on (t.TrackId = il.TrackId)
        INNER JOIN genre g on (g.GenreId = t.GenreId)
        inner join album a on (a.AlbumId = t.AlbumId)
        INNER JOIN artist at on (at.ArtistId = a.ArtistId)
        WHERE g.Name like :genre
        group by t.TrackId
        HAVING at.Name = :artist_name
        order by sum(il.UnitPrice) desc, t.Name asc
    ';

    $bind = ['genre' => 'TV%', 'artist_name' => 'Lost'];
    $sth = $pdo->prepare($stm);
    $sth->execute($bind);

Next, we've specified a semi-complex SQL statement, using named parameters to aid in avoiding SQL injection attacks. The named parameters are for genre and artist name. These we've substituted for TV% and Lost. We've then called execute() to run the query.

$layout_registry = $view->getLayoutRegistry();
    $layout_registry->set('default', './../src/templates/sales.layout.php');

    $view_registry = $view->getViewRegistry();
    $view_registry->set('sales-data', './../src/templates/data/sales/view.php');
    $view->setView('sales-data');
    $view->setLayout('default');

    // the "sub" template
    $view_registry->set('_result', './../src/templates/data/sales/result.php');

As before, we've setup the layout and view templates for rendering the page.

 $view->setData([
        'results' => $pdo->fetchObjects($stm, $bind, '\DatabaseObjects\Entity\SalesData')
    ]);

    $response = $di->get('aura/web-kernel:response');
    $response->content->set($view());
});

Finally, we've set a view template variable called results which will hold the value of querying the database. Note the third parameter. This indicates that every result in the resultset, will be a hydrated \DatabaseObjects\Entity\SalesData object. Now I didn't have to do this, but I find it makes working with larger datasets a whole lot simpler, as well as object-oriented.

Updating the Routing Table

With the new endpoints created, we now need to update the routing table, so that we can reach them. To do so, in modifyWebRouter(), add the following code:

// Add an about page
$router->add('about', '/about')
       ->setValues(['action' => 'about']);

// Add a sales data display page
$router->add('data-view-sales', '/data/view/sales')
       ->setValues(['action' => 'data-view-sales']);

These create named routes, about and data-view-sales, pointing to /about and/data/view/sales respectively. The setValues() function is how we pass view template variable data, which admittedly I've not used in the code so far.

The View Templates

Honestly, most of the templates are just plain HTML, which I've borrowed from the Twitter Bootstrap examples. However two templates are worth looking over. These are view.phpand result.php in src/templates/data/sales/. In view.php you'll see the following code:

<?php
  foreach ($this->results as $result) {
      echo $this->render('_result', array(
              'result' => $result,
          )
      );
  }
?>

This iterates over each result, rendering the template named _result, which isresult.php, passing it the current result. Looking at result.php, we see the following code:

<tr>
    <td><?php print $result->TrackId; ?></td>
    <td><?php print $result->TotalSales; ?></td>
    <td><?php print $result->TrackName; ?></td>
    <td><?php print $result->Genre; ?></td>
    <td><?php print $result->AlbumTitle; ?></td>
    <td><?php print $result->ArtistName; ?></td>
</tr>

Nothing too special, but it outputs the value of the six object member variables. I didn't have to separate the templates out, but felt that it was worth showing how to do so, and how to make the code more reusable.

Other frameworks, such as Zend Framework, would use the term Partial here. There's not a strict implementation of partials, but an in-situ one.

Running The Application

Now that everything's setup, let's get it running. As it's not making use of external services or databases, then I'm going to launch it using PHP's built-in web server. To do so, I'll run the following command:  

php -S localhost:8080 -t ./web

Wrapping Up

And that's all it takes to create a basic, database-enabled Aura application. Sure, I've only covered the basics. But in the follow-on article, I'll be adding a range of extra functionality, so that you get a better understanding, both of what's on offer, and how to better organise it, making better use of dependency injection.