Aura Framework v2

Aura Framework v2
Aura Framework v2
Buy on Leanpub

Table of Contents

Getting Started

Composer has become the de facto standard for installing libraries in the php world. Aura does the same.

Installation

There are 3 types of skeletons

  • aura/web-project : only web application, no cli support built in.
  • aura/cli-project : only for command line applications.
  • aura/framework-project : supports both web and cli

We are going to install aura/framework-project, so we can show command line examples also.

1 composer create-project aura/framework-project {$PROJECT_PATH}

It will create the {$PROJECT_PATH} directory and install the dependencies in vendor folder.

Structure

The directory structure looks something similar to this. The list is not complete for we have removed some of the files and directories.

 1 ├── cli
 2 │   └── console.php
 3 ├── composer.json
 4 ├── composer.lock
 5 ├── config
 6 │   ├── Common.php
 7 │   ├── Dev.php
 8 │   ├── _env.php
 9 │   ├── Prod.php
10 │   └── Test.php
11 ├── src
12 ├── tmp
13 │   ├── cache
14 │   └── log
15 ├── vendor
16 │   ├── aura
17 │   │   ├── cli
18 │   │   ├── cli-kernel
19 │   │   ├── di
20 │   │   ├── dispatcher
21 │   │   ├── project-kernel
22 │   │   ├── router
23 │   │   ├── web
24 │   │   └── web-kernel
25 │   ├── autoload.php
26 │   ├── monolog
27 │   │   └── monolog
28 │   └── psr
29 │       └── log
30 └── web
31     └── index.php

The web/index.php is where you need to point your virtual host. Check the chapter setting up your virtual host for more information.

Alternatively you can start the built-in PHP server.

1 php -S localhost:8000 -t web/

If you point your web browser to http://localhost:8000 you can see the message Hello World!.

Great! Everything is working fine.

Exploring the Hello World!

Open the file config/Common.php. Look into the modifyWebRouter() and modifyWebDispatcher() methods.

1 public function modifyWebRouter(Container $di)
2 {
3     $router = $di->get('aura/web-kernel:router');
4     $router->add('hello', '/')
5            ->setValues(array('action' => 'hello'));
6 }

The modifyWebRouter() gets the shared router service and add a named route hello which points to /. So any request to http://localhost:8000 is satisfied by route named hello.

Now we have the route, the router don’t know what to do when a request come. The dispatcher is what helps to dispatch things.

1 public function modifyWebDispatcher($di)
2 {
3     $dispatcher = $di->get('aura/web-kernel:dispatcher');
4 
5     $dispatcher->setObject('hello', function () use ($di) {
6         $response = $di->get('aura/web-kernel:response');
7         $response->content->set('Hello World!');
8     });
9 }

We get the shared dispatcher service, set the same name as in the controller of route in setObject, and use a Closure or Callable.

In this example we are using a Closure, which get the di container and use it as a service, get the shared web response and set the content.

Don’t worry too much about dependency injection and dependency injection container. We will be talking more details in the coming chapter.

Configuration

Although configuration is a project-level concern, each Aura kernel and project handles it in the same way.

Setting The Config Mode

Set the configuration mode using $_ENV['AURA_CONFIG_MODE'], either via a server variable or the project-level config/_env.php file. Each Aura project comes with dev (local development), test (shared testing/staging), and prod (production) modes pre-defined.

Config File Location

Project-level configuration files are located in the project-level config/ directory. Each configuration file is a class that extends Aura\Di\Config, and represents a configuration mode. Each configuration class has two methods:

  • define(), which allows you to define params, setters, and services in the project Container; and
  • modify(), which allows you to pull objects out of the Container for programmatic modification. (This happens after the Container is locked, so you cannot add new services or change params and setters here.)

The two-stage configuration system loads all the configuration classes in order by library, kernel, and project, then runs all the define() methods, locks the container, and finally runs all the modify() methods.

Mapping Config Modes To Classes

The config modes are mapped to their related config class files via the project-level composer.json file in the extra:aura:config block. The entry key is the config mode, and the entry value is the class to use for that mode.

 1 {
 2     "autoload": {
 3         "psr-0": {
 4             "": "src/"
 5         },
 6         "psr-4": {
 7             "Aura\\Web_Project\\_Config\\": "config/"
 8         }
 9     },
10     "extra": {
11         "aura": {
12             "type": "project",
13             "config": {
14                 "common": "Aura\\Web_Project\\_Config\\Common",
15                 "dev": "Aura\\Web_Project\\_Config\\Dev",
16                 "test": "Aura\\Web_Project\\_Config\\Test",
17                 "prod": "Aura\\Web_Project\\_Config\\Prod"
18             }
19         }
20     }
21 }

Config classes are autoloaded via a PSR-4 entry for that project namespace.

The “common” config class is always loaded regardless of the actual config mode. For example, if the config mode is dev, first the Common class is loaded, and then the Dev class.

Changing Config Settings

First, open the config file for the related config mode. To change configuration params, setters, and services, edit the define() method. To programmatically change a service after all definitions are complete, edit the modify() method.

Adding A Config Mode

If you want to add a new configuration mode, say qa, you need to do three things.

First, create a config class for it in config/:

 1 <?php
 2 namespace Aura\Web_Project\_Config;
 3 
 4 use Aura\Di\Config;
 5 use Aura\Di\Container;
 6 
 7 class Qa extends Config
 8 {
 9     public function define(Container $di)
10     {
11         // define params, setters, and services here
12     }
13 
14     public function modify(Container $di)
15     {
16         // modify existing services here
17     }
18 }
19 ?>

Next, edit the project-level composer.json file to add the new config mode with its related class:

 1 {
 2     "extra": {
 3         "aura": {
 4             "type": "project",
 5             "config": {
 6                 "common": "Aura\\Web_Project\\_Config\\Common",
 7                 "dev": "Aura\\Web_Project\\_Config\\Dev",
 8                 "test": "Aura\\Web_Project\\_Config\\Test",
 9                 "prod": "Aura\\Web_Project\\_Config\\Prod",
10                 "qa": "Aura\\Web_Project\\_Config\\Qa"
11             }
12         }
13     }
14 }

Finally, run composer update so that Composer makes the necessary changes to the autoloader system.

Routing

Configuration of routing and dispatching is done via the project-level config/ class files. If a route needs to be available in every config mode, edit the project-level config/Common.php class file. If it only needs to be available in a specific mode, e.g. dev, then edit the config file for that mode (config/Dev.php).

The modify() method is where we get the router service (‘aura/web-kernel:router’) and add routes to the application.

 1 <?php
 2 namespace Aura\Framework_Project\_Config;
 3 
 4 use Aura\Di\Config;
 5 use Aura\Di\Container;
 6 
 7 class Common extends Config
 8 {
 9     public function define(Container $di)
10     {
11         // define params, setters, and services here
12     }
13 
14     public function modify(Container $di)
15     {
16         // get the router service
17         $router = $di->get('aura/web-kernel:router');
18         // ... your application routes go below
19     }
20 }

The aura/web-kernel:router is an object of type Aura\Router\Router . So if you are familiar with Aura.Router then you are done with this chapter, else read on.

Aura framework can act both as a micro framework or full stack framework. If you are using it as a micro framework, you can set a Closure as the action value, else set the same name of the action in the dispatcher. Don’t worry, we will cover dispatching in next chapter.

Note: This chapter gives you a basic understanding of the different types of methods available in router.

Adding a Route

We will not be showing the whole config file to reduce the space used. This document assumes you are adding the route in the modify() method after getting the router service.

To create a route, call the add() method on the Router. Named path-info params are placed inside braces in the path.

 1 // add a simple named route without params
 2 $router->add('home', '/');
 3 
 4 // add a simple unnamed route with params
 5 $router->add(null, '/{action}/{id}');
 6 
 7 // add a named route with an extended specification
 8 $router->add('blog.read', '/blog/read/{id}{format}')
 9     ->addTokens(array(
10         'id'     => '\d+',
11         'format' => '(\.[^/]+)?',
12     ))
13     ->addValues(array(
14         'action'     => 'BlogReadAction',
15         'format'     => '.html',
16     ));

You can create a route that matches only against a particular HTTP method as well. The following Router methods are identical to add() but require the related HTTP method:

  • $router->addGet()
  • $router->addDelete()
  • $router->addOption()
  • $router->addPatch()
  • $router->addPost()
  • $router->addPut()

Advanced Usage

Extended Route Specification

You can extend a route specification with the following methods:

  • addTokens() – Adds regular expression subpatterns that params must match.
    1   addTokens(array(
    2       'id' => '\d+',
    3   ))
    

    Note that setTokens() is also available, but this will replace any previous subpatterns entirely, instead of merging with the existing subpatterns.

  • addServer() – Adds regular expressions that server values must match.
    1   addServer(array(
    2       'REQUEST_METHOD' => 'PUT|PATCH',
    3   ))
    

    Note that setServer() is also available, but this will replace any previous expressions entirely, instead of merging with the existing expressions.

  • addValues() – Adds default values for the params.
    1   addValues(array(
    2       'year' => '1979',
    3       'month' => '11',
    4       'day' => '07'
    5   ))
    

    Note that setValues() is also available, but this will replace any previous default values entirely, instead of merging with the existing default value.

  • setSecure() – When true the $server['HTTPS'] value must be on, or the request must be on port 443; when false, neither of those must be in place.
  • setWildcard() – Sets the name of a wildcard param; this is where arbitrary slash-separated values appearing after the route path will be stored.
  • setRoutable() – When false the route will be used only for generating paths, not for matching (true by default).
  • setIsMatchCallable() – A custom callable with the signature function(array $server, \ArrayObject $matches) that returns true on a match, or false if not. This allows developers to build any kind of matching logic for the route, and to change the $matches for param values from the path.
  • setGenerateCallable() – A custom callable with the signature function(\ArrayObject $data). This allows developers to modify the data for path interpolation.

Here is a full extended route specification named read:

 1 $router->add('blog.read', '/blog/read/{id}{format}')
 2     ->addTokens(array(
 3         'id' => '\d+',
 4         'format' => '(\.[^/]+)?',
 5         'REQUEST_METHOD' => 'GET|POST',
 6     ))
 7     ->addValues(array(
 8         'id' => 1,
 9         'format' => '.html',
10     ))
11     ->setSecure(false)
12     ->setRoutable(false)
13     ->setIsMatchCallable(function(array $server, \ArrayObject $matches) {
14 
15         // disallow matching if referred from example.com
16         if ($server['HTTP_REFERER'] == 'http://example.com') {
17             return false;
18         }
19 
20         // add the referer from $server to the match values
21         $matches['referer'] = $server['HTTP_REFERER'];
22         return true;
23 
24     })
25     ->setGenerateCallable(function (\ArrayObject $data) {
26         $data['foo'] = 'bar';
27     });

Default Route Specifications

You can set the default route specifications with the following Router methods; the values will apply to all routes added thereafter.

 1 // add to the default 'tokens' expressions; setTokens() is also available
 2 $router->addTokens(array(
 3     'id' => '\d+',
 4 ));
 5 
 6 // add to the default 'server' expressions; setServer() is also available
 7 $router->addServer(array(
 8     'REQUEST_METHOD' => 'PUT|PATCH',
 9 ));
10 
11 // add to the default param values; setValues() is also available
12 $router->addValues(array(
13     'format' => null,
14 ));
15 
16 // set the default 'secure' value
17 $router->setSecure(true);
18 
19 // set the default wildcard param name
20 $router->setWildcard('other');
21 
22 // set the default 'routable' flag
23 $router->setRoutable(false);
24 
25 // set the default 'isMatch()' callable
26 $router->setIsMatchCallable(function (...) { ... });
27 
28 // set the default 'generate()' callable
29 $router->setGenerateCallable(function (...) { ... });

Simple Routes

You don’t need to specify an extended route specification. With the following simple route …

1 $router->add('archive', '/archive/{year}/{month}/{day}');

… the Router will use a default subpattern that matches everything except slashes for the path params. Thus, the above simple route is equivalent to the following extended route:

1 $router->add('archive', '/archive/{year}/{month}/{day}')
2     ->addTokens(array(
3         'year'  => '[^/]+',
4         'month' => '[^/]+',
5         'day'   => '[^/]+',
6     ));

Automatic Params

The Router will automatically populate values for the action route param if one is not set manually.

1 // ['action' => 'foo.bar'] because it has not been set otherwise
2 $router->add('foo.bar', '/path/to/bar');
3 
4 // ['action' => 'zim'] because we add it explicitly
5 $router->add('foo.dib', '/path/to/dib')
6        ->addValues(array('action' => 'zim'));
7 
8 // the 'action' param here will be whatever the path value for {action} is
9 $router->add('/path/to/{action}');

Optional Params

Sometimes it is useful to have a route with optional named params. None, some, or all of the optional params may be present, and the route will still match.

To specify optional params, use the notation {/param1,param2,param3} in the path. For example:

1 $router->add('archive', '/archive{/year,month,day}')
2     ->addTokens(array(
3         'year'  => '\d{4}',
4         'month' => '\d{2}',
5         'day'   => '\d{2}'
6     ));

With that, the following routes will all match the ‘archive’ route, and will set the appropriate values:

1 /archive
2 /archive/1979
3 /archive/1979/11
4 /archive/1979/11/07

Optional params are sequentially optional. This means that, in the above example, you cannot have a “day” without a “month”, and you cannot have a “month” without a “year”.

Only one set of optional params per path is recognized by the Router.

Optional params belong at the end of a route path; placing them elsewhere may result in unexpected behavior.

Wildcard Params

Sometimes it is useful to allow the trailing part of the path be anything at all. To allow arbitrary trailing params on a route, extend the route definition with setWildcard() to specify param name under which the arbitrary trailing param values will be stored.

1 $router->add('wild_post', '/post/{id}')
2     ->setWildcard('other');

Attaching Route Groups

You can add a series of routes all at once under a single “mount point” in your application. For example, if you want all your blog-related routes to be mounted at /blog in your application, you can do this:

 1 $name_prefix = 'blog';
 2 $path_prefix = '/blog';
 3 
 4 $router->attach($name_prefix, $path_prefix, function ($router) {
 5 
 6     $router->add('browse', '{format}')
 7         ->addTokens(array(
 8             'format' => '(\.json|\.atom|\.html)?'
 9         ))
10         ->addValues(array(
11             'format' => '.html',
12         ));
13 
14     $router->add('read', '/{id}{format}', array(
15         ->addTokens(array(
16             'id'     => '\d+',
17             'format' => '(\.json|\.atom|\.html)?'
18         )),
19         ->addValues(array(
20             'format' => '.html',
21         ));
22 
23     $router->add('edit', '/{id}/edit{format}', array(
24         ->addTokens(array(
25             'id' => '\d+',
26             'format' => '(\.json|\.atom|\.html)?'
27         ))
28         ->addValues(array(
29             'format' => '.html',
30         ));
31 });

Each of the route names will be prefixed with ‘blog.’, and each of the route paths will be prefixed with /blog, so the effective route names and paths become:

  • blog.browse => /blog{format}
  • blog.read => /blog/{id}{format}
  • blog.edit => /blog/{id}/edit{format}

You can set other route specification values as part of the attachment specification; these will be used as the defaults for each attached route, so you don’t need to repeat common information. (Setting these values will not affect routes outside the attached group.)

 1 $name_prefix = 'blog';
 2 $path_prefix = '/blog';
 3 
 4 $router->attach($name_prefix, $path_prefix, function ($router) {
 5 
 6     $router->setTokens(array(
 7         'id'     => '\d+',
 8         'format' => '(\.json|\.atom)?'
 9     ));
10 
11     $router->setValues(array(
12         'format' => '.html',
13     ));
14 
15     $router->add('browse', '');
16     $router->add('read', '/{id}{format}');
17     $router->add('edit', '/{id}/edit');
18 });

Attaching REST Resource Routes

The router can attach a series of REST resource routes for you with the attachResource() method:

1 $router->attachResource('blog', '/blog');

That method call will result in the following routes being added:

Route Name HTTP Method Route Path Purpose
blog.browse GET /blog{format} Browse multiple resources
blog.read GET /blog/{id}{format} Read a single resource
blog.edit GET /blog/{id}/edit The form for editing a resource
blog.add GET /blog/add The form for adding a resource
blog.delete DELETE /blog/{id} Delete a single resource
blog.create POST /blog Create a new resource
blog.update PATCH /blog/{id} Update part of an existing resource
blog.replace PUT /blog/{id} Replace an entire existing resource

The {id} token is whatever has already been defined in the router; if not already defined, it will be any series of numeric digits. Likewise, the {format} token is whatever has already been defined in the router; if not already defined, it is an optional dot-format file extension (including the dot itself).

The action value is the same as the route name.

If you want calls to attachResource() to create a different series of REST routes, use the setResourceCallable() method to set your own callable to create them.

1 $router->setResourceCallable(function ($router) {
2     $router->setTokens(array(
3         'id' => '([a-f0-9]+)'
4     ));
5     $router->addPost('create', '/{id}');
6     $router->addGet('read', '/{id}');
7     $router->addPatch('update', '/{id}');
8     $router->addDelete('delete', '/{id}');
9 });

The example will cause only four CRUD routes, using hexadecimal resource IDs, to be added for the resource when you call attachResource().

Dispatching

Aura web/framework projects can handle different variations of dispatching with the help of Aura.Dispatcher.

So if your application starts small and grows, it is easy to modify the application routes acting as a micro framework to a full-stack style.

Microframework

The following is an example of a micro-framework style route, where the action logic is embedded in the route params. In the modify() config method, we retrieve the shared aura/web-kernel:request and aura/web-kernel:response services, along with the aura/web-kernel:router service. We then add a route names blog.read and embed the action code as a closure.

 1 <?php
 2 namespace Aura\Web_Project\_Config;
 3 
 4 use Aura\Di\Config;
 5 use Aura\Di\Container;
 6 
 7 class Common extends Config
 8 {
 9     // ...
10 
11     public function modify(Container $di)
12     {
13         $request = $di->get('aura/web-kernel:request');
14         $response = $di->get('aura/web-kernel:response');
15 
16         $router = $di->get('aura/web-kernel:router');
17         $router
18             ->add('blog.read', '/blog/read/{id}')
19             ->addValues(array(
20                 'action' => function ($id) use ($request, $response) {
21                     $content = "Reading blog post $id";
22                     $response->content->set(htmlspecialchars(
23                         $content, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8'
24                     ));
25                 }
26             ));
27     }
28 
29     // ...
30 }

Modified Micro-Framework Style

We can modify the above example to put the action logic in the dispatcher instead of the route itself.

Extract the action closure to the dispatcher under the name blog.read. Then, in the route, use a action value that matches the name in the dispatcher.

 1 <?php
 2 namespace Aura\Web_Project\_Config;
 3 
 4 use Aura\Di\Config;
 5 use Aura\Di\Container;
 6 
 7 class Common extends Config
 8 {
 9     // ...
10 
11     public function modify(Container $di)
12     {
13         $request = $di->get('aura/web-kernel:request');
14         $response = $di->get('aura/web-kernel:response');
15 
16         $dispatcher = $di->get('aura/web-kernel:dispatcher');
17         $dispatcher->setObject(
18             'blog.read',
19             function ($id) use ($request, $response) {
20                 $content = "Reading blog post $id";
21                 $response->content->set(htmlspecialchars(
22                     $content, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8'
23                 ));
24             }
25         );
26 
27         $router = $di->get('aura/web-kernel:router');
28         $router
29             ->add('blog.read', '/blog/read/{id}')
30             ->addValues(array(
31                 'action' => 'blog.read',
32             ));
33     }
34 
35     // ...
36 }

Full-Stack Style

You can migrate from a micro-framework style to a full-stack style (or start with full-stack style in the first place).

First, define a action class and place it in the project src/ directory.

 1 <?php
 2 /**
 3  * {$PROJECT_PATH}/src/App/Actions/BlogRead.php
 4  */
 5 namespace App\Actions;
 6 
 7 use Aura\Web\Request;
 8 use Aura\Web\Response;
 9 
10 class BlogRead
11 {
12     public function __construct(Request $request, Response $response)
13     {
14         $this->request = $request;
15         $this->response = $response;
16     }
17 
18     public function __invoke($id)
19     {
20         $content = "Reading blog post $id";
21         $this->response->content->set(htmlspecialchars(
22             $content, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8'
23         ));
24     }
25 }

Next, tell the project how to build the BlogRead through the DI Container. Edit the project config/Common.php file to configure the Container to pass the aura/web-kernel:request and aura/web-kernel:response service objects to the BlogRead constructor.

 1 <?php
 2 namespace Aura\Web_Project\_Config;
 3 
 4 use Aura\Di\Config;
 5 use Aura\Di\Container;
 6 
 7 class Common extends Config
 8 {
 9     public function define(Container $di)
10     {
11         // ...
12 
13         $di->params['App\Actions\BlogRead'] = array(
14             'request' => $di->lazyGet('aura/web-kernel:request'),
15             'response' => $di->lazyGet('aura/web-kernel:response'),
16         );
17     }
18 
19     // ...
20 }

After that, put the App\Actions\BlogRead object in the dispatcher under the name blog.read as a lazy-loaded instantiation …

 1 <?php
 2 namespace Aura\Web_Project\_Config;
 3 
 4 use Aura\Di\Config;
 5 use Aura\Di\Container;
 6 
 7 class Common extends Config
 8 {
 9     // ...
10 
11     public function modify(Container $di)
12     {
13         // ...
14         $dispatcher = $di->get('aura/web-kernel:dispatcher');
15         $dispatcher->setObject(
16             'blog.read',
17             $di->lazyNew('App\Actions\BlogRead')
18         );
19     }
20 
21     // ...
22 }

… and finally, point the router to the blog.read action object:

 1 <?php
 2 namespace Aura\Web_Project\_Config;
 3 
 4 use Aura\Di\Config;
 5 use Aura\Di\Container;
 6 
 7 class Common extends Config
 8 {
 9     // ...
10 
11     public function modify(Container $di)
12     {
13         // ...
14         $router = $di->get('aura/web-kernel:router');
15         $router
16             ->add('blog.read', '/blog/read/{id}')
17             ->addValues(array(
18                 'action' => 'blog.read',
19             ));
20     }
21 
22     // ...
23 }

Request

The Request object describes the current web execution context for PHP. Note that it is not an HTTP request object proper, since it includes things like $_ENV and various non-HTTP $_SERVER keys.

You can get the Request object from the DI,

1 <?php
2 $request = $di->get('aura/web-kernel:request');
3 
4 // or can inject to another class as
5 
6 $di->lazyGet('aura/web-kernel:request');

The Request object contains several property objects. Some represent a copy of the PHP superglobals …

  • $request->cookies for $_COOKIES
  • $request->env for $_ENV
  • $request->files for $_FILES
  • $request->post for $_POST
  • $request->query for $_GET
  • $request->server for $_SERVER

… and others represent more specific kinds of information about the request:

  • $request->client for the client making the request
  • $request->content for the raw body of the request
  • $request->headers for the request headers
  • $request->method for the request method
  • $request->accept for content negotiation
  • $request->params for path-info parameters
  • $request->url for the request URL

The Request object has only one method, isXhr(), to indicate if the request is an XmlHttpRequest or not.

Superglobals

Each of the superglobal representation objects has a single method, get(), that returns the value of a key in the superglobal, or an alternative value if the key is not present. The values here are read-only.

 1 <?php
 2 // returns the value of $_POST['field_name'], or 'not set' if 'field_name' is
 3 // not present in $_POST
 4 $field_name = $request->post->get('field_name', 'not set');
 5 
 6 // if no key is given, returns an array of all values in the superglobal
 7 $all_server_values = $request->server->get();
 8 
 9 // the $_FILES array has been rearranged to look like $_POST
10 $file = $request->files->get('file_field', array());
11 ?>

Client

The $request->client object has these methods:

  • getForwardedFor() returns the values of the X-Forwarded-For headers as an array.
  • getReferer() returns the value of the Referer header.
  • getIp() returns the value of $_SEVER['REMOTE_ADDR'], or the appropriate value of X-Forwarded-For.
  • getUserAgent() return the value of the User-Agent header.
  • isCrawler() returns true if the User-Agent header matches one of a list of bot/crawler/robot user agents (otherwise false).
  • isMobile() returns true if the User-Agent header matches one of a list of mobile user agents (otherwise false).

Content

The $request->content object has these methods:

  • getType() returns the content-type of the request body
  • getRaw() return the raw request body
  • get() returns the request body after decoding it based on the content type

The Content object has two decoders built in. If the request specified a content type of application/json, the get() method will automatically decode the body with json_decode(). Likewise, if the content type is application/x-www-form-urlencoded, the get() method will automatically decode the body with parse_str().

Headers

The $request->headers object has a single method, get(), that returns the value of a particular header, or an alternative value if the key is not present. The values here are read-only.

1 <?php
2 // returns the value of 'X-Header' if present, or 'not set' if not
3 $header_value = $request->headers->get('X-Header', 'not set');
4 ?>

Method

The $request->method object has these methods:

  • get(): returns the request method value
  • isDelete(): Did the request use a DELETE method?
  • isGet(): Did the request use a GET method?
  • isHead(): Did the request use a HEAD method?
  • isOptions(): Did the request use an OPTIONS method?
  • isPatch(): Did the request use a PATCH method?
  • isPut(): Did the request use a PUT method?
  • isPost(): Did the request use a POST method?
1 <?php
2 if ($request->method->isPost()) {
3     // perform POST actions
4 }
5 ?>

You can also call is*() on the Method object; the part after is is treated as custom HTTP method name, and checks if the request was made using that HTTP method.

1 <?php
2 if ($request->method->isCustom()) {
3     // perform CUSTOM actions
4 }
5 ?>

Sometimes forms use a special field to indicate a custom HTTP method on a POST. By default, the Method object honors the _method form field.

1 <?php
2 // a POST with the field '_method' will use the _method value instead of POST
3 $_SERVER['REQUEST_METHOD'] = 'POST';
4 $_POST['_method'] = 'PUT';
5 echo $request->method->get(); // PUT
6 ?>

Params

Unlike most Request property objects, the Params object is read-write (not read-only). The Params object allows you to set application-specific parameter values. These are typically discovered by parsing a URL path through a router of some sort (e.g. Aura.Router).

The $request->params object has two methods:

  • set() to set the array of parameters
  • get() to get back a specific parameter, or the array of all parameters

For example:

 1 <?php
 2 // parameter values discovered by a routing mechanism
 3 $values = array(
 4     'controller' => 'blog',
 5     'action' => 'read',
 6     'id' => '88',
 7 );
 8 
 9 // set the parameters on the request
10 $request->params->set($values);
11 
12 // get the 'id' param, or false if it is not present
13 $id = $request->params->get('id', false);
14 
15 // get all the params as an array
16 $all_params = $request->params->get();
17 ?>

Url

The $request->url object has two methods:

  • get() returns the full URL string; or, if a component constant is passed, returns only that part of the URL
  • isSecure() indicates if the request is secure, whether via SSL, TLS, or forwarded from a secure protocol
 1 <?php
 2 // get the full URL string
 3 $string = $request->url->get();
 4 
 5 // get a particular part of the URL; for the component constants, see
 6 // http://php.net/parse-url
 7 $scheme   = $request->url->get(PHP_URL_SCHEME);
 8 $host     = $request->url->get(PHP_URL_HOST);
 9 $port     = $request->url->get(PHP_URL_PORT);
10 $user     = $request->url->get(PHP_URL_USER);
11 $pass     = $request->url->get(PHP_URL_PASS);
12 $path     = $request->url->get(PHP_URL_PATH);
13 $query    = $request->url->get(PHP_URL_QUERY);
14 $fragment = $request->url->get(PHP_URL_FRAGMENT);
15 ?>

Response

The Response object describes the web response that should be sent to the client. It is not an HTTP response object proper. Instead, it is a series of hints to be used when building the HTTP response with the delivery mechanism of your choice.

Setting values on the Response object does not cause values to be sent to the client. The Response can be inspected during testing to see if the correct values have been set without generating output.

You can get the Response object from the DI,

1 <?php
2 $di->get('aura/web-kernel:response');
3 
4 // or can inject to another class as
5 
6 $di->lazyGet('aura/web-kernel:response');

The Response object is composed of several property objects representing different parts of the response:

  • $response->status for the status code, status phrase, and HTTP version
  • $response->headers for non-cookie headers
  • $response->cookies for cookie headers
  • $response->content for describing the response content, and for convenience methods related to content type, charset, disposition, and filename
  • $response->cache for convenience methods related to cache headers
  • $response->redirect for convenience methods related to Location and Status

Status

Use the $response->status object as follows:

 1 <?php
 2 // set the status code, phrase, and version at once
 3 $response->status->set('404', 'Not Found', '1.1');
 4 
 5 // set them individually
 6 $response->status->setCode('404');
 7 $response->status->setPhrase('Not Found');
 8 $response->status->setVersion('1.1');
 9 
10 // get the full status line
11 $status = $response->status->get(); // "HTTP/1.1 404 Not Found"
12 
13 // get the status values individually
14 $code    = $response->status->getCode();
15 $phrase  = $response->status->getPhrase();
16 $version = $response->status->getVersion();
17 ?>

Headers

The $response->headers object has these methods:

  • set() to set a single header, resetting previous values on that header
  • get() to get a single header, or to get all headers
 1 <?php
 2 // X-Header-Value: foo
 3 $response->headers->set('X-Header-Value', 'foo');
 4 
 5 // get the X-Header-Value
 6 $value = $response->headers->get('X-Header-Value');
 7 
 8 // get all headers
 9 $all_headers = $response->headers->get();
10 ?>

Setting a header value to null, false, or an empty string will remove that header; setting it to zero will not remove it.

Cookies

The $response->cookies object has these methods:

  • setExpire() sets the default expiration for cookies
  • setPath() sets the default path for cookies
  • setDomain() sets the default domain for cookies
  • setSecure() sets the default secure value for cookies
  • setHttpOnly() sets the default for whether or not cookies will be sent by HTTP only.
  • set() sets a cookie name and value along with its meta-data. This method mimics the setcookie() PHP function. If meta- data such as path, domain, secure, and httponly are missing, the defaults will be filled in for you.
  • get() returns a cookie by name, or all the cookies at once.
 1 <?php
 2 // set a default expire time to 10 minutes from now on a domain and path
 3 $response->cookies->setDomain('example.com');
 4 $response->cookies->setPath('/');
 5 $response->cookies->setExpire('+600');
 6 
 7 // set two cookie values
 8 $response->cookies->set('foo', 'bar');
 9 $response->cookies->set('baz', 'dib');
10 
11 // get a cookie descriptor array from the response
12 $foo_cookie = $response->cookies->get('foo');
13 
14 // get all the cookie descriptor arrays from the response, keyed by name
15 $cookies = $response->cookies->get();
16 ?>

The cookie descriptor array looks like this:

 1 <?php
 2 $cookies['foo'] = array(
 3     'value' => 'bar',
 4     'expire' => '+600', // will become a UNIX timestamp with strtotime()
 5     'path' => '/',
 6     'domain' => 'example.com',
 7     'secure' => false,
 8     'httponly' => true,
 9 );
10 ?>

Content

The $response->content object has these convenience methods related to the response content and content headers:

  • set() sets the body content of the response (this can be anything at all, including an array, a callable, an object, or a string – it is up to the sending mechanism to translate it properly)
  • get() get the body content of the response which has been set via set()
  • setType() sets the Content-Type header
  • getType() returns the Content-Type (not including the charset)
  • setCharset() sets the character set for the Content-Type
  • getCharset() returns the charset portion of the Content-Type header
  • setDisposition() sets the Content-Disposition type and filename
  • setEncoding() sets the Content-Encoding header
 1 <?php
 2 // set the response content, type, and charset
 3 $response->content->set(array('foo' => 'bar', 'baz' => 'dib'));
 4 $response->content->setType('application/json');
 5 
 6 // elsewhere, before sending the response, modify the content based on type
 7 switch ($response->content->getType()) {
 8     case 'application/json':
 9         $json = json_encode($response->content->get());
10         $response->content->set($json);
11         break;
12     // ...
13 }
14 ?>

Cache

The $response->cache object has several convenience methods related to HTTP cache headers.

  • reset() removes all cache-related headers
  • disable() turns off caching by removing all cache-related headers, then sets the following:
    1   Cache-Control: max-age=0, no-cache, no-store, must-revalidate, proxy-revalidate
    2   Expires: Mon, 01 Jan 0001 00:00:00 GMT
    3   Pragma: no-cache
    
  • setAge() sets the Age header value in seconds
  • setControl() sets an array of Cache-Control header directives all at once; alternatively, use the individual directive methods:
    • setPublic() and setPrivate() set the public and private cache control directives (each turns off the other)
    • setMaxAge() and setSharedMaxAge() set the max-age and s-maxage cache control directives (set to null or false to remove them)
    • setNoCache() and setNoStore() set the no-cache and no-store cache control directives (set to null or false to remove them)
    • setMustRevalidate() and setProxyRevalidate() to set the must-revalidate and proxy-revalidate directives (set to null or false to remove them)
  • setEtag() and setWeakEtag() set the ETag header value
  • setExpires() sets the Expires header value; will convert recognizable date formats and DateTime objects to a correctly formatted HTTP date
  • setLastModified() sets the Last-Modified header value; will convert recognizable date formats and DateTime objects to a correctly formatted HTTP date
  • setVary() sets the Vary header; pass an array for comma-separated values

For more information about caching headers, please consult the HTTP 1.1 headers spec along with these descriptions from Palizine.

Redirect

The $response->redirect object has several convenience methods related to status and Location headers for redirection.

  • to($location, $code = 302, phrase = null) sets the status and headers for redirection to an arbitrary location with an arbitrary status code and phrase
  • afterPost($location) redirects to the $location with a 303 See Other status; this automatically disables HTTP caching
  • created($location) redirects to $location with 201 Created
  • movedPermanently($location) redirects to $location with 301 Moved Permanently
  • found($location) redirects to $location with 302 Found
  • seeOther($location) redirects to $location with 303 See Other; this automatically disables HTTP caching
  • temporaryRedirect($location) redirects to $location with 307 Temporary Redirect
  • permanentRedirect($location) redirects to $location with 308 Permanent Redirect

Dependency Injection

Aura.Di is a dependency injection container system with the following features:

  • constructor and setter injection
  • explicit and implicit auto-resolution of typehinted constructor parameter values
  • configuration of setters across interfaces and traits
  • inheritance of constructor parameter and setter method values
  • lazy-loaded services, values, and instances
  • instance factories

We will concentrate on constructor and setter injection in this chapter for easiness. It is recommend you should read Aura.Di documentation

Setting And Getting Services

A “service” is an object stored in the Container under a unique name. Any time you get() the named service, you always get back the same object instance.

 1 <?php
 2 // define the Example class
 3 class Example
 4 {
 5     // ...
 6 }
 7 
 8 // set the service
 9 $di->set('service_name', new Example);
10 
11 // get the service
12 $service1 = $di->get('service_name');
13 $service2 = $di->get('service_name');
14 
15 // the two service objects are the same
16 var_dump($service1 === $service2); // true
17 ?>

That usage is great if we want to create the Example instance at the same time we set the service. However, we generally want to create the service instance at the moment we get it, not at the moment we set it.

The technique of delaying instantiation until get() time is called “lazy loading.” To lazy-load an instance, use the lazyNew() method on the Container and give it the class name to be created:

1 <?php
2 // set the service as a lazy-loaded new instance
3 $di->set('service_name', $di->lazyNew('Example'));
4 ?>

Now the service is created only when we we get() it, and not before. This lets us set as many services as we want, but only incur the overhead of creating the instances we actually use.

Constructor Injection

When we use the Container to instantiate a new object, we often need to inject (i.e., set) constructor parameter values in various ways.

Default Parameter Values

We can define default values for constructor parameters using the $di->params array on the Container.

Let’s look at a class that takes some constructor parameters:

 1 <?php
 2 class ExampleWithParams
 3 {
 4     protected $foo;
 5     protected $bar;
 6     public function __construct($foo, $bar)
 7     {
 8         $this->foo = $foo;
 9         $this->bar = $bar;
10     }
11 }
12 ?>

If we were to try to set a service using $di->lazyNew('ExampleWithParams'), the instantiation would fail. The $foo param is required, and the Container does not know what to use for that value.

To remedy this, we tell the Container what values to use for each ExampleWithParams constructor parameter by name using the $di->params array:

1 <?php
2 $di->params['ExampleWithParams']['foo'] = 'foo_value';
3 $di->params['ExampleWithParams']['bar'] = 'bar_value';
4 ?>

Now when a service is defined with $di->lazyNew('ExampleWithParams'), the instantiation will work correctly. Each time we create an ExampleWithParams instance through the Container, it will apply the $di->params['ExampleWithParams'] values.

Instance-Specific Parameter Values

If we want to override the default $di->params values for a specific new instance, we can pass a $params array as the second argument to lazyNew() to merge with the default values. For example:

1 <?php
2 $di->set('service_name', $di->lazyNew(
3     'ExampleWithParams',
4     array(
5         'bar' => 'alternative_bar_value',
6     )
7 ));
8 ?>

This will leave the $foo parameter default in place, and override the $bar parameter value, for just that instance of the ExampleWithParams.

Lazy-Loaded Services As Parameter Values

Sometimes a class will need another service as one of its parameters. By way of example, the following class needs a database connection:

 1 <?php
 2 class ExampleNeedsService
 3 {
 4     protected $db;
 5     public function __construct($db)
 6     {
 7         $this->db = $db;
 8     }
 9 }
10 ?>

To inject a shared service as a parameter value, use $di->lazyGet() so that the service object is not created until the ExampleNeedsService object is created:

1 <?php
2 $di->params['ExampleNeedsService']['db'] = $di->lazyGet('db_service');
3 ?>

This keeps the service from being created until the very moment it is needed. If we never instantiate anything that needs the service, the service itself will never be instantiated.

Setter Injection

This package supports setter injection in addition to constructor injection. (These can be combined as needed.)

Setter Method Values

After the Container constructs a new instance of an object, we can specify that certain methods should be called with certain values immediately after instantiation by using the $di->setter array. Say we have class like the following:

 1 <?php
 2 class ExampleWithSetter
 3 {
 4     protected $foo;
 5 
 6     public function setFoo($foo)
 7     {
 8         $this->foo = $foo;
 9     }
10 }
11 ?>

We can specify that, by default, the setFoo() method should be called with a specific value after construction like so:

1 <?php
2 $di->setter['ExampleWithSetter']['setFoo'] = 'foo_value';
3 ?>

The value can be any valid value: a literal, a call to lazyNew() or lazyGet(), and so on.

Note, however, that auto-resolution does not apply to setter methods. This is because the Container does not know which methods are setters and which are “normal use” methods.

Note also that this works only with explicitly-defined setter methods. Setter methods that exist only via magic __call() will not be honored.

Instance-Specific Setter Values

As with constructor injection, we can note instance-specific setter values to use in place of the defaults. We do so via the third argument to $di->lazyNew(). For example:

1 <?php
2 $di->set('service_name', $di->lazyNew(
3     'ExampleWithSetters',
4     array(), // no $params overrides
5     array(
6         'setFoo' => 'alternative_foo_value',
7     )
8 ));
9 ?>

View

Aura web framework doesn’t come packaged with any templating. The reason is love for templating differs from person to person.

With the help of foa/responder-bundle, we can integrate

The advantage of using foa/responder-bundle is you have a common method render, which helps you to switch between template engine with less overhead.

It also helps integrating the Action Domain Responder which we will cover on a different chapter.

Installation

1 composer require foa/responder-bundle

Choose your templating engine and install the same. In this example we are going to make use of foa/html-view-bundle which integrates aura/view and aura/html.

1 composer require foa/html-view-bundle

Configuration

Add the below lines in {PROJECT_PATH}/config/Common.php in the define method.

1 <?php
2 $di->params['Aura\View\TemplateRegistry']['paths'] = array(dirname(__DIR__) '/te\
3 mplates');
4 $di->params['FOA\Responder_Bundle\Renderer\AuraView']['engine'] = $di->lazyNew('\
5 Aura\View\View');
6 $di->set('renderer', $di->lazyNew('FOA\Responder_Bundle\Renderer\AuraView'));

Setting the paths to Aura\View\TemplateRegistry will only work for version >= 2.1

Integration with actions

Let us integrate the above renderer to the full stack framework example shown in previous chapter.

Edit the {$PROJECT_PATH}/src/App/Actions/BlogRead.php to inject an instance of FOA\Responder_Bundle\Renderer\RendererInterface.

 1 <?php
 2 /**
 3  * {$PROJECT_PATH}/src/App/Actions/BlogRead.php
 4  */
 5 namespace App\Actions;
 6 
 7 use Aura\Web\Request;
 8 use Aura\Web\Response;
 9 use FOA\Responder_Bundle\Renderer\RendererInterface;
10 
11 class BlogRead
12 {
13     // ...
14 
15     public function __construct(
16         Request $request,
17         Response $response,
18         RendererInterface $renderer
19     ) {
20         $this->request = $request;
21         $this->response = $response;
22         $this->renderer = $renderer;
23     }
24 
25     public function __invoke($id)
26     {
27         // set the content rendered from the renderer
28         $this->response->content->set($this->renderer->render(array('id' => $id)\
29 , 'read'));
30     }
31 }

Modify the config/Common.php for the DI container to pass an instance that satisfies FOA\Responder_Bundle\Renderer\RendererInterface to App\Actions\BlogRead.

 1 <?php
 2 namespace Aura\Web_Project\_Config;
 3 
 4 use Aura\Di\Config;
 5 use Aura\Di\Container;
 6 
 7 class Common extends Config
 8 {
 9     public function define(Container $di)
10     {
11         // ...
12 
13         $di->params['App\Actions\BlogRead'] = array(
14             'request' => $di->lazyGet('aura/web-kernel:request'),
15             'response' => $di->lazyGet('aura/web-kernel:response'),
16             'renderer' => $di->lazyGet('renderer'),
17         );
18     }
19 
20     // ...
21 }

Create the file templates/read.php with contents

1 echo "Reading blog post {$this->id}!";

Please refer respective library documentation on usage inside template file.

Forms

Forms are an integral part of web application. Aura.Input is a tool to describe HTML fields and values.

Installation

Even though Aura.Input has a base filter implementation, it is good to integrate a powerful filter system like Aura.Filter.

The foa/filter-input-bundle, and foa/filter-intl-bundle already have done the heavy lifting integrating the aura/input, aura/filter, aura/intl and having the necessary DI configuration.

Add those bundles to your composer.json.

1 {
2     "require": {
3         "foa/filter-input-bundle": "1.1.*",
4         "foa/filter-intl-bundle": "1.1.*"
5     }
6 }

and run

1 composer update

Usage

Inorder to create a form, we need to extend the Aura\Input\Form class and override the init() method.

An example is shown below.

 1 <?php
 2 /**
 3  * {$PROJECT_PATH}/src/App/Input/ContactForm.php
 4  */
 5 namespace App\Input;
 6 
 7 use Aura\Input\Form;
 8 
 9 class ContactForm extends Form
10 {
11     public function init()
12     {
13         $states = array(
14             'AL' => 'Alabama',
15             'AK' => 'Alaska',
16             'AZ' => 'Arizona',
17             'AR' => 'Arkansas',
18             // ...
19         );
20 
21         // set input fields
22         // hint the view layer to treat the first_name field as a text input,
23         // with size and maxlength attributes
24         $this->setField('first_name', 'text')
25              ->setAttribs(array(
26                  'name' => "contact[first_name]",
27                  'id' => 'first_name',
28                 'size' => 20,
29                 'maxlength' => 20,
30              ));
31 
32         // hint the view layer to treat the state field as a select, with a
33         // particular set of options (the keys are the option values,
34         // and the values are the displayed text)
35         $this->setField('state', 'select')
36              ->setAttribs(array(
37                  'name' => "contact[state]",
38                  'id' => 'state',
39              ))
40              ->setOptions($states);
41 
42         $this->setField('message', 'textarea')
43             ->setAttribs([
44                 'name' => "contact[message]",
45                 'id' => 'message',
46                 'cols' => 40,
47                 'rows' => 5,
48             ]);
49         // etc.
50 
51         // get filter object
52         $filter = $this->getFilter();
53         // set your filters.
54         $filter->addSoftRule('first_name', $filter::IS, 'string');
55         $filter->addSoftRule('first_name', $filter::IS, 'strlenMin', 4);
56         $filter->addSoftRule('state', $filter::IS, 'inKeys', array_keys($states)\
57 );
58         $filter->addSoftRule('message', $filter::IS, 'string');
59         $filter->addSoftRule('message', $filter::IS, 'strlenMin', 6);
60     }
61 }

We will talk about Aura.Filter in next chapter.

Note : We are using v1 components of input, intl, filter.

Configuration

If we create App\Input\ContactForm object via the new operator we need to pass the dependencies manually as

1 use Aura\Input\Form;
2 use Aura\Input\Builder;
3 use FOA\Filter_Input_Bundle\Filter;
4 
5 $filter = new Filter();
6 
7 // add rules to the filter
8 
9 $contact_form = new ContactForm(new Builder, $filter);

Creating object via DI configuration helps us not to add the dependencies, add the necessary rules to the filter.

We only need to figure out where we need the form, and use params or setter injection.

1 $di->params['Vendor\Package\SomeDomain']['contact_form'] = $di->lazyNew('App\Inp\
2 ut\ContactForm');

Populating

Form can be populated using fill() method.

1 $this->contact_form->fill($_POST);

In aura term it will be $this->request->post->get()

Validating User Input

You can validate the form via the filter() method.

1 // apply the filters
2 $pass = $this->contact_form->filter();
3 
4 // did all the filters pass?
5 if ($pass) {
6     // yes input is valid.
7 } else {
8     // no; user input is not valid.
9 }

Rendering

Inorder to render the form, we need to pass the ContactForm object and use the Aura.Html helpers.

Assuming you have passed the ContactForm object, and the variable assigned is contact_form you can use the get method on the form object to get the hints of field, and pass to input helper.

An example is given below :

1 echo $this->input($this->contact_form->get('first_name'));

Read more on form helpers here.

In the session chapter we will learn how to set flash message when the form submission was success.

Validation

Aura.Filter is a tool to validate and sanitize data.

We are going to look into version 1 of Aura.Filter.

Installation

We have already installed aura/filter in the previous chapter about forms. If you have not installed please do the same.

Applying Rules to Data Objects

Soft, Hard, and Stop Rules

There are three types of rule processing we can apply:

  • The addSoftRule() method adds a soft rule: if the rule fails, the filter will keep applying all remaining rules to that field and all other fields.
  • The addHardRule() method adds a hard rule: if the rule fails, the filter will not apply any more rules to that field, but it will keep filtering other fields.
  • The addStopRule() method adds a stopping rule: if the rule fails, the filter will not apply any more filters to any more fields; this stops all filtering on the data object.

Validating and Sanitizing

We validate data by applying a rule with one of the following requirements:

  • RuleCollection::IS means the field value must match the rule.
  • RuleCollection::IS_NOT means the field value must not match the rule.
  • RuleCollection::IS_BLANK_OR means the field value must either be blank, or match the rule. This is useful for optional field values that may or may not be filled in.

We sanitize data by applying a rule with one of the following transformations:

  • RuleCollection::FIX to force the field value to comply with the rule; this may forcibly transform the value. Some transformations are not possible, so sanitizing the field may result in an error message.
  • RuleCollection::FIX_BLANK_OR will convert blank values to null; non-blank fields will be forced to comply with the rule. This is useful for sanitizing optional field values that may or may not match the rule.

Each field is sanitized in place; i.e., the data object property will be modified directly.

Blank Values

Aura Filter incorporates the concept of “blank” values, as distinct from isset() and empty(). A value is blank if it is null, an empty string, or a string composed of only whitespace characters. Thus, the following are blank:

1 <?php
2 $blank = [
3     null,           // a null value
4     '',             // an empty string
5     " \r \n \t ",   // a whitespace-only string
6 ];

Integers, floats, booleans, and other non-strings are never counted as blank, even if they evaluate to zero:

1 <?php
2 $not_blank = [
3     0,              // integer
4     0.00,           // float
5     false,          // boolean false
6     [],             // empty array
7     (object) [],    // an object
8 ];

Available Rules

  • alnum: Validate the value as alphanumeric only. Sanitize to leave only alphanumeric characters. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'alnum');
    
  • alpha: Validate the value as alphabetic only. Sanitize to leave only alphabetic characters. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'alpha');
    
  • between: Validate the value as being within or equal to a minimum and maximum value. Sanitize so that values lower than the range are forced up to the minimum; values higher than the range are forced down to the maximum. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'between', $min, $max);
    
  • blank: Validate the value as being blank. Sanitize to null. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'blank');
    
  • bool: Validate the value as being a boolean, or a pseudo-boolean. Pseudo-true values include the strings ‘1’, ‘y’, ‘yes’, and ‘true’; pseudo-false values include the strings ‘0’, ‘n’, ‘no’, and ‘false’. Sanitize to a strict PHP boolean. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'bool');
    
  • creditCard: Validate the value as being a credit card number. The value cannot be sanitized. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'creditCard');
    
  • dateTime: Validate the value as representing a date and/or time. Sanitize the value to a specified format, default 'Y-m-d H:i:s'. Usage (note that this is to sanitize, not validate):
    1   $filter->addSoftRule('field', $filter::FIX, 'dateTime', $format);
    
  • email: Validate the value as being a properly-formed email address. The value cannot be sanitized. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'email');
    
  • equalToField: Validate the value as loosely equal to the value of another field in the data object. Sanitize to the value of that other field. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'equalToField', 'other_field_name');
    
  • equalToValue: Validate the value as loosely equal to a specified value. Sanitize to the specified value. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'equalToValue', $other_value);
    
  • float: Validate the value as representing a float. Sanitize the value to transform it into a float; for weird strings, this may not be what you expect. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'float');
    
  • inKeys: Validate that the value is loosely equal to a key in a given array. The value cannot be sanitized. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'inKeys', $array);
    
  • inValues: Validate that the value is strictly equal to at least one value in a given array. The value cannot be sanitized. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'inValues', $array);
    
  • int: Validate the value as representing an integer Sanitize the value to transform it into an integer; for weird strings, this may not be what you expect. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'int');
    
  • ipv4: Validate the value as an IPv4 address. The value cannot be sanitized. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'ipv4');
    
  • locale: Validate the given value against a list of locale strings. If it’s not found returns false. The value cannot be sanitized. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'locale');
    
  • max: Validate the value as being less than or equal to a maximum. Sanitize so that values higher than the maximum are forced down to the maximum. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'max', $max);
    
  • min: Validate the value as being greater than or equal to a minimum. Sanitize so that values lower than the minimum are forced up to the minimum. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'min', $min);
    
  • regex: Validate the value using preg_match(). Sanitize the value using preg_replace().
    1   $filter->addSoftRule('field', $filter::IS, 'regex', $expr);
    
  • strictEqualToField: Validate the value as strictly equal to the value of another field in the data object. Sanitize to the value of that other field. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'strictEqualToField', 'other_field_\
    2 name');
    
  • strictEqualToValue: Validate the value as strictly equal to a specified value. Sanitize to the specified value. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'strictEqualToValue', $other_value);
    
  • string: Validate the value can be represented by a string. Sanitize the value by casting to a string and optionally using str_replace(). Usage (note that this is to sanitize, not validate):
    1   $filter->addSoftRule('field', $filter::FIX, 'string', $find, $replace);
    
  • strlen: Validate the value has a specified length. Sanitize the value to cut off longer values at the right, and str_pad() shorter ones. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'strlen', $len);
    
  • strlenBetween: Validate the value length as being within or equal to a minimum and maximum value. Sanitize the value to cut off values longer than the maximum, longer values at the right, and str_pad() shorter ones. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'strlenBetween', $min, $max);
    
  • strlenMax: Validate the value length as being no longer than a maximum. Sanitize the value to cut off values longer than the maximum. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'strlenMax', $max);
    
  • strlenMin: Validate the value length as being no shorter than a minimum. Sanitize the value to str_pad() values shorter than the minimum. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'strlenMin', $min);
    
  • trim: Validate the value is trim()med. Sanitize the value to trim() it. Optionally specify characters to trim. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'trim', $chars);
    
  • upload: Validate the value represents a PHP upload information array, and that the file is an uploaded file. The value cannot be sanitized. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'upload');
    
  • url: Validate the value is a well-formed URL. The value cannot be sanitized. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'url');
    
  • word: Validate the value as being composed only of word characters. Sanitize the value to remove non-word characters. Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'word');
    
  • isbn: Validate the value is a correct ISBN (International Standard Book Number). Usage:
    1   $filter->addSoftRule('field', $filter::IS, 'isbn');
    
  • any: Validate the value passes at-least one of the rules. These rules are the ones added in rule locator.
    1   $filter->addSoftRule('field', $filter::IS, 'any', [
    2           ['alnum'],
    3           ['email'],
    4           // more rules
    5       ]
    6   );
    
  • all: Validate the value against a set of rules. These rules are should be added in rule locator. You will not get seprate error messages for which all rules it failed.
    1   $filter->addSoftRule('field', $filter::IS, 'all', [
    2           // rules
    3       ]
    4   );
    

Custom Messages

By default when a rule fails, the messages you will be getting are from the intl/en_US.php. But you can also provide a single custom message for all the failures.

1 $filter->useFieldMessage('field', 'Custom Message');

Example:

 1 $filter->addSoftRule('username', $filter::IS, 'alnum');
 2 $filter->addSoftRule('username', $filter::IS, 'strlenBetween', 6, 12);
 3 $data = (object) [
 4     'username' => ' sds',
 5 ];
 6 
 7 $filter->useFieldMessage('username', 'User name already exists');
 8 // filter the object and see if there were failures
 9 $success = $filter->values($data);
10 if (! $success) {
11     $messages = $filter->getMessages();
12     var_export($messages);
13 }

As you have used useFieldMessage you will see

1 array (
2   'username' =>
3   array (
4     0 => 'User name already exists',
5   ),
6 )

instead of

1 array (
2   'username' =>
3   array (
4     0 => 'Please use only alphanumeric characters.',
5     1 => 'Please use between 6 and 12 characters.',
6   ),
7 )

Creating and Using Custom Rules

There are three steps to creating and using new rules:

  1. Write a rule class
  2. Set that class as a service in the RuleLocator
  3. Use the new rule in our filter chain

Writing a Rule Class

Writing a rule class is straightforward:

  • Extend Aura\Filter\AbstractRule with two methods: validate() and sanitize().
  • Add params as needed to each method.
  • Each method should return a boolean: true on success, or false on failure.
  • Use getValue() to get the value being validated, and setValue() to change the value being sanitized.
  • Add a property $message to indicate a string that should be translated as a message when validation or sanitizing fails.

Here is an example of a hexadecimal rule:

 1 <?php
 2 namespace Vendor\Package\Filter\Rule;
 3 
 4 use Aura\Filter\AbstractRule;
 5 
 6 class Hex extends AbstractRule
 7 {
 8     protected $message = 'FILTER_HEX';
 9 
10     public function validate($max = null)
11     {
12         // must be scalar
13         $value = $this->getValue();
14         if (! is_scalar($value)) {
15             return false;
16         }
17 
18         // must be hex
19         $hex = ctype_xdigit($value);
20         if (! $hex) {
21             return false;
22         }
23 
24         // must be no longer than $max chars
25         if ($max && strlen($value) > $max) {
26             return false;
27         }
28 
29         // done!
30         return true;
31     }
32 
33     public function sanitize($max = null)
34     {
35         // must be scalar
36         $value = $this->getValue();
37         if (! is_scalar($value)) {
38             // sanitizing failed
39             return false;
40         }
41 
42         // strip out non-hex characters
43         $value = preg_replace('/[^0-9a-f]/i', '', $value);
44         if ($value === '') {
45             // failed to sanitize to a hex value
46             return false;
47         }
48 
49         // now check length and chop if needed
50         if ($max && strlen($value) > $max) {
51             $value = substr($value, 0, $max);
52         }
53 
54         // retain the sanitized value, and done!
55         $this->setValue($value);
56         return true;
57     }
58 }

Set The Class As A Service

Now we set the rule class into the RuleLocator.

1 <?php
2 $locator = $filter->getRuleLocator();
3 $locator->set('hex', function () {
4     return new Vendor\Package\Filter\Rule\Hex;
5 });

Apply The New Rule

Finally, we can use the rule in our filter:

1 <?php
2 // the 'color' field must be a hex value of no more than 6 digits
3 $filter->addHardRule('color', $filter::IS, 'hex', 6);

That is all!

Internationalization

The Aura.Intl package provides internationalization (I18N) tools, specifically package-oriented per-locale message translation.

Installation

1 composer require "aura/intl:1.1.*"

Service

In your modify method you can get the service intl_translator_locator like

1 $translators = $di->get('intl_translator_locator');

Setting Localized Messages For A Package

We can set localized messages for a package through the PackageLocator object from the translator locator. We create a new Package with messages and place it into the locator as a callable. The messages take the form of a message key and and message string.

 1 <?php
 2 use Aura\Intl\Package;
 3 
 4 // get the package locator
 5 $packages = $translators->getPackages();
 6 
 7 // place into the locator for Vendor.Package
 8 $packages->set('Vendor.Package', 'en_US', function() {
 9     // create a US English message set
10     $package = new Package;
11     $package->setMessages([
12         'FOO' => 'The text for "foo."';
13         'BAR' => 'The text for "bar."';
14     ]);
15     return $package;
16 });
17 
18 // place into the locator for a Vendor.Package
19 $packages->set('Vendor.Package', 'pt_BR', function() {
20     // a Brazilian Portuguese message set
21     $package = new Package;
22     $package->setMessages([
23         'FOO' => 'O texto de "foo".';
24         'BAR' => 'O texto de "bar".';
25     ]);
26     return $package;
27 });
28 ?>

Setting The Default Locale

We can set the default locale for translations using the setLocale() method:

1 <?php
2 $translators->setLocale('pt_BR');
3 ?>

Getting A Localized Message

Now that the translator locator has messages and a default locale, we can get an individual package translator. The package translator is suitable for injection into another class, or for standalone use. You will neeed to create a tanslator helper which can return the service.

1 <?php
2 // recall that the default locale is pt_BR
3 $translator = $translators->get('Vendor.Package');
4 echo $translator->translate('FOO'); // 'O texto de "foo".'
5 ?>

You can get a translator for a non-default locale as well:

1 <?php
2 $translator = $translators->get('Vendor.Package', 'en_US');
3 echo $translator->translate('FOO'); // 'The text for "foo."'
4 ?>

Replacing Message Tokens With Values

We often need to use dynamic values in translated messages. First, the message string needs to have a token placeholder for the dynamic value:

 1 <?php
 2 // get the packages out of the translator locator
 3 $packages = $translators->getPackages();
 4 
 5 $packages->set('Vendor.Dynamic', 'en_US', function() {
 6 
 7     // US English messages
 8     $package = new Package;
 9     $package->setMessages([
10         'PAGE' => 'Page {page} of {pages} pages.';
11     ]);
12     return $package;
13 });
14 
15 $packages->set('Vendor.Dynamic', 'pt_BR', function() {
16     // Brazilian Portuguese messages
17     $package = new Package;
18     $package->setMessages([
19         'PAGE' => 'Página {page} de {pages} páginas.';
20     ]);
21     return $package;
22 });
23 ?>

Then, when we translate the message, we provide an array of tokens and replacement values. These will be interpolated into the message string.

1 <?php
2 // recall that the default locale is pt_BR
3 $translator = $translators->get('Vendor.Dynamic');
4 echo $translator->translate('PAGE', [
5     'page' => 1,
6     'pages' => 1,
7 ]); // 'Página 1 de 1 páginas.'
8 ?>

Pluralized Messages

Usually, we need to use different messages when a value is singular or plural. The BasicFormatter is not capable of presenting different messages based on different token values. The IntlFormatter is capable, but the PHP intl extension must be loaded to take advantage of it, and we must specify the 'intl' formatter for the package in the catalog.

When using the IntlFormatter, we can build our message strings to present singular or plural messages, as in the following example:

 1 <?php
 2 // get the packages out of the translator locator
 3 $packages = $translators->getCatalog();
 4 
 5 // get the Vendor.Dynamic package en_US locale and set
 6 // US English messages with pluralization. note the use
 7 // of # instead of {pages} herein; using the placeholder
 8 // "inside itself" with the Intl formatter causes trouble.
 9 $package->setMessages([
10     'PAGE' => '{pages,plural,'
11             . '=0{No pages.}'
12             . '=1{One page only.}'
13             . 'other{Page {page} of # pages.}'
14             . '}'
15 ]);
16 
17 // use the 'intl' formatter for this package and locale
18 $package->setFormatter('intl');
19 
20 // now that we have added the pluralizable messages,
21 // get the US English translator for the package
22 $translator = $translators->get('Vendor.Dynamic', 'en_US');
23 
24 // zero translation
25 echo $translator->translate('PAGE', [
26     'page' => 0,
27     'pages' => 0,
28 ]); // 'No pages.'
29 
30 // singular translation
31 echo $translator->translate('PAGE', [
32     'page' => 1,
33     'pages' => 1,
34 ]); // 'One page only.'
35 
36 // plural translation
37 echo $translator->translate('PAGE', [
38     'page' => 3,
39     'pages' => 10,
40 ]); // 'Page 3 of 10 pages.'
41 ?>

Note that you can use other tokens within a pluralized token string to build more complex messages. For more information, see the following:

http://icu-project.org/apiref/icu4j/com/ibm/icu/text/MessageFormat.html

Session

Provides session management functionality, including lazy session starting, session segments, next-request-only (“flash”) values, and CSRF tools.

Installation

We are going to install aura/session.

1 composer require aura/session

Service

Aura.Session already have aura/session:session service which is an object of Aura\Session\Session . You can get inject the service to responder or view helper and make use of the Aura\Session\Session object.

Segments

In normal PHP, we keep session values in the $_SESSION array. However, when different libraries and projects try to modify the same keys, the resulting conflicts can result in unexpected behavior. To resolve this, we use Segment objects. Each Segment addresses a named key within the $_SESSION array for deconfliction purposes.

For example, if we get a Segment for Vendor\Package\ClassName, that Segment will contain a reference to $_SESSION['Vendor\Package\ClassName']. We can then set() and get() values on the Segment, and the values will reside in an array under that reference.

 1 <?php
 2 // get a _Segment_ object
 3 $segment = $session->getSegment('Vendor\Package\ClassName');
 4 
 5 // try to get a value from the segment;
 6 // if it does not exist, return an alternative value
 7 echo $segment->get('foo'); // null
 8 echo $segment->get('baz', 'not set'); // 'not set'
 9 
10 // set some values on the segment
11 $segment->set('foo', 'bar');
12 $segment->set('baz', 'dib');
13 
14 // the $_SESSION array is now:
15 // $_SESSION = array(
16 //      'Vendor\Package\ClassName' => array(
17 //          'foo' => 'bar',
18 //          'baz' => 'dib',
19 //      ),
20 // );
21 
22 // try again to get a value from the segment
23 echo $segment->get('foo'); // 'bar'
24 
25 // because the segment is a reference to $_SESSION, we can modify
26 // the superglobal directly and the segment values will also change
27 $_SESSION['Vendor\Package\ClassName']['zim'] = 'gir'
28 echo $segment->get('zim'); // 'gir'
29 ?>

The benefit of a session segment is that we can deconflict the keys in the $_SESSION superglobal by using class names (or some other unique name) for the segment names. With segments, different packages can use the $_SESSION superglobal without stepping on each other’s toes.

To clear all the values on a Segment, use the clear() method.

Lazy Session Starting

Merely instantiating the Session manager and getting a Segment from it does not call session_start(). Instead, session_start() occurs only in certain circumstances:

  • If we read from a Segment (e.g. with get()) the Session looks to see if a session cookie has already been set. If so, it will call session_start() to resume the previously-started session. If not, it knows there are no previously existing $_SESSION values, so it will not call session_start().
  • If we write to a Segment (e.g. with set()) the Session will always call session_start(). This will resume a previous session if it exists, or start a new one if it does not.

This means we can create each Segment at will, and session_start() will not be invoked until we actually interact with a Segment in a particular way. This helps to conserve the resources involved in starting a session.

Of course, we can force a session start or reactivation by calling the Session start() method, but that defeats the purpose of lazy-loaded sessions.

Saving, Clearing, and Destroying Sessions

To save the session data and end its use during the current request, call the commit() method on the Session manager:

1 <?php
2 $session->commit(); // equivalent of session_write_close()
3 ?>

To clear all session data, but leave the session active during the current request, use the clear() method on the Session manager.

1 <?php
2 $session->clear();
3 ?>

To clear all flash values on a segment, use the clearFlash() method:

To clear the data and terminate the session for this and future requests, thereby destroying it completely, call the destroy() method:

1 <?php
2 $session->destroy(); // equivalent of session_destroy()
3 ?>

Calling destroy() will also delete the session cookie via setcookie(). If we have an alternative means by which we delete cookies, we should pass a callable as the second argument to the SessionFactory method newInstance(). The callable should take three parameters: the cookie name, path, and domain.

1 <?php
2 // assume $response is a framework response object.
3 // this will be used to delete the session cookie.
4 $delete_cookie = function ($name, $path, $domain) use ($response) {
5     $response->cookies->delete($name, $path, $domain);
6 }
7 
8 $session = $session_factory->newInstance($_COOKIE, $delete_cookie);
9 ?>

Session Security

Session ID Regeneration

Any time a user has a change in privilege (that is, gaining or losing access rights within a system) be sure to regenerate the session ID:

1 <?php
2 $session->regenerateId();
3 ?>

Cross-Site Request Forgery

A “cross-site request forgery” is a security issue where the attacker, via malicious JavaScript or other means, issues a request in-the-blind from a client browser to a server where the user has already authenticated. The request looks valid to the server, but in fact is a forgery, since the user did not actually make the request (the malicious JavaScript did).

http://en.wikipedia.org/wiki/Cross-site_request_forgery

Defending Against CSRF

To defend against CSRF attacks, server-side logic should:

  1. Place a token value unique to each authenticated user session in each form; and
  2. Check that all incoming POST/PUT/DELETE (i.e., “unsafe”) requests contain that value.

For this example, the form field name will be __csrf_value. In each form we want to protect against CSRF, we use the session CSRF token value for that field:

 1 <?php
 2 /**
 3  * @var Vendor\Package\User $user A user-authentication object.
 4  * @var Aura\Session\Session $session A session management object.
 5  */
 6 ?>
 7 <form method="post">
 8 
 9     <?php if ($user->auth->isValid()) {
10         $csrf_value = $session->getCsrfToken()->getValue();
11         echo '<input type="hidden" name="__csrf_value" value="'
12            . htmlspecialchars($csrf_value, ENT_QUOTES, 'UTF-8')
13            . '"></input>';
14     } ?>
15 
16     <!-- other form fields -->
17 
18 </form>

When processing the request, check to see if the incoming CSRF token is valid for the authenticated user:

 1 <?php
 2 /**
 3  * @var Vendor\Package\User $user A user-authentication object.
 4  * @var Aura\Session\Session $session A session management object.
 5  */
 6 
 7 $unsafe = $_SERVER['REQUEST_METHOD'] == 'POST'
 8        || $_SERVER['REQUEST_METHOD'] == 'PUT'
 9        || $_SERVER['REQUEST_METHOD'] == 'DELETE';
10 
11 if ($unsafe && $user->auth->isValid()) {
12     $csrf_value = $_POST['__csrf_value'];
13     $csrf_token = $session->getCsrfToken();
14     if (! $csrf_token->isValid($csrf_value)) {
15         echo "This looks like a cross-site request forgery.";
16     } else {
17         echo "This looks like a valid request.";
18     }
19 } else {
20     echo "CSRF attacks only affect unsafe requests by authenticated users.";
21 }
22 ?>
CSRF Value Generation

For a CSRF token to be useful, its random value must be cryptographically secure. Using things like mt_rand() is insufficient. Aura.Session comes with a Randval class that implements a RandvalInterface, and uses either the openssl or the mcrypt extension to generate a random value. If you do not have one of these extensions installed, you will need your own random-value implementation of the RandvalInterface. We suggest a wrapper around RandomLib.

Flash Values

Segment values persist until the session is cleared or destroyed. However, sometimes it is useful to set a value that propagates only through the next request, and is then discarded. These are called “flash” values.

Setting And Getting Flash Values

To set a flash value on a Segment, use the setFlash() method.

1 <?php
2 $segment = $session->getSegment('Vendor\Package\ClassName');
3 $segment->setFlash('message', 'Hello world!');
4 ?>

Then, in subsequent requests, we can read the flash value using getFlash():

1 <?php
2 $segment = $session->getSegment('Vendor\Package\ClassName');
3 $message = $segment->getFlash('message'); // 'Hello world!'
4 ?>

Using setFlash() makes the flash value available only in the next request, not the current one. To make the flash value available immediately as well as in the next request, use setFlashNow($key, $val).

Using getFlash() returns only the values that are available now from having been set in the previous request. To read a value that will be available in the next request, use getFlashNext($key, $alt).

Keeping and Clearing Flash Values

Sometimes we will want to keep the flash values in the current request for the next request. We can do so on a per-segment basis by calling the Segment keepFlash() method, or we can keep all flashes for all segments by calling the Session keepFlash() method.

Similarly, we can clear flash values on a per-segment basis or a session-wide bases. Use the clearFlash() method on the Segment to clear flashes just for that segment, or the same method on the Session to clear all flash values for all segments.

Authentication

Authentication is made possible with the help of aura/auth.

1 {
2     "require": {
3         // more packages
4         "aura/auth": "2.0.*@dev"
5     }
6 }

Aura.Auth supports below adapters :

  • Apache htpasswd files
  • SQL tables via the PDO extension
  • IMAP/POP/NNTP via the imap extension
  • LDAP and Active Directory via the ldap extension
  • OAuth via customized adapters

We will concentrate on authentication via PDO adapter.

Building Service class

 1 <?php
 2 namespace Vendor\Package;
 3 
 4 use Aura\Auth\Auth;
 5 use Aura\Auth\Service\LoginService;
 6 use Aura\Auth\Service\LogoutService;
 7 use Aura\Auth\Service\ResumeService;
 8 use Aura\Auth\Status;
 9 
10 class AuthService
11 {
12     protected $auth;
13 
14     protected $login_service;
15 
16     protected $logout_service;
17 
18     protected $resume_service;
19 
20     protected $resumed = false;
21 
22     public function __construct(
23         Auth $auth,
24         LoginService $login_service,
25         LogoutService $logout_service,
26         ResumeService $resume_service
27     ) {
28         $this->auth = $auth;
29         $this->login_service  = $login_service;
30         $this->logout_service = $logout_service;
31         $this->resume_service = $resume_service;
32     }
33 
34     public function login(array $input)
35     {
36         return $this->login_service->login($this->auth, $input);
37     }
38 
39     public function forceLogin(
40         $name,
41         array $data = array(),
42         $status = Status::VALID
43     ) {
44         return $this->login_service->forceLogin($this->auth, $name, $data, $stat\
45 us);
46     }
47 
48     public function logout($status = Status::ANON)
49     {
50         return $this->logout_service->logout($this->auth, $status);
51     }
52 
53     public function forceLogout($status = Status::ANON)
54     {
55         return $this->logout_service->forceLogout($this->auth, $status);
56     }
57 
58     /**
59      *
60      * Magic call to all auth related methods
61      *
62      */
63     public function __call($method, array $params)
64     {
65         $this->resume();
66         return call_user_func_array(array($this->auth, $method), $params);
67     }
68 
69     public function getAuth()
70     {
71         $this->resume();
72         return $this->auth;
73     }
74 
75     protected function resume()
76     {
77         if (! $this->resumed) {
78             $this->resume_service->resume($this->auth);
79             $this->resumed = true;
80         }
81     }
82 }

Configuration

 1 <?php
 2 // {PROJECT_PATH}/config/Common.php
 3 namespace Aura\Framework_Project\_Config;
 4 
 5 use Aura\Di\Config;
 6 use Aura\Di\Container;
 7 
 8 class Common extends Config
 9 {
10     public function define(Container $di)
11     {
12         // more code
13         $di->set('aura/auth:auth_service', $di->lazyNew('Vendor\Package\AuthServ\
14 ice'));
15 
16         /**
17          * Auth service
18          */
19         $di->params['Vendor\Package\AuthService'] = array(
20             'auth' => $di->lazyGet('aura/auth:auth'),
21             'login_service' => $di->lazyGet('aura/auth:login_service'),
22             'logout_service' => $di->lazyGet('aura/auth:logout_service'),
23             'resume_service' => $di->lazyGet('aura/auth:resume_service')
24         );
25 
26         $di->params['Aura\Auth\Verifier\PasswordVerifier'] = array(
27             'algo' => PASSWORD_BCRYPT,
28         );
29 
30         $di->set('aura/auth:adapter', $di->lazyNew('Aura\Auth\Adapter\PdoAdapter\
31 '));
32 
33         $di->params['Aura\Auth\Adapter\PdoAdapter'] = array(
34             'pdo' => $di->lazyGet('default_connection'),
35             'verifier' => $di->lazyNew('Aura\Auth\Verifier\PasswordVerifier'),
36             'cols' => array(
37                 'username',
38                 'password',
39                 'roles',
40             ),
41             'from' => 'users',
42             'where' => 'active=1'
43         );
44     }
45 
46     // more code
47 }

Consider reading adapters if you need changes/improvements.

Calling Auth Methods

You can retrieve authentication information using the following methods on the AuthService instance :

  • getUserName(): returns the authenticated username string
  • getUserData(): returns the array of optional arbitrary user data
  • getFirstActive(): returns the Unix time of first activity (login)
  • getLastActive(): return the Unix time of most-recent activity (generally that of the current request)
  • getStatus(): returns the current authentication status constant. These constants are:
    • Status::ANON – anonymous/unauthenticated
    • Status::IDLE – the authenticated session has been idle for too long
    • Status::EXPIRED – the authenticated session has lasted for too long in total
    • Status::VALID – authenticated and valid
  • isAnon(), isIdle(), isExpired(), isValid(): these return true or false, based on the current authentication status.

You can also use the set*() variations of the get*() methods above to force the Auth object to whatever values you like.

Eg : Calling methods inside action

 1 <?php
 2 namespace Vendor\Package;
 3 
 4 class SomeAction
 5 {
 6     protected $auth;
 7 
 8     public function __construct(AuthService $auth)
 9     {
10         $this->auth = $auth;
11     }
12 
13     public function __invoke()
14     {
15         $this->auth->isValid();
16         $this->auth->login($input);
17         $this->auth->logout();
18         // etc
19     }
20 }

Action Domain Responder

It is recommend to read Action Domain Responder in short ADR.

Aura framework v2 promote the usage of one action per class.

In ADR there are 3 components.

  1. Action is the logic that connects the Domain and Responder. It uses the request input to interact with the Domain, and passes the Domain output to the Responder.
  2. Domain is the logic to manipulate the domain, session, application, and environment data, modifying state and persistence as needed.
  3. Responder is the logic to build an HTTP response or response description. It deals with body content, templates and views, headers and cookies, status codes, and so on.

Basically

  1. The web handler receives a client request and dispatches it to an Action.
  2. The Action interacts with the Domain.
  3. The Action feeds data to the Responder. (N.b.: This may include results from the Domain interaction, data from the client request, and so on.)
  4. The Responder builds a response using the data fed to it by the Action.
  5. The web handler sends the response back to the client.

Responder Bundle

We have FOA.Responder_Bundle which helps to render different template engines like Aura.View, Twig, Mustache etc. See full list.

Installation

1 composer require foa/responder-bundle

Note : Current version 0.4

Let us modify our previous example of BlogRead action class to render the contents via BlogRead responder.

Save at {$PROJECT_PATH}/src/App/Responder/BlogRead.php

 1 <?php
 2 /**
 3  * {$PROJECT_PATH}/src/App/Responders/BlogRead.php
 4  */
 5 namespace App\Responders;
 6 
 7 use Aura\View\View;
 8 use Aura\Web\Response;
 9 use FOA\Responder_Bundle\AbstractResponder;
10 
11 class BlogRead extends AbstractResponder
12 {
13     protected $available = array(
14         'text/html' => '',
15         'application/json' => '.json',
16     );
17 
18     protected function init()
19     {
20         $this->payload_method['FOA\DomainPayload\Found'] = 'display';
21     }
22 
23     protected function display()
24     {
25         $this->renderView('read', 'layout');
26     }
27 }

Now modify the actions class {$PROJECT_PATH}/src/App/Actions/BlogRead.php to inject the BlogRead responder. You also need to inject a Domain service which can fetch the details of the id. We are skipping the service and assume you have some way to get the data.

Remove the View and Response objects from the action class because the responder is responsible for rendering the view and set the response.

Now your modified action class will look like

 1 <?php
 2 /**
 3  * {$PROJECT_PATH}/src/App/Actions/BlogRead.php
 4  */
 5 namespace App\Actions;
 6 
 7 use Aura\Web\Request;
 8 use App\Responders\BlogRead as BlogReadResponder;
 9 use FOA\DomainPayload\PayloadFactory;
10 
11 class BlogRead
12 {
13     protected $request;
14 
15     protected $responder;
16 
17     public function __construct(
18         Request $request,
19         BlogReadResponder $responder
20     ) {
21         // you may want to inject some service in-order to fetch the details
22         $this->request = $request;
23         $this->responder = $responder;
24     }
25 
26     public function __invoke($id)
27     {
28         $blog = (object) array(
29             'id' => $id, 'title' => 'Some awesome title', 'author' => 'Hari KT'
30         );
31         // In real life you want to do something like
32         // $blog = $this->service->fetchId($id);
33 
34         $payload_factory = new PayloadFactory();
35         $payload = $payload_factory->found($blog);
36         $this->responder->setPayload($payload);
37         return $this->responder;
38     }
39 }

Modify our Closure as a view file and save in {$PROJECT_PATH}/src/App/Responders/views/read.php.

1 <?php echo "Reading '{$this->blog->title}' with post id: {$this->blog->id} !"; ?>

Time to edit your configuration file {$PROJECT_PATH}/config/Common.php .

Modify the class params for App\Actions\BlogRead to reflect the changes made to the constructor.

 1 $di->params['App\Actions\BlogRead'] = array(
 2     'request' => $di->lazyGet('aura/web-kernel:request'),
 3     'responder' => $di->lazyNew('App\Responders\BlogRead'),
 4 );
 5 
 6 $di->params['FOA\Responder_Bundle\Renderer\AuraView']['engine'] = $di->lazyNew('\
 7 Aura\View\View');
 8 // responder
 9 $di->params['FOA\Responder_Bundle\AbstractResponder']['response'] = $di->lazyGet\
10 ('aura/web-kernel:response');
11 $di->params['FOA\Responder_Bundle\AbstractResponder']['renderer'] = $di->lazyNew\
12 ('FOA\Responder_Bundle\Renderer\AuraView');
13 $di->params['FOA\Responder_Bundle\AbstractResponder']['accept'] = $di->lazyNew('\
14 Aura\Accept\Accept');

Browse the http://localhost:8000/blog/read/1 .

Questions

What have we achieved other than creating lots of classes ?

That is really a good question. We are moving the responsibility to its own layers which will help us in testing the application. Web applications get evolved even we start small, so testing each and every part is always a great way to move forward.

This help us in to test the action classes, services etc.

Command line / cli / console

In this chapter we assume you have installed either aura/framework-project or aura/cli-project.

Both aura/framework-project and aura/cli-project make use of the aura/cli library. Aura.Cli can be used as standalone library. Please refer getting started if you looking for standalone usage.

Features of Aura.Cli

Context Discovery

The Context object provides information about the command line environment, including any option flags passed via the command line. (This is the command line equivalent of a web request object.)

Please have a look at services

You can access the $_ENV, $_SERVER, and $argv values with the $env, $server, and $argv property objects, respectively. (Note that these properties are copies of those superglobals as they were at the time of Context instantiation.) You can pass an alternative default value if the related key is missing.

 1 <?php
 2 // get copies of superglobals
 3 $env    = $context->env->get();
 4 $server = $context->server->get();
 5 $argv   = $context->argv->get();
 6 
 7 // equivalent to:
 8 // $value = isset($_ENV['key']) ? $_ENV['key'] : null;
 9 $value = $context->env->get('key');
10 
11 // equivalent to:
12 // $value = isset($_ENV['key']) ? $_ENV['key'] : 'other_value';
13 $value = $context->env->get('key', 'other_value');
14 ?>

Getopt Support

The Context object provides support for retrieving command-line options and params, along with positional arguments.

To retrieve options and arguments parsed from the command-line $argv values, use the getopt() method on the Context object. This will return a GetoptValues object for you to use as as you wish.

Defining Options and Params

To tell getopt() how to recognize command line options, pass an array of option definitions. The definitions array format is similar to, but not exactly the same as, the one used by the getopt() function in PHP. Instead of defining short flags in a string and long options in a separate array, they are both defined as elements in a single array. Adding a * after the option name indicates it can be passed multiple times; its values will be stored in an array.

 1 <?php
 2 $options = array(
 3     'a',        // short flag -a, parameter is not allowed
 4     'b:',       // short flag -b, parameter is required
 5     'c::',      // short flag -c, parameter is optional
 6     'foo',      // long option --foo, parameter is not allowed
 7     'bar:',     // long option --bar, parameter is required
 8     'baz::',    // long option --baz, parameter is optional
 9     'g*::',     // short flag -g, parameter is optional, multi-pass
10 );
11 
12 $getopt = $context->getopt($options);
13 ?>

Use the get() method on the returned GetoptValues object to retrieve the option values. You can provide an alternative default value for when the option is missing.

1 <?php
2 $a   = $getopt->get('-a', false); // true if -a was passed, false if not
3 $b   = $getopt->get('-b');
4 $c   = $getopt->get('-c', 'default value');
5 $foo = $getopt->get('--foo', 0); // true if --foo was passed, false if not
6 $bar = $getopt->get('--bar');
7 $baz = $getopt->get('--baz', 'default value');
8 $g   = $getopt->get('-g', []);
9 ?>

If you want alias one option name to another, comma-separate the two names. The values will be stored under both names;

 1 <?php
 2 // alias -f to --foo
 3 $options = array(
 4     'foo,f:',  // long option --foo or short flag -f, parameter required
 5 );
 6 
 7 $getopt = $context->getopt($options);
 8 
 9 $foo = $getopt->get('--foo'); // both -f and --foo have the same values
10 $f   = $getopt->get('-f'); // both -f and --foo have the same values
11 ?>

If you want to allow an option to be passed multiple times, add a ‘*’ to the end of the option name.

 1 <?php
 2 $options = array(
 3     'f*',
 4     'foo*:'
 5 );
 6 
 7 $getopt = $context->getopt($options);
 8 
 9 // if the script was invoked with:
10 // php script.php --foo=foo --foo=bar --foo=baz -f -f -f
11 $foo = $getopt->get('--foo'); // ['foo', 'bar', 'baz']
12 $f   = $getopt->get('-f'); // [true, true, true]
13 ?>

If the user passes options that do not conform to the definitions, the GetoptValues object retains various errors related to the parsing failures. In these cases, hasErrors() will return true, and you can then review the errors. (The errors are actually Aura\Cli\Exception objects, but they don’t get thrown as they occur; this is so that you can deal with or ignore the different kinds of errors as you like.)

 1 <?php
 2 $getopt = $context->getopt($options);
 3 if ($getopt->hasErrors()) {
 4     $errors = $getopt->getErrors();
 5     foreach ($errors as $error) {
 6         // print error messages to stderr using a Stdio object
 7         $stdio->errln($error->getMessage());
 8     }
 9 };
10 ?>
Positional Arguments

To get the positional arguments passed to the command line, use the get() method and the argument position number:

 1 <?php
 2 $getopt = $context->getopt();
 3 
 4 // if the script was invoked with:
 5 // php script.php arg1 arg2 arg3 arg4
 6 
 7 $val0 = $getopt->get(0); // script.php
 8 $val1 = $getopt->get(1); // arg1
 9 $val2 = $getopt->get(2); // arg2
10 $val3 = $getopt->get(3); // arg3
11 $val4 = $getopt->get(4); // arg4
12 ?>

Defined options will be removed from the arguments automatically.

 1 <?php
 2 $options = array(
 3     'a',
 4     'foo:',
 5 );
 6 
 7 $getopt = $context->getopt($options);
 8 
 9 // if the script was invoked with:
10 // php script.php arg1 --foo=bar -a arg2
11 $arg0 = $getopt->get(0); // script.php
12 $arg1 = $getopt->get(1); // arg1
13 $arg2 = $getopt->get(2); // arg2
14 $foo  = $getopt->get('--foo'); // bar
15 $a    = $getopt->get('-a'); // 1
16 ?>

Standard Input/Output Streams

The Stdio object allows you to work with standard input/output streams. (This is the command line equivalent of a web response object.)

Please have a look at services

It defaults to using php://stdin, php://stdout, and php://stderr, but you can pass whatever stream names you like as parameters to the newStdio() method.

The Stdio object methods are …

  • getStdin(), getStdout(), and getStderr() to return the respective Handle objects;
  • outln() and out() to print to stdout, with or without a line ending;
  • errln() and err() to print to stderr, with or without a line ending;
  • inln() and in() to read from stdin until the user hits enter; inln() leaves the trailing line ending in place, whereas in() strips it.

You can use special formatting markup in the output and error strings to set text color, text weight, background color, and other display characteristics. See the formatter cheat sheet below.

1 <?php
2 // print to stdout
3 $stdio->outln('This is normal text.');
4 
5 // print to stderr
6 $stdio->errln('<<red>>This is an error in red.');
7 $stdio->errln('Output will stay red until a formatting change.<<reset>>');
8 ?>

Exit Codes

This library comes with a Status class that defines constants for exit status codes. You should use these whenever possible. For example, if a command is used with the wrong number of arguments or improper option flags, exit() with Status::USAGE. The exit status codes are the same as those found in sysexits.h.

Writing Commands

The Aura.Cli library does not come with an abstract or base command class to extend from, but writing commands for yourself is straightforward. The following is a standalone command script, but similar logic can be used in a class. Save it in a file named hello and invoke it with php hello [-v,--verbose] [name].

 1 <?php
 2 use Aura\Cli\CliFactory;
 3 use Aura\Cli\Status;
 4 
 5 require '/path/to/Aura.Cli/autoload.php';
 6 
 7 // get the context and stdio objects
 8 $cli_factory = new CliFactory;
 9 $context = $cli_factory->newContext($GLOBALS);
10 $stdio = $cli_factory->newStdio();
11 
12 // define options and named arguments through getopt
13 $options = ['verbose,v'];
14 $getopt = $context->getopt($options);
15 
16 // do we have a name to say hello to?
17 $name = $getopt->get(0);
18 if (! $name) {
19     // print an error
20     $stdio->errln("Please give a name to say hello to.");
21     exit(Status::USAGE);
22 }
23 
24 // say hello
25 if ($getopt->get('--verbose')) {
26     // verbose output
27     $stdio->outln("Hello {$name}, it's nice to see you!");
28 } else {
29     // plain output
30     $stdio->outln("Hello {$name}!");
31 }
32 
33 // done!
34 exit(Status::SUCCESS);
35 ?>

Writing Command Help

Sometimes it will be useful to provide help output for your commands. With Aura.Cli, the Help object is separate from any command you may write. It may be manipulated externally or extended.

For example, extend the Help object and override the init() method.

 1 <?php
 2 use Aura\Cli\Help;
 3 
 4 class MyCommandHelp extends Help
 5 {
 6     protected function init()
 7     {
 8         $this->setSummary('A single-line summary.');
 9         $this->setUsage('<arg1> <arg2>');
10         $this->setOptions(array(
11             'f,foo' => "The -f/--foo option description",
12             'bar::' => "The --bar option description",
13         ));
14         $this->setDescr("A multi-line description of the command.");
15     }
16 }
17 ?>

Then instantiate the new class and pass its getHelp() output through Stdio:

 1 <?php
 2 use Aura\Cli\CliFactory;
 3 use Aura\Cli\Context\OptionFactory;
 4 
 5 $cli_factory = new CliFactory;
 6 $stdio = $cli_factory->newStdio();
 7 
 8 $help = new MyCommandHelp(new OptionFactory);
 9 $stdio->outln($help->getHelp('my-command'));
10 ?>
  • We keep the command name itself outside of the help class, because the command name may be mapped differently in different projects.
  • We pass a GetoptParser to the Help object so it can parse the option defintions.
  • We can get the option definitions out of the Help object using getOptions(); this allows us to pass a Help object into a hypothetical command object and reuse the definitions.

The output will look something like this:

 1 SUMMARY
 2     my-command -- A single-line summary.
 3 
 4 USAGE
 5     my-command <arg1> <arg2>
 6 
 7 DESCRIPTION
 8     A multi-line description of the command.
 9 
10 OPTIONS
11     -f
12     --foo
13         The -f/--foo option description.
14 
15     --bar[=<value>]
16         The --bar option description.

Formatter Cheat Sheet

On POSIX terminals, <<markup>> strings will change the display characteristics. Note that these are not HTML tags; they will be converted into terminal control codes, and do not get “closed”. You can place as many space-separated markup codes between the double angle-brackets as you like.

 1 reset       reset display to defaults
 2 
 3 black       black text
 4 red         red text
 5 green       green text
 6 yellow      yellow text
 7 blue        blue text
 8 magenta     magenta (purple) text
 9 cyan        cyan (light blue) text
10 white       white text
11 
12 blackbg     black background
13 redbg       red background
14 greenbg     green background
15 yellowbg    yellow background
16 bluebg      blue background
17 magentabg   magenta (purple) background
18 cyanbg      cyan (light blue) background
19 whitebg     white background
20 
21 bold        bold in the current text and background colors
22 dim         dim in the current text and background colors
23 ul          underline in the current text and background colors
24 blink       blinking in the current text and background colors
25 reverse     reverse the current text and background colors

For example, to set bold white text on a red background, add <<bold white redbg>> into your output or error string. Reset back to normal with <<reset>>.

Services

Aura.Cli_Kernel defines the following service objects in the Container:

  • aura/cli-kernel:dispatcher: an instance of Aura\Dispatcher\Dispatcher
  • aura/cli-kernel:context: an instance of Aura\Cli\Context
  • aura/cli-kernel:stdio: an instance of Aura\Cli\Stdio
  • aura/cli-kernel:help_service: an instance of Aura\Cli_Kernel\HelpService
  • aura/project-kernel:logger: an instance of Monolog\\Logger

Quick Start

The dependency injection Container is absolutely central to the operation of an Aura project. Please be familiar with the DI docs before continuing.

You should also familiarize yourself with Aura.Dispatcher, as well as the Aura.Cli Context, Stdio, and Status objects.

Project Configuration

Every Aura project is configured the same way. Please see the shared configuration docs for more information.

Logging

The project automatically logs to {$PROJECT_PATH}/tmp/log/{$mode}.log. If you want to change the logging behaviors for a particular config mode, edit the related config file (e.g., config/Dev.php) file to modify the aura/project-kernel:logger service.

Commands

We configure commands via the project-level config/ class files. If a command needs to be available in every config mode, edit the project-level config/Common.php class file. If it only needs to be available in a specific mode, e.g. dev, then edit the config file for that mode.

Here are two different styles of command definition.

Micro-Framework Style

The following is an example of a command where the logic is embedded in the dispatcher, using the aura/cli-kernel:context and aura/cli-kernel:stdio services along with standard exit codes. (The dispatcher object name doubles as the command name.)

 1 <?php
 2 namespace Aura\Cli_Project\_Config;
 3 
 4 use Aura\Di\Config;
 5 use Aura\Di\Container;
 6 
 7 class Common extends Config
 8 {
 9     // ...
10 
11     public function modifyCliDispatcher(Container $di)
12     {
13         $context = $di->get('aura/cli-kernel:context');
14         $stdio = $di->get('aura/cli-kernel:stdio');
15         $dispatcher = $di->get('aura/cli-kernel:dispatcher');
16         $dispatcher->setObject(
17             'foo',
18             function ($id = null) use ($context, $stdio) {
19                 if (! $id) {
20                     $stdio->errln("Please pass an ID.");
21                     return \Aura\Cli\Status::USAGE;
22                 }
23 
24                 $id = (int) $id;
25                 $stdio->outln("You passed " . $id . " as the ID.");
26             }
27         );
28     }
29 ?>

You can now run the command to see its output.

1 cd {$PROJECT_PATH}
2 php cli/console.php foo 88

(If you do not pass an ID argument, you will see an error message.)

Full-Stack Style

You can migrate from a micro-controller style to a full-stack style (or start with full-stack style in the first place).

First, define a command class and place it in the project src/ directory.

 1 <?php
 2 /**
 3  * {$PROJECT_PATH}/src/App/Command/FooCommand.php
 4  */
 5 namespace App\Command;
 6 
 7 use Aura\Cli\Stdio;
 8 use Aura\Cli\Context;
 9 use Aura\Cli\Status;
10 
11 class FooCommand
12 {
13     public function __construct(Context $context, Stdio $stdio)
14     {
15         $this->context = $context;
16         $this->stdio = $stdio;
17     }
18 
19     public function __invoke($id = null)
20     {
21         if (! $id) {
22             $this->stdio->errln("Please pass an ID.");
23             return Status::USAGE;
24         }
25 
26         $id = (int) $id;
27         $this->stdio->outln("You passed " . $id . " as the ID.");
28     }
29 }
30 ?>

Next, tell the project how to build the FooCommand through the DI Container. Edit the project config/Common.php file to configure the Container to pass the aura/cli-kernel:context and aura/cli-kernel:stdio service objects to the FooCommand constructor. Then put the AppCommandFooCommand object in the dispatcher under the name foo as a lazy-loaded instantiation.

 1 <?php
 2 namespace Aura\Cli_Project\_Config;
 3 
 4 use Aura\Di\Config;
 5 use Aura\Di\Container;
 6 
 7 class Common extends Config
 8 {
 9     public function define(Container $di)
10     {
11         $di->set('aura/project-kernel:logger', $di->newInstance('Monolog\Logger'\
12 ));
13 
14         $di->params['App\Command\FooCommand'] = array(
15             'context' => $di->lazyGet('aura/cli-kernel:context'),
16             'stdio' => $di->lazyGet('aura/cli-kernel:stdio'),
17         );
18     }
19 
20     // ...
21 
22     public function modifyCliDispatcher(Container $di)
23     {
24         $dispatcher = $di->get('aura/cli-kernel:dispatcher');
25 
26         $dispatcher->setObject(
27             'foo',
28             $di->lazyNew('App\Command\FooCommand')
29         );
30     }
31 ?>

You can now run the command to see its output.

1 cd {$PROJECT_PATH}
2 php cli/console.php foo 88

(If you do not pass an ID argument, you will see an error message.)

Setting up your virtual host

Apache

We are going to point the virtual host to aura.localhost. If you are in a debain based OS, you want to create a file /etc/apache2/sites-available/aura2.localhost.conf with the below contents.

1 <VirtualHost *:80>
2     ServerName aura2.localhost
3     ServerAlias www.aura2.localhost
4     DocumentRoot /path/to/project/web
5     <Directory /path/to/project/web>
6         DirectoryIndex index.php
7         AllowOverride All
8     </directory>
9 </VirtualHost>

path/to/project is where you installed the aura/web-project or aura/framework-project.

NOTE: Apache 2.4 users might have to add Require all granted below AllowOverride all in order to prevent a 401 response caused by the changes in access control.

Enable the site using

1 a2ensite aura2.localhost

and reload the apache

1 service apache2 reload

Before we go and check in browser add one more line in the /etc/hosts

1 127.0.0.1   aura2.localhost www.aura2.localhost

Nginx

In ubuntu 12.04 the configuration file is under /etc/nginx/sites-available

 1 server {
 2     listen   80;
 3     root /path/to/aura-project/web;
 4     index index.php index.html index.htm;
 5     server_name aura2.localhost;
 6 
 7     location / {
 8         try_files $uri $uri/ /index.php?$args;
 9     }
10 
11     error_page 404 /404.html;
12 
13     location ~ \.php$ {
14         fastcgi_pass unix:/var/run/php5-fpm.sock;
15         fastcgi_split_path_info ^(.+\.php)(/.+)$;
16         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
17         include fastcgi_params;
18     }
19 }

Check http://aura2.localhost in your favourite browser.