Welcome to Abdul Malik Ikhsan's Blog

Install Imagick extension for PHP 8 in macOS Big Sur

Posted in Teknologi, Tutorial PHP by samsonasik on January 4, 2021

I am really grateful I’ve had a chance to have the Mac mini M1 on a new year 2021. It’s really fast, even I only use 8 gigs ram version! Also, the software that in my old mac no longer can use its latest update – like php with homebrew – is back! In this post, I will show you how to install imagick for PHP 8 in macOS Big Sur for it.

First, I assume that you already have Homebrew installed. Next is install PHP 8, we can use shivammathur/php for it, the installation steps are in its readme.

When PHP 8 installed, we can ensure that imagemagick already installed, if not, if we can install via Homebrew as well:

$ brew install imagemagick

Now, time to install imagick. There is no imagick release yet for php 8, but we can install the dev-master already following this issue :

$ git clone https://github.com/Imagick/imagick
$ cd imagick
$ phpize && ./configure
$ make
$ sudo make install

When you run make, if you got the following error:

/opt/homebrew/Cellar/php/8.0.0_1/include/php/ext/pcre/php_pcre.h:23:10: fatal error: 'pcre2.h' file not found

You can copy installed pcre2.h from Homebrew:

cp /opt/homebrew/Cellar/pcre2/10.36/include/pcre2.h .

Next, you can re-run :

$ make
$ sudo make install

After it installed, you can register imagick.so to /opt/homebrew/etc/php/8.0/php.ini:

extension="imagick.so"

Now, let’s check if it is installed:

➜  ~ php -a
Interactive shell

php > echo phpversion('imagick');
@PACKAGE_VERSION@

Yes, above @PACKAGE_VERSION@ should be just fine. That’s it!

Install php 8.0 in MacOS Sierra with Macports

Posted in php, Teknologi by samsonasik on December 6, 2020

Yes, you read it right. I live in the past, with old hardware, with legacy operating system. There is homebrew that previously still works in php 7.4 in early releases, but now no longer works. There is an alternative like a one click install app like MAMP PRO but didn’t support php 8.0 yet. XAMPP ? not really extension installable user friendly (as far as I tried)!

I don’t want to lost hope, I give a Macports a try, and it works while it not documented in the wiki yet. If you are using MacOS Sierra and want to try, you can do the following steps:

1. First, you need to have Macports installed first, that we can read in the documentation.
2. Install php 8.0

sudo port install php80

You will get the following note in the end of installation process:

 To customize php80, copy /opt/local/etc/php80/php.ini-development (if this is a development server) or /opt/local/etc/php80/php.ini-production (if this is a production server) to /opt/local/etc/php80/php.ini and then make changes.

You can follow that if you want to changes the PHP configuration.

3. Install common extensions

For example, you will need intl and mbstring extension, you can run:

sudo port install php80-intl
sudo port install php80-mbstring 

You can search already available extensions in the search page.

4. Now, you can check if it is actually installed:

php80 --version

That’s it πŸ˜‰

How to Create Typo Variable Fixer with Rector

Posted in php, Rector, Teknologi by samsonasik on October 21, 2020

Rector is a code refactoring tool that can help us with major code changes (like upgrade legacy code) or daily work. There are already many rules that ready to use for us.

What if we want a custom rule, like we want a daily work can to do “Typo” check in variables? In this post, I want to show you how to create a Typo Variable Fixer with Rector, a custom Rector rule!

Preparation

First, let say, we build a new app, we use composer for it with add rector/rector to require-dev:

composer init


  Welcome to the Composer config generator



This command will guide you through creating your composer.json config.

Package name (<vendor>/<name>) [samsonasik/how-to-create-typo-variable-fixer]: samsonasik/app

Description []: App Demo

Author [Abdul Malik Ikhsan <samsonasik@gmail.com>, n to skip]:

Minimum Stability []:

Package Type (e.g. library, project, metapackage, composer-plugin) []:

License []: MIT

Define your dependencies.

Would you like to define your dependencies (require) interactively [yes]? no
Would you like to define your dev dependencies (require-dev) interactively [yes]? yes
Search for a package: rector/rector
Enter the version constraint to require (or leave blank to use the latest version):

Using version ^0.8.40 for rector/rector

Search for a package:

{
    "name": "samsonasik/app",
    "description": "App Demo",
    "require-dev": {
        "rector/rector": "^0.8.40"
    },
    "license": "MIT",
    "authors": [
        {
            "name": "Abdul Malik Ikhsan",
            "email": "samsonasik@gmail.com"
        }
    ],
    "require": {}
}

Do you confirm generation [yes]? yes
Would you like to install dependencies now [yes]? yes

After it, let say we need an app directory, we can create an app directory and write a php file inside it:

mkdir -p app && touch app/app.php

with file app/app.php content:

<?php

namespace App;

$previuos = 0;
$begining = 1;
$statment = $previuos . ' is lower than ' . $begining;

Yes, there are 3 typos in above file! For example, we will have a sample library.php file for common typos, for example, inside utils directory:

mkdir -p utils && touch utils/library.php

with file utils/library.php content:

<?php

namespace Utils;

return [
    'previous' => ['previuos', 'previuous'],
    'beginning' => ['begining', 'beginign'],
    'statement' => ['statment'],
];

We can setup composer autoload for with add the following to our composer.json file:

    "autoload": {
        "psr-4": {
            "App\\": "app"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Utils\\": "utils"
        }
    }

After it, run composer dump-autoload command:

composer dump-autoload

The preparation is done!

Create the Typo Fixer Rule

We can follow the Rector‘s documentation to create new custom rule. So, for example, we create TypoVariableFixerRule under own utils/Rector directory:

mkdir -p utils/Rector && touch utils/Rector/TypoVariableFixerRule.php

Our directory will looks like the following:

.
β”œβ”€β”€ app
β”‚   └── app.php
β”œβ”€β”€ composer.json
β”œβ”€β”€ utils
β”‚   β”œβ”€β”€ Rector
β”‚   β”‚   └── TypoVariableFixerRule.php
β”‚   └── library.php

Now, we can start create the TypoVariableFixerRule:

<?php

declare(strict_types=1);

namespace Utils\Rector;

use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;

final class TypoVariableFixerRule extends AbstractRector
{
    public function getNodeTypes(): array
    {
        return [Variable::class];
    }

    /**
     * @param Variable $node
     */
    public function refactor(Node $node): ?Node
    {
        return $node;
    }

    public function getDefinition(): RectorDefinition
    {
        return new RectorDefinition(
            'Change Typo in variable', [
                new CodeSample(
                    // code before
                    '$previuos',
                    // code after
                    '$previous'
                ),
            ]
        );
    }
}

Above, we extends AbstractRector for new Rector rule. We operate with nikic/php-parser to do refactor. The getNodeTypes returns the node that we want to refactor, at this case, we want to refactor Variable node in our refactor method.

Before we continue, let’s register our new TypoVariableFixerRule to rector config to ensure it works. We can create rector config as follow:

touch rector.php

with file rector.php content:

<?php

use Rector\Core\Configuration\Option;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Utils\Rector\TypoVariableFixerRule;

return static function (ContainerConfigurator $containerConfigurator): void {
    $parameters = $containerConfigurator->parameters();
    $parameters->set(Option::PATHS, [__DIR__ . '/app']);

    $services = $containerConfigurator->services();
    $services->set(TypoVariableFixerRule::class);
};

and test with run:

vendor/bin/rector process

So we see the “Green” OK:

Now, time to make refactor work! We can modify the refactor method:

    public function refactor(Node $node): ?Node
    {
        // get the variable name
        $variableName = $this->getName($node);

        // get the library content
        $library = include 'utils/library.php';

        foreach ($library as $correctWord => $commonTypos) {
            if (! in_array($variableName, $commonTypos, true)) {
                continue;
            }

            $node->name = $correctWord;
            return $node;
        }

        return null;
    }

Above, we find if the variable name is in common typos, then we return node (as variable) with updated its name with the correct word. Now, let’s run it with --dry-run to see the diff that can be made:

vendor/bin/rector process --dry-run

and we can see:

Seems great! Let’s apply the changes:

vendor/bin/rector process

Awesome! We now already make typo fixer succesfully working! Let’s run again, and it will take no effect as already fixed:

That’s it!

How to Avoid –stderr When Running phpunit for Functional/Integration Testing

Posted in Teknologi, testing by samsonasik on March 3, 2020

When you do a Functional/Integration test with session and/or header relation. It will force you to use --stderr when running phpunit, or it will got error, eg: you’re testing that on logout when session exists as user, page will be redirected to login page with status code 302, and it got the following error:

$ vendor/bin/phpunit test/Integration/LogoutPageTest.php
PHPUnit 8.5.2 by Sebastian Bergmann and contributors.

Logout Page (AppTest\Integration\LogoutPage)
 ✘ Open logout page as auser redirect to login page
   ┐
   β”œ Failed asserting that 500 matches expected 302.
   β”‚
   β•΅ /Users/samsonasik/www/mezzio-authentication-with-authorization/test/Integration/LogoutPageTest.php:36
   β”΄

Time: 155 ms, Memory: 10.00 MB


FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

You can use --stderr option on running it:

$ vendor/bin/phpunit test/Integration/LogoutPageTest.php --stderr
PHPUnit 8.5.2 by Sebastian Bergmann and contributors.

Logout Page (AppTest\Integration\LogoutPage)
 √ Open logout page as auser redirect to login page

Time: 150 ms, Memory: 8.00 MB

OK (1 test, 2 assertions)

or define stderr=true in phpunit.xml configuration:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
         testdox="true"
         stderr="true">

    <!-- testsuites, filter, etc config -->   

</phpunit>

Marking all test to be using stderr is a workaround, as not all tests actually require that, eg: unit test doesn’t need that. To avoid it, we can define @runTestsInSeparateProcesses and @preserveGlobalState disabled in the controller class that require that, so, the test class will be like the following:

<?php

declare(strict_types=1);

namespace AppTest\Integration;

use Laminas\Diactoros\ServerRequest;
use Laminas\Diactoros\Uri;
use Mezzio\Authentication\UserInterface;
use PHPUnit\Framework\TestCase;

/**
 * @runTestsInSeparateProcesses
 * @preserveGlobalState disabled
 */
class LogoutPageTest extends TestCase
{
    private $app;

    protected function setUp(): void
    {
        $this->app = AppFactory::create();
    }

    public function testOpenLogoutPageAsAuserRedirectToLoginPage()
    {
        $sessionData                    = [
            'username' => 'samsonasik',
            'roles'    => [
                'user',
            ],
        ];
        $_SESSION[UserInterface::class] = $sessionData;

        $uri           = new Uri('/logout');
        $serverRequest = new ServerRequest([], [], $uri);

        $response = $this->app->handle($serverRequest);
        $this->assertEquals(302, $response->getStatusCode());
        $this->assertEquals('/login', $response->getHeaderLine('Location'));
    }
}

That’s it!

Run test and coverage report with ant build in Windows

Posted in Teknologi, testing 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:

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!

Create API Service in CodeIgniter 4 with Request Filtering

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

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

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

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

namespace App\Controllers\Api;

use function time;

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

final class Ping extends Controller
{
    use ResponseTrait;

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

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

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

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

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

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

{
    "ack": 1537596713
}

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

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

namespace App\Filters;

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

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

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

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

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

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

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

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

    // ...

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

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

GET: Method Not Allowed 405

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

POST: Got Response OK 200

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

{
    "ack": 1537596774
}

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

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

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

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

Let’s start by install a fresh skeleton:

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

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

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

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

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

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

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

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

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

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

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

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

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

The New Middleware

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return new Response();
}

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

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

return new Response();

The complete middleware class can be as follow:

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

namespace App\Middleware;

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

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

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

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

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

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

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

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

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

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

            return new Response();
        }

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

        return new Response();
    }
}

FilePrgMiddleware Service Registration

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

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

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

The Upload Form and Its Page

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

<?php

namespace App\Form;

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

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

        $this->init();
    }

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

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

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

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

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

Next, we can create an Upload Page Handler :

<?php

declare(strict_types=1);

namespace App\Handler;

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

class UploadPageHandler  implements MiddlewareInterface
{
    private $template;

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

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

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

$ touch templates/app/upload-page.phtml

and write with the form helper:

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

echo $this->form($form);

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

<?php

declare(strict_types=1);

namespace App\Handler;

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

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

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

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

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

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

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

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

Using The FilePrg in UploadPageHandler

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

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

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

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

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

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

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

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

That’s it ;).

Tagged with: ,

Introduce IsDeprecated: PHP7+ Helper for E_USER_DEPRECATED and E_DEPRECATED Detection

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

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

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

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

When reality is not always what you want

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

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

Installation

This helper can be installed via composer:

composer require samsonasik/is-deprecated

This helper have features:

1. Detect E_USER_DEPRECATED

  • At independent function
  • At function inside class

You can use IsDeprecated\isDeprecatedUser function with signature:

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

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

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

1a. Independent Function

The usage is like the following:

use function IsDeprecated\isDeprecatedUser;

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

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

1.b Function Inside Class

The usage is like the following:

use function IsDeprecated\isDeprecatedUser;

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

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

        $this->foo();
    }
}

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

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

2. Detect E_DEPRECATED

E_DEPRECATED can be triggered on Core PHP function call.

You can use IsDeprecated\isDeprecatedCore function with signature:

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

The usage is like the following:

use function IsDeprecated\isDeprecatedCore;

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

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

Limitation

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

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

That’s it ;).

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. (support zend-servicemanager, symfony, aura, auryn, and pimple for zend-expressive application at version 2.9.0)
  • Make support for non zend-view for custom page template engine when error happen. (supported at version 2.1.0)

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

Testing Hard Dependency with AspectMock

Posted in Teknologi, testing, Tutorial PHP by samsonasik on January 28, 2016

This is another testing legacy application post. Don’t tell your client/other people to refactor, if your job is only to make tests for it, as at some situations, there are reasons to not refactor. You may have situation to test hard dependency that impossible to be mocked and stubbed. There is a library named AspectMock for it, that you can use in PHPUnit, for example.

So, to have it, you can require via composer:

composer require "codeception/aspect-mock:^0.5.5" --dev

For example, you have the following class:

namespace App;

class MyController
{
    public function save()
    {
        $user = new User();
        if (! $user->save()) {
            echo 'not saved';
            return;
        }

        echo 'saved';
    }
}

That bad, huh! Ok, let’s deal to tests it even you don’t really like it. First, setup your phpunit.xml to have ‘backupGlobals=”false”‘ config:

<?xml version="1.0" encoding="UTF-8"?>

<phpunit
    colors="true"
    backupGlobals="false"
    bootstrap="bootstrap.php">
    <testsuites>
        <testsuite name="AppTest">
            <directory suffix=".php">./test</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist addUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./src</directory>
        </whitelist>
    </filter>
</phpunit>

Now, based on config above, you can create bootstrap.php:

include 'vendor/autoload.php';

use AspectMock\Kernel;

$kernel = Kernel::getInstance();
$kernel->init([
    'debug' => true,
    'cacheDir' => __DIR__ . '/data/cache',
    'includePaths' => [__DIR__.'/src'],
]);

Assumption: You have ‘./data/cache’ for saving cache and ‘src/’ for your source code directory, if you use your own autoloader, you can add:

// ...
$kernel->loadFile('YourAutoloader.php');

as the AspectMock documentation mentioned.

Now, time to write the tests:

  1. Preparation
    namespace AppTest;
    
    use PHPUnit_Framework_TestCase;
    use App\MyController;
    use AspectMock\Test as test;
    
    class MyControllerTest extends PHPUnit_Framework_TestCase
    {
        private $myController;
    
        protected function setUp()
        {
            $this->myController = new MyController;
        }
    
        protected function tearDown()
        {
            test::clean(); // remove all registered test doubles
        }
    }
    

  2. write the test cases

class MyControllerTest extends PHPUnit_Framework_TestCase
{
    // ...
    public function provideSave()
    {
        return [
            [true, 'saved'],
            [false, 'not saved'],
        ];
    }

    /**
     * @dataProvider provideSave
     */
    public function testSave($succeed, $echoed)
    {
        // mock
        $userMock = test::double('App\User', ['save' => $succeed]);

        ob_start();
        $this->myController->save();
        $content = ob_get_clean();
        $this->assertEquals($echoed, $content);

        // stub
        $userMock->verifyInvoked('save');
    }
    // ...
}

Done πŸ˜‰

references:
https://github.com/Codeception/AspectMock
https://twitter.com/grmpyprogrammer/status/642847787713884160
https://littlehart.net/atthekeyboard/2014/12/14/stop-telling-me-to-refactor/

PHPUnit: Testing Closure passed to Collaborator

Posted in Teknologi, testing, Tutorial PHP by samsonasik on October 4, 2015

Yes! Closure is callable, so you can just call __invoke() to the closure returned when test it! This is happen when we, for example, have a class and function that have closure inside it like the following:

class Awesome
{
    public function call($foo)
    {
        return function() use ($foo) {
            return $foo;
        };
    }
}

This can be tested with:

use Awesome;
use PHPUnit_Framework_TestCase;

class AwesomeTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        $this->awesome = new Awesome();
    }

    public function testCall()
    {
        $foo = 'foo';
        $call = $this->awesome->call($foo);
        $this->assertTrue(is_callable($call));

        $invoked = $call->__invoke();
        $this->assertEquals($foo, $invoked);
    }
}

We need an __invoke() call, as the closure never executed before it invoked. So, we need to call that.

On Collaborator Case

The problem is when we have a collaborator, and closure is processed inside the collaborator:

class Awesome
{
    private $awesomeDependency;

    public function __construct(AwesomeDependency $awesomeDependency)
    {
        $this->awesomeDependency = $awesomeDependency;
    }

    public function call($foo)
    {
        $closure = function() use ($foo) {
            return $foo;
        };
        return $this->awesomeDependency->call($closure);
    }
}

and the closure inside call only executed in the AwesomeDependency class:

class AwesomeDependency
{
    public function call($call)
    {
        return $call();
    }
}

Our test can be like the following:

use Awesome;
use AwesomeDependency;
use PHPUnit_Framework_TestCase;

class AwesomeTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        $this->awesomeDependency = $this->prophesize(AwesomeDependency::class);
        $this->awesome     = new Awesome($this->awesomeDependency->reveal());
    }

    public function testCall()
    {
        $foo = 'foo';
        $closure = function() use ($foo) {
            return $foo;
        };

        $this->awesomeDependency
             ->call($closure)
             ->will(function() use ($closure) {
                return $closure->__invoke();
             })
             ->shouldBeCalled();

        $call = $this->awesome->call($foo);
    }
}

As we can see, the $this->awesomeDependency is act as a mock, and calling __invoke() in will() is represent a $closure that already passed to the mock, not the original $closure one, and we will get partial coverage:

closure-call-collaborator1

We know now, it won’t coverable as completed! What we can do? A refactor! But wait, it may be a legacy code, an aggressive refactor may cause problem, so a little but works patch may work for it.

  • Make a $closure as class property, and add mutator and accessor for it.
class Awesome
{
    private $closure;

    // ...

    public function setClosure($closure)
    {
        $this->closure = $closure;
    }

    public function getClosure()
    {
        return $this->closure;
    }

    // ...
}
  • Set $closure property when call call() function:
class Awesome
{
    // ...
    public function call($foo)
    {
        $this->setClosure(function() use ($foo) {
            return $foo;
        });

        return $this->awesomeDependency->call($this->getClosure());
    }
}
  • And in tests, we can now has:
class AwesomeTest extends PHPUnit_Framework_TestCase
{
    // ...
    public function testCall()
    {
        $foo = 'foo';
        $closure = function() use ($foo) {
            return $foo;
        };
        $awesome = $this->awesome;

        $this->awesomeDependency
             ->call($closure)
             ->will(function() use ($awesome) {
                return $awesome->getClosure()->__invoke();
            })
            ->shouldBeCalled();

        $call = $this->awesome->call($foo);
        $this->assertEquals($foo, $call);
    }
}

closure-call-collaborator2

Need a better way? We can replace a closure with an array callback, so, we add additional function that called via call_user_func_array():

class Awesome
{
    public function call($foo)
    {
        return $this->awesomeDependency->call(call_user_func_array(
            [$this, 'onFoo'],
            [$foo]
        ));
    }

    public function onFoo($foo)
    {
        return function() use ($foo) {
            return $foo;
        };
    }
}

And in our tests, we can do:

    public function testCall()
    {
        $foo = 'foo';
        $closure = function() use ($foo) {
            return $foo;
        };
        $awesome = $this->awesome;
        $this->awesomeDependency->call($closure)
             ->will(function() use ($awesome, $foo) {
                 return $awesome->onFoo($foo)->__invoke();
             })
            ->shouldBeCalled();

        $call = $this->awesome->call($foo);
        $this->assertEquals($foo, $call);
    }

And, we now have a fully coverage too:

closure-call-collaborator3

Tagged with: ,

Re-fill selectize js value

Posted in Javascript, Teknologi by samsonasik on November 28, 2014

It’s been a while since I didn’t write a post about non-framework category. Ok, this time, I will show you how to use selectize js on re-fill functionality. Selectize js is a jQuery plugin that useful for tagging and autocomplete. I used it in several projects. Once it’s installed and selectize() called, your form can be like the following :
selectize-1
In images demo above, I want to re-set the “district” based on the “province” changes by javascript. To make selectize still applied to “district”, you need to do :

  • re-set Html option values
  • re-set selectize value options

Ok, let’s do a demo application for this.
1. Preparation
1.a make bower.json for dependency requirements definition

{
    "name":"Selectize Demo",
    "dependencies": {
        "jquery": "1.11.1",
        "selectize":"0.11.2"
    }
}

1.b make .bowerrc for specification

{
    "directory": "js",
    "json": "bower.json"
}

1.c install dependencies

bower install

2. Initialize selectize
We can initialize selectize js by include it in the header ( js and css ) like this :

    <link href="./js/selectize/dist/css/selectize.default.css" media="screen" rel="stylesheet" type="text/css">

    <script type="text/javascript" src="./js/jquery/dist/jquery.min.js"></script>
    <script type="text/javascript" src="./js/selectize/dist/js/standalone/selectize.min.js"></script>

and then, we create the elements which we want to selectize :

<form method="post">

     <select name="province_id" id="province_id">
            <option value="0">--Select Province--</option>
            <option value="1">Jawa Barat</option>
            <option value="2">Jawa Tengah</option>
      </select>

      <select name="district" id="district">
            <option value="0">--Select District--</option>
      </select>

</form>

Now, time to execute :


        $(document).ready(function() {
            //initialize selectize for both fields
            $("#province_id").selectize();
            $("#district").selectize();
        });  

3. Do the awesome
Ok, now what ? We need to re-fill the “district” data on change of “province”, In this case, I wrote a case when using Ajax request and catched by PHP script. So, create a “change-data.php” file :

<?php

if (isset($_POST['province_id'])) {

    $data = [];
    if ($_POST['province_id'] == 1) {
        $data = [
            0 => [
                'id' => 1,
                'name' => 'Bandung',
            ],
            1 => [
                'id' => 2,
                'name' => 'Cimahi',
            ]
        ];
    }

    if ($_POST['province_id'] == 2) {
        $data = [
            0 => [
                'id' => 3,
                'name' => 'Kudus',
            ],
            1 => [
                'id' => 4,
                'name' => 'Cirebon',
            ]
        ];
    }

    echo json_encode($data);
}

Basically, the selectize can be filled by json object that have “text” and “value” key, like the following :

[
    {text: "Bandung", value: 1 },
    {text: "Cimahi", value: 2 }
]

So, we need to get the data, and convert to json object, we can do with eval :

new_value_options = eval('(' + new_value_options + ')');

Ok, now, let’s do this :

$(document).ready(function() {
            //initialize selectize for both fields
            $("#province_id").selectize();
            $("#district").selectize();

            // onchange
            $("#province_id").change(function() {
                $.post('./change-data', { 'province_id' : $(this).val() } , function(jsondata) {
                    var htmldata = '';
                    var new_value_options   = '[';
                    for (var key in jsondata) {
                        htmldata += '<option value="'+jsondata[key].id+'">'+jsondata[key].name+'</option>';

                        var keyPlus = parseInt(key) + 1;
                        if (keyPlus == jsondata.length) {
                            new_value_options += '{text: "'+jsondata[key].name+'", value: '+jsondata[key].id+'}';
                        } else {
                            new_value_options += '{text: "'+jsondata[key].name+'", value: '+jsondata[key].id+'},';
                        }
                    }
                    new_value_options   += ']';

                    //convert to json object
                    new_value_options = eval('(' + new_value_options + ')');
                    if (new_value_options[0] != undefined) {
                        // re-fill html select option field 
                        $("#district").html(htmldata);
                        // re-fill/set the selectize values
                        var selectize = $("#district")[0].selectize;

                        selectize.clear();
                        selectize.clearOptions();
                        selectize.renderCache['option'] = {};
                        selectize.renderCache['item'] = {};

                        selectize.addOption(new_value_options);

                        selectize.setValue(new_value_options[0].value);
                    }

                }, 'json');
            });
        });

That’s it, hope it helpful. Want to grab the code ? grab it from https://github.com/samsonasik/selectize-demo

Tagged with: ,

Zend Framework 2 : Reset HeadTitle Position from View

Posted in Teknologi, Tutorial PHP, Zend Framework 2 by samsonasik on July 14, 2013

zf2-zendframework2Sometime, we need to reset position of title that default we can look at ZendSkeletonApplication like “My Album – ZF2 Skeleton Application” to something like “ZF2 Skeleton Application – My Album”.
We CAN NOT do this from view :

$this->headTitle($title, 'PREPEND');
//OR
$this->headTitle($title, 'APPEND');

because of the layout rendered after view rendered, so we need to pass a value from view to layout via placeholder. So after set a title at view, we need to pass a value via placeholder view helper.

// module/Album/view/album/album/index.phtml:
$title = 'My albums';
$this->headTitle($title);
$this->placeholder('titleType')->set('PREPEND');

so we can set the title type at layout like the following :

// module/Application/view/layout/layout.phtml:
echo $this->headTitle('ZF2 Skeleton Application', 
        $this->placeholder('titleType', 'APPEND'))
          ->setSeparator(' - ')->setAutoEscape(false);

$this->placeholder(‘titleType’, ‘APPEND’) that passed at 2nd parameter of headTitle() means that if titleType already set, so get from the already data, if no, set to ‘APPEND’ as default value.

When situation need to remove layout title ( parameter is ‘SET’), and use the view title, we need to make a conditional like this :

// module/Application/view/layout/layout.phtml:
if ($this->placeholder('titleType', 'APPEND') == 'SET') { 
    echo $this->headTitle()->setAutoEscape(false);
} else {
   echo $this->headTitle('ZF2 Skeleton Application', 
          $this->placeholder('titleType', 'APPEND'))
             ->setSeparator(' - ')->setAutoEscape(false);
}

That’s it πŸ˜‰

references:
1. http://stackoverflow.com/questions/13949809/zend-framework-2-make-content-page-variable-accessable-in-layout-phtml
2. http://zf2.readthedocs.org/en/latest/modules/zend.view.helpers.placeholder.html#zend-view-helpers-initial-placeholder

Zend Framework 2 : Generate Doctrine Entities from Existing Database using DoctrineModule and DoctrineORMModule

Posted in orm, Teknologi, Tutorial PHP, Zend Framework 2 by samsonasik on April 10, 2013

zf2-zendframework2If we are working with Doctrine , we usually create entities first, and generate the database tables. How if the situation is the database tables and data is already ready, and we have to create an application based on doctrine and Zend Framework 2? We need generate entities! Don’t create them manually!
For example, i have two tables : album and track (i’m using PostgreSQL ) like the following :

-- Table: album
CREATE TABLE album
(
  id bigserial NOT NULL,
  artist character varying(255),
  title character varying(255),
  CONSTRAINT pk_album PRIMARY KEY (id )
)
WITH (
  OIDS=FALSE
);
ALTER TABLE album
  OWNER TO developer;

-- Table: track
CREATE TABLE track
(
  track_id bigserial NOT NULL,
  track_title character varying(255),
  album_id bigint,
  CONSTRAINT track_pkey PRIMARY KEY (track_id ),
  CONSTRAINT fk_track_album FOREIGN KEY (album_id)
      REFERENCES album (id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE
)
WITH (
  OIDS=FALSE
);
ALTER TABLE track
  OWNER TO developer;

Ok, let’s create an application based on ZF2 step by step :
1. install ZF2 SkeletonApplication

git clone git://github.com/zendframework/ZendSkeletonApplication.git zftutordoctrine

2. add “doctrine/doctrine-orm-module” to your zftutordoctrine/composer.json

{
    "name": "zendframework/skeleton-application",
    "description": "Skeleton Application for ZF2",
    "license": "BSD-3-Clause",
    "keywords": [
        "framework",
        "zf2"
    ],
    "homepage": "http://framework.zend.com/",
    "require": {
        "php": ">=5.3.3",
        "zendframework/zendframework": ">2.1.3",
        "doctrine/doctrine-orm-module": "0.*"
    }
}

3. Install it

php composer.phar self-update && php composer.phar install

4. Create an Album module with structure entity like the following :
5-albumomodule-structure
5. Configure doctrine connection
You can define it at one file, but separate it with two file(local and global) can make security happy πŸ˜€
a. config/autoload/doctrine.global.php

//config/autoload/doctrine.global.php
return array(
    'doctrine' => array(
        'connection' => array(
            'orm_default' => array(
                'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
                    'params' => array(
                        'host' => 'localhost',
                        'port' => '5432',
                        'dbname' => 'zftutordoctrine',
                ),
            ),
        )
));

b. config/autoload/doctrine.local.php

//config/autoload/doctrine.local.php
return array(
    'doctrine' => array(
        'connection' => array(
            'orm_default' => array(
                'driverClass' => 'Doctrine\DBAL\Driver\PDOPgSql\Driver',
                    'params' => array(
                        'user' => 'developer',
                        'password' => '123456',
                ),
            ),
        )
));

6. register Album\Entity into doctrine driver in module/Album/config/module.config.php

//module/Album/config/module.config.php
return array(
    'doctrine' => array(
        'driver' => array(
            'Album_driver' => array(
                'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
                'cache' => 'array',
                'paths' => array(__DIR__ . '/../src/Album/Entity')
            ),
            'orm_default' => array(
                'drivers' => array(
                     'Album\Entity' =>  'Album_driver'
                ),
            ),
        ),
    ),                 
);

7. Register modules into config/application.config.php

//config/application.config.php
return array(
    'modules' => array(
        'Application',
        'DoctrineModule',
        'DoctrineORMModule',
        'Album'
    ),
    
    // These are various options for the listeners attached to the ModuleManager
    'module_listener_options' => array(
        'module_paths' => array(
            './module',
            './vendor',
        ),
        'config_glob_paths' => array(
            'config/autoload/{,*.}{global,local}.php',
        ),
    ),
);

8. Generate Time !
a. convert-mapping

./vendor/doctrine/doctrine-module/bin/doctrine-module orm:convert-mapping --namespace="Album\\Entity\\" --force  --from-database annotation ./module/Album/src/

it will export “annotation” mapping information into ./module/Album/src/
b. generate-entities

 ./vendor/doctrine/doctrine-module/bin/doctrine-module orm:generate-entities ./module/Album/src/ --generate-annotations=true

it will add setter/getter into entities.
and you will get the following entities AUTOMATICALLY :
Album\Entity\Album

<?php

namespace Album\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Album
 *
 * @ORM\Table(name="album")
 * @ORM\Entity
 */
class Album
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="bigint", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="SEQUENCE")
     * @ORM\SequenceGenerator(sequenceName="album_id_seq", allocationSize=1, initialValue=1)
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="artist", type="string", length=255, nullable=true)
     */
    private $artist;

    /**
     * @var string
     *
     * @ORM\Column(name="title", type="string", length=255, nullable=true)
     */
    private $title;



    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set artist
     *
     * @param string $artist
     * @return Album
     */
    public function setArtist($artist)
    {
        $this->artist = $artist;
    
        return $this;
    }

    /**
     * Get artist
     *
     * @return string 
     */
    public function getArtist()
    {
        return $this->artist;
    }

    /**
     * Set title
     *
     * @param string $title
     * @return Album
     */
    public function setTitle($title)
    {
        $this->title = $title;
    
        return $this;
    }

    /**
     * Get title
     *
     * @return string 
     */
    public function getTitle()
    {
        return $this->title;
    }
}

Album\Entity\Track

<?php

namespace Album\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Track
 *
 * @ORM\Table(name="track")
 * @ORM\Entity
 */
class Track
{
    /**
     * @var integer
     *
     * @ORM\Column(name="track_id", type="bigint", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="SEQUENCE")
     * @ORM\SequenceGenerator(sequenceName="track_track_id_seq", allocationSize=1, initialValue=1)
     */
    private $trackId;

    /**
     * @var string
     *
     * @ORM\Column(name="track_title", type="string", length=255, nullable=true)
     */
    private $trackTitle;

    /**
     * @var \Album\Entity\Album
     *
     * @ORM\ManyToOne(targetEntity="Album\Entity\Album")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="album_id", referencedColumnName="id")
     * })
     */
    private $album;



    /**
     * Get trackId
     *
     * @return integer 
     */
    public function getTrackId()
    {
        return $this->trackId;
    }

    /**
     * Set trackTitle
     *
     * @param string $trackTitle
     * @return Track
     */
    public function setTrackTitle($trackTitle)
    {
        $this->trackTitle = $trackTitle;
    
        return $this;
    }

    /**
     * Get trackTitle
     *
     * @return string 
     */
    public function getTrackTitle()
    {
        return $this->trackTitle;
    }

    /**
     * Set album
     *
     * @param \Album\Entity\Album $album
     * @return Track
     */
    public function setAlbum(\Album\Entity\Album $album = null)
    {
        $this->album = $album;
    
        return $this;
    }

    /**
     * Get album
     *
     * @return \Album\Entity\Album 
     */
    public function getAlbum()
    {
        return $this->album;
    }
}

JUST IMAGINE that YOU SHOULD WRITE THEM MANUALLY :p
9. Fill database tables rows, and Let’s call from whatever controller for testing :

    public function indexAction()
    {
        $em = $this->getServiceLocator()
                ->get('doctrine.entitymanager.orm_default');
        $data = $em->getRepository('Album\Entity\Track')->findAll();
        foreach($data as $key=>$row)
        {
            echo $row->getAlbum()->getArtist().' :: '.$row->getTrackTitle();
            echo '<br />';
        }
    }

References :
1. Conversation with Anass Ans
1. http://docs.doctrine-project.org/en/2.0.x/reference/tools.html
2. http://wildlyinaccurate.com/useful-doctrine-2-console-commands