Welcome to Abdul Malik Ikhsan's Blog

Zend Framework 2 : Using __invoke(PluginManager $manager) in Service’s Factory

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on March 31, 2015

zf2-zendframework2I assume you already knew about registering service with factories type via closure or factory class that implements Zend\ServiceManager\FactoryInterface which is quite complex to do, and if we use pluginManager, for example, on Controller creation, we pushed to use :

namespace Application\Factory\Controller;

use Application\Controller\IndexController;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManger\ServiceLocatorInterface;

class IndexControllerFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $services  = $serviceLocator->getServiceLocator();    
        $myService = $services->get('MyService');
        
        return new IndexController($myService);
    }
}

Although above way will work, if you check above code with scrutinizer, you will get error :
interface-error-implementation

This is because the getServiceLocator() only exists in the concrete implementation(the ControllerManager class), not in the ServiceLocatorInterface interface.

__invoke(PluginManager $manager) for the rescue

If you read the documentation, you can get quote :

The factories should be either classes implementing Zend\ServiceManager\FactoryInterface 
or invokable classes.

That mean, you can use class that has __invoke() method, so you can pass the PluginManager instead of ServiceLocatorInterface.

namespace Application\Factory\Controller;

use Application\Controller\IndexController;
use Zend\Mvc\Controller\ControllerManager;

class IndexControllerFactory
{
    public function __invoke(ControllerManager $controllerManager)
    {
        $services  = $controllerManager->getServiceLocator();
        $myService = $services->get('MyService');
        
        return new IndexController($myService);
    }
}

Now, your scrutinizer check will be happy ;).

Note :
As Lucas Suggestion, if you still want to use createService(), you can check it with whenever $serviceLocator instanceof Zend\ServiceManager\ServiceLocatorAwareInterface, then we call getServiceLocator() :

// ...
function createService(ServiceLocatorInterface $serviceLocator)
{
   if ($serviceLocator instanceof ServiceLocatorAwareInterface) {
       $serviceLocator = $serviceLocator->getServiceLocator();
   }
   // ...
}
// ...

References :
1. http://blog.alejandrocelaya.com/2014/10/09/advanced-usage-of-service-manager-in-zend-framework-2/#comment-1627763990
2. https://samsonasik.wordpress.com/2013/01/02/zend-framework-2-cheat-sheet-service-manager/
3. http://framework.zend.com/manual/current/en/modules/zend.service-manager.quick-start.html#using-configuration

Using Doctrine Data Fixture for Testing QueryBuilder inside Repository

Posted in testing, Tutorial PHP by samsonasik on March 24, 2015

Doctrine This is an immediate post to incorporate my latest post about Mocking Doctrine\ORM\AbstractQuery to test querybuilder. Well, while that works, Ocramius – A Doctrine core team – said that we shouldn’t do that way, as SQL and DQL are actual code. What we can do is using data fixture. There is a sample that he gave with ZF2 environment. In this post, I will try to write a demo with step by step about repository class that consume a QueryBuilder, so we can test it.

Let’s start with the Entity

Like my post before, I will going to use News entity :

namespace DoctrineFixtureDemo\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * News
 *
 * @ORM\Table(name="news")
 * @ORM\Entity(repositoryClass="DoctrineFixtureDemo\Repository\NewsRepository")
 */
class News
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="bigint", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

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

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

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

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

        return $this;
    }

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

    /**
     * Set content.
     *
     * @param string $content
     *
     * @return self
     */
    public function setContent($content)
    {
        $this->content = $content;

        return $this;
    }

    /**
     * Get content.
     *
     * @return string
     */
    public function getContent()
    {
        return $this->content;
    }
}

The Repository

I have a repository to get latest news with limit parameter, like the following :

namespace DoctrineFixtureDemo\Repository;

use Doctrine\ORM\EntityRepository;

class NewsRepository extends EntityRepository
{
    /**
     * Get Latest News.
     *
     * @param int $limit
     *
     * @return array
     */
    public function getLatestNews($limit)
    {
        $result = $this->createQueryBuilder('n')
                       ->setFirstResult(0)
                       ->setMaxResults($limit)
                       ->getQuery()->getResult();

        return $result;
    }
}

Fixture Dependency

We need "doctrine/data-fixtures" dependency in our vendor/, we can require by composer command :

$ composer require "doctrine/data-fixtures 1.0.*"

Fixture Class

We can create a fixture class to insert some sample data to be tested.

namespace DoctrineFixtureDemo\DataFixture;

use DoctrineFixtureDemo\Entity\News;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\DataFixtures\FixtureInterface;

class NewsLoad implements FixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $news = new News();
        $news->setTitle('bar');
        $news->setContent('BarBazBat');

        $news2 = new News();
        $news2->setTitle('bar2');
        $news2->setContent('BarBazBat2');

        $manager->persist($news);
        $manager->persist($news2);
        $manager->flush();
    }
}

We are going to use Doctrine\Common\DataFixtures\Executor\ORMExecutor to execute loaded fixture class(es). We can setup fixture test by use database that only for test / not for production ( – note : this is just sample, you can rely on your framework for get the db setting, EntityManager, SchemaTool, etc – ) by creating class like the following :

namespace DoctrineFixtureDemotest;

use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Tools\SchemaTool;

final class FixtureManager
{
    /**
     * Get EntityManager
     */
    public static function getEntityManager()
    {
        $paths = [dirname(__DIR__).'/src/DoctrineFixtureDemo/Entity'];
        $isDevMode = true;

        // the TEST DB connection configuration
        $connectionParams = [
            'driver'   => 'pdo_mysql',
            'user'     => 'root',
            'password' => '',
            'dbname'   => 'foobartest',
        ];

        $config = Setup::createConfiguration($isDevMode);
        $driver = new AnnotationDriver(new AnnotationReader(), $paths);

        AnnotationRegistry::registerLoader('class_exists');
        $config->setMetadataDriverImpl($driver);

        $entityManager = EntityManager::create($connectionParams, $config);

        return $entityManager;
    }
    
    /**
     * Make sure drop and create tables
     */
    public static function start()
    {
        $schemaTool = new SchemaTool(static::getEntityManager());
        $metadatas  = static::getEntityManager()
                            ->getMetadataFactory()
                            ->getAllMetadata();

        $schemaTool->dropSchema($metadatas);
        $schemaTool->createSchema($metadatas);
     }

    /**
     * @return ORMExecutor
     */
    public static function getFixtureExecutor()
    {
        return new ORMExecutor(
            static::getEntityManager(),
            new ORMPurger(static::getEntityManager())
        );
    }
}

In favor of above class, we can always call \DoctrineFixtureDemotest\FixtureManager::start(); at our phpunit’s bootstrap :

chdir(__DIR__);

$loader = null;
if (file_exists('../vendor/autoload.php')) {
    $loader = include '../vendor/autoload.php';
} else {
    throw new RuntimeException('vendor/autoload.php could not be found. Did you run `php composer.phar install`?');
}

$loader->add('DoctrineFixtureDemotest', __DIR__);
\DoctrineFixtureDemotest\FixtureManager::start();

So, all tables will be removed in the every beginning test.

Repository Test

Time for test, Our repository class test can be like the following :

namespace DoctrineFixtureDemoTest\Repository;

use DoctrineFixtureDemo\DataFixture\NewsLoad;
use DoctrineFixtureDemotest\FixtureManager;
use DoctrineFixtureDemo\Repository\NewsRepository;
use Doctrine\ORM\Mapping\ClassMetadata;
use PHPUnit_Framework_TestCase;

class NewsRepositoryTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        $this->repository      = new NewsRepository(
            FixtureManager::getEntityManager(),
            new ClassMetadata('DoctrineFixtureDemo\Entity\News')
        );

        $this->fixtureExecutor = FixtureManager::getFixtureExecutor();
    }

    public function testGetLatestNews()
    {
        $this->fixtureExecutor->execute([new NewsLoad()]);
        $this->assertCount(1, $this->repository->getLatestNews(1));
        $this->assertInstanceOf('DoctrineFixtureDemo\Entity\News', $this->repository->getLatestNews(1)[0]); 
    }
}

If everything goes well, then your repository class will be tested.

Want to grab the sourcecode?, you can take a look https://github.com/samsonasik/DoctrineFixtureDemo

Another approach

You can already have prepared tables before test executed, and call truncate in every tearDown() at your unit test class as Manuel Stosic suggestion.

References :
1. https://twitter.com/Ocramius/status/577979551281729536
2. https://github.com/doctrine/data-fixtures
3. https://gist.github.com/Ocramius/3994325

Testing Doctrine 2 Query Result from QueryBuilder with PHPUnit

Posted in testing, Tutorial PHP by samsonasik on March 18, 2015

DoctrineYes, to get the result of QueryBuilder, we need to get Query first(which is final class :p). So, how to mock it? Impossible, huh? Don’t worry, we have the Query Abstract class! I got the very smart solution from this post by Julius Beckmann. Let me create a use case and paraphrase for that sample. The way we an do is we can mock the Abstract class and registers methods that we want to use, for example, on this case, the getResult method.

Use Case

We need to get latest data of news with limit and order, so we use QueryBuilder and its Query Result like the following :

namespace OurApp\Service;

use OurApp\Entity\News;
use Doctrine\ORM\EntityManager;

class NewsService
{
    /**
     * @var EntityManager
     */
    private $manager;
    
    /**
     * Construct
     * @param EntityManager $manager
     */
    public function __construct(EntityManager $manager)
    {
        $this->manager = $manager;    
    }
    
    /**
     * Get Last Recent News
     *
     * @param int $limit
     * @return array
     */
    public function getLastRecentNews($limit)
    {
        $result = $this->manager->getRepository(News::class)->createQueryBuilder('n')
                       ->setFirstResult(0)
                       ->setMaxResults($limit)
                       ->orderBy('n.id', 'DESC')
                       ->getQuery()
                       ->getResult();
        return $result;
    }
}

Of course, you can create instance of above class by factory with constructor injection.

The Test

This is it, we can mock the EntityManager, expects EntityRepository, expects QueryBuilder, use QueryBuilder until getResult() ready to be called, and use AbstractQuery with define only ‘getResult’ for expecting Result.

namespace OurAppTest\Service;

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\AbstractQuery;
use OurApp\Entity\News;
use OurApp\Service\NewsService;
use PHPUnit_Framework_TestCase;

class NewsServiceTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var EntityManager
     */
    private $manager;
    
    /**
     * @var NewsService
     */
    private $service; 
    
    public function setUp()
    {
        $this->manager  = $this->getMockBuilder(EntityManager::class)
                               ->disableOriginalConstructor()
                               ->getMock();

        $this->service = new NewsService($this->entityManager);
    }
    
    public function testLastRecentNews()
    {
        $repository = $this->getMockBuilder(EntityRepository::class)
                           ->disableOriginalConstructor()
                           ->getMock();
        $this->manager->expects($this->once())
                            ->method('getRepository')
                            ->with(News::class)
                            ->will($this->returnValue($repository));
        
        $queryBuilder = $this->getMockBuilder(QueryBuilder::class)
                           ->disableOriginalConstructor()
                           ->getMock();
        $repository->expects($this->once())
                   ->method('createQueryBuilder')
                   ->with('n')
                   ->will($this->returnValue($queryBuilder));
        
        // We use QueryBuilder as Fluent Interface
        // several times, so we need to make it sequence
        $queryBuilder->expects($this->at(0))
                     ->method('setFirstResult')
                     ->with(0)
                     ->will($this->returnValue($queryBuilder));
        $queryBuilder->expects($this->at(1))
                     ->method('setMaxResults')
                     ->with(2)
                     ->will($this->returnValue($queryBuilder));
        $queryBuilder->expects($this->at(2))
                     ->method('orderBy')
                     ->with('n.id', 'DESC')
                     ->will($this->returnValue($queryBuilder));

        // We use AbstractQuery
        $getQuery = $this->getMockBuilder(AbstractQuery::class)
                         ->setMethods(array('getResult'))
                         ->disableOriginalConstructor()
                         ->getMockForAbstractClass();
        $queryBuilder->expects($this->at(3))
                     ->method('getQuery')
                     ->will($this->returnValue($getQuery));
        
        $entity1 = new News();
        $entity1->setHeadLine('Hello world from PHP');
        
        $entity2 = new News();
        $entity2->setHeadLine('Hello world from PHPUnit');
        
        $result = [
            0 => $entity1,
            1 => $entity2,
        ];             
        $getQuery->expects($this->once())
                 ->method('getResult')
                 ->will($this->returnValue($result));
        
        $this->assertEquals($result, $this->service->getLastRecentNews(2));
    }
}

I hope this sample make easier to understand for you who find a way to solve it.

Note
Ocramius suggest to not do it. He suggest to use Fixture instead like his gist. I created a new post in favor of it.

References :
1. http://h4cc.tumblr.com/post/61502458780/phpunit-mock-for-doctrine-orm-query
2. https://gist.github.com/gnutix/7746893

Image :
https://avatars2.githubusercontent.com/u/209254?v=3&s=200

Conditional Redirect on ZfcUser when login success

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on March 15, 2015

I have been digging with ZfcUser in this week, and I realize, I need to do conditional redirection when authentication successful. With current version ( 1.2.2 ), the ‘login_redirect_route’ only support ‘string’ value,  which is very strict. We currently can’t pass callable to it like the following code ( hopefully, in next version or  2.* we can do ) :

'login_redirect_route' => function(\ZfcUser\Entity\UserInterface $user) {
    if ($user->getRole()->getName() === 'admin') {
        return 'zfcadmin';
    }
    return 'zfcuser';
}

We need another approach right now!

Clues

1. ZfcUser\Options\ModuleOptions

The ZfcUser configuration is consumed by ZfcUser\Options\ModuleOptions, registered as ‘zfcuser_module_options’ service, so, we can use it to re-set the ‘loginRedirectRoute’ property value, and re-register as service.

2. ‘authenticate.success’ event

‘authenticate.success’ is an event that will be triggered when we catch that user has Identity on Authentication process, in ZfcUser\Authentication\Adapter\AdapterChain::prepareForAuthentication.

Execution

With 2 clues above, What we can do now is, using listener that re-set ‘zfcuser_module_options’ service which bring new ‘loginRedirectRoute’, that react to ‘authenticate.success’. We can do in our Module class like the following :

use Application\Entity\User;
class Module
{
    // ...
    public function onBootstrap($e)
    {
        $servicemaManager = $e->getTarget()->getServiceManager();
        $objectManager    = $serviceManager->get('objectManager');

        $zfcAuthEvents    =  $serviceManager->get('ZfcUser\Authentication\Adapter\AdapterChain')->getEventManager();
        $zfcAuthEvents->attach('authenticate.success', function($authEvent) use ($serviceManager, $objectManager) {
            $userId = $authEvent->getIdentity();
            $user   = $objectManager->find(User::class, $userId);

            if ($user->getRole()->getName() === 'admin') {
                // we are going to re-set service,
                // we need to set allow_override of serviceManager= true
                $serviceManager->setAllowOverride(true);

                $zfcuserModuleOptions = $serviceManager->get('zfcuser_module_options');
                $zfcuserModuleOptions->setLoginRedirectRoute('zfcadmin');
                $serviceManager->setService('zfcuser_module_options', $zfcuserModuleOptions);

                // set 'allow_override' back to false
                $serviceManager->setAllowOverride(false);
            }
        });
    }
}

Now, whenever we got role name = ‘admin’ during authentication process, we will be redirected to ‘zfcadmin’ route.

References :
1. http://stackoverflow.com/questions/16053479/zfcuser-redirect-roles-to-different-pages-after-login
2. Conversation with Mr. Daniel Strøm
3. http://circlical.com/blog/2013/7/5/capturing-auth-events-with-zfcuser