Welcome to Abdul Malik Ikhsan's Blog

Create templated 404 page in Slim 4 with CallableResolver

Posted in Slim 4, Tutorial PHP by samsonasik on November 8, 2019

In Slim 4 Skeleton, the 404 response handled in App\Application\Handlers\HttpErrorHandler::respond() which check against Slim\Exception\HttpNotFoundException. We can create a templated 404 page for it with utilize Slim\CallableResolver via callableResolver property which can resolve the callable handler. I assume that we are using Twig template engine, and already has setup of Twig service like in my previous post.

We can create a not found handler like the following in src/Application/Handlers/NotFoundHandler.php

<?php
// src/Application/Handlers/NotFoundHandler.php
declare(strict_types=1);

namespace App\Application\Handlers;

use Psr\Http\Message\ResponseInterface;
use Slim\Views\Twig;

use function compact;

class NotFoundHandler
{
    private $view;

    public function __construct(Twig $view)
    {
        $this->view = $view;
    }

    public function __invoke(
        ResponseInterface $response,
        string            $message
    ): ResponseInterface {
        return $this->view->render($response, '404.html.twig', compact('message'));
    }
}

We can create a view based on it like the following at templates/404.html.twig:

{# templates/404.html.twig #}

{% extends "layout.html.twig" %}
{% block title '404 - '~parent() %}

{% block body %}

{{ message }}

{% endblock %}

Now, in App\Application\Handlers\HttpErrorHandler::respond(), we can check when $exception instanceof HttpNotFoundException to make a self called resolved NotFoundHandler.

// src/Application/Handlers/HttpErrorHandler.php
// ...
    protected function respond(): Response
    {
        // ...
        if ($exception instanceof HttpNotFoundException) {
            $response = $this->responseFactory->createResponse($statusCode);
            return ($this->callableResolver->resolve(NotFoundHandler::class)(
                $response,
                $exception->getMessage()
            ));
        }
        // ...
    }
// ...

So, when the Slim\Exception\HttpNotFoundException thrown, it will shown the 404 page with brought the message passed into it like the following:

Bonus

We can create a handling against Request as well, eg: show 404 templated page only when request doesn’t has Accept: application/json or X-Requested-With:XmlHttpRequest header, so, we can modify like the following:

// src/Application/Handlers/HttpErrorHandler.php
// ...
    protected function respond(): Response
    {
        // ...
        if ($exception instanceof HttpNotFoundException) {            
            $isAppJsonAccept  = $this->request->getHeaderLine('Accept')           === 'application/json';
            $isXmlHttpRequest = $this->request->getHeaderLine('X-Requested-With') === 'XmlHttpRequest';

            if (! $isAppJsonAccept && ! $isXmlHttpRequest) {
                $response = $this->responseFactory->createResponse($statusCode);
                return ($this->callableResolver->resolve(NotFoundHandler::class)(
                    $response,
                    $exception->getMessage()
                ));
            }

            // already return early, no need else
            $error->setType(ActionError::RESOURCE_NOT_FOUND);
        }
        // ...
    }
// ...

So, for example, when called via ajax, it will show like the following:

Create “Abstract Factory” service in PHP-DI with RequestedEntry and Wildcards

Posted in Slim 4, Tutorial PHP by samsonasik on November 6, 2019

If you’re familiar with Zend Framework’s servicemanager, you may already used the abstract factory which acts as limbo when service not registered. When the pattern checked is matched, it will try to create service based on it, automatically.

We can do that in PHP-DI as well. For example, we are experimenting with my previous post on DDD in slim 4 framework:

├───Application
├───Domain
│   ├───DomainException
│   │       DomainException.php
│   │       DomainRecordNotFoundException.php
│   │
│   ├───Post
│   │       Post.php
│   │       PostNotFoundException.php
│   │       PostRepository.php
│
└───Infrastructure
    └───Persistence
        ├───Post
        │       ZendDbPostRepository.php

We are going to create service with pattern “App\Domain\Post\PostRepository” from App\Infrastructure\Persistence namespace with as an object from “App\Infrastructure\Persistence\Post\ZendDbPostRepository”. In PHP-DI, we can combine both RequestedEntry and Wildcards definitions, the service definition will be like the following:

<?php
declare(strict_types=1);

use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Db\TableGateway\TableGateway;
use Zend\Hydrator\ObjectPropertyHydrator;
use DI\Factory\RequestedEntry;

return function (ContainerBuilder $containerBuilder) {

    $containerBuilder->addDefinitions([

        'App\Domain\*\*Repository' => function (RequestedEntry $entry, ContainerInterface $c) {

            // get entity class name, 
            // eg: "Post" by service named "App\Domain\Post\PostRepository" 
            preg_match(
                '/(?<=App\\\\Domain\\\\)([A-Z][a-z]{1,})(?=\\\\\1Repository)/',
                $entry->getName(),
                $matches
            );
            $entity          = current($matches);
            $fullEntityClass = 'App\Domain' . str_repeat('\\' . $entity, 2);
            $fullRepoClass   = 'App\Infrastructure\Persistence' . '\\' . $entity . '\ZendDb' . $entity . 'Repository';

            $tableGateway = new TableGateway(
                $fullEntityClass::TABLE,
                $c->get(AdapterInterface::class),
                null,
                new HydratingResultSet(new ObjectPropertyHydrator(), new $fullEntityClass)
            );

            return new $fullRepoClass($tableGateway);
        },

    ]);
};

That’s it!

Tagged with:

Apply Twig Extension via Middleware in Slim 4

Posted in Slim 4 by samsonasik on October 28, 2019

If we use Slim 4, we can use Twig for template engine, and there is slim/twig-view for that. However, when dealing with extension, especially routing and uri related stuff, we can’t use a way like in Slim 3, at least, by default, at least, what I know right now. There is an alternative way to add twig extension, which is via Middleware!

First, I assume that the Twig service already registered in app/dependencies.php:

<?php
// app/dependencies.php
declare(strict_types=1);

use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;
use Slim\Views\Twig;

return function (ContainerBuilder $containerBuilder) {
    $containerBuilder->addDefinitions([
        // ...
        Twig::class => function (ContainerInterface $c) {
            return new Twig(__DIR__ . '/../templates', [
                'cache' => __DIR__ . '/../var/cache',
                'auto_reload' => true
            ]);
        },
        // ...
    ]);
};

Now, we need to add extension, which consume Slim\Routing\RouteCollector and Slim\Psr7\Uri instance. Now, we can create middleware on the fly in app/middleware.php, eg: via anonymous class that implements Psr\Http\Server\MiddlewareInterface.

<?php
// app/middleware.php
declare(strict_types=1);

// ...
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\App;
use Slim\Routing\RouteCollector;
use Slim\Views\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
// ...

return function (App $app) {

    // ...
    $container = $app->getContainer();
    $twig      = $container->get(Twig::class);
    $router    = $app->getRouteCollector();

    $app->add(new class ($twig, $router) implements MiddlewareInterface {

        private $twig;
        private $router;

        public function __construct(Twig $twig, RouteCollector $router)
        {
            $this->twig   = $twig;
            $this->router = $router;
        }

        public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
        {
           $uri = $request->getUri();
           $this->twig->addExtension(new class ($this->router, $uri) extends AbstractExtension {

                private $router;
                private $uri;

                public function __construct(RouteCollector $router, UriInterface $uri)
                {
                    $this->router = $router;
                    $this->uri    = $uri;
                }

                public function getFunctions()
                {
                    return [
                        new TwigFunction('base_path', function () : string {
                            return $this->router->getBasePath();
                        }),
                        new TwigFunction('full_url_for', function (string $routeName, array $data = [], array $queryParams = []) : string {
                            return $this->router->getRouteParser()->fullUrlFor($this->uri, $routeName, $data, $queryParams);
                        }),
                    ];
                }

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

In its __construct, we inject with Slim\Views\Twig and Slim\Routing\RouteCollector instance. In its process() function, we call Slim\Views\Twig::addExtension() function to add extension, which we can create another anonymous class that extends Twig\Extension\AbstractExtension, injected with Slim\Routing\RouteCollector and Slim\Psr7\Uri (which pulled from object instance of Psr\Http\Message\ServerRequestInterface), which we can add getFunctions() method to add twig functions.

That’s it, in above example, I added base_path and full_url_for function which now can be used in twig view, eg:

    {# display base path url #}
    {{ base_path() }}

    {# display full url for route with name "postpagedetail" with parameter id = 1 and query parameter action = view  #}
    {{ full_url_for('postpagedetail', { 'id': 1 }, { 'action': 'view' }) }}

You are feeling have too much code in app/middleware.php ? Of course, you can create a separate class for it!

Using DDD architecture with zend-db in Slim 4

Posted in Slim 4 by samsonasik on October 27, 2019

Slim 4 provides a skeleton that uses DDD directory style. By this, we can implement DDD architecture easier. Let’s try with zend-db for it. In this post, I assume that you already uses Twig as view template engine like in previous post.

Requirements

First, we need to require zend-db via composer:

$ composer require zendframework/zend-db \
    zendframework/zend-hydrator \
    --sort-packages

We are going to make pages to display post table record(s) with the following data:

# create and use database
create database slim;
use slim;

# create table
create table post(id int not null primary key auto_increment, title varchar(50) not null, content text not null);

# insert data
insert into post(title, content) values('first post', 'first post content');
insert into post(title, content) values('second post', 'second post content');

Database Configuration

<?php
// app/settings.php
// ...
return function (ContainerBuilder $containerBuilder) {
    $containerBuilder->addDefinitions([
            // ...
            'db' => [
                'username' => 'root',
                'password' => '',
                'driver'   => 'pdo_mysql',
                'database' => 'slim',
                'host'     => 'localhost',
                'driver_options' => [
                    \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',
                ],
            ],
            // ...
        ],
    ]);
};

We can register database configuration at app/settings.php, for example, use MySQL database as configured above.

DB Service

We are using service named Zend\Db\Adapter\AdapterInterface, so we can register as follow in app/dependencies.php:

<?php
// app/dependencies.php
declare(strict_types=1);

use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Adapter\AdapterInterface;

return function (ContainerBuilder $containerBuilder) {
    $containerBuilder->addDefinitions([
        // ...
        AdapterInterface::class => function (ContainerInterface $c) {
            return new Adapter($c->get('settings')['db']);
        },
        // ...
    ]);
};

Domain

a. Post class:

<?php
// src/Domain/Post/Post.php
declare(strict_types=1);

namespace App\Domain\Post;

final class Post
{
    public $id;
    public $title;
    public $content;

    public const TABLE = 'post';

    public function __construct(Post $post = null)
    {
        if ($post instanceof Post) {
            $this->id      = +$post->id;
            $this->title   = ucwords($post->title);
            $this->content = ucfirst($post->content);
        }
    }
}

b. PostRepository class:

<?php
// src/Domain/Post/PostRepository.php
declare(strict_types=1);

namespace App\Domain\Post;

interface PostRepository
{
    public function findAll(): array;
    public function findPostOfId(int $id): Post;
}

c. PostNotFoundException class:

<?php
// src/Domain/Post/PostNotFoundException.php
declare(strict_types=1);

namespace App\Domain\Post;

use App\Domain\DomainException\DomainRecordNotFoundException;

class PostNotFoundException extends DomainRecordNotFoundException
{
    public $message = 'The post you requested does not exist.';
}

Infrastructure

We can create a repository that implements App\Domain\Post\PostRepository, eg: named ZendDbPostRepository:

<?php
// src/Infrastructure/Persitence/Post/ZendDbPostRepository.php
declare(strict_types=1);

namespace App\Infrastructure\Persistence\Post;

use function compact;

use App\Domain\Post\Post;
use App\Domain\Post\PostNotFoundException;
use App\Domain\Post\PostRepository;
use Zend\Db\TableGateway\AbstractTableGateway;

class ZendDbPostRepository implements PostRepository
{
    private $tableGateway;

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

    public function findAll(): array
    {
        $results = $this->tableGateway->select();
        $posts   = [];

        foreach ($results as $result) {
            $posts[] = new Post($result);
        }

        return $posts;
    }

    public function findPostOfId(int $id): Post
    {
        $current = $this->tableGateway->select(compact('id'))->current();
        if (! $current) {
            throw new PostNotFoundException();
        }

        return new Post($current);
    }
}

The Repository class above needs to be registered to app/repositories.php:

<?php
// app/repositories.php
declare(strict_types=1);

use App\Domain\Post\Post;
use App\Domain\Post\PostRepository;
use App\Infrastructure\Persistence\Post\ZendDbPostRepository;
use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Db\TableGateway\TableGateway;
use Zend\Hydrator\ObjectPropertyHydrator;

return function (ContainerBuilder $containerBuilder) {
    $containerBuilder->addDefinitions([
        // ...
        PostRepository::class => function (ContainerInterface $c) {
            $tableGateway = new TableGateway(
                Post::TABLE,
                $c->get(AdapterInterface::class),
                null,
                new HydratingResultSet(new ObjectPropertyHydrator(), new Post())
            );

            return new ZendDbPostRepository($tableGateway);
        },
        // ...
    ]);
};

Application

For application, we can create 2 classes to display all posts and display post by id.

a. All posts via PostPage class

<?php
// src/Application/Page/PostPage.php

declare(strict_types=1);

namespace App\Application\Page;

use App\Domain\Post\PostRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Response;
use Slim\Views\Twig;

class PostPage implements RequestHandlerInterface
{
    private $view;
    private $postRepository;

    public function __construct(Twig $view, PostRepository $postRepository)
    {
        $this->view           = $view;
        $this->postRepository = $postRepository;
    }

    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        return $this->view->render(
            new Response(),
            'page/post/index.html.twig',
            [
                'posts' => $this->postRepository->findAll(),
            ]
        );
    }
}

b. Post By Id via PostPageDetail class

<?php
// src/Application/Page/PostPageDetail.php

declare(strict_types=1);

namespace App\Application\Page;

use App\Domain\DomainException\DomainRecordNotFoundException;
use App\Domain\Post\PostRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Exception\HttpNotFoundException;
use Slim\Psr7\Response;
use Slim\Views\Twig;

class PostPageDetail implements RequestHandlerInterface
{
    private $view;
    private $postRepository;

    public function __construct(Twig $view, PostRepository $postRepository)
    {
        $this->view           = $view;
        $this->postRepository = $postRepository;
    }

    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        $id = +$request->getAttribute('id');
        try {
            $post = $this->postRepository->findPostOfId($id);
        } catch (DomainRecordNotFoundException $e) {
            throw new HttpNotFoundException($request, $e->getMessage());
        }

        return $this->view->render(
            new Response(),
            'page/post/detail.html.twig',
            [
                'post' => $post,
            ]
        );
    }
}

Routing

Now, time to register routing:

<?php
// app/routes.php
declare(strict_types=1);

use App\Application\Page\PostPage;
use App\Application\Page\PostPageDetail;
use Slim\App;
use Slim\Handlers\Strategies\RequestHandler;

return function (App $app) {
    $routeCollector = $app->getRouteCollector();
    $routeCollector->setDefaultInvocationStrategy(
        new RequestHandler(true)
    );

    $app->get('/posts', PostPage::class);
    $app->get('/posts/{id:[0-9]+}', PostPageDetail::class);
};

View

a. templates/page/post/index.html.twig:

{# templates/page/post/index.html.twig #}
{% extends "layout.html.twig" %}

{% block body %}

    {% for post in posts %}
        <h3> {{ post.title }} </h3>

        <p> {{ post.content }} </p>
    {% endfor %}

{% endblock %}

b. templates/page/post/detail.html.twig:

{# templates/page/post/detail.html.twig #}
{% extends "layout.html.twig" %}

{% block body %}

    <h3> {{ post.title }} </h3>
    <p>  {{ post.content }} </p>

{% endblock %}

Run PHP Server

php -S localhost:8080 -t public

Now, we can see the pages, http://localhost:8080/posts for all posts:

and http://localhost:8080/posts/1 for detail post:

Using routed PSR-15 RequestHandlerInterface in Slim 4

Posted in Slim 4 by samsonasik on September 2, 2019

Slim 4 already support PSR-15, and we can use class implements Psr\Http\Server\RequestHandlerInterface that produce response and register to the routes. First, we can install Slim-Skeleton to get started:

$ composer create-project slim/slim-skeleton

After it, for example, we have the following AboutPage class:

<?php
// src/Application/Page/AboutPage.php
declare(strict_types=1);

namespace App\Application\Page;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Response;

class AboutPage implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        $response = new Response();
        $response->getBody()->write('about page');

        return $response;
    }
}

We can register above to routes configuration in app/routes.php as follow:

<?php
// app/routes.php
declare(strict_types=1);

use App\Application\Page\AboutPage;
use Slim\App;

return function (App $app) {
    // ...
    $app->get('/about', AboutPage::class);
};

To test, we can run PHP Development Server:

$ php -S localhost:8080 -t public

Then open it in the web browser as http://localhost:8080/about :

How about injecting service(s) to it?

We can supply the service(s) into its __construct and it will automatically injected with the service(s), eg: we need to inject service named Psr\Log\LoggerInterface which its service definition already registered in app/dependencies.php:

<?php
// src/Application/Page/AboutPage.php
// ...
use Psr\Log\LoggerInterface;
// ...
class AboutPage implements RequestHandlerInterface
{
    private $logger;

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

    // ...
}

How about add template?

For example, we want to use slim/twig-view package for it, we can first require it:

$ composer require slim/twig-view

After it, we can create cache and templates directories for it:

# create var/cache directory
$ mkdir -p var/cache

# ignore cached data from git repo (in case use git as version control) 
$ touch var/cache/.gitignore && (echo '*' && echo '!.gitignore') > var/cache/.gitignore

# create templates directories
$ mkdir -p templates/page

By above directory structure, we can register Slim\Views\Twig service in app/dependencies.php as follow:

<?php
// app/dependencies.php
// ...
use Slim\Views\Twig;

return function (ContainerBuilder $containerBuilder) {
    $containerBuilder->addDefinitions([
        // ...
        Twig::class => function (ContainerInterface $c) {
            return new Twig(__DIR__ . '/../templates', [
                'cache'       => __DIR__ . '/../var/cache',
                'auto_reload' => true
            ]);
        },
        // ...
    ]);
};

After it, we can prepare the templates with the following structure:

├───templates
│   │   layout.html.twig
│   └───page
│           about.html.twig

Next, apply templates/layout.html.twig:

<!-- templates/layout.html.twig -->
<!DOCTYPE html>
<html>
    <head>
        <title>{% block title %}Slim App{% endblock %}</title>
    </head>
    <body>
        {% block body %}{% endblock %}
    </body>
</html>

Next, apply templates/page/about.html.twig:

<!-- templates/page/about.html.twig -->
{% extends "layout.html.twig" %}

{% block body %}

About Page.

{% endblock %}

Finally, in the AboutPage class, we can inject Slim\Views\Twig instance and use it to render the twig view as instance of Slim\Psr7\Response:

<?php
// src/Application/Page/AboutPage.php
declare(strict_types=1);

namespace App\Application\Page;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Response;
use Slim\Views\Twig;

class AboutPage implements RequestHandlerInterface
{
    private $view;

    public function __construct(Twig $view)
    {
        $this->view = $view;
    }

    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        return $this->view->render(new Response(), 'page/about.html.twig');
    }
}

How about getting request attribute value?

To do this, we need to enable it in the routing by apply Slim\Handlers\Strategies\RequestHandler as its router invocation strategy, so, for example, we need to pass optional “id” parameter, we can do in app/routes:

<?php
// app/routes.php
declare(strict_types=1);

use App\Application\Page\AboutPage;
use Slim\App;
use Slim\Handlers\Strategies\RequestHandler;

return function (App $app) {
    // ...
    $app->get('/about[/{id:[0-9]+}]', AboutPage::class)
        ->setInvocationStrategy(new RequestHandler(true));
};

We can also set default invocation strategy by apply it before register the route(s) that the pages implements RequestHandlerInterface:

<?php
// app/routes.php
declare(strict_types=1);

use App\Application\Page\AboutPage;
use Slim\App;
use Slim\Handlers\Strategies\RequestHandler;

return function (App $app) {
    $routeCollector = $app->getRouteCollector();
    // ... before setDefaultInvocationStrategy placement,
    // ... it will compatible with RequestResponse strategy    
  
    $routeCollector->setDefaultInvocationStrategy(new RequestHandler(true));
    // ... after setDefaultInvocationStrategy placement,
    // ... it will compatible only with selected default invocable strategy

    $app->get('/about[/{id:[0-9]+}]', AboutPage::class);
};

So, from now on, we can get attribute in the handle() method and passed parameter to the view:

<?php
// src/Application/Page/AboutPage.php
declare(strict_types=1);

namespace App\Application\Page;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Response;
use Slim\Views\Twig;
use Psr\Log\LoggerInterface;

class AboutPage implements RequestHandlerInterface
{
    private $view;

    public function __construct(Twig $view)
    {
        $this->view = $view;
    }

    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        $id = $request->getAttribute('id');

        return $this->view->render(
            new Response(),
            'page/about.html.twig',
            [
                'id' => $id
            ]
        );
    }
}

So, in the view, we can do:

<!-- templates/page/about.html.twig -->
{% extends "layout.html.twig" %}

{% block body %}

About Page with id {{ id }}

{% endblock %}

and when open http://localhost:8080/about/1 , we can get:

That’s it!

Tagged with: , , , , ,

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!