Welcome to Abdul Malik Ikhsan's Blog

Using PSR-7 UploadedFileInterface with zend-filter’s File and zend-validator’s File in zend-form instance

Posted in expressive, Tutorial PHP, Zend Framework by samsonasik on February 3, 2019

The latest zend-filter:^2.9 and zend-validator:^2.12 have full support for PSR-7 UploadedFileInterface handling. If we are using it with zend-form instance:

use Zend\Diactoros\StreamFactory;
use Zend\Diactoros\UploadedFileFactory;
use Zend\Filter\File\RenameUpload;
use Zend\Validator\File\Size;

// ...
    public function getInputFilterSpecification()
    {
        return [
            [
                'name' => 'filename',
                'required' => true,
                'filters' => [
                    [
                        'name' => RenameUpload::class,
                        'options' => [
                            'target'               => \getcwd() . '/public/uploads',
                            'use_upload_extension' => true,
                            'use_upload_name'      => true,
                            'overwrite'            => true,
                            'randomize'            => true,

                            'stream_factory'       => new StreamFactory(),
                            'upload_file_factory'  => new UploadedFileFactory(),
                        ],
                    ],
                ],
                'validators' => [
                    [
                        'name' => Size::class,
                        'options' => [
                            'max' => '10MB',
                        ],
                    ],
                ],
            ],
        ];
    }
// ...

The only config that needed to be changed is in the filters: the stream_factory and upload_file_factory config with provide objects of class that implements Psr\Http\Message\StreamFactoryInterface and Psr\Http\UploadedFileFactoryInterface, which if we are using zend-diactoros, we can use Zend\Diactoros\StreamFactory and Zend\Diactoros\UploadedFileFactory.

For Form set data, we can now do the following:

use Psr\Http\Message\ServerRequestInterface;
// ...
    // $request is an object of class implements ServerRequestInterface
    $postAndFileData = \array_merge_recursive(
        $request->getParsedBody(),
        $request->getUploadedFiles()
    );
// ...

On get data, we have different result from normal Zend\Http\Phpenvironment\Request. If we are using normal Zend\Http\Phpenvironment\Request, we normally get the following data:

array (size=1)
  'filename' => 
    array (size=5)
      'name' => string 'Untitled-1.png' (length=14)
      'type' => string 'image/png' (length=9)
      'size' => int 761087
      'tmp_name' => string '/path/to/public/uploads/Untitled-1_5c51c49e1d0855_97287659.png' (length=103)
      'error' => int 0

With the new parameters, the form data will be:

array (size=1)
  'filename' => 
    object(Zend\Diactoros\UploadedFile)[966]
      private 'clientFilename' => string 'Untitled-1.png' (length=14)
      private 'clientMediaType' => string 'image/png' (length=9)
      private 'error' => int 0
      private 'file' => null
      private 'moved' => boolean false
      private 'size' => int 761087
      private 'stream' => 
        object(Zend\Diactoros\Stream)[967]
          protected 'resource' => resource(12, stream)
          protected 'stream' => string '/path/to/public/uploads/Untitled-1_5c51c49e1d0855_97287659.png' (length=103)

We can consume the file uploaded data from Zend\Diactoros\UploadedFile‘s functions, or we want a array data, we can loop the fields recursively and convert to array data whenever the value is instanceof Psr\Http\Message\UploadedFileInterface, eg:

use Psr\Http\Message\UploadedFileInterface;

// ...
$data = $form->getData();

\array_walk_recursive($data, function (& $value) {
    if ($value instanceof UploadedFileInterface) {
        $value = [
            'name'     => $value->getClientFilename(),
            'type'     => $value->getClientMediaType(),
            'size'     => $value->getSize(),
            'tmp_name' => $value->getStream()->getMetadata('uri'),
            'error'    => $value->getError(),
        ];
    }
});
// ...

The data are now completely an array so we are safe to use in another use case, eg: save to temporary session for File PRG use case.

Tinify : Resize your image without reduce quality

Posted in php, Teknologi, tips and tricks by samsonasik on February 3, 2019

I tried some client library services for my project to resize images. They resized images correctly, however, they made the images quality slightly reduced. Until my client suggested me to try Tinify API service, that the API key I can just use, and it works pretty well.

The Tinify provide free 500 compresions per-month if you need to try. You can upgrade to paid account if you need more. To register, you can go to https://tinypng.com/developers and got the following page and fill your name and email and click “Get your API key”:

You can then check your email to get the link and be redirected to the dashboard page with information of your API key and how many compressions left for current month:

There are libraries for different languages, for example, if you are using PHP, you can use tinify-php that you can require via composer in your project by run:

$ composer require tinify/tinify

Once required, you can do :

include 'vendor/autoload.php';

\Tinify\setKey('YOUR_API_KEY'); // fill with your API key

$source = \Tinify\fromFile('/path/to/file.png'); // source file
$resized = $source->resize(array(
    'method' => 'scale',
    'width'  => 500,
));
$resized->toFile('target-thumbnail.png'); // target file

For complete reference, you can look at https://tinypng.com/developers/reference/php . That’s it!

Using conflict in composer.json for BC Break handling on Optional feature

Posted in hack, php by samsonasik on January 11, 2019

You may have a PHP library that can work with multiple project skeletons with optional feature, so the dependencies are not required. For example, your composer.json library looks like:

{
    "name": "your/library",
    "require": {
        "php": "^7.1",
    },
    "require-dev": {
        "foo/bar": "^2.0",
        "phpunit/phpunit": "^7.0"
    },
    "suggest": {
        "foo/bar": "^2.0 usage via Your\\Library\\Adapter\\Foo\\Bar adapter"
    }
}

The “foo/bar” library is an optional library which used in “your/library” that consume it, collected in the require-dev above for testing purpose.

The “foo/bar” library has class “Foo\Bar\Way”. There are 2 versions of “foo/bar” lib, which version 1 and version 2 have different signature as follow:

a. “foo/bar” version 1 of “Foo\Bar\Way”

namespace Foo\Bar;

class Way
{
    public function execute(string $a, string $b)
    {
        // ...
    }
}

b. “foo/bar” version 2 of “Foo\Bar\Way”

namespace Foo\Bar;

use stdClass;

class Way
{
    public function execute(stdClass $std)
    {
        // ...
    }
}

In above code, if current user are using foo/bar:^1.0, the user code will be break when user upgrade “your/library” to version 2.0. So, to avoid that, you can restrict it via “conflict”, as follow:

{
    "name": "your/library",
    "require": {
        "php": "^7.1",
    },
    "require-dev": {
        "foo/bar": "^2.0",
        "phpunit/phpunit": "^7.0"
    },
    "suggest": {
        "foo/bar": "^2.0 usage via Your\\Library\\Adapter\\Foo\\Bar adapter"
    },
    "conflict": {
        "foo/bar": "<2.0"
    }
}

Now, you can tag “your/library” as “2.0” and user won’t be allowed to install new tagged library if they still uses “foo/bar”:”^1.0″ in existing project, That’s it!

Replace Hard Dependency Class with New Simulation Class via “replace” and “classmap” in Composer Configuration

Posted in hack, php by samsonasik on October 15, 2018

If we are using 3rd party library that managed by composer, which has hard dependency that we don’t want to use, for example, at the following use case:

The one of the solutions for that is by using “replace” and “classmap” configuration in our composer.json. First, we need to prepare of the class to simulate the Logger class, for example, we have it in src/App/Apache/Logger.php:

<?php
// src/App/Apache/Logger.php

class Logger 
{
    function debug(...$args) {}
    function info(...$args) {}
    function trace(...$args) {}
    function warn(...$args) {}
    function error(...$args) {}
    function fatal(...$args) {}

    public static function configure(...$args) {}
    public static function getLogger() { return new self(); }
}

Yes, above class doesn’t do anything, for silent action when Logger::{themethod()} called in realexpayments/rxp-remote-php library classes.

Next, we can register it to our composer.json:

{
    "require": {
        // ...
        "realexpayments/rxp-remote-php": "^1.2"
        // ...
    },
    "replace": {
        "apache/log4php": "^2.3.0"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/App/",
        },
        "classmap": [
            "src/App/Apache/Logger.php"
        ]
    }
}   

In above configuration, the replace of “apache/log4php” doesn’t has replacement library in ‘require’ part will make the dependency removed entirely as we don’t want to use it anymore, and by the classmap configuration, we have new redefined of the Logger class as simulation of “apache/log4php” Logger class.

Last step, we can run:

➜ composer update

That’s it!

Using zend-expressive-session-cache as PSR-6 session persistence adapter in Expressive 3

Posted in expressive, Tutorial PHP, Zend Framework by samsonasik on October 14, 2018

zend-expressive-session-cache is a PSR-6 session persistence adapter for zend-expressive-session that can be used in Expressive 3. In current post, I will provide the use case of it with apcu for cache adapter with zend-cache for cache item pool.

Let’s start with create new project from skeleton with the following command:

➜ composer create-project \
    zendframework/zend-expressive-skeleton \
    expressive3-cache-tutorial

After the skeleton installed, we can require the zendframework/zend-expressive-session-cache and zend-cache with the following command:

➜ cd expressive3-cache-tutorial
➜ composer require \
    zendframework/zend-expressive-session-cache \
    zendframework/zend-cache

Now, ensure config/config.php has the following ConfigProviders registered:

<?php
// config/config.php
$aggregator = new ConfigAggregator([
    // ...
    \Zend\Cache\ConfigProvider::class,
    \Zend\Expressive\Session\Cache\ConfigProvider::class,
    \Zend\Expressive\Session\ConfigProvider::class,
    // ...
], $cacheConfig['config_cache_path']);

For apcu adapter, we need the apcu extension to be installed, we can install via pecl like the following:

➜ sudo pecl install apcu

After it installed, we need to set apc.use_request_time = 0 in php.ini like the following:

# your php.ini
apc.use_request_time = 0

For Cache Item Pool service, we can create a factory for it, for example, like the following:

<?php
// src/App/Cache/CacheItemPoolFactory.php

declare(strict_types=1);

namespace App\Cache;

use Psr\Cache\CacheItemPoolInterface;
use Psr\Container\ContainerInterface;
use Zend\Cache\Psr\CacheItemPool\CacheItemPoolDecorator;
use Zend\Cache\StorageFactory;

final class CacheItemPoolFactory
{
    public function __invoke(ContainerInterface $container) : CacheItemPoolInterface
    {
        $storage = StorageFactory::factory([
            'adapter' => [
                'name'    => 'apcu',
            ],
        ]);

        return new CacheItemPoolDecorator($storage);
    }
}

We can register then the cache pool service and make alias of SessionPersistenceInterface with CacheSessionPersistence:

<?php
// config/autoload/dependencies.global.php
use App\Cache\CacheItemPoolFactory;
use Psr\Cache\CacheItemPoolInterface;
use Zend\Expressive\Session\Cache\CacheSessionPersistence;
use Zend\Expressive\Session\SessionPersistenceInterface;

return [
    // ...
    'aliases' => [
        SessionPersistenceInterface::class => CacheSessionPersistence::class,
    ],
    'factories'  => [
        CacheItemPoolInterface::class => CacheItemPoolFactory::class,
    ],
    // ...
];

If we need a custom configuration for zend-expressive-session-cache, we can define at config/autoload/zend-expressive.global.php:

<?php
// config/autoload/zend-expressive.global.php
use Psr\Cache\CacheItemPoolInterface;

return [
    // ...
    'zend-expressive-session-cache' => [
        'cache_item_pool_service' => CacheItemPoolInterface::class,
        'cookie_name'             => 'PHPSESSION',
        'cookie_path'             => '/',
        'cache_limiter'           => 'nocache',
        'cache_expire'            => 10800,
        'last_modified'           => null,
    ],
    // ...
];

To start the session, we can apply Zend\Expressive\Session\SessionMiddleware into config/pipeline.php before Zend\Expressive\Router\Middleware\RouteMiddleware registration:

<?php
use Zend\Expressive\Router\Middleware\RouteMiddleware;
use Zend\Expressive\Session\SessionMiddleware;

// ...
    $app->pipe(SessionMiddleware::class);
    $app->pipe(RouteMiddleware::class);
// ...

The preparation done! Now, we can use it by consume it from via SessionMiddleware, for example, we need to generate “csrf” token when rendering the form, like I written at the CSRF usage in Expressive post, we can just use the session as is, and the cache will be used as session persistence:

<?php
// src/Handler/LoginPageHandler.php
use Zend\Expressive\Session\SessionInterface;
use Zend\Expressive\Csrf\SessionCsrfGuard;
use Zend\Expressive\Session\SessionMiddleware;
use Zend\Expressive\Csrf\CsrfMiddleware;

    // ...
    private function getToken(SessionInterface $session, SessionCsrfGuard $guard)
    {
        if (! $session->has('__csrf')) {
            return $guard->generateToken();
        }

        return $session->get('__csrf');
    }

    public function process(
         ServerRequestInterface $request,
         RequestHandlerInterface $handler
    ) : ResponseInterface
    {
        $guard     = $request->getAttribute(CsrfMiddleware::GUARD_ATTRIBUTE);
        $loginForm = new LoginForm($guard);

        $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
        $token   = $this->getToken($session, $guard);

        // ...
    }
    // ...

That’s it!

Using “no-api” key on Github Forked Repository in Composer repositories configuration for Travis Build

Posted in hack, php by samsonasik on October 7, 2018

So, you’re doing a pull request, you want the feature to be merged so bad to public repository, but for whatever reason, it is not merged yet. You can register your forked repository under composer “repositories” config:

{
    "require": {
        "awesome/library": "dev-my-fork-awesome-feature"
    },
    "repositories" : [
        {
            "type" : "vcs",
            "url" : "https://github.com/yourgithubuser/library"
        }
    ]
}

That will work for your local dev and your server, but unfortunatelly, that won’t work on Travis! We will get the following error:

Failed to clone the git@github.com:yourgithubuser/library.git repository, try running in interactive mode so that you can enter your GitHub credentials
                                                                                                                                                                                      
  [RuntimeException]                                                                                                                                                                  
  Failed to execute git clone --mirror 'git@github.com:yourgithubuser/library.git' '/home/travis/.composer/cache/vcs/git-github.com-yourgithubuser-library.git/'  

To make that work, we will need the “no-api” key in your composer.json under the per-repository inside “repositories”, as follow:

// ...
        {
            "type" : "vcs",
            "url" : "https://github.com/yourgithubuser/library",
            "no-api": true
        }
// ...

Now, our composer.json will look like this:

{
    "require": {
        "awesome/library": "dev-my-fork-awesome-feature"
    },
    "repositories" : [
        {
            "type" : "vcs",
            "url" : "https://github.com/yourgithubuser/library",
            "no-api": true
        }
    ]
}

That’s it!

Create API Service in CodeIgniter 4 with Request Filtering

Posted in CodeIgniter 4, Teknologi, Tutorial PHP by samsonasik on September 22, 2018

In CodeIgniter 4, there is a trait that specialized to be used for API, named CodeIgniter\API\ResponseTrait for that, that consist of collection of functionality to build a response. How about request filtering ? We will need a filter class that implements CodeIgniter\Filters\FilterInterface interface.

For example, we are going to create a /api/ping api service, which will returns time value, we can create controller as follow:

<?php
// application/Controllers/Api/Ping.php
declare(strict_types=1);

namespace App\Controllers\Api;

use function time;

use CodeIgniter\API\ResponseTrait;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\ResponseInterface;

final class Ping extends Controller
{
    use ResponseTrait;

	public function index(): ResponseInterface
	{
        return $this->respond(['ack' => time()]);
    }
}

Without filtering, we can call url and got result like the following based on Accept header, for example, for application/xml, it will get:

➜  ~ curl -i -H "Accept: application/xml" http://localhost:8080/api/ping
HTTP/1.1 200 OK
Host: localhost:8080
Date: Sat, 22 Sep 2018 01:11:30 -0500
Connection: close
X-Powered-By: PHP/7.2.9
Cache-control: no-store, max-age=0, no-cache
Content-Type: application/xml; charset=UTF-8
Debugbar-Time: 1537596690
Debugbar-Link: http://localhost:8080/index.php/?debugbar_time=1537596690

<?xml version="1.0"?>
<response><ack>1537596690</ack></response>

and when it changed to application/json, it will get:

➜  ~ curl -i -H "Accept: application/json" http://localhost:8080/api/ping
HTTP/1.1 200 OK
Host: localhost:8080
Date: Sat, 22 Sep 2018 01:11:53 -0500
Connection: close
X-Powered-By: PHP/7.2.9
Cache-control: no-store, max-age=0, no-cache
Content-Type: application/json; charset=UTF-8
Debugbar-Time: 1537596713
Debugbar-Link: http://localhost:8080/index.php/?debugbar_time=1537596713

{
    "ack": 1537596713
}

Let’s try create filter it to only allow the “POST” request! We can create filter as follow:

<?php
// application/Filters/PostRequestOnly.php
declare(strict_types=1);

namespace App\Filters;

use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;

final class PostRequestOnly implements FilterInterface
{
	public function before(RequestInterface $request)
	{
        if ($request->getMethod() === 'post') {
            return;
        }

        return Services::response()
            ->setStatusCode(ResponseInterface::HTTP_METHOD_NOT_ALLOWED);
    }

	public function after(RequestInterface $request, ResponseInterface $response)
	{
	}
}

In above PostRequestOnly filter, we are allowing request only for “POST”, other request methods will get response with “Method Not Allowed” (405). To make it work, we need register it into Config\Filters::$aliases class under application directory, and ensure it applied into Config\Filters::$filters to register specific uri for the filter, as follow:

<?php
// application/Config/Filters.php

// ...
use App\Filters\PostRequestOnly;

class Filters extends BaseConfig
{
	public $aliases = [
        // ...
        'postRequestOnly' => PostRequestOnly::class,
    ];

    // ...

	public $filters = [
        // ...
        'postRequestOnly' => [
            'before' => [
                'api/ping',
            ],
        ],
    ];
}

That’s it! Now, when we try to see different result with GET and POST, it will get the following response:

GET: Method Not Allowed 405

➜  ~ curl -i -H "Accept: application/json" -X GET http://localhost:8080/api/ping
HTTP/1.1 405 Method Not Allowed
Host: localhost:8080
Date: Sat, 22 Sep 2018 13:12:22 +0700
Connection: close
X-Powered-By: PHP/7.2.9
Cache-control: no-store, max-age=0, no-cache
Content-Type: text/html; charset=UTF-8

POST: Got Response OK 200

➜  ~ curl -i -H "Accept: application/json" -X POST http://localhost:8080/api/ping
HTTP/1.1 200 OK
Host: localhost:8080
Date: Sat, 22 Sep 2018 01:12:54 -0500
Connection: close
X-Powered-By: PHP/7.2.9
Cache-control: no-store, max-age=0, no-cache
Content-Type: application/json; charset=UTF-8
Debugbar-Time: 1537596774
Debugbar-Link: http://localhost:8080/index.php/?debugbar_time=1537596774

{
    "ack": 1537596774
}

Upload File with Validation in CodeIgniter 4

Posted in CodeIgniter 4, Tutorial PHP by samsonasik on September 5, 2018

In CodeIgniter 4, there is CodeIgniter\Validation\FileRules that can be used out of the box from validate() from controllers that we can using it on upload process.

For example, we have an upload form page with flow as follow:

  1. Display form at /form page with “avatar” file field
  2. Process form at /form/process with validations:
    • is uploaded
    • is mime in image/jpg,image/jpeg,image/gif,image/png
    • the max size is less or equal than 4MB
  3. On valid upload, we move uploaded file to writable/uploads directory.

Before we start, please ensure that you’re using latest CodeIgniter4 develop branch by clone it:

$ git clone https://github.com/bcit-ci/CodeIgniter4.git

If you already have it, ensure to pull latest develop branch via command:

$ cd CodeIgniter4
$ git checkout develop
$ git remote add upstream https://github.com/bcit-ci/CodeIgniter4.git
$ git pull upstream develop

Most of the time, the updated code in the CodeIgniter4 repository will be core framework (system) and its tests, so, if we need to be always up to date framework, we can create new branch based on develop branch, for example:

$ git checkout -b tutorial-upload

Then, we can regularly rebase against develop :

$ git checkout develop && git pull upstream develop
$ git checkout tutorial-upload && git rebase develop

Enough with introduction, let’s start with code!

1. Display Form

<?php
// application/Views/form.php
$validationErrors = $this->config->plugins['validation_errors'];

helper('form');
echo form_open(
    'form/process',
    [
        'enctype' => 'multipart/form-data'
    ]
);

echo form_input('avatar', '', '', 'file');
echo $validationErrors(['field' => 'avatar']);

echo form_submit('Send', 'Send');
echo form_close();

Above, we display form with form helpers, and display validation errors by get it in validation_errors key in plugins.

To make it a form page, we can create a Form controller as follow:

<?php
// application/Controllers/Form.php
namespace App\Controllers;

use CodeIgniter\Controller;

class Form extends Controller
{
    public function index()
    {
        return view('form');
    }
}

To see the page, let’s fire a command:

$ php spark serve

The form page is now displayed at http://localhost:8080/form or http://localhost:8080/form/index like below:

2. Upload validation

We are going to use “form/process” page for file upload validation, so, we can create an process() function at the Form controller:

class Form extends Controller
{
    // ...
    public function process()
    {
        // verify if request method is POST
        if ($this->request->getMethod() !== 'post') {
            return redirect('index');
        }

        // validation
        $validated = $this->validate([
            'avatar' => [
                'uploaded[avatar]',
                'mime_in[avatar,image/jpg,image/jpeg,image/gif,image/png]',
                'max_size[avatar,4096]',
            ],
        ]);

        if ($validated) {
            // Business!
        }

        return redirect('index')->withInput();
    }
}

On above process() function, the validate() call supply the rules of avatar field needed, which are uploaded, mime_in, and max_size which we can found as functions at CodeIgniter\Validation\FileRules. When it is valid, it will returns true. We redirect back to /form/index with bring validation errors that saved in session flash with key _ci_validation_errors to be allowed to be displayed once in the next request like below:

3. Move uploaded file to writable/uploads directory

We have a WRITEPATH constant that refers to writable directory, and we can use its “$request->getFile(‘avatar’)->move()” to it, so when we have validated, we can do:

if ($validated) {
    $avatar = $this->request->getFile('avatar');
    $avatar->move(WRITEPATH . 'uploads');
}

That’s it! The complete Form controller class can be as follow:

<?php
// application/Controllers/Form.php
namespace App\Controllers;

use CodeIgniter\Controller;

class Form extends Controller
{
    public function index()
    {
        return view('form');
    }

    public function process()
    {
        if ($this->request->getMethod() !== 'post') {
            return redirect('index');
        }

        $validated = $this->validate([
            'avatar' => [
                'uploaded[avatar]',
                'mime_in[avatar,image/jpg,image/jpeg,image/gif,image/png]',
                'max_size[avatar,4096]',
            ],
        ]);

        if ($validated) {
            $avatar = $this->request->getFile('avatar');
            $avatar->move(WRITEPATH . 'uploads');
        }

        return redirect('index')->withInput();
    }
}

Create Middleware for File Post/Redirect/Get in Expressive 3

Posted in expressive, Teknologi, Tutorial PHP, Zend Framework by samsonasik on July 31, 2018

Previously, I wrote a post about Post/Redirect/Get in Expressive 3 which specifically handle POST parsed body. How about File upload? Let’s do it!

In this post, I will do with Zend\Form component.

Let’s start by install a fresh skeleton:

$ composer create-project \
     zendframework/zend-expressive-skeleton \
     expressive-fileprg-tutorial

To save temporary the form data and its invalid input error messages during redirect, we can use session, so, we can require session components:

$ cd expressive-fileprg-tutorial
$ composer require \
     zendframework/zend-expressive-session:^1.0.0 \
     zendframework/zend-expressive-session-ext:^1.1.1

After above components installed, ensure that your config/config.php injected with ConfigProvider like below:

<?php
// config/config.php
$aggregator = new ConfigAggregator([
    // session requirements
    \Zend\Expressive\Session\Ext\ConfigProvider::class,
    \Zend\Expressive\Session\ConfigProvider::class,
    // ...
]);

The session middleware from zend-expressive-session component need to be registered in our config/pipeline.php before RouteMiddleware:

// config/pipeline.php
// ...
use Zend\Expressive\Session\SessionMiddleware;

// ...
$app->pipe(SessionMiddleware::class);
$app->pipe(RouteMiddleware::class);

Next, we are going to require a form dependencies for it with a command:

$ composer require \
     zendframework/zend-form:^2.11 \
     zendframework/zend-i18n:^2.7

After above components installed, ensure that your config/config.php have sessions and form ConfigProviders:

<?php
// config/config.php
$aggregator = new ConfigAggregator([
    // session requirements
    \Zend\Expressive\Session\Ext\ConfigProvider::class,
    \Zend\Expressive\Session\ConfigProvider::class,
    // ...

    // form requirements
    \Zend\I18n\ConfigProvider::class,
    \Zend\Form\ConfigProvider::class,
    \Zend\InputFilter\ConfigProvider::class,
    \Zend\Filter\ConfigProvider::class,
    \Zend\Hydrator\ConfigProvider::class,
]);

The New Middleware

We are going to create a middleware for our application, for example, we name it FilePrgMiddleware, placed at src/App/Middleware. I will explain part by part.

Unlike the normal PRG middleware in previous post, the FilePrgMiddleware need to bring the Zend\Form\Form object to be filtered and validated, so, it will be applied in the routes after the Page handler registration.

First, we check whether the request method is POST and has the media type is “multipart/form-data”, then applied POST and FILES data into the form instance. We can save form post and file data, a form filtered/validated data (“form->getData()”), and its form error messages into session. Next, we redirect to current page with status code = 303.

As I explored, the easiest way to work with zend-form for post and files data is by using zend-psr7bridge for it like below:

use Zend\Diactoros\Response\RedirectResponse;
use Zend\Expressive\Session\SessionMiddleware;
use Zend\Psr7Bridge\Psr7ServerRequest;

$session     = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);

$zendRequest        = Psr7ServerRequest::toZend($request);
$zendRequestHeaders = $zendRequest->getHeaders();
$isMultiPart        = $zendRequestHeaders->has('Content-type')
    ? $zendRequestHeaders->get('Content-type')->getMediaType() === 'multipart/form-data'
    : false;

if ($request->getMethod() === 'POST' && $isMultiPart === true) {
    $postAndFileData = \array_merge_recursive(
        $zendRequest->getPost()->toArray(),
        $zendRequest->getFiles()->toArray()
    );

    $session->set('post_and_file_data', $postAndFileData);
    $form->setData($postAndFileData);

    if ($form->isValid()) {
        $session->set('form_data', $form->getData());
    }

    if ($messages = $form->getMessages()) {
        $session->set('form_errors', $messages);
    }

    return new RedirectResponse($request->getUri(), 303);
}

On next flow, we can check if the session has “post_and_file_data” key, set form error messages when session has “form_errors” key, and return Response.

if ($session->has('post_and_file_data')) {
    $form->setData($session->get('post_and_file_data'));
    $session->unset('post_and_file_data');

    if ($session->has('form_errors')) {
        $form->setMessages($session->get('form_errors'));
        $session->unset('form_errors');
    }

    return new Response();
}

Above, we didn’t use the “form_data” session as the form data may be used in the page handler, so, we can handle it with keep the “form_data” as “form->getData()” result once by return Response above, and remove it when it already hit in next refresh, so next flow can be:

if ($session->has('form_data')) {
    $session->unset('form_data');
}

return new Response();

The complete middleware class can be as follow:

<?php
// src/App/Middleware/FilePrgMiddleware.php
declare(strict_types=1);

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response;
use Zend\Diactoros\Response\RedirectResponse;
use Zend\Expressive\Session\SessionMiddleware;
use Zend\Psr7Bridge\Psr7ServerRequest;

class FilePrgMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ) : ResponseInterface
    {
        $session     = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);

        $zendRequest        = Psr7ServerRequest::toZend($request);
        $zendRequestHeaders = $zendRequest->getHeaders();
        $isMultiPart        = $zendRequestHeaders->has('Content-type')
            ? $zendRequestHeaders->get('Content-type')->getMediaType() === 'multipart/form-data'
            : false;

        $form = $request->getAttribute('form');
        if ($request->getMethod() === 'POST' && $isMultiPart === true) {
            $postAndFileData = \array_merge_recursive(
                $zendRequest->getPost()->toArray(),
                $zendRequest->getFiles()->toArray()
            );

            $session->set('post_and_file_data', $postAndFileData);
            $form->setData($postAndFileData);

            if ($form->isValid()) {
                $session->set('form_data', $form->getData());
            }

            if ($messages = $form->getMessages()) {
                $session->set('form_errors', $messages);
            }

            return new RedirectResponse($request->getUri(), 303);
        }

        if ($session->has('post_and_file_data')) {
            $form->setData($session->get('post_and_file_data'));
            $session->unset('post_and_file_data');

            if ($session->has('form_errors')) {
                $form->setMessages($session->get('form_errors'));
                $session->unset('form_errors');
            }

            return new Response();
        }

        if ($session->has('form_data')) {
            $session->unset('form_data');
        }

        return new Response();
    }
}

FilePrgMiddleware Service Registration

We can register the FilePrgMiddleware at src/App/ConfigProvider under getDependencies() function:

// src/App/ConfigProvider.php
use Zend\ServiceManager\Factory\InvokableFactory;

    // ...
    public function getDependencies() : array
    {
        return [
            'factories'  => [
                // ...
                Middleware\FilePrgMiddleware::class => InvokableFactory::class,
            ],
        ];
    }

The Upload Form and Its Page

Time for usage. First, we can create an upload form, for example like below:

<?php

namespace App\Form;

use Zend\Form\Element\File;
use Zend\Form\Element\Submit;
use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;
use Zend\Validator\File\MimeType;
use Zend\Validator\File\Size;
use Zend\Filter\File\RenameUpload;

class UploadForm extends Form implements InputFilterProviderInterface
{
    public function __construct()
    {
        parent::__construct('upload-form');

        $this->init();
    }

    public function init()
    {
        $this->add([
            'type' => File::class,
            'name' => 'filename',
            'options' => [
                'label' => 'File upload',
            ],
        ]);

        $this->add([
            'name' => 'submit',
            'type' => Submit::class,
            'attributes' => [
                'value' => 'Submit',
            ],
        ]);
    }

    public function getInputFilterSpecification()
    {
        return [
            [
                'name' => 'filename',
                'required' => true,
                'filters' => [
                    [
                        'name' => RenameUpload::class,
                        'options' => [
                            'target'               => \getcwd() . '/public/uploads',
                            'use_upload_extension' => true,
                            'use_upload_name'      => true,
                            'overwrite'            => true,
                            'randomize'            => false,
                        ],
                    ],
                ],
                'validators' => [
                    [
                        'name' => Size::class,
                        'options' => [
                            'max' => '10MB',
                        ],
                    ],
                    [
                        'name' => MimeType::class,
                        'options' => [
                            'mimeType' => [
                                'image/jpg',
                                'image/jpeg',
                                'image/png',
                            ],
                        ]
                     ]
                ],
            ],
        ];
    }
}

To ensure the file upload correctly, I applied filter RenameUpload to move it to public/uploads directory. We can create an uploads directory by run command:

$ mkdir -p public/uploads && chmod 755 public/uploads

Next, we can create an Upload Page Handler :

<?php

declare(strict_types=1);

namespace App\Handler;

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

class UploadPageHandler  implements MiddlewareInterface
{
    private $template;

    public function __construct(Template\TemplateRendererInterface $template)
    {
        $this->template      = $template;
    }

    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ) : ResponseInterface
    {
        $data = [];
        return new HtmlResponse($this->template->render('app::upload-page', $data));
    }
}

We need template in templates/app/upload-page.phtml for it. For view, we can create a file:

$ touch templates/app/upload-page.phtml

and write with the form helper:

<?php // templates/app/upload-page.phtml

echo $this->form($form);

As above UploadPageHandler require an TemplateRendererInterface service, we need factory for it, as follow:

<?php

declare(strict_types=1);

namespace App\Handler;

use Psr\Container\ContainerInterface;
use Psr\Http\Server\MiddlewareInterface;
use Zend\Expressive\Template\TemplateRendererInterface;

class UploadPageHandlerFactory
{
    public function __invoke(ContainerInterface $container) : MiddlewareInterface
    {
        $template = $container->get(TemplateRendererInterface::class);
        return new UploadPageHandler($template);
    }
}

We can register the UploadPageHandler at src/App/ConfigProvider under getDependencies() function:

// src/App/ConfigProvider.php
use Zend\ServiceManager\Factory\InvokableFactory;

    // ...
    public function getDependencies() : array
    {
        return [
            'factories'  => [
                // ...
                Handler\UploadPageHandler::class => Handler\UploadPageHandlerFactory::class,
                // ...
            ],
        ];
    }

To make it accessible, we need to register its routing, for example, at config/routes.php:

return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
    // ...
    $app->route(
        '/upload',
        [
            App\Handler\UploadPageHandler::class,
            App\Middleware\FilePrgMiddleware::class,
        ],
        [
            'GET',
            'POST'
        ],
        'upload'
    );
    // ...
};

So, we will get the following display when access “/upload” page:

Using The FilePrg in UploadPageHandler

We can set the Zend\Form\Form instance into request attribute and call it handler, on form is valid, we can apply after its response check not a RedirectResponse.

use App\Form\UploadForm;
use Zend\Expressive\Session\SessionMiddleware;

// ...
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ) : ResponseInterface
    {
        $form = new UploadForm();

        $request = $request->withAttribute('form', $form);
        $response = $handler->handle($request);
        if ($response instanceof RedirectResponse) {
            return $response;
        }

        $session     = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
        if ($session->has('form_data')) {
            $formData = $session->get('form_data');
            // we can returns RedirectResponse to "success upload" page,
            // process form data,
            // set view variable to allow display that the upload success,
            // etc
        }

        $data = ['form' => $form];
        return new HtmlResponse($this->template->render('app::upload-page', $data));
    }
// ...

When we get invalid input, we will get the following form page like below:

When we refresh it, it will normal refresh and won’t call a duplicate form submission.

That’s it ;).

Tagged with: ,

Create Middleware for Post/Redirect/Get in Expressive 3

Posted in expressive, Zend Framework by samsonasik on February 13, 2018

Yesterday, we already explore about CSRF usage in Expressive 3 using zend-expressive-csrf component, which I gave the sample inside a Login Page which utilize both GET and POST in single page, which we need to tweak the csrf regeneration to ensure next retry will use newly regenerated token. As I noted in the last paragraph, the better usage is by using PRG ( Post/Redirect/Get ) or delegate to separate middleware and redirect back to its page. Today, we will explore how to create Post/Redirect/Get Middleware for Expressive 3.

Requirements

If you already followed my 4 previous expressive posts, all requirements already applied.

The New Middleware

We are going to create a middleware for our application, for example, we name it PrgMiddleware, placed at src/App/Middleware. I will explain part by part.

First, we check whether the request method is POST, then save the POST data into session with new key, eg: ‘post_data’, then redirect to current page with status code = 303.

use Zend\Diactoros\Response\RedirectResponse;
use Zend\Expressive\Session\SessionMiddleware;
// ...
    $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
    if ($request->getMethod() === 'POST') {
        $session->set('post_data', $request->getParsedBody());
        return new RedirectResponse($request->getUri(), 303);
    }
    // ...

On next flow, we can check if the session has ‘post_data’ key:

    if ($session->has('post_data')) {
        $post = $session->get('post_data');
        $session->unset('post_data');

        $request = $request->withMethod('POST');
        $request = $request->withParsedBody($post);
    }
    // ...

As in authentication process, we use Zend\Expressive\Authentication\Session\PhpSession::authenticate() which require request method to be POST and use its parsed body to be used for authentication, we need to set method to POST with parsed body of saved session with key ‘post_data’ which immediately removed above.

Lastly, we return the $handler->handle($request) as whenever response it used:

    return $handler->handle($request);

The complete middleware class can be as follows:

<?php
// src/App/Middleware/PrgMiddleware.php
declare(strict_types=1);

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\RedirectResponse;
use Zend\Expressive\Session\SessionMiddleware;

class PrgMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);

        if ($request->getMethod() === 'POST') {
            $session->set('post_data', $request->getParsedBody());
            return new RedirectResponse($request->getUri(), 303);
        }

        if ($session->has('post_data')) {
            $post = $session->get('post_data');
            $session->unset('post_data');

            $request = $request->withMethod('POST');
            $request = $request->withParsedBody($post);
        }

        return $handler->handle($request);
    }
}

PrgMiddleware Service Registration

We can register the PrgMiddleware at src/App/ConfigProvider under getDependencies() function:

// src/App/ConfigProvider.php
use Zend\ServiceManager\Factory\InvokableFactory;

    // ...
    public function getDependencies() : array
    {
        return [
            'factories'  => [
                // ...
                Middleware\PrgMiddleware::class => InvokableFactory::class,
            ],
        ];
    }

Now, we can register it inside config/routes.php before LoginPageHandler:

// config/routes.php
$app->route('/login', [
    // csrf
    \Zend\Expressive\Csrf\CsrfMiddleware::class,

    // prg middleware
    App\Middleware\PrgMiddleware::class,

    // login page
    App\Handler\LoginPageHandler::class,

    // authentication middleware
    \Zend\Expressive\Authentication\AuthenticationMiddleware::class,
], ['GET', 'POST'],'login');

Using The Prg in LoginPageHandler

We can check if it is a PRG using the parsed body and use it to fill the form data:

// src/App/Handler/LoginPageHandler.php
    // ...
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        $guard     = $request->getAttribute(CsrfMiddleware::GUARD_ATTRIBUTE);
        $loginForm = new LoginForm($guard);

        $prg = $request->getParsedBody();
        if ($prg) {
            $loginForm->setData($prg);
            if ($loginForm->isValid()) {
                // ...
            }
        }
    }

By above, we just need to generate the token once. The complete LoginPageHandler can be as follow:

<?php
// src/App/Handler/LoginPageHandler.php
declare(strict_types=1);

namespace App\Handler;

use App\Form\LoginForm;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\RedirectResponse;
use Zend\Expressive\Csrf\CsrfMiddleware;
use Zend\Expressive\Flash\FlashMessageMiddleware;
use Zend\Expressive\Session\SessionMiddleware;
use Zend\Expressive\Template\TemplateRendererInterface;

class LoginPageHandler implements MiddlewareInterface
{
    private $template;

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

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        $guard     = $request->getAttribute(CsrfMiddleware::GUARD_ATTRIBUTE);
        $loginForm = new LoginForm($guard);

        $prg = $request->getParsedBody();
        if ($prg) {
            $loginForm->setData($prg);
            if ($loginForm->isValid()) {
                $response = $handler->handle($request);

                $flashMessages = $request->getAttribute(FlashMessageMiddleware::FLASH_ATTRIBUTE);
                if ($response->getStatusCode() !== 302) {
                    $flashMessages->flash('message', 'You are succesfully authenticated');
                    return new RedirectResponse('/');
                }

                $flashMessages->flash('message', 'Login Failure, please try again');
                return new RedirectResponse('/login');
            }
        }

        $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
        $token   = $guard->generateToken();

        return new HtmlResponse(
            $this->template->render('app::login-page', [
                'form'  => $loginForm,
                'token' => $token,
            ])
        );
    }
}

In view, we can just show the form:

<?php // templates/app/login-page.phtml

$form->get('csrf')->setValue($token);
$form->prepare();
echo $this->form($form);

Done 😉

Using zend-expressive-csrf with zend-form in Expressive 3

Posted in expressive, Tutorial PHP, Zend Framework by samsonasik on February 13, 2018

If you followed my post about authentication and authorization posts with Expressive 3, this time, I write another session related post for securing request, which uses zend-expressive-csrf with zend-form.

Setup

We already installed session and form components, so, we can just require the zend-expressive-csrf component via command:

$ composer require \
    zendframework/zend-expressive-csrf:^1.0.0alpha1

On Login route, we register the csrf middleware before the LoginPageHandler:

// config/routes.php
$app->route('/login', [
    // csrf middleware
    \Zend\Expressive\Csrf\CsrfMiddleware::class,

    App\Handler\LoginPageHandler::class,
    \Zend\Expressive\Authentication\AuthenticationMiddleware::class,
], ['GET', 'POST'],'login');

Csrf Validation in Form

We can inject the LoginForm with Zend\Expressive\Csrf\SessionCsrfGuard instance which next we use it to validate csrf token.

// src/App/Form/LoginForm.php
use Zend\Expressive\Csrf\SessionCsrfGuard;

    // ...
    private $guard;

    public function __construct(SessionCsrfGuard $guard)
    {
        parent::__construct('login-form');
        $this->guard = $guard;

        $this->init();
    }
    // ...

In above __construct(), I call init() immediatelly on __construct() to add form elements on form creation, as we are going to inject the Form with Zend\Expressive\Csrf\SessionCsrfGuard instance on LoginPageHandler which pulled from request object.

We then can add new element, for example, named: csrf as hidden input, as follows:

// src/App/Form/LoginForm.php
use Zend\Form\Element\Hidden;

    // ...
    public function init()
    {
        $this->add([
            'type'  => Hidden::class,
            'name'  => 'csrf',
        ]);
        //...
    }
    // ...

To validate it, we register the csrf validation token by Zend\Expressive\Csrf\SessionCsrfGuard in getInputFilterSpecification():

// src/App/Form/LoginForm.php
    // ...
    public function getInputFilterSpecification()
    {
        return [
            [
                'name' => 'csrf',
                'required' => true,
                'validators' => [
                    [
                        'name' => 'callback',
                        'options' => [
                            'callback' => function ($value) {
                                return $this->guard->validateToken($value);
                            },
                            'messages' => [
                                'callbackValue' => 'The form submitted did not originate from the expected site'
                            ],
                        ],
                    ]
                ],
            ],
            // ...
        ];
    }

Above, we supply a callback validator (you can create a special validator just for it) with call the Zend\Expressive\Csrf\SessionCsrfGuard::validateToken($value) which returns true when valid, and false when invalid. On invalid token, we will get callbackValue message key which we can customize its value.

The LoginPageHandler

As the Zend\Expressive\Csrf\SessionCsrfGuard instance will be injected at the LoginPageHandler itself, we can remove the LoginForm from LoginPageHandler dependency, so, we just need to have TemplateRendererInterface:

// src/Handler/LoginPageHandler.php
    // ...
    public function __construct(TemplateRendererInterface $template)
    {
        $this->template  = $template;
    }
    // ...

The factory for it will remove LoginForm dependency as well, so just the template:

// src/Handler/LoginPageFactory.php
    // ...
    public function __invoke(ContainerInterface $container) : MiddlewareInterface
    {
        $template  = $container->get(TemplateRendererInterface::class);
        return new LoginPageHandler($template);
    }

As the token csrf is a one time token, and we use a single page for both GET (show form) and POST (authenticate), we can create a function to get generated token to be called before POST method check, and when authentication failure or the form is invalid to ensure next retry will use newly generated token:

// src/Handler/LoginPageHandler.php
use Zend\Expressive\Session\SessionInterface;
use Zend\Expressive\Csrf\SessionCsrfGuard;

    // ...
    private function getToken(SessionInterface $session, SessionCsrfGuard $guard)
    {
        if (! $session->has('__csrf')) {
            return $guard->generateToken();
        }

        return $session->get('__csrf');
    }
    // ...

Now, the LoginForm and the token can be created during process() method:

// src/Handler/LoginPageHandler.php
use Zend\Expressive\Session\SessionMiddleware;
use Zend\Expressive\Csrf\CsrfMiddleware;
use Zend\Expressive\Csrf\SessionCsrfGuard;

    // ...
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        $guard     = $request->getAttribute(CsrfMiddleware::GUARD_ATTRIBUTE);
        $loginForm = new LoginForm($guard);

        $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
        $token   = $this->getToken($session, $guard);

        // ...
    }

On after form is valid check, the $token need to be re-generated:

// src/Handler/LoginPageHandler.php

    // ...
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        // ...
        if ($loginForm->isValid()) {
            // ...
        }

        // re-new token on failure login or not valid form
        $token   = $this->getToken($session, $guard);
    }

The $token will be consumed by view and can be set via template render:

// src/Handler/LoginPageHandler.php

    // ...
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        // ...
        return new HtmlResponse(
            $this->template->render('app::login-page', [
                'form'  => $loginForm,
                'error' => $error,
                'token' => $token,
            ])
        );
    }

The View

Finally, we can set the csrf element value in view via form object, as follows:

<?php // templates/app/login-page.phtml
echo $error;

$form->get('csrf')->setValue($token);
$form->prepare();

echo $this->form($form);

All done, now, when the request is not using the session generated token, it will show form error:

The form submitted did not originate from the expected site

like the following screenshot:

Better Practice and Possible Refactor

For real application, it is better to use PRG or use another handler to handle it to be redirected back to the form when failure, so you don’t need to tweak the token regeneration as when form re-displayed again, it already in next request and we can just use the new token. I’ve written new post for create middleware for Post/Redirect/Get in Expressive 3 for it.

Using View Helper for Accessing zend-expressive-flash Messages in Expressive 3

Posted in expressive, Zend Framework by samsonasik on February 11, 2018

If you already tried zend-expressive 3.0.0alpha3, and want to use flash message for it, you can use the new component: “zendframework/zend-expressive-flash”.

Setup

We need to require the component and its dependencies:

$ composer require
    zendframework/zend-expressive-session:^1.0.0 \
    zendframework/zend-expressive-session-ext:^1.0.0 \
    zendframework/zend-expressive-flash:^1.0.0

After it, we can ensure that the ConfigProvider for above components registered at config/config.php:

$aggregator = new ConfigAggregator([
    // ...
    \Zend\Expressive\Session\ConfigProvider::class,
    \Zend\Expressive\Session\Ext\ConfigProvider::class,
    \Zend\Expressive\Flash\ConfigProvider::class,
    // ...
]);

To make the flash messages available in all pages, we need to register it in the pipeline before RouteMiddleware piping:

use Zend\Expressive\Flash\FlashMessageMiddleware;
use Zend\Expressive\Session\SessionMiddleware;
// ...

    $app->pipe(SessionMiddleware::class);
    $app->pipe(FlashMessageMiddleware::class);

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

Get the Messages via View Helper

Most of the use cases, we are displaying the flash messages in the layout. If we are using zend-view for the template engine, we can create view helper to access the flash messages. What we need is a Zend\Expressive\Flash\FlashMessages instance with passes Zend\Expressive\Session\Session which passes $_SESSION into it, as follows:

use Zend\Expressive\Session\Session;
use Zend\Expressive\Flash\FlashMessages;

// ...
$flashMessages = FlashMessages::createFromSession(new Session($_SESSION));

The messages is a private property “currentMessages” which currently no public function that returns it. We can wait for the PR for it to be merge or use it via ReflectionProperty or Closure

which can be called by method:

$flashMessages->getFlashes();

Knowing that, we can create the view helper as follows:

<?php
// src/App/View\Helper\Flash.php

declare(strict_types=1);

namespace App\View\Helper;

use Zend\Expressive\Session\Session;
use Zend\Expressive\Flash\FlashMessages;
use Zend\View\Helper\AbstractHelper;

class Flash extends AbstractHelper
{
    public function __invoke() : array
    {
        $flashMessages = FlashMessages::createFromSession(
            new Session($_SESSION)
        );

        return $flashMessages->getFlashes();
    }
}

and register it into view_helpers config at src/App/ConfigProvider::__invoke():

// ...
    public function __invoke() : array
    {
        return [
            'dependencies' => $this->getDependencies(),
            'templates'    => $this->getTemplates(),

            // register additional view helpers
            'view_helpers' => [
                'invokables' => [
                    'flash' => View\Helper\Flash::class,
                ],
            ],
        ];
    }
// ...

Accessing Flash Messages in Layout

In layout, we can now access it via invoked flash view helper with loop the messages to be displayed:

<?php if ($flashes = $this->flash()) :?>
    <ul>
        <?php foreach($flashes as $message) : ?>
            <li><?php echo $message; ?></li>
        <?php endforeach; ?>
    </ul>
<?php endif; ?>

Give it a try

You can now create a flash messages in some page and access it in next request in the layout, check the documentation for its usage!

Introduce IsDeprecated: PHP7+ Helper for E_USER_DEPRECATED and E_DEPRECATED Detection

Posted in hack, php, Teknologi by samsonasik on January 18, 2018

Before I continue, allow me to give you my suggestions:

Live with deprecated function is bad, you may can’t upgrade to newer PHP version or newer library as the function that was deprecated already removed in next major/minor version.

Use same environment or at least same major and minor version between local dev and production environment when possible, your life will be easier.

When reality is not always what you want

You may found a a reality when you work at different version of PHP version or library that rely on specific function which already deprecated in some environment, the IsDeprecated may be solution at that time that can verify E_USER_DEPRECATED or E_DEPRECATED trigger error. It utilize jeremeamia/FunctionParser for user defined function check, and zendframework/zend-stdlib’s ErrorHandler for E_DEPRECATED function check.

When you found the passed function is deprecated, you can use alternative function.

Installation

This helper can be installed via composer:

composer require samsonasik/is-deprecated

This helper have features:

1. Detect E_USER_DEPRECATED

  • At independent function
  • At function inside class

You can use IsDeprecated\isDeprecatedUser function with signature:

/**
 * @param  string|array $function the "functionName" or ["ClassName" or object, "functionName"] or "ClassName::functionName"
 * @throws InvalidArgumentException when trigger_error found but the error is not E_USER_DEPRECATED
 * @throws InvalidArgumentException when trigger_error and E_USER_DEPRECATED found but misplaced
 * @return bool
 */
function isDeprecatedUser($function): bool

Note: when trigger_error E_USER_DEPRECATED inside condition, you need to use actual call with signature:

/**
 * @param  callable $function callable function
 * @return bool
 */
function isDeprecatedWithActualCall(callable $function)

1a. Independent Function

The usage is like the following:

use function IsDeprecated\isDeprecatedUser;

function foo()
{
    trigger_error('this method has been deprecated.', E_USER_DEPRECATED);
}

if (isDeprecatedUser('foo')) {
    // apply alternative/new function to call...
} else {
    foo();
}

1.b Function Inside Class

The usage is like the following:

use function IsDeprecated\isDeprecatedUser;

class AClass
{
    public function foo()
    {
        trigger_error('this method has been deprecated.', E_USER_DEPRECATED);
    }

    // check inside with $this
    public function execute()
    {
        if (isDeprecatedUser([$this, 'foo'])) {
             // apply alternative/new function to call...
             return;
        }

        $this->foo();
    }
}

// you may call after instantiation
$object = new \AClass();
if (isDeprecatedUser([$object, 'foo'])) {
    // apply alternative/new function to call...
} else {
    $object->foo();
}

// or
if (isDeprecatedUser(['AClass', 'foo'])) {
    // apply alternative/new function to call...
} else {
    (new \AClass())->foo();
}

2. Detect E_DEPRECATED

E_DEPRECATED can be triggered on Core PHP function call.

You can use IsDeprecated\isDeprecatedCore function with signature:

/**
 * @param  callable $function callable function
 * @return bool
 */
function isDeprecatedCore(callable $function): bool

The usage is like the following:

use function IsDeprecated\isDeprecatedCore;

$function = function () {
    mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
};

if (isDeprecatedCore($function)) {
    // alternative function, eg: openssl ...
} else {
    $function();
}

Limitation

For Core PHP Functions or user function with condition (T_IF or T_SWITCH token), the function passed actually need to be called. It ensure that we don’t get error during call deprecated function, and we can use alternative function if the isDeprecatedCore() returns true with call of isDeprecatedWithActualCall.

You want to use it? You can check my repository https://github.com/samsonasik/IsDeprecated

That’s it ;).

Create Authorization functionality in Expressive 3

Posted in expressive, Zend Framework by samsonasik on January 13, 2018

So, yesterday, I already posted about Authentication part in “Create Login functionality in Expressive 3” post, so, it’s time for authorization part. If you didn’t read that, please read first.

We will use role of user when deciding what access right of the user for accessed page. For example, we define role field in users table with the following SQL:

expressive=# ALTER TABLE users ADD COLUMN role character varying(255) NOT NULL DEFAULT 'user';
ALTER TABLE

Ok, we have new column named role with default value = ‘user’. So, we have existing data with role = ‘user’ :

expressive=# SELECT * FROM users;
  username  |                           password                           | role
------------+--------------------------------------------------------------+------
 samsonasik | $2a$06$uPvOqYT7fQFP5EYR2jzVrOefwU03GltjAHt.q8l1vWXmkTIbeBcHe | user

Let’s add another user with different role, eg: ‘admin’, as follows:

expressive=# INSERT INTO users(username, password, role) VALUES('admin', crypt('123456', gen_salt('bf')), 'admin');
INSERT 0 1

expressive=# SELECT * FROM users;
  username  |                           password                           | role
------------+--------------------------------------------------------------+-------
 samsonasik | $2a$06$uPvOqYT7fQFP5EYR2jzVrOefwU03GltjAHt.q8l1vWXmkTIbeBcHe | user
 admin      | $2a$06$0pLYG/GVQOL6v9tLmjBB..cvUIk0vBdcDM8aV373AVO3ve9MdSbom | admin
(2 rows)

Perfect, now, we need to add sql_get_roles config under [‘authentication’][‘pdo’] to get role from users table, we can add at our config/autoload/local.php:

<?php
// config/autoload/local.php
return [

    'authentication' => [
        'pdo' => [
            // ...
            'sql_get_roles' => 'SELECT role FROM users WHERE username = :identity'
        ],
        // ...
    ],

];

When we login and var_dump the session data, we will get the following array value:

// var_dump($session->get(UserInterface::class));
array (size=2)
  'username' => string 'samsonasik' (length=10)
  'roles' =>
    array (size=1)
      0 => string 'user' (length=4)

Until here we are doing great!

To differentiate access page, let’s create a different page for admin only, for example: AdminPageHandler:

<?php

declare(strict_types=1);

namespace App\Handler;

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

class AdminPageHandler implements RequestHandlerInterface
{
    private $template;

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

    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        return new HtmlResponse($this->template->render('app::admin-page', []));
    }
}

with factory as follows:

<?php

declare(strict_types=1);

namespace App\Handler;

use Psr\Container\ContainerInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Expressive\Template\TemplateRendererInterface;

class AdminPageFactory
{
    public function __invoke(ContainerInterface $container) : RequestHandlerInterface
    {
        $template = $container->get(TemplateRendererInterface::class);
        return new AdminPageHandler($template);
    }
}

We can register the AdminPageHandler middleware at App\ConfigProvider::getDependencies():

<?php

class ConfigProvider
{
    public function getDependencies() : array
    {
        return [
            'invokables' => [ /**/ ],
            'factories'  => [
                // ...
                Handler\AdminPageHandler::class => Handler\AdminPageFactory::class,
            ],
        ];
    }
}

Then let’s define route for it, eg: ‘/admin’:

// config/routes.php
    $app->route('/admin', [
        \Zend\Expressive\Authentication\AuthenticationMiddleware::class,
        App\Handler\AdminPageHandler::class,
    ], ['GET'], 'admin');

The view can just show it that it is currently at admin page:

<?php // templates/app/admin-page.phtml ?>
Admin Page

So, if we logged in as role = user, and access `/admin’ page, we still can see the page.

Let’s authorize it!

First, we can add components for it, for example, we are going to use ACL, we can install expressive component for it via command:

$ composer require \
    zendframework/zend-expressive-authorization:^1.0 \
    zendframework/zend-expressive-authorization-acl:^1.0

It will install the following components:
* zendframework/zend-expressive-authorization
* zendframework/zend-permissions-acl
* zendframework/zend-expressive-authorization-acl

After they installed, ensure our config/config.php has registered the following ConfigProvider classes:

<?php
// config/config.php
$aggregator = new ConfigAggregator([
    // ...
    \Zend\Expressive\Authorization\Acl\ConfigProvider::class,
    \Zend\Expressive\Authorization\ConfigProvider::class,
    // ...
]);

Then, we can map Zend\Expressive\Authorization\AuthorizationInterface::class to Zend\Expressive\Authorization\Acl\ZendAcl::class at config/autoload/dependencies.global.php under alias to use the ZendAcl service :

<?php
// config/autoload/dependencies.global.php
return [

    'dependencies' => [
        'aliases' => [
            // ...
            Zend\Expressive\Authorization\AuthorizationInterface::class =>
                Zend\Expressive\Authorization\Acl\ZendAcl::class
        ],
    ],

];

Roles, Resources, and Rights definitions

We can define roles, resources, and rights under [‘zend-expressive-authorization-acl’] config, for example, at config/autoload/zend-expressive.global.php:

<?php
// config/autoload/zend-expressive.global.php

return [
    // ...
    'zend-expressive-authorization-acl' => [
        'roles' => [
            'guest' => [],
            'user'  => ['guest'],
            'admin' => ['user'],
        ],
        'resources' => [
            'home',
            'admin',
            'login',
            'logout',
        ],
        'allow' => [
            'guest' => [
                'login',
            ],
            'user'  => [
                'logout',
                'home',
            ],
            'admin' => [
                'admin',
            ],
        ],
    ],
    // ...
];

I’m going to mark non-logged user with role = “guest”, “user” role will inherit all guest rights, and “admin” role inherit all user rights, that mean, admin can access what user can access, but not opposite.

The resources are route names that registered at config/routes.php.

Authorization Process

To get ‘roles’ value, we have Zend\Expressive\Authorization\AuthorizationMiddleware that checks from request attribute named Zend\Expressive\Authentication\UserInterface::class, we can define at config/pipeline.php before $app->pipe(DispatchMiddleware::class); with continue pipe the Zend\Expressive\Authorization\AuthorizationMiddleware after it, as follow:

// config/pipeline.php

$app->pipe(new class implements Psr\Http\Server\MiddlewareInterface{

    public function process(
        Psr\Http\Message\ServerRequestInterface $request,
        Psr\Http\Server\RequestHandlerInterface $handler
    ) : Psr\Http\Message\ResponseInterface {

        $session = $request->getAttribute(
            Zend\Expressive\Session\SessionMiddleware::SESSION_ATTRIBUTE
        );

        // No Session data 
        if (! $session->has(Zend\Expressive\Authentication\UserInterface::class)) {
            $user  = '';
            $roles = ['guest'];

            $request = $request->withAttribute(
                Zend\Expressive\Authentication\UserInterface::class,
                new Zend\Expressive\Authentication\DefaultUser(
                    $user,
                    $roles
                )
            );

            $response = $handler->handle($request);
            if ($request->getUri()->getPath() === '/login' || $response->getStatusCode() !== 403) {
                return $response;
            }

            return new Zend\Diactoros\Response\RedirectResponse('/login');
        }

        // at /login page, redirect to authenticated page
        if ($request->getUri()->getPath() === '/login') {
            return new Zend\Diactoros\Response\RedirectResponse('/');
        }

        // define roles from DB
        $sessionData = $session->get(Zend\Expressive\Authentication\UserInterface::class);
        $request = $request->withAttribute(
            Zend\Expressive\Authentication\UserInterface::class,
            new Zend\Expressive\Authentication\DefaultUser(
                $sessionData['username'],
                $sessionData['roles']
            )
        );

        return $handler->handle($request);
    }
});

$app->pipe(\Zend\Expressive\Authorization\AuthorizationMiddleware::class);
$app->pipe(DispatchMiddleware::class);

When we logged as user, but want to access “admin” resource, eg: “/admin”, we will get “403 Forbidden” :

How about integration with “404” page?

With current setup, when we go to 404 page, eg: “/404”, we will get the “Resource ” not found”. To make it work with the NotFoundHandler, we need to inject NotFoundHandler to our authorization check. Let’s make above authorization check to a dedicated class with inject the NotFoundHandler, as follow:

<?php

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\RedirectResponse;
use Zend\Expressive\Authentication\DefaultUser;
use Zend\Expressive\Authentication\UserInterface;
use Zend\Expressive\Handler\NotFoundHandler;
use Zend\Expressive\Router\RouteResult;
use Zend\Expressive\Session\SessionMiddleware;

class AuthorizationMiddleware implements MiddlewareInterface
{
    private $notFoundHandler;
    private $redirect;

    public function __construct(NotFoundHandler $notFoundHandler, string $redirect)
    {
        $this->notFoundHandler = $notFoundHandler;
        $this->redirect        = $redirect;
    }

    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ) : ResponseInterface {

        // 404 check early 
        $routeResult = $request->getAttribute(RouteResult::class);
        if ($routeResult->isFailure()) {
            return $this->notFoundHandler->handle($request, $handler);
        }

        $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);

        // No Session data  
        if (! $session->has(UserInterface::class)) {
            $user  = '';
            $roles = ['guest'];

            $request = $request->withAttribute(
                UserInterface::class,
                new DefaultUser(
                    $user,
                    $roles
                )
            );

            $response = $handler->handle($request);
            if ($request->getUri()->getPath() === $this->redirect || $response->getStatusCode() !== 403) {
                return $response;
            }

            return new RedirectResponse($this->redirect);
        }

        // at /login page, redirect to authenticated page
        if ($request->getUri()->getPath() === $this->redirect) {
            return new RedirectResponse('/');
        }

        // define roles from DB
        $sessionData = $session->get(UserInterface::class);
        $request = $request->withAttribute(
            UserInterface::class,
            new DefaultUser(
                $sessionData['username'],
                $sessionData['roles']
            )
        );

        return $handler->handle($request);
   }
}

With above “$routeResult->isFailure()” check in line 30 above, we return 404 page early before check against authorization.

For service creation, we can create a AuthorizationMiddlewareFactory for it:

<?php

declare(strict_types=1);

namespace App\Middleware;

use Psr\Container\ContainerInterface;
use Psr\Http\Server\MiddlewareInterface;
use Zend\Expressive\Handler\NotFoundHandler;

class AuthorizationMiddlewareFactory
{
    public function __invoke(ContainerInterface $container) : MiddlewareInterface
    {
        $notFoundHandler   = $container->get(NotFoundHandler::class);
        $redirect          = $container->get('config')['authentication']['redirect'];

        return new AuthorizationMiddleware($notFoundHandler, $redirect);
    }
}

Now, we can register as a service for the AuthorizationMiddleware in the ConfigProvider class:

<?php

class ConfigProvider
{
    public function getDependencies() : array
    {
        return [
            'invokables' => [ /**/ ],
            'factories'  => [
                // ...
                Middleware\AuthorizationMiddleware::class => Middleware\AuthorizationMiddlewareFactory::class,
            ],
        ];
    }
}

Now, in the pipeline, we have a cleaner, and with 404 handler integration:

    $app->pipe(\Zend\Expressive\Session\SessionMiddleware::class);
    $app->pipe(RouteMiddleware::class);

    // ... 
    $app->pipe(\App\Middleware\AuthorizationMiddleware::class);
    $app->pipe(\Zend\Expressive\Authorization\AuthorizationMiddleware::class);
    $app->pipe(DispatchMiddleware::class) 

That’s it ;).

Create Login functionality in Expressive 3

Posted in expressive, Zend Framework by samsonasik on January 12, 2018

Zend Expressive 3 is not released yet released, and expressive session related components are in active development ready to use. However, we already can give them a try.

Use case

For example, we need simple login functionalities:

  1. Login Form
  2. Authentication process, read from DB
  3. Save authenticated value to Session

Setup

First, we can install the Zend Expressive 3 skeleton with the following command:

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

There are components that can be installed via command:

$ cd expressive3
$ composer require \
     zendframework/zend-form:^2.11 \
     zendframework/zend-i18n:^2.7 \
     zendframework/zend-expressive-authentication:^1.0 \
     zendframework/zend-expressive-authentication-session:^1.0 \
     zendframework/zend-expressive-session:^1.0 \
     zendframework/zend-expressive-session-ext:^1.0

After above components installed, ensure that your config/config.php injected with ConfigProvider like below:

<?php
// config/config.php
$aggregator = new ConfigAggregator([
    // ... form requirements
    \Zend\I18n\ConfigProvider::class,
    \Zend\Form\ConfigProvider::class,
    \Zend\InputFilter\ConfigProvider::class,
    \Zend\Filter\ConfigProvider::class,
    \Zend\Hydrator\ConfigProvider::class,
    // ...

    // ... auth requirements
    \Zend\Expressive\Authentication\ConfigProvider::class,
    \Zend\Expressive\Authentication\Session\ConfigProvider::class,
    \Zend\Expressive\Session\ConfigProvider::class,
    \Zend\Expressive\Session\Ext\ConfigProvider::class,
    // ...
];

we can first setup database data, in this case, I tried with Postgresql:

$ createdb -Udeveloper expressive
Password:

$ psql -Udeveloper expressive
Password for user developer:

psql (10.1)
Type "help" for help.

expressive=# CREATE TABLE users(username character varying(255) PRIMARY KEY NOT NULL, password text NOT NULL);
CREATE TABLE

expressive=# CREATE EXTENSION pgcrypto;
CREATE EXTENSION

expressive=# INSERT INTO users(username, password) VALUES('samsonasik', crypt('123456', gen_salt('bf')));
INSERT 0 1

Above, I create database named “expressive”, create table named “users” with username and password field, insert sample data with pgcrypto extension for create hashed password of 123456 using blowfish.

Now, we can setup the authentication configuration at config/autoload/local.php as follows:

<?php
// config/autoload/local.php
return [

    'authentication' => [
        'pdo' => [
            'dsn'   => 'pgsql:host=localhost;port=5432;dbname=expressive;user=developer;password=xxxxx',
            'table' => 'users',
            'field' => [
                'identity' => 'username',
                'password' => 'password',
            ],
        ],
        'username' => 'username',
        'password' => 'password',
        'redirect' => '/login',
    ],

];

Then, we can map Zend\Expressive\Authentication\UserRepositoryInterface::class to Zend\Expressive\Authentication\UserRepository\PdoDatabase::class under alias and register Zend\Expressive\Authentication\AuthenticationInterface::class under factories config at config/autoload/dependencies.global.php :

<?php
// config/autoload/dependencies.global.php
return [

    'dependencies' => [
        'aliases' => [
            // ...
            Zend\Expressive\Authentication\UserRepositoryInterface::class =>
                Zend\Expressive\Authentication\UserRepository\PdoDatabase::class
        ],

        'factories' => [
            // ...
            Zend\Expressive\Authentication\AuthenticationInterface::class =>
                Zend\Expressive\Authentication\Session\PhpSessionFactory::class,
        ],
        // ...
    ],

];

For Session operations, we need Zend\Expressive\Session\SessionMiddleware middleware before routing middleware, so, in config/pipeline.php, we call pipe on it before $app->pipe(RouteMiddleware::class);:

// ...
$app->pipe(\Zend\Expressive\Session\SessionMiddleware::class);

// Register the routing middleware in the middleware pipeline
$app->pipe(RouteMiddleware::class);
// ...

as example: we want to redirect non-logged user to /login page, eg: at home page (/), we can register “home” routes config:

<?php
// config/routes.php
    $app->route('/', [
        \Zend\Expressive\Authentication\AuthenticationMiddleware::class,
        App\Handler\HomePageHandler::class,
    ], ['GET'], 'home');

When access ‘/’ page, we should be redirected to /login page which currently a 404 page, nice!

Login Page

First, we create a LoginForm with username and password field like the following:

<?php
// src/App/Form/LoginForm.php
declare(strict_types=1);

namespace App\Form;

use Zend\Form\Element\Password;
use Zend\Form\Element\Text;
use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;

class LoginForm extends Form implements InputFilterProviderInterface
{
    public function __construct()
    {
        parent::__construct('login-form');
    }

    public function init()
    {
        $this->add([
            'type' => Text::class,
            'name' => 'username',
            'options' => [
                'label' => 'Username',
            ],
        ]);

        $this->add([
            'type' => Password::class,
            'name' => 'password',
            'options' => [
                'label' => 'Password',
            ],
        ]);

        $this->add([
            'name' => 'Login',
            'type' => 'submit',
            'attributes' => [
                'value' => 'Login',
            ],
        ]);
    }

    public function getInputFilterSpecification()
    {
        return [
            [
                'name' => 'username',
                'required' => true,
                'filters' => [
                    ['name' => 'StripTags'],
                    ['name' => 'StringTrim'],
                  ],
            ],

            [
                'name' => 'password',
                'required' => true,
                'filters' => [
                    ['name' => 'StripTags'],
                    ['name' => 'StringTrim'],
                ],
            ],
        ];
    }
}

We then can create a login page handler with inject it with login form with the following factory:

<?php
// src/App/Handler/LoginPageFactory.php
declare(strict_types=1);

namespace App\Handler;

use App\Form\LoginForm;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Container\ContainerInterface;
use Zend\Expressive\Template\TemplateRendererInterface;
use Zend\Form\FormElementManager;

class LoginPageFactory
{
    public function __invoke(ContainerInterface $container) : MiddlewareInterface
    {
        $template  = $container->get(TemplateRendererInterface::class);
        $loginForm = $container->get(FormElementManager::class)
                               ->get(LoginForm::class);

        return new LoginPageHandler($template, $loginForm);
    }
}

The LoginPageHandler itself can be initialized with :

<?php
// src/App/Handler/LoginPageHandler.php
declare(strict_types=1);

namespace App\Handler;

use App\Form\LoginForm;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\RedirectResponse;
use Zend\Expressive\Authentication\UserInterface;
use Zend\Expressive\Session\SessionMiddleware;
use Zend\Expressive\Template\TemplateRendererInterface;

class LoginPageHandler implements MiddlewareInterface
{
    private $template;
    private $loginForm;

    public function __construct(
        TemplateRendererInterface $template,
        LoginForm                 $loginForm
    ) {
        $this->template  = $template;
        $this->loginForm = $loginForm;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
        if ($session->has(UserInterface::class)) {
            return new RedirectResponse('/');
        }

        $error = '';
        // handle authentication here Next

        return new HtmlResponse(
            $this->template->render('app::login-page', [
                'form'  => $this->loginForm,
                'error' => $error,
            ])
        );
    }
}

Above, we redirect to ‘/’ page when there is a session data as it already authenticated check. We are going to add authentication process next.

The Login form can be as simple as the following:

<?php // templates/app/login-page.phtml

echo $error;

$form->prepare();
echo $this->form($form);

We can register the LoginPageHandler at App\ConfigProvider::getDependencies() config:

<?php
// src/App/ConfigProvider.php
class ConfigProvider
{
    public function getDependencies() : array
    {
        return [
            'invokables' => [ /**/ ],
            'factories'  => [
                // ...
                Handler\LoginPageHandler::class => Handler\LoginPageFactory::class,
            ],
        ];
    }
}

The routing can be registered as follows with add \Zend\Expressive\Authentication\AuthenticationMiddleware::class for next middleware:

// config/routes.php
// ...
$app->route('/login', [
    App\Handler\LoginPageHandler::class,
    // for authentication next handling
    \Zend\Expressive\Authentication\AuthenticationMiddleware::class,
], ['GET', 'POST'],'login');

Above, we allow ‘GET’ and ‘POST’ in same ‘/login’ page.

Authentication process

Time for authentication process, we utilize Zend\Expressive\Authentication\AuthenticationMiddleware class that registered at the last entry at the /login route, we can accomodate it after check of form is valid

<?php
// src/App/Handler/LoginPageHandler.php
class LoginPageHandler implements MiddlewareInterface
{
    // ...
    public function __construct(
        TemplateRendererInterface $template,
        LoginForm                 $loginForm) { /* */ }

    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ) : ResponseInterface
    {
        // .......
        $error  = '';
        if ($request->getMethod() === 'POST') {
            $this->loginForm->setData($request->getParsedBody());
            if ($this->loginForm->isValid()) {
                $response = $handler->handle($request);
                if ($response->getStatusCode() !== 302) {
                    return new RedirectResponse('/');
                }

                $error = 'Login Failure, please try again';
            }
        }
        // ...

        return new HtmlResponse(
            $this->template->render('app::login-page', [
                'form'  => $this->loginForm,
                'error' => $error,
            ])
        );
    }

We call handle($request) for next Zend\Expressive\Authentication\AuthenticationMiddleware with:

$response = $handler->handle($request);
if ($response->getStatusCode() !== 302) {
    return new RedirectResponse('/');
}

When status code is not 301 302, it authenticated and session filled, we can then redirect to page that need to be authenticated to be access. Failure authentication default behaviour has 301 302 status code which we can set config “redirect” in “authentication” config, on above code, I just want to show it in the login form that the login failure, so I set the $error variable value to “Login Failure, please try again”, so when login failure, it will got the error like the following:

That’s it ;).

How about logout ? We can use clear() method from SessionMiddleware::SESSION_ATTRIBUTE attribute like the following:

use Zend\Expressive\Authentication\UserInterface;
use Zend\Expressive\Session\SessionMiddleware;

class LogoutPageHandler implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
        if ($session->has(UserInterface::class)) {
            $session->clear();
        }
        // ...
    }
}

How about authorization part? You can read my next post about create authorization functionality in zend expressive 3