Welcome to Abdul Malik Ikhsan's Blog

Zend Framework 2 : Using DoctrineModule\Form\Element\ObjectSelect and custom repository

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

zf2-zendframework2When we are using Doctrine2 in Zend Framework 2 project, we can use existing module named DoctrineModule that can be used to easist our job. Now I will explain about how to use DoctrineModule\Form\Element\ObjectSelect in our Form to load data into select element with custom query instead of default one.
For example, we have data like this :
countries-continent
Now, we need to build a form element that collect continent data, that’s means, we need to ‘group by continent’ for query-ing the table data like this :
continent-grab
To make it work, we need to create custom repository. Ok, let’s start.
1. Prepare the Entity

//module/Tutorial/src/Tutorial/Entity/Countries.php
namespace Tutorial\Entity;

use Doctrine\ORM\Mapping as ORM;

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

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

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



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

    /**
     * Set country
     *
     * @param string $country
     * @return Countries
     */
    public function setCountry($country)
    {
        $this->country = $country;

        return $this;
    }

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

    /**
     * Set continent
     *
     * @param string $continent
     * @return Countries
     */
    public function setContinent($continent)
    {
        $this->continent = $continent;

        return $this;
    }

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

Above entity is pretty generic, but we add new “repositoryClass” attribute in the @ORM\Entity annotation to linked with our custom repository.
2. Create a custom repository

//module/Tutorial/src/Tutorial/Repository/CountriesRepository.php
namespace Tutorial\Repository;

use Doctrine\ORM\EntityRepository;

class CountriesRepository extends EntityRepository
{
    public function getContinent()
    {
        $querybuilder = $this->createQueryBuilder('c');
        return $querybuilder->select('c')
                    ->groupBy('c.continent')
                    ->orderBy('c.id', 'ASC')
                    ->getQuery()->getResult();
    }
}

The getContinent grab the countries data group by continent.
3. Create form

//module/Tutorial/src/Tutorial/Form/CountriesForm.php
namespace Tutorial\Form;

use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;
use Doctrine\ORM\EntityManager;

class CountriesForm extends Form
    implements InputFilterProviderInterface
{
    protected $entityManager;
    
    public function __construct(EntityManager $entityManager)
    {
        parent::__construct();

        $this->entityManager = $entityManager;       
    }
    
    public function init()
    {
        $this->add(array(
           'name' => 'continent',
           'type' => 'DoctrineModule\Form\Element\ObjectSelect',
           'options' => array(
                'object_manager'     => $this->entityManager,
                'target_class'       => 'Tutorial\Entity\Countries',
                'property' => 'continent',
                'is_method' => true,
                'find_method'        => array(
                    'name'   => 'getContinent',
                ),
            ), 
        ));
    }
    
    public function getInputFilterSpecification()
    {
        return array(); // filter and validation here
    }
}

We need to inject form with Doctrine\ORM\EntityManager, and call the getContinent method that we already define at our CountriesRepository. So the Form need to be created via factory :

//module/Tutorial/src/Tutorial/Factory/Form/CountriesFormFactory.php
namespace Tutorial\Factory\Form;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Tutorial\Form\CountriesForm;

class CountriesFormFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $services         = $serviceLocator->getServiceLocator();
        $entityManager    = $services->get('Doctrine\ORM\EntityManager');
        
        $form = new CountriesForm($entityManager);
        return $form;
    }
}

4. Create controller

//module/Tutorial/src/Tutorial/Controller/CountriesController.php
namespace Tutorial\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\Form\FormInterface;
use Zend\View\Model\ViewModel;

class CountriesController extends AbstractActionController
{
    protected $countriesForm;
    
    public function __construct(FormInterface $countriesForm)
    {
        $this->countriesForm = $countriesForm;
    }
    
    public function indexAction()
    {
        return new ViewModel(array(
            'form' => $this->countriesForm, 
        ));
    }
}

We need to inject controller with the CountriesForm, so the Controller need to be created via factory :

//module/Tutorial/src/Tutorial/Factory/Controller/CountriesControllerFactory.php
namespace Tutorial\Factory\Controller;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Tutorial\Controller\CountriesController;

class CountriesControllerFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $services       = $serviceLocator->getServiceLocator();
        $countryForm    = $services->get('FormElementManager')->get('Tutorial\Form\CountriesForm');
        $controller = new CountriesController($countryForm);

        return $controller;
    }
}

5. Register services

//module/Tutorial/config/module.config.php
return array(
    'doctrine' => array(
        'driver' => array(
            'Tutorial_Entities' => array(
                'class' =>'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
                'cache' => 'array',
                'paths' => array(__DIR__ . '/../src/Tutorial/Entity')
            ),
            'orm_default' => array(
                'drivers' => array(
                    'Tutorial\Entity' => 'Tutorial_Entities'
                ),
            ),
        ),
    ),
    
    'controllers' => array(
        'factories' => array(
            'Tutorial\Controller\Countries' => 'Tutorial\Factory\Controller\CountriesControllerFactory',  
        ),
    ),
    
    'form_elements' => array(
        'factories' => array(
            'Tutorial\Form\CountriesForm' => 'Tutorial\Factory\Form\CountriesFormFactory',  
        ),
    ),
    
    'router' => array(
        'routes' => array(
            'countries' => array(
                'type'    => 'segment',
                'options' => array(
                    'route'    => '/countries[/:action]',
                    'constraints' => array(
                        'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
                    ),
                    'defaults' => array(
                        'controller' => 'Tutorial\Controller\Countries',
                        'action'     => 'index',
                    ),
                ),
            ),
        ),
    ),
    
    'view_manager' => array(
        'template_path_stack' => array(
            'tutorial' => __DIR__ . '/../view',
        ),
    ),
    
);

6. Last but not least, build a view :

// module/Tutorial/view/tutorial/countries/index.phtml
$form = $this->form;
$form->prepare();

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

Ok, done ;)

26 Responses

Subscribe to comments with RSS.

  1. seyferx said, on May 22, 2014 at 12:15 pm

    I’m always made it via Form/Element/Select. Just inject Doctrine manager inside, make request and fill options with result.
    This way, what you showed like some binding. Thank.

  2. Webdevilopers (@webdevilopers) said, on May 22, 2014 at 2:34 pm

    Great work as always, @samsonasik!

    I didn’t take an In-Depth look at the hydration but regarding the label_generator option (https://github.com/doctrine/DoctrineModule/blob/master/docs/form-element.md#example-2–modifying-the-label) I wonder if Doctrine will always return an object hydrated result.

    Is array hydration possible? Because when you use a lot of this feature you may get a lot of overhead from the objects. Do you have any info about this?

  3. Webdevilopers (@webdevilopers) said, on May 22, 2014 at 7:56 pm

    BTW: I recognized that with the current version of Doctrine setting the object_manager on the formElement is obsolete when the Fieldset implements the ObjectManagerAwareInterface?

    • samsonasik said, on June 1, 2014 at 6:15 pm

      if you’re working with many forms, doing initializers is ok I think, but for one form, create one factory for it with contructor injection is not bad :)

  4. Igor Carvalho de Paula said, on June 11, 2014 at 12:29 am

    Great, and how i do with for annotations?

    • Jan Malte said, on September 1, 2014 at 3:25 pm

      I’m currently looking for a way to use doctrines ObjectSelect within a form build with annotations. This would absolutely increase the speed of rapid development.

      Does someone has a hint how to achieve it?

  5. Jan Malte said, on September 1, 2014 at 4:41 pm

    Using “DoctrineORMModule\Form\Annotation\AnnotationBuilder” instead of the “Zend\Form\Annotaion\AnnotationBuilder” will automatically resolve dependencies into ObjectSelect form elements.

  6. Tobi said, on January 7, 2015 at 9:25 am

    I am trying to follow your documentation but I am getting the error “Method “customFindBy” could not be found in repository “Doctrine\ORM\EntityRepository””. I am not populating the form via factories – does that have an effect? I don’t understand how the form knows how to look for your getContinent method.

    • samsonasik said, on January 7, 2015 at 12:24 pm

      the form injected with EntityManager which uses the entity that have repository, in your repository, you need to define your own “customFindBy” that return query result.

  7. Tobi said, on January 7, 2015 at 11:05 pm

    I do have “customFindBy” in my custom repository but it seems like Zend can’t find it. I have @ORM\Entity(repositoryClass=”Application\Repository\AssessmentRepository”) in my entity. Thank you for the quick reply, I really appreciate it!

    • samsonasik said, on January 8, 2015 at 1:21 am

      it should work as far as everything configured correctly. just need to be patient to check everything ;)

      • Tobi said, on January 8, 2015 at 2:04 am

        Okay, thanks again =D

  8. Akbar Adeeb said, on February 28, 2015 at 4:19 pm

    I want sql query of left join using doctrin in zf2. But don’t know how to achive this. I have lean about custom repositery in doctrin2. Can you please suggest me the best way to achive this in zf2

  9. George Istomin said, on March 4, 2015 at 7:25 am

    It makes sense to add the validation of the selected country in the class CountriesForm:

    public function getInputFilterSpecification()
    {
    return array(
    ‘continent’ => array(
    ‘validators’ => array(
    array(
    ‘name’ => ‘DoctrineModule\Validator\ObjectExists’,
    ‘options’ => array(
    ‘object_repository’ => $this->entityManager->getRepository(‘Tutorial\Entity\Countries’),
    ‘fields’ => ‘id’
    )
    )
    )
    )
    );
    }

    • samsonasik said, on March 4, 2015 at 9:09 pm

      yes, this is just a demo for custom repository in objectselect

  10. Akbar Adeeb said, on March 5, 2015 at 5:21 pm

    Hi i want to select few fields from table. The query ren successfull but it return result in array. i also try hydrate but it does not work my code is foulling

    $qb = $this->_em->getRepository($this->getEntityName())
    ->createQueryBuilder(‘p’);

    $qb->select(‘p.name,p.price,c.name as category’)
    ->leftJoin(‘\Application\Entity\Category’, ‘c’, ‘WITH’, ‘c.id = p.cid’);
    return $qb->getQuery()->getResult();

  11. Pat said, on July 8, 2015 at 10:26 pm

    Hi, how can I show the Continent and Country name in the option, instead of Continent only? For Example: China, Asia. Thx!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 319 other followers

%d bloggers like this: