1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Core;
6:
7: /**
8: * A class with this trait will have setDefaults() method that can
9: * be passed list of default properties.
10: *
11: * $view->setDefaults(['ui' => 'segment']);
12: *
13: * Typically you would want to do that inside your constructor. The
14: * default handling of the properties is:
15: *
16: * - only apply properties that are defined
17: * - only set property if it's current value is null
18: * - ignore defaults that have null value
19: * - if existing property and default have array, then both arrays will be merged
20: *
21: * Several classes may opt to extend setDefaults, for example in Agile UI
22: * setDefaults is extended to support classes and content:
23: *
24: * $segment->setDefaults(['Hello There', 'red', 'ui' => 'segment']);
25: *
26: * WARNING: Do not use this trait unless you have a lot of properties
27: * to inject. Also follow the guidelines on
28: * https://github.com/atk4/ui/wiki/Object-Constructors
29: *
30: * Relying on this trait excessively may cause anger management issues to
31: * some code reviewers.
32: */
33: trait DiContainerTrait
34: {
35: use WarnDynamicPropertyTrait;
36:
37: /**
38: * Call from __construct() to initialize the properties allowing
39: * developer to pass Dependency Injector Container.
40: *
41: * @param array<string, mixed> $properties
42: * @param bool $passively If true, existing non-null values will be kept
43: *
44: * @return $this
45: */
46: public function setDefaults(array $properties, bool $passively = false)
47: {
48: foreach ($properties as $k => $v) {
49: if (is_int($k)) { // @phpstan-ignore-line
50: $k = (string) $k; // @phpstan-ignore-line
51: }
52:
53: if (property_exists($this, $k)) {
54: if ($passively && isset($this->{$k}) && $this->{$k} !== null) {
55: continue;
56: }
57:
58: if ($v !== null) {
59: $this->{$k} = $v;
60: }
61: } else {
62: $this->setMissingProperty($k, $v);
63: }
64: }
65:
66: return $this;
67: }
68:
69: /**
70: * @param mixed $value
71: */
72: protected function setMissingProperty(string $propertyName, $value): void
73: {
74: throw (new Exception('Property for specified object is not defined'))
75: ->addMoreInfo('object', $this)
76: ->addMoreInfo('property', $propertyName)
77: ->addMoreInfo('value', $value);
78: }
79:
80: /**
81: * Return the argument and assert it is instance of current class.
82: *
83: * The best, typehinting-friendly, way to annotate object type if it not defined
84: * at method header or strong typing in method header cannot be used.
85: *
86: * @return static
87: */
88: public static function assertInstanceOf(object $object)// :static supported by PHP8+
89: {
90: if (!$object instanceof static) {
91: throw (new Exception('Object is not an instance of static class'))
92: ->addMoreInfo('static_class', static::class)
93: ->addMoreInfo('object_class', get_class($object));
94: }
95:
96: return $object;
97: }
98:
99: /**
100: * @param array<mixed>|object $seed
101: *
102: * @return array<mixed>|object
103: */
104: private static function _fromSeedPrecheck($seed, bool $unsafe)
105: {
106: if (!is_object($seed)) {
107: if (!is_array($seed)) { // @phpstan-ignore-line
108: throw (new Exception('Seed must be an array or an object'))
109: ->addMoreInfo('seed_type', gettype($seed));
110: }
111:
112: if (!isset($seed[0])) {
113: throw (new Exception('Class name is not specified by the seed'))
114: ->addMoreInfo('seed', $seed);
115: }
116:
117: $cl = $seed[0];
118: if (!$unsafe && !is_a($cl, static::class, true)) {
119: throw (new Exception('Seed class is not a subtype of static class'))
120: ->addMoreInfo('static_class', static::class)
121: ->addMoreInfo('seed_class', $cl);
122: }
123: }
124:
125: return $seed;
126: }
127:
128: /**
129: * Create new object from seed and assert it is instance of current class.
130: *
131: * The best, typehinting-friendly, way to create an object if it should not be
132: * immediately added to a parent (otherwise use addTo() method).
133: *
134: * @param array<mixed>|object $seed the first element specifies a class name, other elements are seed
135: * @param array<mixed> $defaults
136: *
137: * @return static
138: */
139: public static function fromSeed($seed = [], $defaults = [])// :static supported by PHP8+
140: {
141: $seed = self::_fromSeedPrecheck($seed, false);
142: $object = Factory::factory($seed, $defaults);
143:
144: return static::assertInstanceOf($object);
145: }
146:
147: /**
148: * Same as fromSeed(), but the new object is not asserted to be an instance of this class.
149: *
150: * @param array<mixed>|object $seed the first element specifies a class name, other elements are seed
151: * @param array<mixed> $defaults
152: *
153: * @return static
154: */
155: public static function fromSeedUnsafe($seed = [], $defaults = [])
156: {
157: $seed = self::_fromSeedPrecheck($seed, true);
158: $object = Factory::factory($seed, $defaults);
159:
160: // @phpstan-ignore-next-line https://github.com/phpstan/phpstan/issues/9022
161: return $object; // @phpstan-ignore-line
162: }
163: }
164: