Welcome to Abdul Malik Ikhsan's Blog

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 = :username'
        ],
        // ...
    ],

];

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: AdminPageAction:

<?php

declare(strict_types=1);

namespace App\Action;

use Interop\Http\Server\MiddlewareInterface;
use Interop\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 AdminPageAction implements MiddlewareInterface
{
    private $template;

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

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

        return new HtmlResponse($this->template->render('app::admin-page', []));
    }
}

with factory as follows:

<?php

declare(strict_types=1);

namespace App\Action;

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

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

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

<?php

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

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

// config/routes.php
$app->get('/admin', [
    App\Action\AdminPageAction::class,
], '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-acl:^1.0.0-dev

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 [‘authorization’] config, for example, at config/autoload/zend-expressive.global.php:

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

return [
    // ...
    'authorization' => [
        '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 after pipeRoutingMiddleware() and then pipe the Zend\Expressive\Authorization\AuthorizationMiddleware after it, as follow:

// config/pipeline.php
$app->pipeRoutingMiddleware();

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

    use Zend\Expressive\Authentication\UserRepository\UserTrait;

    public function process(
        Psr\Http\Message\ServerRequestInterface $request,
        Interop\Http\Server\RequestHandlerInterface $handler
    ) : Psr\Http\Message\ResponseInterface {
        $session = $request->getAttribute(
            Zend\Expressive\Session\SessionMiddleware::SESSION_ATTRIBUTE
        );

        // no session and at login page, set roles as "guest"
        if (! $session->has(Zend\Expressive\Authentication\UserInterface::class)) {
            if ($request->getUri()->getPath() === '/login') {
                $user = '';
                $roles = ['guest'];

                $request = $request->withAttribute(
                    Zend\Expressive\Authentication\UserInterface::class,
                    $this->generateUser(
                        $user,
                        $roles
                    )
                );
                return $handler->handle($request);
            }

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

        // has session but 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,
            $this->generateUser(
                $sessionData['username'],
                $sessionData['roles']
            )
        );
        return $handler->handle($request);
    }
});

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

By above, you can clean up $session->has() check in all pages.

Yes, you can move the middleware new class inside pipe() to dedicated class and register it as service to be called as its classname, use custom template, you name it.

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

That’s it ;).

Advertisements

Create Login functionality in Expressive 3

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

Zend Expressive 3 is not released yet, and expressive session related components are in active development. 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.x-dev" \
     expressive-3.0-dev

There are components that can be installed via command:

$ cd expressive-3.0-dev
$ composer require \
     zendframework/zend-form:^2.11 \
     zendframework/zend-i18n:^2.7 \
     zendframework/zend-expressive-authentication-session:^1.0.0-dev \
     zendframework/zend-expressive-session-ext:^1.0.0-dev

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\Session\Ext\ConfigProvider::class,
    \Zend\Expressive\Authentication\ConfigProvider::class,
    \Zend\Expressive\Authentication\Session\ConfigProvider::class,
    \Zend\Expressive\Session\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' => [
                'username' => 'username',
                'password' => 'password',
            ],
        ],
        'username' => 'username',
        'password' => 'password',
    ],

];

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 pipeRoutingMiddleware():

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

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

// Register the routing middleware in the middleware pipeline
$app->pipeRoutingMiddleware();
// ...

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->get('/', [
    App\Action\HomePageAction::class
], 'home');

and in HomePageAction, we can check:

<?php
// src/App/Action/HomePageAction.php
declare(strict_types=1);
namespace App\Action;

// ...
use Zend\Diactoros\Response\RedirectResponse;
use Zend\Expressive\Authentication\UserInterface;
use Zend\Expressive\Session\SessionMiddleware;
// ...

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

        // ...
    }
}

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 action with inject it with login form with the following factory:

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

namespace App\Action;

use App\Form\LoginForm;
use Interop\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 LoginPageAction($template, $loginForm);
    }
}

The LoginPageAction itself can be initialized with :

<?php
// src/App/Action/LoginPageAction.php
declare(strict_types=1);

namespace App\Action;

use App\Form\LoginForm;
use Interop\Http\Server\MiddlewareInterface;
use Interop\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 LoginPageAction 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 LoginPageAction at App\ConfigProvider::getDependencies() config:

<?php
// src/App/ConfigProvider.php
class ConfigProvider
{
    public function getDependencies() : array
    {
        return [
            'invokables' => [ /**/ ],
            'factories'  => [
                // ...
                Action\LoginPageAction::class => Action\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\Action\LoginPageAction::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/Action/LoginPageAction.php
class LoginPageAction 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() !== 301) {
                    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() !== 301) {
    return new RedirectResponse('/');
}

When status code is not 301, 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 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\Session\SessionMiddleware;

class LogoutPageAction
{
    // ...
    public function __invoke(/**/)
    {
        $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

ErrorHeroModule : a Hero for Your Zend Mvc and Expressive Application

Posted in expressive, Teknologi, Zend Framework, Zend Framework 2, Zend Framework 3 by samsonasik on December 20, 2017

After > 1 year work with 52 releases, I think it is time to show off. Even you have 100% test coverage, error may can still happen, that’s why ErrorHeroModule was born. ErrorHeroModule is a Hero for your Zend Mvc, and zend-expressive application to trap php errors and exception with configureable options.

The logging storage is mainly a database, then can continue “log” to your email when you want it.

Features

1. Save to DB with Db Writer Adapter

We can choose using Zend\Db or Doctrine via DoctrineORMModule. The error log is recorded like below:

2. Log Exception (dispatch.error and render.error) and PHP Errors in all events process

This handle all Exceptions and Errors with support PHP 7 Error during MVC process or middleware flow.

3. Support excludes PHP E_* Error (eg: exclude E_USER_DEPRECATED) in config settings

This can be used when you have a functionality which has collection of E_* errors, and you need to keep the functionality to run.

4. Support excludes PHP Exception (eg: Exception class or classes that extends it) in config settings

This can be used when you have exceptions that you want to have special treatment.

5. Handle only once log error for same error per configured time range

This can be used when on some environment, eg: in production, we don’t want to get same error repeatly reported in some periodic time while we are fixing it.

6. Set default page (web access) or default message (console access) for error if configured ‘display_errors’ = 0

This can be used to set a “nice” page on web environment:

or content on console access:

7. Set default content when request is XMLHttpRequest via ‘ajax’ configuration

This can be used to set a default content when request is an XMLHttpRequest.

8. Provide request information ( http method, raw data, query data, files data, and cookie data )

This can be used to help reproduce the error.

9. Send Mail

This has options:
– many receivers to listed configured email
– with include $_FILES into attachments on upload error.

This can be used to help reproduce the error, with include uploaded data when error happen when we just submitted a form with upload process.

Support

This module support zend-mvc:^2.5 and zend-expressive:^1.1|^2.0 with php version ^5.6|^7.0. My plan is to drop php ^5.6 in version 2.

Limitations

There are some limitations right now and I want it to be implemented in next releases:

General functionality:

  • Allow custom formatter when log to email, currently, it send Json format to email.

Current Json Formatter is really stable with the following format sample data:

{
    "timestamp": "2017-12-20T15:23:00+07:00",
    "priority": 3,
    "priorityName": "ERR",
    "message": "a sample error preview",
    "extra": {
        "url": "http://app.dev/error-preview",
        "file": "/var/www/app/vendor/samsonasik/error-hero-module/src/Controller/ErrorPreviewController.php",
        "line": 11,
        "error_type": "Exception",
        "trace": "#0 /var/www/app/vendor/zendframework/zend-mvc/src/Controller/AbstractActionController.php(78): ErrorHeroModule\\Controller\\ErrorPreviewController->exceptionAction()
#1 /var/www/app/vendor/zendframework/zend-eventmanager/src/EventManager.php(322): Zend\\Mvc\\Controller\\AbstractActionController->onDispatch(Object(Zend\\Mvc\\MvcEvent))
#2 /var/www/app/vendor/zendframework/zend-eventmanager/src/EventManager.php(179): Zend\\EventManager\\EventManager->triggerListeners(Object(Zend\\Mvc\\MvcEvent), Object(Closure))
#3 /var/www/app/vendor/zendframework/zend-mvc/src/Controller/AbstractController.php(106): Zend\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Zend\\Mvc\\MvcEvent))
#4 /var/www/app/vendor/zendframework/zend-mvc/src/DispatchListener.php(138): Zend\\Mvc\\Controller\\AbstractController->dispatch(Object(Zend\\Http\\PhpEnvironment\\Request), Object(Zend\\Http\\PhpEnvironment\\Response))
#5 /var/www/app/vendor/zendframework/zend-eventmanager/src/EventManager.php(322): Zend\\Mvc\\DispatchListener->onDispatch(Object(Zend\\Mvc\\MvcEvent))
#6 /var/www/app/vendor/zendframework/zend-eventmanager/src/EventManager.php(179): Zend\\EventManager\\EventManager->triggerListeners(Object(Zend\\Mvc\\MvcEvent), Object(Closure))
#7 /var/www/app/vendor/zendframework/zend-mvc/src/Application.php(332): Zend\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Zend\\Mvc\\MvcEvent))
#8 /var/www/app/public/index.php(53): Zend\\Mvc\\Application->run()
#9 {main}",
        "request_data": {
            "query": [],
            "request_method": "GET",
            "body_data": [],
            "raw_data": "",
            "files_data": [],
            "cookie_data": {
                "ZS6SESSID": "pbihc9ts004oq4b5alg4tg91b6",
                "PHPSESSID": "bkd7jaj22z936vstc9l0xuc9sr2dqp4g",
            }
        }
    }
}

The drawback with allow custom formatter is you maintain/keep an eye yourself for the formatter you provide!

Zend Mvc application:

  • Trap exception and error when they happen at Module::init().

Zend Expressive application:

  • Make support for non zend-servicemanager for container.
  • Make support for non zend-view for custom page template engine when error happen.

That’s it for now. If you see something can be improved, please contribute! Thank you for all users that using it.

Apigility: Using zf-oauth2’s refresh_token_lifetime to create client’s remember me functionality

Posted in Tutorial PHP, Zend Framework 2, Zend Framework 3 by samsonasik on August 13, 2017

If you’re building client based application which require oauth authentication to apigility application which uses time based expire access token, you may want to create a remember me functionality in client.

PHP Configuration

The very first required is set the client and the server side has same timezone, eg:

# your php.ini
date.timezone = "Asia/Jakarta"

This is ensure that you have actually same time for both client and api server.

DB data requirements

If you already setup the Oauth2 DB, you need to insert/update the client to support “password” and “refresh_token”, that use both means use space-separated value eg:

INSERT INTO `oauth_clients` (`client_id`, `client_secret`, `redirect_uri`, `grant_types`, `scope`, `user_id`) VALUES
(
    'test',
    '$2y$10$vbuy12RNSTJ.LHDdivegwu9dqkxh8h6OS4VoIX64HQGngAqUfcSJe',
    '/oauth/receivecode',
    'password refresh_token',
    NULL,
    NULL
);

Above sql insert data to oauth_clients table with client_id valued “test” with bcrypted client_secret “test”.

You can also insert a users data, for example:

INSERT INTO `oauth_users` (`username`, `password`) VALUES
(
    'test',
    '$2y$10$vbuy12RNSTJ.LHDdivegwu9dqkxh8h6OS4VoIX64HQGngAqUfcSJe'
),

Above sql insert data to oauth_users table with username valued “test” with bcrypted password “test”.

ZF-Oauth2 configuration

In apigility side, we can configure the “zf-oauth2” config, for example, as follows:

// config/autoload/global.php
return [
    // ...
    'zf-oauth2' => [
        'access_lifetime' => 1800,
        'options' => [
            'refresh_token_lifetime' => 604800,
            'always_issue_new_refresh_token' => true,
        ],
    ],
];

The configuration above means we can have an access token lifetime in 1800 seconds, and we can re-issue the new token lifetime with existing “refresh_token” as far as the time range is not > 604800 seconds ( 1 week ). For example, we authenticate with data:

{
    "grant_type": "password",
    "username": "test",
    "password": "test",
    "client_id": "test",
    "client_secret" : "test"
}

have authenticated tokens data like the following:

{
  "access_token": "8e4b0e5ddc874a6f1500514ef530dbea3976ae77",
  "expires_in": 1800,
  "token_type": "Bearer",
  "scope": null,
  "refresh_token": "d19b79cd376924409c14ee46e5230617482fb169"
}

The “refresh_token” is the key here.

The client application

I assume you’re using Zend Framework 2/3 application for client side, which we can use Zend\Authentication\AuthenticationService service. We can build custom Auth storage for it, eg:

namespace Application\Storage;

use Zend\Authentication\Storage;

class AuthStorage extends Storage\Session
{
    public function __construct()
    {
        parent::__construct('app_client');

        $sessionConfigOptions = [
            'use_cookies'     => true,
            'cookie_httponly' => true,
            'gc_maxlifetime'  => 1800,
            'cookie_lifetime' => 1800,
        ];
        $this->getSessionManager()->getConfig()
                                  ->setOptions($sessionConfigOptions);
    }

    public function rememberMe($time)
    {
        $this->getSessionManager()->rememberMe($time);
    }

    public function clear()
    {
        $this->getSessionManager()->forgetMe();
        parent::clear();
    }

    public function getSessionManager()
    {
        return $this->session->getManager();
    }
}

You can now create factory to build the AuthenticationService service with the authstorage like I blog posted at Create ZF Client Authentication for Apigility Oauth with ApigilityConsumer post.

On authentication part, eg: AuthenticationController, you can do:

$result = $this->authenticationService->authenticate();

if ($result->isValid()) {
    $storage = $this->authenticationService->getStorage();

    // save next "expires" time to session
    $storage->write(
        $storage->read() +
        [
            // it is better to use
            // api service to get the `oauth_access_tokens` real expires
            'expires' => date('Y-m-d H:i:s', time() + $read['expires_in'])
        ]
    );

    // for example, you have "rememberme" checkbox
    if (($rememberme = $request->getPost('rememberme')) == 1 ) {

        $storage->rememberMe(604800);

        $read = $storage->read();
        $storage->write(
            compact('rememberme') +
            $read +
            [
                // it is better to use
                // api service to get the `oauth_refresh_tokens` real expires
                'refreshExpires' => date('Y-m-d H:i:s', time() + 604800)
            ]
        );
    }
    // ...
}

We are going to use “expires” as immediate check session lifetime hit the expires, and “refreshExpires” to check when it stil be able to re-issue new token.

In bootstrap, for example, in Application\Module::onBootstrap() you can verify it to re-issue the token when access lifetime has hit.

namespace Application;

use Zend\Authentication\AuthenticationService;
use Zend\Mvc\MvcEvent;

class Module
{
    // ...
    public function onBootstrap(MvcEvent $e)
    {
        $services = $e->getApplication()->getServiceManager();
        $storage  = $services->get(AuthenticationService::class)->getStorage();
        $read     = $storage->read();

        if (isset($read['access_token'])) {

            $timeFirst   = strtotime($read['expires']);
            $currentTime = date('Y-m-d H:i:s');
            $timeSecond  = strtotime($currentTime);
            $counter     = $timeFirst - $timeSecond;

            if (! empty($rememberme = $read['rememberme'])) {

                $storage->getSessionManager()
                        ->getConfig()
                        ->setStorageOption('gc_maxlifetime', 604800)
                        ->setStorageOption('cookie_lifetime', 604800);
                
                if ($counter < 0 && $currentTime < $read['refreshExpires']) {

                    // API CALL to apigility oauth uri
                    // with grant_types = "refresh_token" and uses
                    // refresh_token as the key to re-issue new token
                    //
                    //    {
                    //        "grant_type"    : "refresh_token",
                    //        "refresh_token" : $read['refresh_token']
                    //        "client_id"     : "test",
                    //        "client_secret" : "test"
                    //    }
                    //
                    $storage->write(
                        [
                            // it is better to use
                            // api service to get the `oauth_access_tokens` real expires
                            'expires' => date('Y-m-d H:i:s', time() + $read['expires_in']),

                            // api service to get the `oauth_refresh_tokens` real expires
                            'refreshExpires' => date('Y-m-d H:i:s', time() + 604800),
                        ] +
                        compact('rememberme') +
                        [
                            "access_token": "<new access token based on oauth call>",
                            "expires_in": <new expire_in based on oauth call>,
                            "token_type": "<new token_type based on oauth call>",
                            "refresh_token": "<new refresh_token token based on oauth call>"
                        ] +
                        $read
                    );

                    $read = $storage->read();
                }
            }

            if ($currentTime > $read['expires'])) {
                // force clean up session
                $storage->clear();
            }

        }

    }
    // ...
}

Note

As commented in the codes sample above, in your real life application, it is beter to use real token expires instead of adding current time with expire_in time or manual fill refresh token lifetime. Do more automation yourself!

If you use Zend Framework 2/3 or Zend Expressive, you can try ApigilityConsumer for client module to consume api services. Enjoy 😉

Auto add _links property of HAL Resources into all api service in Apigility

Posted in Tutorial PHP, Zend Framework, Zend Framework 2, Zend Framework 3 by samsonasik on July 10, 2017

If you want to have the _links property value to HAL Resource in apigility api service, for example:

{
    "id": 1,
    "name": "Abdul Malik Ikhsan",
    
    "_links": {
        "self": {
            "href": "http://api.dev/user/1"
        }
    }
}

you can do manually in every api service:

use ZF\ContentNegotiation\ViewModel;
use ZF\Hal\Entity as HalEntity;
use ZF\Hal\Link\Link;

// ...

    public function userAction()
    {
        $halEntity = new HalEntity([
             'id' => 1,
             'name' => 'Abdul Malik Ikhsan',   
        ]);

        $link = $halEntity->getLinks();
        $link->add(Link::factory(
              [
                  'rel' => 'self',
                  'url' => $this->getRequest()->getUriString(),
              ]
        ));

        return new ViewModel([
            'payload' => $halEntity,
        ]);
    }

// ...

You can eliminate that by apply via EventManager’s Shared Manager which attach to Zend\Mvc\Controller\AbstractActionController on dispatch event, like below:

namespace Application;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\Mvc\MvcEvent;
use ZF\Hal\Link\Link;
use ZF\Hal\Plugin\Hal;

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $app       = $e->getApplication();
        $sharedEvm = $app->getEventManager()->getSharedManager();

        $sharedEvm->attach(AbstractActionController::class, 'dispatch',
            function($event) use ($sharedEvm) {

                $uri = $event->getRequest()->getUriString();

                $sharedEvm->attach(Hal::class, 'renderEntity', function($event) use ($uri) {
                    $event->getParam('entity')
                          ->getLinks()
                          ->add(Link::factory(
                                [
                                    'rel' => 'self',
                                    'url' => $uri,
                                ]
                            ));
                });

            },
            100
        );

    }

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

On above code, we attach ZF\Hal\Plugin\Hal on renderEntity event which get the ZF\Hal\Entity object from ZF\ContentNegotiation\ViewModel payload property, and apply Link into it via ZF\Hal\Link\Link::factory().

Now, you can eliminate unneeded repetitive codes in all every api services.

Done 😉

Tagged with: , ,

Using Direct ArrayObject instance as ObjectPrototype in Zend\Db

Posted in Zend Framework 2, Zend Framework 3 by samsonasik on May 25, 2017

When creating a table model for ZF2 or ZF3 application with Zend\DB, direct ArrayObject instance can be usefull as ResultSet object prototype. We can no longer need to create an individual class that has getArrayCopy() or exchangeArray() for data transformation.

For example, we have the following table model:

<?php
namespace Application\Model;

use Zend\Db\TableGateway\AbstractTableGateway;

class CountryTable
{
    public static $table = 'country';
    private $tableGateway;

    public function __construct(AbstractTableGateway $tableGateway)
    {
        $this->tableGateway = $tableGateway;
    }

    public function getCountriesInAsia()
    {
        $select  = $this->tableGateway->getSql()->select();
        $select->where([
            'continent' => 'ASIA'
        ]);

        return $this->tableGateway->selectWith($select);
    }
}

The ArrayObject usage we can use is:

new ArrayObject([], ArrayObject::ARRAY_AS_PROPS);

So, we can build the factory for above table model as follows:

<?php
namespace Application\Model;

use ArrayObject;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Db\TableGateway\TableGateway;

class CountryTableFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $resultSetPrototype = new HydratingResultSet();
        $resultSetPrototype->setObjectPrototype(
             new ArrayObject([], ArrayObject::ARRAY_AS_PROPS)
        );
        
        $tableGateway =  new TableGateway(
            CountryTable::$table,
            $container->get('Zend\Db\Adapter\Adapter'),
            null,
            $resultSetPrototype
        );

        return new CountryTable($tableGateway);
    }
}

and register it into service_manager under factories:

<?php
namespace Application;

return [
    // ...
    'service_manager' => [
        'factories' => [
            Model\CountryTable::class => Model\CountryTableFactory:class,
        ],
    ],
];

When retrieving the data, you can do the followings:

use Application\Model\CountryTable;

$countryTable    = $container->get(CountryTable::class);
$countriesInAsia = $countryTable->getCountriesInAsia();

foreach ($countriesInAsia as $key => $row) {

    // dump a copy of the ArrayObject
    var_dump($arrayCopy = $row->getArrayCopy());

    // echoed column as property
    echo $row->name; // with value "INA"
    echo $row->iso;  // with value "ID"
    echo $row->continent; // with value "ASIA"

    // echoed as array with provided key
    echo $row['name']; // with value "INA"
    echo $row['iso'];  // with value "ID"
    echo $row['continent']; // with value "ASIA"

    // modify data via exhangeArray
    $row->exchangeArray(array_merge(
		$arrayCopy,
		[
			'name' => 'INDONESIA',
		]
	));

    // or modify its data by its property
    $row->name = 'INDONESIA';
    // or modify its data by its index array
    $row['name'] = 'INDONESIA';

    echo $row->name; // now has value "INDONESIA"
    echo $row['name']; // now has value "INDONESIA"
}

Bonus:

To avoid repetitive creating factory class for each table model, we can create an abstract factory for it:

<?php

namespace Application\Model;

use ArrayObject;
use Interop\Container\ContainerInterface;
use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Db\TableGateway\TableGateway;
use Zend\ServiceManager\Factory\AbstractFactoryInterface;

class CommonModelTableFactory implements AbstractFactoryInterface
{
    public function canCreate(ContainerInterface $container, $requestedName)
    {
        return ((substr($requestedName, -5) === 'Table') && class_exists($requestedName));
    }

    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $tableModel = '\\' . $requestedName;

        $resultSetPrototype = new HydratingResultSet();
        $resultSetPrototype->setObjectPrototype(
            new ArrayObject([], ArrayObject::ARRAY_AS_PROPS)
        );

        $tableGateway =  new TableGateway(
            $tableModel::$table,
            $container->get('Zend\Db\Adapter\Adapter'),
            null,
            $resultSetPrototype
        );

        return new $tableModel($tableGateway);
    }
}

So, now, we can have 1 abstract factory for all table model services:

<?php
namespace Application;

return [
    // ...
    'service_manager' => [
        'abstract_factories' => [
            Model\CommonModelTableFactory:class,
        ],
    ],
];

That’s it 😉

Using Github Access Token with Composer for Private Repository

Posted in GIT, Tutorial PHP by samsonasik on April 16, 2017

If you successfully run composer install which clone Github private repository by hand, you may found a blocker when run the command via script even already added ssh public key and added ssh private key to ssh-agent.

Use case

For example, you have a git hook on post-receive which run composer install.

#!/bin/sh
GIT_WORK_TREE=/var/www/app git checkout -f
cd /var/www/html/app && composer install --no-dev

And your composer.json of your app has a github private repository, for example, as follow:

{
    "require": {
        "yourcompany/lib": "^1.0"
    },

    "repositories" : [
        {
            "type": "vcs",
            "url" : "git@github.com:yourcompany/Lib.git"
        }
    ]

}

Solution

To make it work, first, you need to create a token for it in https://github.com/settings/tokens .

When you get generated token, you can register it in composer.json so the configuration like below:

{
    // ...
    "config": {
      "github-oauth": {
        "github.com": "th3t0k3nth4tG3n3r4t3d"
      }
    }
}

Now, your complete composer.json will be as follow:

{

    "require": {
        "yourcompany/lib": "^1.0"
    },

    "repositories" : [
        {
            "type": "vcs",
            "url" : "git@github.com:yourcompany/Lib.git"
        }
    ],

    "config": {
      "github-oauth": {
        "github.com": "th3t0k3nth4tG3n3r4t3d"
      }
    }

}

That’s it.

Create ZF Client Authentication for Apigility Oauth with ApigilityConsumer

Posted in Tutorial PHP, Zend Framework, Zend Framework 2, Zend Framework 3 by samsonasik on March 28, 2017

If you have Apigility as API builder in API side, and client app that consume it using Zend Framework 2/3 or ZF Expressive, you can create an authentication from the client application that call oauth in apigility side.

Zend\Authentication has AbstractAdapter that you can extends to create custom adapter for its need. Let’s assume the applications are like the following diagram:

[CLIENT - A ZF Application]              [API - An Apigility Application]
         |                                          |
   AuthController                     ZF\MvcAuth\Authentication\OAuth2Adapter          
         |                                          |
         |       authenticateAction()               |
         |   ------------------------------------>  |
         |         identity json                    |
         |   <------------------------------------  |

On oauth result call, you may get the following result:

{
  "access_token": "8e4b0e5ddc874a6f1500514ef530dbea3976ae77",
  "expires_in": 3600,
  "token_type": "Bearer",
  "scope": null,
  "refresh_token": "d19b79cd376924409c14ee46e5230617482fb169"
}

The ApigilityConsumer

ApigilityConsumer is a ZF2/ZF3 Apigility Client module (can also be used in ZF Expressive) to consume Apigility API Services.

You can install by run composer command:

composer require samsonasik/apigility-consumer

For full configurations and features, you can read at its README, for this post’s need, you can do something like this:

<?php
// config/autoload/apigility-consumer.local.php
return [
    'apigility-consumer' => [
        // your apigility host url
        'api-host-url' => 'https://your.apigilty.api.host',

        // your apigility oauth setting
        'oauth' => [

            'grant_type'    => 'password',
            'client_id'     => 'your client id',
            'client_secret' => 'your client secret',

        ],

    ],
];

and register the module into config/application.config.php or config/modules.config.php:

<?php
// config/application.config.php or config/modules.config.php
return [
    'ApigilityConsumer', // <-- register here
    'Application',
],

Create Adapter

You need to extends Zend\Authentication\Adapter\AbstractAdapter and implements Zend\Authentication\Adapter\AdapterInterface. So, You can have the class:

<?php

namespace Application\Adapter;

use ApigilityConsumer\Service\ClientAuthService;
use Zend\Authentication\Adapter\AbstractAdapter;
use Zend\Authentication\Adapter\AdapterInterface;
use Zend\Authentication\Result;

class ApigilityAuthenticationAdapter
    extends AbstractAdapter
    implements AdapterInterface
{
    /**
     * @var ClientAuthService
     */
    private $clientAuthService;

    /**
     * @param  ClientAuthService $clientAuthService
     */
    public function __construct(ClientAuthService $clientAuthService)
    {
        $this->clientAuthService = $clientAuthService;
    }

    /**
     * @return Result
     */
    public function authenticate()
    {
        $clientResult = $this->clientAuthService->callAPI(
            [
                // your oauth registered route segment in apigility. 
                'api-route-segment' => '/oauth', 

                'form-data' => [
                    'username' => $this->getIdentity(),
                    'password' => $this->getCredential(),
                ],

                'form-request-method' => 'POST',
            ]
        );

        if (! $clientResult->success) {
            return new Result(Result::FAILURE, null, $clientResult::$messages);
        }

        return new Result(RESULT::SUCCESS, $clientResult->data);
    }
}

Your can now build a factory from it:

<?php
namespace Application\Adapter;

use ApigilityConsumer\Service\ClientAuthService;

class ApigilityAuthenticationAdapterFactory
{
    public function __invoke($container)
    {
        return new ApigilityAuthenticationAdapter(
            $container->get(ClientAuthService::class)
        );
    }
}

You can then register at service_manager:

<?php
// module/Application/config/module.config.php
namespace Application;

'service_manager' => [
    'factories' => [
        // ...
        Adapter\ApigilityAuthenticationAdapter::class => Adapter\ApigilityAuthenticationAdapterFactory::class,
    ],
],

For ZF Expressive, you can register under ‘dependencies’ key.

Set Adapter into AuthenticationService

You need to set authentication service’s adapter with defined adapter above with factory:

<?php
namespace Application\Factory;

use Application\Adapter\ApigilityAuthenticationAdapter;
use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Storage\Session;

class AuthenticationServiceFactory
{
    public function __invoke($container)
    {
        $adapter = $container->get(ApigilityAuthenticationAdapter::class);

        return new AuthenticationService(
            new Session(), // or your own storage implementing  Zend\Authentication\Storage\StorageInterface
            $adapter
        );
    }
}

You can then register also at service_manager:

<?php
// module/Application/config/module.config.php
namespace Application;

use Zend\Authentication\AuthenticationService;

'service_manager' => [
    'factories' => [
        // ...
        AuthenticationService::class => Factory\AuthenticationServiceFactory::class,
    ],
],

For ZF Expressive, you can register under ‘dependencies’ key.

The AuthController::authenticate()

I assume that you already inject controler with login form, use “username” and “password” as field names, and fill the data, so, your AuthController::authenticate() can be like the following:

<?php
namespace Application\Controller;

use Application\Form\LoginForm;
use Zend\Authentication\AuthenticationService;

class AuthController
{
    public function __construct(
        AuthenticationService $authenticationService,
        LoginForm $loginForm,
    ) { /* ...*/ }

    public function authenticateAction()
    {
        /*
         *    check request and form validity here
         */
        $formData = $this->loginForm->getData();
        $this->authenticationService->getAdapter()
                                    ->setIdentity($formData['username'])
                                    ->setCredential($formData['password']);

        $result = $this->authenticationService->authenticate();
        if (!$result->isValid()) {
            /**
             * For security reason, you should not show user the reason of failure,
             * However, if it actually needed for specific purpose, you can pull by call:
             *
             *     $result->getMessages();
             *
             */
            return $this->redirect()->toRoute('/auth');
        }

        return $this->redirect()->toRoute('/account');
    }
}

For ZF Expressive, you can create routed Authentication middleware.

That’s it, you’re now have successfully created a client authentication for your ZF2/ZF3 or ZF Expressive application that consume Apigility oauth.

Tagged with: , ,

Testing Zend Expressive 2 with kahlan 3

Posted in testing, Tutorial PHP, Zend Framework by samsonasik on March 15, 2017

Zend\Expressive ^2.0 has different default approach for piping and routing middleware which is programmatically way. In this post, I am going to show you how you can test Zend\Expressive ^2.0 application, with assumption, you use its skeleton with kahlan 3.

First, of course, install the Expressive ^2.0 skeleton, for example, install into directory named “expressive2”:

$ composer create-project zendframework/zend-expressive-skeleton:^2.0 expressive2
Installing zendframework/zend-expressive-skeleton (2.0.0)
  - Installing zendframework/zend-expressive-skeleton (2.0.0) Downloading: 100%
Created project in expressive2
> ExpressiveInstaller\OptionalPackages::install
Setting up optional packages
Setup data and cache dir
Removing installer development dependencies

  What type of installation would you like?
  [1] Minimal (no default middleware, templates, or assets; configuration only)
  [2] Flat (flat source code structure; default selection)
  [3] Modular (modular source code structure; recommended)
  Make your selection (2): 3

Now, install kahlan:

$ cd expressive2
$ composer require kahlan/kahlan:^3.1

We are going to need the $app variable inside tests, for example, when testing functionality for each routed middlewares. To simplify and avoid repetitive code, we can register it into kahlan-config.php in root application:

// ./kahlan-config.php
use Kahlan\Filter\Filter;
use Zend\Expressive\Application;

Filter::register('initialize app', function($chain) {

    $root = $this->suite();

    ob_start();

    $root->beforeAll(function ($var) {

        ob_start();

        $var->app = $app
                  = (require 'config/container.php')->get(Application::class);

        require 'config/pipeline.php';
        require 'config/routes.php';

    });

    return $chain->next();

});
Filter::apply($this, 'run', 'initialize app');

By assign $app into “$var->app” like above, the “$app” is accessible from entire tests via “$this->app”, so, we can write test like the following:

// ./src/App/spec/Action/HomePageActionSpec.php
namespace AppSpec\Action;

use Zend\Diactoros\ServerRequest;

describe('HomePageAction', function () {

    describe('/', function () {

        it('contains welcome message', function () {

            $serverRequest = new ServerRequest([], [], '/', 'GET');
            $this->app->run($serverRequest);
            $actual = ob_get_clean();

            expect($actual)->toContain('Welcome to <span class="zf-green">zend-expressive</span>');

        });

    });

});

Now, let’s run the tests:

$ vendor/bin/kahlan --spec=src/App/spec/
            _     _
  /\ /\__ _| |__ | | __ _ _ __
 / //_/ _` | '_ \| |/ _` | '_ \
/ __ \ (_| | | | | | (_| | | | |
\/  \/\__,_|_| |_|_|\__,_|_| | |

The PHP Test Framework for Freedom, Truth and Justice.

Working Directory: /Users/samsonasik/www/expressive2

.                                                                   1 / 1 (100%)



Expectations   : 1 Executed
Specifications : 0 Pending, 0 Excluded, 0 Skipped

Passed 1 of 1 PASS in 0.375 seconds (using 8Mo)

That’s it 😉

Querying PostgreSQL’s JSONB with Zend\Db

Posted in Tutorial PHP by samsonasik on March 13, 2017

PostgreSQL is one of Databases that has json support. If you have table(s) that has json column(s), we can query it with Zend\Db in your php application layer.

For example, we have the following data:

CREATE TABLE album (id serial primary key, data jsonb);

INSERT INTO album VALUES
   (1, '{ "title": "Hello", "singer": "Adelle" }'),
   (2, '{ "title": "September", "singer": "Justin Timberlake" }')
;

We then want to produce SQL like :

select * from album where data ->> 'title' = 'Hello'

There is Zend\Db\Sql\Predicate\Expression for that. So, you can build select with:

use Zend\Db\Sql\Select;
use Zend\Db\Sql\Predicate\Expression;

$select = new Select();
$select->from('album')
       ->where([new Expression("data ->> 'title' = ?", 'Hello')]);

That’s easy! Let’s make it more complicated. We then want to produce SQL with subset of jsonb like:

select * from album where data @> '{"singer": "Adelle"}'

In this case, as it is not a common sql operation across DBs, you need to pass the filter as $expression, the first parameter of Zend\Db\Sql\Predicate\Expression::__construct:

use Zend\Db\Sql\Select;
use Zend\Db\Sql\Predicate\Expression;

$expression = <<<expr
data @> '{"singer": "Adelle"}'
expr;

$select = new Select();
$select->from('album')
       ->where([new Expression($expression)]);

That’s it 😉

Testing CodeIgniter 4 application with kahlan ^3.0

Posted in CodeIgniter 4, Tutorial PHP by samsonasik on January 14, 2017

This is another post series about CodeIgniter and Kahlan, but now, it will uses CodeIgniter 4. CodeIgniter 4 requires some constants and required classes and functions to be included during application bootstrap to make front controller works.
For example, we need to do functional test App\Controllers\Home controller, this is the kahlan-config.php we need to have:

<?php

use CodeIgniter\CodeIgniter;
use CodeIgniter\Services;
use Config\App;
use Config\Autoload;
use Kahlan\Filter\Filter;

Filter::register('ci.start', function($chain) {
    $root = $this->suite();
    $root->beforeAll(function ($var) {

        define('ENVIRONMENT', 'testing');
        define('BASEPATH',    'system'      . DIRECTORY_SEPARATOR);
        define('APPPATH',     'application' . DIRECTORY_SEPARATOR);
        define('ROOTPATH',    'public'      . DIRECTORY_SEPARATOR);
        define('WRITEPATH',   'writable'    . DIRECTORY_SEPARATOR);
        define('CI_DEBUG',    1);

        require BASEPATH . 'Autoloader/Autoloader.php';
        require APPPATH  . 'Config/Constants.php';
        require APPPATH  . 'Config/Autoload.php';
        require APPPATH  . 'Config/Services.php';

        class_alias('Config\Services', 'CodeIgniter\Services');

        $loader = Services::autoloader();
        $loader->initialize(new Autoload());
        $loader->register();

        require BASEPATH . 'Common.php';
        $app = new App();
        Services::exceptions($app, true)->initialize();

        $var->codeIgniter = new CodeIgniter(
            $app
        );

    });
    return $chain->next();
});
Filter::apply($this, 'run', 'ci.start');

We now can call $this->codeIgniter property in all entire tests.

We can then place the spec under spec/ directory:

.
├── DCO.txt
├── README.md
├── application
│   ├── Controllers
│   │   └── Home.php
├── spec
│   └── Controllers
│       └── HomeDispatchSpec.php

We can apply $_SESSION['argv'] and $_SESSION['argc'] to assign URI routing data:

$_SERVER['argv'] = [
    __FILE__,
    '/', // path
];
$_SERVER['argc'] = 2;

ob_start();
$this->codeIgniter->run();
$actual = ob_get_clean();

expect($actual)->toContain('Welcome to CodeIgniter');

Here is the complete tests:

<?php

namespace AppSpec\Controllers;

describe('Home Dispatch', function () {

    describe('/', function () {

        it('contains "welcome" greeting', function () {

            $_SERVER['argv'] = [
                __FILE__,
                '/', // path
            ];
            $_SERVER['argc'] = 2;

            ob_start();
            $this->codeIgniter->run();
            $actual = ob_get_clean();

            expect($actual)->toContain('Welcome to CodeIgniter');

        });

    });

});

Time to run test:

$ vendor/bin/kahlan --coverage=4 --src=application/Controllers/Home.php
            _     _
  /\ /\__ _| |__ | | __ _ _ __
 / //_/ _` | '_ \| |/ _` | '_ \
/ __ \ (_| | | | | | (_| | | | |
\/  \/\__,_|_| |_|_|\__,_|_| | |

The PHP Test Framework for Freedom, Truth and Justice.

Working Directory: /Users/samsonasik/www/CodeIgniter4

.                                                                   1 / 1 (100%)



Expectations   : 1 Executed
Specifications : 0 Pending, 0 Excluded, 0 Skipped

Passed 1 of 1 PASS in 0.120 seconds (using 4Mo)

Coverage Summary
----------------
                               Lines           %

 \                             1 / 1     100.00%
└── App\                       1 / 1     100.00%
   └── Controllers\            1 / 1     100.00%
      └── Home                 1 / 1     100.00%
         └── Home::index()     1 / 1     100.00%

Total: 100.00% (1/1)

Coverage collected in 0.003 seconds (using an additionnal 16Ko)

Done 😉

Tagged with: ,

Functional Test for Zend\Expressive Routed Middleware with Kahlan ^3.0

Posted in testing, Tutorial PHP, Zend Framework by samsonasik on January 13, 2017

You may tried do functional test Zend\Expressive Routed Middleware and end up with “Unable to emit response; headers already sent” error.
This can happen because of during run test, the Test framework itself already run fwrite() or echo to build test report, and make the headers_sent() return true.

To handle that, we can use ob_start(), but since the header is sent in the background, we need to place in both places:

  • test bootstrap
  • before each test

Seriously? Yes! That’s make sure we only get Diactoros response that we use in the buffer to be tested.

Preparation

As usual, we need require kahlan/kahlan:^3.0 in require-dev:

$ composer require --dev kahlan/kahlan:^3.0 --sort-packages

Set Kahlan’s Bootstrap and before each globally

In Kahlan, we can set tests bootstrap and what in all before each test with Kahlan\Filter\Filter in kahlan-config.php, so we can write:

<?php
//kahlan-config.php
use Kahlan\Filter\Filter;

ob_start();
Filter::register('ob_start at each', function($chain) {
    $root = $this->suite();
    $root->beforeEach(function () {
        ob_start();
    });
    return $chain->next();
});
Filter::apply($this, 'run', 'ob_start at each');

Write Spec and Run In Action

Now, if we use Expressive skeleton application, and for example, we need to test App\Action\PingAction routed middleware, we can write spec in spec directory:

.
├── composer.json
├── config
├── data
├── kahlan-config.php
├── public
├── spec
│   └── App
│       └── Action
│           ├── PingActionDispatchSpec.php
├── src
│   └── App
│       └── Action
│           ├── PingAction.php

As the App\Ping\PingAction is return Zend\Diactoros\Response\JsonResponse which contains “ack” data with time() method call:

return new JsonResponse(['ack' => time()]);

The spec can be the following:

<?php
namespace AppSpec\Action;

use Zend\Diactoros\ServerRequest;
use Zend\Expressive\Application;

describe('PingAction Dispatch', function () {

    beforeAll(function() {
        $container = require 'config/container.php';
        $this->app = $container->get(Application::class);
    });

    describe('/api/ping', function () {

        it('contains json "ack" data', function () {

            allow('time')->toBeCalled()->andReturn('1484291901');

            $serverRequest = new ServerRequest([], [], '/api/ping', 'GET');
            $this->app->run($serverRequest);

            $actual = ob_get_clean();
            expect($actual)->toBe('{"ack":"1484291901"}');

        });

    });

});

The ob_start() will automatically called during test bootstrap and before each test.

Now, we can run the test:

$ vendor/bin/kahlan --coverage=4 --src=src/App/Action/PingAction.php 
            _     _
  /\ /\__ _| |__ | | __ _ _ __
 / //_/ _` | '_ \| |/ _` | '_ \
/ __ \ (_| | | | | | (_| | | | |
\/  \/\__,_|_| |_|_|\__,_|_| | |

The PHP Test Framework for Freedom, Truth and Justice.

Working Directory: /Users/samsonasik/www/expressive

.                                                                   1 / 1 (100%)



Expectations   : 1 Executed
Specifications : 0 Pending, 0 Excluded, 0 Skipped

Passed 1 of 1 PASS in 0.210 seconds (using 7Mo)

Coverage Summary
----------------
                                        Lines           %

 \                                      1 / 1     100.00%
└── App\                                1 / 1     100.00%
   └── Action\                          1 / 1     100.00%
      └── PingAction                    1 / 1     100.00%
         └── PingAction::__invoke()     1 / 1     100.00%

Total: 100.00% (1/1)

Coverage collected in 0.003 seconds (using an additionnal 0o)

Done 😉

Merge multiple coverages for Kahlan with istanbul merge

Posted in testing, Tutorial PHP by samsonasik on December 24, 2016

As you may already knew, you can generate HTML coverage report with kahlan with the following command:

$ ./bin/kahlan --src=path/to/src --spec=path/to/spec --istanbul="coverage.json"
$ istanbul report

For multiple src path, with different specs location, as there are multiple coverages, we need to merge them. For example, we have the following application structure:

.
└── module
    ├── A
    │   ├── spec
    │   │   └── ASpec.php
    │   └── src
    │       └── A.php
    └── B
        ├── spec
        │   └── BSpec.php
        └── src
            └── B.php

Prepare dependencies

1. Install the following tools:

2. Install kahlan/kahlan:^3.0

$ composer require kahlan/kahlan:^3.0 --dev --sort-packages

Here is the sample of composer.json:

// composer.json
{
    "name": "samsonasik/kahlan-demo",
    "type": "project",
    "require-dev": {
        "kahlan/kahlan": "^3.0"
    },

    "autoload": {
        "psr-4": {
            "A\\": "module/A/src",
            "B\\": "module/B/src"
        }
    },

    "autoload-dev": {
        "psr-4": {
            "ASpec\\": "module/A/spec",
            "BSpec\\": "module/B/spec"
        }
    },

    "license": "MIT",
    "authors": [
        {
            "name": "Abdul Malik Ikhsan",
            "email": "samsonasik@gmail.com"
        }
    ],
    "config": {
        "bin-dir": "bin"
    },
    "minimum-stability": "dev",
    "prefer-stable": true
}

Write specs

We can write specs under module/{FOLDER}/spec.

Define Tasks on build.xml

We need to register multiple commands in build.xml

  • run bin/kahlan for module/A/src which set coverage target to coverage/coverge-A.json
  • run bin/kahlan for module/B/src which set coverage target to coverage/coverge-B.json
  • run instanbul-merge that merge coverage/coverage*.json to coverage.json
  • run instanbul report

The build.xml can be like the following:

<?xml version="1.0" encoding="UTF-8"?>
<project name="kahlan-demo app" default="build">

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

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

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

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

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

    </target>

    <target name="coverage-report"
            description="Run coverage report generation">

            <!-- merging coverage under coverage/ to coverage.json -->
            <exec executable="istanbul-merge" failonerror="true" taskname="istanbul merge">
                <arg line="--out coverage.json coverage/*.json"/>
            </exec>

            <!-- generate report with use of merged coverages to coverage.json  -->
            <exec executable="istanbul" failonerror="true" taskname="istanbul report">
                <arg line="report"/>
            </exec>

    </target>

</project>

Run tasks

We can run ant command and we will get the following output:

$ ant
Buildfile: /Users/samsonasik/www/kahlan-demo/build.xml

kahlan:
   [kahlan]             _     _
   [kahlan]   /\ /\__ _| |__ | | __ _ _ __
   [kahlan]  / //_/ _` | '_ \| |/ _` | '_ \
   [kahlan] / __ \ (_| | | | | | (_| | | | |
   [kahlan] \/  \/\__,_|_| |_|_|\__,_|_| | |
   [kahlan]
   [kahlan] The PHP Test Framework for Freedom, Truth and Justice.
   [kahlan]
   [kahlan] Working Directory: /Users/samsonasik/www/kahlan-demo
   [kahlan]
   [kahlan] .                                                                   1 / 1 (100%)
   [kahlan]
   [kahlan]
   [kahlan]
   [kahlan] Expectations   : 1 Executed
   [kahlan] Specifications : 0 Pending, 0 Excluded, 0 Skipped
   [kahlan]
   [kahlan] Passed 1 of 1 PASS in 0.074 seconds (using 2Mo)
   [kahlan]
   [kahlan] Coverage Summary
   [kahlan] ----------------
   [kahlan]
   [kahlan] Total: 100.00% (1/1)
   [kahlan]
   [kahlan] Coverage collected in 0.002 seconds (using an additionnal 70Ko)
   [kahlan]
   [kahlan]
   [kahlan]             _     _
   [kahlan]   /\ /\__ _| |__ | | __ _ _ __
   [kahlan]  / //_/ _` | '_ \| |/ _` | '_ \
   [kahlan] / __ \ (_| | | | | | (_| | | | |
   [kahlan] \/  \/\__,_|_| |_|_|\__,_|_| | |
   [kahlan]
   [kahlan] The PHP Test Framework for Freedom, Truth and Justice.
   [kahlan]
   [kahlan] Working Directory: /Users/samsonasik/www/kahlan-demo
   [kahlan]
   [kahlan] .                                                                   1 / 1 (100%)
   [kahlan]
   [kahlan]
   [kahlan]
   [kahlan] Expectations   : 1 Executed
   [kahlan] Specifications : 0 Pending, 0 Excluded, 0 Skipped
   [kahlan]
   [kahlan] Passed 1 of 1 PASS in 0.045 seconds (using 2Mo)
   [kahlan]
   [kahlan] Coverage Summary
   [kahlan] ----------------
   [kahlan]
   [kahlan] Total: 100.00% (1/1)
   [kahlan]
   [kahlan] Coverage collected in 0.001 seconds (using an additionnal 70Ko)
   [kahlan]
   [kahlan]

coverage-report:
[istanbul report] Done

build:

BUILD SUCCESSFUL
Total time: 2 seconds

Now, we have successfully gotten merged coverage results with open coverage/lcov-report/index.html:

kahlan-multiple-cov-merge-result

Using Layout in CodeIgniter 4

Posted in CodeIgniter 4, Tutorial PHP by samsonasik on November 25, 2016

CodeIgniter 4 is not ready for production use, but we can already play with it. We can apply layout support to avoid repetitive header/footer/sidebar html code in our application’s views by provide helper and autoload it in the hook.

Ok, let’s start, first, we can clone a CI4 project by run command:

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

We can use PHP-Development server launcher by go to CodeIgniter4 directory and run command:

php serve.php 

and we will get the page:
ci4
The view of “Home::index()” of controller is placed at application/Views/welcome_message.php:

application/
├── Config
├── Controllers
│   └── Home.php
├── Views
│   ├── errors
│   ├── form.php
│   └── welcome_message.php

with code called in the controller like the following:

<?php namespace App\Controllers;

use CodeIgniter\Controller;

class Home extends Controller
{
    public function index()
    {
        return view('welcome_message');  
    }
}

For example, we need to move the header and footer to the separate file, named application/Views/layout.php:

application/
├── Config
├── Controllers
│   └── Home.php
├── Views
│   ├── errors
│   ├── form.php
│   ├── layout.php
│   └── welcome_message.php

So, Let’s prepare the layout:

<html>
   <head></head> <!-- move code from welcome_message.php's html <head> to here -->
   <body>
        <!-- move code from welcome_message.php's style to here, 
             or use separate file for css and apply to <head>
        -->
	
        <div class="wrap">
            <?php echo $content; ?>
        </div>
   </body>
</html>

Now, we can write a helper to wrap it, for example, named render helper. We can create a file named application/Helpers/render_helper.php:

application/
├── Config
├── Controllers
├── Helpers
│   └── render_helper.php
├── Views

Our render helper can have function() for render view with layout functionality, which we get the content of view, and then apply to the layout:

<?php

if ( ! function_exists('render'))
{
    function render(string $name, array $data = [], array $options = [])
    {
        return view(
            'layout',
            [
                'content' => view($name, $data, $options),
            ],
            $options
        );
    }
}

If most of the controllers will use the render helper, we can then autoload it in the Events:

application/
├── Config
│   ├── Events.php
├── Controllers
├── Helpers
├── Views

with post_controller_constructor event point:

<?php namespace Config;

use CodeIgniter\Events\Events;

Events::on('post_controller_constructor', function() {
    helper('render');
});

And now, we are ready to use the render() function in the controller:

<?php namespace App\Controllers;

use CodeIgniter\Controller;

class Home extends Controller
{
    public function index()
    {
        return render('welcome_message');  
    }
}

Done 😉

Unit and Functional testing Zend Framework 3 Controller with Kahlan 3.0

Posted in testing, Tutorial PHP, Zend Framework 2 by samsonasik on October 24, 2016

This post will cover unit and functional testing ZF3 Controller with Kahlan 3.0. For example, you have a ZF3 Skeleton application with an IndexController like the following:

namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class IndexController extends AbstractActionController
{
    public function indexAction()
    {
        return new ViewModel();
    }
}

As usual, we need to require kahlan/kahlan:^3.0 via composer command:

composer require --dev kahlan/kahlan:^3.0 --sort-packages

You can then write the spec. Let’s write our spec inside module/Application/spec like the following structure:

module/Application/
├── config
├── spec
│   ├── Controller
│   │   ├── IndexControllerDispatchSpec.php
│   │   └── IndexControllerSpec.php
├── src
│   ├── Controller
│   │   ├── IndexController.php

if we are only have the 1 module, named Application module, we can define the spec and src path via kahlan-config.php like the following:

// ./kahlan.config.php
$commandLine = $this->commandLine();
$commandLine->option('spec', 'default', 'module/Application/spec');
$commandLine->option('src', 'default', 'module/Application/src');

Or for multi-modules, we can run parallel command that specify --spec and --src in command like the following:

vendor/bin/kahlan --spec=module/Application/spec --src=module/Application/src

in each iteration. If you’re using ant, you can write a build.xml for tasks definition:

<?xml version="1.0" encoding="UTF-8"?>
<project name="My Website" default="build">
    
    <!-- executable files directory definition -->
    <property name="toolsdir" value="${basedir}/vendor/bin/"/>
    <!-- module directory definition --> 
    <property name="moduledir" value="${basedir}/module/"/>

    <target name="build"
            depends="kahlan"
            description=""/>

    <target name="kahlan"
            description="Run kahlan">
        
        <parallel>    
        
            <!-- Application -->    
            <exec executable="${toolsdir}kahlan" failonerror="true" taskname="kahlan">
                <arg 
                    line="-spec=${moduledir}Application/spec/ 
                    --src=${moduledir}Application/src"/>
            </exec>
            <!-- Application -->
            
            <!-- other modules run test definition go here --> 
        </parallel>
        
    </target>

</project>

Unit testing

Let’s write the unit testing inside spec/Controller/IndexControllerSpec.php:

namespace ApplicationSpec\Controller;

use Application\Controller\IndexController;
use Zend\View\Model\ViewModel;

describe('IndexController', function () {
    
    given('controller', function () {
        
        return new IndexController();
    
    });
    
    describe('->indexAction()', function() {
        
        it('instance of ViewModel', function() {
            
            $actual = $this->controller->indexAction();
            expect($actual)->toBeAnInstanceOf(ViewModel::class);
            
        });
        
    });
    
});

That’s enough for IndexController::indexAction() unit test, nothing complex logic we need to accomodate as it only return the ViewModel instance, so we just need to check if return values is instance of ViewModel.

Functional Testing

Now, we need to make sure if the dispatch response of IndexController::indexAction() by open ‘/’ url that shown by user is the expected result, that show a welcome page, let’s do with spec/Controller/IndexControllerDispatchSpec.php:

namespace ApplicationSpec\Controller;

use Zend\Console\Console;
use Zend\Mvc\Application;

describe('IndexController Dispatch', function () {
    
    // setup the Application
    beforeAll(function () {
        
        Console::overrideIsConsole(false);
        $appConfig = include __DIR__ . '/../../../../config/application.config.php';
        $this->application = Application::init($appConfig);

        $events = $this->application->getEventManager();
        $this->application->getServiceManager()
                          ->get('SendResponseListener')
                          ->detach($events);

    });
    
    // dispatch '/' page tests
    describe('/', function() {
        
        it('contains welcome page', function() {
            
            $request     = $this->application->getRequest();
            
            $request->setMethod('GET');
            $request->setUri('/'); 
            
            // run app with '/' url
            $app =  $this->application->run();
            
            // expect actual response is contain
            // a welcome page
            expect(
                $app->getResponse()->toString()
            )->toContain('<h1>Welcome to <span class="zf-green">Zend Framework</span></h1>');
            
        });
        
    });
    
});

That’s it 😉