Value object w Symfony – update

Problem z null

Ten wpis jest kontynuacją poprzedniego artykułu o Value Object – Value object w Symfony. Z czym to się je.

W poprzednim wpisie poświęconym używaniu obiektów wartości w Symfony pokazałem jak korzystać z nich z wykorzystaniem mechanizmu Embeddable w Doctrine. Ten sposób ma jednak jeden wyraźny problem. Wartość mapowana jako Embedd nie może przyjmować wartości null. Tym razem postaram się pokazać dwa inne sposoby, które można wykorzystać gdy potrzebujemy, aby nasz VO mógł być przyjmować wartość null jako pole encji.

Mapowanie w getterach/setterach

Pierwszy sposób polega na zrezygnowaniu ze specjalizowanych mechanizmów mapowania z Doctrine. Zamiast tego samodzielnie definiujemy kolumny potrzebne do przechowywania naszego VO i dokonujemy odpowiedniego przekształcenia za pomocą odpowiednich metod:

/**
 * @ORM\Entity(repositoryClass=Product1Repository::class)
 */
class Product1
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\Column(type="integer")
     */
    private $priceValue;

    /**
     * @ORM\Column(type="string")
     */
    private $priceCurrency;

    public function __construct(string $name, Money $price)
    {
        $this->name = $name;
        $this->priceValue = $price->value();
        $this->priceCurrency = $price->currency();
    }

    ...


    public function setPrice(Money $price)
    {
        $this->priceValue = $price->value();
        $this->priceCurrency = $price->currency();
    }

    public function getPrice()
    {
        return new Money($this->priceValue, $this->priceCurrency);
    }

}

Jak widać mamy zdefiniowane pola $priceValue oraz $priceCurrency, które posłużą przechowywaniu wartości oraz waluty z VO Money. Konstruktor oraz setter przyjmują jako argument obiekt Money, a następnie wyciągają z niego potrzebne infomacje i zapisują w polach $priceValue oraz $priceCurrency. Getter natomiast konstruuje obiekt Money na podstawie informacji z bazy i go zwraca.

Mapowanie za pomocą Cutom Mapping Type

Drugi sposób korzysta z mechanizmu Custom Mapping Type z Doctrine, Pozwala on zdefiniować własne typy mapowań dla Doctrine. Działają one jak standardowe typy kolumn, więc mogą przyjmować parametr 'nullable’. Mają natomiast tę wadę, że musimy zmapować nasz obiekt na pojedynczą kolumnę w bazie danych.

Po pierwsze definiujemy nasz mapowanie:

namespace App\Type;

use App\ValueObject\Money;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;

class MoneyType extends Type
{
    const MONEY = 'money';

    public function getSQLDeclaration(array $column, AbstractPlatform $platform)
    {
        return 'varchar(100)';
    }

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        [$value, $currency] = explode(' ', $value);
        return new Money($value, $currency);
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        if ($value === null) {
            return null;
        }
        return $value->value().' '.$value->currency();
    }

    public function getName()
    {
        return self::MONEY;
    }
}

Funkcja getSQLDeclaration wskazuje za pomocą jakiego typu kolumny będziemy przechowywać naszą wartość w bazie.

Funkcja convertToPHPValue przekształca wartość z bazy na nasz VO. W tym wypadku przechowujemy informacje w postaci typu string, gdzie wartość i waluta są oddzielone znakiem spacji, więc rozbijamy ją na tablicę za pomocą explode i przekazujemy odpowiednie elementy do konstruktora Money.

Funkcja convertToDatabaseValue służy do przekształcenia naszego VO do formatu, który będziemy w stanie przechowywać w bazie.

Ostatnia funkcja getName określa nazwę naszego mapowania, którego będziemy używali w adnotacjach Doctrine.

Następnie rejestrujemy nasze mapowanie w Symfony:

# doctrine.yaml

doctrine:
    dbal:
        url: '%env(resolve:DATABASE_URL)%'

        # IMPORTANT: You MUST configure your server version,
        # either here or in the DATABASE_URL env var (see .env file)
        #server_version: '13'

        types:
          money: App\Type\MoneyType

        mapping_types:
          money: string

Teraz możemy użyć naszego mapowania do określenia typu kolumny w encji:

/**
 * @ORM\Entity(repositoryClass=ProductCustomMappingRepository::class)
 */
class ProductCustomMapping
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\Column(type="money", nullable=true)
     */
    private $price;

    public function __construct(string $name, ?Money $price)
    {
        $this->name = $name;
        $this->price = $price;
    }

    ...

    /**
     * @return null|Money
     */
    public function getPrice(): ?Money
    {
        return $this->price;
    }

    /**
     * @param  ?Money  $price
     */
    public function setPrice(?Money $price): void
    {
        $this->price = $price;
    }

}

Linki

https://www.doctrine-project.org/projects/doctrine-orm/en/2.9/cookbook/custom-mapping-types.html

https://www.doctrine-project.org/projects/doctrine-orm/en/2.9/cookbook/advanced-field-value-conversion-using-custom-mapping-types.html

Subscribe
Powiadom o
guest
1 Komentarz
najstarszy
najnowszy oceniany
Inline Feedbacks
View all comments
1
0
Would love your thoughts, please comment.x