Value Object in Symfony - An update - Blog | createIT
Get a free advice now!

    Pick the topic
    Developer OutsourcingWeb developingApp developingDigital MarketingeCommerce systemseEntertainment systems

    Thank you for your message. It has been sent.

    Value Object in Symfony – an update

    October 12, 2021
    Last update: February 8, 2023
    3 min read
    8
    0
    0
    Value Object in Symfony – an update

    A problem with null

    This entry is a follow-up to an earlier one about Value Object – “All about value object in Symfony”.

    In the previous article on the use of value object in Symfony I showed how to use VOs along with the Embeddable mechanism in Doctrine. However, this method has a one, clear problem. The mapping value as Embedd cannot assume the null value. This time, I will try to show two different ways that can be used when we need our VO to be able to assume the null value as an entity field.

    Setter/getter mapping

    The first way is to resign from specialized mapping mechanisms with Doctrine. Instead, we personally define the columns required to store our VO, and we perform the appropriate transformation using the right methods:

    /** 
     * @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); 
       } 
     }

    As you can see, $priceValue and $priceCurrency are defined, they will serve to store the value and currency from VO Money. The constructor and setter assume the Money object as an argument, and then they extract the necessary information from it and save it in the $priceValue and $priceCurrency fields. The getter constructs the Money object on the basis of the information from the database and returns it.

    Mapping using Custom Mapping Type

    The second method uses the Custom Mapping Type from Doctrine. It allows to define our own mapping types for Doctrtine. They work as standard column types so they can assume the “nullable” parameter. The downside is that we must map our object to a single column in the database.

    First, we define our mapping:

    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; 
         }
     }

    getSQLDeclaration indicates the type of column we will use to store our value in the database.

    convertToPHPValue transforms the value from the database into our VO. In this case, we store information as a string type , where the value and currency are separated with the space sign, so we divide it into a table using explode and transfer the appropriate elements to the Money constructor.

    convertToDatabaseValue is used to transform our VO to a format that we will be able to store in the database.

    The last function, getName, defines the name of our mapping, which we will be using in Doctrine annotations.

    Next, we register our mapping in 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

    We can now use our mapping to determine the type of column in the entity:

    /** 
     * @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; 
        } 
      }

    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

    About the author:

    Adam Zając is a Backend Developer with many years of experience, but he is also skilled in Frontend. Among his many interests, there are architecture, clean code and broadly understood quality in programming.

    References:

    Polish version: http://adamzajac.info/2021/09/30/value-object-w-symfony-update/

    Support – Tips and Tricks
    All tips in one place, and the database keeps growing. Stay up to date and optimize your work!

    Contact us