You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Currently in order to create Bag value objects you must extend the \Bag\Bag class. This requirement limits where you can use Bag value objects — you cannot simply add Value Object behavior to an existing value-object-like class. This is intended as an alternative way to use Bag.
We can solve this in two ways:
Composition using BagInterface and AsBag Trait
The first approach is more type-safe, by creating a BagInterface we enforce Bag-like UX on any class that wishes to be a Bag Value Object. Primarily this means enforcing the following methods:
and the default Bag class can simply be the following to continue allowing the existing extend Bag UX:
readonlyclass Bag implements Arrayable, Jsonable, JsonSerializable, Castable, BagInterface
{
use AsBag;
}
Bag::make()
Alternatively, we can implement a Bag::make() method with the following signature:
/** * @template T of object * @param class-string<T> $className * @return T */publicstaticfunction make(string$className, mixed$values): object;
This would then resolve the constructor arguments for $className and create a new instance. In addition static methods such as Bag::validate() and Bag::collect() can be updated to support objects that don't extend Bag.
However, with() is an instance method and the alternative to this to re-use Bag::make() and pass in the original value object values along with the new changes.
To enable support for features such as Hidden, we would also need to implement output functions such as toArray(), and toJson()/jsonSerialize(), which would either need to make heavy of (slow) reflection, or wrap around those functions in the underlying object.
Discussion
With the interface-and-trait implementation the resulting class has all the functionality and UX of Bag value objects, however it still requires you to update your existing class definition. The Bag::make() option requires less changes to the class, although you can opt-in to features using the attributes for features like casting or validation.
The primary downfall of the Bag::make() implementation is that the pipeline must be changed from working on Bag objects and instead just use object instead, losing any type safety in the process.
It should be noted that both of these solutions and the original extend Bag solution can co-exist, however there are concerns about complicating the UX by being too flexible.
The text was updated successfully, but these errors were encountered:
This is the approach I chose, in a similar situation. It's proven a good balance between being flexible and providing a default "hit the ground running" implementation.
dshafik
changed the title
Bag-less Value Objects
[RFC] Bag-less Value Objects
May 13, 2024
Currently in order to create Bag value objects you must extend the
\Bag\Bag
class. This requirement limits where you can use Bag value objects — you cannot simply add Value Object behavior to an existing value-object-like class. This is intended as an alternative way to use Bag.We can solve this in two ways:
Composition using
BagInterface
andAsBag
TraitThe first approach is more type-safe, by creating a
BagInterface
we enforce Bag-like UX on any class that wishes to be a Bag Value Object. Primarily this means enforcing the following methods:We can then provide the implementation using the
AsBag
trait, meaning that to implement Bag functionality in any object, you can do the following:and the default
Bag
class can simply be the following to continue allowing the existingextend Bag
UX:Bag::make()
Alternatively, we can implement a
Bag::make()
method with the following signature:This would then resolve the constructor arguments for
$className
and create a new instance. In addition static methods such asBag::validate()
andBag::collect()
can be updated to support objects that don't extendBag
.However,
with()
is an instance method and the alternative to this to re-useBag::make()
and pass in the original value object values along with the new changes.To enable support for features such as
Hidden
, we would also need to implement output functions such astoArray()
, andtoJson()
/jsonSerialize()
, which would either need to make heavy of (slow) reflection, or wrap around those functions in the underlying object.Discussion
With the interface-and-trait implementation the resulting class has all the functionality and UX of
Bag
value objects, however it still requires you to update your existing class definition. TheBag::make()
option requires less changes to the class, although you can opt-in to features using the attributes for features like casting or validation.The primary downfall of the
Bag::make()
implementation is that the pipeline must be changed from working onBag
objects and instead just useobject
instead, losing any type safety in the process.It should be noted that both of these solutions and the original
extend Bag
solution can co-exist, however there are concerns about complicating the UX by being too flexible.The text was updated successfully, but these errors were encountered: