Welcome to Abdul Malik Ikhsan's Blog

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

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

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

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

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

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

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

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

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

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

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

Last step, we can run:

➜ composer update

That’s it!

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

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

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

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

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

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

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

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

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

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

➜ sudo pecl install apcu

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

# your php.ini
apc.use_request_time = 0

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

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

declare(strict_types=1);

namespace App\Cache;

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

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

        return new CacheItemPoolDecorator($storage);
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        // ...
    }
    // ...

That’s it!

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

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

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

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

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

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

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

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

Now, our composer.json will look like this:

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

That’s it!