PHPixie Documentation

Processors are the of the PHPixie runtime, they implement the Chain-of-responsibility pattern. A Processor is just a simple interface:

1
2
3
4
5
6
7
8
namespace PHPixie\Processors;

interface Processor
{
    // A processor takes a single value
    // and returns some result
    public function process($value);
}

By chaining multiple processors one after another we can express the entire flow of the application while separating the logic of the building blocks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Consider this simple function
function calculate($x)
{
    $x = $x*2;
    $x = $x+5;
    return $x;
}

// With soe processors we could
// express it like this:
$calculate = new ChainProcessor(
    new MultiplyProcessor(2),
    new AddProcessor(5);
)

$calcualte->process($x);

This simple example might seem like an overkill, but when doing complex processing it’s much easier to work with separate pluggable building blocks instead of monolithic functions. The other benefit is that composite processors like the ChainProcessor in the example above still expose the same Processor interface, so it is easy to subsititute a simple processor with a whole chain of complex ones without changing any code around it.

Let’s for example take a quick look at the HTTP processing chain of PHPixie:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// This method builds a processor chain
// that takes care of building the Request
// object from a PSR-7 ServerRequest
protected function requestProcessor()
{
    $components = $this->builder->components();

    $processors     = $components->processors();
    $httpProcessors = $components->httpProcessors();

    // Chain processors
    return $processors->chain(array(

        // Parse body (e.g. JSON) into data
        $httpProcessors->parseBody(),

        // Match route and set attributes
        $this->parseRouteProcessor(),

        // Update cookie and seesion handlers
        $httpProcessors->updateContext(
            $this->builder->context()
        ),

        // Build a PHPixie Request from
        // the ServerRequest
        $httpProcessors->buildRequest()
    ));
}

This processor is later plugged in into a larger HTTP chain. by overriding this method we can plug in additional processors, such as add IP filtering, etc.

Before we take a deeper look and get to how this influences your Controllers, we have to take a look at one additional interface, the Selective processor:

1
2
3
4
5
6
7
8
9
namespace PHPixie\Processors\Processor;

// It adds a method that tells
// whether a particular value
// can be processed or not
interface Selective extends \PHPixie\Processors\Processor
{
    public function isProcessable($value);
}

One example of its usage is checking whether the HTTP processor specified by the user can process the request, if not we show him the 404 page. This is handled by the processing chain like this: “`php?start_inline=1 $processors->chain(array( // The processor that builds the request // We already took a look at it $this->requestProcessor(),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// A special processors that performs the check
$processors->checkIsProcessable(
    // This is the main processor
    // specified by the user
    $this->builder->configuration()->httpProcessor(),

    // This chain gets called if the request
    // can be processed (e.g. route matched)
    $this->dispatchProcessor(),

    // This gets called if the request
    // is invalid
    $this->notFoundProcessor()
)

)); ”`

As you can see from the aboce code the HTTP chain requires only a single Processor from the user, this is where the interesting part starts to happen. Remember that each bundle has its own HTTPProcessor instance? the framework comes with a default HTTP processor that checks the bundle attribute on the request (that came from routing) and forwards the call to the appropriate bundle. If the specified bundle doesn’t exist the isProcessable method will return false and a 404 page will be shown.

So when a request is routed to your bundle it calls your HTTPProcessor class and asks it first whether it can process the request (using isProcessable) and then to continue processing it (using process). At this point your bundle has complete control over what to do with it. The default approach is to mimick the standard MVC architecture of finding another processor specified by the processor attribute, this is why your HTTPProcessor extends \PHPixie\DefaultBundle\Processor\HTTP\Builder, and looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace Project\App;

class HTTPProcessor extends \PHPixie\DefaultBundle\Processor\HTTP\Builder
{
    protected $builder;

    // Request attribute that defines
    // the name of the subprocessor
    protected $attribute = 'processor';

    public function __construct($builder)
    {
        $this->builder = $builder;
    }

    // Method that builds the `greet` processor
    protected function buildGreetProcessor()
    {
        $components = $this->builder->components();

        return new HTTPProcessors\Greet(
            $components->template()    
        );
    }
}

The interesting part here is that the Greet processor might do exactly the same thing, and route the request to yet another subprocessor. This frees you from the standard Controller/Action pair and allows you to build any architecture you like inside your bundle. E.g. you could have this as your HTTPProcessor without the need of any controllers:

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace Project\App;

// In this case all the URLs
// inside your bundle will 
// output the same message
class HTTPProcessor implements \PHPixie\Processors\Processor
{
    public function process($request)
    {
        return 'Hello World';
    }
}

Unlike other frameworks the PHPixie Route component is entirely unaware of anything related to request processing, like your controllers, models, etc. All it does is match the request URL to the specified routes and fill in matched data as request attributes. The default processors on the other hand only look at the attribute data and don’t care about how it got there. So if you wanted to you could substitute the PHPixie router for something else, e.g. get parameters from $_GET.

To finish off let’s take a look at some base processor classes you could use:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// The most familiar processor
// that forwards the request to
// an the next processor specified
// by the attribute
class Home extends \PHPixie\DefaultBundle\Processor\HTTP\Builder
{
    // attribute name
    protected $attribute = 'processor';

    protected function buildWelcomeProcessor()
    {
        return new Welcome();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// This one calls a method
// specified by the attribute
// with the 'Action' suffix.
class Home extends \PHPixie\DefaultBundle\Processor\HTTP\Actions
{
    // attribute name
    protected $attribute = 'action';

    protected function welcomeAction($request)
    {
        return "Hello";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Or you could try using query
// data instead of attributes
class Home extends \PHPixie\Processors\Processor\Dispatcher\Builder
{
    protected $parameterName = 'processor';

    protected function getProcessorNameFor($request)
    {
        return $request->query()->get($this->parameterName);
    }

    protected function buildWelcomeProcessor()
    {
        return new Welcome();
    }
}

Processors can be used for a lot of other use cases too. If you can split some of your data processing into basic building blocks you will eb able to reuse all of the existing processor handlers, like chaining, selective processing, etc.