Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
68 / 68 |
|
100.00% |
18 / 18 |
CRAP | |
100.00% |
1 / 1 |
HtmlFactory | |
100.00% |
68 / 68 |
|
100.00% |
18 / 18 |
32 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getContainer | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setContainer | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDefinitionFactory | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setDefinitionFactory | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setDefinitionsFile | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getDefinitionsFile | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDefinitionArray | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
hydrateContainer | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
makeDefinition | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 | |||
getDefinitionTypes | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
getDefinitionType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDefinitionIds | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isAmbiguousName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
makeElement | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
makeAttribute | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
makeEvent | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
makeCustomData | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * @author: Doug Wilbourne (dougwilbourne@gmail.com) |
5 | */ |
6 | |
7 | declare(strict_types=1); |
8 | |
9 | namespace pvc\html\htmlBuilder; |
10 | |
11 | use pvc\html\attributeArrayElement\AttributeCustomData; |
12 | use pvc\html\err\DTOInvalidPropertyValueException; |
13 | use pvc\html\err\DuplicateDefinitionIdException; |
14 | use pvc\html\err\InvalidDefinitionsFileException; |
15 | use pvc\html\err\InvalidEventNameException; |
16 | use pvc\html\err\InvalidTagNameException; |
17 | use pvc\html\err\InvalidAttributeIdNameException; |
18 | use pvc\interfaces\html\attributeArrayElement\AttributeCustomDataInterface; |
19 | use pvc\interfaces\html\attributeArrayElement\AttributeInterface; |
20 | use pvc\interfaces\html\attributeArrayElement\EventInterface; |
21 | use pvc\interfaces\html\htmlBuilder\definitions\DefinitionFactoryInterface; |
22 | use pvc\interfaces\html\htmlBuilder\definitions\DefinitionType; |
23 | use pvc\interfaces\html\htmlBuilder\HtmlContainerInterface; |
24 | use pvc\interfaces\html\htmlBuilder\HtmlFactoryInterface; |
25 | use pvc\interfaces\html\element\TagVoidInterface; |
26 | use pvc\interfaces\validator\ValTesterInterface; |
27 | use pvc\validator\val_tester\always_true\AlwaysTrueTester; |
28 | |
29 | /** |
30 | * Class HtmlFactory |
31 | * |
32 | * @phpstan-import-type DefArray from DefinitionFactoryInterface |
33 | * |
34 | * @template VendorSpecificDefinition of DefinitionFactoryInterface |
35 | * |
36 | * @implements HtmlFactoryInterface<VendorSpecificDefinition> |
37 | */ |
38 | class HtmlFactory implements HtmlFactoryInterface |
39 | { |
40 | /** |
41 | * @var HtmlContainerInterface<VendorSpecificDefinition> |
42 | */ |
43 | protected HtmlContainerInterface $container; |
44 | |
45 | /** |
46 | * @var DefinitionFactoryInterface<VendorSpecificDefinition> |
47 | */ |
48 | protected DefinitionFactoryInterface $definitionFactory; |
49 | |
50 | /** |
51 | * @var string |
52 | */ |
53 | protected string $definitionsFile = __DIR__ . '/definitions/Definitions.json'; |
54 | |
55 | /** |
56 | * @var array<string, string> |
57 | */ |
58 | protected array $definitionTypes; |
59 | |
60 | /** |
61 | * there are several identifiers in html which are duplicates, i.e. out of context, you would not know whether |
62 | * you are referring to an attributeArrayElement or an element. For this reason and because we are using a single container, |
63 | * there are some cases where the definition id needs to be different from the name of the object. The |
64 | * method of disambiguation is to append an _attr or _element to the names of the objects and make those the |
65 | * definition ids. For example, cite => cite_attr / cite_element. |
66 | * |
67 | * The ambiguous identifiers are: |
68 | * |
69 | * cite |
70 | * data |
71 | * form |
72 | * label |
73 | * span |
74 | * style |
75 | * title |
76 | * |
77 | * @var array<string> |
78 | */ |
79 | protected array $ambiguousIdentifiers = [ |
80 | 'cite', |
81 | 'data', |
82 | 'form', |
83 | 'label', |
84 | 'span', |
85 | 'style', |
86 | 'title', |
87 | 'type', |
88 | ]; |
89 | |
90 | /** |
91 | * @param HtmlContainerInterface<VendorSpecificDefinition> $container |
92 | * @param DefinitionFactoryInterface<VendorSpecificDefinition> $definitionFactory |
93 | */ |
94 | public function __construct( |
95 | HtmlContainerInterface $container, |
96 | DefinitionFactoryInterface $definitionFactory, |
97 | string $definitionsFile = null, |
98 | ) { |
99 | $this->setContainer($container); |
100 | $this->setDefinitionFactory($definitionFactory); |
101 | |
102 | if ($definitionsFile) { |
103 | $this->setDefinitionsFile($definitionsFile); |
104 | } |
105 | $this->hydrateContainer($this->getContainer()); |
106 | } |
107 | |
108 | /** |
109 | * getContainer |
110 | * @return HtmlContainerInterface<VendorSpecificDefinition> |
111 | */ |
112 | public function getContainer(): HtmlContainerInterface |
113 | { |
114 | return $this->container; |
115 | } |
116 | |
117 | /** |
118 | * setContainer |
119 | * @param HtmlContainerInterface<VendorSpecificDefinition> $container |
120 | */ |
121 | public function setContainer(HtmlContainerInterface $container): void |
122 | { |
123 | $this->container = $container; |
124 | } |
125 | |
126 | /** |
127 | * getDefinitionFactory |
128 | * @return DefinitionFactoryInterface<VendorSpecificDefinition> |
129 | */ |
130 | public function getDefinitionFactory(): DefinitionFactoryInterface |
131 | { |
132 | return $this->definitionFactory; |
133 | } |
134 | |
135 | /** |
136 | * setDefinitionFactory |
137 | * @param DefinitionFactoryInterface<VendorSpecificDefinition> $definitionFactory |
138 | */ |
139 | public function setDefinitionFactory(DefinitionFactoryInterface $definitionFactory): void |
140 | { |
141 | $this->definitionFactory = $definitionFactory; |
142 | } |
143 | |
144 | protected function setDefinitionsFile(string $filename): void |
145 | { |
146 | if (!is_readable($filename)) { |
147 | throw new InvalidDefinitionsFileException($filename); |
148 | } |
149 | $this->definitionsFile = $filename; |
150 | } |
151 | |
152 | /** |
153 | * getDefinitionsFile |
154 | * @return string |
155 | */ |
156 | public function getDefinitionsFile(): string |
157 | { |
158 | return $this->definitionsFile; |
159 | } |
160 | |
161 | /** |
162 | * getDefinitionArray |
163 | * @return array<mixed> |
164 | * @throws InvalidDefinitionsFileException |
165 | */ |
166 | protected function getDefinitionArray(): array |
167 | { |
168 | /** @var string $jsonString */ |
169 | $jsonString = file_get_contents($this->getDefinitionsFile()); |
170 | $defs = json_decode($jsonString, true); |
171 | |
172 | if (is_null($defs)) { |
173 | throw new InvalidDefinitionsFileException($this->getDefinitionsFile()); |
174 | } |
175 | |
176 | assert(is_array($defs)); |
177 | return $defs; |
178 | } |
179 | |
180 | /** |
181 | * hydrateContainer |
182 | * @param HtmlContainerInterface<VendorSpecificDefinition> $container |
183 | * @throws DTOInvalidPropertyValueException |
184 | * @throws DuplicateDefinitionIdException |
185 | * @throws InvalidDefinitionsFileException |
186 | */ |
187 | protected function hydrateContainer(HtmlContainerInterface $container): void |
188 | { |
189 | /** @var array<DefArray> $jsonDefs */ |
190 | $jsonDefs = $this->getDefinitionArray(); |
191 | |
192 | foreach ($jsonDefs as $jsonDef) { |
193 | /** |
194 | * this artifact is really for diagnostics. Using the getDefinitionIdsTypes method you can return |
195 | * all of this array or filter it for definition ids of a certain type |
196 | */ |
197 | $defId = $jsonDef['defId']; |
198 | $defType = $jsonDef['defType']; |
199 | |
200 | $def = $this->makeDefinition($jsonDef); |
201 | |
202 | if (isset($this->definitionTypes[$defId])) { |
203 | throw new DuplicateDefinitionIdException($defId); |
204 | } else { |
205 | $this->definitionTypes[$defId] = $defType; |
206 | } |
207 | |
208 | $container->add($defId, $def); |
209 | } |
210 | } |
211 | |
212 | /** |
213 | * makeDefinition |
214 | * @param DefArray $defArray |
215 | * @return VendorSpecificDefinition |
216 | * @throws DTOInvalidPropertyValueException |
217 | */ |
218 | protected function makeDefinition(array $defArray): mixed |
219 | { |
220 | $defTypeString = $defArray['defType']; |
221 | |
222 | $result = match(strval($defTypeString)) { |
223 | 'Attribute' => $this->definitionFactory->makeAttributeDefinition($defArray), |
224 | 'AttributeValueTester' => $this->definitionFactory->makeAttributeValueTesterDefinition($defArray), |
225 | 'Element' => $this->definitionFactory->makeElementDefinition($defArray), |
226 | 'Event' => $this->definitionFactory->makeEventDefinition($defArray), |
227 | 'Other' => $this->definitionFactory->makeOtherDefinition($defArray), |
228 | default => throw new DTOInvalidPropertyValueException('defType', $defTypeString, 'DTOTrait'), |
229 | }; |
230 | return $result; |
231 | } |
232 | |
233 | /** |
234 | * getDefinitionIdsTypes |
235 | * @param DefinitionType $type |
236 | * @return array<string, string> |
237 | */ |
238 | public function getDefinitionTypes(DefinitionType $type = null): array |
239 | { |
240 | $result = []; |
241 | foreach ($this->definitionTypes as $defId => $defType) { |
242 | if (is_null($type) || $defType == $type->value) { |
243 | $result[$defId] = $defType; |
244 | } |
245 | } |
246 | return $result; |
247 | } |
248 | |
249 | /** |
250 | * getDefinitionType |
251 | * @param string $defId |
252 | * @return string|null |
253 | */ |
254 | public function getDefinitionType(string $defId): ?string |
255 | { |
256 | return $this->definitionTypes[$defId] ?? null; |
257 | } |
258 | |
259 | /** |
260 | * getDefinitionIds |
261 | * @param DefinitionType|null $type |
262 | * @return array<string> |
263 | */ |
264 | public function getDefinitionIds(DefinitionType $type = null): array |
265 | { |
266 | return array_keys($this->getDefinitionTypes($type)); |
267 | } |
268 | |
269 | protected function isAmbiguousName(string $name): bool |
270 | { |
271 | return(in_array($name, $this->ambiguousIdentifiers)); |
272 | } |
273 | |
274 | /** |
275 | * makeElement |
276 | * @param string $elementName |
277 | * @return TagVoidInterface<VendorSpecificDefinition> |
278 | * @throws InvalidTagNameException |
279 | * @throws \Psr\Container\ContainerExceptionInterface |
280 | * @throws \Psr\Container\NotFoundExceptionInterface |
281 | */ |
282 | public function makeElement(string $elementName): TagVoidInterface |
283 | { |
284 | $elementDefId = ($this->isAmbiguousName($elementName) ? $elementName . '_element' : $elementName); |
285 | |
286 | if (!$this->container->has($elementDefId)) { |
287 | throw new InvalidTagNameException($elementName); |
288 | } |
289 | /** @var TagVoidInterface<VendorSpecificDefinition> $element */ |
290 | $element = $this->getContainer()->get($elementDefId); |
291 | $element->setHtmlBuilder($this); |
292 | return $element; |
293 | } |
294 | |
295 | /** |
296 | * makeAttribute |
297 | * @param string $attributeName |
298 | * @return AttributeInterface |
299 | * @throws InvalidAttributeIdNameException |
300 | * @throws \Psr\Container\ContainerExceptionInterface |
301 | * @throws \Psr\Container\NotFoundExceptionInterface |
302 | */ |
303 | public function makeAttribute(string $attributeName): AttributeInterface |
304 | { |
305 | $attributeDefId = ($this->isAmbiguousName($attributeName) ? $attributeName . '_attr' : $attributeName); |
306 | if (!$this->container->has($attributeDefId)) { |
307 | throw new InvalidAttributeIdNameException($attributeName); |
308 | } |
309 | /** @var AttributeInterface $attributeArrayElement */ |
310 | $attributeArrayElement = $this->getContainer()->get($attributeDefId); |
311 | return $attributeArrayElement; |
312 | } |
313 | |
314 | /** |
315 | * makeEvent |
316 | * @param string $eventName |
317 | * @return EventInterface |
318 | * @throws InvalidEventNameException |
319 | * @throws \Psr\Container\ContainerExceptionInterface |
320 | * @throws \Psr\Container\NotFoundExceptionInterface |
321 | */ |
322 | public function makeEvent(string $eventName): EventInterface |
323 | { |
324 | if (!$this->container->has($eventName)) { |
325 | throw new InvalidEventNameException($eventName); |
326 | } |
327 | /** @var EventInterface $event */ |
328 | $event = $this->getContainer()->get($eventName); |
329 | return $event; |
330 | } |
331 | |
332 | |
333 | /** |
334 | * makeCustomData |
335 | * @param string $attributeName |
336 | * @param ValTesterInterface<string>|null $valTester |
337 | * @return AttributeCustomDataInterface |
338 | * @throws \Psr\Container\ContainerExceptionInterface |
339 | * @throws \Psr\Container\NotFoundExceptionInterface |
340 | * |
341 | * although we never like hard dependencies inside a method, this class is a htmlBuilder. And this method is, by |
342 | * definition, tightly coupled to the AttributeCustomData class....... |
343 | */ |
344 | public function makeCustomData( |
345 | string $attributeName, |
346 | string $value, |
347 | ValTesterInterface $valTester = null |
348 | ): AttributeCustomDataInterface { |
349 | |
350 | $valTester = $valTester ?: new AlwaysTrueTester(); |
351 | |
352 | $attributeArrayElement = new AttributeCustomData(); |
353 | $attributeArrayElement->setDefId($attributeName); |
354 | $attributeArrayElement->setName($attributeName); |
355 | $attributeArrayElement->setTester($valTester); |
356 | $attributeArrayElement->setValue($value); |
357 | |
358 | return $attributeArrayElement; |
359 | } |
360 | } |