Blog Entries - ZF Blog Blog Entries - ZF Blog Wed, 16 May 2018 20:15:00 +0000 Zend_Feed_Writer 2 (http://www.4645285.com) http://www.4645285.com/blog Zend Framework/Homestead Integration Wed, 16 May 2018 20:15:00 +0000 http://www.4645285.com/blog/2018-05-16-homestead.html http://www.4645285.com/blog/2018-05-16-homestead.html Matthew Weier O'Phinney Matthew Weier O'Phinney Last year, we wrote about using Laravel Homestead with ZF projects. Today, we contributed some changes to Homestead to simplify setting it up to serve Apigility, Expressive, and Zend Framework projects.

As of version 7.6.0, Homestead users can now define sites with any of the following "type" values:

  • apigility
  • expressive
  • zf

When one of these values is used, Homestead will setup the nginx instance used by Homestead to properly to work with the project.

Getting started

Much of what we detailed last year is still true:

  • You will need to add the laravel/homestead box to Vagrant: vagrant box add laravel/homestead.

  • You will likely want to use the vagrant-hostsupdater plugin to Vagrant to facilitate mapping the VM IP address and server name to your system hosts file: vagrant plugin install vagrant-hostsupdater.

  • You will need to temporarily add the laravel/homestead package as a development dependency to your application: composer require --dev laravel/homestead.

  • You will need to use the Homestead tooling to create a Vagrantfile and Homestead.yaml configuration file: ./vendor/bin/homestead make.

Configuring Homestead

Once you have your Homestead.yaml file created, you can edit it. The two things we need specifically are:

  • A folder mapping the application root directory to a directory in the vagrant image.
  • A site definition.

Generally, the folder mapping is already present, and will look something like the following:

folders:
  - map: /home/username/dev/application
    to: /home/vagrant/code

If you want the Homestead.yaml to be portable, however, you can tell it to map the current directory, and not a fully qualified path:

folders:
  - map: .
    to: /home/vagrant/code

Next, we'll look at the site definition. After you first run homestead make, you should have the following:

sites:
  - map: homestead.test
    to: /home/vagrant/code/public

Let's change this a bit. First, we'll give a new site name, then a site type (I'll use "expressive" here, but you can change this to "apigility" or "zf" based on your application), and we'll enable Z-Ray.

sites:
  - map: expressive.test
    to: /home/vagrant/code/public
    type: expressive
    zray: "true"

Yes, the correct value for the zray setting is "true"; see this issue for details.

From here, we can finally get running:

$ vagrant up

If you are not using the vagrant-hostsupdater plugin, you'll need to add an entry to your system hosts file:

192.168.10.10 expressive.test

Fin

We're hoping having this support in place will allow Zend Framework zend-mvc, Apigility, and Expressive developers to create and share development environments easily between their teams. If you have additional features you would like enabled by default (e.g., auto-detection of ZF, Apigility, and Expressive applications by homestead make, additional default nginx configuration, etc.), be sure to swing by the Slack or forums and ask!

I want to extend a hearty thank you to Joe Ferguson for helping me provide the integration, and guiding me through the contribution process for Homestead.

]]>
0
PHP 7.2 Support! Tue, 08 May 2018 21:55:00 +0000 http://www.4645285.com/blog/2018-05-08-php-7.2-support.html http://www.4645285.com/blog/2018-05-08-php-7.2-support.html Matthew Weier O'Phinney Matthew Weier O'Phinney With Expressive 3 complete, we were able to turn our sights on another important initiative: PHP 7.2 support across all components and Apigilty modules.

The short story is: as of today, that initiative is complete! If you are using the Zend Framework MVC framework, Expressive, or Apigility, or any of the ZF components standalone, you should be able to perform a composer update to get versions that support PHP 7.2.

The full story is much longer.

How we got there

The PHP project does a pretty stellar job of preserving backwards compatibility. Some might say they do it to a fault, being averse to any changes that might cause breakage for users, even if the change fixes bad behavior on the part of the language.

Interestingly, there have been a ton of initiatives to tighten up the language and have it behave more predictably. Unfortunately, we, and a number of projects on which we depend, were bit by some of these efforts that went into PHP 7.1 and 7.2.

One in particular was problematic.

Let's say you have a class such as the following:

class SomeContainer
{
    public function get($name, array $options = null)
    {
    }
}

Next, we'll have an extension to that class that overrides that method and changes the default value:

class AContainerExtension extends SomeContainer
{
    public function get($name, array $options = [])
    {
    }
}

These should be fine, right? Wrong.

Starting in 7.1.0, the above emits an E_WARNING due to incompatible signatures. This is because PHP 7.1 adds nullable types, and considers the first signature equivalent to a nullable array.

The problem is that PHPUnit, on seeing an E_WARNING, creates an error status for the test in which it is raised.

There were a number of other minor changes such as deprecated APIs that also affected our code, often leading to unexpected test errors. Technically, the code likely could run, but not without emitting deprecation notices and/or warnings — and our goal is to run cleanly, so that users can see only the warnings pertinent to their own application code.

On top of this, a number of PHPUnit classes exhibited similar behaviors, which meant that under PHP 7.2, with the versions of PHP we were using, we could not verify that our code could work under that version.

The upshot for us is that testing against 7.2 wasn't as easy as just adding another PHP version to the test matrix. We also had to find a set of different PHP versions that we could test against (for instance, PHPUnit 6 and 7 require PHP 7 versions, but we also still support PHP 5.6 in many of our components and modules), figure out how to get Travis-CI to install a PHPUnit version appropriate to the PHP version we were testing in, and ensure that the PHPUnit features we were using worked across all PHPUnit versions against which we might test.

Thankfully, we had a secret weapon: Micha? Bundyra (@MichalBundyra on twitter). Micha? spent a fair bit of time this past year developing increasingly effective approaches to this sort of problem, and, once the 7.2 initiative was announced, jumped in and created patches for almost every single component and module we ship.

Seriously, this was work above and beyond anything I can reasonably expect of a volunteer. Go thank him, already!

Our approach

The approach Micha? developed involves two things. First, a range of known-good PHPUnit dependencies, and, second, a set of configuration for Travis-CI that will allow installing the appropriate dependencies.

First, we use the following constraints for PHPUnit in our composer.json files when both PHP 5.6 and PHP 7 versions are required:

"phpunit/phpunit": "^5.7.21 || ^6.3 || ^7.1",

These allow us to use the 5.7 series for PHP 5.6, and either the 6.3 or 7.1 series when under other PHP versions.

We also commit our composer.lock file. I'll show why in a moment.

Next, we use configuration similar to the following with Travis-CI:

sudo: false

language: php

cache:
  directories:
    - $HOME/.composer/cache

env:
  global:
    - COMPOSER_ARGS="--no-interaction"

matrix:
  include:
    - php: 5.6
      env:
        - DEPS=lowest
    - php: 5.6
      env:
        - DEPS=locked
        - LEGACY_DEPS="phpunit/phpunit zendframework/zend-code"
    - php: 5.6
      env:
        - DEPS=latest
    - php: 7
      env:
        - DEPS=lowest
    - php: 7
      env:
        - DEPS=locked
        - CS_CHECK=true
        - LEGACY_DEPS="phpunit/phpunit-mock-objects phpspec/prophecy zendframework/zend-code"
    - php: 7
      env:
        - DEPS=latest
    - php: 7.1
      env:
        - DEPS=lowest
    - php: 7.1
      env:
        - DEPS=locked
        - TEST_COVERAGE=true
    - php: 7.1
      env:
        - DEPS=latest
    - php: 7.2
      env:
        - DEPS=lowest
    - php: 7.2
      env:
        - DEPS=locked
    - php: 7.2
      env:
        - DEPS=latest

before_install:
  - if [[ $TEST_COVERAGE != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi

install:
  - travis_retry composer install $COMPOSER_ARGS --ignore-platform-reqs
  - if [[ $LEGACY_DEPS != '' ]]; then travis_retry composer update $COMPOSER_ARGS --with-dependencies $LEGACY_DEPS ; fi
  - if [[ $DEPS == 'latest' ]]; then travis_retry composer update $COMPOSER_ARGS ; fi
  - if [[ $DEPS == 'lowest' ]]; then travis_retry composer update --prefer-lowest --prefer-stable $COMPOSER_ARGS ; fi
  - stty cols 120 && composer show

script:
  - vendor/bin/phpunit
  - if [[ $CS_CHECK == 'true' ]]; then vendor/bin/phpcs ; fi

Let's break that down.

We set up a few things up front for all builds: we're using dockerized php jobs (sudo: false, language: php), we're caching composer metadata between builds (which greatly speeds up the installation process!), and defining our default composer arguments.

From there, we define our test matrix. Each job in the matrix includes:

  • The PHP version we are testing against.
  • Environment variables for that specific build.

You'll notice we have three jobs for each PHP version, corresponding to the following environment variables:

  • DEPS=lowest
  • DEPS=locked
  • DEPS=latest

These variables are indicating how we want to install dependencies:

  • locked indicates we want to use those specified in the composer.lock file.
  • lowest means we want to test against the lowest stable dependencies we allow.
  • latest indicates we want to test against the latest available dependences we allow.

This approach allows us to determine:

  • When we start using features from a library that are not present in the earliest version we have indicated we support. If the lowest tests fail, we likely either need to change what part of a third-party API we are consuming, or bump the minimum supported version of that dependency.

  • When a library has introduced a BC break in a more recent release than we tested against previously. In such cases, we can try and find a way to make our own usage of that library forwards-compatible with the new version; create an issue notifying the developer(s) of that library of the BC break; or change our dependencies to not allow the newer version.

Additionally, some jobs have more variables they define:

  • CS_CHECK will tell the job whether or not to run CS checks. (We also often define an env variable for running coverage reports.)

  • LEGACY_DEPS allows us to specify dependencies we need to update after initial installation. More on that in the coming paragraphs.

We also disable xdebug unless we're running coverage reports. This speeds up Composer operations as well as running unit tests. I have the before_install script detailed above, but do not define any environments with the TEST_COVERAGE variable set, nor demonstrate how we use it to run reports.

When we hit the install section is when the "magic" happens. The first thing we do is an install from the lockfile. When we do so, we pass the --ignore-platform-reqs option, as we cannot guarantee that the dependencies in the lockfile will work for the current PHP version being used.

We then check to see if LEGACY_DEPS is non-empty. If so, we do a composer update --with-dependencies, passing the value of LEGACY_DEPS as the packages to update. This allows us to use the lockfile on locked versions, but then get platform-specific dependencies for libraries where we know that what's in the lockfile may not work on all platforms.

Next, we check for DEPS=latest, running composer update, and DEPS=lowest, running composer update --prefer-lowest --prefer-stable.

Finally, we display what dependencies were installed, along with their versions. We use the construct stty cols 120 to set the display columns, as otherwise composer will not detect a TTY, and spit out only the dependencies, with no associated version.

The beauty of this approach is that we are able to use it almost verbatim across our repositories, with only minor changes to which LEGACY_DEPS we need, and which versions need them. Having multiple tests per version, spanning a range of dependencies, has allowed us to identify and solve problems arising from libraries we consume quickly.

This approach allowed us to run tests under PHP 7.2, fix any issues identified, and finally release new versions that fully support PHP 7.2.

What's next?

We have a number of initiatives we're working on in the coming months:

  • Frank Brückner is working on a site refresh to both make the documentation and main sites more consistent in look-and-feel, as well as better support mobile browsers.

  • We continue to work on the Apigility on Expressive initiative. While many features were released with stable versions for Expressive 3, there's still work to be done, including tooling support.

  • Aleksei Khudiakov has been working on a set of proposals for a PSR-7-based zend-mvc v4.

  • We want to work on tutorials and guides to help users make the most of Expressive, as well as migrate to Expressive from zend-mvc.

If you want to help out with any of these initiatives:

]]>
0
Async Expressive? Try Swoole! Wed, 21 Mar 2018 20:55:00 +0000 http://www.4645285.com/blog/2018-03-21-expressive-swoole.html http://www.4645285.com/blog/2018-03-21-expressive-swoole.html Matthew Weier O'Phinney Matthew Weier O'Phinney When we were finalizing features for Expressive 3, we had a number of users testing using asynchronous PHP web servers. As a result, we made a number of changes in the last few iterations to ensure that Expressive will work well under these paradigms.

Specifically, we made changes to how response prototypes are injected into services.

Response prototypes?

What's the problem?

In an async system, one advantage is that you can bootstrap the application once, and then respond to requests until the server is shutdown.

However, this can become problematic with services that compose a response prototype in order to produce a response (e.g., authentication middleware that may need to produce an "unauthenticated" response; middleware that will produce a "not found" response; middleware that will produce a "method not allowed" response; etc.). We have standardized on providing response prototypes via dependency injection, using a service named after the interface they implement: Psr\Http\Message\ResponseInterface.

If a particular service accepts a response instance that's injected during initial service creation, that same instance will be used for any subsequent requests that require it. And that's where the issue comes in.

When running PHP under traditional conditions — php-fpm, the Apache SAPI, etc. — all requests are isolated; the environment is both created and torn down for each and every request. As such, passing an instance is perfectly safe; there's very little chance, if any, that any other service will be working with the same instance.

With an async server, however, the same instance will be used on each and every request. Generally, manipulations of PSR-7 message instances will create new instances, as the interfaces they implement are specified as immutable. Unfortunately, due to technical limitations of the PHP language, we were unable to make the body of response messages immutable. This means that if one process writes to that body, then a subsequent process — or even those executing in parallel! — will receive the same changes. This can lead to, in the best case scenario, duplicated content, and, in the worst, provide incorrect content or perform information leaking!

To combat these situations, we modified the Psr\Http\Message\ResponseInterface service we register with the dependency injection container: it now returns not an instance of the interface, but a factory capable of producing an instance. Services should compose this factory, and then call on it each time they need to produce a response. This fixes the async problem, as it ensures a new instance is used each time, instead of the same instance.

(Additionally, this change helps us prepare for the upcoming PSR-17, which describes factories for PSR-7 artifacts; this solution will be compatible with that specification once complete.)

Why async?

If asynchronous systems operate so differently, why bother?

There's many reasons, but the one that generally gets the attention of developers is performance.

We performed benchmarks of Expressive 2 and Expressive 3 under both Apache and nginx, and found version 3 received around a 10% improvement.

We then tested using Swoole. Swoole is a PHP extension that provides built-in async, multi-threaded input/output (I/O) modules; it's essentially the I/O aspects of node.js — which allow you to create network servers and perform database and filesystem operations — but for PHP.

A contributor, Westin Shafer, has written a module for Expressive 3 that provides an application wrapper for Swoole that is exposed via a CLI command. We ran our same benchmarks against this, and the results were astonishing: applications ran consistently 4 times faster under this asynchronous framework, and used fewer resources!

While performance is a great reason to explore async, there are other reasons as well. For instance, if you do not need the return value of an I/O call (e.g., a database transaction or cache operation), you can fire it off asynchronously, and finish out the response without waiting for it. This can lead to reduced waiting times for clients, further improving your performance.

We have had fun testing Swoole, and think it has tremendous possibilities when it comes to creating microservices in PHP. The combination of Expressive and Swoole is remarkably simple to setup and run, making it a killer combination!

Notes on setting up Swoole

The wshafer/swoole-expressive package requires a version 2 release of the Swoole extension.

However, there's a slight bug in the PECL installer whereby it picks up the most recent release as the "latest", even if a version with greater stability exists. As of the time of writing, version 1.10.2 of Swoole was released after version 2.1.1, causing it to be installed instead of the more 2.X version.

You can force installation of a version by appending the version you want when invoking the pecl command:

$ pecl install swoole-2.1.1

The version must be fully qualified for it to install correctly; no partials (such as swoole-2 or swoole-2.1 will work.

]]>
0
Expressive 3! Fri, 16 Mar 2018 15:15:00 +0000 http://www.4645285.com/blog/2018-03-16-expressive-3.html http://www.4645285.com/blog/2018-03-16-expressive-3.html Matthew Weier O'Phinney Matthew Weier O'Phinney Yesterday, we tagged and released Expressive 3!

Expressive 3 provides a middleware microframework.

Create a new Expressive application using Composer:

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

The installer will prompt you for your choice of:

  • Initial application architecture (minimal, flat, modular)
  • Which dependency injection container you would like to use.
  • Which routing library you would like to use.
  • Which templating library you would like to use, if any.
  • Which error handling library you would like to use, if any.

From there, it creates a new project for you, and allows you to get started developing immediately.

You can read more in our quick start, and may want to check out our command line tooling to see what we provide to make development even faster for you!

What are the features?

Expressive 3 embraces modern PHP, and requires PHP 7.1 or higher. Strong type-hinting, including return type hints, make both our job and your job easier and more predictable. The ability to use all modern PHP features helps us deliver a solid base for your application.

Expressive 3 provides full support for the PSR-15 (Middleware and Request Handlers) standard. We believe strongly in supporting standards, to the extent that this release also drops direct support for the "double-pass" middleware style we have supported since version 1.0

Expressive 3 massively refactors its internals as well. In fact, the majority of the code in the zend-expressive package was removed, moved to other existing packages where it had a better semantic affiliation1, or extracted to new packages2. This base package now mainly handles coordinating collaborators and providing a user-friendly interface to creating your application pipeline and routes.3

Expressive 3 provides more command line tooling and tooling improvements in order to make developing your application easier. We added a command for creating factories for existing classes (factory:create).4 The middleware:create command now creates a factory for the middleware generated. We added support for creating request handlers5, complete with factory generation and registration, as well as template support.6

Finally, we recognize that Expressive has changed massively between versions 1 and 3, while simultaneously keeping its primary API stable and unchanged. However, to help users find the information they need for the version they run, we have rolled out versioned documentation, with each version providing only information specific to its release cycle:

The most recent version will always be present in the primary navigation, with links to other versions present as well.

New components!

We have several new components that provide features for Expressive — or any PSR-15 framework you may be using! These include:

We have a number of other packages in the works around authentication, authorization, and data validation that we will be releasing in the coming weeks and months; stay tuned for announcements!

What about upgrading?

We have prepared a migration document that covers new features, removed features, and a list of all changes.

Additionally, we have provided migration tooling to aid you in your migration from version 2 to version 3. The tool will not necessarily give you a fully running application, but it will take care of the majority of the changes necessary to bump your application to version 3, including setting up appropriate dependencies, and updating your bootstrapping files to conform to the new skeleton application structure.

If you need assistance, you can find community help:

What's next?

We have been working on a number of API-related modules for Expressive (and any PSR-15 applications) since last summer, with a number of components already completed, and others close to completion. We plan to finalize these in the next few months.

Thank You!

We extend a hearty thank you to everyone who tested the various pre-releases and provided feedback. Additionally, we are singling out the following individuals who provided significant contributions to the Expressive 3 project:

  • Enrico Zimuel provided a ton of feedback and critique during the design phase, and was a driving force behind many of the API usability decisions.

  • Rob Allen did a workshop at SunshinePHP, right as we dropped our initial alpha releases, and provided feedback and testing for much of our tooling additions.

  • Frank Brückner provided ongoing feedback and review of pull requests, primarily around documentation; he is also responsible for a forthcoming rewrite of our documentation theme to make it more responsive and mobile-friendly.

  • Daniel Gimenes provided feedback and ideas as we refactored zend-stratigility; he is the one behind package-level utility functions such as Zend\Stratigility\doublePassMiddleware(), Zend\Stratigility\path(), and more.

  • Witold Wasiczko provided the majority of the rewrite of zend-stratigility for version 3. He can be celebrated for removing over half the code from that repository!

In addition to these people, I want to extend a personal thank you to the following people:

  • Geert Eltink has helped maintain Expressive v2, and particularly the various routers and template engines, making them ready for v3 and testing continually. As a maintainer, I was able to rely on him to take care of merges as we finalized the releases, and was pleasantly surprised to wake up to new releases several times when he fixed critical issues in our alpha and RC releases.

  • Micha? Bundyra provided a constant stream of pull requests related to quality assurance (including ongoing work on our phpcs extension!), as well as critical review of incoming patches. He spearheaded important work in the refactoring process, including changes to how we handle response prototypes, and critical fixes in our routers to address issues with how we detect allowed methods for path route matches. We synced each and every single day, often arguing, but always coming to consensus and plowing on.

If you get a chance, reach out to these contributors and thank them for the release!

Footnotes

  • 0: The Expressive ecosystem makes use of many other standards as well, including PSR-7 HTTP Messages, PSR-11 Container, and PSR-13 HTTP Links.

  • 1: As an example, the routing, dispatch, and "implicit methods" middleware were all moved to the zend-expressive-router package, as they each work with the router and route results.

  • 2: Request generation, application dispatch, and response emission were all moved to a new package, zend-httphandlerrunner.

  • 3: These refactors led to a net removal of code across the board, vastly simplifying the internals. This will lead to ease of maintenance, greater stability, and, based on benchmarks we've been performing, 10% better performance and less system resource usage.

  • 4: factory:create uses PHP's Reflection API in order to determine what dependencies are in place in order to generate a factory class; it also registers the class and factory with the container!

  • 5: In previous Expressive versions, we referred to "actions", which were any middleware that returned a response instead of delegating to another layer of the application. PSR-15 calls such classes request handlers. Our tooling provides an action:create command, however, for those who prefer the "action" verbiage.

  • 6: The command creates a template named after the handler created; it uses the root namespace of the class to determine where to put it in the filesystem. Additionally, it alters the generated request handler to render the template into a zend-diactoros HtmlResponse!

]]>
0
Expressive 3.0.0RC2 released Wed, 07 Mar 2018 17:15:00 +0000 http://www.4645285.com/blog/2018-03-07-expressive-3-rc2.html http://www.4645285.com/blog/2018-03-07-expressive-3-rc2.html Matthew Weier O'Phinney Matthew Weier O'Phinney This week, we've worked on backports from Expressive 3 to Expressive 2, and, in the process, identified a few issues with how the routing package handles implicit HEAD and OPTIONS requests. As a result, we've just released 3.0.0rc2:

  • https://github.com/zendframework/zend-expressive-skeleton/releases/3.0.0rc2

What are "implicit" HEAD and OPTIONS requests?

Implicit HEAD and OPTIONS requests are requests using those methods made to routes that do not explicitly define them; in other words, if no routes for a given path include the HEAD or OPTIONS methods.

We provide a way for router implementations to flag a routing failure as being due to requesting a method that is not explicitly allowed. We also provide middleware for providing responses to HEAD and OPTIONS requests under those conditions, as well as separate middleware for simply reporting that a method is not allowed.

Getting started with RC2

To start a new project based on 3.0.0rc2, use Composer to create a new project:

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

If you want to install to a custom directory name, use the following instead:

$ composer create-project zendframework/zend-expressive-skeleton {your directory} 3.0.0rc2

Once installed, you can follow the same instructions as for RC1.

Updating from RC1

Updating from RC1 requires a few manual steps.

Prior to upgrading, you will need to do the following:

$ composer require "zendframework/zend-diactoros:^1.7.1"

Then run:

$ composer update

Once done, you will need to make one change to your config/pipeline.php.

Locate the following line:

$app->pipe(MethodNotAllowedMiddleware::class);

Cut the line, and paste it following the line reading:

$app->pipe(ImplicitOptionsMiddleware::class);

This change is necessary due to how each of these middleware inspect the routing result and act on it. If MethodNotAllowedMiddleware operates before the Implicit*Middleware, it will detect a 405 condition. Moving it after those middleware allow them to intercept for HEAD and OPTIONS requests.

Roadmap

We still have a number of tasks to accomplish before the stable 3.0.0 release. In particular:

  • We need to provide full documentation for the v3 release.

  • We will be issuing a 2.2 release with:

    • Deprecations, based on the v3 changes.
    • Backports of select v3 changes in order to aid migration.
    • See the following for full details: https://discourse.zendframework.com/t/roadmap-expressive-2-2/504
  • We need to document migration from v2.2 to v3, and potentially provide automated tooling.

  • We anticipate users may still find bugs in the RC, and will be actively incorporating bugfixes before the stable release.

Our target date is still 15 March 2018, but we need your help! Help by testing the RC2 skeleton and providing your feedback. As we prepare the v2.2 release, help by testing tooling and applying our migration documentation to your applications, and let us know what works and what doesn't. If you find features that are not documented, let us know by filing issues or asking questions in our Slack.

We look forward to the stable release, and the positive impact PSR-15 will have on the PHP ecosystem!

]]>
0
Expressive 3.0.0RC1 is now ready! Tue, 27 Feb 2018 21:25:00 +0000 http://www.4645285.com/blog/2018-02-27-expressive-3-rc1.html http://www.4645285.com/blog/2018-02-27-expressive-3-rc1.html Matthew Weier O'Phinney Matthew Weier O'Phinney We've been working diligently the past three weeks to finalize API changes and new features for the Expressive 3.0 release, and are pleased to announce immediate availability of our first release candidate, 3.0.0rc1!

Why RC and not beta?

Why the jump from alpha to release candidate? Most of our planned features also contained API changes, whether these were namespace or class name changes, signature changes due to adopting type hints, or wholesale refactors. As a result, we held an extended alpha release cycle so that we could continue making API changes as we worked on new features. In the last three weeks, we've continued to release new alpha versions of components, which users were picking up as they updated; surprisingly, the majority of these continued to work due to efforts at backwards compatibility that we made along the way.

We feel at this point that we've identified and implemented all desired changes both in terms of API as well as features, and announced a feature freeze yesterday. This puts us in a status more analogous to release candidates than beta (where features could still be added).

In this post, we'll cover:

Getting started with RC1

To start a new project based on 3.0.0rc1, use Composer to create a new project:

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

If you want to install to a custom directory name, use the following instead:

$ composer create-project zendframework/zend-expressive-skeleton {your directory} 3.0.0rc1

The installer will prompt you for a number of things:

  • What style install (minimal, flat, or modular).
  • Which DI container to use.
  • Which router to use.
  • Which template engine to use.
  • Which error handler to use.

We recommend the defaults, except in the case of a template engine; in that case, choose the one you're most familiar with.

Once the installer has gathered its information, it will begin installing dependencies, enable development mode, and then let you know it's done!

You can create your first request handler to handle an incoming request using our tooling:

$ composer expressive handler:create "App\Handler\HelloWorldHandler"

Tooling

Run the following command to find out what other commands we expose:

$ composer expressive help

You can also get help on individual commands:

$ composer expressive help handler:create

This will create a new class and tell you where in the filesystem you'll find it; it will also create a factory and tell you about that. If you have enabled a template engine, it will also create a template file and tell you where it is.

To route to it, edit the file config/routes.php and add the following line within the callback it defines:

    $app->get('/hello/world', App\Handler\HelloWorldHandler::class);

Next, fire up the built-in web server:

$ composer serve

and navigate to http://localhost:8080/hello/world to see your handiwork!

You can also create middleware to add to your application pipeline, or within route-specific pipelines. From here, you have the basic building blocks and application structure to get started!

Migrating from alpha3 to rc1

While a ton has changed between the 3.0.0alpha3 release and today, most applications built on alpha3 should be able to continue working as they were before, due to a number of backwards compatibility efforts we put into place to aid migration both for alpha users, as well as v2 users.

However, we recommend making the following changes to your application as well, to ensure it follows the structure that will be used in the final stable release.

composer.json

Whenever you see a constraint with the format ^X.Y.ZalphaW || ^X.Y, remove the || ^X.Y part of the constraint. This ensures that a previous alpha version cannot be installed if you use the --prefer-lowest flag when running composer update. More stable versions will still be installed when they become available.

config/config.php

Ensure the following configuration providers are present:

    \Zend\HttpHandlerRunner\ConfigProvider::class,
    \Zend\Expressive\ConfigProvider::class,
    \Zend\Expressive\Router\ConfigProvider::class,

Most likely, you will have been prompted to install these already anyways, but double-check to be sure.

config/pipeline.php

alpha3 refered to the following classes via import statements; they should be updated per the following table:

alpha3 reference rc1 reference
Zend\Expressive\Middleware\DispatchMiddleware Zend\Expressive\Router\Middleware\DispatchMiddleware
Zend\Expressive\Middleware\ImplicitHeadMiddleware Zend\Expressive\Router\Middleware\ImplicitHeadMiddleware
Zend\Expressive\Middleware\ImplicitOptionsMiddleware Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware
Zend\Expressive\Middleware\NotFoundMiddleware Zend\Expressive\Handler\NotFoundHandler
Zend\Expressive\Middleware\RouteMiddleware Zend\Expressive\Router\Middleware\PathBasedRoutingMiddleware

Also add the following import statement:

use Zend\Expressive\Router\Middleware\MethodNotAllowedMiddleware;

Change the line reading:

    $app->pipe(RouteMiddleware::class);

to:

    $app->pipe(PathBasedRoutingMiddleware::class);
    $app->pipe(MethodNotAllowedMiddleware::class);

Change the line reading:

    $app->pipe(NotFoundMiddleware::class);

to:

    $app->pipe(NotFoundHandler::class);

Changes between alpha3 and rc1

A number of substantial changes were released within the core Expressive packages. We'll detail them package-by-package, providing cumulative changes (vs. release-by-release changes), with general changes affecting all packages listed first.

General changes

Potentially the most far-reaching change was a decision to no longer compose response prototypes in classes that need to produce a response, but rather response factories. All classes that previously accepted a Psr\Http\Message\ResponseInterface instance (with the exception of Zend\Stratigility\Middleware\DoublePassMiddleware) now accept a PHP callable capable of producing a ResponseInterface instance. The simplest form, using zend-diactoros, would look like the following:

function () {
    return new Response();
}

We made this change for a variety of reasons.

First, not all containers allow marking an instance as non-shared. This meant that a ResponseInterface service would return the same instance each time. While this is generally fine, as the various with*() methods produce new instances, it falls apart when you write to the response body, as the body is the one part of a response that is mutable. (We often addressed this fact by also composing a stream factory, for producing an empty body stream to use with a response.) While we could solve this problem by indicating the ResponseInterface service was non-shared, this would not work on all containers, and led to convoluted solutions such as using "virtual services" to refer to discrete instances.

Second, an upcoming specification from PHP-FIG will largely address this. The proposed PSR-17 defines a number of interfaces describing factories for PSR-7 HTTP messages.

By changing our classes to compose a callable factory, we accomplish several things:

  • We can now share the ResponseInterface service safely, and re-use it in any service that needs to produce a response. Since the factory will always return a new response instance, sharing the factory for the ResponseInterface service is safe.

  • When PSR-17 is available, we will be able to decorate its response factory via a closure and continue to use it. Expressive applications will be immediately PSR-17 compatible.

Solving type-safety issues

The one problem with passing a PHP callable for the factory is that we have no guarantees that it actually returns a PSR-7 ResponseInterface!

To solve this, each class that composes a response factory re-assigns it as follows:

$this->responseFactory = function () use ($responseFactory) : ResponseInterface {
    return $responseFactory();
};

This approach ensures that a TypeError is raised if the factory returns any other type!

zend-stratigility

Stratigility received the following changes since the Expressive alpha3 release.

  • The Zend\Stratigility\Middleware\ErrorHandler and NotFoundHandler classes were updated to accept response factories instead of prototypes, as outlined in the previous section.

  • All middleware and handlers, as well as the Next implementation, were marked final.

zend-expressive-router

zend-expressive-router underwent a massive rewrite. The rewrite can be characterized as two major changes: routes and route results are now middleware, and all middleware from zend-expressive other than the LazyLoadingMiddleware was moved to this package.

One general change was made: the package now ships a ConfigProvider class and exposes it to zend-component-installer. This allows it to ship factories for middleware it exposes and ensure that middleware can be used immediately within Expressive applications.

Routes and RouteResults

Zend\Expressive\Router\Route now implements the PSR-15 MiddlewareInterface. Its process() method proxies to the middleware passed to its constructor.

Zend\Expressive\Router\RouteResult also now implements MiddlewareInterface. In the case of a successful result, its process() method will proxy to the route matched. In the case of a failed match, the method instead acts as a no-op, proxying directly to the handler argument. Due to these changes, we felt we could remove the getMatchedMiddleware() method; the middleware is still accessible via the composed Route class, and, generally speaking, you should not need direct access to it.

These changes greatly simplify the DispatchMiddleware, as it no longer needs to check if a successful match occurred, and can instead process the route result directly.

Additionally, the RouteMiddleware::process() logic was simplified, as it no longer needs to conditionally inject a RouteResult as a request attribute; it does it regardless of the result of matching.

Middleware

All previously provided middleware (RouteMiddleware, PathBasedRoutingMiddleware, and DispatchMiddleware) were moved to the Zend\Expressive\Router\Middleware namespace.

Additionally, we moved the following middleware from zend-expressive into this package:

zend-expressive middleware class zend-expressive-router middleware class
Zend\Expressive\Middleware\ImplicitHeadMiddleware Zend\Expressive\Router\Middleware\ImplicitHeadMiddleware
Zend\Expressive\Middleware\ImplicitOptionsMiddleware Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware

As detailed in the General changes section, each was modified to compose a response factory instead of a prototype.

These middleware were imported into zend-expressive-router as they are closely related to routing:

  • ImplicitHeadMiddleware introspects a RouteResult in order to return a response if a HEAD request was made and other conditions are met.

  • ImplicitOptionsMiddleware introspects a RouteResult in order to return a response if an OPTIONS request was made and other conditions are met.

We also refactored the RouteMiddleware (and, by extension, the PathBasedRoutingMiddleware) in order to extract the functionality for indicating a 405 Method Not Allowed response. This functionality is now shipped in a new class, Zend\Expressive\Router\Middleware\MethodNotAllowedMiddleware, which should be piped immediately following the RouteMiddleware or PathBasedRoutingMiddleware. This solves a problem several users have reported: we previously had no ability to modify how a 405 response is returned. You can now pipe alternative middleware for this feature if you desire using templates, Problem Details, or other formats.

zend-expressive

The zend-expressive package also had a large number of changes. The majority were related to exporting functionality to more relevant packages; other changes were made to provide backwards compatibility with previous alpha releases, as well as v2 releases.

API/Behavior changes.

  • Zend\Expressive\Container\ApplicationConfigInjectionDelegator now raises an exception if the $callback argument produces an instance of anything other than a Zend\Expressive\Application instance. Previously, it would return it immediately. Now, it raises an exception in order to detail to the user what changes they need to make to their application.

  • The exceptions Zend\Expressive\Exception\InvalidMiddlewareException and MissingDependencyException were updated to implement the PSR-11 ContainerExceptionInterface.

New classes

  • Zend\Expressive\Response\ServerRequestErrorResponseGenerator is an invokable class capable of generating a response from a Throwable as provided by Zend\HttpHandlerRunner\RequestHandlerRunner. The class Zend\Expressive\Container\ServerRequestErrorResponseGeneratorFactory was updated to create and return an instance of this class.

  • Zend\Expressive\Response\ErrorResponseGeneratorTrait contains the bulk of the logic for generating an error response used both by the above class, as well as the existing Zend\Expressive\Middleware\ErrorResponseGenerator.

New factories

The package provides two new factories:

  • Zend\Expressive\Container\ResponseFactoryFactory will return a PHP callable capable of producing a PSR-7 ResponseInterface. By default, the class provides a closure around instantiation of a zend-diactoros Response instance. The package maps it to the service Psr\Http\Message\ResponseInterface.

  • Zend\Expressive\Container\StreamFactoryFactory will return a PHP callable capable of producing a PSR-7 StreamInterface. By default, the class provides a closure around instantiation of a zend-diactoros Stream instance backed by a writable php://temp resource. The package maps it to the service Psr\Http\Message\StreamInterface.

Middleware removals

The following middleware were removed; in all cases, they were added to the zend-expressive-router package:

  • Zend\Expressive\Middleware\ImplicitHeadMiddleware
  • Zend\Expressive\Middleware\ImplicitOptionsMiddleware

The class Zend\Expressive\Middleware\NotFoundMiddleware was renamed to Zend\Expressive\Handler\NotFoundHandler, and now implements the PSR-15 RequestHandlerInterface (instead of MiddlewareInterface). The class now also composes a response factory instead of a response prototype. (Its related factory does this for you automatically.)

Factory removals

The following factories were removed, as they are either unnecessary, or provided by other packages:

  • Zend\Expressive\Container\RouteMiddlewareFactory (now provided in zend-expressive-router)
  • Zend\Expressive\Container\DispatchMiddlewareFactory (now provided in zend-expressive-router)
  • Zend\Expressive\Container\ImplicitHeadMiddlewareFactory (now provided in zend-expressive-router)
  • Zend\Expressive\Container\ImplicitOptionsMiddlewareFactory (now provided in zend-expressive-router)

zend-expressive-tooling

We spent a fair amount of time on zend-expressive-tooling to provide both migration tools as well as tools to make you more productive during development. In particular, we added the following:

  • migrate:interop-middleware will migrate middleware implementing http-interop interfaces to PSR-15. (This was in previous releases, but not highlighted before!)

  • migrate:middleware-to-request-handler will scan a directory (your src/ tree by default) for middleware. When it detects middleware that does not call on its handler argument, it converts it to a request handler.

  • action:create is an alias to the handler:create detailed in the previous post on alpha3, and exposes the same set of functionality for creating a PSR-15 request handler. Some developers prefer the "Action" verbiage, and requested this feature.

  • factory:create will generate a factory class file for the given class, using PHP's Reflection API. The generated file will be a sibling to the original class file. The functionality also auto-registers the class and factory in your configuration.

    The middleware:create, handler:create, and action:create commands now all use this functionality to create a factory for the class generated and wire it in your application configuration. (You may disable this capability via a CLI switch.)

Additionally, we added template awareness to the handler:create and action:create commands. By default, if they detect a Zend\Expressive\Template\TemplateRendererInterface service in your container, they will:

  • Generate a template named after the root namespace and newly generated class (minus any Handler, Action, or Middleware suffix).
  • Place a template file in the configured template path for the root namespace, and named after the generated class.
  • Modify the generated class to accept a TemplateRendererInterface instance to its constructor, and render the named template to a zend-diactoros HtmlResponse.

The commands allow you to disable template capabilities via a switch, as well as provide an alternate template namespace, template name, and template file extension.

Ecosystem updates

The skeleton releases cover the core functionality of Expressive: Stratigility, Expressive itself, routing, template engines, and the helpers. However, the Expressive ecosystem includes other functionality as well:

  • zend-expressive-session and its adapters
  • zend-expressive-authentication and its adapters
  • zend-expressive-authorization and its adapters
  • zend-problem-details
  • zend-expressive-hal

We have provided alpha releases of each of these packages to provide the following:

  • PSR-15 support
  • Response factory (vs response prototype) support
  • Compatibility with core alpha and RC releases

As such, if you are using the RC1 skeleton (or have updated your alpha3 skeleton), you will be able to use these packages without issue; installing them will grab these latest alpha versions, which will be compatible with the stable release (and for which their own stable releases will work with the Expressive v3 release).

Roadmap

So, we're done, right?

Wrong!

There's still work that remains. In particular:

  • We plan to version the existing documentation. This will allow us to provide version-specific docs, without confusing users about different usage and declarations.

  • We need to provide full documentation for the v3 release.

  • We will be issuing a 2.2 release with:

    • Deprecations, based on the v3 changes.
    • Backports of select v3 changes in order to aid migration.
  • We need to document migration from v2.2 to v3, and potentially provide automated tooling.

  • We anticipate users will find bugs in the RC, and will be actively incorporating bugfixes before the stable release.

Our target date is still 15 March 2018, but we need your help! Help by testing the RC1 skeleton and providing your feedback. As we prepare the v2.2 release, help by testing tooling and applying our migration documentation to your applications, and let us know what works and what doesn't. If you find features that are not documented, let us know by filing issues or asking questions in our Slack.

We look forward to the stable release, and the positive impact PSR-15 will have on the PHP ecosystem!

Updates

  • 2018-03-01: Fixes a syntax error in the instructions for creating a project. Additionally, fixes a grammatical issue in one of the headers.
]]>
0
Expressive 3 Alpha 3 Thu, 08 Feb 2018 15:50:00 +0000 http://www.4645285.com/blog/2018-02-08-expressive-3-alpha3.html http://www.4645285.com/blog/2018-02-08-expressive-3-alpha3.html Matthew Weier O'Phinney Matthew Weier O'Phinney 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 Zend\Stratigility\doublePassMiddleware() 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 "App\XClacksOverheadMiddleware"

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

<?php
namespace App;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

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' => [
        App\XClacksOverheadMiddleware::class => App\XClacksOverheadMiddleware::class,
    ],
],

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

// likely an early statement, before routing
$app->pipe(App\XClacksOverheadMiddleware::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 "App\Handler\HelloWorldHandler"

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 Zend\Diactoros\Response\HtmlResponse. Open the file, and edit the contents to look like the following:

<?php

namespace App\Handler;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\HtmlResponse;

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' => [
        App\Handler\HelloWorldHandler::class => App\Handler\HelloWorldHandler::class,
    ],
],

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

$app->get('/hello', App\Handler\HelloWorldHandler::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', [
    App\XClacksOverheadMiddleware::class,
    App\Handler\HelloWorldHandler::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!

]]>
0
Expressive 3 Preview Thu, 14 Dec 2017 17:30:00 +0000 http://www.4645285.com/blog/2017-12-14-expressive-3-dev.html http://www.4645285.com/blog/2017-12-14-expressive-3-dev.html Matthew Weier O'Phinney Matthew Weier O'Phinney 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 Interop\Http\ServerMiddleware to Interop\Http\Server. These will therefor become Psr\Http\Server 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 Interop\Http\Server\MiddlewareInterface:

    namespace YourModule;
    
    use Interop\Http\Server\MiddlewareInterface;
    use Interop\Http\Server\RequestHandlerInterface;
    use Psr\Http\Message\ResponseInterface;
    use Psr\Http\Message\RequestHandlerInterface;
    
    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.

]]>
0
A new release of zend-db Wed, 06 Dec 2017 17:30:00 +0000 http://www.4645285.com/blog/2017-12-06-zend-db-2.9.0.html http://www.4645285.com/blog/2017-12-06-zend-db-2.9.0.html Enrico Zimuel Enrico Zimuel 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.

]]>
0
Emitting Responses with Diactoros Thu, 14 Sep 2017 16:02:00 +0000 http://www.4645285.com/blog/2017-09-14-diactoros-emitters.html http://www.4645285.com/blog/2017-09-14-diactoros-emitters.html Matthew Weier O'Phinney Matthew Weier O'Phinney 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 Zend\Diactoros\Response\EmitterInterface:

namespace Zend\Diactoros\Response;

use Psr\Http\Message\ResponseInterface;

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:

  • Zend\Diactoros\Emitter\SapiEmitter
  • Zend\Diactoros\Emitter\SapiStreamEmitter

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 Zend\Expressive\Emitter\EmitterStack. 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 Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response\EmitterInterface;
use Zend\Diactoros\Response\SapiEmitter;
use Zend\Diactoros\Response\SapiStreamEmitter;
use Zend\Expressive\Emitter\EmitterStack;

$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 Zend\Diactoros\Emitter\EmitterInterface service, then the above stack will be used by Zend\Expressive\Application 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:

]]>
0
Logging PHP applications Tue, 12 Sep 2017 16:24:00 +0000 http://www.4645285.com/blog/2017-09-12-zend-log.html http://www.4645285.com/blog/2017-09-12-zend-log.html Enrico Zimuel Enrico Zimuel 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 Zend\Log\Logger;
use Zend\Log\Writer\Stream;

$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 Zend\Log\Formatter\Simple('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 Zend\Log\Logger;
use Zend\Log\Writer;

$logger = new Logger;
$writer = new Writer\Stream(__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 Zend\Log\Filter\Priority(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 Zend\Log\Logger 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 Zend\Log\Processer 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 Zend\Db\Adapter\Adapter as DbAdapter;
use Zend\Log\Formatter;
use Zend\Log\Writer;
use Zend\Log\Logger;

// 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 Writer\Db($db, 'log', $mapping); // log table
$formatter = new Formatter\Base();
$formatter->setDateTimeFormat('Y-m-d H:i:s'); // MySQL DATETIME format
$writerDb->setFormatter($formatter);

// Create our file log writer
$writerFile = new Writer\Stream(__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 Zend\Log\PsrLoggerAdapter. This logger can be used anywhere a Psr\Log\LoggerInterface is expected.

As an example:

use Psr\Log\LogLevel;
use Zend\Log\Logger;
use Zend\Log\PsrLoggerAdapter;

$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 Zend\Log\Writer\Psr class. In order to use it, you need to pass a Psr\Log\LoggerInterface instance to the $psrLogger constructor argument:

$writer = new Zend\Log\Writer\Psr($psrLogger);

zend-log also supports PSR-3 message placeholders via the Zend\Log\Processor\PsrPlaceholder 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 Zend\Log\Logger;
use Zend\Log\Processor\PsrPlaceholder;

$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 Zend\Log as a module in your application.

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

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 Zend\Log\Logger;
use Zend\ServiceManager\Factory\FactoryInterface;

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 App\Action;

use Psr\Container\ContainerInterface;
use Zend\Log\Logger;

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

As an example of logging in middleware:

namespace App\Action;

use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Log\Logger;

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, Zend\Stratigility\Middleware\ErrorHandler 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 Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;
use Zend\Log\Logger;

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 Psr\Container\ContainerInterface;
use Zend\Log\Logger;
use Zend\Log\Processor\PsrPlaceholder;
use Zend\Stratigility\Middleware\ErrorHandler;

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 Zend\Stratigility\Middleware\ErrorHandler;

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.

]]>
0
Specialized Response Implementations in Diactoros Thu, 24 Aug 2017 16:24:00 +0000 http://www.4645285.com/blog/2017-08-24-diactoros-responses.html http://www.4645285.com/blog/2017-08-24-diactoros-responses.html Matthew Weier O'Phinney Matthew Weier O'Phinney 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, Zend\Diactoros\Response:

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

$body in this signature allows either a Psr\Http\Message\StreamInterface 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 Zend\Diactoros\Response\EmptyResponse, 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.

Zend\Diactoros\Response\RedirectResponse 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 Psr\Http\Message\UriInterface 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 Zend\Diactoros\Response\TextResponse, 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 Zend\Diactoros\Response\HtmlResponse, 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 Zend\Diactoros\Response\JsonResponse, 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.

]]>
0
Protecting passwords with Argon2 in PHP 7.2 Thu, 17 Aug 2017 16:12:00 +0000 http://www.4645285.com/blog/2017-08-17-php72-argon2-hash-password.html http://www.4645285.com/blog/2017-08-17-php72-argon2-hash-password.html Enrico Zimuel Enrico Zimuel PHP 7.2 will be released later this year (2017). This version contains some interesting additions, including two new security features: support of the Argon2 password hash algorithm, and the ext/sodium extension wrapping the libsodium library.

With these new features, PHP is the first programming language to adopt modern cryptography in its standard library.

In this article, we demonstrate the usage of the Argon2 password hash algorithm.

Installation of PHP 7.2

If you are reading this article before the general availability of 7.2, you need to compile PHP to use that version. You can download the source code from the PHP downloads site. Today, 17 August 2017, the most recent available version is 7.2.0 Beta 3 (file php-7.2.0beta3.tar.gz).

Before compiling PHP, you need to install the argon2 library. If you are using a Debian/Ubuntu Linux distribution, you can run the following command:

sudo apt install argon2 libargon2-0 libargon2-0-dev

To compile PHP, you need to extract the previous source code in a folder and run the following commands:

./configure --with-password-argon2
make
make install

Please note the usage of the option --with-password-argon2 for including support for the Argon2 algorithm.

This will install PHP 7.2 as the default PHP on your system. If you do not want to change the default PHP, you can omit the execution of the last command make install, or use the --prefix option to configure to specify an alternate location. In each of these cases, you will need to reference PHP 7.2 using the path of the folder installation.

Argon2

Argon2 is a password-based key derivation function winner of the Password Hashing Competition in July 2015.

This function is an evolution of the bcrypt and scrypt algorithms.

Argon2 provides security against brute force attacks using a predefined memory size, CPU time, and a degree of parallelism to prevent GPU attacks.

It uses 3 parameters that control the memory requirements, the execution time, and the parallelism level.

There are two main versions of this algorithm: Argon2i and Argon2d. Argon2i is the safest against side-channel attacks, while Argon2d provides the highest resistance against GPU cracking attacks.

Argon2d is not suitable for password hashing and should not be used.

PHP 7.2 adds Argon2i support to its Password Hashing Functions.

Usage of Argon2i in PHP

Argon2 support in PHP was proposed by Charles R. Portwood II in via an RFC.

The implemented algorithm in PHP is Argon2i (v1.3), and it can be provided via the $algo parameter to the password_hash() function. The signature of password_hash() is as follows:

password_hash( string $password , integer $algo [, array $options ]) : string

The second parameter ($algo) specifies the algorithm to use when hashing; the Argon2i algorithm is represented by the constant PASSWORD_ARGON2I.

As an example:

$password = 'test';
$hash = password_hash($password, PASSWORD_ARGON2I);
var_dump($hash);

The $hash result will contains a string of 98 characters as follows:

$argon2i$v=19$m=1024,t=2,p=2$TmxLemFoVnZFaEJuT1NyYg$4j2ZFDn1fVS70ZExmlJ33rXOinafcBXrp6A6grHEPkI

This string contains sub-string of parts, separated by dollar ($). These parts are:

argon2i
v=19
m=1024,t=2,p=2
TmxLemFoVnZFaEJuT1NyYg
4j2ZFDn1fVS70ZExmlJ33rXOinafcBXrp6A6grHEPkI

The first part is the algorithm name (argon2i), the second is the Argon2i version, and the third part is a list of algorithm parameters related to memory cost (in Kb), time cost, and threads to be used (parallelism).

The fourth parameter is the random salt value, encoded in Base64. This value is generated by password_hash() using a random value for each execution. This is why we have different hash outputs for the same input string. The default size of the salt is 16 bytes.

The fifth and last parameter of the string contains the hash value, encoded in Base64. The hash size is 32 bytes.

PHP provides a function named password_get_info($hash) to get information about the hash generated by password_hash(). For instance, if you use password_get_info() on the previous value, you will receive:

array(3) {
  ["algo"]=>
  int(2)
  ["algoName"]=>
  string(7) "argon2i"
  ["options"]=>
  array(3) {
    ["memory_cost"]=>
    int(1024)
    ["time_cost"]=>
    int(2)
    ["threads"]=>
    int(2)
  }
}

The default parameters for the algorithm are a memory_cost of 1024 Kb (1 Mb), a time_cost of 2, and two threads to be used for parallelism. The Argon2 specifications suggest to use a power of 2 value for the memory_cost.

These values can be changed using the $options parameter of the password_hash() function. As an example:

$password = 'test';
$options = [
    'memory_cost' => 1<<17, // 128 Mb
    'time_cost'   => 4,
    'threads'     => 3,
];
$hash = password_hash($password, PASSWORD_ARGON2I, $options);
var_dump($hash);

PHP will generate an E_WARNING for values that cannot be used as options for the PASSWORD_ARGON2I algorithm.

Regarding the default option values, we suggest to change it according to the use cases and CPU + RAM available. From the PHP RFC:

Due to the variety of platforms PHP runs on, the cost factors are deliberately set low as to not accidentally exhaust system resources on shared or low resource systems when using the default cost parameters. Consequently, users should adjust the cost factors to match the system they're working on. As Argon2 doesn't have any "bad" values, however consuming more resources is considered better than consuming less. Users are encouraged to adjust the cost factors for the platform they're developing for.

Conclusion

In this article we demonstrated usager of the Argon2 password hash algorithm with PHP 7.2. The Argon2 algorithm is the state of the art for password protection and it can be now used in PHP without installing additional extensions. This is a very nice security feature that will improve the security of PHP applications that store user passwords. In a future article, we will cover the Sodium extension, another new security feature included in PHP 7.2. With these new features, PHP is the first language to support modern cryptography in the core of the language.

]]>
0
REST Representations for Expressive Tue, 08 Aug 2017 20:14:00 +0000 http://www.4645285.com/blog/2017-08-08-expressive-rest-representations.html http://www.4645285.com/blog/2017-08-08-expressive-rest-representations.html Matthew Weier O'Phinney Matthew Weier O'Phinney We've been working towards our various Apigility on Expressive goals, and have recently published two new components:

These components provide response representations for APIs built with PSR-7 middleware. Specifically, they provide:

These two formats provide both JSON and XML representation options (the latter through a secondary proposal).

What's in a representation?

So you're developing an API!

What can clients expect when they make a request to your API? Will they get a wall of text? or some sort of serialization? If it's a serialized format, which ones do you support? And how is the data structured?

The typical answer will be, "we'll provide JSON responses." That answers the serialization aspect, but not the data structure; for that, you might develop and publish a schema for your end users, so they know how to parse the response.

But there may still be unanswered questions when you do so:

  • How does the consumer know what actions can next be taken, or what resources might be related to the one requested?
  • If the resource contains other entities, how can they identify which ones they can request separately, versus those that are just part of the data structure?

These and all of the previous are questions that a representation format answers. A well-considered representation format will:

  • Provide links to the actions that may be performed next, as well as to related resources.
  • Indicate which data elements represent other requestable resources.
  • Be extensible, to allow representing arbitrary data.

I tend to think of representations as falling into two buckets:

  • Representations of errors.
  • Representations of application resources.

Errors need separate representation, as they are not requestable on their own; they are returned when something goes wrong, and need to provide enough detail that the consumer can determine what they need to change in order to perform a new request.

The Problem Details specification provides exactly this. As an example:

{
    "type": "https://example.com/problems/rate-limit-exceeded",
    "title": "You have exceeded your API rate limit.",
    "detail": "You have hit your rate limit of 5000 requests per hour.",
    "requests_this_hour": 5025,
    "rate_limit": 5000,
    "rate_limit_reset": "2017-05-03T14:39-0500"
}

We chose Problem Details to standardize on when starting the Apigility project as it has very few requirements, but can model any error easily. The ability to link to documentation detailing general error types provides the ability to communicate with your consumers about known errors and how to correct them.

Application resources generally should have their own schema, but having a predictable structure for providing relational links (answering the "what can I do next" question) and embedding related resources can help those making clients or those consuming your API to automate many of their processes. Instead of having a list of URLs they can access, they can hit one resource, and start following the composed links; when they present data, they can also present controls for the embedded resources, making it simpler to make requests to these other items.

HAL provides these details in a simple way: relational links are objects under the _links element, and embedded resources are under the _embedded element; all other data is represented as normal key/value pairs, allowing for arbitrary nesting of structures. An example payload might look like the following:

{
    "_links": {
        "self": { "href": "/api/books?page=7" },
        "first": { "href": "/api/books?page=1" },
        "prev": { "href": "/api/books?page=6" },
        "next": { "href": "/api/books?page=8" },
        "last": { "href": "/api/books?page=17" }
        "search": {
            "href": "/api/books?query={searchTerms}",
            "templated": true
        }
    },
    "_embedded": {
        "book": [
            {
                "_links": {
                    "self": { "href": "/api/books/1234" }
                }
                "id": 1234,
                "title": "Hitchhiker's Guide to the Galaxy",
                "author": "Adams, Douglas"
            },
            {
                "_links": {
                    "self": { "href": "/api/books/6789" }
                }
                "id": 6789,
                "title": "Ancillary Justice",
                "author": "Leckie, Ann"
            }
        ]
    },
    "_page": 7,
    "_per_page": 2,
    "_total": 33
}

The above provides controls to allow a consumer to navigate through a result set, as well as to perform another search against the API. It provides data about the result set, and also embeds a number of resources, with links so that the consumer can make requests against those individually. Having links present in the payloads means that if the URI scheme changes later, a well-written client will be unaffected, as it will follow the links delivered in response payloads instead of hard-coding them. This allows our API to evolve, without affecting the robustness of clients.

A number of other representation formats have become popular over the years, including:

Each are powerful and flexible in their own right. We standardized on HAL for Apigility originally as it was one of the first published specifications; we've continued with it as it is a format that's both easy to generate as well as parse, and extensible enough to answer the needs of most API representations.

zend-problem-details

The package zendframework/zend-problem-details provides a Problem Details implementation for PHP, and specifically for generating PSR-7 responses. It provides a multi-faceted approach to providing error details to your users.

First, you can compose the ProblemDetailsResponseFactory into your middleware, and use it to generate and return your error responses:

return $this->problemDetails->createResponse(
    $request,                                           // PSR-7 request
    422,                                                // HTTP status
    'Invalid data detected in book submission',         // Detail
    'Invalid book',                                     // Problem title
    'https://example.com/api/doc/errors/invalid-book',  // Problem type (URL to details)
    ['messages' => $validator->getMessages()]           // Additional data
);

The request instance is passed to the factory to allow it to perform content negotiation; zend-problem-details uses the Accept header to determine whether to serve a JSON or an XML representation, defaulting to XML if it is unable to match to either format.

The above will generate a response like the following:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json

{
  "status": 422,
  "title": "Invalid Book",
  "type": "https://example.com/api/doc/errors/invalid-book",
  "detail": "Invalid data detected in book submission",
  "messages": [
    "Missing title",
    "Missing author"
  ]
}

ProblemDetailsFactory is agnostic of PSR-7 implementation, and allows you to inject a response prototype and stream factory during instantiation. By default, it uses zend-diactoros for these artifacts if none are provided.

Second, you can create a response from a caught exception or throwable:

return $this->problemDetails->createResponseFromThrowable(
    $request,
    $throwable
);

Currently, the factory uses the exception message for the detail, and 4XX and 5XX exception codes for the status (defaulting to 500 for any other value).

We are currently evaluating a proposal that would have caught exceptions generate a canned Problem Details response with a status of 500, so the above behavior may change in the future. If you want to guarantee the code and message are used, you can create custom exceptions, as outlined below.

Third, extending on the ability to create details from throwables, we provide a custom exception interface, ProblemDetailsException. This interface defines methods for pulling additional information to provide to a Problem Details response:

namespace Zend\ProblemDetails\Exception;

interface ProblemDetailsException
{
    public function getStatus() : int;
    public function getType() : string;
    public function getTitle() : string;
    public function getDetail() : string;
    public function getAdditionalData() : array;
}

If you throw an exception that implements this interface, the createResponseFromThrowable() method shown above will pull data from these methods in order to create the response. This allows you to define domain-specific exceptions that can provide additional details when used in an API context.

Finally, we also provide optional middleware, ProblemDetailsMiddleware, that does the following:

  • Registers an error handler that casts PHP errors in the current error_reporting bitmask to ErrorException instances.
  • Wraps calls to the delegate in a try/catch block.
  • Passes any caught throwables to the createResponseFromThrowable() factory in order to return Problem Details responses.

We recommend using custom exceptions and this middleware, as the combination allows you to focus your efforts on the positive outcome paths within your middleware.

Using it in Expressive

When using Expressive, you can then compose the ProblemDetailsMiddleware within route-specific pipelines, allowing you to have separate error handlers for the API parts of your application:

// In config/routes.php:

// Per route:
$app->get('/api/books', [
    Zend\ProblemDetails\ProblemDetailsMiddleware::class,
    Books\Action\ListBooksAction::class,
], 'books');
$app->post('/api/books', [
    Zend\ProblemDetails\ProblemDetailsMiddleware::class,
    Books\Action\CreateBookAction::class,
]);

Alternately, if all API endpoints have a common URI path prefix, register it as path-segregated middleware:

// In config/pipeline.php:

$app->pipe('/api', Zend\ProblemDetails\ProblemDetailsMiddleware::class);

These approaches allow you to deliver consistently structured, useful errors to your API consumers.

zend-expressive-hal

The package zendframework/zend-expressive-hal provides a HAL implementation for PSR-7 applications. Currently, it allows creating PSR-7 response payloads only; we may consider parsing HAL requests at a future date, however.

zend-expressive-hal implements PSR-13 (Link Definition Interfaces), and provides structures for:

  • Defining relational links
  • Defining HAL resources
  • Composing relational links in HAL resources
  • Embedding HAL resources in other HAL resources

These utilities can be used manually, without any other requirements:

use Zend\Expressive\Hal\HalResource;
use Zend\Expressive\Hal\Link;

$author = new HalResource($authorDataArray);
$author = $author->withLink(
    new Link('self', '/authors/' .  $authorDataArray['id'])
);

$book = new HalResource($bookDataArray);
$book = $book->withLink(
    new Link('self', '/books/' .  $bookDataArray['id'])
);
$book = $book->embed('authors', [$author]);

Both Link and HalResource are immutable; as such, if you wish to make iterative changes, you will need to re-assign the original value.

These clases allow you to model the data to return in your representation, but what about returning a response based on them? To handle that, we have the HalResponseFactory, which will generate a response from a resource provided to it:

return $halResponseFactory->createResponse($request, $book);

Like the ProblemDetailsFactory, the HalResponseFactory is agnostic of PSR-7 implementation, and allows you to inject a response prototype and stream factory during instantiation.

Also, it, too, uses content negotiation in order to determine whether a JSON or XML response should be generated.

The above might generate the following response:

HTTP/1.1 200 OK
Content-Type: application/hal+json

{
  "_links": {
    "self": {"href": "/books/42"}
  },
  "id": 42
  "title": "The HitchHiker's Guide to the Galaxy",
  "_embedded": {
    "authors": [
      {
        "_links": {
          "self": {"href": "/author/12"}
        },
        "id": 12,
        "name": "Douglas Adams"
      }
    ]
  }
}

If your resources might be used in multiple API endpoints, you may find that creating them manually everywhere you need them is a bit of a chore!

One of the most powerful pieces of zend-expressive-hal is that it provides tools for mapping object types to how they should be represented. This is done via a metadata map, which maps class types to zend-hydrator extractors for the purpose of generating a representation. Additionally, we provide tools for generating link URIs based on defined routes, which allows metadata to provide dynamic link generation for generated resources.

I won't go into the architecture of how all this works, as there's a fair amount of detail. In practice, what will generally happen is:

  • You'll define a metadata map in your application configuration, mapping your own classes to details on how to represent them.
  • You'll compose a Zend\Expressive\Hal\ResourceGenerator (which will use a metadata map based on your configuration) and a HalResponseFactory in your middleware.
  • You'll pass an object to the ResourceGenerator in order to produce a HalResource.
  • You'll pass the generated HalResource to your HalResponseFactory to produce a response.

So, as an example, I might define the following metadata map configuration:

namespace Books;

use Zend\Expressive\Hal\Metadata\MetadataMap;
use Zend\Expressive\Hal\Metadata\RouteBasedCollectionMetadata;
use Zend\Expressive\Hal\Metadata\RouteBasedResourceMetadata;
use Zend\Hydreator\ObjectProperty as ObjectPropertyHydrator;

class ConfigProvider
{
    public function __invoke() : array
    {
        return [
            'dependencies' => $this->getDependencies(),
            MetadataMap::class => $this->getMetadataMap(),
        ];
    }

    public function getDependencies() : array
    {
        return [ /* ... */ ];
    }

    public function getMetadataMap() : array
    {
        return [
            [
                '__class__' => RouteBasedResourceMetadata::class,
                'resource_class' => Author::class,
                'route' => 'author',
                'extractor' => ObjectPropertyHydrator::class,
            ],
            [
                '__class__' => RouteBasedCollectionMetadata::class,
                'collection_class' => AuthorCollection::class,
                'collection_relation' => 'authors',
                'route' => 'authors',
            ],
            [
                '__class__' => RouteBasedResourceMetadata::class,
                'resource_class' => Book::class,
                'route' => 'book',
                'extractor' => ObjectPropertyHydrator::class,
            ],
            [
                '__class__' => RouteBasedCollectionMetadata::class,
                'collection_class' => BookCollection::class,
                'collection_relation' => 'books',
                'route' => 'books',
            ],
        ];
    }
}

The above defines metadata for authors and books, both as individual resources as well as collections. This allows us to then embed an author as a property of a book, and have it represented as an embedded resource!

From there, we could have middleware that composes both a ResourceGenerator and a HalResponseFactory in order to produce representations:

namespace Books\Action;

use Books\Repository;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Expressive\Hal\HalResponseFactory;
use Zend\Expressive\Hal\ResourceGenerator;

class ListBooksAction implements MiddlewareInterface
{
    private $repository;
    private $resourceGenerator;
    private $responseFactory;

    public function __construct(
        Repository $repository,
        ResourceGenerator $resourceGenerator,
        HalResponseFactory $responseFactory
    ) {
        $this->repository = $repository;
        $this->resourceGenerator = $resourceGenerator;
        $this->responseFactory = $responseFactory;
    }

    public function process(
        ServerRequestInterface $request,
        DelegateInterface $delegate
    ) : ResponseInterface {
        /** @var \Books\BookCollection $books */
        $books = $this->repository->fetchAll();

        return $this->responseFactory->createResponse(
            $request,
            $this->resourceGenerator->fromObject($books)
        );
    }
}

When using zend-expressive-hal to generate your responses, the majority of your middleware will look almost exactly like this!

We provide a number of other features in the package as well:

  • You can define your own metadata types, and strategy classes for producing representations based on objects matching that metadata.
  • You can specify custom mediatypes for your generated responses.
  • You can provide your own link generation (useful if you're not using Expressive).
  • You can provide your own JSON and XML renderers, if you want to vary the output for some reason (e.g., always adding specific links).

Use Anywhere!

These two packages, while part of the Zend Framework and Expressive ecosystems, can be used anywhere you use PSR-7 middleware. The Problem Details component provides a factory for producing a PSR-7 Problem Details response, and optionally middleware for automating reporting of errors. The HAL component provides only a factory for producing a PSR-7 HAL response, and a number of tools for modeling the data to return in that response.

As such, we encourage Slim, Lumen, and other PSR-7 framework users to consider using these components in your API applications to provide standard, robust, and extensible representations to your users!

For more details and examples, visit the docs for each component:

]]>
0
Apigility on Expressive Update for 2017-07-13 Thu, 13 Jul 2017 23:30:00 +0000 http://www.4645285.com/blog/2017-07-13-apigility-on-expressive-update.html http://www.4645285.com/blog/2017-07-13-apigility-on-expressive-update.html Matthew Weier O'Phinney Matthew Weier O'Phinney We've been working on the Apigility on Expressive initiative for a couple months now, and have a bit of progress to report.

First, if you're unfamiliar with the initiative, please head over and read the RFC.

In this post, we'll discuss what's done and ready to review, and what pieces are in the works.

Ready to review

Two pieces are currently ready to review:

These two provide us the representations that will be returned by your API. Problem Details is used to describe API errors — whether those are due to the client sending bad information, or server-side errors that occur. HAL is used to provide your API payloads to the client.

Problem Details

There are three facets to the proposed implementation:

  • We provide a ProblemDetailsResponseFactory for you to compose in middleware. When you encounter an error condition, you can use it to generate and return a Problem Details response. It has built-in content negotiation, and will return either a JSON or XML response based on the Accept client request header.

  • We provide a custom exception interface, ProblemDetailsException, which you can implement that defines a number of getter methods for retrieving information to use when building a Problem Details response. (We also provide a trait, CommonProblemDetailsException, that implements the various getters, allowing you to focus on constructors.)

  • We provide ProblemDetailsMiddleware, which acts as error handling middleware, catching exceptions and PHP errors and turning them into Problem Details responses via the ProblemDetailsResponseFactory. If you throw a ProblemDetailsException, this middleware will pull data from it to fully populate the error details!

We feel these three faculties allow a great deal of flexibility in how you handle errors for your APIs.

HAL

Our HAL implementation has several facets:

  • Low-level value objects representing relational links and HAL resources. These can be created manually, and independently of any other ZF components.

  • A LinkGenerator that uses a PSR-7 request instance and a composed UrlGenerator to allow creating links that reference application routes.

  • Renderers for both JSON and XML. Each accepts a HAL resource, with its relational links, and produces the serialized version.

  • A ResourceGenerator that maps an object to related metadata, and the related metadata to a strategy for creating the HAL resource. The shipped metadata and strategies use zend-hydrator for extracting data from objects, and zend-paginator awareness for producing pagination relational links.

  • A HalResponseFactory for rendering resources and returning PSR-7 responses. It has built-in content negotiation to allow producing a response with the correct format.

While the library allows developers to manually create resources and links, the real power comes from the ability to pass objects directly to the ResourceGenerator in order to create a fully populated HAL resource with its self-relational link; this vastly reduces boilerplate in middleware.

Examples

The following demonstrates how you might use the two features together within middleware to return responses.

First, we have some configuration for the metadata map that tells it how we want to represent our objects:

// In Books\ConfigProvider, or a config/autoload/*.global.php file:

use Books\Book;
use Books\BookCollection;
use Hal\Metadata\MetadataMap;
use Hal\Metadata\RouteBasedCollectionMetadata;
use Hal\Metadata\RouteBasedResourceMetadata;
use Zend\Hydrator\ObjectProperty as ObjectPropertyHydrator;

MetadataMap::class => [
    [
          [
              '__class__' => RouteBasedResourceMetadata::class,
              'resource_class' => Book::class,
              'route' => 'book',
              'extractor' => ObjectPropertyHydrator::class,
          ],
          [
              '__class__' => RouteBasedCollectionMetadata::class,
              'collection_class' => BookCollection::class,
              'collection_relation' => 'book',
              'route' => 'books',
          ],
    ],
],

Books\Book is a value object with public properties. Books\BookCollection extends Zend\Paginator\Paginator, allowing a paginated collection.

We will also assume we have the following routes defined:

  • book will map to /books/{id}
  • books will map to /books

Finally, we get to our middleware. It assumes a Books\Repository, which is simply a class that accesses our persistent storage.

namespace Books;

use Hal\HalResponseFactory;
use Hal\ResourceGenerator;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use ProblemDetails\ProblemDetailsResponseFactory;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Expressive\Helper\ServerUrlHelper;
use Zend\Expressive\Helper\UrlHelper;

class BookMiddleware implements MiddlewareInterface
{
    /** @var ProblemDetailsResponseFactory */
    private $problemDetails;

    /** @var Repository */
    private $repository;

    /** @var ResourceGenerator */
    private $resourceGenerator;

    /** @var HalResponseFactory **/
    private $responseFactory;

    public function __construct(
        Repository $repository,
        ResourceGenerator $resourceGenerator,
        HalResponseFactory $responseFactory,
        ProblemDetailsResponseFactory $problemDetails
    ) {
        $this->repository = $repository;
        $this->resourceGenerator = $resourceGenerator;
        $this->responseFactory = $responseFactory;
        $this->problemDetails = $problemDetails;
    }

    /**
     * @param ServerRequestInterface $request
     * @param DelegateInterface $delegate
     * @return ResponseInterface
     */
    public function process(ServerRequestInterface $request, DelegateInterface $delegate)
    {
        $id = $request->getAttribute('id', false);

        if (false === $id) {
            // Return a problem details response!
            return $this->problemDetails->createResponse(
                $request,
                400,
                'Missing book identifier',
                'Client Error',
                'book.id'
            );
        }

        $user = $request->getAttribute('user');

        try {
            $book = $this->repository->fetch($id, $user);
        } catch (Exception\BookNotFoundException $e) {
            // Return a problem details response!
            return $this->problemDetails->createResponse(
                $request,
                404,
                'Book not found',
                'Not Found',
                'book.not_found',
                [ 'book_id' => $id]
            );
        }

        // Create the resource
        $resource = $this->resourceGenerator->fromObject($book, $request);

        // Add another relational link
        $resource->withLink($this->resourceGenerator->getLinkGenerator()->templatedFromRoute(
            'search',
            $request,
            'books',
            [],
            ['query' => '{searchTerms}']
        ));

        // Return a response with the accepted representation
        return $this->responseFactory->createResponse($request, $resource);
    }
}

A different approach we could take would be to have our exceptions implement ProblemDetailsException, and either throw them directly, or simply not catch them. We would then register the ProblemDetailsMiddleware within our routed middleware:

$app->get('/books/{id}', [
    \ProblemDetails\ProblemDetailsMiddleware::class,
    \Books\BookMiddleware::class,
], 'book');

The approaches allow our middleware to focus primarily on gathering input, calling our model, and then preparing a response.

In the works

While these two are ready to review, we also have a number of other modules in the works, and likely ready to review in the next few weeks:

  • Authentication (by Enrico Zimuel)
  • Authorization (by Enrico Zimuel)
  • OAuth2 (by Julien Guittard)

The content negotiation module scope has decreased; since the proposed Problem Details and HAL modules have negotiation built-in, we only need to focus on the problem of negotiating incoming data. As such, we can likely tackle this quickly as well.

Help out!

We'd love for you to help out. You can do so by reviewing the linked RFCs, as well as trying out the current code in your projects and reporting issues or proposing improvements.

We're excited to start building full-fledged, featureful REST APIs with Expressive!

]]>
0
六合特码资料
  • <cite id="jdh17"></cite>
    <ins id="jdh17"></ins>
    <var id="jdh17"></var>
    <ins id="jdh17"><span id="jdh17"><var id="jdh17"></var></span></ins>
    <var id="jdh17"></var>
    <cite id="jdh17"><span id="jdh17"></span></cite>
    <var id="jdh17"></var>
    <thead id="jdh17"><strike id="jdh17"><listing id="jdh17"></listing></strike></thead>
    <cite id="jdh17"><video id="jdh17"><menuitem id="jdh17"></menuitem></video></cite>
  • <cite id="jdh17"></cite>
    <ins id="jdh17"></ins>
    <var id="jdh17"></var>
    <ins id="jdh17"><span id="jdh17"><var id="jdh17"></var></span></ins>
    <var id="jdh17"></var>
    <cite id="jdh17"><span id="jdh17"></span></cite>
    <var id="jdh17"></var>
    <thead id="jdh17"><strike id="jdh17"><listing id="jdh17"></listing></strike></thead>
    <cite id="jdh17"><video id="jdh17"><menuitem id="jdh17"></menuitem></video></cite>