The Bootstrap Process

The process of bootstrapping a framework / application is just a fancy name for setting things up. Generally this entails instantiating some objects, setting some parameters and then calling some method to get the framework up and running. This usually happens within your index.php file or some other file included into it.

In most frameworks, what happens once this process is started is something of a black box, the next thing you know, your controller is producing a web page. Unless you have a deep understanding of how the framework works this process can often feel like magic.

While this can make it easy to get started building applications with the default functionality your framework provides, it can also make the task of customising this process quite daunting to someone who doesn't really want to dig into a frameworks internals.

One of Proem's main objectives was to simplify this process and make the logic easy to follow, understand and customise. Proem does this by making extensive use of Events. Events are triggered at certain points in the execution process, these events can be listened for and can be sent responses.

Bootstrapping Proem

Bootstrapping the Proem framework with only defaults is as simple as:

    (new \Proem\Proem)->init();
  

However, that doesn't really help us much. Below, we are going to describe what exactly happens when you call Proem's init() method, and later how you can attach code to Events that will allow you to customise this process.

The Proem Object

The Proem object is a home for your application and your entire application runs inside of this single object. It is responsible for setting up all of the low level infrastructure that your application needs in order to execute. While it is possible to bootstrap Proem without using the Proem object, without it, the bootstrap process would be far more tedious. The Proem object provides you with an interface for configuring the framework and your application while still allowing a great deal of flexibility through the methods it exposes.

So let's have a look at what the Proem object actually does. Starting the with __construct:

    public function __construct()
    {
        $this->events = new Asset;
        $this->events->set('Proem\Signal\Manager\Template', $this->events->single(function($asset) {
            return new SignalManager;
        }));

        $this->serviceManager = new ServiceManager;
    }
  

Here Proem sets up a Signal Manager and a Service Manager. (Asset is an alias of Proem\Service\Asset\Standard. These two objects form the heart of an application built on top of Proem and are passed around the entire bootstrap process and into userland (your application).

So what else? Let's take a look at the actual init() method:

    public function init($environment = null)
    {
        $this->serviceManager->set('events', $this->events);

        $this->events->get()->trigger(
            (new Bootstrap('proem.init'))->setServiceManager($this->serviceManager)->setEnvironment($environment),
            function($response) {
                if ($response instanceof Proem\Filter\Manager\Template) {
                    $this->filterManager = $response;
                }
            }
        );

        if ($this->filterManager === null) {
            $this->filterManager = new FilterManager;
        }

        $this->filterManager
            ->setServiceManager($this->serviceManager)
            ->attachEvent(new Response, FilterManager::RESPONSE_EVENT_PRIORITY)
            ->attachEvent(new Request, FilterManager::REQUEST_EVENT_PRIORITY)
            ->attachEvent(new Route, FilterManager::ROUTE_EVENT_PRIORITY)
            ->attachEvent(new Dispatch, FilterManager::DISPATCH_EVENT_PRIORITY)
            ->init();
    }
  

From this small snippet it is easy to see that init() stores some events within the Service Manager (these are added via attachEventListener), it then triggers a Bootstrap event named proem.init and creates a new Filter Manager, passing it our Service Manager instance.

So WTF is this Filter Manager?

The Bootstrap Filter Chain

Before I explain what the Bootstrap Filter Chain (Managed by the FilterManager) is and the functionality it provides it's probably a good idea to show you a simple example of the Filter Chain pattern used.

    class Event1
    {
        public function execute($events)
        {
            echo "in Event1\n";
            if ($e = next($events)) {
                $e->execute($events);
            }
            echo "out Event1\n";
        }
    }

    class Event2
    {
        public function execute($events)
        {
            echo "in Event2\n";
            if ($e = next($events)) {
                $e->execute($events);
            }
            echo "out Event2\n";
        }
    }

    class Chain {
        private $events;

        public function __construct()
        {
            $this->events[] = new Event1;
            $this->events[] = new Event2;
        }

        public function start()
        {
            current($this->events)->execute($this->events);
        }
    }

    (new Chain)->start();
  

This is a very simple working example of the pattern used by Proem's filter chain. If you run this code you will see the output:

    in Event1
    in Event2
    out Event2
    out Event1
  

The Chain is basically a collection of objects that have the ability to execute each other in a series without needing to know what the next object in the chain is.

While the Proem Filter Chain implementation is quite a bit more robust than this simple example, the basic premise is the same and the Proem object's init() method merely kicks off this process. It simply executes a series of events that are specifically designed to get the framework up and running.

So what Filter Events are involved in bootstrapping?

So what Events does Proem's init() method execute? If we look at the code again you can see the events:

  • Response
  • Request
  • Route
  • Dispatch

As the names suggest, these Events are responsible for setting up the Request, Response Routing and Dispatch parts of the framework.

Each of these events extend the Proem\Filter\Event\Generic object which is as I said little more robust (along with the Proem\Filter\Manager\Standard (Chain)) than the simple example shown above.

One of the differences is that each of these Filter Events has it's own inBound and outBound methods. These basically take the place of the echo "in Event1 and echo "out Event1 parts of the simple example above.

It might be best to illistrate this with an image:

These Filter Events do the bulk of the grunt work in terms of setting up the framework ready to bootstrap an application.

How can we customise this process?

So now we have an understanding of what happens when we call (new Proem\Proem)->init(), but how can we customise this process? If you look again at the Proem\Filter\Event\Generic abstract you will notice pre* and post* methods surround the inBound and outBound methods. These pre / post methods are responsible for publishing Events which we can listen to in order to customise the bootstrap process.

This means that the Chain can potentially publish 16 different events that we can listen to using the Signal Component. On top of that, there is the initial "proem.init" event (which we can also see within the init() code) and some of these events themselves have the potential to again trigger more events (the dispatch stage comes to mind). The main Events triggered here are:

  • proem.init
  • proem.pre.in.response
  • proem.post.in.response
  • proem.pre.in.request
  • proem.post.in.request
  • proem.pre.in.router
  • proem.post.in.router
  • proem.pre.in.dispatch
  • proem.post.in.dispatch
  • proem.pre.out.dispatch
  • proem.post.out.dispatch
  • proem.pre.out.router
  • proem.post.out.router
  • proem.pre.out.request
  • proem.post.out.request
  • proem.pre.out.response
  • proem.post.out.response
As of 0.6.0 the first thing the Signal Manager does with an Event is checks to see if there are any listeners, if there are no listeners, the Signal Manager does nothing. This means that Events have relatively little overhead until they actually have a listener attached. And then, the overhead really depends upon what happens within there communication exchange.

So what do we do with all these events?

All of the above mentioned events are entry points to the framework. These are the events we can listen for and act upon in order to customise the bootstrap process.

For instance, out of the box there are only a minimal set of default Routes set up by Proem. If we want to add a Route to the Router we can do so by waiting for the Router to exist, and then access it and add our Route.

 

A simple example, adding a Route to the Router

    (new \Proem\Proem)->attachEventListener('proem.post.in.router', function($event) {
        $event->getServiceManager()->get('router')->attach(
            'logout', new \Proem\Routing\Route\Standard([
                'rule'      => '/logout',
                'targets'   => ['controller' => 'auth', 'action' => 'logout']
            ])
        );
    })->init();
  

In this example we can see that the Proem object provides an attachEventListener() method. This method proxies through and attaches listeners to the main Signal Manager that is passed around the framework.

All Events triggered directly from the Bootstrap Filter Chain are instances of the custom Proem\Bootstrap\Signal\Event\Bootstrap event which provides a few extra methods that are specific to the bootstrap process.

Because we are listening for the proem.post.in.route event, the router already exists and is within the Service Manager ready for us to customise. A general rule of thumb is that default implementations of objects are added during the inBound() filter Events. These can be overridden completely by sending your own implementation as a response to the applicable proem.pre.in.* Event (see The Signal Component, or can be configured by listening to and responding to the applicable proem.post.in.*

But what if we don't want to use the Standard Router? Using the Events provided we can overload it on the fly.

Overloading provided services

For whatever reason we have decided that we need our Router to implement a useless helloWorld method. How do we overload the Router that Proem is going to provide us with by default? Easy.

The Router is created within the Router Filter Chain Event inBound(). All we need do is listen for the proem.pre.in.route event and from within our listener, return an event with the 'router.asset' parameter set to our new Router wrapped within a Service Asset Container (Being sure to pass it a valid url to it's constructor).

    namespace MyApp;

    class Router extends \Proem\Routing\Router\Standard
    {
        public function helloWorld()
        {
            return 'Boo!';
        }
    }

    $proem->attachEventListener('proem.pre.in.route', function($event) {
        $asset = new \Proem\Service\Asset\Generic;
        $event->setParam('router.asset', $asset->set('MyApp\Router', function() use ($event) {
            return new MyApp\Router(
                $event->getServiceManager()->get('request')->getBaseUri()
            );
        }));
        return $event;
    })->init();
  

Summary

I hope this has cleared up a lot about how the bootstrap process is intended to work within Proem. I trust that you can see how easy it is going to be to customise this process to your needs.

I can also hear people screaming that "by the time I listen for all these events my bootstrap is going to be a mess!". While this might look like the case, once you start looking at Modules and Plugins you will see that there are ways around this as well.