Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.47% covered (warning)
75.47%
283 / 375
14.29% covered (danger)
14.29%
1 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ZObjectFactory
75.47% covered (warning)
75.47%
283 / 375
14.29% covered (danger)
14.29%
1 / 7
118.76
0.00% covered (danger)
0.00%
0 / 1
 getLogger
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 createPersistentContent
80.23% covered (warning)
80.23%
69 / 86
0.00% covered (danger)
0.00%
0 / 1
11.93
 validatePersistentKeys
82.14% covered (warning)
82.14%
23 / 28
0.00% covered (danger)
0.00%
0 / 1
4.09
 create
77.36% covered (warning)
77.36%
82 / 106
0.00% covered (danger)
0.00%
0 / 1
21.76
 createKeyValues
86.05% covered (warning)
86.05%
37 / 43
0.00% covered (danger)
0.00%
0 / 1
12.39
 extractInnerObject
16.67% covered (danger)
16.67%
2 / 12
0.00% covered (danger)
0.00%
0 / 1
4.31
 extractObjectType
69.39% covered (warning)
69.39%
68 / 98
0.00% covered (danger)
0.00%
0 / 1
19.62
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
11namespace MediaWiki\Extension\WikiLambda;
12
13use MediaWiki\Extension\WikiLambda\Registry\ZErrorTypeRegistry;
14use MediaWiki\Extension\WikiLambda\Registry\ZTypeRegistry;
15use MediaWiki\Extension\WikiLambda\ZObjects\ZFunctionCall;
16use MediaWiki\Extension\WikiLambda\ZObjects\ZObject;
17use MediaWiki\Extension\WikiLambda\ZObjects\ZPersistentObject;
18use MediaWiki\Extension\WikiLambda\ZObjects\ZReference;
19use MediaWiki\Extension\WikiLambda\ZObjects\ZString;
20use MediaWiki\Extension\WikiLambda\ZObjects\ZType;
21use MediaWiki\Extension\WikiLambda\ZObjects\ZTypedList;
22use MediaWiki\Logger\LoggerFactory;
23use MediaWiki\Title\Title;
24use Psr\Log\LoggerInterface;
25
26class ZObjectFactory {
27
28    private static ?LoggerInterface $logger = null;
29
30    /**
31     * Because much of the uses of this class is static, we can't just create the logger in the
32     * constructor like we do elsewhere, so instead we have a static LoggerInterface instance.
33     */
34    private static function getLogger(): LoggerInterface {
35        self::$logger ??= LoggerFactory::getInstance( 'WikiLambda' );
36        return self::$logger;
37    }
38
39    /**
40     * Validates and creates a ZPersistentObject from the given input data.
41     * If the input already has the ZPersistentObject keys, it uses
42     * them to construct the wrapper object. If not, it builds a wrapper
43     * ZPersistentObject with empty values. The resulting ZObject will be
44     * structurally valid or well-formed.
45     *
46     * This method is the entrypoint from WikiLambda content object.
47     *
48     * TODO (T375065): This is probably unnecessary now. We never create an object
49     * just passing the inner object, so we should probably strip this feature.
50     *
51     * @param string|array|\stdClass $input The item to turn into a ZObject
52     * @return ZPersistentObject
53     * @throws ZErrorException
54     */
55    public static function createPersistentContent( $input ) {
56        // 1. Get ZObject type. If not present or invalid, throw a not wellformed error
57        try {
58            $inputTypeZObject = self::extractObjectType( $input );
59            $typeZid = $inputTypeZObject->getZValue();
60        } catch ( ZErrorException $e ) {
61            self::getLogger()->info(
62                __METHOD__ . ': Parsing input failed: {message}',
63                [ 'message' => $e->getMessage() ]
64            );
65            throw new ZErrorException(
66                ZErrorFactory::createValidationZError( $e->getZError() )
67            );
68        }
69
70        $object = $input;
71
72        // 2. If ZObject type is Z2/Z_PERSISTENT_OBJECT, get the inner object.
73        // If not present, throw a not wellformed error.
74        if ( $typeZid === ZTypeRegistry::Z_PERSISTENTOBJECT ) {
75            try {
76                $object = self::extractInnerObject( $input );
77            } catch ( ZErrorException $e ) {
78                self::getLogger()->info(
79                    __METHOD__ . ': type loading failed: {message}',
80                    [ 'message' => $e->getMessage() ]
81                );
82                throw new ZErrorException(
83                    ZErrorFactory::createValidationZError( $e->getZError() )
84                );
85            }
86            // Get type of the inner ZObject. If not present, throw a not wellformed error
87            try {
88                $innerTypeZObject = self::extractObjectType( $object );
89                $typeZid = $innerTypeZObject->getZValue();
90            } catch ( ZErrorException $e ) {
91                self::getLogger()->info(
92                    __METHOD__ . ': type parsing failed: {message}',
93                    [ 'message' => $e->getMessage() ]
94                );
95                throw new ZErrorException(
96                    ZErrorFactory::createValidationZError( $e->getZError() )
97                );
98            }
99        }
100
101        // 3. Make sure that the ZObject type is not one of the disallowed types
102        // to directly wrap in a ZPersistentObject
103        if ( in_array( $typeZid, ZTypeRegistry::DISALLOWED_ROOT_ZOBJECTS ) ) {
104            self::getLogger()->info(
105                __METHOD__ . ': Disallowed root ZObject type attempted: {typeZid}',
106                [ 'typeZid' => $typeZid ]
107            );
108            throw new ZErrorException(
109                ZErrorFactory::createZErrorInstance(
110                    ZErrorTypeRegistry::Z_ERROR_DISALLOWED_ROOT_ZOBJECT,
111                    [
112                        'data' => $object
113                    ]
114                )
115            );
116        }
117
118        // 4. Create ZPersistentObject wrapper
119        // 4.1. Extract persistent keys or assign empty values
120        $persistentId = null;
121        $persistentLabel = null;
122        $persistentAliases = null;
123        $persistentDescription = null;
124        if ( $inputTypeZObject->getZValue() === ZTypeRegistry::Z_PERSISTENTOBJECT ) {
125            // Check that required keys exist
126            try {
127                self::validatePersistentKeys( $input );
128            } catch ( ZErrorException $e ) {
129                self::getLogger()->info(
130                    __METHOD__ . ': validating key failed: {message}',
131                    [ 'message' => $e->getMessage() ]
132                );
133                throw new ZErrorException(
134                    ZErrorFactory::createValidationZError( $e->getZError() )
135                );
136            }
137            // Build the values
138            $persistentId = self::create( $input->{ ZTypeRegistry::Z_PERSISTENTOBJECT_ID } );
139            $persistentLabel = self::create( $input->{ ZTypeRegistry::Z_PERSISTENTOBJECT_LABEL } );
140
141            $persistentAliases = property_exists( $input, ZTypeRegistry::Z_PERSISTENTOBJECT_ALIASES )
142                ? self::create( $input->{ ZTypeRegistry::Z_PERSISTENTOBJECT_ALIASES } )
143                : null;
144
145            $persistentDescription = property_exists( $input,
146                ZTypeRegistry::Z_PERSISTENTOBJECT_DESCRIPTION )
147                ? self::create( $input->{ ZTypeRegistry::Z_PERSISTENTOBJECT_DESCRIPTION } )
148                : null;
149        }
150
151        // Build empty values if we are creating a new ZPersistentObject wrapper
152        // TODO (T362249): Looks like this case is never really used: contemplate removing it
153        $persistentId ??= self::create( ZTypeRegistry::Z_NULL_REFERENCE );
154        $persistentLabel ??= self::create( (object)[
155            ZTypeRegistry::Z_OBJECT_TYPE => ZTypeRegistry::Z_MULTILINGUALSTRING,
156            ZTypeRegistry::Z_MULTILINGUALSTRING_VALUE => [ ZTypeRegistry::Z_MONOLINGUALSTRING ]
157        ] );
158
159        // 4.2. Create and validate inner ZObject: can throw Z502/Not wellformed
160        $zObject = self::create( $object );
161
162        // 4.3. Construct ZPersistentObject()
163        $persistentObject = new ZPersistentObject( $persistentId, $zObject, $persistentLabel,
164            $persistentAliases, $persistentDescription );
165
166        // 4.4. Check validity, to make sure that ID, label and aliases have the right format
167        if ( !$persistentObject->isValid() ) {
168            self::getLogger()->info( __METHOD__ . ': ZPersistentObject keys failed validation' );
169            throw new ZErrorException(
170                // TODO (T300506): Detail persistent object-related errors
171                ZErrorFactory::createZErrorInstance(
172                    ZErrorTypeRegistry::Z_ERROR_UNKNOWN,
173                    [
174                        'message' => "ZPersistentObject not valid"
175                    ]
176                )
177            );
178        }
179
180        return $persistentObject;
181    }
182
183    /**
184     * Check that the required ZPersistentObject keys exists and, if they don't, raise
185     * Z511/Missing key errors
186     *
187     * @param string|array|\stdClass $input The item to check is a ZObject
188     * @return bool
189     * @throws ZErrorException
190     */
191    public static function validatePersistentKeys( $input ): bool {
192        // We have a different error for Z2K2 being missing vs. the others.
193        if ( !property_exists( $input, ZTypeRegistry::Z_PERSISTENTOBJECT_VALUE ) ) {
194            self::getLogger()->info( __METHOD__ . ': Input missing Z_PERSISTENTOBJECT_VALUE key' );
195            throw new ZErrorException( ZErrorFactory::createZErrorInstance(
196                ZErrorTypeRegistry::Z_ERROR_MISSING_PERSISTENT_VALUE,
197                [ 'data' => $input ],
198            ) );
199        }
200
201        $otherRequiredKeys = [
202            ZTypeRegistry::Z_PERSISTENTOBJECT_ID,
203            ZTypeRegistry::Z_PERSISTENTOBJECT_LABEL,
204            // Disabled for now, optional
205            // ZTypeRegistry::Z_PERSISTENTOBJECT_ALIASES,
206            // Disabled for now, optional
207            // ZTypeRegistry::Z_PERSISTENTOBJECT_DESCRIPTION,
208        ];
209
210        foreach ( $otherRequiredKeys as $_i => $requiredKey ) {
211            if ( !property_exists( $input, $requiredKey ) ) {
212                self::getLogger()->info(
213                    __METHOD__ . ': Input missing required key {key}',
214                    [ 'key' => $requiredKey ]
215                );
216                throw new ZErrorException( ZErrorFactory::createZErrorInstance(
217                    ZErrorTypeRegistry::Z_ERROR_MISSING_KEY,
218                    [
219                        'data' => $input,
220                        'keywordArgs' => [ 'missing' => $requiredKey ],
221                    ]
222                ) );
223            }
224        }
225
226        return true;
227    }
228
229    /**
230     * Creates an object of type ZObject from the given input. This method should only
231     * be called internally, either from the ZObjectFactory of from the ZObject
232     * constructors. ZObjects created using this method will not necessarily be
233     * structurally valid.
234     *
235     * @param string|array|ZObject|\stdClass $object The item to turn into a ZObject
236     * @return ZObject
237     * @throws ZErrorException
238     */
239    public static function create( $object ) {
240        if ( $object instanceof ZObject ) {
241            return $object;
242        }
243
244        if ( is_string( $object ) ) {
245            if ( ZObjectUtils::isValidZObjectReference( $object ) ) {
246                return new ZReference( $object );
247            } else {
248                return new ZString( $object );
249            }
250        }
251
252        if ( is_array( $object ) ) {
253            if ( count( $object ) === 0 ) {
254                self::getLogger()->info( __METHOD__ . ': Input array empty' );
255                throw new ZErrorException(
256                    ZErrorFactory::createZErrorInstance(
257                        ZErrorTypeRegistry::Z_ERROR_UNDEFINED_LIST_TYPE,
258                        [
259                            'data' => $object
260                        ]
261                    )
262                );
263            }
264
265            $rawListType = array_shift( $object );
266            $listType = self::create( $rawListType );
267
268            if ( !(
269                // Mostly we expect direct references to ZTypes (but we don't check it's a type)
270                $listType instanceof ZReference ||
271                // … sometimes it's a ZFunctionCall to make a ZType (but we don't check it's a type)
272                $listType instanceof ZFunctionCall ||
273                // … occasionally it's an inline ZType (or a dereferenced one)
274                $listType instanceof ZType
275            ) ) {
276                self::getLogger()->info(
277                    __METHOD__ . ': Input list type is not allowed: {rawListType}',
278                    [ 'rawListType' => $rawListType ]
279                );
280                throw new ZErrorException(
281                    ZErrorFactory::createZErrorInstance(
282                        ZErrorTypeRegistry::Z_ERROR_WRONG_LIST_TYPE,
283                        [
284                            'data' => $rawListType
285                        ]
286                    )
287                );
288            }
289
290            $items = [];
291
292            foreach ( $object as $index => $value ) {
293                try {
294                    $item = self::create( $value );
295                } catch ( ZErrorException $e ) {
296                    // We increment the index to point at the correct array item
297                    // because we removed the first element by doing array_shift
298                    $arrayIndex = $index + 1;
299                    self::getLogger()->info(
300                        __METHOD__ . ': Key {index} broken: {message}',
301                        [ 'index' => $arrayIndex, 'message' => $e->getMessage() ]
302                    );
303                    throw new ZErrorException(
304                        ZErrorFactory::createArrayElementZError( (string)( $arrayIndex ), $e->getZError() )
305                    );
306                }
307                $items[] = $item;
308            }
309
310            return new ZTypedList( ZTypedList::buildType( $listType ), $items );
311        }
312
313        if ( !is_object( $object ) ) {
314            self::getLogger()->info(
315                __METHOD__ . ': Input is not a valid ZObject (type: {type})',
316                [ 'type' => gettype( $object ) ]
317            );
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, $typeClass );
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, $builtinClass );
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            self::getLogger()->info(
358                __METHOD__ . ': User-defined type {typeZid} not found on wiki',
359                [ 'typeZid' => $typeZid ]
360            );
361            throw new ZErrorException(
362                ZErrorFactory::createZErrorInstance(
363                    ZErrorTypeRegistry::Z_ERROR_ZID_NOT_FOUND,
364                    [
365                        'data' => $typeZid
366                    ]
367                )
368            );
369        }
370        $zObjectStore = WikiLambdaServices::getZObjectStore();
371        $targetObject = $zObjectStore->fetchZObjectByTitle( $targetTitle );
372        if ( !$targetObject ) {
373            self::getLogger()->warning(
374                __METHOD__ . ': User-defined type {typeZid} found but could not be fetched from store',
375                [ 'typeZid' => $typeZid ]
376            );
377            throw new ZErrorException(
378                ZErrorFactory::createZErrorInstance(
379                    ZErrorTypeRegistry::Z_ERROR_ZID_NOT_FOUND,
380                    [
381                        'data' => $typeZid
382                    ]
383                )
384            );
385        }
386
387        $objectArgs = self::createKeyValues( $objectVars, ZObject::class );
388        return new ZObject( ...$objectArgs );
389    }
390
391    /**
392     * This method takes an input and a built-in ZObject type name and returns the
393     * required arguments to call the ZObject constructur
394     *
395     * @param array $objectVars
396     * @param class-string $targetClass
397     * @return array arguments to pass to the target ZObject constructor
398     * @phan-return non-empty-array
399     * @throws ZErrorException
400     */
401    private static function createKeyValues( array $objectVars, string $targetClass ) {
402        $targetDefinition = $targetClass::getDefinition();
403
404        $creationArray = [];
405        foreach ( $targetDefinition['keys'] as $key => $settings ) {
406            if ( array_key_exists( $key, $objectVars ) ) {
407                if ( in_array( $key, ZTypeRegistry::TERMINAL_KEYS ) ) {
408                    // Return the value if it belongs to a terminal key (Z6K1 or Z9K1)
409                    $creationArray[] = $objectVars[ $key ];
410                } else {
411                    // Build the value of a given key to create nested ZObjects
412                    // If it fails, throw a Z526/Key value error
413                    try {
414                        $creationArray[] = self::create( $objectVars[ $key ] );
415                    } catch ( ZErrorException $e ) {
416                        self::getLogger()->info(
417                            __METHOD__ . ': Key value {key} broken: {message}',
418                            [ 'key' => $key, 'message' => $e->getMessage() ]
419                        );
420                        throw new ZErrorException( ZErrorFactory::createKeyValueZError( $key, $e->getZError() ) );
421                    }
422                }
423
424                // Remove every definition key from the objectVars, so that we can pass
425                // them as additional parameters if the definition specifies so.
426                unset( $objectVars[ $key ] );
427
428            } else {
429                // If it doesn't exist in $objectVars, we pass null
430                $creationArray[] = null;
431                if ( array_key_exists( 'required', $settings ) && ( $settings['required'] ) ) {
432                    // Error Z511/Missing key
433                    self::getLogger()->info(
434                        __METHOD__ . ': Required key {key} missing',
435                        [ 'key' => $key ]
436                    );
437                    throw new ZErrorException(
438                        ZErrorFactory::createZErrorInstance(
439                            ZErrorTypeRegistry::Z_ERROR_MISSING_KEY,
440                            [
441                                'data' => $objectVars,
442                                'keywordArgs' => [ 'missing' => $key ]
443                            ]
444                        )
445                    );
446                }
447            }
448        }
449
450        // If ZObject Definition include the parameter 'additionalKeys',
451        // pass the leftover objectVars as the last argument (ignore Z1K1)
452        if ( array_key_exists( 'additionalKeys', $targetDefinition ) && $targetDefinition[ 'additionalKeys' ] ) {
453            $args = [];
454            foreach ( $objectVars as $key => $value ) {
455                if ( $key === ZTypeRegistry::Z_OBJECT_TYPE ) {
456                    continue;
457                }
458                try {
459                    $args[ $key ] = self::create( $value );
460                } catch ( ZErrorException $e ) {
461                    self::getLogger()->info(
462                        __METHOD__ . ': Additional key {key} broken: {message}',
463                        [ 'key' => $key, 'message' => $e->getMessage() ]
464                    );
465                    throw new ZErrorException( ZErrorFactory::createKeyValueZError( $key, $e->getZError() ) );
466                }
467            }
468            $creationArray[] = $args;
469        }
470
471        return $creationArray;
472    }
473
474    /**
475     * Returns the inner ZObject of a given ZPersistentObject representation, which
476     * corresponds to is value key (Z2K2)
477     *
478     * @param \stdClass $object
479     * @return \stdClass|array|string
480     * @throws ZErrorException
481     */
482    private static function extractInnerObject( $object ) {
483        if ( !property_exists( $object, ZTypeRegistry::Z_PERSISTENTOBJECT_VALUE ) ) {
484            self::getLogger()->info( __METHOD__ . ': Missing Z_PERSISTENTOBJECT_VALUE key' );
485            throw new ZErrorException(
486                ZErrorFactory::createZErrorInstance(
487                    ZErrorTypeRegistry::Z_ERROR_MISSING_KEY,
488                    [
489                        'data' => $object,
490                        'keywordArgs' => [ 'missing' => ZTypeRegistry::Z_PERSISTENTOBJECT_VALUE ]
491                    ]
492                )
493            );
494        }
495        return $object->{ ZTypeRegistry::Z_PERSISTENTOBJECT_VALUE };
496    }
497
498    /**
499     * Given a ZObject, return its type.
500     *
501     * It accepts both a canonical or a normal input.
502     *
503     * Throws errors if:
504     * * The object doesn't have a type/Z1K1
505     * * The object type is not a reference or a function call
506     * * The object type reference points at an unexisting object
507     * * The object type reference points at an object that's not a type
508     *
509     * @param \stdClass|array|string $object
510     * @return ZReference|ZFunctionCall Object type represented by a reference or a function call
511     * @throws ZErrorException
512     */
513    private static function extractObjectType( $object ) {
514        // Check for canonical strings and references
515        if ( is_string( $object ) ) {
516            if ( ZObjectUtils::isValidOrNullZObjectReference( $object ) ) {
517                // returns ZReference
518                return new ZReference( ZTypeRegistry::Z_REFERENCE );
519            }
520            // returns ZReference
521            return new ZReference( ZTypeRegistry::Z_STRING );
522        }
523
524        // Check for canonical arrays
525        if ( is_array( $object ) ) {
526            // TODO (T298126): We should probably infer the type of ZObjects contained in
527            // this array instead of just creating an untyped list of Z1s
528            return new ZFunctionCall(
529                new ZReference( ZTypeRegistry::Z_FUNCTION_TYPED_LIST ),
530                [ ZTypeRegistry::Z_FUNCTION_TYPED_LIST_TYPE => ZTypeRegistry::Z_OBJECT_TYPE ]
531            );
532        }
533
534        // Error invalid type
535        if ( !is_object( $object ) ) {
536            self::getLogger()->info(
537                __METHOD__ . ': Invalid type (got {type})',
538                [ 'type' => gettype( $object ) ]
539            );
540            throw new ZErrorException(
541                ZErrorFactory::createZErrorInstance(
542                    ZErrorTypeRegistry::Z_ERROR_INVALID_FORMAT,
543                    [
544                        'data' => $object
545                    ]
546                )
547            );
548        }
549
550        // Error key Z1K1 does not exist
551        if ( !property_exists( $object, ZTypeRegistry::Z_OBJECT_TYPE ) ) {
552            self::getLogger()->info( __METHOD__ . ': Input missing Z1K1 key' );
553            throw new ZErrorException(
554                ZErrorFactory::createZErrorInstance(
555                    ZErrorTypeRegistry::Z_ERROR_MISSING_TYPE,
556                    [
557                        'data' => $object
558                    ]
559                )
560            );
561        }
562
563        // Value of Z1K1 can be a string or an object,
564        // resulting on a ZReference or a ZFunctionCall
565        $objectType = $object->{ ZTypeRegistry::Z_OBJECT_TYPE };
566        $type = self::create( $objectType );
567        $typeRegistry = ZTypeRegistry::singleton();
568
569        // If it's a ZReference, it must point at an object of type Z4
570        if ( $type instanceof ZReference ) {
571            $errorRegistry = ZErrorTypeRegistry::singleton();
572            $typeZid = $type->getZValue();
573
574            // Make sure that the reference is to a Z4
575
576            // TODO (T375065): isZObjectKeyKnown fetches and validates types,
577            // this has the potential of going into infinite loops when creating
578            // error objects.
579            // We should have a more efficient way to check that a Zid belongs to
580            // a type, or a function (the most important cases for creation and
581            // function call, respectively), probably through secondaty tables.
582            // Notice that current labels table has this info but ONLY for those
583            // objects containing labels, which means that:
584            // * the process would not be fully trustworthy, or
585            // * we'd need to make sure that types and functions are stored with labels
586            if ( !$typeRegistry->isZObjectKeyKnown( $typeZid ) ) {
587                self::getLogger()->info(
588                    __METHOD__ . ': Reference type unknown: {typeZid}',
589                    [ 'typeZid' => $typeZid ]
590                );
591                throw new ZErrorException(
592                    ZErrorFactory::createZErrorInstance(
593                        ZErrorTypeRegistry::Z_ERROR_UNKNOWN_REFERENCE,
594                        [
595                            'data' => $typeZid
596                        ]
597                    )
598                );
599            }
600
601            // return ZReference
602            return $type;
603        }
604
605        if ( $type instanceof ZFunctionCall ) {
606            // Only check return type for non built-in type functions, as we know
607            // that those have Z4 as their return type
608            if ( !$typeRegistry->isZFunctionBuiltIn( $type->getZValue() ) ) {
609                $returnType = $type->getReturnType();
610                // Make sure that the function Zid exists
611                if ( $returnType === null ) {
612                    self::getLogger()->info(
613                        __METHOD__ . ': Function call return type for {zValue} not found',
614                        [ 'zValue' => $type->getZValue() ]
615                    );
616                    throw new ZErrorException(
617                        ZErrorFactory::createZErrorInstance(
618                            ZErrorTypeRegistry::Z_ERROR_ZID_NOT_FOUND,
619                            [
620                                'data' => $type->getZValue()
621                            ]
622                        )
623                    );
624                }
625                // Make sure that the return type of the function is Z4 or Z1
626                if (
627                    ( $returnType !== ZTypeRegistry::Z_TYPE ) &&
628                    ( $returnType !== ZTypeRegistry::Z_OBJECT )
629                ) {
630                    self::getLogger()->info(
631                        __METHOD__ . ': Function call return type is wrong: {returnType}',
632                        [ 'returnType' => $type->getReturnType() ]
633                    );
634                    throw new ZErrorException(
635                        ZErrorFactory::createZErrorInstance(
636                            ZErrorTypeRegistry::Z_ERROR_UNEXPECTED_ZTYPE,
637                            [
638                                'expected' => ZTypeRegistry::Z_TYPE,
639                                'used' => $type->getReturnType()
640                            ]
641                        )
642                    );
643                }
644            }
645            // return ZFunctionCall
646            return $type;
647        }
648
649        if ( $type instanceof ZType ) {
650            // return the content of the Type Identity field
651            return $type->getTypeId();
652        }
653
654        // Invalid type: Z1K1 contains something else than a ZReference or a ZFunctionCall
655        self::getLogger()->info(
656            __METHOD__ . ': Z1K1 is not a ZReference or ZFunctionCall (class: {class})',
657            [ 'class' => get_class( $type ) ]
658        );
659        throw new ZErrorException(
660            ZErrorFactory::createZErrorInstance(
661                ZErrorTypeRegistry::Z_ERROR_REFERENCE_VALUE_INVALID,
662                [
663                    'data' => $type->getZValue()
664                ]
665            )
666        );
667    }
668}