Doctrine 2 - ORM
  1. Doctrine 2 - ORM
  2. DDC-93

It would be nice if we could have support for ValueObjects

    Details

    • Type: New Feature New Feature
    • Status: Resolved
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: None
    • Fix Version/s: 2.5
    • Component/s: ORM
    • Security Level: All
    • Labels:
      None

      Description

      class User {
      	/**
      	 * @Column(type="string")
      	 */
      	private $address;
      	
      	/**
      	 * @Column(type="string")
      	 */
      	private $city;
      	
      	/**
      	 * @Column(type="string")
      	 */
      	private $state;
      }
      

      We could have:

      class User {
      	/**
      	 * @Component(class="Address")
      	 */
      	 private $address;
      }
      

      It would my life a lot easier....


      Notes for implementation

      Value objects can come in two forms:

      a) as embedded value objects
      b) as collections of value objects

      An implementation should concentrate on a) first. The following things all concentrate on a).

      DQL Support

      Conditions:

      1. "select f from Foo f where f.embedded.value = ?1" (setParameter(1, $scalarValue))
      2. "select f from Foo f where f.embedded = ?1" (setParameter(1, $embeddedValueObject))

      At least Nr.1 must be possible in a first implementation.

      Selecting:

      1. "select f from Foo f" must explode embedded value objects in the SQL SELECT clause.
      2. "select f.embedded from Foo f" must explode the columns of the embedded object in the SQL SELECT clause.

      At least Nr. 1 must be possible in a first implementation, obviously.

      Components affected (among others): Parser, SqlWalker, ...

      Persisters

      The persisters need to take embedded value objects into account when persisting as well as loading entities.

      Components affected (among others): Persisters, UnitOfWork, ...

      Metadata

      ClassMetadataInfo needs to be extended with a field (probably an array) that contains the mappings of embedded values.
      New annotations as well as XML/YAML elements are needed.

      Components affected (among others): ClassMetadataInfo, AnnotationDriver, YamlDriver, XmlDriver, doctrine-mapping.xsd, ...

      Change Tracking

      If value objects are supposed to be immutable this is easy and might require no or few changes. If, however, we want to track changes in mutable value objects it might get more complicated.

      Components affected (among others): UnitOfWork, ...

        Issue Links

          Activity

          Hide
          Benjamin Eberlei added a comment -

          formated snippets nicely

          Show
          Benjamin Eberlei added a comment - formated snippets nicely
          Hide
          Andrea Turso added a comment -

          I need this feature too.

          But I would suggest using the same annotation used by JPA

          @Embeddable

          +1

          Show
          Andrea Turso added a comment - I need this feature too. But I would suggest using the same annotation used by JPA @Embeddable +1
          Hide
          Alan Gabriel Bem added a comment -

          You should also take into consideration different storage strategies of ValueObjects.

          Martin Fowler points out - in „PoEAA" - two approaches: Embedded Value (which is the one presented above) and Serialized LOB .
          Both have their pros and cons, that's why Doctrine2 should give developers choice of selecting the fittest solution.

          Show
          Alan Gabriel Bem added a comment - You should also take into consideration different storage strategies of ValueObjects. Martin Fowler points out - in „PoEAA" - two approaches: Embedded Value (which is the one presented above) and Serialized LOB . Both have their pros and cons, that's why Doctrine2 should give developers choice of selecting the fittest solution.
          Hide
          Avi Block added a comment -

          Of course technically we can similate a serialized LOB with a new Doctrine 2 type.

          Show
          Avi Block added a comment - Of course technically we can similate a serialized LOB with a new Doctrine 2 type.
          Hide
          Alan Gabriel Bem added a comment -

          I don't like that idea - Its so not generic.

          VO as a pattern is important building block of domain model, which clearly indicates that VO as a feature of Doctrine2 should be tailor-made.


          To anyone of dev-team reading this issue: without VOs Doctrine is not yet DDD-ready, please hurry

          Show
          Alan Gabriel Bem added a comment - I don't like that idea - Its so not generic. VO as a pattern is important building block of domain model , which clearly indicates that VO as a feature of Doctrine2 should be tailor-made. To anyone of dev-team reading this issue: without VOs Doctrine is not yet DDD-ready, please hurry
          Hide
          Roman S. Borschel added a comment -

          Serialized LOB is not very useful IMHO and has lots of problems (many mentioned in PoEEA already).

          @Alan: I appreciate your nice reminder and I'm sure you mean it in a friendly way, but please keep in mind that noone is paid to work on this project. It all happens in free/spare time and the current state of the project already consumed at least 1 1/2 years spending many hours weekly on this project from me alone. Not to speak of the others.

          Thus, there is no point in demanding something or telling us to hurry. The best way to get a feature in is to provide a (good) patch that we find worth including.

          I started to add notes to this issue to collect all the things that need to be done for this feature.

          In the meantime, its not too hard/ugly to get a half-way decent embedded value yourself:

          /** @Entity @HasLifecycleCallbacks */
          class Foo {
              // annotations not shown
              private $id;
              private $embedded;
              private $value1; // never reveal to public
              private $value2; // never reveal to public
              private $value3; // never reveal to public
          
             public function getEmbedded() {
                 return $this->embedded;
             }
          
             public function setEmbedded($embedded) {
                 $this->embedded = $embedded;
             }
             
             /** @PrePersist @PreUpdate */
             function _destructEmbedded() {
                 // destruct $embedded into $value1, $value2, $value3
             }
          
             /** @PostLoad */
             function _constructEmbedded() {
                // construct $embedded from $value1, $value2, $value3 
             }
          }
          

          Several variations of this are possible, also with an external event listener instead of callbacks but in that case you might need to use reflection to get at the values.

          Show
          Roman S. Borschel added a comment - Serialized LOB is not very useful IMHO and has lots of problems (many mentioned in PoEEA already). @Alan: I appreciate your nice reminder and I'm sure you mean it in a friendly way, but please keep in mind that noone is paid to work on this project. It all happens in free/spare time and the current state of the project already consumed at least 1 1/2 years spending many hours weekly on this project from me alone. Not to speak of the others. Thus, there is no point in demanding something or telling us to hurry. The best way to get a feature in is to provide a ( good ) patch that we find worth including. I started to add notes to this issue to collect all the things that need to be done for this feature. In the meantime, its not too hard/ugly to get a half-way decent embedded value yourself: /** @Entity @HasLifecycleCallbacks */ class Foo { // annotations not shown private $id; private $embedded; private $value1; // never reveal to public private $value2; // never reveal to public private $value3; // never reveal to public public function getEmbedded() { return $ this ->embedded; } public function setEmbedded($embedded) { $ this ->embedded = $embedded; } /** @PrePersist @PreUpdate */ function _destructEmbedded() { // destruct $embedded into $value1, $value2, $value3 } /** @PostLoad */ function _constructEmbedded() { // construct $embedded from $value1, $value2, $value3 } } Several variations of this are possible, also with an external event listener instead of callbacks but in that case you might need to use reflection to get at the values.
          Hide
          Alan Gabriel Bem added a comment -

          I want to share my thoughts on possible VOs collections implementations.

          1. As it was mentioned earlier serialized (C)LOB is one solution. Implementation of storing/retrieving object graphs alone is quite simple, but it's complex in terms of SELECTs with conditions.
          Composing SQL condition would result in some nasty constructions e.g. vo_collection_column LIKE '%foo%bar%' which output format would depend on serialization target (CSV, XML, YAML, PHP serialized objects etc.). Also in most cases it would be impossible to obtain eligible result.

          I'm not taking Regexp or XPath operators into consideration as only few RDBMS support them.

          2. The second solution is to break VOs graph into separate related table... or tables if we consider that VO can contain another VO(s). It's not so fast as serialized LOB but more flexible and it utilize power of RDMS,
          But there is one catch: Doctrine2 must preserve nature of VO. To make it happen during Entities persisting - if any change in dependant VOs graph has been made - all associated VOs rows in database should be deleted and the new/changed VOs graph should be inserted in their place.
          I know it could be inefficient while dealing with large object graphs, yet faster than comparing VOs one-by-one.

          In conclusion:
          serialized LOB is extremely fast in CRUD-like operations on aggregates, however very search unfriendly.
          Separate ValueObjects tables are better where serialized LOB lacks, but slower in exploitation.

          I can't tell which approach is superior, because each of them is valid under different circumstances.

          Hope this helps.

          @Alan: I appreciate your nice reminder and I'm sure you mean it in a friendly way [...]

          Of course I do.

          Show
          Alan Gabriel Bem added a comment - I want to share my thoughts on possible VOs collections implementations. 1. As it was mentioned earlier serialized (C)LOB is one solution. Implementation of storing/retrieving object graphs alone is quite simple, but it's complex in terms of SELECTs with conditions. Composing SQL condition would result in some nasty constructions e.g. vo_collection_column LIKE '%foo%bar%' which output format would depend on serialization target (CSV, XML, YAML, PHP serialized objects etc.). Also in most cases it would be impossible to obtain eligible result. I'm not taking Regexp or XPath operators into consideration as only few RDBMS support them. 2. The second solution is to break VOs graph into separate related table... or tables if we consider that VO can contain another VO(s) . It's not so fast as serialized LOB but more flexible and it utilize power of RDMS, But there is one catch: Doctrine2 must preserve nature of VO. To make it happen during Entities persisting - if any change in dependant VOs graph has been made - all associated VOs rows in database should be deleted and the new/changed VOs graph should be inserted in their place. I know it could be inefficient while dealing with large object graphs, yet faster than comparing VOs one-by-one. In conclusion: serialized LOB is extremely fast in CRUD-like operations on aggregates, however very search unfriendly. Separate ValueObjects tables are better where serialized LOB lacks, but slower in exploitation. I can't tell which approach is superior, because each of them is valid under different circumstances. Hope this helps. @Alan: I appreciate your nice reminder and I'm sure you mean it in a friendly way [...] Of course I do.
          Hide
          Benjamin Eberlei added a comment -

          It would be easy to implement value objects in userland using the XML capabilities of many RDBMS:

          1. Implement an Xpath function on the Dql Parser
          2. Implement a User-Defined Type for each value object that handles the translation from and to XML.

          The second point can be heavily optimized when value objects are immutable with an own identiy map of value types inside the Type flyweight instance.

          Show
          Benjamin Eberlei added a comment - It would be easy to implement value objects in userland using the XML capabilities of many RDBMS: 1. Implement an Xpath function on the Dql Parser 2. Implement a User-Defined Type for each value object that handles the translation from and to XML. The second point can be heavily optimized when value objects are immutable with an own identiy map of value types inside the Type flyweight instance.
          Hide
          Avi Block added a comment -

          I more or less suggested something similar above.

          Show
          Avi Block added a comment - I more or less suggested something similar above.
          Hide
          Benjamin Eberlei added a comment -

          ah, my bad - i must have overseen this

          Show
          Benjamin Eberlei added a comment - ah, my bad - i must have overseen this
          Hide
          John Kleijn added a comment -

          +1

          This would be awesome.

          Show
          John Kleijn added a comment - +1 This would be awesome.
          Hide
          Matthias Pigulla added a comment - - edited

          Don't forget (especially with regard to SLOBs) that values might in turn contain references to Entities.

          Example: An "Order" might be an @Entity and might have a field (an array) of OrderLineItems as value. Each OrderLineItem might e. g. carry quantity or disconunt and references a Product (@Entity).

          So even if you don't need the traversal from Product to all the Orders it is contained in, serializing the OrderLineItems needs a way to "cut off" the object graph at the transition towards the Product but must place some kind of referral there so that upon unserialization (of the OrderLineItem list, that is, during Order load) the Product references in every OrderLineItem are at least initialized with proxies again.

          Don't know whether/how referential integrity (OrderLineItems <-> Products) would make sense or could be implemented here.

          Show
          Matthias Pigulla added a comment - - edited Don't forget (especially with regard to SLOBs) that values might in turn contain references to Entities. Example: An "Order" might be an @Entity and might have a field (an array) of OrderLineItems as value. Each OrderLineItem might e. g. carry quantity or disconunt and references a Product (@Entity). So even if you don't need the traversal from Product to all the Orders it is contained in, serializing the OrderLineItems needs a way to "cut off" the object graph at the transition towards the Product but must place some kind of referral there so that upon unserialization (of the OrderLineItem list, that is, during Order load) the Product references in every OrderLineItem are at least initialized with proxies again. Don't know whether/how referential integrity (OrderLineItems <-> Products) would make sense or could be implemented here.
          Hide
          Benjamin Eberlei added a comment -

          Pushed back to 2.x, this feature is probably the largest feature request we have and we'd rather focus on small improvements for 2.1

          Show
          Benjamin Eberlei added a comment - Pushed back to 2.x, this feature is probably the largest feature request we have and we'd rather focus on small improvements for 2.1
          Hide
          Nino Martincevic added a comment - - edited

          Several thinks to consider/not to oversee here:

          1) There are value objects with identity. I know that is not DDD-conform but only at first sight. It means they are technically entities but are treated like VOs.
          Common examples are Zipcode or country. As they have identity (e.g. Zipcode: de-40723) they are entities but are created and interchanged like normal VOs.
          On the google DDD-List they were often referenced aS Lookup Entities.

          2) In virtually all (business) cases a collection of VO is an Entity. How else could you reference (add or remove) single elements of that list?
          There are exceptions here like a undefinded number of VOs in a collection, but in that case you can only add or remove a quantity of it.
          As a true collection (say 3 addresses for a client = Entity ClientAdresses) you would have to give them some kind of identity, even if it is only having
          a sequential number in that collection.

          @Matthias: OrderLineItems is an example of actually being an Entity.

          Show
          Nino Martincevic added a comment - - edited Several thinks to consider/not to oversee here: 1) There are value objects with identity. I know that is not DDD-conform but only at first sight. It means they are technically entities but are treated like VOs. Common examples are Zipcode or country. As they have identity (e.g. Zipcode: de-40723) they are entities but are created and interchanged like normal VOs. On the google DDD-List they were often referenced aS Lookup Entities. 2) In virtually all (business) cases a collection of VO is an Entity. How else could you reference (add or remove) single elements of that list? There are exceptions here like a undefinded number of VOs in a collection, but in that case you can only add or remove a quantity of it. As a true collection (say 3 addresses for a client = Entity ClientAdresses) you would have to give them some kind of identity, even if it is only having a sequential number in that collection. @Matthias: OrderLineItems is an example of actually being an Entity.
          Hide
          Ondrej Sibrina added a comment -

          Hi guys. I face this in my own way. Hope you won't wake up your neighbours with loud laugh.

          Every @Entity extends my BaseEntity object which provide kind of wrap for value with ValueBase object. So when want to get/set value from entity you call $entity->getData() where you won't get value "data" but wrapping ValueBase for value "data". Then you can get bare value by getValue(). Name of value class is in annotation and would be child of ValueBase.

          There's also parent class Base for EntityBase and ValueBase. In my case class Base is something like HTML element. So in the end you can use $entity->renderHtml() or $value->renderHtml() no matter if you're rendering value or @Entity. There's more features like validation, filtering and hydration value/entity from HTML forms, but it's extra.

          Implementation:

          "Base.php"
           
           /* @MappedSuperclass */
          abstract class Base {
            /* there're methods like _getParent(), _getPropertyName(), etc. used in code behind */
          }
          
          "ValueBase.php"
           
          abstract class ValueBase extends Base { 
             public function getValue() {
                  return $this->_getParent()->{$this->_getPropertyName()};
             }
             
             public function setValue($value) {
                  $this->_getParent()->{$this->_getPropertyName()} = $value;
             }
          }
          
          "EntityBase.php"
           
          /** @MappedSuperclass */
          abstract class EntityBase extends Base {
              public function __call($name, $arguments) {
                  /* get property object */
                  $pattern = '/^get(.*)$/u';
                  preg_match($pattern, $name, $matches);
                  if (isset($matches[1])) {
                      $propertyName = lcfirst($matches[1]);
                      return $this->get($propertyName);
                  }
          
                  /* set entity */
                  $pattern = '/^set(.*)$/u';
                  preg_match($pattern, $name, $matches);
                  if (isset($matches[1])) {
                      $propertyName = lcfirst($matches[1]);
                      return $this->set($propertyName, $arguments[0]);
                  }
              }
          
              public function get($propertyName) {
          	    $property = $this->_getElementProperty($propertyName);
          
          	    if ($property == null)
          		throw new Exception(sprintf("There isn't property like '%s'.", $propertyName));
          
          	    /* for collections and entities */
          	    if ($property["type"] == "collection" || $property["type"] == "entity") {
          		$element = $this->{$propertyName};
          		if ($element != null) {
          		    $element->_setParent($this);
          		    $element->_setPropertyName($propertyName);
          		} elseif ($property["type"] == "entity") {
          		    $element = new $property["class"];
          		    $element->_setParent($this);
          		    $element->_setPropertyName($propertyName);
          		    $element->_setNullEntity();
          		    $this->{$propertyName} = $element;
          		}
          		return $element;
          	    }
          	    else {
          	    /* for values */
          		if (!isset($this->_loadedEntities[$propertyName])) {
          		    $this->_loadedEntities[$propertyName] = new $property["class"]($this, $propertyName);
          		}
          		return $this->_loadedEntities[$propertyName];
          	    }
              }
          
              public function set($propertyName, $value) {
                  $property = $this->_getElementProperty($propertyName);
          
                  if ($property == null)
                      throw new Exception(sprintf("There isn't property like '%s'.", $propertyName));
          
                  /* for collections and entities */
                  if ($property["type"] == "collection" || $property["type"] == "entity") {
                      $this->{$propertyName} = $value;
                  }
                  /* for values */ else {
                      throw new Exception(sprintf("Can't call set on value property '%s'.", $propertyName));
                  }
          
                  return $this;
              }
          }
          

          Note that there's something i call "NullEntity". Instead of getting bare "null" you'll get @Entity child of EntityBase, where is set property nullEntity. Then there's posibility to work with null entity (for example renderHtml with empty inputs).

          It would be nice, if this is support by Doctrine natively, because i have some performace problems with my implementation. If it's interest in my whole code i can send you. But of course there's some security holes so i'll send it privetely. Thanks for understand and for Doctrine of course.

          Show
          Ondrej Sibrina added a comment - Hi guys. I face this in my own way. Hope you won't wake up your neighbours with loud laugh. Every @Entity extends my BaseEntity object which provide kind of wrap for value with ValueBase object. So when want to get/set value from entity you call $entity->getData() where you won't get value "data" but wrapping ValueBase for value "data". Then you can get bare value by getValue(). Name of value class is in annotation and would be child of ValueBase. There's also parent class Base for EntityBase and ValueBase. In my case class Base is something like HTML element. So in the end you can use $entity->renderHtml() or $value->renderHtml() no matter if you're rendering value or @Entity. There's more features like validation, filtering and hydration value/entity from HTML forms, but it's extra. Implementation: "Base.php" /* @MappedSuperclass */ abstract class Base { /* there're methods like _getParent(), _getPropertyName(), etc. used in code behind */ } "ValueBase.php" abstract class ValueBase extends Base { public function getValue() { return $ this ->_getParent()->{$ this ->_getPropertyName()}; } public function setValue($value) { $ this ->_getParent()->{$ this ->_getPropertyName()} = $value; } } "EntityBase.php" /** @MappedSuperclass */ abstract class EntityBase extends Base { public function __call($name, $arguments) { /* get property object */ $pattern = '/^get(.*)$/u'; preg_match($pattern, $name, $matches); if (isset($matches[1])) { $propertyName = lcfirst($matches[1]); return $ this ->get($propertyName); } /* set entity */ $pattern = '/^set(.*)$/u'; preg_match($pattern, $name, $matches); if (isset($matches[1])) { $propertyName = lcfirst($matches[1]); return $ this ->set($propertyName, $arguments[0]); } } public function get($propertyName) { $property = $ this ->_getElementProperty($propertyName); if ($property == null ) throw new Exception(sprintf( "There isn't property like '%s'." , $propertyName)); /* for collections and entities */ if ($property[ "type" ] == "collection" || $property[ "type" ] == "entity" ) { $element = $ this ->{$propertyName}; if ($element != null ) { $element->_setParent($ this ); $element->_setPropertyName($propertyName); } elseif ($property[ "type" ] == "entity" ) { $element = new $property[ "class" ]; $element->_setParent($ this ); $element->_setPropertyName($propertyName); $element->_setNullEntity(); $ this ->{$propertyName} = $element; } return $element; } else { /* for values */ if (!isset($ this ->_loadedEntities[$propertyName])) { $ this ->_loadedEntities[$propertyName] = new $property[ "class" ]($ this , $propertyName); } return $ this ->_loadedEntities[$propertyName]; } } public function set($propertyName, $value) { $property = $ this ->_getElementProperty($propertyName); if ($property == null ) throw new Exception(sprintf( "There isn't property like '%s'." , $propertyName)); /* for collections and entities */ if ($property[ "type" ] == "collection" || $property[ "type" ] == "entity" ) { $ this ->{$propertyName} = $value; } /* for values */ else { throw new Exception(sprintf( "Can't call set on value property '%s'." , $propertyName)); } return $ this ; } } Note that there's something i call "NullEntity". Instead of getting bare "null" you'll get @Entity child of EntityBase, where is set property nullEntity. Then there's posibility to work with null entity (for example renderHtml with empty inputs). It would be nice, if this is support by Doctrine natively, because i have some performace problems with my implementation. If it's interest in my whole code i can send you. But of course there's some security holes so i'll send it privetely. Thanks for understand and for Doctrine of course.
          Hide
          Mathias Verraes added a comment - - edited

          Note that Roman's workaround presented here does not work.

             /** @PrePersist @PreUpdate */
             function _destructEmbedded() {
                 // destruct $embedded into $value1, $value2, $value3
             }
          

          Doctrine tracks changes and does not perform updates when no changes are found. $embedded is not mapped, so it's not tracked and won't be taken into account by Doctrine when updating. Therefore, if $embedded is the only value that was changed, the PreUpdate event won't be triggered.

          The easiest thing to do is to simply destruct the VO on every mutation:

             public function setEmbedded($embedded) {
                 $this->embedded = $embedded;
                 $this->_destructEmbedded();
             }
          

          The downside is that you need to remember to call the method in every setter, but apart from that, there are no side effects, it always works and it's just one line of code

          _constructEmbedded() keeps working as is, postLoad will always be triggered.

          Show
          Mathias Verraes added a comment - - edited Note that Roman's workaround presented here does not work. /** @PrePersist @PreUpdate */ function _destructEmbedded() { // destruct $embedded into $value1, $value2, $value3 } Doctrine tracks changes and does not perform updates when no changes are found. $embedded is not mapped, so it's not tracked and won't be taken into account by Doctrine when updating. Therefore, if $embedded is the only value that was changed, the PreUpdate event won't be triggered. The easiest thing to do is to simply destruct the VO on every mutation: public function setEmbedded($embedded) { $ this ->embedded = $embedded; $ this ->_destructEmbedded(); } The downside is that you need to remember to call the method in every setter, but apart from that, there are no side effects, it always works and it's just one line of code _constructEmbedded() keeps working as is, postLoad will always be triggered.
          Hide
          Benjamin Dulau added a comment - - edited

          Hi,

          This feature would be awesome !
          VOs are really essential in a good domain design.

          If you plan to implement this, please remember that you can have nested VOs.
          Take the design for a Booking process for instance, you would have a DateRange object embedding two DateTime objects (in the simplest case).

          I have no doubts that you've already took this in consideration, but i prefer pointing this out, just in case

          Show
          Benjamin Dulau added a comment - - edited Hi, This feature would be awesome ! VOs are really essential in a good domain design. If you plan to implement this, please remember that you can have nested VOs. Take the design for a Booking process for instance, you would have a DateRange object embedding two DateTime objects (in the simplest case). I have no doubts that you've already took this in consideration, but i prefer pointing this out, just in case
          Hide
          Benjamin Eberlei added a comment -
          Show
          Benjamin Eberlei added a comment - work has been started, https://github.com/doctrine/doctrine2/pull/265
          Hide
          Matthias Pigulla added a comment -

          Does the new "complex sql types" feature help here - I mean, could that be used to map a value object to more than one column in the database?

          Show
          Matthias Pigulla added a comment - Does the new "complex sql types" feature help here - I mean, could that be used to map a value object to more than one column in the database?
          Hide
          songoko songowan added a comment -

          @Benjamin Eberlei The request seems to be closed in the link you provided! Does that mean that this feature won't be implemented?!

          Show
          songoko songowan added a comment - @Benjamin Eberlei The request seems to be closed in the link you provided! Does that mean that this feature won't be implemented?!
          Hide
          Marco Pivetta added a comment -

          songoko songowan no, it just probably wasn't the correct way of implementing this

          Show
          Marco Pivetta added a comment - songoko songowan no, it just probably wasn't the correct way of implementing this
          Hide
          Daniel Pitts added a comment -

          I'm curious if any effort is currently being put into this. I would really love to have this feature available.

          Show
          Daniel Pitts added a comment - I'm curious if any effort is currently being put into this. I would really love to have this feature available.
          Hide
          Marco Pivetta added a comment -
          Show
          Marco Pivetta added a comment - Daniel Pitts this is being developed in DDC-2374 ( https://github.com/doctrine/doctrine2/pull/634 )
          Hide
          Andrei Tchijov added a comment -

          I would argue that this SHOULD NOT be implemented. This is typical "convenience" feature. The desired functionality could be implemented with current state of Doctrine with very little efforts. I do not think that ROI on this feature will really be significant. Great frameworks are often defined by what they choose NOT to implement. Lets keep Doctrine Grate!

          Show
          Andrei Tchijov added a comment - I would argue that this SHOULD NOT be implemented. This is typical "convenience" feature. The desired functionality could be implemented with current state of Doctrine with very little efforts. I do not think that ROI on this feature will really be significant. Great frameworks are often defined by what they choose NOT to implement. Lets keep Doctrine Grate!
          Hide
          Daniel Pitts added a comment -

          Andrei, how is this a convenience feature? It allows a more efficient schema to be generated, reduces unnecessary boilerplate code, and can improve runtime performance. Is there a work-around which really provides those three features?

          Let's make Doctrine Great! (Grates won't help with ORM, they just get in the way)

          Show
          Daniel Pitts added a comment - Andrei, how is this a convenience feature? It allows a more efficient schema to be generated, reduces unnecessary boilerplate code, and can improve runtime performance. Is there a work-around which really provides those three features? Let's make Doctrine Great! (Grates won't help with ORM, they just get in the way)
          Hide
          Matthieu Napoli added a comment -

          Andrei: what??

          Can you justify this is a "convenience feature"?

          Show
          Matthieu Napoli added a comment - Andrei: what?? Can you justify this is a "convenience feature"?
          Hide
          Andrei Tchijov added a comment -

          @Matthieu:

          Can you justify this is a "convenience feature"?

          @Roman S. Borschel above showed one way of dealing with this. At the same time, in my experience if you think you need "ValueObject" - most likely you need another table in your schema.

          Show
          Andrei Tchijov added a comment - @Matthieu: Can you justify this is a "convenience feature"? @Roman S. Borschel above showed one way of dealing with this. At the same time, in my experience if you think you need "ValueObject" - most likely you need another table in your schema.
          Hide
          Daniel Pitts added a comment -

          Another table in your schema, yes, but not another entity with a primary key. There is no way to do collections of objects which aren't entities upon themselves.

          ORM's are a convenience feature, if you don't like convenience features, don't use Doctrine

          Show
          Daniel Pitts added a comment - Another table in your schema, yes, but not another entity with a primary key. There is no way to do collections of objects which aren't entities upon themselves. ORM's are a convenience feature, if you don't like convenience features, don't use Doctrine
          Hide
          Andrei Tchijov added a comment -

          @Daniel, What is wrong with another entity with PK? It does not "cost" you anything.

          ORM's are a convenience feature, if you don't like convenience features, don't use Doctrine

          There are small conveniences and there are huge conveniences. "huge conveniences" - is what makes projects great. "small conveniences" - in most cases are amount to "feature creep". I would much prefer (limited) resources of Doctrine community spent on "huge conveniences".

          Show
          Andrei Tchijov added a comment - @Daniel, What is wrong with another entity with PK? It does not "cost" you anything. ORM's are a convenience feature, if you don't like convenience features, don't use Doctrine There are small conveniences and there are huge conveniences. "huge conveniences" - is what makes projects great. "small conveniences" - in most cases are amount to "feature creep". I would much prefer (limited) resources of Doctrine community spent on "huge conveniences".
          Hide
          Marco Pivetta added a comment - - edited

          If you don't understand value objects then please don't depreciate them like that.

          An entity is basically a value object with an identity. A value object itself has NO identity. It exists as a representation value or complex value.

          Besides doctrine ORM, a value object has HUGE advantages when dealing with values composed of multiple fields (currencies, time intervals, etc.).

          And Doctrine would just provide a good way of serializing/unserializing those to DB.
          And no, separate table with identifiers/fields is a no-go.

          Show
          Marco Pivetta added a comment - - edited If you don't understand value objects then please don't depreciate them like that. An entity is basically a value object with an identity. A value object itself has NO identity. It exists as a representation value or complex value. Besides doctrine ORM, a value object has HUGE advantages when dealing with values composed of multiple fields (currencies, time intervals, etc.). And Doctrine would just provide a good way of serializing/unserializing those to DB. And no, separate table with identifiers/fields is a no-go.
          Hide
          Andrei Tchijov added a comment -

          @Marco Pivetta

          And no, separate table with identifiers/fields is a no-go.

          Why? I am genuinely curious.

          I could be misinterpreting your comment, but it sounds to me like you are talking about storing "objects" in DB fields (in serialized form). If I am correct, this is most certainly doable with current state of Doctrine. Just write you own getter/setter.

          Show
          Andrei Tchijov added a comment - @Marco Pivetta And no, separate table with identifiers/fields is a no-go. Why? I am genuinely curious. I could be misinterpreting your comment, but it sounds to me like you are talking about storing "objects" in DB fields (in serialized form). If I am correct, this is most certainly doable with current state of Doctrine. Just write you own getter/setter.
          Hide
          Marco Pivetta added a comment - - edited

          Separate tables/identifiers is a no-go because of the additional joins per fields, indexes, general overhead and it creates new types instead of standardized types.

          Additionally, you are adding identifiers to values, which is not what you may want. To make an example, you don't pick 3 coins from your purse, count "13 pennies" and then assign it a name "paul" (identifier). That's not how it works. You don't pay in "paul" amounts, and also you have only one "paul", which is also a problem since you got a lot of possible groupings of 13 pennies.

          I don't want to make lessons on what value objects are on an issue tracker (and again, this is an issue tracker, so I'm ok if you want to say something against an issue, but it should be backed by some valid arguments) since there's quite enough writings on the topic out there.

          If I am correct, this is most certainly doable with current state of Doctrine. Just write you own getter/setter.

          Andrei Tchijov no, that's not feasible with the current state of hydrators and without using a big amount of post-load events, which basically means the feature is patched up instead of supported out of the box (as it is in other ORMs)

          Getters/setters mean that your entity has an understanding of how to deal with the value object instantiation. That's not something that should be in the entity, and leads to a lot of code duplication.

          Show
          Marco Pivetta added a comment - - edited Separate tables/identifiers is a no-go because of the additional joins per fields, indexes, general overhead and it creates new types instead of standardized types. Additionally, you are adding identifiers to values, which is not what you may want. To make an example, you don't pick 3 coins from your purse, count "13 pennies" and then assign it a name "paul" (identifier). That's not how it works. You don't pay in "paul" amounts, and also you have only one "paul", which is also a problem since you got a lot of possible groupings of 13 pennies. I don't want to make lessons on what value objects are on an issue tracker (and again, this is an issue tracker, so I'm ok if you want to say something against an issue, but it should be backed by some valid arguments) since there's quite enough writings on the topic out there. If I am correct, this is most certainly doable with current state of Doctrine. Just write you own getter/setter. Andrei Tchijov no, that's not feasible with the current state of hydrators and without using a big amount of post-load events, which basically means the feature is patched up instead of supported out of the box (as it is in other ORMs) Getters/setters mean that your entity has an understanding of how to deal with the value object instantiation. That's not something that should be in the entity, and leads to a lot of code duplication.
          Hide
          Daniel Pitts added a comment -

          A new PK does indeed cost. It adds an index, which has a runtime cost. It adds a column, which has a runtime cost. It adds unnecessary management of the PK when you want to copy a value, which is both a runtime and development cost.

          Not having this feature also makes it so that I have to create a new entity for each use of the "value object". For example, if I have an otherwise reusable LineItem class, and I have three entities (eg, Invoice, Purchase Order, Shipment) which have a "collection of LineItem", I need to create three LineItem implementations, each one its own entity. So I'd end up with InvoiceLineItem, PurchaseOrderLineItem, and ShipmentLineItem, even though the structure and semantics are exactly the same. or I'd end up with one LineItem table, and three join tables, even though the id space really isn't the same.

          Show
          Daniel Pitts added a comment - A new PK does indeed cost. It adds an index, which has a runtime cost. It adds a column, which has a runtime cost. It adds unnecessary management of the PK when you want to copy a value, which is both a runtime and development cost. Not having this feature also makes it so that I have to create a new entity for each use of the "value object". For example, if I have an otherwise reusable LineItem class, and I have three entities (eg, Invoice, Purchase Order, Shipment) which have a "collection of LineItem", I need to create three LineItem implementations, each one its own entity. So I'd end up with InvoiceLineItem, PurchaseOrderLineItem, and ShipmentLineItem, even though the structure and semantics are exactly the same. or I'd end up with one LineItem table, and three join tables, even though the id space really isn't the same.
          Hide
          Nino Martincevic added a comment -

          @Daniel

          A collection of Value Objects does not work in most cases.
          Can you call one specific element?
          How do you remove one element of the list if it has no identity?
          Though it does work when you remove items by its values, say remove all line items that are red.

          @Marco
          "An entity is basically a value object with an identity. A value object itself has NO identity. It exists as a representation value or complex value."

          That's not correct. An VO is a value, true. But an entity is not defined by its value(s) plus an identity but by its identity, lifecycle and behaviour.
          Would be a poor entity otherwise and doubtful if it's an entity at all.

          Show
          Nino Martincevic added a comment - @Daniel A collection of Value Objects does not work in most cases. Can you call one specific element? How do you remove one element of the list if it has no identity? Though it does work when you remove items by its values, say remove all line items that are red. @Marco "An entity is basically a value object with an identity. A value object itself has NO identity. It exists as a representation value or complex value." That's not correct. An VO is a value, true. But an entity is not defined by its value(s) plus an identity but by its identity, lifecycle and behaviour. Would be a poor entity otherwise and doubtful if it's an entity at all.
          Hide
          Nino Martincevic added a comment - - edited

          One of the most dicussed representations of VO are addresses.
          In most busniess cases they are value objects, sometimes a combination of VOs.

          Say you have a customer table with name, address, phone, etc.
          Usually that is stored in one table, or at max in more tables with a 1:1 relation.

          The whole thing here is not the problem of an ORM but of the application.
          You must not use setStreet() or setZip() in you Entities to create or update an address,
          but setAddress(VOAddress $address) and the mapping of the attributes of the VO to the fields
          of the table goes to the application/base classes.

          If you restore the Entity Customer from storage you can use a factory that builds the VO again from
          the single fields. A VO can manage the validity of its building, so if any fields are corrupted in the
          storage the VO won't be restored - and most probably an exception is thrown, or some compensating event.

          No matter if I use Doctrine, another ORM or a self-build persistence-to-domain mechanism, all rules
          and invariants for the creation and restoring of entities and value objects are managed by my domain model
          and I never trust the persistence.

          Show
          Nino Martincevic added a comment - - edited One of the most dicussed representations of VO are addresses. In most busniess cases they are value objects, sometimes a combination of VOs. Say you have a customer table with name, address, phone, etc. Usually that is stored in one table, or at max in more tables with a 1:1 relation. The whole thing here is not the problem of an ORM but of the application. You must not use setStreet() or setZip() in you Entities to create or update an address, but setAddress(VOAddress $address) and the mapping of the attributes of the VO to the fields of the table goes to the application/base classes. If you restore the Entity Customer from storage you can use a factory that builds the VO again from the single fields. A VO can manage the validity of its building, so if any fields are corrupted in the storage the VO won't be restored - and most probably an exception is thrown, or some compensating event. No matter if I use Doctrine, another ORM or a self-build persistence-to-domain mechanism, all rules and invariants for the creation and restoring of entities and value objects are managed by my domain model and I never trust the persistence.
          Hide
          Nino Martincevic added a comment -

          Sorry to bug again, but one very important point is not mentioned yet but could be
          very helpful when talking about designing Entity/VO in Doctrine, too:

          An Object can be an Entity in one context and a VO in another!

          Simplified but still very common example:
          A shopping system, where you don't bother which address a customer has. There could even
          be many customers from the very same address. A customer only has to have ANY address,
          so in that context it's a VO.
          But in the Accounting and Distribution context you want to check the correctness of a specific
          address, being it to send the invoice to the legally correct address or to do bulk shipments.
          In that context address is an Entity, because you check and qualify THAT address.

          Enitities and VOs are a logical construct. If it's this or that depends on the bounded context.
          No context, no model.

          Show
          Nino Martincevic added a comment - Sorry to bug again, but one very important point is not mentioned yet but could be very helpful when talking about designing Entity/VO in Doctrine, too: An Object can be an Entity in one context and a VO in another! Simplified but still very common example: A shopping system, where you don't bother which address a customer has. There could even be many customers from the very same address. A customer only has to have ANY address, so in that context it's a VO. But in the Accounting and Distribution context you want to check the correctness of a specific address, being it to send the invoice to the legally correct address or to do bulk shipments. In that context address is an Entity, because you check and qualify THAT address. Enitities and VOs are a logical construct. If it's this or that depends on the bounded context. No context, no model.
          Hide
          Matthieu Napoli added a comment -

          Nino: correct me if I'm wrong, but from what I've read about DDD in that case you should create 2 separate classes. So there shouldn't be a class that could be an entity and a VO.

          Show
          Matthieu Napoli added a comment - Nino: correct me if I'm wrong, but from what I've read about DDD in that case you should create 2 separate classes. So there shouldn't be a class that could be an entity and a VO.
          Hide
          Daniel Pitts added a comment -

          I agree, VO and Entities are distinct things, and should probably be distinct classes. If you need an "entity" version of your VO, you would create an Entity which contains only the VO.

          Show
          Daniel Pitts added a comment - I agree, VO and Entities are distinct things, and should probably be distinct classes. If you need an "entity" version of your VO, you would create an Entity which contains only the VO.
          Hide
          Nino Martincevic added a comment -

          @Matthieu

          That's the old question of when a customer is a customer?
          A customer could be:

          • someone who visits the shop ([anonymous] visitor)
          • someone who registers (user)
          • someone who buys something (buyer)
          • in delivery context it's just an address (recipient)
          • in accounting context it's a legal person or company (invoice recipient)

          Guess you wouldn't have separate database entities/tables for all of these roles.

          So, yes. A class could (but doesn't has to) be a VO and an Entity, but only in a different context.
          In most cases it just plays another role. It could be specially factored by a repository
          for a specific use case, having other behaviour than in another.
          E.g. in delivery context it could have more than one address, a private and a delivery one.

          The more we talk about the more we see that the business (language) drives the design of your classes.
          And if it's clear which objects play a role in which context it becomes very clear what
          kind of classes or roles you need. And also that without a bounded context you could never
          answer the question what kind of classes you really need.

          Hope that helps.

          Show
          Nino Martincevic added a comment - @Matthieu That's the old question of when a customer is a customer? A customer could be: someone who visits the shop ( [anonymous] visitor) someone who registers (user) someone who buys something (buyer) in delivery context it's just an address (recipient) in accounting context it's a legal person or company (invoice recipient) Guess you wouldn't have separate database entities/tables for all of these roles. So, yes. A class could (but doesn't has to) be a VO and an Entity, but only in a different context. In most cases it just plays another role. It could be specially factored by a repository for a specific use case, having other behaviour than in another. E.g. in delivery context it could have more than one address, a private and a delivery one. The more we talk about the more we see that the business (language) drives the design of your classes. And if it's clear which objects play a role in which context it becomes very clear what kind of classes or roles you need. And also that without a bounded context you could never answer the question what kind of classes you really need. Hope that helps.
          Hide
          songoko songowan added a comment -

          well I can see that much work was done on implementing this feature on https://github.com/doctrine/doctrine2/pull/835#issuecomment-28697601

          However, it seems mapping a collection of value objects won't be supported in this patch. Currently we have to treat them as entities till further notice.

          Should a new issue be opened specifically for mapping a collection of value objects?

          Show
          songoko songowan added a comment - well I can see that much work was done on implementing this feature on https://github.com/doctrine/doctrine2/pull/835#issuecomment-28697601 However, it seems mapping a collection of value objects won't be supported in this patch. Currently we have to treat them as entities till further notice. Should a new issue be opened specifically for mapping a collection of value objects?
          Hide
          Marco Pivetta added a comment -

          songoko songowan yes please! Especially if you can provide some links/details on what the feature would look like and how it is implemented in JPA/Hibernate.

          Show
          Marco Pivetta added a comment - songoko songowan yes please! Especially if you can provide some links/details on what the feature would look like and how it is implemented in JPA/Hibernate.
          Hide
          Doctrine Bot added a comment -

          A related Github Pull-Request [GH-634] was closed:
          https://github.com/doctrine/doctrine2/pull/634

          Show
          Doctrine Bot added a comment - A related Github Pull-Request [GH-634] was closed: https://github.com/doctrine/doctrine2/pull/634
          Hide
          Doctrine Bot added a comment -

          A related Github Pull-Request [GH-835] was closed:
          https://github.com/doctrine/doctrine2/pull/835

          Show
          Doctrine Bot added a comment - A related Github Pull-Request [GH-835] was closed: https://github.com/doctrine/doctrine2/pull/835

            People

            • Assignee:
              Guilherme Blanco
              Reporter:
              Avi Block
            • Votes:
              40 Vote for this issue
              Watchers:
              37 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: