|
17 | 17 | * @template TDecorator of object |
18 | 18 | * @implements IteratorAggregate<int, TDecorator> |
19 | 19 | * |
| 20 | + * @phpstan-type Ident string|class-string |
20 | 21 | * @phpstan-type decoratorOptionsFormat array<string, mixed> |
21 | | - * @phpstan-type _decoratorsFormat1 array<string, decoratorOptionsFormat> |
22 | | - * @phpstan-type _decoratorsFormat2 array<int, string|TDecorator|array{name: string, options?: decoratorOptionsFormat}> |
| 22 | + * @phpstan-type _decoratorsFormat1 array<Ident, decoratorOptionsFormat> |
| 23 | + * @phpstan-type _decoratorsFormat2 array<int, Ident|TDecorator|array{name: Ident, options?: decoratorOptionsFormat}> |
23 | 24 | * @phpstan-type decoratorsFormat _decoratorsFormat1 | _decoratorsFormat2 |
24 | 25 | */ |
25 | 26 | class DecoratorChain implements IteratorAggregate |
@@ -62,7 +63,7 @@ public function addDecoratorLoader(string $namespace, string $suffix = ''): stat |
62 | 63 | /** |
63 | 64 | * Add a decorator to the chain. |
64 | 65 | * |
65 | | - * @param TDecorator|string $decorator |
| 66 | + * @param TDecorator|Ident $decorator |
66 | 67 | * @param decoratorOptionsFormat $options Only allowed if parameter 1 is a string |
67 | 68 | * |
68 | 69 | * @return $this |
@@ -100,28 +101,34 @@ public function addDecorator(object|string $decorator, array $options = []): sta |
100 | 101 | * ``` |
101 | 102 | * // When no options are required or defaults are sufficient |
102 | 103 | * $decorators = [ |
103 | | - * 'HtmlTag', |
104 | | - * 'Label' |
| 104 | + * 'HtmlTag', |
| 105 | + * 'Label' |
105 | 106 | * ]; |
106 | 107 | * |
107 | 108 | * // Override default options by defining the option key and value |
108 | 109 | * |
109 | 110 | * // key: decorator name, value: options |
110 | 111 | * $decorators = [ |
111 | | - * 'HtmlTag' => ['tag' => 'span', 'placement' => 'append'], |
112 | | - * 'Label' => ['class' => 'element-label'] |
| 112 | + * 'HtmlTag' => ['tag' => 'span', 'placement' => 'append'], |
| 113 | + * 'Label' => ['class' => 'element-label'] |
113 | 114 | * ]; |
114 | 115 | * |
115 | 116 | * // or define the `name` and `options` key |
116 | 117 | * $decorators = [ |
117 | | - * ['name' => 'HtmlTag', 'options' => ['tag' => 'span', 'placement' => 'append']], |
118 | | - * ['name' => 'Label', 'options' => ['class' => 'element-label']] |
| 118 | + * ['name' => 'HtmlTag', 'options' => ['tag' => 'span', 'placement' => 'append']], |
| 119 | + * ['name' => 'Label', 'options' => ['class' => 'element-label']] |
119 | 120 | * ]; |
120 | 121 | * |
121 | 122 | * // or add Decorator instances |
122 | 123 | * $decorators = [ |
123 | | - * (new HtmlTagDecorator())->getAttributes()->add(['tag' => 'span', 'placement' => 'append']), |
124 | | - * (new LabelDecorator())->getAttributes()->add(['class' => 'element-label']) |
| 124 | + * (new HtmlTagDecorator())->getAttributes()->add(['tag' => 'span', 'placement' => 'append']), |
| 125 | + * (new LabelDecorator())->getAttributes()->add(['class' => 'element-label']) |
| 126 | + * ]; |
| 127 | + * |
| 128 | + * // Class paths are also supported |
| 129 | + * $decorators = [ |
| 130 | + * LabelDecorator::class, |
| 131 | + * ['name' => HtmlTagDecorator::class, ['tag' => 'span', 'placement' => 'append']] |
125 | 132 | * ]; |
126 | 133 | * ``` |
127 | 134 | * |
@@ -214,34 +221,44 @@ public function clearDecorators(): static |
214 | 221 | /** |
215 | 222 | * Create a decorator from the given name and options |
216 | 223 | * |
217 | | - * @param string $name |
| 224 | + * @param Ident $name |
218 | 225 | * @param decoratorOptionsFormat $options |
219 | 226 | * |
220 | 227 | * @return TDecorator |
221 | 228 | * |
222 | | - * @throws InvalidArgumentException If the given decorator is unknown |
| 229 | + * @throws InvalidArgumentException If the given decorator is unknown or not an instance of the expected type |
| 230 | + * @throws UnexpectedValueException If the loaded decorator is not an instance of the expected type |
223 | 231 | */ |
224 | 232 | protected function createDecorator(string $name, array $options = []): object |
225 | 233 | { |
226 | | - $class = $this->loadPlugin('decorator', $name); |
227 | | - |
228 | | - if (! $class) { |
229 | | - throw new InvalidArgumentException(sprintf( |
230 | | - "Can't load decorator '%s'. decorator unknown", |
231 | | - $name |
232 | | - )); |
233 | | - } |
234 | | - |
235 | | - $decorator = new $class(); |
| 234 | + if (class_exists($name)) { |
| 235 | + $decorator = new $name(); |
| 236 | + if (! $decorator instanceof $this->decoratorType) { |
| 237 | + throw new InvalidArgumentException(sprintf( |
| 238 | + "Invalid decorator class '%s'. decorator must be an instance of %s", |
| 239 | + $name, |
| 240 | + $this->decoratorType, |
| 241 | + )); |
| 242 | + } |
| 243 | + } else { |
| 244 | + $class = $this->loadPlugin('decorator', $name); |
| 245 | + if (! $class) { |
| 246 | + throw new InvalidArgumentException(sprintf( |
| 247 | + "Can't load decorator '%s'. decorator unknown", |
| 248 | + $name |
| 249 | + )); |
| 250 | + } |
236 | 251 |
|
237 | | - if (! $decorator instanceof $this->decoratorType) { |
238 | | - throw new UnexpectedValueException(sprintf( |
239 | | - "%s expects loader to return an instance of %s for decorator '%s', got %s instead", |
240 | | - __METHOD__, |
241 | | - $this->decoratorType, |
242 | | - $name, |
243 | | - get_php_type($decorator) |
244 | | - )); |
| 252 | + $decorator = new $class(); |
| 253 | + if (! $decorator instanceof $this->decoratorType) { |
| 254 | + throw new UnexpectedValueException(sprintf( |
| 255 | + "%s expects loader to return an instance of %s for decorator '%s', got %s instead", |
| 256 | + __METHOD__, |
| 257 | + $this->decoratorType, |
| 258 | + $name, |
| 259 | + get_php_type($decorator) |
| 260 | + )); |
| 261 | + } |
245 | 262 | } |
246 | 263 |
|
247 | 264 | if (! empty($options)) { |
|
0 commit comments