Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
75.88% |
236 / 311 |
|
33.33% |
3 / 9 |
CRAP | |
0.00% |
0 / 1 |
ZObjectFactory | |
75.88% |
236 / 311 |
|
33.33% |
3 / 9 |
147.74 | |
0.00% |
0 / 1 |
createPersistentContent | |
82.09% |
55 / 67 |
|
0.00% |
0 / 1 |
11.70 | |||
validatePersistentKeys | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
create | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
5 | |||
createCustom | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
createChild | |
81.18% |
69 / 85 |
|
0.00% |
0 / 1 |
20.16 | |||
createKeyValues | |
60.61% |
20 / 33 |
|
0.00% |
0 / 1 |
20.80 | |||
extractInnerObject | |
18.18% |
2 / 11 |
|
0.00% |
0 / 1 |
4.19 | |||
extractObjectType | |
71.25% |
57 / 80 |
|
0.00% |
0 / 1 |
22.08 | |||
trackSelfReference | |
81.82% |
9 / 11 |
|
0.00% |
0 / 1 |
5.15 |
1 | <?php |
2 | /** |
3 | * WikiLambda ZObject factory |
4 | * |
5 | * @file |
6 | * @ingroup Extensions |
7 | * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt |
8 | * @license MIT |
9 | */ |
10 | |
11 | namespace MediaWiki\Extension\WikiLambda; |
12 | |
13 | use MediaWiki\Extension\WikiLambda\Registry\ZErrorTypeRegistry; |
14 | use MediaWiki\Extension\WikiLambda\Registry\ZTypeRegistry; |
15 | use MediaWiki\Extension\WikiLambda\Validation\ZObjectStructureValidator; |
16 | use MediaWiki\Extension\WikiLambda\ZObjects\ZFunctionCall; |
17 | use MediaWiki\Extension\WikiLambda\ZObjects\ZObject; |
18 | use MediaWiki\Extension\WikiLambda\ZObjects\ZPersistentObject; |
19 | use MediaWiki\Extension\WikiLambda\ZObjects\ZReference; |
20 | use MediaWiki\Extension\WikiLambda\ZObjects\ZString; |
21 | use MediaWiki\Extension\WikiLambda\ZObjects\ZType; |
22 | use MediaWiki\Extension\WikiLambda\ZObjects\ZTypedList; |
23 | use MediaWiki\Title\Title; |
24 | |
25 | class ZObjectFactory { |
26 | |
27 | /** |
28 | * Validates and creates a ZPersistentObject from the given input data. |
29 | * If the input already has the ZPersistentObejct keys, it uses |
30 | * them to construct the wrapper object. If not, it builds a wrapper |
31 | * ZPersistentObject with empty values. The resulting ZObject will be |
32 | * structurally valid or well-formed. |
33 | * |
34 | * This method is the entrypoint from WikiLambda content object. |
35 | * |
36 | * @param string|array|\stdClass $input The item to turn into a ZObject |
37 | * @return ZPersistentObject |
38 | * @throws ZErrorException |
39 | */ |
40 | public static function createPersistentContent( $input ) { |
41 | // 1. Get ZObject type. If not present or invalid, throw a not wellformed error |
42 | try { |
43 | $inputTypeZObject = self::extractObjectType( $input ); |
44 | $typeZid = $inputTypeZObject->getZValue(); |
45 | } catch ( ZErrorException $e ) { |
46 | throw new ZErrorException( |
47 | ZErrorFactory::createValidationZError( $e->getZError() ) |
48 | ); |
49 | } |
50 | |
51 | $object = $input; |
52 | |
53 | // 2. If ZObject type is Z2/Z_PERSISTENT_OBJECT, get the inner object. |
54 | // If not present, throw a not wellformed error. |
55 | if ( $typeZid === ZTypeRegistry::Z_PERSISTENTOBJECT ) { |
56 | try { |
57 | $object = self::extractInnerObject( $input ); |
58 | } catch ( ZErrorException $e ) { |
59 | throw new ZErrorException( |
60 | ZErrorFactory::createValidationZError( $e->getZError() ) |
61 | ); |
62 | } |
63 | |
64 | // Get type of the inner ZObject. If not present, throw a not wellformed error |
65 | try { |
66 | $innerTypeZObject = self::extractObjectType( $object ); |
67 | $typeZid = $innerTypeZObject->getZValue(); |
68 | } catch ( ZErrorException $e ) { |
69 | throw new ZErrorException( |
70 | ZErrorFactory::createValidationZError( $e->getZError() ) |
71 | ); |
72 | } |
73 | } |
74 | |
75 | // 3. Make sure that the ZObject type is not one of the disallowed types |
76 | // to directly wrap in a ZPersistentObject |
77 | if ( in_array( $typeZid, ZTypeRegistry::DISALLOWED_ROOT_ZOBJECTS ) ) { |
78 | throw new ZErrorException( |
79 | ZErrorFactory::createZErrorInstance( |
80 | ZErrorTypeRegistry::Z_ERROR_DISALLOWED_ROOT_ZOBJECT, |
81 | [ |
82 | 'data' => $object |
83 | ] |
84 | ) |
85 | ); |
86 | } |
87 | |
88 | // 4. Create ZPersistentObject wrapper |
89 | // 4.1. Extract persistent keys or assign empty values |
90 | $persistentId = null; |
91 | $persistentLabel = null; |
92 | $persistentAliases = null; |
93 | $persistentDescription = null; |
94 | if ( $inputTypeZObject->getZValue() === ZTypeRegistry::Z_PERSISTENTOBJECT ) { |
95 | // Check that required keys exist |
96 | try { |
97 | self::validatePersistentKeys( $input ); |
98 | } catch ( ZErrorException $e ) { |
99 | throw new ZErrorException( |
100 | ZErrorFactory::createValidationZError( $e->getZError() ) |
101 | ); |
102 | } |
103 | // Build the values |
104 | $persistentId = self::createChild( $input->{ ZTypeRegistry::Z_PERSISTENTOBJECT_ID } ); |
105 | $persistentLabel = self::createChild( $input->{ ZTypeRegistry::Z_PERSISTENTOBJECT_LABEL } ); |
106 | |
107 | $persistentAliases = property_exists( $input, ZTypeRegistry::Z_PERSISTENTOBJECT_ALIASES ) |
108 | ? self::createChild( $input->{ ZTypeRegistry::Z_PERSISTENTOBJECT_ALIASES } ) |
109 | : null; |
110 | |
111 | $persistentDescription = property_exists( $input, |
112 | ZTypeRegistry::Z_PERSISTENTOBJECT_DESCRIPTION ) |
113 | ? self::createChild( $input->{ ZTypeRegistry::Z_PERSISTENTOBJECT_DESCRIPTION } ) |
114 | : null; |
115 | } |
116 | |
117 | // Build empty values if we are creating a new ZPersistentObject wrapper |
118 | // TODO (T362249): Looks like this case is never really used: contemplate removing it |
119 | $persistentId ??= self::createChild( ZTypeRegistry::Z_NULL_REFERENCE ); |
120 | $persistentLabel ??= self::createChild( (object)[ |
121 | ZTypeRegistry::Z_OBJECT_TYPE => ZTypeRegistry::Z_MULTILINGUALSTRING, |
122 | ZTypeRegistry::Z_MULTILINGUALSTRING_VALUE => [ ZTypeRegistry::Z_MONOLINGUALSTRING ] |
123 | ] ); |
124 | |
125 | // 4.2 Track self-reference if Z_PERSISNTENT_ID is present |
126 | self::trackSelfReference( $persistentId->getZValue(), self::SET_SELF_ZID ); |
127 | |
128 | // 4.3. Create and validate inner ZObject: can throw Z502/Not wellformed |
129 | $zObject = self::create( $object ); |
130 | |
131 | // 4.5. Construct ZPersistentObject() |
132 | $persistentObject = new ZPersistentObject( $persistentId, $zObject, $persistentLabel, |
133 | $persistentAliases, $persistentDescription ); |
134 | |
135 | // 4.6. Check validity, to make sure that ID, label and aliases have the right format |
136 | if ( !$persistentObject->isValid() ) { |
137 | throw new ZErrorException( |
138 | // TODO (T300506): Detail persistent object-related errors |
139 | ZErrorFactory::createZErrorInstance( |
140 | ZErrorTypeRegistry::Z_ERROR_UNKNOWN, |
141 | [ |
142 | 'message' => "ZPersistentObject not valid" |
143 | ] |
144 | ) |
145 | ); |
146 | } |
147 | |
148 | // 4.6. Untrack self-reference |
149 | self::trackSelfReference( $persistentId->getZValue(), self::UNSET_SELF_ZID ); |
150 | return $persistentObject; |
151 | } |
152 | |
153 | /** |
154 | * Check that the required ZPersistentObject keys exists and, if they don't, raise |
155 | * Z511/Missing key errors |
156 | * |
157 | * @param string|array|\stdClass $input The item to check is a ZObject |
158 | * @return bool |
159 | * @throws ZErrorException |
160 | */ |
161 | public static function validatePersistentKeys( $input ): bool { |
162 | $validator = ZObjectStructureValidator::createCanonicalValidator( ZTypeRegistry::Z_PERSISTENTOBJECT ); |
163 | $status = $validator->validate( $input ); |
164 | |
165 | if ( !$status->isValid() ) { |
166 | throw new ZErrorException( $status->getErrors() ); |
167 | } |
168 | |
169 | return true; |
170 | } |
171 | |
172 | /** |
173 | * Validates and creates an object of type ZObject from a given input data. |
174 | * The resulting ZObject will be structurally valid or well-formed. |
175 | * |
176 | * This method is the entrypoint from WikiLambda ZObject creation parting |
177 | * from their serialized representation. |
178 | * |
179 | * @param string|array|\stdClass $input |
180 | * @return ZObject |
181 | * @throws ZErrorException |
182 | */ |
183 | public static function create( $input ): ZObject { |
184 | // 1. Get ZObject type. If not present, return a not wellformed error. |
185 | try { |
186 | $typeZObject = self::extractObjectType( $input ); |
187 | $typeZid = $typeZObject->getZValue(); |
188 | } catch ( ZErrorException $e ) { |
189 | throw new ZErrorException( |
190 | ZErrorFactory::createValidationZError( $e->getZError() ) |
191 | ); |
192 | } |
193 | |
194 | // 2. Create ZObjectStructureValidator to check that the ZObject is well formed |
195 | try { |
196 | // TODO (T309409): Generic validator should work with lists but it fails during ZObject migration |
197 | $validator = ZObjectStructureValidator::createCanonicalValidator( is_array( $input ) ? "LIST" : $typeZid ); |
198 | } catch ( ZErrorException $e ) { |
199 | // If there's no function-schemata validator (user-defined type), we do a generic custom validation |
200 | $validator = ZObjectStructureValidator::createCanonicalValidator( ZTypeRegistry::Z_OBJECT ); |
201 | } |
202 | |
203 | $status = $validator->validate( $input ); |
204 | |
205 | // 3. Check structural validity or wellformedness: |
206 | // If structural validation does not succeed, we cannot save the ZObject: |
207 | // throw ZErrorException with the ZError returned by the validator |
208 | if ( !$status->isValid() ) { |
209 | throw new ZErrorException( $status->getErrors() ); |
210 | } |
211 | |
212 | // 4. Everything is correct, create ZObject instances |
213 | return self::createChild( $input ); |
214 | } |
215 | |
216 | /** |
217 | * Creates an instance of a custom or user-defined type after validating its basic |
218 | * structure: the keys are valid ZObject keys and the values have the correct types |
219 | * |
220 | * @deprecated |
221 | * @param string|array|\stdClass $input |
222 | * @return ZObject |
223 | * @throws ZErrorException |
224 | */ |
225 | public static function createCustom( $input ): ZObject { |
226 | try { |
227 | ZObjectUtils::isValidZObject( $input ); |
228 | } catch ( ZErrorException $e ) { |
229 | throw new ZErrorException( |
230 | ZErrorFactory::createValidationZError( $e->getZError() ) |
231 | ); |
232 | } |
233 | |
234 | return self::createChild( $input ); |
235 | } |
236 | |
237 | /** |
238 | * Creates an object of type ZObject from the given input. This method should only |
239 | * be called internally, either from the ZObjectFactory of from the ZObject |
240 | * constructors. ZObjects created using this method will not necessarily be |
241 | * structurally valid. |
242 | * |
243 | * @param string|array|ZObject|\stdClass $object The item to turn into a ZObject |
244 | * @return ZObject |
245 | * @throws ZErrorException |
246 | */ |
247 | public static function createChild( $object ) { |
248 | if ( $object instanceof ZObject ) { |
249 | return $object; |
250 | } |
251 | |
252 | if ( is_string( $object ) ) { |
253 | if ( ZObjectUtils::isValidZObjectReference( $object ) ) { |
254 | return new ZReference( $object ); |
255 | } else { |
256 | return new ZString( $object ); |
257 | } |
258 | } |
259 | |
260 | if ( is_array( $object ) ) { |
261 | if ( count( $object ) === 0 ) { |
262 | throw new ZErrorException( |
263 | ZErrorFactory::createZErrorInstance( |
264 | ZErrorTypeRegistry::Z_ERROR_UNDEFINED_LIST_TYPE, |
265 | [ |
266 | 'data' => $object |
267 | ] |
268 | ) |
269 | ); |
270 | } |
271 | |
272 | $rawListType = array_shift( $object ); |
273 | $listType = self::createChild( $rawListType ); |
274 | |
275 | // TODO (T330321): All of the checks in the following if block have already been |
276 | // checked during static validation, but the following block is: |
277 | // A) Incomplete (lacks other possible resolvers) |
278 | // B) Doesn't check that the objects resolve to ZType (not sure if we wanna do that) |
279 | // So either we remove it completely, or we fix B. |
280 | if ( !( |
281 | // Mostly we expect direct references to ZTypes (but we don't check it's a type) |
282 | $listType instanceof ZReference || |
283 | // … sometimes it's a ZFunctionCall to make a ZType (but we don't check it's a type) |
284 | $listType instanceof ZFunctionCall || |
285 | // … occasionally it's an inline ZType (or a dereferenced one) |
286 | $listType instanceof ZType |
287 | ) ) { |
288 | throw new ZErrorException( |
289 | ZErrorFactory::createZErrorInstance( |
290 | ZErrorTypeRegistry::Z_ERROR_WRONG_LIST_TYPE, |
291 | [ |
292 | 'data' => $rawListType |
293 | ] |
294 | ) |
295 | ); |
296 | } |
297 | |
298 | $items = []; |
299 | |
300 | foreach ( $object as $index => $value ) { |
301 | try { |
302 | $item = self::createChild( $value ); |
303 | } catch ( ZErrorException $e ) { |
304 | // We increment the index to point at the correct array item |
305 | // because we removed the first element by doing array_shift |
306 | $arrayIndex = $index + 1; |
307 | throw new ZErrorException( |
308 | ZErrorFactory::createArrayElementZError( (string)( $arrayIndex ), $e->getZError() ) |
309 | ); |
310 | } |
311 | $items[] = $item; |
312 | } |
313 | |
314 | return new ZTypedList( ZTypedList::buildType( $listType ), $items ); |
315 | } |
316 | |
317 | if ( !is_object( $object ) ) { |
318 | throw new ZErrorException( |
319 | ZErrorFactory::createZErrorInstance( |
320 | ZErrorTypeRegistry::Z_ERROR_INVALID_FORMAT, |
321 | [ |
322 | 'data' => $object |
323 | ] |
324 | ) |
325 | ); |
326 | } |
327 | |
328 | $typeRegistry = ZTypeRegistry::singleton(); |
329 | $typeZObject = self::extractObjectType( $object ); |
330 | $typeZid = $typeZObject->getZValue(); |
331 | $objectVars = get_object_vars( $object ); |
332 | |
333 | // If typeZObject is a ZReference and a built-in, build args and call constructor |
334 | if ( ( $typeZObject instanceof ZReference ) && ( $typeRegistry->isZTypeBuiltIn( $typeZid ) ) ) { |
335 | $typeName = $typeRegistry->getZObjectTypeFromKey( $typeZid ); |
336 | $typeClass = "MediaWiki\\Extension\\WikiLambda\\ZObjects\\$typeName"; |
337 | $objectArgs = self::createKeyValues( $objectVars, $typeName ); |
338 | // Magic: |
339 | return new $typeClass( ...$objectArgs ); |
340 | } |
341 | |
342 | // If typeZObject is a ZFunctionCall and a built-in, build args and call constructor |
343 | if ( ( $typeZObject instanceof ZFunctionCall ) && ( $typeRegistry->isZFunctionBuiltIn( $typeZid ) ) ) { |
344 | $builtinName = $typeRegistry->getZFunctionBuiltInName( $typeZid ); |
345 | $builtinClass = "MediaWiki\\Extension\\WikiLambda\\ZObjects\\$builtinName"; |
346 | $objectArgs = self::createKeyValues( $objectVars, $builtinName ); |
347 | // Magic: |
348 | return new $builtinClass( $typeZObject, ...$objectArgs ); |
349 | } |
350 | |
351 | // No built-in type or function call, so we build a generic ZObject instance: |
352 | // * typeZid is a user-defined type Zid or function Zid, so: |
353 | // * we check that it exists in the wiki |
354 | // * we call the ZObject constructor |
355 | $targetTitle = Title::newFromText( $typeZid, NS_MAIN ); |
356 | if ( !$targetTitle->exists() ) { |
357 | throw new ZErrorException( |
358 | ZErrorFactory::createZErrorInstance( |
359 | ZErrorTypeRegistry::Z_ERROR_ZID_NOT_FOUND, |
360 | [ |
361 | 'data' => $typeZid |
362 | ] |
363 | ) |
364 | ); |
365 | } |
366 | $zObjectStore = WikiLambdaServices::getZObjectStore(); |
367 | $targetObject = $zObjectStore->fetchZObjectByTitle( $targetTitle ); |
368 | if ( !$targetObject ) { |
369 | throw new ZErrorException( |
370 | ZErrorFactory::createZErrorInstance( |
371 | ZErrorTypeRegistry::Z_ERROR_ZID_NOT_FOUND, |
372 | [ |
373 | 'data' => $typeZid |
374 | ] |
375 | ) |
376 | ); |
377 | } |
378 | |
379 | $objectArgs = self::createKeyValues( $objectVars, "ZObject" ); |
380 | return new ZObject( ...$objectArgs ); |
381 | } |
382 | |
383 | /** |
384 | * This method takes an input and a built-in ZObject type name and returns the |
385 | * required arguments to call the ZObject constructur |
386 | * |
387 | * @param array $objectVars |
388 | * @param string $targetType |
389 | * @return array arguments to pass to the target ZObject constructor |
390 | * @phan-return non-empty-array |
391 | */ |
392 | private static function createKeyValues( array $objectVars, string $targetType ) { |
393 | // Magic |
394 | $targetDefinition = call_user_func( |
395 | 'MediaWiki\Extension\WikiLambda\ZObjects\\' . $targetType . '::getDefinition' |
396 | ); |
397 | |
398 | $creationArray = []; |
399 | foreach ( $targetDefinition['keys'] as $key => $settings ) { |
400 | if ( array_key_exists( $key, $objectVars ) ) { |
401 | if ( in_array( $key, ZTypeRegistry::TERMINAL_KEYS ) ) { |
402 | // Return the value if it belongs to a terminal key (Z6K1 or Z9K1) |
403 | $creationArray[] = $objectVars[ $key ]; |
404 | } else { |
405 | // Build the value of a given key to create nested ZObjects |
406 | // If it fails, throw a Z526/Key value error |
407 | try { |
408 | $creationArray[] = self::createChild( $objectVars[ $key ] ); |
409 | } catch ( ZErrorException $e ) { |
410 | throw new ZErrorException( ZErrorFactory::createKeyValueZError( $key, $e->getZError() ) ); |
411 | } |
412 | } |
413 | |
414 | // Remove every definition key from the objectVars, so that we can pass |
415 | // them as additional parameters if the definition specifies so. |
416 | unset( $objectVars[ $key ] ); |
417 | |
418 | } else { |
419 | // If it doesn't exist in $objectVars, we pass null |
420 | $creationArray[] = null; |
421 | if ( array_key_exists( 'required', $settings ) && ( $settings['required'] ) ) { |
422 | // Error Z511/Missing key |
423 | throw new ZErrorException( |
424 | ZErrorFactory::createZErrorInstance( |
425 | ZErrorTypeRegistry::Z_ERROR_MISSING_KEY, |
426 | [ |
427 | 'data' => $objectVars, |
428 | 'keywordArgs' => [ 'missing' => $key ] |
429 | ] |
430 | ) |
431 | ); |
432 | } |
433 | } |
434 | } |
435 | |
436 | // If ZObject Definition include the parameter 'additionalKeys', |
437 | // pass the leftover objectVars as the last argument (ignore Z1K1) |
438 | if ( array_key_exists( 'additionalKeys', $targetDefinition ) && $targetDefinition[ 'additionalKeys' ] ) { |
439 | $args = []; |
440 | foreach ( $objectVars as $key => $value ) { |
441 | if ( $key === ZTypeRegistry::Z_OBJECT_TYPE ) { |
442 | continue; |
443 | } |
444 | try { |
445 | $args[ $key ] = self::createChild( $value ); |
446 | } catch ( ZErrorException $e ) { |
447 | throw new ZErrorException( ZErrorFactory::createKeyValueZError( $key, $e->getZError() ) ); |
448 | } |
449 | } |
450 | $creationArray[] = $args; |
451 | } |
452 | |
453 | return $creationArray; |
454 | } |
455 | |
456 | /** |
457 | * Returns the inner ZObject of a given ZPersistentObject representation, which |
458 | * corresponds to is value key (Z2K2) |
459 | * |
460 | * @param \stdClass $object |
461 | * @return \stdClass|array|string |
462 | * @throws ZErrorException |
463 | */ |
464 | private static function extractInnerObject( $object ) { |
465 | if ( !property_exists( $object, ZTypeRegistry::Z_PERSISTENTOBJECT_VALUE ) ) { |
466 | throw new ZErrorException( |
467 | ZErrorFactory::createZErrorInstance( |
468 | ZErrorTypeRegistry::Z_ERROR_MISSING_KEY, |
469 | [ |
470 | 'data' => $object, |
471 | 'keywordArgs' => [ 'missing' => ZTypeRegistry::Z_PERSISTENTOBJECT_VALUE ] |
472 | ] |
473 | ) |
474 | ); |
475 | } |
476 | return $object->{ ZTypeRegistry::Z_PERSISTENTOBJECT_VALUE }; |
477 | } |
478 | |
479 | /** |
480 | * Get a given ZObject's type, irrespective of it being in canonical or in normal form |
481 | * |
482 | * @param \stdClass|array|string $object |
483 | * @return ZReference|ZFunctionCall Object type represented by a reference or a function call |
484 | * @throws ZErrorException |
485 | */ |
486 | private static function extractObjectType( $object ) { |
487 | // Check for canonical strings and references |
488 | if ( is_string( $object ) ) { |
489 | if ( ZObjectUtils::isValidOrNullZObjectReference( $object ) ) { |
490 | // returns ZReference |
491 | return new ZReference( ZTypeRegistry::Z_REFERENCE ); |
492 | } |
493 | // returns ZReference |
494 | return new ZReference( ZTypeRegistry::Z_STRING ); |
495 | } |
496 | |
497 | // Check for canonical arrays |
498 | if ( is_array( $object ) ) { |
499 | // TODO (T298126): We should probably infer the type of ZObjects contained in |
500 | // this array instead of just creating an untyped list of Z1s |
501 | return new ZFunctionCall( |
502 | new ZReference( ZTypeRegistry::Z_FUNCTION_TYPED_LIST ), |
503 | [ ZTypeRegistry::Z_FUNCTION_TYPED_LIST_TYPE => ZTypeRegistry::Z_OBJECT_TYPE ] |
504 | ); |
505 | } |
506 | |
507 | // Error invalid type |
508 | if ( !is_object( $object ) ) { |
509 | throw new ZErrorException( |
510 | ZErrorFactory::createZErrorInstance( |
511 | ZErrorTypeRegistry::Z_ERROR_INVALID_FORMAT, |
512 | [ |
513 | 'data' => $object |
514 | ] |
515 | ) |
516 | ); |
517 | } |
518 | |
519 | // Error key Z1K1 does not exist |
520 | if ( !property_exists( $object, ZTypeRegistry::Z_OBJECT_TYPE ) ) { |
521 | throw new ZErrorException( |
522 | ZErrorFactory::createZErrorInstance( |
523 | ZErrorTypeRegistry::Z_ERROR_MISSING_TYPE, |
524 | [ |
525 | 'data' => $object |
526 | ] |
527 | ) |
528 | ); |
529 | } |
530 | |
531 | // Value of Z1K1 can be a string or an object, |
532 | // resulting on a ZReference or a ZFunctionCall |
533 | $objectType = $object->{ ZTypeRegistry::Z_OBJECT_TYPE }; |
534 | $type = self::createChild( $objectType ); |
535 | $typeRegistry = ZTypeRegistry::singleton(); |
536 | |
537 | // If it's a ZReference, it must point at an object of type Z4 |
538 | if ( $type instanceof ZReference ) { |
539 | $errorRegistry = ZErrorTypeRegistry::singleton(); |
540 | $typeZid = $type->getZValue(); |
541 | |
542 | // TODO (T298093): Remove this exception, we will not have ZReferences to |
543 | // ZErrorTypes here, but ZFunctionCalls to Z885. |
544 | // |
545 | // For now, if it's a reference to a ZErrorType (e.g. Z511), accept it |
546 | // as if it were a type. |
547 | if ( |
548 | !$typeRegistry->isZObjectKeyKnown( $typeZid ) && |
549 | $errorRegistry->instanceOfZErrorType( $typeZid ) |
550 | ) { |
551 | return $type; |
552 | } |
553 | |
554 | // Make sure that the reference is to a Z4 |
555 | if ( !$typeRegistry->isZObjectKeyKnown( $typeZid ) ) { |
556 | throw new ZErrorException( |
557 | ZErrorFactory::createZErrorInstance( |
558 | ZErrorTypeRegistry::Z_ERROR_UNKNOWN_REFERENCE, |
559 | [ |
560 | 'data' => $typeZid |
561 | ] |
562 | ) |
563 | ); |
564 | } |
565 | |
566 | // return ZReference |
567 | return $type; |
568 | } |
569 | |
570 | if ( $type instanceof ZFunctionCall ) { |
571 | // Only check return type for non built-in type functions, as we know |
572 | // that those have Z4 as their return type |
573 | if ( !$typeRegistry->isZFunctionBuiltIn( $type->getZValue() ) ) { |
574 | $returnType = $type->getReturnType(); |
575 | // Make sure that the function Zid exists |
576 | if ( $returnType === null ) { |
577 | throw new ZErrorException( |
578 | ZErrorFactory::createZErrorInstance( |
579 | ZErrorTypeRegistry::Z_ERROR_ZID_NOT_FOUND, |
580 | [ |
581 | 'data' => $type->getZValue() |
582 | ] |
583 | ) |
584 | ); |
585 | } |
586 | // Make sure that the return type of the function is Z4 or Z1 |
587 | if ( |
588 | ( $returnType !== ZTypeRegistry::Z_TYPE ) && |
589 | ( $returnType !== ZTypeRegistry::Z_OBJECT ) |
590 | ) { |
591 | throw new ZErrorException( |
592 | ZErrorFactory::createZErrorInstance( |
593 | ZErrorTypeRegistry::Z_ERROR_UNEXPECTED_ZTYPE, |
594 | [ |
595 | 'expected' => ZTypeRegistry::Z_TYPE, |
596 | 'used' => $type->getReturnType() |
597 | ] |
598 | ) |
599 | ); |
600 | } |
601 | } |
602 | // return ZFunctionCall |
603 | return $type; |
604 | } |
605 | |
606 | if ( $type instanceof ZType ) { |
607 | // return the content of the Type Identity field |
608 | return $type->getTypeId(); |
609 | } |
610 | |
611 | // Invalid type: Z1K1 contains something else than a ZReference or a ZFunctionCall |
612 | throw new ZErrorException( |
613 | ZErrorFactory::createZErrorInstance( |
614 | ZErrorTypeRegistry::Z_ERROR_REFERENCE_VALUE_INVALID, |
615 | [ |
616 | 'data' => $type->getZValue() |
617 | ] |
618 | ) |
619 | ); |
620 | } |
621 | |
622 | /** |
623 | * @const bool |
624 | */ |
625 | private const SET_SELF_ZID = 1; |
626 | private const UNSET_SELF_ZID = 2; |
627 | private const CHECK_SELF_ZID = 3; |
628 | |
629 | /** |
630 | * Tracks Zids that appear in the ZObject validation context, which might referenced again from |
631 | * another key of the same ZObject. Depending on the mode flag, it sets a newly observed Zid, |
632 | * unsets it or just checks its presence. |
633 | * |
634 | * @param string $zid |
635 | * @param int $mode |
636 | * @return bool |
637 | */ |
638 | private static function trackSelfReference( $zid, $mode = self::CHECK_SELF_ZID ): bool { |
639 | static $context = []; |
640 | $isObserved = array_key_exists( $zid, $context ); |
641 | |
642 | switch ( $mode ) { |
643 | case self::CHECK_SELF_ZID: |
644 | return $isObserved; |
645 | case self::SET_SELF_ZID: |
646 | $context[ $zid ] = true; |
647 | return $isObserved; |
648 | case self::UNSET_SELF_ZID: |
649 | unset( $context[ $zid ] ); |
650 | return $isObserved; |
651 | default: |
652 | return false; |
653 | } |
654 | } |
655 | } |