Welcome to Abdul Malik Ikhsan's Blog

Zend Framework 2 : Using INSERT INTO … SELECT with Zend\Db

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on March 19, 2014

zf2-zendframework2Zend Framework 2.3.0 released with dozen of features. One of feature on Zend\Db is we can make insert using select instance. It can make life easier when you have a situation, for example : to ‘copy’ the content of some table to other table, for example : migration with some criteria need to be achieved. Here we go :

//this example is when we use component instead of full stack framework
include './vendor/autoload.php';

use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Insert;
use Zend\Db\Sql\Sql;

$adapterSelect = new Zend\Db\Adapter\Adapter(array(
    'driver'   => 'pdo_mysql',
    'database' => 'DbSELECT',
    'username' => 'root',
    'password' => ''
));

$adapterInsert = new Zend\Db\Adapter\Adapter(array(
    'driver'   => 'pdo_mysql',
    'database' => 'DbINTO',
    'username' => 'root',
    'password' => ''
));

/**
 * php 5.4 code, for php 5.3 code, you can extract with
   $selectedtable = new Zend\Db\TableGateway\TableGateway('tableneedtobeselected', $adapterSelect);
   $select = $selectedtable->getSql()->select()->where(array('field' => 'value'));
 */
$select = (new Zend\Db\TableGateway\TableGateway('tableneedtobeselected', $adapterSelect))
                ->getSql()->select()
                ->where(array('field' => 'value'));

/**
 * table that need to be inserted
 */
$tableToBeInserted = new Zend\Db\TableGateway\TableGateway('tablenamewillinserted', $adapterInsert);

//insert with select
$tableToBeInserted->insert($select);

Done ;)

References :
1. http://framework.zend.com/blog/zend-framework-2-3-0-released.html

Zend Framework 2 : Utilize ValidatorManager to work with Custom Validator in Zend\Form

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on February 2, 2014

zf2-zendframework2ValidatorManager is a Plugin Manager that corresponding to Zend\Validator\ValidatorPluginManager, and used to manage validator instances. While we work with existing validators in Zend\Form, we can call validators in ‘validators’ key under inputFilter specification with the ‘invokables’ registered service at Zend\Validator\ValidatorPluginManager. In this post, I will propose detail way to work with custom validator.

For example, we have custom validator named Special to handle words with at least one special character in it, so the form when not valid will show like this :
form-validator-custom
So, we need to start with creating the validator like the following :

//filename : module/TutorialValidator/src/TutorialValidator/Validator/Special.php
namespace TutorialValidator\Validator;

use Zend\Validator\AbstractValidator;
                    
class Special extends AbstractValidator
{
    const NOTSPECIAL = 'NOTSPECIAL';

    protected $messageTemplates = array(
        self::NOTSPECIAL => 'Value should at least one special character',
    );
    
    public function __construct(array $options = array())
    {
       parent::__construct($options);
    }

    public function isValid($value)
    {
       $this->setValue($value);
       
       $special   = preg_match('#[\W]{1,}#', $value);

       if (!$special) {
           $this->error(self::NOTSPECIAL);
           return false;
       }

       return true;
    }
}

And we need to use it to validate the forms, so we need to register the validator in ‘validators’ key in our configuration:

//filename : module/TutorialValidator/config/module.config.php
'validators' => array(
    'invokables' => array(
        'Special' => 'TutorialValidator\Validator\Special'  
     ),
),

There are two ways to build form with custom validator.

A. We can use Zend\Form\FormAbstractServiceFactory that can be configured via ‘forms’ key in configuration files.
To do this, we need to :
1. register Zend\Form\FormAbstractServiceFactory into configuration

//filename : module/TutorialValidator/config/module.config.php
'service_manager' => array(
    //other service registration here...
    'abstract_factories' => array(
        'Zend\Form\FormAbstractServiceFactory',
    ),
    //other service registration here...
),

2. And then, configure the ‘forms’

//filename : module/TutorialValidator/config/module.config.php
'forms' => array(
    'SampleForm' => array(
        'hydrator' => 'ObjectProperty',
        'type'     => 'Zend\Form\Form',
        'elements' => array(
            array(
                'spec' => array(
                    'type' => 'Text',
                    'name' => 'sampleinput',
                    'options' => array(
                        'label' => 'Sample Input:',
                    )
                ),
            ),
            array(
                'spec' => array(
                    'type' => 'Submit',
                    'name' => 'submit',
                    'attributes' => array(
                        'value' => 'Go',
                    )
                ),
            ),
        ),
        'input_filter' => array(
            'sampleinput' => array(
                'required'   => true,
                'validators' => array(
                    array(
                        'name' => 'Special',
                    ),
                ),
            ),
        ),
    ),
),

SampleForm is a registered form service that can be called via ‘FormElementManager’ manager. Special is a custom validator that registered at ‘validators’ key or getValidatorConfig() under Module class.

B. Using form class
For form class, we need to implements Zend\InputFilter\InputFilterProviderInterface and implement method getInputFilterSpecification :
To do this, we need to :
1. create the form class

//filename : module/TutorialValidator/src/TutorialValidator/Form/SampleForm.php
namespace TutorialValidator\Form;

use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;

class SampleForm extends Form implements InputFilterProviderInterface
{
    public function init()
    {
        $this->add(array(
            'name' => 'sampleinput',
            'type' => 'Text',
            'options' => array(
                'label' => 'Sample Input: ',
            ),
        ));

        $this->add(array(
            'name' => 'submit',
            'attributes' => array(
                'type'  => 'submit',
                'value' => 'Go',
                'id' => 'submitbutton',
            ),
        ));
    }

    public function getInputFilterSpecification()
    {
        return array(
            array(
                'name' => 'sampleinput',
                'required' => true,
                'validators' => array(
                    array('name' => 'Special'),
                ),
            ),
        );
    }
}

2. Register form service in ‘form_elements’ key in config

//filename : module/TutorialValidator/config/module.config.php
'form_elements' => array(
    'invokables' => array(
        'SampleForm' => 'TutorialValidator\Form\SampleForm'
    ),                         
),

When everything is OK, we can call the form via ‘FormElementManager’ in controller ( you can inject controller with SampleForm service via ‘factories’ key of course :)) :

    public function indexAction()
    {
        $form = $this->getServiceLocator()
                     ->get('FormElementManager')
                     ->get('SampleForm');
        
        $request = $this->getRequest();
        if ($request->isPost()) {
            $form->setData($request->getPost());
            if ($form->isValid()) {
                echo 'Great!form is valid';
            }
        }
        
        $viewmodel = new ViewModel;
        $viewmodel->setVariable('form', $form);
        
        return $viewmodel;
    }

Done. I’ve uploaded the sample code to my github account : https://github.com/samsonasik/TutorialValidator . Hope it helpful ;)

Note : Thanks to Daniel Gimenes for the suggestion to this post and make a PR to my repository on github.
danizord-suggestion-on-getInputFilterSpecification

References :
1. http://zf2.readthedocs.org/en/latest/modules/zend.mvc.services.html
2. http://stackoverflow.com/questions/19642139/possible-to-create-a-factory-to-instantiate-custom-form-validators
3. http://stackoverflow.com/questions/13476164/zend-framework-2-custom-validators-for-forms
4. https://github.com/samsonasik/TutorialValidator/pull/1

Zend Framework 2 : Getting Closer with PluginManager

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on January 29, 2014

zf2-zendframework2In ZF2, there is PluginManager that we can use to collect services with same contract interfaces or ancestor class into single class properties. After that, we can validate whenever the plugins as services are instance of that interfaces or ancestor class ( usually an abstract class ). There are properties of PluginManager class that can collect the services :
1. $factories
2. $invokableClasses
3. $aliases(aliasing registered services at $factories or $invokableClasses)
The basic class structure of PluginManager is like this :

namespace YourModule;

use Zend\ServiceManager\AbstractPluginManager;

class PluginManager extends AbstractPluginManager
{
    protected $factories = array(
        //represent factories key
    );
    
    protected $invokableClasses = array(
        //represent invokables key
    );
    
    protected $aliases = array(
        //represent aliases key
    );
    
    public function validatePlugin($plugin)
    {
        if ($plugin instanceof Plugin\PluginInterface) {
            // we're okay
            return;
        }
        
        throw new \InvalidArgumentException(sprintf(
            'Plugin of type %s is invalid; must implement %s\Plugin\PluginInterface',
            (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
            __NAMESPACE__
        ));
    }
}

For example, you need to create collection of services that can convert content into another format, for example, you want to create services that can convert to ‘xls’, ‘pdf’, or other. so you can create pluginmanager to collect that services, like the following :
pluginmanager_shot
Let’s code!
1. create the ConverterContentPluginManager PluginManager

//filename : module/Tutorial/src/Tutorial/ConverterContentPluginManager.php
namespace Tutorial;

use Zend\ServiceManager\AbstractPluginManager;

class ConverterContentPluginManager extends AbstractPluginManager
{
    protected $invokableClasses = array(
        //represent invokables key
        'xls' => 'Tutorial\Plugin\Xls',
        'pdf' => 'Tutorial\Plugin\Pdf'
    );

    public function validatePlugin($plugin)
    {
        if ($plugin instanceof Plugin\PluginInterface) {
            // we're okay
            return;
        }

        throw new \InvalidArgumentException(sprintf(
            'Plugin of type %s is invalid; must implement %s\Plugin\PluginInterface',
            (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
            __NAMESPACE__
        ));
    }
}

2. Create the plugin interface to be implemented

//filename : module/Tutorial/src/Tutorial/Plugin/PluginInterface.php
namespace Tutorial\Plugin;

interface PluginInterface
{
    public function convert($content);
}

3. Create that services
a. the Xls service

//filename : module/Tutorial/src/Tutorial/Plugin/Xls.php
namespace Tutorial\Plugin;

class Xls implements PluginInterface
{   
    public function convert($content)
    {
        echo 'xls convert here';
        //implementation of convert $content to convert content into xls
    }
}

b. the Pdf service

//filename : module/Tutorial/src/Tutorial/Plugin/Pdf.php
namespace Tutorial\Plugin;

class Pdf implements PluginInterface
{   
    public function convert($content)
    {
        echo 'pdf convert here';
        //implementation of convert $content to convert content into pdf
    }
}

4. Create a factory class for the ConverterContentPluginManager

//filename : module/Tutorial/src/Tutorial/Service/ConverterContentPluginManagerFactory.php
namespace Tutorial\Service;

use Zend\Mvc\Service\AbstractPluginManagerFactory;

class ConverterContentPluginManagerFactory extends AbstractPluginManagerFactory
{
    const PLUGIN_MANAGER_CLASS = 'Tutorial\ConverterContentPluginManager';
}

5. Last step, register into service manager ( just one service, the ConverterContentPluginManagerFactory one )

//filename : module/Tutorial/config/module.config.php
return array(
    'service_manager' => array(
        'factories' => array(
            'convertercontent' => 'Tutorial\Service\ConverterContentPluginManagerFactory'  
        ),
    ),
);

Ok, now you can grab the plugins via :

$content = 'sample content you want to convert';
$converter = $this->getServiceLocator()->get('convertercontent')
$converter->get('xls')->convert($content);
$converter->get('pdf')->convert($content);

Done ;)

References :
1. http://raing3.gshi.org/2013/05/26/creating-custom-plugin-manager-in-zend-framework-2/
2. http://zf2.readthedocs.org/en/latest/modules/zend.mvc.services.html

Zend Framework 2 : Handle and Catch E_* PHP errors

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on January 21, 2014

zf2-zendframework2I made this post because real application is hard ( err…, not as easy as we imagine ) . One error exposed to user can be harmful for our site. As developer, there is a time that something can go wrong even it unit tested ( because we are human!). The ‘dispatch.error’ and ‘render.error’ are errors that handled by Framework to handle “framework specific” error, like  service not found, or view file not found. But what if the error is PHP Error Constant, like you forgot to handle empty array and just :

$array = array();
//many complex things here
//and you echoing..
 echo $array[1];
//that is empty...

You will got error like this : Notice: Undefined offset: 1 in /your/path/to/file.
It’s very dangerous because it exposed to your user. We need to fix it as soon as possible!, so when we need on site access, we need to automate logging ( for example, save error to file and send mail) with the error described and show user that something is go wrong and developer is working to fix it.

Ok, first, prepare the view file that will show user that something is go wrong :

<!-- //module/Application/view/error/e_handler.phtml -->
website is down right now :), we are working on it. please come back again...

Next, we create code to handle the PHP E_* errors :

// ./e_errorhandler.php in root of ZF2 app
//adapt from http://stackoverflow.com/questions/277224/how-do-i-catch-a-php-fatal-error
define('E_FATAL',  E_ERROR | E_USER_ERROR | E_PARSE | E_CORE_ERROR |
        E_COMPILE_ERROR | E_RECOVERABLE_ERROR);

define('DISPLAY_ERRORS', TRUE);
define('ERROR_REPORTING', E_ALL | E_STRICT);

register_shutdown_function('shut');
set_error_handler('handler');

//catch function
function shut()
{
    $error = error_get_last();
    if ($error && ($error['type'] & E_FATAL)) {
        handler($error['type'], $error['message'], $error['file'], $error['line']);
    }
}

function handler($errno, $errstr, $errfile, $errline)
{
    switch ($errno) {

        case E_ERROR: // 1 //
            $typestr = 'E_ERROR'; break;
        case E_WARNING: // 2 //
            $typestr = 'E_WARNING'; break;
        case E_PARSE: // 4 //
            $typestr = 'E_PARSE'; break;
        case E_NOTICE: // 8 //
            $typestr = 'E_NOTICE'; break;
        case E_CORE_ERROR: // 16 //
            $typestr = 'E_CORE_ERROR'; break;
        case E_CORE_WARNING: // 32 //
            $typestr = 'E_CORE_WARNING'; break;
        case E_COMPILE_ERROR: // 64 //
            $typestr = 'E_COMPILE_ERROR'; break;
        case E_CORE_WARNING: // 128 //
            $typestr = 'E_COMPILE_WARNING'; break;
        case E_USER_ERROR: // 256 //
            $typestr = 'E_USER_ERROR'; break;
        case E_USER_WARNING: // 512 //
            $typestr = 'E_USER_WARNING'; break;
        case E_USER_NOTICE: // 1024 //
            $typestr = 'E_USER_NOTICE'; break;
        case E_STRICT: // 2048 //
            $typestr = 'E_STRICT'; break;
        case E_RECOVERABLE_ERROR: // 4096 //
            $typestr = 'E_RECOVERABLE_ERROR'; break;
        case E_DEPRECATED: // 8192 //
            $typestr = 'E_DEPRECATED'; break;
        case E_USER_DEPRECATED: // 16384 //
            $typestr = 'E_USER_DEPRECATED'; break;
    }
    
    $message = " Error PHP in file : ".$errfile." at line : ".$errline."
    with type error : ".$typestr." : ".$errstr." in ".$_SERVER['REQUEST_URI'];

    if(!($errno & ERROR_REPORTING)) {
        return;
    }

    if (DISPLAY_ERRORS) {
        //logging...
        $logger = new Zend\Log\Logger;
		
        //stream writer			
        $writerStream = new Zend\Log\Writer\Stream(__DIR__.'/data/logs/'.date('Ymd').'-log.txt');
        //mail writer
        $mail = new Zend\Mail\Message();
        $mail->setFrom('system@yoursite.com', 'Sender\'s name');
        $mail->addTo('team@yoursite.com', 'Your Site Team');
        $transport = new Zend\Mail\Transport\Sendmail(); 
        $writerMail = new Zend\Log\Writer\mail($mail, $transport);
        $writerMail->setSubjectPrependText("PHP Error :  $typestr : $errstr ");
        
        $logger->addWriter($writerStream);
        $logger->addWriter($writerMail);
      
        //log it!
        $logger->crit($message);
        
        //show user that's the site is down right now
        include __DIR__.'/module/Application/view/error/e_handler.phtml';
        die;
    }
}

The code line 61-87 is show how to log it and show user custom error page immediately.
Ok, time to include the handler in public/index.php and keep silent ( @ for parse error ) for showing error :

/**
 * This makes our life easier when dealing with paths. Everything is relative
 * to the application root now.
 */
chdir(dirname(__DIR__));

// Decline static file requests back to the PHP built-in webserver
if (php_sapi_name() === 'cli-server' && is_file(__DIR__ . parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH))) {
    return false;
}

// Setup autoloading
require 'init_autoloader.php';
//Setup error handler
require_once 'e_errorhandler.php';

// Run the application!
@Zend\Mvc\Application::init(require 'config/application.config.php')->run();

Ok, done! Hope helpful ;)

References :
1. http://stackoverflow.com/questions/277224/how-do-i-catch-a-php-fatal-error
2. http://zf2.readthedocs.org/en/latest/modules/zend.mail.introduction.html
3. http://zf2.readthedocs.org/en/latest/modules/zend.log.writers.html

Zend Framework 2 : using PSR-4 autoloader in your Module

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on January 11, 2014

zf2-zendframework2 This post is inspired by Phil Sturgeon blog post about Autoloading Laravel application code with PSR-4. It can be applied when you’re using Zend Framework module. Instead of using default structure, you can define the structure like this.zf2-module-with-psr-4The Classes inside Controller folder will have namespace Foo\Controller, and in the Model folder will have namespace Foo\Model.
Ok, let’s make it happen!
1. Let’s the Module::getAutoloaderConfig() function empty

//module/Foo/Module.php
namespace Foo;

class Module
{
    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }

    public function getAutoloaderConfig()
    {
    }
}

2. Configure composer.json
Add psr-4 autolaod config into your composer.json.

{
    "autoload": {
        "psr-4":{
            "Foo\\" : "module/Foo/src/"
        }
    },
    "require": {
        "php": ">=5.3.3",
        "zendframework/zendframework": "2.2.5"
    }
}

3. update composer

$ php composer.phar self-update
Updating to version 69e77fbbb564e57a6c1a97eaa3c8b751bab70688.
    Downloading: 100% 

4. Run dump-autoload

$ php composer.phar dump-autoload
Generating autoload files

5. Now, You are getting new generated autoload file named vendor/composer/autoload_psr4.php automatically

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Foo\\' => array($baseDir . '/module/Foo/src'),
);

6. Perfect!, Let’s creating file for samples
a. the model class

//module/Foo/src/Model/FooModel.php
namespace Foo\Model;

class FooModel
{
    function __construct()
    {
        echo "foo";
    }
}

b. the controller class

//module/Foo/src/Controller/FooController.php
namespace Foo\Controller;

use Zend\Mvc\Controller\AbstractActionController;

class FooController extends AbstractActionController
{
    public function indexAction()
    {
        new \Foo\Model\FooModel();
        die; //break for test
    }
}

c. the config/module.config.php

//module/Foo/config/module.config.php
return array(
    'router' => array(
        'routes' => array(
            'foo' => array(
                'type' => 'Zend\Mvc\Router\Http\Literal',
                'options' => array(
                    'route'    => '/foo',
                    'defaults' => array(
                        'controller' => 'Foo\Controller\Foo',
                        'action'     => 'index',
                    ),
                ),
            ),
        ),
    ),
    'controllers' => array(
        'invokables' => array(
            'Foo\Controller\Foo' => 'Foo\Controller\FooController'
        ),
    ),
);

7. Register your module into config/application.config.php as usual.

return array(
    'modules' => array(
        'Application',
        'Foo'
    ),
    'module_listener_options' => array(
        'module_paths' => array(
            './module',
            './vendor',
        ),
        'config_glob_paths' => array(
            'config/autoload/{,*.}{global,local}.php',
        ),
    ),
);

8. Let’s call in browser : http://yourapphost/foo , if it’s working as planned, it will show us :
foo-screen-call

Done! I hope this post helpful.

References :
1. http://philsturgeon.co.uk/blog/2014/01/autoloading-laravel-application-code-with-psr4
2. https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md
3. http://akrabat.com/zend-framework-2/thoughts-on-module-directory-structure/
4. http://zf2.readthedocs.org/en/latest/user-guide/modules.html

Zend Framework 2 : Move out your listeners from Module class

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on January 8, 2014

zf2-zendframework2 As we already knew. We can have ‘listeners’ with array callback that placed on Module class which can be called via onBootstrap() method. When our application is growing, to many methods on Module class will make project maintenance take harder, and make our application less-readable.
Here is a sample if we don’t have move out the method yet :

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

        $eventManager->attach('dispatch.error',
                array($this, 'handleDispatchErrorWithLogger'), 100);
    }

    public function handleDispatchErrorWithLogger(MvcEvent $e)
    {
        $exception = $e->getParam('exception');

        //it is just a sample, you can create service for logger
        $writer = new \Zend\Log\Writer\Stream('./data/logs/'.date('Y-m-d').'-log.txt');
        $log      = new \Zend\Log\Logger();
        $log->addWriter($writer);

        $log->err($exception);
    }
    public function getConfig(){/*common code*/}
    public function getAutoloaderConfig(){/*common code*/}
}

And many more when application growing, so, this is how it can be moved out :
1. Create a class that has __invoke method that will be fired when event triggered

class DispatchErrorHandlerListener
{
    public function __invoke(MvcEvent $e)
    {
        $exception = $e->getParam('exception');

        //it is just a sample, you can create service for logger
        $writer = new \Zend\Log\Writer\Stream('./data/logs/'.date('Y-m-d').'-log.txt');
        $log      = new \Zend\Log\Logger();
        $log->addWriter($writer);

        $log->err($exception);
    }
}

2. Make the listener as object

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

        $eventManager->attach('dispatch.error', new \Tutorial\Listener\DispatchErrorHandlerListener, 100);
    }

    public function getConfig(){/*common code*/}
    public function getAutoloaderConfig(){/*common code*/}
}

and if you like the listener as service, you can pass like this :

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $app            = $e->getApplication();
        $eventManager   = $app->getEventManager();
        $service        = $app->getServiceManager();

        $eventManager->attach('dispatch.error', $sm->get('YourRegisteredErrorHandlerListener'), 100);
    }

    public function getConfig(){/*common code*/}
    public function getAutoloaderConfig(){/*common code*/}
}

Done, now your Module class is simplified ;)

Zend Framework 2 : Programmatically handle 404 page

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on January 1, 2014

zf2-zendframework2Sometime, in real life project, we need to handle 404 page more than only show the 404 page, but logging, redirecting, forwarding, change template/content, etc. We can handle it by programmatically at Module.php. In Zend Framework 2, there is 4 types of 404 ( page not found ), they are : ERROR_CONTROLLER_CANNOT_DISPATCH, ERROR_CONTROLLER_NOT_FOUND, ERROR_CONTROLLER_INVALID, and ERROR_ROUTER_NO_MATCH.  Let’s investigate one by one.

1. ERROR_CONTROLLER_CANNOT_DISPATCH
It means the controller is matched, but the action that passed to the url can’t be dispatched. We can handle it via onBootstrap like the following :

namespace YourModule;

use Zend\Mvc\MvcEvent;

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $eventManager  = $e->getApplication()->getEventManager();
        $sharedManager = $eventManager->getSharedManager();
        //controller can't dispatch request action that passed to the url
        $sharedManager->attach('Zend\Mvc\Controller\AbstractActionController',
                  'dispatch', 
                  array($this, 'handleControllerCannotDispatchRequest' ), 101);
    }

    public function handleControllerCannotDispatchRequest(MvcEvent $e)
    {
        $action = $e->getRouteMatch()->getParam('action');
        $controller = get_class($e->getTarget());
        
        // error-controller-cannot-dispatch
        if (! method_exists($e->getTarget(), $action.'Action')) {
            $logText = 'The requested controller '.
                        $controller.' was unable to dispatch the request : '.$action.'Action';
            //you can do logging, redirect, etc here..
             echo $logText;
        }
    }   

    public function getConfig() { //common code here }
    public function getAutoloaderConfig() { //common code here }
}

2. ERROR_CONTROLLER_NOT_FOUND
It means controller class not found with requested [/:controller] route that defined already at module.config.php
3. ERROR_CONTROLLER_INVALID
It means the controller is not dispatchable, it usually because the controller is not extends Zend\Mvc\Controller\AbstractActionController
4. ERROR_ROUTER_NO_MATCH
It means The requested URL could not be matched by routing, for example, there is no route with prefix /foo that passed at the url.
For point 2, 3, and 4, we can handle by :

namespace YourModule;

use Zend\Mvc\MvcEvent;
use Zend\Mvc\Application;

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $eventManager = $e->getApplication()->getEventManager();
        $eventManager->attach('dispatch.error',
             array($this,
             'handleControllerNotFoundAndControllerInvalidAndRouteNotFound'), 100);
    }
     
    public function handleControllerNotFoundAndControllerInvalidAndRouteNotFound(MvcEvent $e)
    {
        $error  = $e->getError();
        if ($error == Application::ERROR_CONTROLLER_NOT_FOUND) {
            //there is no controller named $e->getRouteMatch()->getParam('controller')
            $logText =  'The requested controller '
                        .$e->getRouteMatch()->getParam('controller'). '  could not be mapped to an existing controller class.';
            
            //you can do logging, redirect, etc here..
            echo $logText;
        }
        
        if ($error == Application::ERROR_CONTROLLER_INVALID) {
            //the controller doesn't extends AbstractActionController
            $logText =  'The requested controller '
                        .$e->getRouteMatch()->getParam('controller'). ' is not dispatchable';
            
            //you can do logging, redirect, etc here..
            echo $logText;
        }
        
        if ($error == Application::ERROR_ROUTER_NO_MATCH) {
            // the url doesn't match route, for example, there is no /foo literal of route
            $logText =  'The requested URL could not be matched by routing.';
            //you can do logging, redirect, etc here...
            echo $logText;
        }
    } 
    public function getConfig() { //common code here }
    public function getAutoloaderConfig() { //common code here }
}

Want to handle All ? Let’s do this :

namespace YourModule;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\Application;

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $eventManager = $e->getApplication()->getEventManager();
        $sharedManager = $eventManager->getSharedManager();
        //controller can't dispatch request action that passed to the url
        $sharedManager->attach('Zend\Mvc\Controller\AbstractActionController',
               'dispatch',
               array($this, 'handleControllerCannotDispatchRequest' ), 101);
        //controller not found, invalid, or route is not matched anymore
        $eventManager->attach('dispatch.error', 
               array($this,
              'handleControllerNotFoundAndControllerInvalidAndRouteNotFound' ), 100);
    }
    
    public function handleControllerCannotDispatchRequest(MvcEvent $e)
    {
        $action = $e->getRouteMatch()->getParam('action');
        $controller = get_class($e->getTarget());
        
        // error-controller-cannot-dispatch
        if (! method_exists($e->getTarget(), $action.'Action')) {
            $logText = 'The requested controller '.
                        $controller.' was unable to dispatch the request : '.$action.'Action';
            //you can do logging, redirect, etc here..
             echo $logText;
        }
    }
    
    public function handleControllerNotFoundAndControllerInvalidAndRouteNotFound(MvcEvent $e)
    {
        $error  = $e->getError();
        if ($error == Application::ERROR_CONTROLLER_NOT_FOUND) {
            //there is no controller named $e->getRouteMatch()->getParam('controller')
            $logText =  'The requested controller '
                        .$e->getRouteMatch()->getParam('controller'). '  could not be mapped to an existing controller class.';
            
            //you can do logging, redirect, etc here..
            echo $logText;
        }
        
        if ($error == Application::ERROR_CONTROLLER_INVALID) {
            //the controller doesn't extends AbstractActionController
            $logText =  'The requested controller '
                        .$e->getRouteMatch()->getParam('controller'). ' is not dispatchable';
            
            //you can do logging, redirect, etc here..
            echo $logText;
        }
        
        if ($error == Application::ERROR_ROUTER_NO_MATCH) {
            // the url doesn't match route, for example, there is no /foo literal of route
            $logText =  'The requested URL could not be matched by routing.';
            //you can do logging, redirect, etc here...
            echo $logText;
        }
    } 
    
    public function getConfig(){ // common code }
    public function getAutoloaderConfig(){ //common code }
}

Done ;)

Zend Framework 2 : Centralize phpunit test

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on November 19, 2013

zf2-zendframework2 Centralize phpunit test is good when we work at integration test to test all modules we have created. What we need is place autoloader at Bootstrap file, and make ‘ServiceManager Grabber’ to grab services over modules.

1. Preparation :
a. Create tests folder at our ZF2 Application.
tests-folder-prepare
At this phase, we added Bootstrap.php and phpunit.xml as configuration.
b. Write Bootstrap.php

//we create ServiceManagerGrabber class later...
use ModulesTests\ServiceManagerGrabber; 

error_reporting(E_ALL | E_STRICT);

$cwd = __DIR__;
chdir(dirname(__DIR__));

// Assume we use composer
$loader = require_once  './vendor/autoload.php';
$loader->add("ModulesTests\\", $cwd);
$loader->register();

ServiceManagerGrabber::setServiceConfig(require_once './config/application.config.php');
ob_start();

c. Write phpunit.xml
We register module that we add directory of module tests, at this case, named “ModulesTests”.

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

<phpunit
         backupStaticAttributes="false"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="false"

         bootstrap="Bootstrap.php">
    <testsuites>
        <testsuite name="sanzf2">
            <directory suffix=".php">./ModulesTests</directory>
        </testsuite>
    </testsuites>
</phpunit>

d. Based on the phpunit.xml, we then need to create directory named “ModulesTests” to collect test for modules.
moduletests-collect-rev

e. Create ServiceManager Grabber ( as you can see at “ModulesTests” folder )

//inspired from https://github.com/doctrine/DoctrineModule/blob/master/tests/DoctrineModuleTest/ServiceManagerTestCase.php
// thanks to Marco Pivetta

namespace ModulesTests;

use Zend\ServiceManager\ServiceManager;
use Zend\Mvc\Service\ServiceManagerConfig;

class ServiceManagerGrabber
{
    protected static $serviceConfig = null;
    
    public static function setServiceConfig($config)
    {
        static::$serviceConfig = $config;
    }
    
    public function getServiceManager()
    {
        $configuration = static::$serviceConfig ? : require_once './config/application.config.php';
        
        $smConfig = isset($configuration['service_manager']) ? $configuration['service_manager'] : array();
        $serviceManager = new ServiceManager(new ServiceManagerConfig($smConfig));
        $serviceManager->setService('ApplicationConfig', $configuration);
 
        $serviceManager->get('ModuleManager')->loadModules();
        
        return $serviceManager;
    }
}

This class will grab the our ZF2 application config, load Modules, that means the serviceManager of all modules will be returned.

f. create per-module test case
moduletests-collect-hasmodule-collect
At this case, I added directory named “SanDbModellingWithZendDbTest” to test my “SanDbModellingWithZendDb” module. It’s up to us to create other name, or collect or not collect module per directory, but for my perspective, it should be on separate folder per-module test.

2. Write a Test
The test can be formatted like the following :

namespace ModulesTests\SanDbModellingWithZendDbTest\Model;

use PHPUnit_Framework_TestCase;
use ModulesTests\ServiceManagerGrabber;

class AlbumTrackMapperTest extends PHPUnit_Framework_TestCase
{
    protected $serviceManager;
    
    public function setUp()
    {
        $serviceManagerGrabber   = new ServiceManagerGrabber();
        $this->serviceManager = $serviceManagerGrabber->getServiceManager();
    }
    
    public function testJoinLeft()
    {
        $count =  count($this->serviceManager->get('AlbumTrackMapper')->findAll());
        ($count > 0 ) ? $this->assertNotEmpty($count) : $this->assertEmpty($count);
    }
}

3. Run Test
We just need to go to tests folder with command line :

$ cd ~/yourzf2app/tests
$ phpunit

And you will get this :
phpunit-run-cli-zf2

Of course, we can add more folders under “ModulesTests” folder.

Thanks to Marco Pivetta that give me enlightenment to not share one ServiceManager accross different tests.
thanks-to-marco-pivetta-for-suggestion-to-not-share-one-service-accross-different-test

Done ;)

Zend Framework 2 : Zend\Db Modelling – The hard way

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on November 6, 2013

zf2-zendframework2This post inspire from Ralph Schindler presentation about Modelling in ZF2 with Zend\Db. Of course, using Zend\Db, we can do “join” directly when dealing with table relation, but when we have several columns that same in tables, like “album” and “track” that has same column named “title” per-table, we need to know what columns comes from. We can argue to use “aliasing” for same column, but we can’t do it easily when columns that same is more than we can imagine :p. So, we actually need it. I will show you how to do it.
Preparations :
1. Create tables
I create two kind of tables for this post purpose with this fake data ( I have no time for finding the right one :P ) :

DROP TABLE IF EXISTS `album`;
CREATE TABLE IF NOT EXISTS `album` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `artist` varchar(255) NOT NULL,
  `title` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

--
-- Dumping data for table `album`
--

INSERT INTO `album` (`id`, `artist`, `title`) VALUES
(1, 'Bruno Mars', 'Go'),
(2, 'Syahrini', 'Membahana'),
(3, 'Justin Timberlake', 'Love');

-- --------------------------------------------------------

--
-- Table structure for table `track`
--

DROP TABLE IF EXISTS `track`;
CREATE TABLE IF NOT EXISTS `track` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) DEFAULT NULL,
  `album_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

--
-- Dumping data for table `track`
--

INSERT INTO `track` (`id`, `title`, `album_id`) VALUES
(1, 'Sesuatu', 2),
(2, 'Aku Tak Biasa', 2),
(3, 'Grenade', 1),
(4, 'I like it!', 1);

2. Create Entities
a. Track Entity

class Track
{
    public $id;
    public $title;
    public $album_id;
}

b. Album Entity

class Album
{
    public $id;
    public $artist;
    public $title;
}

How two kinds of entity “relate” ? Of course, add more function : setTrack() and getTrack() to Album Entity :

class Album
...
    protected $tracks;

    public function setTracks($tracks)
    {
        $this->tracks = $tracks;
    }

    public function getTracks()
    {
        return $this->tracks;
    }
...
}

3. Create AlbumTable and TrackTable, we can adapt from the docs ( I will not repeat here )
4. Create a mapper for them :

class AlbumTrackMapper
{
    protected $album;
    protected $track;

    public function __construct(AlbumTable $album, TrackTable $track)
    {
        $this->album = $album;
        $this->track = $track;
    }

    public function findAll()
    {
        $albums = $this->album->getTableGateway()->select();
        $albums->buffer();

        foreach ($albums as $album) {
            $trackrows = $this->track->getTableGateway()
                                     ->select(array('album_id' => $album->id));
            $album->setTracks(iterator_to_array($trackrows));
        }

        return $albums;
    }
}

5. Build instance of AlbumTrackMapper using ‘factories’.

    'service_manager' => array(
        'factories' => array(
            //register AlbumTable and TrackTable here
            //we can do same as the docs or using abstract_factories to "automate" them

            'AlbumTrackMapper' => function($sm) {
                 $albumtable = $sm->get('SanDbModellingWithZendDb\Model\AlbumTable');
                 $tracktable = $sm->get('SanDbModellingWithZendDb\Model\TrackTable');
 
                $mapper = new Model\AlbumTrackMapper($albumtable, $tracktable);

                return $mapper;
            },
        ),
    ),

6. So, when everything goes fine, we can var_dump the findAll at got it :

object(Zend\Db\ResultSet\HydratingResultSet)[257]
  protected 'hydrator' => 
    object(Zend\Stdlib\Hydrator\ObjectProperty)[250]
      protected 'strategies' => 
        object(ArrayObject)[251]
      protected 'filterComposite' => 
        object(Zend\Stdlib\Hydrator\Filter\FilterComposite)[252]
          protected 'orFilter' => 
            object(ArrayObject)[253]
          protected 'andFilter' => 
            object(ArrayObject)[254]
  protected 'objectPrototype' => 
    object(SanDbModellingWithZendDb\Model\Album)[244]
      public 'id' => null
      public 'artist' => null
      public 'title' => null
      protected 'tracks' => null
  protected 'buffer' => 
    array (size=3)
      0 => 
        object(SanDbModellingWithZendDb\Model\Album)[293]
          public 'id' => string '1' (length=1)
          public 'artist' => string 'Bruno Mars' (length=10)
          public 'title' => string 'Go' (length=2)
          protected 'tracks' => 
            array (size=2)
              0 => 
                object(SanDbModellingWithZendDb\Model\Track)[303]
                  public 'id' => string '3' (length=1)
                  public 'title' => string 'Grenade' (length=7)
                  public 'album_id' => string '1' (length=1)
              1 => 
                object(SanDbModellingWithZendDb\Model\Track)[256]
                  public 'id' => string '4' (length=1)
                  public 'title' => string 'I like it!' (length=10)
                  public 'album_id' => string '1' (length=1)
      1 => 
        object(SanDbModellingWithZendDb\Model\Album)[305]
          public 'id' => string '2' (length=1)
          public 'artist' => string 'Syahrini' (length=8)
          public 'title' => string 'Membahana' (length=9)
          protected 'tracks' => 
            array (size=2)
              0 => 
                object(SanDbModellingWithZendDb\Model\Track)[304]
                  public 'id' => string '1' (length=1)
                  public 'title' => string 'Sesuatu' (length=7)
                  public 'album_id' => string '2' (length=1)
              1 => 
                object(SanDbModellingWithZendDb\Model\Track)[297]
                  public 'id' => string '2' (length=1)
                  public 'title' => string 'Aku Tak Biasa' (length=13)
                  public 'album_id' => string '2' (length=1)
      2 => 
        object(SanDbModellingWithZendDb\Model\Album)[296]
          public 'id' => string '3' (length=1)
          public 'artist' => string 'Justin Timberlake' (length=17)
          public 'title' => string 'Love' (length=4)
          protected 'tracks' => 
            array (size=0)
              empty
  protected 'count' => int 3
  protected 'dataSource' => 
    object(Zend\Db\Adapter\Driver\Pdo\Result)[291]
      protected 'statementMode' => string 'forward' (length=7)
      protected 'resource' => 
        object(PDOStatement)[292]
          public 'queryString' => string 'SELECT `album`.* FROM `album`' (length=29)
      protected 'options' => null
      protected 'currentComplete' => boolean true
      protected 'currentData' => boolean false
      protected 'position' => int 3
      protected 'generatedValue' => string '0' (length=1)
      protected 'rowCount' => int 3
  protected 'fieldCount' => int 3
  protected 'position' => int 3

In my machine, I try using HydratingResultSet.

And done. so we can show that with html like the following :

        echo '<ul>';
        foreach ($albums as $album) {
            echo  '<li>';

            echo $album->artist ;
            echo '<ul>';
                foreach ($album->getTracks() as $i => $track) {
                    echo '<li>' . ($i+1) . ': ' .  $track->title  . '</li>';
                }
            echo '</ul>';

            echo '</li>';
        }
        echo '</ul>';

Want to grap codes ? I have uploaded to my github account : https://github.com/samsonasik/SanDbModellingWithZendDb

References :
1. https://speakerdeck.com/ralphschindler/building-models-in-zf2-a-crash-course
2. https://gist.github.com/ralphschindler/6910421

Zend Framework 2 : using ‘caches’ configuration to setting up cache services

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on October 6, 2013

zf2-zendframework2Zend Framework 2.2 has Zend\Cache\Service\StorageCacheAbstractServiceFactory that allow us to configure cache services via array configuration. We just need to register the abstract factory and create a config array that represent cache options. I will give you an example if we want to create memcached service.

1. register to service manager

//config/autoload/global.php
'service_manager' => array(
     'abstract_factories' => array(
            'Zend\Cache\Service\StorageCacheAbstractServiceFactory',
      )
),

2. configure array cache options, for example, I write at config/autoload/cache.local.php

//config/autoload/cache.local.php
<?php

return array(
    'caches' => array(
        'memcached' => array( //can be called directly via SM in the name of 'memcached'
            'adapter' => array(
                'name'     =>'memcached',
                'lifetime' => 7200,
                'options'  => array(
                    'servers'   => array(
                        array(
                            '127.0.0.1',11211
                        )
                    ),
                    'namespace'  => 'MYMEMCACHEDNAMESPACE',
                    'liboptions' => array (
                        'COMPRESSION' => true,
                        'binary_protocol' => true,
                        'no_block' => true,
                        'connect_timeout' => 100
                    )
                )
            ),
            'plugins' => array(
                'exception_handler' => array(
                    'throw_exceptions' => false
                ),
            ),
        ),
    ),
);

Done, and we can call

$this->getServiceLocator()->get('memcached'); 

from service that implements ServiceLocatorAwareInterface ( controllers or other service(s)).

Want to check ? Check this at your controller :

//filling cache value
public function indexAction()
{
      $this->getServiceLocator()->get('memcached')->setItem('foo', 'bar');
}
//retrieve cache value
public function retrieveAction()
{
    echo $this->getServiceLocator()->get('memcached')->getItem('foo');
}

If you want to set multi-cache config, just configure, and call them :).

Zend Framework 2 : Using HydratingResultSet and ObjectProperty’s Hydrator

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on September 5, 2013

zf2-zendframework2When joining table using Zend\Db, calling coloumn(s) in other table when make a join is a pain. It because if we use normal Zend\Db\ResultSet\ResultSet and setting up ArrayObjectPrototype with the class object, we only get the current model class only ( that consist of columns of single table). Register other table column on that class is NOT a solution, it will make a confusion on what entity is for. Maybe not best (errr…good) solution but simplest solution is to use Zend\Db\ResultSet\HydratingResultSet and Zend\Stdlib\Hydrator\ObjectProperty.
There is I explain on case situation : “we have a table named album ( with columns : `id`, `artist`, `title` ) and table named track ( with columns : `track_id`, `track_title`, and `album_id` as foreign key agains album )”.
Ok, let’s create model like as the docs :

//filename : module/Album/src/Album/Model/Album.php
namespace Album\Model;

class Album
{
    public $id;
    public $artist;
    public $title;

    public function exchangeArray($data)
    {
        $this->id     = (isset($data['id']))     ? $data['id']     : null;
        $this->artist = (isset($data['artist'])) ? $data['artist'] : null;
        $this->title  = (isset($data['title']))  ? $data['title']  : null;
    }

    public function getArrayCopy()
    {
        return get_object_vars($this);
    }
}

Ok, Let’s create a sample table class for album table :

//filename : module/Album/src/Album/Model/AlbumTable.php
namespace Album\Model;

use Zend\Db\TableGateway\TableGateway;

class AlbumTable
{
    protected $tableGateway;

    public function __construct(TableGateway $tableGateway)
    {
        $this->tableGateway = $tableGateway;
    }
    
   //to retrieve tableGateway object when needed.
    public function getTableGateway()
    {
        return $this->tableGateway;
    }

    public function JoinfetchAll()
    {
        $sqlSelect = $this->tableGateway->getSql()
                          ->select()
                          ->join('track', 'track.album_id = album.id', array('*'), 'left');

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

If you want to reduce closure usage at your application, you can create factory for AlbumTable creation like the following :

//filename : module/Album/src/Album/Factory/Model/AlbumTableFactory.php
namespace Album\Factory\Model;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Db\TableGateway\TableGateway;
use Album\Model\AlbumTable;
use Album\Model\Album;

use Zend\Stdlib\Hydrator\ObjectProperty;
use Zend\Db\ResultSet\HydratingResultSet;

class AlbumTableFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $db = $serviceLocator->get('Zend\Db\Adapter\Adapter');

        $resultSetPrototype = new HydratingResultSet();
        $resultSetPrototype->setHydrator(new ObjectProperty());
        $resultSetPrototype->setObjectPrototype(new Album());

        $tableGateway       = new TableGateway('album', $db, null, $resultSetPrototype);
        $table              = new AlbumTable($tableGateway);

        return $table;
    }
}

and we just make a call like this :

//filename : module/Album/Module.php
namespace Album;

class Module
{
     // getAutoloaderConfig() and getConfig() methods here

     // Add this method:
     public function getServiceConfig()
     {
         return array(
             'factories' => array(
                 'Album\Model\AlbumTable' =>  'Album\Factory\Model\AlbumTableFactory'
             ),
         );
     }
 }

That’s it, we now can call like this :

$table = $this->getServiceLocator()->get('Album\Model\AlbumTable');
$joinedData = $table->JoinfetchAll();

foreach($joinedData as $row) {
    //$row->track_id will called even not registered
    //in album model class.
    echo $row->artist.' : '.$row->track_id; 
}

We know that we need to retrieve track table column form *JoinfetchAll()* table. but we don’t need to register it to be retrieve-able.

Ok, done, happy hacking ;)

Zend Framework 2 : multiple named DB adapter instances using ‘adapters’ subkey

Posted in Zend Framework 2 by samsonasik on July 27, 2013

zf2-zendframework2Zend Framework 2.2 comes with abstract_factories Zend\Db\Adapter\AdapterAbstractServiceFactory that allow us to configure multiple named DB adapter instances. This is step by step to do it  :

1. Register Zend\Db\Adapter\AdapterAbstractServiceFactory at ‘abstract_factories’ type under ‘service_manager’ key.

//config/autoload/global.php
//.... part of config/autoload/global.php
    'service_manager' => array( 
        'abstract_factories' => array(
            'Zend\Db\Adapter\AdapterAbstractServiceFactory',
        ),
    ),

2. Configure ‘adapters’ subkey under ‘db’ key at config/autoload/global.php

//config/autoload/global.php
//.... part of config/autoload/global.php
    'db' => array(
        'adapters' => array(
            
            'db1' => array(
               'driver'         => 'Pdo',
               'dsn'             => 'mysql:dbname=zf2_staging;host=localhost',
               'driver_options'  => array(
                    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
                ),
            ),
            
            'db2' => array(
               'driver'         => 'Pdo',
               'dsn'             => 'mysql:dbname=zf2_test;host=localhost',
               'driver_options'  => array(
                    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
                ),
            ),
        ),
    ),

3. Configure ‘adapters’ subkey under ‘db’ key at config/autoload/local.php

//config/autoload/local.php
return array(
    'db' => array(
        'adapters' => array(
            'db1' => array(
                'username' => 'root',
                'password' => '',
            ),
            'db2' => array(
                'username' => 'other_user',
                'password' => 'other_user_passwd',
            ),
        ),
    ),
);

3. Call adapter using ‘db1′ or ‘db2′ as db adapter from ServiceManager

$sm->get('db1');
//OR
$sm->get('db2');

If you need to get $sm->get(‘Zend\Db\Adapter\Adapter’) as primary adapter, ‘db1′ and ‘db2′ as other adapter for specific purpose, then you need to define primary adapter directly under db, so the configuration of config/autoload/global.php will be like the following :

//config/autoload/global.php
return array(
    'db' => array(
        //this is for primary adapter....
        'driver'         => 'Pdo',
        'dsn'             => 'mysql:dbname=zf21_learn;host=localhost',
        'driver_options'  => array(
             PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
         ),
        
        //other adapter when it needed...
        'adapters' => array(
            
            'db1' => array(
               'driver'         => 'Pdo',
               'dsn'             => 'mysql:dbname=zf2_staging;host=localhost',
               'driver_options'  => array(
                    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
                ),
            ),
            'db2' => array(
               'driver'         => 'Pdo',
               'dsn'             => 'mysql:dbname=zf2_test;host=localhost',
               'driver_options'  => array(
                    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
                ),
            ),
            
        ),
    ),
    'service_manager' => array(
        // for primary db adapter that called
        // by $sm->get('Zend\Db\Adapter\Adapter')
        'factories' => array(
            'Zend\Db\Adapter\Adapter'
                    => 'Zend\Db\Adapter\AdapterServiceFactory',
        ),
        // to allow other adapter to be called by
        // $sm->get('db1') or $sm->get('db2') based on the adapters config.
        'abstract_factories' => array(
            'Zend\Db\Adapter\AdapterAbstractServiceFactory',
        ),
    ),
);

The config/autoload/global.local.php should be configured too like the following :

//config/autoload/local.php
return array(
    'db' => array(
        // for primary db adapter that called
        // by $sm->get('Zend\Db\Adapter\Adapter')
        'username' => 'root',
        'password' => '',
        
        // to allow other adapter to be called by
        // $sm->get('db1') or $sm->get('db2') based on the adapters config.
        'adapters' => array(
            'db1' => array(
                'username' => 'root',
                'password' => '',
            ),
            'db2' => array(
                'username' => 'other_user',
                'password' => 'other_user_passwd',
            ),
        ),
    ),
);

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 : Handling Db connection Error

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on June 2, 2013

zf2-zendframework2Db Connection error is not part of dispatch.error, so we should make a tricky way to handle it to make user happy when see the page. We should try the connection, and catch when failed. call ViewModel to set template with our custom error page, then render with ViewRenderer.
This is it :

//filename : module/YourModule/Module.php
namespace YourModule;
use Zend\Mvc\MvcEvent;

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $application   = $e->getApplication();
        $sm            = $application->getServiceManager();
        
        //try to connect, and if not connected, then catch...
        try {
            $dbInstance = $application->getServiceManager()
                                      ->get('Zend\Db\Adapter\Adapter');
            $dbInstance->getDriver()->getConnection()->connect();
        } catch (\Exception $ex) {
            $ViewModel = $e->getViewModel();
            $ViewModel->setTemplate('layout/layout');
                
            $content = new \Zend\View\Model\ViewModel();
            $content->setTemplate('error/mydberrorpagecustompage');
            
            //set $this->layout()->"content" variable
            //with error/mydberrorpagecustompage.phtml
            $ViewModel->setVariable('content', $sm->get('ViewRenderer')
                                                  ->render($content));
            
            exit($sm->get('ViewRenderer')->render($ViewModel));
        }
    }
    
    public function getConfig() { /* common code here */ }
    public function getAutoloaderConfig(){ /*common code here */   }
}

Still want to using EventManager? use at Wildcard attachment to make $callback available in all events (we can’t rely on dispatch.error, render.error, or other), like the following :

//filename : module/YourModule/Module.php
namespace YourModule;
use Zend\Mvc\MvcEvent;

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $application   = $e->getApplication();
        $eventManager  = $application->getEventManager();
        
        //using '*' to make $callback available in all events. 
        $eventManager->attach('*', array($this, 'dbInstanceError' ), 1000); 
    }

    public function dbInstanceError(MvcEvent $e)
    {
        $application   = $e->getTarget();
        $sm            = $application->getServiceManager();
        
        //try to connect, and if not connected, then catch...
        try {
            $dbInstance = $application->getServiceManager()
                                      ->get('Zend\Db\Adapter\Adapter');
            $dbInstance->getDriver()->getConnection()->connect();
        } catch (\Exception $ex) {
            $ViewModel = $e->getViewModel();
            $ViewModel->setTemplate('layout/layout');
                
            $content = new \Zend\View\Model\ViewModel();
            $content->setTemplate('error/mydberrorpagecustompage');
            
            //set $this->layout()->"content" variable
            //with error/mydberrorpagecustompage.phtml
            $ViewModel->setVariable('content', $sm->get('ViewRenderer')
                                                  ->render($content));
            
            echo $sm->get('ViewRenderer')->render($ViewModel);
            $e->stopPropagation();
        }
    } 
    
    public function getConfig() { /* common code here */ }
    public function getAutoloaderConfig(){ /*common code here */   }
}

If we are using EventManager, we can use :

$e->stopPropagation();

to stop further event.

Zend Framework 2 : Working with AuthenticationService and Session Db Save Handler

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on May 29, 2013

zf2-zendframework2One of the Session SaveHandlers that Zend Framework 2 provide is DbTableGateway Save Handler that utilize Zend\Db as a session save handler. How to combine authentication service with it ? Let’s learn about it! Save Handlers themselves are decoupled from PHP’s save handler functions and are only implemented as a PHP save handler when utilized in conjunction with Zend\Session\SessionManager.
1. Preparation
a. create tables

-- table to save session data....
CREATE TABLE IF NOT EXISTS `session` (
  `id` char(32) NOT NULL DEFAULT '',
  `name` char(32) NOT NULL DEFAULT '',
  `modified` int(11) DEFAULT NULL,
  `lifetime` int(11) DEFAULT NULL,
  `data` text,
  PRIMARY KEY (`id`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

-- users table
CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) NOT NULL,
  `password` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;

-- users data with password = md5('admin')
INSERT INTO `users` (`id`, `username`, `password`) VALUES
(1, 'admin', '21232f297a57a5a743894a0e4a801fc3');

b. create a module with structure like the following ( don’t judge me I’m not explain file location again :p )

sessionsavehandler

2. Create Classes
a. AuthStorage
It’s a class that extends Zend\Authentication\Storage\Session and utilize SessionManager to set Db Handler.

//filename : module/SanAuthWithDbSaveHandler/src/SanAuthWithDbSaveHandler/Storage/AuthStorage.php
namespace SanAuthWithDbSaveHandler\Storage;

use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Authentication\Storage;
use Zend\Session\Config\SessionConfig;
use Zend\Db\TableGateway\TableGateway;
use Zend\Session\SaveHandler\DbTableGateway;
use Zend\Session\SaveHandler\DbTableGatewayOptions;

class AuthStorage extends Storage\Session
    implements ServiceLocatorAwareInterface
{
    protected $serviceLocator;
    protected $namespace;

    public function __construct($namespace = null)
    {
        parent::__construct($namespace); 

        $this->namespace = $namespace;
    }
    
    public function setDbHandler()
    {
        $tableGateway = new TableGateway('session', 
                                            $this->getServiceLocator()
                                                 ->get('Zend\Db\Adapter\Adapter'));

        $saveHandler = new DbTableGateway($tableGateway,
                                            new DbTableGatewayOptions());
        
        //open session
        $sessionConfig = new SessionConfig();
        $saveHandler
            ->open($sessionConfig->getOption('save_path'), $this->namespace);
       //set save handler with configured session 
       $this->session->getManager()->setSaveHandler($saveHandler);
    }

    public function write($contents)
    {
        parent::write($contents);
        /**
            when $this->authService->authenticate(); is valid, the session 
            automatically called write('username')
          in this case, i want to save data like
         ["storage"] => array(4) {
              ["id"] => string(1) "1"
              ["username"] => string(5) "admin"
              ["ip_address"] => string(9) "127.0.0.1"
              ["user_agent"] => string(81) "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7;
                rv:18.0) 
              Gecko/20100101    Firefox/18.0"
        }*/
        if (is_array($contents) && !empty($contents)) {
            $this->getSessionManager()
                          ->getSaveHandler()
                          ->write($this->getSessionId(), \Zend\Json\Json::encode($contents));
        }
    }

    public function clear()
    {
        $this->getSessionManager()->getSaveHandler()->destroy($this->getSessionId());
        parent::clear();
    }

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

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

    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
    }

    public function getServiceLocator()
    {
        return $this->serviceLocator;
    }
}

b. AuthStorageFactory
Remember, that setting up service via closure is bad for performance, so we need to make a factory class for it.

//filename : module/SanAuthWithDbSaveHandler/src/SanAuthWithDbSaveHandler/Factory/Storage/AuthStorageFactory.php
namespace SanAuthWithDbSaveHandler\Factory\Storage;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use SanAuthWithDbSaveHandler\Storage\AuthStorage;

class AuthStorageFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $storage = new AuthStorage('my_storage_namespace');
        $storage->setServiceLocator($serviceLocator);
        $storage->setDbHandler();
        
        return $storage;
    }
}

c. AuthenticationServiceFactory
This is a Class that call AuthStorage class.

//filename : module/SanAuthWithDbSaveHandler/src/SanAuthWithDbSaveHandler/Factory/Storage/AuthenticationServiceFactory.php
namespace SanAuthWithDbSaveHandler\Factory\Storage;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Adapter\DbTable as DbTableAuthAdapter;

class AuthenticationServiceFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $dbAdapter      	 = $serviceLocator->get('Zend\Db\Adapter\Adapter');
        $dbTableAuthAdapter      = new DbTableAuthAdapter($dbAdapter, 'users',
                                                  'username','password', 'MD5(?)');
        
        $authService = new AuthenticationService($serviceLocator->get('AuthStorage'),
                                                                $dbTableAuthAdapter);
        
        return $authService;
    }
}

d. LoginForm class

//filename : module/SanAuthWithDbSaveHandler/src/SanAuthWithDbSaveHandler/Form/LoginForm.php
namespace SanAuthWithDbSaveHandler\Form;

use Zend\Form\Form;
use Zend\InputFilter;

class LoginForm extends Form
{
    public function __construct()
    {
        parent::__construct();
         
        $this->setAttribute('method', 'post');
        
        $this->add(array(
            'name' => 'username',
            'type' => 'Text',
            'options' => array(
                'label' => 'Username : '
            ),
        ));
        
        $this->add(array(
            'name' => 'password',
            'type' => 'Password',
            'options' => array(
                'label' => 'Password : '
            ),
        ));
        
         $this->add(array(
            'name' => 'Loginsubmit',
            'type' => 'Submit',
            'attributes' => array(
                'value' => 'Login',
                'id' => 'Loginsubmit',
            ),
        ));
         
        $this->setInputFilter($this->createInputFilter());
    }
    
    public function createInputFilter()
    {
        $inputFilter = new InputFilter\InputFilter();

        //username
        $username = new InputFilter\Input('username');
        $username->setRequired(true);
        $inputFilter->add($username);
        
        //password
        $password = new InputFilter\Input('password');
        $password->setRequired(true);
        $inputFilter->add($password);

        return $inputFilter;
    }
}

e. AuthController
This is a controller that place a Login Form and authentication service.

//filename : module/SanAuthWithDbSaveHandler/src/SanAuthWithDbSaveHandler/Controller/AuthController.php
namespace SanAuthWithDbSaveHandler\Controller;

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

class AuthController extends AbstractActionController
{
    protected $authService;
    
    //we will inject authService via factory
    public function __construct(AuthenticationService $authService)
    {
        $this->authService = $authService;
    }
    
    public function indexAction()
    {
        if ($this->authService->getStorage()->getSessionManager()
                 ->getSaveHandler()
                 ->read($this->authService->getStorage()->getSessionId())) {
            //redirect to success controller...
            return $this->redirect()->toRoute('success');
        }
        
        $form = $this->getServiceLocator()
                     ->get('FormElementManager')
                     ->get('SanAuthWithDbSaveHandler\Form\LoginForm');   
        $viewModel = new ViewModel();
        
        //initialize error...
        $viewModel->setVariable('error', '');
        //authentication block...
        $this->authenticate($form, $viewModel);
        
        $viewModel->setVariable('form', $form);
        return $viewModel;
    }
    
    /** this function called by indexAction to reduce complexity of function */
    protected function authenticate($form, $viewModel)
    {
        $request = $this->getRequest();
        if ($request->isPost()) {
            $form->setData($request->getPost());
            if ($form->isValid()) {
                $dataform = $form->getData();
                
                $this->authService->getAdapter()
                                       ->setIdentity($dataform['username'])
                                       ->setCredential($dataform['password']);
                $result = $this->authService->authenticate();
                if ($result->isValid()) {
                    //authentication success
                    $resultRow = $this->authService->getAdapter()->getResultRowObject();
                   
                    $this->authService->getStorage()->write(
                         array('id'          => $resultRow->id,
                                'username'   => $dataform['username'],
                                'ip_address' => $this->getRequest()->getServer('REMOTE_ADDR'),
                                'user_agent'    => $request->getServer('HTTP_USER_AGENT'))
                    );
                    
                    return $this->redirect()->toRoute('success', array('action' => 'index'));;       
                } else {
                    $viewModel->setVariable('error', 'Login Error');
                }
            }
        }
    }
    
    public function logoutAction()
    {
        $this->authService->getStorage()->clear();
        return $this->redirect()->toRoute('auth');
    }
}

f. AuthControllerServiceFactory
Controller creation need a factory, so we need to create a factory for it.

//filename : module/SanAuthWithDbSaveHandler/src/SanAuthWithDbSaveHandler/Factory/Controller/AuthControllerServiceFactory.php
namespace SanAuthWithDbSaveHandler\Factory\Controller;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use SanAuthWithDbSaveHandler\Controller\AuthController;

class AuthControllerServiceFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $authService = $serviceLocator->getServiceLocator()->get('AuthService');
        $controller = new AuthController($authService);
        
        return $controller;
    }
}

g. SuccessController
it’s a page that be redirected from AuthController. I’ve made a test only, you can check in real application. Real application should has authService that injected by EventManger in Module::onBootstrap.

//filename : module/SanAuthWithDbSaveHandler/src/SanAuthWithDbSaveHandler/Controller/SuccessController.php
namespace SanAuthWithDbSaveHandler\Controller;

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

class SuccessController extends AbstractActionController
{   
    public function indexAction()
    {
        //here for test only, you should check session
        //for real application       
    }
}

h. login page

<?php
//filename : module/SanAuthWithDbSaveHandler/view/san-auth-with-db-save-handler/auth/index.phtml
$title = 'Login';
$this->headTitle($title);
?>
<h1><?php echo $this->escapeHtml($title); ?></h1>

<?php
$form = $this->form;
$form->setAttribute('action', $this->url(
    'auth'
));
$form->prepare();

echo $this->form()->openTag($form);
echo $this->formCollection($form);
echo $this->form()->closeTag();

echo $this->error;

i. success page

<?php //filename : module/SanAuthWithDbSaveHandler/view/san-auth-with-db-save-handler/success/index.phtml ?>

login succeded.
<a href="<?php echo $this->url('auth', array('action' => 'logout')); ?>">Logout</a>

3. Register services
We should register services at SanAuthWithDbSaveHandler/config/module.config.php

//filename : SanAuthWithDbSaveHandler/config/module.config.php
namespace SanAuthWithDbSaveHandler;

return array(
    
    //controllers services...
    'controllers' => array(
        'factories' => array(
            'SanAuthWithDbSaveHandler\Controller\Auth' =>
               'SanAuthWithDbSaveHandler\Factory\Controller\AuthControllerServiceFactory'  
        ),
        'invokables' => array(
            'SanAuthWithDbSaveHandler\Controller\Success' =>
               'SanAuthWithDbSaveHandler\Controller\SuccessController'   
        ),
    ),
    
    //register auth services...
    'service_manager' => array(
        'factories' => array(
            'AuthStorage' =>
               'SanAuthWithDbSaveHandler\Factory\Storage\AuthStorageFactory',
            'AuthService' => 
               'SanAuthWithDbSaveHandler\Factory\Storage\AuthenticationServiceFactory',
        ),
    ),
    
    //routing configuration...    
    'router' => array(
        'routes' => array(
            
            'auth' => array(
                'type'    => 'segment',
                'options' => array(
                    'route'    => '/auth[/:action]',
                    'defaults' => array(
                        'controller' => 'SanAuthWithDbSaveHandler\Controller\Auth',
                        'action'     => 'index',
                    ),
                ),
            ),
            
            'success' => array(
                'type'    => 'segment',
                'options' => array(
                    'route'    => '/success[/:action]',
                    'defaults' => array(
                        'controller' => 'SanAuthWithDbSaveHandler\Controller\Success',
                        'action'     => 'index',
                    ),
                ),
            ),
        ),
    ),
    
    //setting up view_manager
    'view_manager' => array(
        'template_path_stack' => array(
            'SanAuthWithDbSaveHandler' => __DIR__ . '/../view',
        ),
    ),
);

4. Last but not least, a Module Class ! (It’s very important! haha :D )

//filename : SanAuthWithDbSaveHandler/Module.php
namespace SanAuthWithDbSaveHandler;

class Module
{
    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }

    public function getAutoloaderConfig()
    {
        return array(
            'Zend\Loader\StandardAutoloader' => array(
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ . '/src/' . str_replace('\\', '/' , __NAMESPACE__),
                ),
            ),
        );
    }
}

Now, try to login to application with url : http://YourZF2app/auth with username : admin and password : admin.

Done !
btw, I’ve uploaded source to my github account : https://github.com/samsonasik/SanAuthWithDbSaveHandler

References :
1. https://zf2.readthedocs.org/en/latest/modules/zend.session.save-handler.html
2. https://zf2.readthedocs.org/en/latest/modules/zend.authentication.intro.html
3. https://github.com/cgmartin/ZF2FileUploadExamples/blob/master/src/ZF2FileUploadExamples/Form/CollectionUpload.php

Follow

Get every new post delivered to your Inbox.

Join 220 other followers