1: <?php declare(strict_types=1);
2:
3: namespace Salient\Core\Concern;
4:
5: use Salient\Contract\Core\Immutable;
6: use ReflectionProperty;
7:
8: /**
9: * @api
10: *
11: * @phpstan-require-implements Immutable
12: */
13: trait HasMutator
14: {
15: /**
16: * Get a copy of the object with a value assigned to a property if its
17: * current value differs, otherwise return the object
18: *
19: * @param mixed $value
20: * @return static
21: */
22: private function with(string $property, $value)
23: {
24: if ((
25: isset($this->$property)
26: || ($value === null && $this->propertyIsInitialized($property))
27: ) && $value === $this->$property) {
28: return $this;
29: }
30:
31: $clone = clone $this;
32: $clone->$property = $value;
33: $clone->handlePropertyChanged($property);
34: return $clone;
35: }
36:
37: /**
38: * Get a copy of the object where a property is unset if it is currently
39: * set, otherwise return the object
40: *
41: * @return static
42: */
43: private function without(string $property)
44: {
45: if (
46: !isset($this->$property)
47: && !$this->propertyIsInitialized($property)
48: ) {
49: return $this;
50: }
51:
52: $clone = clone $this;
53: unset($clone->$property);
54: $clone->handlePropertyChanged($property);
55: return $clone;
56: }
57:
58: /**
59: * Called after a property of the object is changed or unset via one of its
60: * mutator methods
61: */
62: private function handlePropertyChanged(string $property): void {}
63:
64: /**
65: * @internal
66: */
67: private function propertyIsInitialized(string $property): bool
68: {
69: if (!property_exists($this, $property)) {
70: return false;
71: }
72:
73: $property = new ReflectionProperty($this, $property);
74: $property->setAccessible(true);
75:
76: return $property->isInitialized($this);
77: }
78: }
79: