Expressive 3 Alpha 3

Today, we pushed the final changes and fixes that culminated in the
Expressive Installer and Skeleton 3.0.0alpha3
release
!

The alpha releases have a ton of great features; keep reading to find out more!

Alpha 1 and Alpha 2

We released 3.0.0alpha1 on Tuesday, 6 February 2018, and 3.0.0alpha2 on
Wednesday, 7 February 2018. While they were usable, there were a few issues we
discovered that we felt should be addressed before a public announcement.
3.0.0alpha3 represents a stable, testable release.

Installation

Currently, we do not have a completed migration path for 3.0; this is our work
in the coming weeks. Additionally, there may yet be changes coming as we get
your feedback. As such, we recommend:

  • Install a fresh project.
  • Do not use the alpha release(s) in production!

To create a new project based on 3.0.0alpha3, use
Composer:

$ composer create-project "zendframework/zend-expressive-skeleton:3.0.0alpha3"

This will install the skeleton, and then start prompting you for specific
components you want to install, including choice of
PSR-11 container, choice of router,
choice of template engine, and choice of error handler. Generally speaking, we
recommend the default values, except in the case of the template engine (which
defaults to none; choose the engine you’re most comfortable with).

Once your selections are made, the skeleton will install dependencies; when it
is complete, you can enter the newly created directory to begin development:

$ cd zend-expressive-skeleton

Alternate directory

You can specify an alternate directory when calling composer create-project;
when you do, you can also specify the specific version separate from the root
package:

$ composer create-project zendframework/zend-expressive-skeleton expressive 3.0.0alpha3

Creating middleware

Version 3 of Expressive will work with
PSR-15 (HTTP server request handlers)
middleware and request handlers only. You will be writing these to create
your application.

Other supported types

Expressive 3 also supports other types of middleware definitions, though they
are not recommended:

  • Callable middleware using the same signature as PSR-15. These can be piped
    and routed to directly.
  • Callable double-pass middleware; these must be decorated using the
    ZendStratigilitydoublePassMiddleware() utility class — which also
    requires a PSR-7 response prototype.

The skeleton project now ships with zend-expressive-tooling
by default, and maps its expressive command as a composer command:

$ composer expressive help

To create your first middleware:

$ composer expressive middleware:create "AppXClacksOverheadMiddleware"

This will create the class AppXClacksOverheadMiddleware, and tell you where
it has been created. You can then edit it:

<?php
namespace App;

use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use PsrHttpServerMiddlewareInterface;
use PsrHttpServerRequestHandlerInterface;

class XClacksOverheadMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        $response = $handler->handle($request);
        return $response->withHeader('X-ClacksOverhead', 'GNU Terry Pratchett');
    }
}

Once your middleware is created, register it in the container as an invokable,
via the config/autoload/dependencies.global.php file:

'dependencies' => [
    'invokables' => [
        AppXClacksOverheadMiddleware::class => AppXClacksOverheadMiddleware::class,
    ],
],

Finally, register it in your config/pipeline.php file:

// likely an early statement, before routing
$app->pipe(AppXClacksOverheadMiddleware::class);

You’ve just created your first middleware!

Creating handlers

PSR-15 defines two interfaces. In the previous section, we demonstrated
implementing the MiddlewareInterface. That interface references another, the
RequestHandlerInterface.

Internally, we provide one that maintains the middleware pipeline and the state
within the pipeline such that calling handle() advances to the next
middleware.

However, there’s another place handlers are of interest: for routes.

Most often, when you create a route, the class you write to handle the route
will generate a response itself, and never need to delegate to another handler.
As such, you can write handlers instead!

Like middleware, the tooling provides a command for creating handlers:

$ composer expressive handler:create "AppHandlerHelloWorldHandler"

This will create a RequestHandlerInterface implementation using the given
name, and then tell you where on the filesystem it created it.

For this example, we’ll assume you’re using zend-diactoros (as it is used in the
Expressive skeleton by default), and we’ll create a handler that generates a
ZendDiactorosResponseHtmlResponse. Open the file, and edit the contents to
look like the following:

<?php

namespace AppHandler;

use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use PsrHttpServerRequestHandlerInterface;
use ZendDiactorosResponseHtmlResponse;

class HelloWorldHandler implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        return new HtmlResponse('<h1>Hello, world!</h1>');
    }
}

Like the XClacksOverhead middleware, We’ll register this with the container as
an invokable, via the file config/autoload/dependencies.global.php:

'dependencies' => [
    'invokables' => [
        AppHandlerHelloWorldHandler::class => AppHandlerHelloWorldHandler::class,
    ],
],

Finally, we’ll route to it via your config/routes.php file:

$app->get('/hello', AppHandlerHelloWorldHandler::class, 'hello');

You’ve just created your first handler!

Handlers and route-specific pipelines

If you have used Expressive before, you may recall that the various routing
methods allowed middleware and middleware pipelines previously. This is still
true! The only difference with version 3 is that we also allow request
handlers.

In fact, you can add handlers into your middleware pipelines as well! If
we wanted the XClacksOverheadMiddleware to only be in that specific route,
we could write it as follows:

$app->get('/hello', [
    AppXClacksOverheadMiddleware::class,
    AppHandlerHelloWorldHandler::class,
], 'hello')

The only caveat is that handlers always return a response, which means they
should always be the last item in a pipeline.

Test it out!

You can use the following command to fire up the PHP built-in web server:

$ composer serve

The command works on Windows and macOS; for Linux users, it will work as long as
you have PHP 7.1.14 and later or 7.2.2 and later installed. For those on earlier
versions, use the following:

$ php -S 0.0.0.0:8080 -t public/ public/index.php

Once you have, try hitting your new route: http://localhost:8080/hello

You should get your HTML content as defined above! If you introspect the request
in your browser (or using a CLI tool such as cURL or HTTPie), you’ll also see a
X-Clacks-Overhead header from the middleware you created!

Roadmap

Hitting our first alpha releases is a huge milestone, and the culmination of
many months of development. We’re very excited about the results. In both
Stratigility (our middleware foundation library) and Expressive, we have
drastically reduced the amount of code, while providing essentially the same
feature set (and, in many cases, expanding that feature set!).

In the coming weeks, we’ll be developing a final version 2 minor release, 2.2.0,
as well as working on documentation and migration tooling. The main goal of the
2.2 release will be to mark deprecated features, provide forward compatible
alternatives, and provide tooling to help you migrate to those alternatives.

We also have a few planned features for Expressive 3 to complete. We’re working
on changes to the zend-component-installer to allow whitelisting packages with
configuration providers, so that users will not need to be prompted during
initial installation to inject configuration for packages we already know about.
We also plan to develop tooling for creating and registering factories based on
a given class, and updating the handler:create and middleware:create
factories to generate and register factories as well. Also, recently we released
version 3 of the zend-di package, and we’re considering integrating it by
default when zend-servicemanager is configured, to provide auto-wiring of
dependencies.

This may sound like quite a bit, but much of it is already in progress, and our
plan is to have a stable release by no later than 15 March 2018.

In the meantime: install and test 3.0.0alpha3! Kick its tires, and let us know
what works, and, more importantly, what doesn’t work, so we can provide you a
stable, exciting 3.0.0 release!

Source: Zend feed

Expressive 3 Preview

Last week, the PSR-15 working group voted to start its review
phase
.
PSR-15 seeks to standardize server-side request handlers and middleware, and
both Stratigility and Expressive have been implementing draft specifications
since their version 2 releases. Entering the review phase is an important
moment: it means that the working group feels the specification is stable and
ready for adoption. If, after the review period is over, no major changes are
required, the specification can be presented to the PHP-FIG core committed for a
final acceptance vote, at which point it will be frozen and ready for mass
adoption.

Our plan is to have Stratigility and Expressive follow the new specification in
its final form. To that end, we have been executing on a plan to prepare all our
projects that work with PSR-15 to adopt the latest round of changes.

That work is ready today!

What has changed in PSR-15?

The latest round of changes to the specification prior to entering the review
period were as follows:

  • The namespace of the draft specification was changed from
    InteropHttpServerMiddleware to InteropHttpServer. These will therefor
    become PsrHttpServer once the specification is accepted.

  • The DelegateInterface was renamed to RequestHandlerInterface, and
    the method it defines renamed to handle().

  • The MiddlewareInterface‘s second argument to process() was updated to
    typehint against RequestHandlerInterface.

  • The package shipping the interface was split into two,
    http-interop/http-server-handler and http-interop/http-server-middleware;
    these will become psr/http-server-handler and psr/http-server-middleware,
    respectively, once the package is accepted. The http-server-middleware
    packages depend on the http-server-handler packages.

These changes, of course, are not backwards compatible, and our attempts to
write a polyfill library were ultimately unsuccessful. As a result, we decided
to bump the major version of all libraries currently depending on the draft
specification.

What we have done

Our approach in updating the various packages was as follows:

  • We created a new release branch named after the next major release. For
    instance, if a library is currently issuing v2 releases, we created a
    release-3.0.0 branch.
  • We updated the branch aliases defined in the composer.json for the package
    as follows, on all branches:

    • The master branch points to the current minor release. As an example, for a
      package with a current stable 2.3.1 version, the branch alias became
      "dev-master": "2.3.x-dev".
    • If a development branch already exists, we updated similarly to the master
      branch. For the above example, the branch alias would read "dev-develop": "2.4.x-dev".
    • The new release branch is then mapped to the upcoming major version:
      `"dev-release-3.0.0": "3.0.x-dev".
  • On the release branches, we updated dependencies as follows:
    • PHP dependencies became simply ^7.1 (per our decision posted in
      June
      ).
    • References to http-interop/http-middleware packages were changed to
      "http-interop/http-server-middleware": "^1.0.1".
    • References to packages that have corresponding release branches were updated
      to have their constraints point to the appropriate development release branch.
      As an example, "zendframework/zend-expressive-router": "^3.0.0-dev".

These changes ensure users can install the new development versions of packages
by feeding an appropriate development constraint.

You’ll note that we bumped the minimum supported PHP version in these packages
as well. Because we were doing that, we also decided to make use of PHP 7.1
features. In particular:

  • Scalar and return type hints.
  • Nullable and void types.
  • Null coalesce.
  • strict_types where it simplifies validation of scalars (which turns out to
    be almost everywhere).

For packages that define interfaces, this meant that we also needed
corresponding major version bumps in packages that implement those interfaces.
This affected the router and template implementations in particular.

If you want a complete list of what was updated, you can visit the burndown
list in the forums
.

How YOU can test

This is all very nice and technical, but how can YOU test out the new versions?

Install the development version of the Expressive skeleton!

$ composer create-project "zendframework/zend-expressive-skeleton:3.0.x-dev" expressive-3.0-dev

This will create the skeleton project, with your selected functionality, in a
directory named expressive-3.0-dev. From there, you can start developing!

When you do, be aware of the following:

  • Middleware must now implement InteropHttpServerMiddlewareInterface:

    namespace YourModule;
    
    use InteropHttpServerMiddlewareInterface;
    use InteropHttpServerRequestHandlerInterface;
    use PsrHttpMessageResponseInterface;
    use PsrHttpMessageRequestHandlerInterface;
    
    class YourMiddleware implements MiddlewareInterface
    {
        public function process(
            ServerRequestInterface $request,
            RequestHandlerInterface $handler
        ) : ResponseInterface {
        }
    }
    

    Note: vendor/bin/expressive middleware:create will create these correctly
    for you with its 1.0.0-dev release!

  • If you want to delegate handling to the next middleware, you will now use the
    $handler, and call its handle() method:

    $response = $handler->handle($request);
    
  • If you want to use one of the optional Expressive packages, such as
    zend-expressive-session, you will need to require it using a development
    constraint. For instance:

    $ composer require zendframework/zend-expressive-session:^1.0.0-dev
    

    Note the use of the semantic pin (^), as well as the -dev suffix; both are
    necessary for composer to identify the development release.

Regarding the last point, the following is a list of all packages with
development release branches, along with the corresponding version you should
use when requiring them while testing:

Package Version
zend-expressive ^3.0.0-dev
zend-expressive-aurarouter ^3.0.0-dev
zend-expressive-authentication ^1.0.0-dev
zend-expressive-authentication-oauth2 ^1.0.0-dev
zend-expressive-authorization ^1.0.0-dev
zend-expressive-csrf ^1.0.0-dev
zend-expressive-fastroute ^3.0.0-dev
zend-expressive-flash ^1.0.0-dev
zend-expressive-helpers ^5.0.0-dev
zend-expressive-plastesrenderer ^2.0.0-dev
zend-expressive-router ^3.0.0-dev
zend-expressive-session ^1.0.0-dev
zend-expressive-skeleton ^3.0.0-dev
zend-expressive-template ^2.0.0-dev
zend-expressive-tooling ^1.0.0-dev
zend-expressive-twigrenderer ^2.0.0-dev
zend-expressive-zendrouter ^3.0.0-dev
zend-expressive-zendviewrenderer ^2.0.0-dev
zend-problem-details ^1.0.0-dev
zend-stratigility ^3.0.0-dev

In most cases, unless you are extending classes we provide, your existing code
should just work with the new packages once you update your middleware to the
new signatures.

Updating an existing application

Updating an existing application requires a bit more effort. You will need to
manually edit your composer.json to update the constraints for each of the
above packages to match what is in the table. Additionally, if you see
references to either http-interop/http-middleware or
webimpress/http-middleware-compatibility, you will need to remove those.
You will also need to add the following two lines to the file:

"minimum-stability": "dev",
"prefer-stable": true

Once done with the composer.json changes, run composer update to pick up
the changes. If you encounter any issues, run rm -Rf composer.lock vendor, and
then execute composer install.

Finally, you will need to update any middleware in your application to
implement the new interface. Ensure you have zend-expressive-tooling
installed, and install it if you do not, using the ^1.0.0-dev constraint
(composer require --dev "zendframework/zend-expressive-tooling:^1.0.0-dev").
Once you do, run:

$ ./vendor/bin/expressive migrate:interop-middleware

What’s next?

If you run into things that do not work, report them on the appropriate issue
tracker.

Once PSR-15 is finalized, our plan is to go through and update each package
depending directly on it to point to the new PHP-FIG sponsored packages, and
update import statements throughout our code appropriately. We’ll then likely
issue a beta release for folks to test against one last time.

In the meantime, we’ll also be looking at other changes we may want to make. New
major version breaks should happen only rarely going forward, and we may want to
make a few more changes to help improve quality, simplify maintenance, and
increase usability before we make the final release. As we do, we’ll update you
here on the blog.

Want some ebooks on ZF and Expressive?

We collated our posts from the first half of 2017 into two ebooks:

  • Zend Framework 3 Cookbook, which covers usage of a couple dozen ZF
    components, within zend-mvc and Expressive applications, as well as
    standalone.
  • Expressive Cookbook, which covers features of Expressive and middleware
    in general.

You can get them free with registration on the zend.com
website
.

Source: Zend feed

A new release of zend-db

Today, we released zend-db 2.9.0!
This is our first new feature release in over 18 months, and contains
7 bug fixes, 6 new features, numerous unit test additions, and many
documentation improvements.

zend-db is an important component of many PHP projects, and we know that its
support is crucial for many people. As such, we allocated a number of weeks to
triaging the various open issues and patches (more than 50) to ensure we would
provide a stable release.

The release contains the following changes:

Added

  • #216 added AFTER support
    in ALTER TABLE syntax for MySQL.
  • #223 added support for
    empty values set with the IN predicate.
  • #271 added support for
    dash characters in MySQL identifiers.
  • #273 added support for
    implementing an error handler when used with db2_prepare.
  • #275 added support for
    LIMIT OFFSET for db2.
  • #280 added the version
    DSN parameter for the pdo_dblib extension.

Fixed

  • #205 fixes whitespace
    issues in ORDER BY syntax.
  • #224 fixes how parameters
    are bound to statements in the PDO adapter. PDO has a restriction on parameter
    names of [0-9a-zA_Z_]; as such, the driver now hashes the parameter names
    using md5() in order to ensure compatibility with other drivers.
  • #229 fixes SSL support
    in the mysqli adapter.
  • #255 fixes an edge case
    when using ResultSet with array values (versus objects).
  • #261 fixes how the
    Firebird adapter attempts to retrieve the last generated value so as to
    prevent exceptions being raised.
  • #276 and
    #287 provide fixes to
    enable usage of the component with PHP 7.2.

We also dropped support for PHP 5.5 (EOL last year) and HHVM; zend-db 2.9 and
above now only support PHP 5.6 and PHP 7+ releases.

Future of zend-db

We are planning a 3.0 release of zend-db release sometime in 2018. This new major version
will contain new features sucha as extended DDL support
for different database vendors (currently, most support targets MySQL), and support
for SEQUENCE.
Additionally, that release will drop support for PHP versions older than 7.1.

If you want to contribute to zend-db, you are more than welcome! For more
information, read the Zend Framework contribution guide.

Special thanks

A special thanks to the following zend-db contributors (in no particular order):

We also extend thanks to our community review team for their efforts in making
this release of zend-db possible.

Source: Zend feed

Emitting Responses with Diactoros

When writing middleware-based applications, at some point you will need to emit
your response
.

PSR-7 defines the various interfaces
related to HTTP messages, but does not define how they will be used.
Diactoros defines several
utility classes for these purposes, including a ServerRequestFactory for
generating a ServerRequest instance from the PHP SAPI in use, and a set of
emitters, for emitting responses back to the client. In this post, we’ll
detail the purpose of emitters, the emitters shipped with Diactoros, and some
strategies for emitting content to your users.

What is an emitter?

In vanilla PHP applications, you might call one or more of the following
functions in order to provide a response to your client:

  • http_response_code() for emitting the HTTP response code to use; this must
    be called before any output is emitted.
  • header() for emitting response headers. Like http_response_code(), this
    must be called before any output is emitted. It may be called multiple times,
    in order to set multiple headers.
  • echo(), printf(), var_dump(), and var_export() will each emit output
    to the current output buffer, or, if none is present, directly to the client.

One aspect PSR-7 aims to resolve is the ability to generate a response
piece-meal, including adding content and headers in whatever order your
application requires. To accomplish this, it provides a ResponseInterface with
which your application interacts, and which aggregates the response status code,
its headers, and all content.

Once you have a complete response, however, you need to emit it.

Diactoros provides emitters to solve this problem. Emitters all implement
ZendDiactorosResponseEmitterInterface:

namespace ZendDiactorosResponse;

use PsrHttpMessageResponseInterface;

interface EmitterInterface
{
    /**
     * Emit a response.
     *
     * Emits a response, including status line, headers, and the message body,
     * according to the environment.
     *
     * Implementations of this method may be written in such a way as to have
     * side effects, such as usage of header() or pushing output to the
     * output buffer.
     *
     * Implementations MAY raise exceptions if they are unable to emit the
     * response; e.g., if headers have already been sent.
     *
     * @param ResponseInterface $response
     */
    public function emit(ResponseInterface $response);
}

Diactoros provides two emitter implementations, both geared towards standard PHP
SAPI implementations:

  • ZendDiactorosEmitterSapiEmitter
  • ZendDiactorosEmitterSapiStreamEmitter

Internally, they operate very similarly: they emit the response status code, all
headers, and the response body content. Prior to doing so, however, they check
for the following conditions:

  • Headers have not yet been sent.
  • If any output buffers exist, no content is present.

If either of these conditions is not true, the emitters raise an exception.
This is done to ensure that consistent content can be emitted; mixing PSR-7 and
global output leads to unexpected and inconsistent results. If you are using
middleware, use things like the error log, loggers, etc. if you want to debug,
instead of mixing strategies.

Emitting files

As noted above, one of the two emitters is the SapiStreamEmitter. The normal
SapiEmitter emits the response body at once via a single echo statement.
This works for most general markup and JSON payloads, but when returning files
(for example, when providing file downloads via your application), this strategy
can quickly exhaust the amount of memory PHP is allowed to consume.

The SapiStreamEmitter is designed to answer the problem of file downloads. It
emits a chunk at a time (8192 bytes by default). While this can mean a bit more
performance overhead when emitting a large file, as you’ll have more method
calls, it also leads to reduced memory overhead, as less content is in memory
at any given time.

The SapiStreamEmitter has another important feature, however: it allows
sending content ranges.

Clients can opt-in to receiving small chunks of a file at a time. While this
means more network calls, it can also help prevent corruption of large files by
allowing the client to re-try failed requests in order to stitch together the
full file. Doing so also allows providing progress status, or even buffering
streaming content.

When requesting content ranges, the client will pass a Range header:

Range: bytes=1024-2047

It is up to the server then to detect such a header and return the requested
range. Servers indicate that they are doing so by responding with a Content-Range
header with the range of bytes being returned and the total number of bytes
possible; the response body then only contains those bytes.

Content-Range: bytes=1024-2047/11576

As an example, middleware that allows returning a content range might look like
the following:

function (ServerRequestInterface $request, DelegateInterface $delegate) : ResponseInterface
{
    $stream = new Stream('path/to/download/file', 'r');
    $response = new Response($stream);

    $range = $request->getHeaderLine('range');
    if (empty($range)) {
        return $response;
    }

    $size  = $body->getSize();
    $range = str_replace('=', ' ', $range);
    $range .= '/' . $size;

    return $response->withHeader('Content-Range', $range);
}

You’ll likely want to validate that the range is within the size of the file, too!

The above code emits a Content-Range response header if a Range header is in
the request. However, how do we ensure only that range of bytes is emitted?

By using the SapiStreamEmitter! This emitter will detect the Content-Range
header and use it to read and emit only the bytes specified by that header; no
extra work is necessary!

Mixing and matching emitters

The SapiEmitter is perfect for content generated within your application
— HTML, JSON, XML, etc. — as such content is usually of reasonable
length, and will not exceed normal memory and resource limits.

The SapiStreamEmitter is ideal for returning file downloads, but can lead to
performance overhead when emitting standard application content.

How can you mix and match the two?

Expressive answers this question by providing
ZendExpressiveEmitterEmitterStack. The class acts as a stack (last in,
first out), executing each emitter composed until one indicates it has handled
the response.

This class capitalizes on the fact that the return value of EmitterInterface
is undefined. Emitters that return a boolean false indicate they were unable
to handle the response
, allowing the EmitterStack to move to the next emitter
in the stack. The first emitter to return a non-false value halts execution.

Both the emitters defined in zend-diactoros return null by default. So, if we
want to create a stack that first tries SapiStreamEmitter, and then defaults
to SapiEmitter, we could do the following:

use PsrHttpMessageResponseInterface;
use ZendDiactorosResponseEmitterInterface;
use ZendDiactorosResponseSapiEmitter;
use ZendDiactorosResponseSapiStreamEmitter;
use ZendExpressiveEmitterEmitterStack;

$emitterStack = new EmitterStack();
$emitterStack->push(new SapiEmitter());
$emitterStack->push(new class implements EmitterInterface {
    public function emit(ResponseInterface $response)
    {
        $contentSize = $response->getBody()->getSize();

        if ('' === $response->getHeaderLine('content-range')
            && $contentSize < 8192
        ) {
            return false;
        }

        $emitter = new SapiStreamEmitter();
        return $emitter->emit($response);
    }
});

The above will execute our anonymous class as the first emitter. If the response
has a Content-Range header, or if the size of the content is greater than 8k,
it will use the SapiStreamEmitter; otherwise, it returns false, allowing the
next emitter in the stack, SapiEmitter, to execute. Since that emitter always
returns null, it acts as a default emitter implementation.

In Expressive, if you were to wrap the above in a factory that returns the
$emitterStack, and assign that factory to the
ZendDiactorosEmitterEmitterInterface service, then the above stack will be
used by ZendExpressiveApplication for the purpose of emitting the
application response!

Summary

Emitters provide you the ability to return the response you have aggregated in
your application to the client. They are intended to have side-effects: sending
the response code, response headers, and body content. Different emitters can
use different strategies when emitting responses, from simply echoing content,
to iterating through chunks of content (as the SapiStreamEmitter does). Using
Expressive’s EmitterStack can provide you with a way to select different
emitters for specific response criteria.

For more information:

Source: Zend feed

Logging PHP applications

Every PHP application generates errors, warnings, and notices and throws
exceptions. If we do not log this information, we lose a way to identify and
solve problems at runtime. Moreover, we may need to log specific actions such as
a user login and logout attempts. All such information should be filtered and
stored in an efficient way.

PHP offers the function error_log() to send an error
message to the defined system logger, and the function
set_error_handler() to specify a handler for
intercepting warnings, errors, and notices generated by PHP.

These functions can be used to customize error management, but it’s up to the
developer to write the logic to filter and store the data.

Zend Framework offers a logging component, zend-log;
the library can be used as a general purpose logging system. It supports
multiple log backends, formatting messages sent to the log, and filtering
messages from being logged.

Last but not least, zend-log is compliant with PSR-3,
the logger interface standard.

Installation

You can install zend-log using the
following composer command:

composer require zendframework/zend-log

Usage

zend-log can be used to create log entries in different formats using multiple
backends. You can also filter the log data from being saved, and process the
log event prior to filtering or writing, allowing the ability to substitute,
add, remove, or modify the data you log.

Basic usage of zend-log requires both a writer and a logger instance.
A writer stores the log entry into a backend, and the logger
consumes the writer to perform logging operations.

As an example:

use ZendLogLogger;
use ZendLogWriterStream;

$logger = new Logger;
$writer = new Stream('php://output');

$logger->addWriter($writer);
$logger->log(Logger::INFO, 'Informational message');

The above produces the following output:

2017-09-11T15:07:46+02:00 INFO (6): Informational message

The output is a string containing a timestamp, a priority (INFO (6)) and the
message (Informational message). The output format can be changed using the
setFormatter() method of the writer object ($writer).
The default log format, produced by the Simple
formatter is as follows:

%timestamp% %priorityName% (%priority%): %message% %extra%

where %extra% is an optional value containing additional information.

For instance, if you wanted to change the format to include only log %message%, you could do the following:

$formatter = new ZendLogFormatterSimple('log %message%' . PHP_EOL);
$writer->setFormatter($formatter);

Log PHP events

zend-log can also be used to log PHP errors and exceptions. You can log PHP
errors using the static method Logger::registerErrorHandler($logger) and
intercept exceptions using the static method Logger::registerExceptionHandler($logger).

use ZendLogLogger;
use ZendLogWriter;

$logger = new Logger;
$writer = new WriterStream(__DIR__ . '/test.log');
$logger->addWriter($writer);

// Log PHP errors
Logger::registerErrorHandler($logger);

// Log exceptions
Logger::registerExceptionHandler($logger);

Filtering data

As mentioned, we can filter the data to be logged; filtering removes messages
that match the filter criteria, preventing them from being logged.

We can use the addFilter() method of the Writer
interface

to add a specific filter.

For instance, we can filter by priority, accepting only log entries with a
priority less than or equal to a specific value:

$filter = new ZendLogFilterPriority(Logger::CRIT);
$writer->addFilter($filter);

In the above example, the logger will only store log entries with a priority
less than or equal to Logger::CRIT (critical). The priorities are defined by
the ZendLogLogger class:

const EMERG   = 0;  // Emergency: system is unusable
const ALERT   = 1;  // Alert: action must be taken immediately
const CRIT    = 2;  // Critical: critical conditions
const ERR     = 3;  // Error: error conditions
const WARN    = 4;  // Warning: warning conditions
const NOTICE  = 5;  // Notice: normal but significant condition
const INFO    = 6;  // Informational: informational messages
const DEBUG   = 7;  // Debug: debug messages

As such, only emergency, alerts, or critical entries would be logged.

We can also filter log data based on regular expressions, timestamps, and more.
One powerful filter uses a zend-validator
ValidatorInterface instance to filter the log; only valid entries would be
logged in such cases.

Processing data

If you need to provide additional information to logs in an automated fashion,
you can use a ZendLogProcesser class. A processor is executed before the
log data are passed to the writer. The input of a processor is a log event,
an array containing all of the information to log; the output is also a log
event
, but can contain modified or additional values. A processor modifies
the log event to prior to sending it to the writer.

You can read about processor adapters offered by zend-log in the
documentation
.

Multiple backends

One of the cool feature of zend-log is the possibility to write logs using
multiple backends. For instance, you can write a log to both a file and a
database using the following code:

use ZendDbAdapterAdapter as DbAdapter;
use ZendLogFormatter;
use ZendLogWriter;
use ZendLogLogger;

// Create our adapter
$db = new DbAdapter([
    'driver'   => 'Pdo',
    'dsn'      => 'mysql:dbname=testlog;host=localhost',
    'username' => 'root',
    'password' => 'password'
]);

// Map event data to database columns
$mapping = [
    'timestamp' => 'date',
    'priority'  => 'type',
    'message'   => 'event',
];

// Create our database log writer
$writerDb = new WriterDb($db, 'log', $mapping); // log table
$formatter = new FormatterBase();
$formatter->setDateTimeFormat('Y-m-d H:i:s'); // MySQL DATETIME format
$writerDb->setFormatter($formatter);

// Create our file log writer
$writerFile = new WriterStream(__DIR__ . '/test.log');

// Create our logger and register both writers
$logger = new Logger();
$logger->addWriter($writerDb, 1);
$logger->addWriter($writerFile, 100);

// Log an information message
$logger->info('Informational message');

The database writer requires the credentials to access the table where you will
store log information. You can customize the field names for the database table
using a $mapping array, containing an associative array mapping log fields to
database columns.

The database writer is composed in $writerDb and the file writer in
$writerFile. The writers are added to the logger using the addWriter()
method with a priority number; higher integer values indicate higher priority
(triggered earliest). We chose priority 1 for the database writer, and priority
100 for the file writer; this means the file writer will log first, followed by
logging to the database.

Note: we used a special date formatter for the database writer. This is
required to translate the log timestamp into the DATETIME format of MySQL.

PSR-3 support

If you need to be compatible with PSR-3,
you can use ZendLogPsrLoggerAdapter. This logger can be used anywhere
a PsrLogLoggerInterface is expected.

As an example:

use PsrLogLogLevel;
use ZendLogLogger;
use ZendLogPsrLoggerAdapter;

$zendLogLogger = new Logger;
$psrLogger = new PsrLoggerAdapter($zendLogLogger);

$psrLogger->log(LogLevel::INFO, 'We have a PSR-compatible logger');

To select a PSR-3 backend for writing, we can use the ZendLogWriterPsr
class. In order to use it, you need to pass a PsrLogLoggerInterface instance
to the $psrLogger constructor argument:

$writer = new ZendLogWriterPsr($psrLogger);

zend-log also supports PSR-3 message
placeholders

via the ZendLogProcessorPsrPlaceholder class. To use it, you need to add a
PsrPlaceholder instance to a logger, using the addProcess() method.
Placeholder names correspond to keys in the "extra" array passed when logging a
message:

use ZendLogLogger;
use ZendLogProcessorPsrPlaceholder;

$logger = new Logger;
$logger->addProcessor(new PsrPlaceholder);

$logger->info('User with email {email} registered', ['email' => 'user@example.org']);

An informational log entry will be stored with the message User with email user@example.org registered.

Logging an MVC application

If you are using a zend-mvc based
application, you can use zend-log as module. zend-log provides a
Module.php
class, which registers ZendLog as a module in your application.

In particular, the zend-log module provides the following services (under
the namespace ZendLog):

Logger::class         => LoggerServiceFactory::class,
'LogFilterManager'    => FilterPluginManagerFactory::class,
'LogFormatterManager' => FormatterPluginManagerFactory::class,
'LogProcessorManager' => ProcessorPluginManagerFactory::class,
'LogWriterManager'    => WriterPluginManagerFactory::class,

The Logger::class service can be configured using the log config key;
the documentation provides configuration examples.

In order to use the Logger service in your MVC stack, grab it from the service
container. For instance, you can pass the Logger service in a controller using
a factory:

use ZendLogLogger;
use ZendServiceManagerFactoryFactoryInterface;

class IndexControllerFactory implements FactoryInterface
{
    public function __invoke(
        ContainerInterface $container,
        $requestedName,
        array $options = null
    ) {
        return new IndexController(
            $container->get(Logger::class)
        );
    }
}

via the following service configuration for the IndexController:

'controllers' => [
    'factories' => [
        IndexController::class => IndexControllerFactory::class,
    ],
],

Logging a middleware application

You can also integrate zend-log in your middleware applications.
If you are using
Expressive,
add the component’s ConfigProvider
to your config/config.php file.

Note: if you are using zend-component-installer,
you will be prompted to install zend-log’s config provider when you install
the component via Composer.

Note: This configuration registers the same services
provided in the zend-mvc example, above.

To use zend-log in middleware, grab it from the dependency injection
container and pass it as a dependency to your middleware:

namespace AppAction;

use PsrContainerContainerInterface;
use ZendLogLogger;

class HomeActionFactory
{
    public function __invoke(ContainerInterface $container) : HomeAction
    {
        return new HomeAction(
            $container->get(Logger::class)
        );
    }
}

As an example of logging in middleware:

namespace AppAction;

use InteropHttpServerMiddlewareDelegateInterface;
use InteropHttpServerMiddlewareMiddlewareInterface as ServerMiddlewareInterface;
use PsrHttpMessageServerRequestInterface;
use ZendLogLogger;

class HomeAction implements ServerMiddlewareInterface
{
    private $logger;

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

    public function process(
        ServerRequestInterface $request,
        DelegateInterface $delegate
    ) {
        $this->logger->info(__CLASS__ . ' has been executed');

        // ...
    }
}

Listening for errors in Expressive

Expressive and Stratigility
provide a default error handler middleware implementation,
ZendStratigilityMiddlewareErrorHandler which listens for PHP errors and
exceptions/throwables. By default, it spits out a simple error page when an
error occurs, but it also provides the ability to attach listeners, which can
then act on the provided error.

Listeners receive the error, the request, and the response that the error
handler will be returning. We can use that information to log information!

First, we create an error handler listener that composes a logger, and logs the
information:

use Exception;
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use Throwable;
use ZendLogLogger;

class LoggingErrorListener
{
    /**      
     * Log message string with placeholders
     */
    const LOG_STRING = '{status} [{method}] {uri}: {error}';

    private $logger;

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

    public function __invoke(
        $error,
        ServerRequestInterface $request,
        ResponseInterface $response
    ) {
        $this->logger->error(self::LOG_STRING, [
            'status' => $response->getStatusCode(),
            'method' => $request->getMethod(),
            'uri'    => (string) $request->getUri(),
            'error'  => $error->getMessage(),
        ]);
    }
}

The ErrorHandler implementation casts PHP errors to ErrorException
instances, which means that $error is always some form of throwable.

We can then write a delegator factory that will register this as a listener on
the ErrorHandler:

use LoggingErrorListener;
use PsrContainerContainerInterface;
use ZendLogLogger;
use ZendLogProcessorPsrPlaceholder;
use ZendStratigilityMiddlewareErrorHandler;

class LoggingErrorListenerFactory
{
    public function __invoke(
        ContainerInterface $container,
        $serviceName,
        callable $callback
    ) : ErrorHandler {
        $logger = $container->get(Logger::class);
        $logger->addProcessor(new PsrPlaceholder());

        $listener = new LoggingErrorListener($logger);
        
        $errorHandler = $callback();
        $errorHandler->attachListener($listener);
        return $errorHandler;
    }
}

And then register the delegator in your configuration:

// In a ConfigProvider, or a config/autoload/*.global.php file:
use LoggingErrorListenerFactory;
use ZendStratigilityMiddlewareErrorHandler;

return [
    'dependencies' => [
        'delegators' => [
            ErrorHandler::class => [
                LoggingErrorListenerFactory::class,
            ],
        ],
    ],
];

At this point, your error handler will now also log errors to your configured
writers!

Summary

The zend-log component offers a wide set of features,
including support for multiple writers, filtering of log data,
compatibility with PSR-3, and
more.

Hopefully you can use the examples above for consuming zend-log in your
standalone, zend-mvc, Expressive, or general middleware applications!

Learn more in the zend-log documentation.

Source: Zend feed

Specialized Response Implementations in Diactoros

When writing PSR-7 middleware, at some
point you’ll need to return a response.

Maybe you’ll be returning an empty response, indicating something along the
lines of successful deletion of a resource. Maybe you need to return some HTML,
or JSON, or just plain text. Maybe you need to indicate a redirect.

But here’s the problem: a generic response typically has a very generic
constructor. Take, for example, ZendDiactorosResponse:

public function __construct(
    $body = 'php://memory',
    $status = 200,
    array $headers = []
)

$body in this signature allows either a PsrHttpMessageStreamInterface
instance, a PHP resource, or a string identifying a PHP stream. This means that
it’s not terribly easy to create even a simple HTML response!

To be fair, there are good reasons for a generic constructor: it allows
setting the initial state in such a way that you’ll have a fully populated
instance immediately. However, the means for doing so, in order to be
generic, leads to convoluted code for most consumers.

Fortunately, Diactoros provides a number of convenience implementations to help
simplify the most common use cases.

EmptyResponse

The standard response from an API for a successful deletion is generally a 204 No Content. Sites emitting webhook payloads often expect a 202 Accepted with
no content. Many APIs that allow creation of resources will return a 201 Created; these may or may not have content, depending on implementation, with
some being empty, but returning a Location header with the URI of the newly
created resource.

Clearly, in such cases, if you don’t need content, why would you be bothered to
create a stream? To answer this, we have
ZendDiactorosResponseEmptyResponse, with the following constructor:

public function __construct($status = 204, array $headers = [])

So, a DELETE endpoint might return this on success:

return new EmptyResponse();

A webhook endpoint might do this:

return new EmptyResponse(StatusCodeInterface::STATUS_ACCEPTED);

An API that just created a resource might do the following:

return new EmptyResponse(
    StatusCodeInterface::STATUS_CREATED,
    ['Location' => $resourceUri]
);

RedirectResponse

Redirects are common within web applications. We may want to redirect a user to
a login page if they are not currently logged in; we may have changed where some
of our content is located, and redirect users requesting the old URIs; etc.

ZendDiactorosResponseRedirectResponse provides a simple way to create and
return a response indicating an HTTP redirect. The signature is:

public function __construct($uri, $status = 302, array $headers = [])

where $uri may be either a string URI, or a PsrHttpMessageUriInterface
instance. This value will then be used to seed a Location HTTP header.

return new RedirectResponse('/login');

You’ll note that the $status defaults to 302. If you want to set a permanent
redirect, pass 301 for that argument:

return new RedirectResponse('/archives', 301);

// or, using fig/http-message-util:
return new RedirectResponse('/archives', StatusCodeInterface::STATUS_PERMANENT_REDIRECT);

Sometimes you may want to set an header as well; do that by passing the third
argument, an array of headers to provide:

return new RedirectResponse(
    '/login',
    StatusCodeInterface::STATUS_TEMPORARY_REDIRECT,
    ['X-ORIGINAL_URI' =>  $uri->getPath()]
);

TextResponse

Sometimes you just want to return some text, whether it’s plain text, XML, YAML,
etc. When doing that, taking the extra step to create a stream feels like
overhead:

$stream = new Stream('php://temp', 'wb+');
$stream->write($content);

To simplify this, we offer ZendDiactorosResponseTextResponse, with the
following signature:

public function __construct($text, $status = 200, array $headers = [])

By default, it will use a Content-Type of text/plain, which means you’ll
often need to supply a Content-Type header with this response.

Let’s return some plain text:

return new TextResponse('Hello, world!');

Now, let’s try returning a Problem Details XML response:

return new TextResponse(
    $xmlPayload,
    StatusCodeInterface::STATUS_UNPROCESSABLE_ENTITY,
    ['Content-Type' => 'application/problem+xml']
);

If you have some textual content, this is the response for you.

HtmlResponse

The most common response from web applications is HTML. If you’re returning
HTML, even the TextResponse may seem a bit much, as you’re forced to provide
the Content-Type header. To answer that, we provide
ZendDiactorosResponseHtmlResponse, which is exactly the same as
TextResponse, but with a default Content-Type header specifying
text/html; charset=utf-8 instead.

As an example:

return new HtmlResponse($renderer->render($template, $view));

JsonResponse

For web APIs, JSON is generally the lingua franca. Within PHP, this generally
means passing an array or object to json_encode(), and supplying a
Content-Type header of application/json or application/{type}+json, where
{type} is a more specific mediatype.

Like text and HTML, you likely don’t want to do this manually every time:

$json = json_encode(
  $data,
  JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES
);
$stream = new Stream('php://temp', 'wb+');
$stream->write($json);
$response = new Response(
    $stream,
    StatusCodeInterface::STATUS_OK,
    ['Content-Type' => 'application/json']
);

To simplify this, we provide ZendDiactorosResponseJsonResponse, with the
following constructor signature:

public function __construct(
    $data,
    $status = 200,
    array $headers = [],
    $encodingOptions = self::DEFAULT_JSON_FLAGS
) {

where $encodingOptions defaults to the flags specified in the previous
example.

This means our most common use case now becomes this:

return new JsonResponse($data);

What if we want to return a JSON-formatted Problem Details response?

return new JsonResponse(
    $details,
    StatusCodeInterface::STATUS_UNPROCESSABLE_ENTITY,
    ['Content-Type' => 'application/problem+json']
);

One common workflow we’ve seen with JSON responses is that developers often want
to manipulate them on the way out through middleware. As an example, they may
want to add additional _links elements to HAL responses, or add counts for
collections.

Starting in version 1.5.0, we provide a few extra methods on this particular
response type:

public function getPayload() : mixed;
public function getEncodingOptions() : int;
public function withPayload(mixed $data) : JsonResponse;
public function withEncodingOptions(int $options) : JsonResponse;

Essentially, what happens is we now store not only the encoded $data
internally, but the raw data; this allows you to pull it, manipulate it, and
then create a new instance with the updated data. Additionally, we allow
specifying a different set of encoding options later; this can be useful, for
instance, for adding the JSON_PRETTY_PRINT flag when in development. When the
options are changed, the new instance will also re-encode the existing data.

First, let’s look at altering the payload on the way out. zend-expressive-hal
injects _total_items, _page, and _page_count properties, and you may want
to remove the underscore prefix for each of these:

function (ServerRequestInterface $request, DelegateInterface $delegate) : ResponseInterface
{
    $response = $delegate->process($request);
    if (! $response instanceof JsonResponse) {
        return $response;
    }

    $payload = $response->getPayload();
    if (! isset($payload['_total_items'])) {
        return $response;
    }

    $payload['total_items'] = $payload['_total_items'];
    unset($payload['_total_items']);

    if (isset($payload['_page'])) {
        $payload['page'] = $payload['_page'];
        $payload['page_count'] = $payload['_page_count'];
        unset($payload['_page'], $payload['_page_count']);
    }

    return $response->withPayload($payload);
}

Now, let’s write middleware that sets the JSON_PRETTY_PRINT option when in
development mode:

function (
    ServerRequestInterface $request,
    DelegateInterface $delegate
) : ResponseInterface use ($isDevelopmentMode) {
    $response = $delegate->process($request);

    if (! $isDevelopmentMode || ! $response instanceof JsonResponse) {
        return $response;
    }

    $options = $response->getEncodingOptions();
    return $response->withEncodingOptions($options | JSON_PRETTY_PRINT);
}

These features can be really powerful when shaping your API!

Summary

The goal of PSR-7 is to provide the ability to standardize on interfaces for
your HTTP interactions. However, at some point you need to choose an actual
implementation, and your choice will often be shaped by the features offered,
particularly if they provide convenience in your development process. Our goal
with these various custom response implementations is to provide convenience
to developers, allowing them to focus on what they need to return, not how to
return it.

You can check out more in the Diactoros documentation.

Source: Zend feed

Community Corner: Discourse Forums!

For many years, we’ve had requests for dedicated Zend Framework forums. We’ve
resisted doing so, and instead deferred to using mailing lists and Stack
Overflow tags. However, these are imperfect: searching for questions and answers
is often difficult if not impossible.

Discourse

In the past few years, Discourse has proved itself
as a community-centric forum solution. Further, they have started offering
free hosting for open source communities, making it a compelling option to
consider.

We recently reached out to them, and they have agreed to host the Zend Framework
forums for us.

Zend Framework Community

So, please join us on the Zend Framework Discourse Forums!

You may sign up with any Google, GitHub, or Twitter account, or register
directly with the site.

Some pages of interest:

Additionally, we have setup a section for
contributors to discuss
architecture, milestones, etc.

Welcome all, and we look forward to your conversations!

Source: Zend feed

Manage permissions with zend-permissions-rbac

In our previous post, we
covered authentication of a user via Expressive middleware. In that post, we
indicated that we would later discuss authorization, which is the activity of
checking if an authenticated user has permissions to perform a specific
action, from within the context of a middleware application.

Before we do that, however, we thought we’d introduce
zend-permissions-rbac,
our lightweight role-based access control (RBAC) implementation.

Installing zend-permissions-rbac

Just as you would any of our components, install zend-permissions-rbac via
Composer:

$ composer require zendframework/zend-permissions-rbac

The component has no requirements at this time other than a PHP version of at
least 5.5.

Vocabulary

In RBAC systems, we have three primary items to track:

  • the RBAC system composes zero or more roles.
  • a role is granted zero or more permissions.
  • we assert whether or not a role is granted a given permission.

zend-permissions-rbac supports role inheritance, even allowing a role to inherit
permissions from multiple other roles. This allows you to create some fairly
complex and fine-grained permissions schemes!

Basics

As a basic example, we’ll create an RBAC for a content-based website. Let’s
start with a "guest" role, that only allows "read" permissions.

use ZendPermissionsRbacRbac;
use ZendPermissionsRbacRole;

// Create some roles
$guest= new Role('guest');
$guest>addPermission('read');

$rbac = new Rbac();
$rbac->addRole($guest);

We can then assert if a given role is granted specific permissions:

$rbac->isGranted('guest', 'read'); // true
$rbac->isGranted('guest', 'write'); // false

Unknown roles

One thing to note: if the role used with isGranted() does not exist, this
method raises an exception, specifically a
ZendPermissionsRbacExceptionInvalidArgumentException, indicating the
role could not be found.

In many situations, this may not be what you want; you may want to handle
non-existent roles gracefully. You could do this in a couple ways. First, you
can test to see if the role exists before you check the permissions, using
hasRole():

if (! $rbac->hasRole($foo)) {
    // failed, due to missing role
}
if (! $rbac->isGranted($foo, $permission)) {
    // failed, due to missing permissions
}

Alternately, wrap the isGranted() call in a try/catch block:

try {
    if (! $rbac->isGranted($foo, $permission)) {
        // failed, due to missing permissions
    }
} catch (RbacInvalidArgumentException $e) {
    if (! strstr($e->getMessage(), 'could be found')) {
        // failed, due to missing role
    }

    // some other error occured
    throw $e;
}

Personally, I don’t like to use exceptions for application flow, so I
recommend the first solution. That said, in most cases, you will be working
with a role instance that you’ve just added to the RBAC.

Role inheritance

Let’s say we want to build on the previous example, and create an "editor" role
that also incorporates the permissions of the "guest" role, and adds a "write"
permission.

You might be inclined to think of the "editor" as inheriting from the "guest"
role — in other words, that it is a descendent or child of it.
However, in RBAC, inheritance works in the opposite direction: a parent
inherits all permissions of its children. As such, we’ll create the role as
follows:

$editor = new Role('editor');
$editor->addChild('guest');
$editor->addPermission('write');

$rbac->addRole($editor);

$rbac->isGranted('editor', 'write'); // true
$rbac->isGranted('editor', 'read');  // true
$rbac->isGranted('guest',  'write'); // false

Another role might be a "reviewer" who can "moderate" content:

$reviewer = new Role('reviewer');
$reviewer->addChild('guest');
$reviewer->addPermission('moderate');

$rbac->addRole($reviewer);

$rbac->isGranted('reviewer', 'moderate'); // true
$rbac->isGranted('reviewer', 'write');    // false; editor only!
$rbac->isGranted('reviewer', 'read');     // true
$rbac->isGranted('guest',    'moderate'); // false

Let’s create another, an "admin" who can do all of the above, but also has
permissions for "settings":

$admin= new Role('admin');
$admin->addChild('editor');
$admin->addChild('reviewer');
$admin->addPermission('settings');

$rbac->addRole($admin);

$rbac->isGranted('admin',    'settings'); // true
$rbac->isGranted('admin',    'write');    // true
$rbac->isGranted('admin',    'moderate'); // true
$rbac->isGranted('admin',    'read');     // true
$rbac->isGranted('editor',   'settings'); // false
$rbac->isGranted('reviewer', 'settings'); // false
$rbac->isGranted('guest',    'write');    // false

As you can see, permissions lookups are recursive and collective; the RBAC
examines all children and each of their descendants as far down as it needs to
determine if a given permission is granted!

Creating your RBAC

When should you create your RBAC, exactly? And should it contain all roles and
permissions?

In most cases, you will be validating a single user’s permissions. What’s
interesting about zend-permissions-rbac is that if you know that user’s role,
the permissions they have been assigned, and any child roles (and their
permissions) to which the role belongs, you have everything you need. This means
that you can do most lookups on-the-fly.

As such, you will typically do the following:

  • Create a finite set of well-known roles and their permissions as a global RBAC.
  • Add roles (and optionally permissions) for the current user.
  • Validate the current user against the RBAC.

As an example, let’s say I have a user Mario who has the role "editor", and also
adds the permission "update". If our RBAC is already populated per the above
examples, I might do the following:

$mario= new Role('mario');
$mario->addChild('editor');
$mario->addPermission('update');

$rbac->addRole($mario);

$rbac->isGranted($mario,   'settings'); // false; admin only!
$rbac->isGranted($mario,   'update');   // true; mario only!
$rbac->isGranted('editor', 'update');   // false; mario only!
$rbac->isGranted($mario,   'write');    // true; all editors
$rbac->isGranted($mario,   'read');     // true; all guests

Assigning roles to users

When you have some sort of authentication system in place, it will return some
sort of identity or user instance generally. You will then need to map this
to RBAC roles. But how?

Hopefully, you can store role information wherever you persist your user
information. Since roles are essentially stored internally as strings by
zend-permissions-rbac, this means that you can store the user role as a discrete
datum with your user identity.

Once you have, you have a few options:

  • Use the role directly from your identity when checking permissions: e.g.,
    $rbac->isGranted($identity->getRole(), 'write')
  • Create a ZendPermissionsRbacRole instance (or other concrete class) with
    the role fetched from the identity, and use that for permissions checks:
    $rbac->isGranted(new Role($identity->getRole()), 'write')
  • Update your identity instance to implement
    ZendPermissionsRbacRoleInterface, and pass it directly to permissions
    checks: $rbac->isGranted($identity, 'write')

This latter approach provides a nice solution, as it then also allows you to
store specific permissions and/or child roles as part of the user data.

The RoleInterface looks like the following:

namespace ZendPermissionsRbac;

use RecursiveIterator;

interface RoleInterface extends RecursiveIterator
{
    /**
     * Get the name of the role.
     *
     * @return string
     */
    public function getName();

    /**
     * Add permission to the role.
     *
     * @param $name
     * @return RoleInterface
     */
    public function addPermission($name);

    /**
     * Checks if a permission exists for this role or any child roles.
     *
     * @param  string $name
     * @return bool
     */
    public function hasPermission($name);

    /**
     * Add a child.
     *
     * @param  RoleInterface|string $child
     * @return Role
     */
    public function addChild($child);

    /**
     * @param  RoleInterface $parent
     * @return RoleInterface
     */
    public function setParent($parent);

    /**
     * @return null|RoleInterface
     */
    public function getParent();
}

The ZendPermissionsRbacAbstractRole contains basic implementations of most
methods of the interface, including logic for querying child permissions, so we
suggest inheriting from that if you can.

As an example, you could store the permissions as a comma-separated string and
the parent role as a string internally when creating your identity instance:

use ZendPermissionsRbacAbstractRole;
use ZendPermissionsRbacRoleInterface;
use ZendPermissionsRbacRole;

class Identity extends AbstractRole
{
    /**
     * @param string $username
     * @param string $role
     * @param array $permissions
     * @param array $childRoles
     */
    public function __construct(
        string $username,
        array $permissions = [],
        array $childRoles = []
    ) {
        // $name is defined in AbstractRole
        $this->name = $username;

        foreach ($this->permissions as $permission) {
            $this->addPermission($permission);
        }

        $childRoles = array_merge(['guest'], $childRoles);
        foreach ($this->childRoles as $childRole) {
            $this->addChild($childRole);
        }
    }
}

Assuming your authentication system uses a database table, and a lookup returns
an array-like row with the user information on a successful lookup, you might
then seed your identity instance as follows:

$identity = new Identity(
    $row['username'],
    explode(',', $row['permissions']),
    explode(',', $row['roles'])
);

This approach allows you to assign pre-determined roles to individual users,
while also allowing you to add fine-grained, individual permissions!

Custom assertions

Sometimes a static assertion is not enough.

As an example, we may want to implement a rule that the creator of a content
item in our website always has rights to edit the item. How would we implement
that with the above system?

zend-permissions-rbac allows you to do so via dynamic assertions. Such
assertions are classes that implement
ZendPermissionsRbacAssertionInterface, which defines the single method
public function assert(Rbac $rbac).

For the sake of this example, let’s assume:

  • The content item is represented as an object.
  • The object has a method getCreatorUsername() that will return the same
    username as we might have in our custom identity from the previous example.

Because we have PHP 7 at our disposal, we’ll create the assertion as an
anonymous class:

use ZendPermissionsRbacAssertionInterface;
use ZendPermissionsRbacRbac;
use ZendPermissionsRbacRoleInterface;

$assertion = new class ($identity, $content) implements AssertionInterface {
    private $content;
    private $identity;

    public function __construct(RoleInterface $identity, $content)
    {
        $this->identity = $identity;
        $this->content = $content;
    }

    public function assert(Rbac $rbac)
    {
        return $this->identity->getName() === $this->content->getCreatorUsername();
    }
};

$rbac->isGranted($mario, 'edit', $assertion); // returns true if $mario created $content

This opens even more possibilities than inheritance!

Summary

zend-permissions-rbac is quite simple to operate, but that simplicity hides a
great amount of flexibility and power; you can create incredibly fine-grained
permissions schemes for your applications using this component!

Next week, Enrico will cover using the component within a middleware stack; stay
tuned!

Save the date!

Want to learn more about Expressive and Zend Framework? What better location
than ZendCon 2017! ZendCon will be hosted 23-26 October 2017 in Las Vegas,
Nevada, USA. Visit the ZendCon website for more
information
.

Source: Zend feed

Middleware authentication

Many web applications require restricting specific areas to authenticated
users, and may further restrict specific actions to authorized user roles.
Implementing authentication and authorization in a PHP application is often
non-trivial as doing so requires altering the application workflow. For
instance, if you have an MVC design, you may need to change the dispatch logic
to add an authentication layer as an initial event in the execution flow, and
perhaps apply restrictions within your controllers.

Using a middleware approach is simpler and more natural, as middleware easily
accommodates workflow changes. In this article, we will demonstrate how to
provide authentication in a PSR-7 middleware application using
Expressive and
zend-authentication. We
will build a simple authentication system using a login page with username and
password credentials.

Since the content of this post is quite long, we’ll detail authorization
in a separate blog post.

Getting started

This article assumes you have already created an Expressive application. For the
purposes of our application, we’ll create a new module, Auth, in which we’ll
put our classes, middleware, and general configuration.

First, if you have not already, install the tooling support:

$ composer require --dev zendframework/zend-expressive-tooling

Next, we’ll create the Auth module:

$ ./vendor/bin/expressive module:create Auth

With that out of the way, we can get started.

Authentication

The zend-authentication component offers an adapter-based authentication
solution, with both a number of concrete adapters as well as mechanisms for
creating and consuming custom adapters.

The component exposes ZendAuthenticationAdapterAdapterInterface, which
defines a single authenticate() method:

namespace ZendAuthenticationAdapter;

interface AdapterInterface
{
    /**
     * Performs an authentication attempt
     *
     * @return ZendAuthenticationResult
     * @throws ExceptionExceptionInterface if authentication cannot be performed
     */
    public function authenticate();
}

Adapters implementing the authenticate() method perform the logic necessary to
authenticate a request, and return the results via a
ZendAuthenticationResult object. This Result object contains the
authentication result code and, in the case of success, the user’s identity.
The authentication result codes are defined using the following constants:

namespace ZendAuthentication;

class Result
{
    const SUCCESS = 1;
    const FAILURE = 0;
    const FAILURE_IDENTITY_NOT_FOUND = -1;
    const FAILURE_IDENTITY_AMBIGUOUS = -2;
    const FAILURE_CREDENTIAL_INVALID = -3;
    const FAILURE_UNCATEGORIZED = -4;
}

If we want to implement a login page with username and password
authentication, we can create a custom adapter such as the following:

// In src/Auth/src/MyAuthAdapter.php:

namespace Auth;

use ZendAuthenticationAdapterAdapterInterface;
use ZendAuthenticationResult;

class MyAuthAdapter implements AdapterInterface
{
    private $password;
    private $username;

    public function __construct(/* any dependencies */)
    {
        // Likely assign dependencies to properties
    }

    public function setPassword(string $password) : void
    {
        $this->password = $password;
    }

    public function setUsername(string $username) : void
    {
        $this->username = $username;
    }

    /**
     * Performs an authentication attempt
     *
     * @return Result
     */
    public function authenticate()
    {
        // Retrieve the user's information (e.g. from a database)
        // and store the result in $row (e.g. associative array).
        // If you do something like this, always store the passwords using the
        // PHP password_hash() function!

        if (password_verify($this->password, $row['password'])) {
            return new Result(Result::SUCCESS, $row);
        }

        return new Result(Result::FAILURE_CREDENTIAL_INVALID, $this->username);
    }
}

We will want a factory for this service as well, so that we can seed the
username and password to it later:

// In src/Auth/src/MyAuthAdapterFactory.php:

namespace Auth;

use InteropContainerContainerInterface;
use ZendAuthenticationAuthenticationService;

class MyAuthAdapterFactory
{
    public function __invoke(ContainerInterface $container)
    {
        // Retrieve any dependencies from the container when creating the instance
        return new MyAuthAdapter(/* any dependencies */);
    }
}

This factory class creates and returns an instance of MyAuthAdapter.
We may need to pass some dependencies to its constructor, such as a database
connection; these would be fetched from the container.

Authentication Service

We can now create a ZendAuthenticationAuthenticationService
that composes our adapter, and then consume the AuthenticationService in
middleware to check for a valid user. Let’s now create a factory for the
AuthenticationService:

// in src/Auth/src/AuthenticationServiceFactory.php:

namespace Auth;

use InteropContainerContainerInterface;
use ZendAuthenticationAuthenticationService;

class AuthenticationServiceFactory
{
    public function __invoke(ContainerInterface $container)
    {
        return new AuthenticationService(
            null,
            $container->get(MyAuthAdapter::class)
        );
    }
}

This factory class retrieves an instance of the MyAuthAdapter service and use
it to return an AuthenticationService instance. The AuthenticationService
class accepts two parameters:

  • A storage service instance, for persisting the user identity. If none is
    provided, the built-in PHP session mechanisms will be used.
  • The actual adapter to use for authentication.

Now that we have created both the custom adapter, as well as factories for the
adapter and the AuthenticationService, we need to configure our application
dependencies to use them:

// In src/Auth/src/ConfigProvider.php:

// Add the following import statement at the top of the classfile:
use ZendAuthenticationAuthenticationService;

// And update the following method:
public function getDependencies()
{
    return [
        'factories' => [
            AuthenticationService::class => AuthenticationServiceFactory::class,
            MyAuthAdapter::class => MyAuthAdapterFactory::class,
        ],
    ];
}

Authenticate using a login page

With an authentication mechanism in place, we now need to create middleware to
render the login form. This middleware will do the following:

  • for GET requests, it will render the login form.
  • for POST requests, it will check for credentials and then attempt to
    validate them.

    • for valid authentication requests, we will redirect to a welcome page
    • for invalid requests, we will provide an error message and redisplay the
      form.

Let’s create the middleware now:

// In src/Auth/src/Action/LoginAction.php:

namespace AuthAction;

use AuthMyAuthAdapter;
use InteropHttpServerMiddlewareDelegateInterface;
use InteropHttpServerMiddlewareMiddlewareInterface as ServerMiddlewareInterface;
use PsrHttpMessageServerRequestInterface;
use ZendAuthenticationAuthenticationService;
use ZendDiactorosResponseHtmlResponse;
use ZendDiactorosResponseRedirectResponse;
use ZendExpressiveTemplateTemplateRendererInterface;

class LoginAction implements ServerMiddlewareInterface
{
    private $auth;
    private $authAdapter;
    private $template;

    public function __construct(
        TemplateRendererInterface $template,
        AuthenticationService $auth,
        MyAuthAdapter $authAdapter
    ) {
        $this->template    = $template;
        $this->auth        = $auth;
        $this->authAdapter = $authAdapter;
    }

    public function process(ServerRequestInterface $request, DelegateInterface $delegate)
    {
        if ($request->getMethod() === 'POST') {
            return $this->authenticate($request);
        }

        return new HtmlResponse($this->template->render('auth::login'));
    }

    public function authenticate(ServerRequestInterface $request)
    {
        $params = $request->getParsedBody();

        if (empty($params['username'])) {
            return new HtmlResponse($this->template->render('auth::login', [
                'error' => 'The username cannot be empty',
            ]));
        }

        if (empty($params['password'])) {
            return new HtmlResponse($this->template->render('auth::login', [
                'username' => $params['username'],
                'error'    => 'The password cannot be empty',
            ]));
        }

        $this->authAdapter->setUsername($params['username']);
        $this->authAdapter->setPassword($params['password']);

        $result = $this->auth->authenticate();
        if (!$result->isValid()) {
            return new HtmlResponse($this->template->render('auth::login', [
                'username' => $params['username'],
                'error'    => 'The credentials provided are not valid',
            ]));
        }

        return new RedirectResponse('/admin');
    }
}

This middleware manages two actions: rendering the login form, and
authenticating the user’s credentials when submitted via a POST request.

You will also need to ensure that you have:

  • Created a login template.
  • Added configuration to map the auth template namespace to one or more
    filesystem paths.

We leave those tasks as an exercise to the reader.

We now need to create a factory to provide the dependencies for this
middleware:

// In src/Auth/src/Action/LoginActionFactory.php:

namespace AuthAction;

use AuthMyAuthAdapter;
use InteropContainerContainerInterface;
use ZendAuthenticationAuthenticationService;
use ZendExpressiveTemplateTemplateRendererInterface;

class LoginActionFactory
{
    public function __invoke(ContainerInterface $container)
    {
        return new LoginAction(
            $container->get(TemplateRendererInterface::class),
            $container->get(AuthenticationService::class),
            $container->get(MyAuthAdapter::class)
        );
    }
}

Map the middleware to this factory in your dependencies configuration witin the
ConfigProvider:

// In src/Auth/src/ConfigProvider.php,

// Update the following method to read as follows:
public function getDependencies()
{
    return [
        'factories' => [
            ActionLoginAction::class => ActionLoginActionFactory::class,
            AuthenticationService::class => AuthenticationServiceFactory::class,
            MyAuthAdapter::class => MyAuthAdapterFactory::class,
        ],
    ];
}

Use zend-servicemanager’s ReflectionBasedAbstractFactory

If you are using zend-servicemanager in your application, you could skip the
step of creating the factory, and instead map the middleware to
ZendServiceManagerAbstractFactoryReflectionBasedAbstractFactory.

Finally, we can create appropriate routes. We’ll map /login to the
LoginAction now, and allow it to react to either the GET or POST methods:

// in config/routes.php:
$app->route('/login', AuthActionLoginAction::class, ['GET', 'POST'], 'login');

Alternately, the above could be written as two separate statements:

// in config/routes.php:
$app->get('/login', AuthActionLoginAction::class, 'login');
$app->post('/login', AuthActionLoginAction::class);

Authentication middleware

Now that we have the authentication service and its adapter and the login
middleware in place, we can create middleware that checks for authenticated
users, having it redirect to the /login page if the user is not authenticated.

// In src/Auth/src/Action/AuthAction.php:

namespace AuthAction;

use InteropHttpServerMiddlewareDelegateInterface;
use InteropHttpServerMiddlewareMiddlewareInterface as ServerMiddlewareInterface;
use PsrHttpMessageServerRequestInterface;
use ZendAuthenticationAuthenticationService;
use ZendDiactorosResponseRedirectResponse;

class AuthAction implements ServerMiddlewareInterface
{
    private $auth;

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

    public function process(ServerRequestInterface $request, DelegateInterface $delegate)
    {
        if (! $this->auth->hasIdentity()) {
            return new RedirectResponse('/login');
        }

        $identity = $this->auth->getIdentity();
        return $delegate->process($request->withAttribute(self::class, $identity));
    }
}

This middleware checks for a valid identity using the hasIdentity() method of
AuthenticationService. If no identity is present, we redirect the redirect
configuration value.

If the user is authenticated, we continue the execution of the next middleware,
storing the identity in a request attribute. This facilitates consumption of the
identity information in subsequent middleware layers. For instance, imagine you
need to retrieve the user’s information:

namespace AppAction;

use InteropHttpServerMiddlewareDelegateInterface;
use InteropHttpServerMiddlewareMiddlewareInterface as ServerMiddlewareInterface;
use PsrHttpMessageServerRequestInterface;

class FooAction
{
    public function process(ServerRequestInterface $request, DelegateInterface $delegate)
    {
        $user = $request->getAttribute(AuthAction::class);
        // $user will contains the user's identity
    }
}

The AuthAction middleware needs some dependencies, so we will need to create
and register a factory for it as well.

First, the factory:

// In src/Auth/src/Action/AuthActionFactory.php:

namespace AuthAction;

use InteropContainerContainerInterface;
use ZendAuthenticationAuthenticationService;
use Exception;

class AuthActionFactory
{
    public function __invoke(ContainerInterface $container)
    {
        return new AuthAction($container->get(AuthenticationService::class));
    }
}

And then mapping it:

// In src/Auth/src/ConfigProvider.php:


// Update the following method to read as follows:
public function getDependencies()
{
    return [
        'factories' => [
            ActionAuthAction::class => ActionAuthActionFactory::class,
            ActionLoginAction::class => ActionLoginActionFactory::class,
            AuthenticationService::class => AuthenticationServiceFactory::class,
            MyAuthAdapter::class => MyAuthAdapterFactory::class,
        ],
    ];
}

Like the LoginActionFactory above, you could skip the factory creation and
instead use the ReflectionBasedAbstractFactory if using zend-servicemanager.

Require authentication for specific routes

Now that we built the authentication middleware, we can use it to protect
specific routes that require authentication. For instance, for each route that
needs authentication, we can modify the routing to create a pipeline that
incorporates our AuthAction middleware early:

$app->get('/admin', [
    AuthActionAuthAction::class,
    AppActionDashBoardAction::class
], 'admin');

$app->get('/admin/config', [
    AuthActionAuthAction::class,
    AppActionConfigAction::class
], 'admin.config');

The order of execution for the middleware is the order of the array elements.
Since the AuthAction middleware is provided as the first element, if a user is
not authenticated when requesting either the admin dashboard or config page,
they will be immediately redirected to the login page instead.

Conclusion

There are many ways to accommodate authentication within middleware
applications; this is just one. Our goal was to demonstrate the ease with which
you may compose authentication into existing workflows by creating middleware
that intercepts the request early within a pipeline.

You could certainly make a number of improvements to the workflow:

  • The path to the login page could be configurable.
  • You could capture the original request path in order to allow redirecting to
    it following successful login.
  • You could introduce rate limiting of login requests.

These are each interesting exercises for you to try!

As noted in the introduction, this article demonstrates only authentication.
Stay tuned for a future article that will demonstrate authorization middleware
using zend-permissions-rbac.

Save the date!

Want to learn more about Expressive and Zend Framework? What better location
than ZendCon 2017! ZendCon will be hosted 23-26 October 2017 in Las Vegas,
Nevada, USA. Visit the ZendCon website for more
information
.

Source: Zend feed

Manage your application with zend-config-aggregator

With the rise of PHP middleware, many developers are creating custom
application architectures, and running into an issue many frameworks already
solve: how to allow runtime configuration of the application.

configuration is often necessary, even in custom applications:

  • Some configuration, such as API keys, may vary between environments.
  • You may want to substitute services between development and production.
  • Some code may be developed by other teams, and pulled into your application
    separately (perhaps via Composer), and require
    configuration.
  • You may be writing code in your application that you will later want to share
    with another team, and recognize it should provide service wiring information
    or allow for dynamic configuration itself.

Faced with this reality, you then have a new problem: how can you configure your
application, as well as aggregate configuration from other sources?

As part of the Expressive initiative, we now offer a standalone solution for
you: zend-config-aggregator

Installation

First, you will need to install zend-config-aggregator:

$ composer require zendframework/zend-config-aggregator

One feature of zend-config-aggregator is the ability to consume multiple
configuration formats via zend-config.
If you wish to use that feature, you will also need to install that package:

$ composer require zendframework/zend-config

Finally, if you are using the above, and want to parse YAML files, you will need
to install the YAML PECL extension.

Configuration providers

zend-config-aggregator allows you to aggregate configuration from configuration
providers. A configuration provider is any PHP callable that will return an
associative array of configuration.

By default, the component provides the following providers out of the box:

  • ZendConfigAggregatorArrayProvider, which accepts an array of configuration
    and simply returns it. This is primarily useful for providing global defaults
    for your application.
  • ZendConfigAggregatorPhpFileProvider, which accepts a glob pattern
    describing PHP files that each return an associative array. When invoked, it
    will loop through each file, and merge the results with what it has previously
    stored.
  • ZendConfigAggregatorZendConfigProvider, which acts similarly to the
    PhpFileProvider, but which can aggregate any format
    zend-config supports, including
    INI, XML, JSON, and YAML.

More interestingly, however, is the fact that you can write providers as simple
invokable objects:

namespace Acme;

class ConfigProvider
{
    public function __invoke()
    {
        return [
            // associative array of configuration
        ];
    }
}

This feature allows you to write configuration for specific application
features, and then seed your application with it. In other words, this feature
can be used as the foundation for a modular
architecture
,
which is exactly what we did with Expressive!

Generators

You may also use invokable classes or PHP callables that define generators as
configuration providers! As an example, the PhpFileProvider could
potentially be rewritten as follows:

use ZendStdlibGlob;

function () {
    foreach (Glob::glob('config/*.php', Glob::GLOB_BRACE) as $file) {
        yield include $file;
    }
}

Aggregating configuration

Now that you have configuration providers, you can aggregate them.

For the purposes of this example, we’ll assume the following:

  • We will have a single configuration file, config.php, at the root of our
    application which will aggregate all other configuration.
  • We have a number of configuration files under config/, including YAML, JSON,
    and PHP files.
  • We have a third-party "module" that exposes the class
    UmbrellaConfigProvider.
  • We have developed our own "module" for re-distribution that exposes the class
    BlanketConfigProvider.

Typically, you will want aggregate configuration such that third-party
configuration is loaded first, with application-specific configuration merged
last, in order to override settings.

Let’s aggregate and return our configuration.

// in config.php:
use ZendConfigAggregatorConfigAggregator;
use ZendConfigAggregatorZendConfigProvider;

$aggregator = new ConfigAggregator([
    UmbrellaConfigProvider::class,
    BlanketConfigProvider::class,
    new ZendConfigProvider('config/*.{json,yaml,php}'),
]);

return $aggregator->getMergedConfig();

This file aggregates the third-party configuration provider, the one we expose
in our own application, and then aggregates a variety of different configuration
files in order to, in the end, return an associative array representing the
merged configuration!

Valid config profider entries

You’ll note that the ConfigAggregator expects an array of providers as the
first argument to the constructor. This array may consist of any of the
following:

  • Any PHP callable (functions, invokable objects, closures, etc.) returning an
    array.
  • A class name of a class that defines __invoke(), and which requires no
    constructor arguments.

This latter is useful, as it helps reduce operational overhead once you
introduce caching, which we discuss below. The above example demonstrates this
usage.

zend-config and PHP configuration

The above example uses only the ZendConfigProvider, and not the
PhpFileProvider. This is due to the fact that zend-config can also consume
PHP configuration.

If you are only using PHP-based configuration files, you can use the
PhpFileProvider instead, as it does not require additionally installing the
zendframework/zend-config package.

Globbing and precedence

Globbing works as it does on most *nix systems. As such, you need to pay
particular attention to when you use patterns that define alternatives, such
as the {json,yaml,php} pattern above. In such cases, all JSON files will be
aggregated, followed by YAML files, and finally PHP files. If you need them
to aggregate in a different order, you will need to change the pattern.

Caching

You likely do not want to aggregate configuration on each and every application
request, particularly if doing so would result in many filesystem hits.
Fortunately, zend-config-aggregator also has built-in caching features.

To enable these features, you will need to do two things:

  • First, you need to provide a second argument to the ConfigAggregator
    constructor, specifying the path to the cache file to create and/or use.
  • Second, you need to enable caching in your configuration, by specifying a
    boolean true value for the key ConfigAggregator::ENABLE_CACHE.

One common strategy is to enable caching by default, and then disable it via
environment-specific configuration.

We’ll update the above example now to enable caching to the file
cache/config.php:

use ZendConfigAggregatorArrayProvider;
use ZendConfigAggregatorConfigAggregator;
use ZendConfigAggregatorPhpFileProvider;
use ZendConfigAggregatorZendConfigProvider;

$aggregator = new ConfigAggregator(
    [
        new ArrayProvider([ConfigAggregator::ENABLE_CACHE => true]),
        UmbrellaConfigProvider::class,
        BlanketConfigProvider::class,
        new ZendConfigProvider('config/{,*.}global.{json,yaml,php}'),
        new PhpFileProvider('config/{,*.}local.php'),
    ],
    'cache/config.php'
);

return $aggregator->getMergedConfig();

The above adds an initial setting that enables the cache, and tells it to cache
it to cache/config.php.

Notice also that this example changes the ZendConfigProvider, and adds a
PhpFileProvider entry. Let’s examine these.

The ZendConfigProvider glob pattern now looks for files named global with
one of the accepted extensions, or those named *.global with one of the
accepted extensions. This allows us to segregate configuration that should
always be present from environment-specific configuration.

We then add a PhpFileProvider that aggregates local.php and/or *.local.php
files specifically. An interesting side-note about the shipped providers is that
if no matching files are found, the provider will return an empty array; this
means that we can have this additional provider that is looking for separate
configurations for the "local" environment! Because this provider is aggregated
last, the settings it exposes will override any others.

As such, if we want to disable caching, we can create a file such as
config/local.php with the following contents:

<?php
use ZendConfigAggregatorConfigAggregator;

return [ConfigAggregator::ENABLE_CACHE => false];

and the application will no longer cache aggregated configuration!

Clear the cache!

The setting outlined above is used to determine whether the configuration
cache file should be created if it does not already exist.
zend-config-aggregator, when provided the location of a configuration cache
file, will load directly from it if the file is present.

As such, if you make the above configuration change, you will first need to
remove any cached configuration:

$ rm cache/config.php

This can even be made into a Composer script:

"scripts": {
    "clear-config-cache": "rm cache/config.php"
}

Allowing you to do this:

$ composer clear-config-cache

Which allows you to change the location of the cache file without needing to
re-learn the location every time you need to clear the cache.

Auto-enabling third-party providers

Being able to aggregate providers from third-parties is pretty stellar; it means
that you can be assured that configuration the third-party code expects is
generally present — with the exception of values that must be provided by the
consumer, that is!

However, there’s one minor problem: you need to remember to register these
configuration providers with your application, by manually editing your
config.php file and adding the appropriate entries.

Zend Framework solves this via the zf-component-installer Composer
plugin
. If your
package is installable via Composer, you can add an entry to your package
definition as follows:

"extra": {
    "zf": {
        "config-provider": [
            "UmbrellaConfigProvider"
        ]
    }
}

If the end-user:

  • Has required zendframework/zend-component-installer in their application (as
    either a production or development dependency), AND
  • has the config aggregation script in config/config.php

then the plugin will prompt you, asking if you would like to add each of the
config-provider entries found in the installed package into the configuration
script.

As such, for our example to work, we would need to move our configuration script
to config/config.php, and likely move our other configuration files into a
sub-directory:

cache/
    config.php
config/
    config.php
    autoload/
        blanket.global.yaml
        global.php
        umbrella.global.json

This approach is essentially that taken by Expressive.

When those changes are made, any package you add to your application that
exposes configuration providers will prompt you to add them to your
configuration aggregation, and, if you confirm, will add them to the top of the
script!

Final notes

First, we would like to thank Mateusz Tymek, whose
prototype ‘expressive-config-manager’ project became zend-config-aggregator.
This is a stellar example of a community project getting adopted into the
framework!

Second, this approach has some affinity to a proposal from the folks who brought
us PSR-11, which defines the ContainerInterface used within Expressive for
allowing usage of different dependency injection containers. That same group is
now working on a service provider
proposal that would standardize how standalone libraries expose services to
containers; we recommend looking at that project as well.

We hope that this post helps spawn ideas for configuring your next project!

Save the date!

Want to learn more about Expressive and Zend Framework? What better location
than ZendCon 2017! ZendCon will be hosted 23-26 October 2017 in Las Vegas,
Nevada, USA. Visit the ZendCon website for more
information
.

Source: Zend feed

1 2 3 4