Zend Framework 3 – Doctrine getScalarResult + Form ObjectSelect

Se você esta utilizando Zend Framework 3 com Doctrine provavelmente já se deparou com algumas situações complicadas relacionadas aos formulários, no meu caso eu utilizo uma combo para trazer os valores e para isso eu sempre utilizava o ObjectSelect do Doctrine localizado em “\DoctrineModule\Form\Element\ObjectSelect”, mas surgiu a necessidade de construir uma consulta totalmente personalizada com relacionamentos de tabelas e que seus resultados são retornados em forma de array, geralmente pra isso utilizamos o retorno getScalarResult().

Pelo que andei mexendo nas classes ainda não existe o recurso para tornar possível essa combinação, pois na hora de exibir os resultados dentro da combo até é possível exibir o texto visível pela opção “label_generator”, mas é apresentado um erro e o valor do elemento não é preenchido com o ID.

Caso seja possível utilizar esse recurso sem a adaptação que eu criei, por favor, ignore meu erro e me informe a respeito, simplesmente me deparei com essa situação e em modo de emergência criei este recurso.

Agora vamos ao meu método:
MODRastreador\Entity\RastreadorRepository

<?php

namespace MODRastreador\Entity;

use Doctrine\ORM\EntityRepository;
use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Storage\Session as SessionStorage;
use \Doctrine\ORM\Query\Expr;

class RastreadorRepository extends EntityRepository {

    protected $admIdentity = null;

    public function buscaRastreadoresDisponiveis() {

        $em = $this->getEntityManager();

        $rastreador = $em->createQueryBuilder('a');
        $rastreadores = $rastreador->select(array('r', 'rm'))
                ->from(\MODRastreador\Entity\Rastreador::class, 'r')
                ->leftJoin(\MODRastreamento\Entity\Rastreamento::class, 'rm', Expr\Join::WITH, 'r.id <> rm.idRastreador')
                ->where('r.ativo = ?1')
                ->andWhere('r.idEmpresa = ?2')
                ->andWhere('rm.idEmpresa = ?3')
                ->groupBy('r.imei')
                ->orderBy('r.imei', 'ASC')
                ->setParameter(1, true)
                ->setParameter(2, $this->getIdentityUser()->getId())
                ->setParameter(3, $this->getIdentityUser()->getId())
                ->getQuery()
                ->getScalarResult();

        return $rastreadores;
    }
}

Não vou postar o código do formulário completo, pois não há necessidade, se você chegou até este ponto é porque você já sabe criar formulários no Zend Framework e não é o meu objetivo ensinar isso agora.
\MODRastreamento\Form\Rastreamento

        // *** OBSERVE O CAMINHO DIFERENTE PARA O OBJECTSELECT

        $id_rastreador = new \MODRastreador\Form\Element\ObjectSelect('idRastreador');
        $id_rastreador->setLabel('Rastreador p/ Transferir');
        $id_rastreador->setOptions(array(
                    'object_manager' => $objectManager,
                    'target_class' => \MODRastreador\Entity\Rastreador::class,
                    'label_generator' => function($target) {
                        return $target['r_imei'];
                    },
                    'value_generator' => function($target) {
                        return $target['r_id'];                                
                    },
                    'is_method' => true,
                    'find_method' => [
                        'name' => 'buscaRastreadoresDisponiveis',
                    ],
                    'display_empty_item' => true,
                    'empty_item_label' => 'Selecione o Rastreador.',
                ))
                ->setAttribute('class', 'form-control select2')
                ->setAttribute('required', true);
        $this->add($id_rastreador);

Veja estou criando um elemento ObjectSelect no qual eu aponto um método para consulta chamado “buscaRastreadoresDisponiveis” estou passando também minha opção de “label_generator” onde até aqui tudo normal, você conseguiria fazer isso no ObjectSelect original.

Mas observe que eu criei uma opção a mais chamada de “value_generator” na qual eu passo através de um método qual valor do meu array será o id da minha combo. Obviamente você já deve ter reparado que eu não chamei o ObjectSelect nativo do Doctrine, pois eu criei um ObjectSelect personalizado. Basicamente essa classe trabalha com a classe Proxy que é onde toda a mágica acontece.

Então dentro da minha pasta Form, eu criei uma pasta chamada Element e dentro coloquei minhas duas classes ObjectSelect e Proxy e quando vou criar chamo eles e não o nativo do Doctrine. (Os meus são apenas uma cópia do original com algumas modificações). Por mais que o Doctrine atualize duvido que terá grandes modificações ao ponto de não conseguir utilizar essas classes ou então que utilizando essas classes deixará de utilizar recursos novos futuros, até mesmo porque até lá se surgir algo de especial eu estarei alterando este artigo.

Siga minhas classes para Download

Vou postar também o código das duas aqui, mas como será grande de mais vale mais a pena baixar:
ObjectSelect

<?php
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the MIT license. For more information, see
 * <http://www.doctrine-project.org>.
 */

namespace MODRastreador\Form\Element;

use MODRastreador\Form\Element\Proxy;
use Zend\Form\Element\Select as SelectElement;
use Zend\Form\Form;
use Zend\Stdlib\ArrayUtils;

class ObjectSelect extends SelectElement
{
    /**
     * @var Proxy
     */
    protected $proxy;

    /**
     * @return Proxy
     */
    public function getProxy()
    {
        if (null === $this->proxy) {
            $this->proxy = new Proxy();
        }
        return $this->proxy;
    }

    /**
     * @param  array|\Traversable $options
     * @return self
     */
    public function setOptions($options)
    {
        $this->getProxy()->setOptions($options);
        return parent::setOptions($options);
    }

    /**
     * @param string $key
     * @param mixed $value
     * @return self
     */
    public function setOption($key, $value)
    {
        $this->getProxy()->setOptions(array($key => $value));
        return parent::setOption($key, $value);
    }

    /**
     * {@inheritDoc}
     */
    public function setValue($value)
    {
        $multiple = $this->getAttribute('multiple');

        if (true === $multiple || 'multiple' === $multiple) {
            if ($value instanceof \Traversable) {
                $value = ArrayUtils::iteratorToArray($value);
            } elseif ($value == null) {
                return parent::setValue(array());
            } elseif (!is_array($value)) {
                $value = (array) $value;
            }

            return parent::setValue(array_map(array($this->getProxy(), 'getValue'), $value));
        }

        return parent::setValue($this->getProxy()->getValue($value));
    }

    /**
     * {@inheritDoc}
     */
    public function getValueOptions()
    {
        if (! empty($this->valueOptions)) {
            return $this->valueOptions;
        }

        $proxyValueOptions = $this->getProxy()->getValueOptions();

        if (! empty($proxyValueOptions)) {
            $this->setValueOptions($proxyValueOptions);
        }

        return $this->valueOptions;
    }
}

Proxy

<?php

/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the MIT license. For more information, see
 * <http://www.doctrine-project.org>.
 */

namespace MODRastreador\Form\Element;

use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Persistence\ObjectManagerAwareInterface;
use InvalidArgumentException;
use ReflectionMethod;
use RuntimeException;
use Traversable;
use Zend\Stdlib\Guard\ArrayOrTraversableGuardTrait;

class Proxy implements ObjectManagerAwareInterface {

    use ArrayOrTraversableGuardTrait;

    /**
     * @var array|Traversable
     */
    protected $objects;

    /**
     * @var string
     */
    protected $targetClass;

    /**
     * @var array
     */
    protected $valueOptions = array();

    /**
     * @var array
     */
    protected $findMethod = array();

    /**
     * @var
     */
    protected $property;

    /**
     * @var array
     */
    protected $option_attributes = array();

    /**
     * @var callable $labelGenerator A callable used to create a label based on an item in the collection an Entity
     */
    protected $labelGenerator;

    /**
     * @var callable $value Generator Um chamado usado para criar um valor com base em um item na coleção uma Entidade
     */
    protected $valueGenerator;

    /**
     * @var bool|null
     */
    protected $isMethod;

    /**
     * @var ObjectManager
     */
    protected $objectManager;

    /**
     * @var bool
     */
    protected $displayEmptyItem = false;

    /**
     * @var string
     */
    protected $emptyItemLabel = '';

    /**
     * @var string|null
     */
    protected $optgroupIdentifier;

    /**
     * @var string|null
     */
    protected $optgroupDefault;

    public function setOptions($options) {
        if (isset($options['object_manager'])) {
            $this->setObjectManager($options['object_manager']);
        }

        if (isset($options['target_class'])) {
            $this->setTargetClass($options['target_class']);
        }

        if (isset($options['property'])) {
            $this->setProperty($options['property']);
        }

        if (isset($options['label_generator'])) {
            $this->setLabelGenerator($options['label_generator']);
        }

        if (isset($options['value_generator'])) {
            $this->setValueGenerator($options['value_generator']);
        }

        if (isset($options['find_method'])) {
            $this->setFindMethod($options['find_method']);
        }

        if (isset($options['is_method'])) {
            $this->setIsMethod($options['is_method']);
        }

        if (isset($options['display_empty_item'])) {
            $this->setDisplayEmptyItem($options['display_empty_item']);
        }

        if (isset($options['empty_item_label'])) {
            $this->setEmptyItemLabel($options['empty_item_label']);
        }

        if (isset($options['option_attributes'])) {
            $this->setOptionAttributes($options['option_attributes']);
        }

        if (isset($options['optgroup_identifier'])) {
            $this->setOptgroupIdentifier($options['optgroup_identifier']);
        }

        if (isset($options['optgroup_default'])) {
            $this->setOptgroupDefault($options['optgroup_default']);
        }
    }

    public function getValueOptions() {
        if (empty($this->valueOptions)) {
            $this->loadValueOptions();
        }

        return $this->valueOptions;
    }

    /**
     * @return array|Traversable
     */
    public function getObjects() {
        $this->loadObjects();

        return $this->objects;
    }

    /**
     * Set the label for the empty option
     *
     * @param string $emptyItemLabel
     *
     * @return Proxy
     */
    public function setEmptyItemLabel($emptyItemLabel) {
        $this->emptyItemLabel = $emptyItemLabel;

        return $this;
    }

    /**
     * @return string
     */
    public function getEmptyItemLabel() {
        return $this->emptyItemLabel;
    }

    /**
     * @return array
     */
    public function getOptionAttributes() {
        return $this->option_attributes;
    }

    /**
     * @param array $option_attributes
     */
    public function setOptionAttributes(array $option_attributes) {
        $this->option_attributes = $option_attributes;
    }

    /**
     * Set a flag, whether to include the empty option at the beginning or not
     *
     * @param boolean $displayEmptyItem
     *
     * @return Proxy
     */
    public function setDisplayEmptyItem($displayEmptyItem) {
        $this->displayEmptyItem = $displayEmptyItem;

        return $this;
    }

    /**
     * @return boolean
     */
    public function getDisplayEmptyItem() {
        return $this->displayEmptyItem;
    }

    /**
     * Set the object manager
     *
     * @param  ObjectManager $objectManager
     *
     * @return Proxy
     */
    public function setObjectManager(ObjectManager $objectManager) {
        $this->objectManager = $objectManager;

        return $this;
    }

    /**
     * Get the object manager
     *
     * @return ObjectManager
     */
    public function getObjectManager() {
        return $this->objectManager;
    }

    /**
     * Set the FQCN of the target object
     *
     * @param  string $targetClass
     *
     * @return Proxy
     */
    public function setTargetClass($targetClass) {
        $this->targetClass = $targetClass;

        return $this;
    }

    /**
     * Get the target class
     *
     * @return string
     */
    public function getTargetClass() {
        return $this->targetClass;
    }

    /**
     * Set the property to use as the label in the options
     *
     * @param  string $property
     *
     * @return Proxy
     */
    public function setProperty($property) {
        $this->property = $property;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getProperty() {
        return $this->property;
    }

    /**
     * Set the label generator callable that is responsible for generating labels for the items in the collection
     *
     * @param callable $callable A callable used to create a label based off of an Entity
     *
     * @throws InvalidArgumentException
     *
     * @return void
     */
    public function setLabelGenerator($callable) {
        if (!is_callable($callable)) {
            throw new InvalidArgumentException(
            'Property "label_generator" needs to be a callable function or a \Closure'
            );
        }

        $this->labelGenerator = $callable;
    }

    /**
     * Defina o gerador de valores que pode ser chamado para gerar valor para os itens da coleção
     *
     * @param callable $callable Usado para criar um valor com base em uma Entidade
     *
     * @throws InvalidArgumentException
     *
     * @return void
     */
    public function setValueGenerator($callable) {
        if (!is_callable($callable)) {
            throw new InvalidArgumentException(
            'Property "value_generator" needs to be a callable function or a \Closure'
            );
        }

        $this->valueGenerator = $callable;
    }

    /**
     * @return callable|null
     */
    public function getLabelGenerator() {
        return $this->labelGenerator;
    }

    /**
     * @return callable||null
     */
    public function getValueGenerator() {
        return $this->valueGenerator;
    }

    /**
     * @return string|null
     */
    public function getOptgroupIdentifier() {
        return $this->optgroupIdentifier;
    }

    /**
     * @param string $optgroupIdentifier
     */
    public function setOptgroupIdentifier($optgroupIdentifier) {
        $this->optgroupIdentifier = (string) $optgroupIdentifier;
    }

    /**
     * @return string|null
     */
    public function getOptgroupDefault() {
        return $this->optgroupDefault;
    }

    /**
     * @param string $optgroupDefault
     */
    public function setOptgroupDefault($optgroupDefault) {
        $this->optgroupDefault = (string) $optgroupDefault;
    }

    /**
     * Set if the property is a method to use as the label in the options
     *
     * @param  boolean $method
     *
     * @return Proxy
     */
    public function setIsMethod($method) {
        $this->isMethod = (bool) $method;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getIsMethod() {
        return $this->isMethod;
    }

    /** Set the findMethod property to specify the method to use on repository
     *
     * @param array $findMethod
     *
     * @return Proxy
     */
    public function setFindMethod($findMethod) {
        $this->findMethod = $findMethod;

        return $this;
    }

    /**
     * Get findMethod definition
     *
     * @return array
     */
    public function getFindMethod() {
        return $this->findMethod;
    }

    /**
     * @param $targetEntity
     *
     * @return string|null
     */
    protected function generateLabel($targetEntity) {
        if (null === ($labelGenerator = $this->getLabelGenerator())) {
            return null;
        }

        return call_user_func($labelGenerator, $targetEntity);
    }

    /**
     * @param $targetEntity
     * 
     * @return value|null
     */
    protected function generateValue($targetEntity) {
        if (null == ($valueGenerator = $this->getValueGenerator())) {
            return null;
        }

        return call_user_func($valueGenerator, $targetEntity);
    }

    /**
     * @param  $value
     *
     * @return array|mixed|object
     * @throws RuntimeException
     */
    public function getValue($value) {
        if (!($om = $this->getObjectManager())) {
            throw new RuntimeException('No object manager was set');
        }

        if (!($targetClass = $this->getTargetClass())) {
            throw new RuntimeException('No target class was set');
        }

        $metadata = $om->getClassMetadata($targetClass);

        if (is_object($value)) {
            if ($value instanceof Collection) {
                $data = array();

                foreach ($value as $object) {
                    $values = $metadata->getIdentifierValues($object);
                    $data[] = array_shift($values);
                }

                $value = $data;
            } else {
                $metadata = $om->getClassMetadata(get_class($value));
                $identifier = $metadata->getIdentifierFieldNames();

                // TODO: handle composite (multiple) identifiers
                if (count($identifier) > 1) {
                    //$value = $key;
                } else {
                    $value = current($metadata->getIdentifierValues($value));
                }
            }
        }

        return $value;
    }

    /**
     * Load objects
     *
     * @throws RuntimeException
     * @throws Exception\InvalidRepositoryResultException
     * @return void
     */
    protected function loadObjects() {
        if (!empty($this->objects)) {
            return;
        }

        $findMethod = (array) $this->getFindMethod();

        if (!$findMethod) {
            $findMethodName = 'findAll';
            $repository = $this->objectManager->getRepository($this->targetClass);
            $objects = $repository->findAll();
        } else {
            if (!isset($findMethod['name'])) {
                throw new RuntimeException('No method name was set');
            }
            $findMethodName = $findMethod['name'];
            $findMethodParams = isset($findMethod['params']) ? array_change_key_case($findMethod['params']) : array();
            $repository = $this->objectManager->getRepository($this->targetClass);

            if (!method_exists($repository, $findMethodName)) {
                throw new RuntimeException(
                sprintf(
                        'Method "%s" could not be found in repository "%s"', $findMethodName, get_class($repository)
                )
                );
            }

            $r = new ReflectionMethod($repository, $findMethodName);
            $args = array();

            foreach ($r->getParameters() as $param) {
                if (array_key_exists(strtolower($param->getName()), $findMethodParams)) {
                    $args[] = $findMethodParams[strtolower($param->getName())];
                } elseif ($param->isDefaultValueAvailable()) {
                    $args[] = $param->getDefaultValue();
                } elseif (!$param->isOptional()) {
                    throw new RuntimeException(
                    sprintf(
                            'Required parameter "%s" with no default value for method "%s" in repository "%s"'
                            . ' was not provided', $param->getName(), $findMethodName, get_class($repository)
                    )
                    );
                }
            }
            $objects = $r->invokeArgs($repository, $args);
        }

        $this->guardForArrayOrTraversable(
                $objects, sprintf('%s::%s() return value', get_class($repository), $findMethodName), 'DoctrineModule\Form\Element\Exception\InvalidRepositoryResultException'
        );

        $this->objects = $objects;
    }

    /**
     * Load value options
     *
     * @throws RuntimeException
     * @return void
     */
    protected function loadValueOptions() {
        if (!($om = $this->objectManager)) {
            throw new RuntimeException('No object manager was set');
        }

        if (!($targetClass = $this->targetClass)) {
            throw new RuntimeException('No target class was set');
        }

        $metadata = $om->getClassMetadata($targetClass);
        $identifier = $metadata->getIdentifierFieldNames();
        $objects = $this->getObjects();
        $options = array();
        $optionAttributes = array();

        if ($this->displayEmptyItem) {
            $options[''] = $this->getEmptyItemLabel();
        }

        foreach ($objects as $key => $object) {
            if (null !== ($generatedLabel = $this->generateLabel($object))) {
                $label = $generatedLabel;
            } elseif ($property = $this->property) {
                if ($this->isMethod == false && !$metadata->hasField($property)) {
                    throw new RuntimeException(
                    sprintf(
                            'Property "%s" could not be found in object "%s"', $property, $targetClass
                    )
                    );
                }

                $getter = 'get' . ucfirst($property);

                if (!is_callable(array($object, $getter))) {
                    throw new RuntimeException(
                    sprintf('Method "%s::%s" is not callable', $this->targetClass, $getter)
                    );
                }

                $label = $object->{$getter}();
            } else {
                if (!is_callable(array($object, '__toString'))) {
                    throw new RuntimeException(
                    sprintf(
                            '%s must have a "__toString()" method defined if you have not set a property'
                            . ' or method to use.', $targetClass
                    )
                    );
                }

                $label = (string) $object;
            }

            // AQUI ESTA O PONTO CHAVE
            if (!$this->getValueGenerator()) {
                if (count($identifier) > 1) {
                    $value = $key;
                } else {
                    $value = current($metadata->getIdentifierValues($object));
                }
            } else {

                if (null !== ($generatedValue = $this->generateValue($object))) {
                    $value = $generatedValue;
                }
            }

            foreach ($this->getOptionAttributes() as $optionKey => $optionValue) {
                if (is_string($optionValue)) {
                    $optionAttributes[$optionKey] = $optionValue;

                    continue;
                }

                if (is_callable($optionValue)) {
                    $callableValue = call_user_func($optionValue, $object);
                    $optionAttributes[$optionKey] = (string) $callableValue;

                    continue;
                }

                throw new RuntimeException(
                sprintf(
                        'Parameter "option_attributes" expects an array of key => value where value is of type'
                        . '"string" or "callable". Value of type "%s" found.', gettype($optionValue)
                )
                );
            }

            // If no optgroup_identifier has been configured, apply default handling and continue
            if (is_null($this->getOptgroupIdentifier())) {
                $options[] = array('label' => $label, 'value' => $value, 'attributes' => $optionAttributes);

                continue;
            }

            // optgroup_identifier found, handle grouping
            $optgroupGetter = 'get' . ucfirst($this->getOptgroupIdentifier());

            if (!is_callable(array($object, $optgroupGetter))) {
                throw new RuntimeException(
                sprintf('Method "%s::%s" is not callable', $this->targetClass, $optgroupGetter)
                );
            }

            $optgroup = $object->{$optgroupGetter}();

            // optgroup_identifier contains a valid group-name. Handle default grouping.
            if (false === is_null($optgroup) && trim($optgroup) !== '') {
                $options[$optgroup]['label'] = $optgroup;
                $options[$optgroup]['options'][] = array(
                    'label' => $label,
                    'value' => $value,
                    'attributes' => $optionAttributes
                );

                continue;
            }

            $optgroupDefault = $this->getOptgroupDefault();

            // No optgroup_default has been provided. Line up without a group
            if (is_null($optgroupDefault)) {
                $options[] = array('label' => $label, 'value' => $value, 'attributes' => $optionAttributes);

                continue;
            }

            // Line up entry with optgroup_default
            $options[$optgroupDefault]['label'] = $optgroupDefault;
            $options[$optgroupDefault]['options'][] = array(
                'label' => $label,
                'value' => $value,
                'attributes' => $optionAttributes
            );
        }

        $this->valueOptions = $options;
    }

}


Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *