user3549136
user3549136

Reputation: 57

Doctrine Data Type (use Factory)

i want to implement a new doctrine data type (http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html).

I have implemented an Country Service which loads country data via adapter from any library. Know i have the following implementation:

<?php
     interface CountryInterface;

     interface Address
     {
         public function setCountry(CountryInterface $country);
         public function getCountry() : CountryInterface;
     }
?>

So, what I want to do is - make a CountryType which converts the Country Object to an specific string value (used field will be set via OptionClass, ex.: Alpha2, Alpha3, IsoNumber).

My problem is, doctrine only allows data types mapping via classname, so I can't implement an factory to load all needed dependencies.

I hope this is understandable.

regards

Upvotes: 1

Views: 931

Answers (1)

Wilt
Wilt

Reputation: 44383

First you will need to register your custom DBAL type for country extending the Doctrine\DBAL\Types\Type class:

<?php

namespace Application\DBAL\Types;

use Application\Resource\Country;
use Application\Service\CountryService;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\Type;
use InvalidArgumentException;

class CountryType extends Type
{
    const NAME = 'country';

    /**
     * Country service
     */
    protected $countryService;

    /**
     * @return string
     */
    public function getName()
    {
        return self::NAME;
    }

    /**
     * {@inheritdoc}
     */
    public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
    {
        return $platform->getDoctrineTypeMapping('text');
    }

    /**
     * {@inheritdoc}
     */
    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        if($value === null){
            return null;
        }

        if ($value instanceof Country) {
            return (string) $value;
        }

        throw ConversionException::conversionFailed($value, self::NAME);
    }

    /**
     * {@inheritdoc}
     */
    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        if($value === null){
            return null;
        }

        $country = $this->countryService->getCountry($value);
        if( ! $country instanceof Country ){
            throw ConversionException::conversionFailed($value, self::NAME);
        }

        return $country;
    }

    /**
     * Set country service
     *
     * @var CountryService $service
     */
    public function setCountryService ($service){
        $this->countryService = $service;
    }
}

This type needs to implement four methods getName, getSQLDeclaration, convertToDatabaseValue and convertToPHPValue.

  • Thre first one returns the name for the type
  • The second one is for the type declaration in the (SQL) database (I used text in the example, but you can also use integer or any other valid doctrine database type).
  • The third method converts your country object to a database value (so in this case a text value).
  • The last method does the opposite; it converts the text value from the database. In this case I just instantiate the Country class and pass the database value to the constructor. You need to add your custom logic inside your class constructor.

In my example I assume that null values are also allowed.

A simple version of your Country class could look like this:

<?php

namespace Application\Resource;

class Country{

    protected $value;

    /**
     * Magic stringify to cast country object to a string
     */
    public function __toString(){
        return $value;
    }

    /**
     * Constructor method
     */
    public function construct($value){
        $this->value = $value
        // set other properties...
    }

    // setters and getters...
}

It is up to you whether value should be alpha2/alpha3/country_name or whatever you want visible in the database. You should somehow also populate the other country with the other properties in the constructor method. I leave this part up to you.

Now you need to register your custom country type so doctrine will use it:

'doctrine' => array(
    //...
    'configuration' => array(
        'orm_default' => array(
            Application\DBAL\Types\CountryType::NAME =>  Application\DBAL\Types\CountryType::class,
        )
    )
)

And you can set your service on bootstrap in your application Module.php file:

/**
 * @param EventInterface|MvcEvent $event
 * @return void
 */
public function onBootstrap(EventInterface $event)
{
    $application    = $event->getApplication();
    $eventManager   = $application->getEventManager();

    $eventManager->attach(MvcEvent::EVENT_BOOTSTRAP, array($this, 'initializeCountryType');
}

/**
 * @param MvcEvent $event
 */
public function initializeCountryType(MvcEvent $event)
{
    $application    = $event->getApplication();
    $serviceManager = $application->getServiceManager();

    //get your country service from service manager
    $countryService = $serviceManager->getCountryService();

    $countryType = \Doctrine\DBAL\Types\Type::getType('country');
    $countryType->setCountryService($countryService);
}

Now you can use your country type in any entity definition as follows:

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

Read more on how to map custom DBAL types in the Doctrine2 documentation chapter Custom Mapping Types

Upvotes: 1

Related Questions