3
votes

For my ZF2 form validation I need the entity manager in my custom validator. I Zend\ServiceManager\ServiceLocatorAwareInterface to get the service locator in my validator so I would be able to get the entitymanager from there.

My problem is that this Zend\ServiceManager\ServiceLocatorAwareInterface::setServiceLocator injects the "Zend\Validator\ValidatorPluginManager" and not the desired ServiceLocator.

Does anybody know how to solve this problem?

My abstract controller

abstract class AbstractController extends AbstractActionController
{    
    /**
     * This property contains the doctrine entitymanager.
     * @var Doctrine\ORM\EntityManager
     */
    protected $_entityManager;

    /**
     * This method returns the doctrine entitymanager.
     * @return \Doctrine\ORM\EntityManager
     */
    public function getEntityManager()
    {
        var_dump(spl_object_hash($this->getServiceLocator())); echo '<br>';
        if (null === $this->_entityManager)
            $this->_entityManager = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
        return $this->_entityManager;
    }
}

My validator:

class EntityUnique extends AbstractValidator implements ServiceLocatorAwareInterface
{
    protected $_serviceLocator;

    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
    {
        var_dump(spl_object_hash($serviceLocator)); echo '<br>';
        $this->_serviceLocator = $serviceLocator;
    }

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

When executing this both var_dumps result in another object hash:

string(32) "000000005e4258390000000024a7f829"
string(32) "000000005e425a2d0000000024a7f829" 
2
Are you re-inventing the wheel? > github.com/doctrine/DoctrineModule/blob/master/docs/… Other than pointing you to the existing DoctrineValidators, i can't help you out tho, sry's ,)Sam
@Sam No I am not. I'm building a more generic validator which will be able to handle filters, exceptions and context parameters.Simon

2 Answers

3
votes

The reason for this is that in your validator you are getting the Zend\Validator\ValidatorPluginManager injected. If you want the the Service Manager call getServiceLocator on the Zend\Validator\ValidatorPluginManager.

For example look at the someMethod() method bellow:

class EntityUnique extends AbstractValidator implements ServiceLocatorAwareInterface
{
    protected $_serviceLocator;

    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
    {
        var_dump(spl_object_hash($serviceLocator)); echo '<br>';
        $this->_serviceLocator = $serviceLocator;
    }

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

    public function someMethod() 
    {
        $validatorPluginManager = $this->getServiceLocator();
        $serviceLocator = $validatorPluginManager->getServiceLocator(); // HERE

    }
}

Hope this helps :)

Stoyan

0
votes

I got it working in the end. I don't know if this is the best practice so if you have any feedback don't hesitate. My validator ended up like this:

<?php
namespace Flex\Validator;

use Doctrine\ORM\EntityManager;
use Zend\Stdlib\ArrayUtils;
use Zend\ServiceManager\ServiceManager;
use Zend\Validator\AbstractValidator;

class EntityUnique extends AbstractValidator
{
    const EXISTS                = 'exists';

    protected $messageTemplates = array(
        self::EXISTS    => 'A %entity% entity allready exists.',
    );
    protected $messageVariables = array(
        'entity'        => '_entity',
    );

    protected $_context;
    protected $_entity;
    protected $_excludes        = array();
    protected $_filters         = array();
    protected $_property;
    protected $_serviceLocator;

    public function __construct($options = null)
    {
        if ($options instanceof \Traversable)
            $options            = ArrayUtils::iteratorToArray($options);

        if (is_array($options))
        {
            if (isset($options['entity']))
                $this->_entity        = $options['entity'];
            if (isset($options['exclude']))
            {
                if (!is_array($options['exclude']))
                    throw new \Exception('Exclude option should be an array');
                if (isset($options['exclude']['property']) && isset($options['exclude']['value']))
                    $this->_excludes[] = array(
                        'property'    => $options['exclude']['property'],
                        'value'       => $options['exclude']['value'],
                    );
                else
                    foreach ($options['exclude'] as $exclude)
                    {
                        if (!isset($exclude['property']) || !isset($exclude['value']))
                            throw new \Exception('exclude shoud contain a property and value');
                            $this->_excludes[] = array(
                                    'property'    => $exclude['property'],
                                    'value'       => $exclude['value'],
                            );
                    }

            }
            if (isset($options['filter']))
            {
                if (!is_array($options['filter']))
                    throw new \Exception('Filter option should be an array');
                if (isset($options['filter']['property']) && isset($options['filter']['value']))
                    $this->_filters[] = array(
                        'property'    => $options['filter']['property'],
                        'value'       => $options['filter']['value'],
                    );
                else
                    foreach ($options['filter'] as $filter)
                    {
                        if (!isset($filter['property']) || !isset($filter['value']))
                            throw new \Exception('Filters shoud contain a property and value');
                            $this->_filters[] = array(
                                    'property'    => $filter['property'],
                                    'value'       => $filter['value'],
                            );
                    }

            }
            if (isset($options['property']))
                $this->_property          = $options['property']; 
            if (!isset($options['serviceLocator']))
                throw new \InvalidArgumentException(__CLASS__ . ' requires the option serviceLocator.');
            $ServiceManagerInstance   = 'Zend\ServiceManager\ServiceManager';
            if (!($options['serviceLocator'] instanceof $ServiceManagerInstance))
                throw new \InvalidArgumentException(__CLASS__ . ' expects the option serviceLocator to be an instance of Zend\ServiceManager\ServiceManager.');

            $this->_serviceLocator    = $options['serviceLocator']; 
        }
        parent::__construct(is_array($options) ? $options : null);
    }
    public function isValid($value, $context = null)
    {
        $this->setValue($value);
        $this->_context  = $context;

        $entityManager   = $this->_serviceLocator->get('doctrine.entitymanager.orm_default');
        $queryBuilder    = $entityManager->createQueryBuilder();
        $queryBuilder->from($this->_entity, 'e')
            ->select('COUNT(e)')
            ->where('e.' . $this->_property . ' = :value')
            ->setParameter('value', $this->getValue());

        if (count($this->_excludes))
            foreach ($this->_excludes as $key => $exclude)
            {
                $exclude['value']        = $this->parse($exclude['value']);
                if ($exclude['value'] !== null)
                    $queryBuilder->andWhere('e.' . $exclude['property'] . ' = :exclude_' . $key)
                        ->setParameter('exclude_' . $key, $exclude['value']);
                else
                    $queryBuilder->andWhere('e.' . $exclude['property'] . ' IS NULL');
            }

        if (count($this->_filters))
            foreach ($this->_filters as $key => $filter)
            {
                $filter['value']        = $this->parse($filter['value']);
                if ($filter['value'] !== null)
                    $queryBuilder->andWhere('e.' . $filter['property'] . ' = :filter_' . $key)
                        ->setParameter('filter_' . $key, $filter['value']);
                else
                    $queryBuilder->andWhere('e.' . $filter['property'] . ' IS NULL');
            }

        $query = $queryBuilder->getQuery();
        if ((integer) $query->getSingleScalarResult() !== 0)
        {
            $this->error(self::EXISTS);
            return false;
        }
        return true;
    }
    private function parse($input, $current = null)
    {
        if (!is_array($input))
            return $input;
        if ($current === null)
            $current     = $this;

        $currentKey      = array_shift($input);
        if (is_object($current) && property_exists($current, $currentKey))
            $current     = $current->$currentKey;
        elseif (is_array($current) && array_key_exists($currentKey, $current))
            $current     = $current[$currentKey];
        else
            return null;

        if (count($input))
            return $this->parse($input, $current);

        if (strlen($current) == 0)
            return null;

        return $current;
    }
}

This is loaded in like this:

<?php
namespace FlexCategories\Form;

use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\ServiceManagerAwareInterface;

class CategoryForm extends Form implements InputFilterProviderInterface, ServiceManagerAwareInterface
{
    private $_serviceManager;

    public function init()
    {
        /* */
    }
    public function getInputFilterSpecification()
    {
        return array(
            'name'         => array(
                'filters'     => array(
                    array(
                        'name'    => 'Zend\Filter\StringTrim'
                    ),
                ),
                'required'    => true,
                'validators'  => array(
                    array(
                        'name'    => 'Flex\Validator\EntityUnique',
                        'options' => array(
                            'entity'    => 'FlexCategories\Entity\Category',
                            'filter'   => array(
                                array('property'     => 'parent',
                                      'value'        => array('_context', 'parent')),
                            ),
                            'property'  => 'name',
                            'serviceLocator'    => $this->_serviceManager,
                        ),
                    ),
                ),
            ),
        );
    }

    public function setServiceManager(ServiceManager $serviceManager)
    {
        $this->_serviceManager = $serviceManager;
        $this->init();

        return $this;
    }
}

The form is registered in the service_manager => invokables and is retreived within the controller thorugh the service manager.

This worked for me. I hope somebody will be able to learn from this or give me feedbacl to create an better solution.