Welcome to Abdul Malik Ikhsan's Blog

Immediate remove cookie data in zend-mvc application

Posted in Tutorial PHP, Zend Framework, Zend Framework 2, Zend Framework 3 by samsonasik on August 8, 2019

In zend-mvc application, we can utilize Zend\Http\Header\SetCookie header to set cookie to be added to response header. For example, we have a setAction() to set it, we can do:

use Zend\Http\Header\SetCookie;
// ...
    public function setAction()
    {
        $request  = $this->getRequest();
        $response = $this->getResponse();

        $response->getHeaders()->addHeader(new SetCookie(
            // name
            'test',

            // value
            'abc',
            
            // expires
            \time() + (60 * 60 * 24 * 365),
            
            // path
            '/',
            
            // domain
            null,
            
            // secure
            $request->getUri()->getScheme() === 'https',
            
            // httponly
            true
        ));

        return $response;
    }
// ...

Above, we set cookie with name “test” with value “abc”, with expires 1 year. To remove it completely before 1 year hit, we can pass 8th parameter as maxAge to be 0, so, for example, we have a removeAction to remove it, we can do:

use Zend\Http\Header\SetCookie;
// ...
    public function removeAction()
    {
        $request  = $this->getRequest();
        $response = $this->getResponse();

        $cookie   = $request->getCookie();
        if (! isset($cookie['test'])) {
            // already removed, return response early
            return $response;
        }

        $response->getHeaders()->addHeader(new SetCookie(
            'test',
            null, // no need to set value
            null, // no need to set expires
            '/',
            null,
            $request->getUri()->getScheme() === 'https',
            true,
            0    // set maxAge to 0 make "test" cookie gone
        ));

        return $response;
    }
// ...

That’s it!

Using Buffer for Resultset::rewind() after Resultset::next() called in Zend\Db

Posted in hack, Tutorial PHP, Zend Framework 3 by samsonasik on August 5, 2019

In Zend\Db, there is Resultset which can be used as result of db records as instanceof PHP Iterator. For example, we have the following table structure:

CREATE TABLE test (
    id serial NOT NULL PRIMARY KEY,
    name character varying(255)
);

and we have the following data:

To build resultset, we can use the following code:

include './vendor/autoload.php';

use Zend\Db\Adapter\Adapter;
use Zend\Db\TableGateway\TableGateway;
use Zend\Db\ResultSet\ResultSet;

$adapter = new Adapter([
    'username' => 'developer',
    'password' => '123456',
    'driver'   => 'pdo_pgsql',
    'database' => 'learn',
    'host'     => 'localhost',
]);

$resultSetPrototype = new ResultSet(
    null,
    new ArrayObject([], ArrayObject::ARRAY_AS_PROPS)
);

$tableGateway = new TableGateway(
    'test',
    $adapter,
    null,
    $resultSetPrototype
);

$select    = $tableGateway->getSql()->select();
$resultSet = $tableGateway->selectWith($select);

On getting the data, we can go to specific record position by using next(), for example: we want to get the 2nd record of selected data, we can use the following code:

$resultSet->current();
$resultSet->next();

var_dump($resultSet->current());

and we will get the following data:

class ArrayObject#16 (1) {
  private $storage =>
  array(2) {
    'id' =>
    int(2)
    'name' =>
    string(6) "test 2"
  }
}

However, when we need to back to first position, we can’t just use rewind() as follow:

$resultSet->rewind();
var_dump($resultSet->current());

Above code will result wrong data, which is a next record data:

class ArrayObject#16 (1) {
  private $storage =>
  array(2) {
    'id' =>
    int(3)
    'name' =>
    string(6) "test 3"
  }
}

To make that work, we need to use buffer() method early after result set created, so, the code will need to be:

$select    = $tableGateway->getSql()->select();
$resultSet = $tableGateway->selectWith($select);

$resultSet->buffer();

$resultSet->current();  // ensure hit 1st record first 
$resultSet->next();     // next position

var_dump($resultSet->current()); // get 2nd record 

$resultSet->rewind(); // back to position 0
var_dump($resultSet->current());  // get 1st record again 

That will show the correct data:

# second record by call of next()
class ArrayObject#16 (1) {
  private $storage =>
  array(2) {
    'id' =>
    int(2)
    'name' =>
    string(6) "test 2"
  }
}

# first record after call of rewind() 
class ArrayObject#16 (1) {
  private $storage =>
  array(2) {
    'id' =>
    int(1)
    'name' =>
    string(6) "test 1"
  }
}

Call private/protected method without ReflectionMethod in PHP

Posted in php by samsonasik on July 21, 2019

For whatever reason, you may need to call private/protected method from an object. For example, we have a Foo class with bar method with private modifier like below:

class Foo
{
    private function bar()
    {
        echo 'hit';
    }
}

We need to call bar() method from an instance of Foo class. If you are familiar with ReflectionMethod, you may do this:

$r = new ReflectionMethod($foo = new Foo(), 'bar');
$r->setAccessible(true);
$r->invoke($foo);

There is another way. It is by using invoked of Closure::bindTo() like below:

(function ($foo) {
    $foo->bar();
})->bindTo($foo = new Foo(), Foo::class)($foo);

That’s it!

Tagged with: , ,

Run test and coverage report with ant build in Windows

Posted in Teknologi by samsonasik on July 9, 2019

So, you want to run test and coverage report commands in Windows environment. For example, you want to run kahlan command for test, then make coverage report by istanbul and istanbul-merge.

Based on the ant documentation on exec part, it noted as follow:

The task delegates to Runtime.exec which in turn apparently calls ::CreateProcess. It is the latter Win32 function that defines the exact semantics of the call. In particular, if you do not put a file extension on the executable, only .EXE files are looked for, not .COM, .CMD or other file types listed in the environment variable PATHEXT. That is only used by the shell.
Note that .bat files cannot in general by executed directly. One normally needs to execute the command shell executable cmd using the /c switch.

If you are familiar with ant command, usually, you can create a build.xml file as default build configuration. You can create another build file that you can mention it when run the ant command, eg: build-windows.xml:

$ ant -buildfile build-windows.xml

Your build-windows.xml then can use “cmd” as executable property with /c /path/to/executable-file as argument. The file will like below:

<?xml version="1.0" encoding="UTF-8"?>
<project name="Your Application name" default="build">

    <property name="toolsdir" value="${basedir}/vendor/bin/"/>
    <property name="moduledir" value="${basedir}/module/"/>

    <target name="build"
            depends="kahlan,coverage-report"
            description=""/>

    <target name="kahlan"
            description="Run kahlan">

            <!-- Application -->
            <exec executable="cmd" failonerror="true" taskname="kahlan">
                <arg
                    line="/c ${toolsdir}kahlan.bat
                    --spec=${moduledir}Application/spec/
                    --src=${moduledir}Application/src
                    --istanbul=coverage/coverage-application.json
                    "/>
            </exec>
            <!-- Application -->

            <!-- other modules to be tested here -->

    </target>

    <target name="coverage-report"
            description="Run coverage report generation">
            
            <!-- merge coverages for many modules test -->
            <exec executable="cmd" failonerror="true" taskname="istanbul merge">
                <arg line="/c istanbul-merge --out coverage.json coverage/*.json"/>
            </exec>
            
            <!-- make report -->
            <exec executable="cmd" failonerror="true" taskname="istanbul report">
                <arg line="/c istanbul report"/>
            </exec>

    </target>

</project>

With above, when you run the command, if it succeed, it will likely as follow:

$ ant -buildfile build-windows.xml
Buildfile: D:\app\build-windows.xml

kahlan:
   [kahlan]             _     _
   [kahlan]   /\ /\__ _| |__ | | __ _ _ __
   [kahlan]  / //_/ _` | '_ \| |/ _` | '_ \
   [kahlan] / __ \ (_| | | | | | (_| | | | |
   [kahlan] \/  \/\__,_|_| |_|_|\__,_|_| | |
   [kahlan]
   [kahlan] The PHP Test Framework for Freedom, Truth and Justice.
   [kahlan]
   [kahlan] src directory  : D:\app\module\Application\src
   [kahlan] spec directory : D:\app\module\Application\spec
   [kahlan]
   [kahlan] ......................................................  11 / 11 (100%)
   [kahlan]
   [kahlan]
   [kahlan]
   [kahlan] Expectations   : 11 Executed
   [kahlan] Specifications : 0 Pending, 0 Excluded, 0 Skipped
   [kahlan]
   [kahlan] Passed 11 of 11 PASS in 1.831 seconds (using 8Mo)
   [kahlan]
   [kahlan] Coverage Summary
   [kahlan] ----------------
   [kahlan]
   [kahlan] Total: 100.00% (25/25)
   [kahlan]
   [kahlan] Coverage collected in 0.035 seconds

coverage-report:
[istanbul report] Done

build:

BUILD SUCCESSFUL
Total time: 4 seconds

That’s it!

References:

Programmatically add route in zend-mvc application

Posted in Tutorial PHP, Zend Framework 3 by samsonasik on June 16, 2019

Yes, we usually setup routing via configuration in our module.config.php in our modules. However, in very specific condition, we may need to programmatically setting it. There is a ‘Router’ service for that, it represent Zend\Router\Http\TreeRouteStack instance and we can use addRoute() method against it.

For example, we need to setting it in Module::onBootstrap(), so we can code like the following:

<?php
namespace Application;

use Zend\Mvc\MvcEvent;
use Zend\Router\Http\Literal;

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        // if ( ... condition to be handled programmatically ... ) :

            $services = $e->getApplication()->getServiceManager();
            $router   = $services->get('Router');

            $route = Literal::factory([
                'route' => '/foo',
                'defaults' => [
                    'controller' => Controller\FooController::class,
                    'action'     => 'index',
                ],
            ]);
            $router->addRoute('foo', $route);

        // end if    
    }

    public function getConfig() { /* ... */ }
}

That’s it!

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 😉