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()
– Whentrue
the$server['HTTPS']
value must be on, or the request must be on port 443; whenfalse
, 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()
– Whenfalse
the route will be used only for generating paths, not for matching (true
by default). -
setIsMatchCallable()
– A custom callable with the signaturefunction(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 signaturefunction(\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 theX-Forwarded-For
headers as an array. -
getReferer()
returns the value of theReferer
header. -
getIp()
returns the value of$_SEVER['REMOTE_ADDR']
, or the appropriate value ofX-Forwarded-For
. -
getUserAgent()
return the value of theUser-Agent
header. -
isCrawler()
returns true if theUser-Agent
header matches one of a list of bot/crawler/robot user agents (otherwise false). -
isMobile()
returns true if theUser-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 viaset()
-
setType()
sets theContent-Type
header -
getType()
returns theContent-Type
(not including the charset) -
setCharset()
sets the character set for theContent-Type
-
getCharset()
returns thecharset
portion of theContent-Type
header -
setDisposition()
sets theContent-Disposition
type and filename -
setEncoding()
sets theContent-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 theAge
header value in seconds -
setControl()
sets an array ofCache-Control
header directives all at once; alternatively, use the individual directive methods:-
setPublic()
andsetPrivate()
set thepublic
andprivate
cache control directives (each turns off the other) -
setMaxAge()
andsetSharedMaxAge()
set themax-age
ands-maxage
cache control directives (set to null or false to remove them) -
setNoCache()
andsetNoStore()
set theno-cache
andno-store
cache control directives (set to null or false to remove them) -
setMustRevalidate()
andsetProxyRevalidate()
to set themust-revalidate
andproxy-revalidate
directives (set to null or false to remove them)
-
-
setEtag()
andsetWeakEtag()
set theETag
header value -
setExpires()
sets theExpires
header value; will convert recognizable date formats andDateTime
objects to a correctly formatted HTTP date -
setLastModified()
sets theLast-Modified
header value; will convert recognizable date formats andDateTime
objects to a correctly formatted HTTP date -
setVary()
sets theVary
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 a303 See Other
status; this automatically disables HTTP caching -
created($location)
redirects to$location
with201 Created
-
movedPermanently($location)
redirects to$location
with301 Moved Permanently
-
found($location)
redirects to$location
with302 Found
-
seeOther($location)
redirects to$location
with303 See Other
; this automatically disables HTTP caching -
temporaryRedirect($location)
redirects to$location
with307 Temporary Redirect
-
permanentRedirect($location)
redirects to$location
with308 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 tonull
; 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 tonull
. 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 usingpreg_match()
. Sanitize the value usingpreg_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 usingstr_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, andstr_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, andstr_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 tostr_pad()
values shorter than the minimum. Usage:1
$filter
->
addSoftRule
(
'field'
,
$filter
::
IS
,
'strlenMin'
,
$min
);
-
trim
: Validate the value istrim()
med. Sanitize the value totrim()
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:
- Write a rule class
- Set that class as a service in the
RuleLocator
- 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()
andsanitize()
. - 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, andsetValue()
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
=
$d
i->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 callsession_start()
to resume the previously-started session. If not, it knows there are no previously existing$_SESSION
values, so it will not callsession_start()
. - If we write to a Segment (e.g. with
set()
) the Session will always callsession_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:
- Place a token value unique to each authenticated user session in each form; and
- 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.
- 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.
- Domain is the logic to manipulate the domain, session, application, and environment data, modifying state and persistence as needed.
- 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
- The web handler receives a client request and dispatches it to an Action.
- The Action interacts with the Domain.
- 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.)
- The Responder builds a response using the data fed to it by the Action.
- 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()
, andgetStderr()
to return the respective Handle objects; -
outln()
andout()
to print to stdout, with or without a line ending; -
errln()
anderr()
to print to stderr, with or without a line ending; -
inln()
andin()
to read from stdin until the user hits enter;inln()
leaves the trailing line ending in place, whereasin()
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 ofMonolog\\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?$a
rgs;
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 $d
ocument_root$fa
stcgi_script_name;
17
include fastcgi_params;
18
}
19
}
Check http://aura2.localhost in your favourite browser.