Search code examples
doctrine-ormzend-framework2zend-framework3laminas

Doctrine 2 define property type without column creation


I have an Address entity which is bind to a ZF / Laminas Form.

Some properties are defined. Such as street, country, ....

/**
 * @var string
 *
 * @ORM\Column(type="string", length=2)
 */
protected $country;

/**
 * @var float
 *
 * @ORM\Column(type="float", nullable=true)
 */
protected $longitude;

/**
 * @var float
 *
 * @ORM\Column(type="float", nullable=true)
 */
protected $latitude;

And a non stored property to force a geolocation through an EventListener

/**
 * @var bool
 */
protected $geolocation = true;

With accessors

/**
 * @param bool $geolocation
 *
 * @return self
 */
public function setGeolocation(bool $geolocation): self
{
    $this->geolocation = $geolocation;

    return $this;
}

Problem is during hydration through accessors. Value of "geolocation" comes out from form in string type '1' or '0' (checkbox)

Hydration is proceed by the DoctrineObject hydrator but property is not managed by ORM. So with the strict type mode enabled, an exception is thrown because of setGeolocation() parameter type.

It should works with an @ORM\Column(type="boolean") but I don't want to store this value.

How to define an entity property without create a column in db ?


Solution

  • I think you don't have a problem with Doctrine or hydration, you've a problem with type-conversion.

    When that value comes from a Laminas\Form\Form object, then you should use the InputFilter to convert the value to bool with a filter. The ObjectHydrator from should then take the value from the input filter.

    Let's take a look at my example (for the sake of simplicity I don't use Doctrine here):

    <?php
    
    declare(strict_types=1);
    
    class MyEntity
    {
    
        private bool $mycheckbox = false;
    
        public function getMycheckbox(): bool
        {
            return $this->mycheckbox;
        }
    
        public function setMycheckbox(bool $mycheckbox): void
        {
            $this->mycheckbox = $mycheckbox;
        }
    
    }
    
    use Laminas\Form\Form;
    use Laminas\InputFilter\InputFilterProviderInterface;
    use Laminas\Form\Element;
    use Laminas\Filter;
    use Laminas\Hydrator\ClassMethods as Hydrator;
    
    class MyForm extends Form implements InputFilterProviderInterface
    {
    
        public function init(): void
        {
            $this->add([
                'name'    => 'mycheckbox',
                'type'    => Element\Checkbox::class,
                'options' => [],
            ]);
    
            $this->setObject(new MyEntity());
            $this->setHydrator(new Hydrator());
        }
    
        public function getInputFilterSpecification(): array
        {
            return [
                'mycheckbox' => [
                    'filters' => [
                        // this filter is the important part
                        [
                            'name'    => Filter\Boolean::class,
                            'options' => [],
                        ]
                    ],
                ],
            ];
        }
    
    }
    
    $form = new MyForm();
    $form->init();
    
    $form->setData(['mycheckbox' => '1']);
    var_dump($form->isValid());
    var_dump($form->getInputFilter()->getValues());
    
    
    var_dump($form->getObject());
    

    The output here is:

    bool(true)
    array(1) {
       'mycheckbox' =>
       bool(true)
    }
    
    class MyEntity#9 (1) {
       private $mycheckbox =>
       bool(true)
    }