Skip to content

Instantly share code, notes, and snippets.

@bwaidelich
Last active January 14, 2022 09:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bwaidelich/3dfde3bec107546467dad231ab32c86b to your computer and use it in GitHub Desktop.
Save bwaidelich/3dfde3bec107546467dad231ab32c86b to your computer and use it in GitHub Desktop.
Example of a "constant" value object that can be compared
<?php
final class SomeIdentifier
{
/**
* @var string
*/
private $value;
/**
* @var self[]
*/
private static $instances = [];
private function __construct(string $value)
{
$this->value = $value;
}
private static function constant(string $value): self
{
return self::$instances[$value] ?? self::$instances[$value] = new self($value);
}
public static function fromString(string $value): self
{
return self::constant($value);
}
public function toString(): string
{
return $this->value;
}
public function __toString(): string
{
return $this->toString();
}
/**
* Cloning of constant value objects is not supported
*/
public function __clone()
{
throw new \RuntimeException('Cloning not supported');
}
/**
* Serialization of constant value objects is not supported
*/
public function __sleep()
{
throw new \RuntimeException('Serialization not supported');
}
/**
* Deserialization of constant value objects is not supported
*/
public function __wakeup()
{
throw new \RuntimeException('Deserialization not supported');
}
}
<?php
final class SomeIdentifiers implements \IteratorAggregate, \Serializable
{
/**
* @var SomeIdentifier[]
*/
private $identifiers;
private function __construct(array $identifiers)
{
$this->identifiers = $identifiers;
}
/**
* @param SomeIdentifier[] $identifiers
* @return self
*/
public static function fromArray(array $identifiers): self
{
array_walk($identifiers, static function($identifier) {
if (!$identifier instanceof SomeIdentifier) {
throw new \InvalidArgumentException('blah blah');
}
});
return new static(array_values($identifiers));
}
/**
* @return \Traversable|SomeIdentifier[]
*/
public function getIterator(): \Traversable
{
return new \ArrayIterator(array_values($this->identifiers));
}
/**
* @inheritDoc
*/
public function serialize(): string
{
return implode(',', $this->identifiers);
}
/**
* @inheritDoc
*/
public function unserialize($serialized): void
{
$this->identifiers = array_map(static function($identifier) {
return AccountIdentifier::fromString($identifier);
}, explode(',', $serialized));
}
}
@bwaidelich
Copy link
Author

$id1 = SomeIdentifier::fromString('some-id');
$id2 = SomeIdentifier::fromString('some-id');
$id3 = SomeIdentifier::fromString('some-other-id');

$id1 === $id2; // true
$id1 === $id3; // false

@kitsunet
Copy link

kitsunet commented Apr 3, 2020

I think you definitely want to prevent __clone as it will always create a new instance, the __clone method won't help as it cannot prevent the creation of a second instance. Additionally (un)serialize needs to be handled I guess, either also preventing it (might be problematic for session stuff and caches and such) or by handling the instances/deducplication there.

@bwaidelich
Copy link
Author

With the latest version (revision 4) it's no longer allowed to clone / serialize the value objects, but the container (SomeIdentifiers in this case) can still be serialized:

$id1 = SomeIdentifier::fromString('some-id');

$ids = SomeIdentifiers::fromArray([$id1]);

iterator_to_array($ids)[0] === iterator_to_array(unserialize(serialize($ids)))[0]); // true

@kitsunet
Copy link

kitsunet commented Apr 7, 2020

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment