Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
69.07% covered (warning)
69.07%
67 / 97
86.67% covered (warning)
86.67%
13 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
ZTypeRegistry
69.07% covered (warning)
69.07%
67 / 97
86.67% covered (warning)
86.67%
13 / 15
65.22
0.00% covered (danger)
0.00%
0 / 1
 initialize
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 register
19.44% covered (danger)
19.44%
7 / 36
0.00% covered (danger)
0.00%
0 / 1
18.07
 unregister
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getCachedZObjectKeys
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isZTypeBuiltIn
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isZFunctionBuiltIn
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getZFunctionBuiltInName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isZObjectKeyCached
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isZObjectKeyKnown
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 getZObjectTypeFromKey
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 getCachedZObjectTypes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isZObjectTypeCached
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isZObjectTypeKnown
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getZObjectKeyFromType
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 isZObjectInstanceOfType
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
7.01
1<?php
2/**
3 * WikiLambda ZTypeRegistry
4 *
5 * @file
6 * @ingroup Extensions
7 * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt
8 * @license MIT
9 */
10
11namespace MediaWiki\Extension\WikiLambda\Registry;
12
13use MediaWiki\Extension\WikiLambda\WikiLambdaServices;
14use MediaWiki\Extension\WikiLambda\ZErrorException;
15use MediaWiki\Extension\WikiLambda\ZErrorFactory;
16use MediaWiki\Extension\WikiLambda\ZObjects\ZObject;
17use MediaWiki\Extension\WikiLambda\ZObjects\ZReference;
18use MediaWiki\Extension\WikiLambda\ZObjectUtils;
19use MediaWiki\Title\Title;
20
21/**
22 * A registry service for ZObject implementations.
23 */
24class ZTypeRegistry extends ZObjectRegistry {
25
26    // Builtin Types that need special treatment while ZObject creation:
27
28    // Needed for quote type, it can be anything
29    public const BUILTIN_ANY = 'Any';
30    // Is there a better way to represent this direct string (as opposed to a ZString?)
31    public const BUILTIN_STRING = 'String';
32    // Is there a better way to represent this direct array
33    public const BUILTIN_ARRAY = 'Array';
34    // Needed until we have a ZReference type
35    public const BUILTIN_REFERENCE = 'Reference';
36    // Needed until we have a better way to cut the Gordian Knot of Z0 references?
37    public const BUILTIN_REFERENCE_NULLABLE = 'NullableReference';
38    // Needed until we have sub-types
39    public const HACK_REFERENCE_TYPE = 'Reference(Type)';
40    // Needed until we have sub-types
41    public const HACK_REFERENCE_LANGUAGE = 'Reference(Language)';
42    // Needed until we have a ZLanguage type (or similar)
43    public const HACK_LANGUAGE = 'Language';
44    // Needed until we have sub-types
45    public const HACK_ARRAY_Z_KEY = 'Array(ZKey)';
46    // Needed until we have sub-types
47    public const HACK_ARRAY_Z_STRING = 'Array(ZString)';
48    // Needed until we have sub-types
49    public const HACK_ARRAY_Z_MONOLINGUALSTRING = 'Array(ZMonoLingualString)';
50    // Needed until we have sub-types
51    public const HACK_ARRAY_Z_MONOLINGUALSTRINGSET = 'Array(ZMonoLingualStringSet)';
52
53    public const Z_NULL_REFERENCE = 'Z0';
54
55    public const Z_OBJECT = 'Z1';
56    public const Z_OBJECT_TYPE = 'Z1K1';
57
58    public const Z_PERSISTENTOBJECT = 'Z2';
59    public const Z_PERSISTENTOBJECT_ID = 'Z2K1';
60    public const Z_PERSISTENTOBJECT_VALUE = 'Z2K2';
61    public const Z_PERSISTENTOBJECT_LABEL = 'Z2K3';
62    public const Z_PERSISTENTOBJECT_ALIASES = 'Z2K4';
63    public const Z_PERSISTENTOBJECT_DESCRIPTION = 'Z2K5';
64
65    public const Z_KEY = 'Z3';
66    public const Z_KEY_TYPE = 'Z3K1';
67    public const Z_KEY_ID = 'Z3K2';
68    public const Z_KEY_LABEL = 'Z3K3';
69    public const Z_KEY_IS_IDENTITY = 'Z3K4';
70
71    public const Z_TYPE = 'Z4';
72    public const Z_TYPE_IDENTITY = 'Z4K1';
73    public const Z_TYPE_KEYS = 'Z4K2';
74    public const Z_TYPE_VALIDATOR = 'Z4K3';
75    public const Z_TYPE_EQUALITY = 'Z4K4';
76    public const Z_TYPE_RENDERER = 'Z4K5';
77    public const Z_TYPE_PARSER = 'Z4K6';
78    public const Z_TYPE_DESERIALISERS = 'Z4K7';
79    public const Z_TYPE_SERIALISERS = 'Z4K8';
80
81    public const Z_ERROR = 'Z5';
82    public const Z_ERROR_TYPE = 'Z5K1';
83    public const Z_ERROR_VALUE = 'Z5K2';
84
85    public const Z_STRING = 'Z6';
86    public const Z_STRING_VALUE = 'Z6K1';
87
88    public const Z_FUNCTIONCALL = 'Z7';
89    public const Z_FUNCTIONCALL_FUNCTION = 'Z7K1';
90
91    public const Z_FUNCTION = 'Z8';
92    public const Z_FUNCTION_ARGUMENTS = 'Z8K1';
93    public const Z_FUNCTION_RETURN_TYPE = 'Z8K2';
94    public const Z_FUNCTION_TESTERS = 'Z8K3';
95    public const Z_FUNCTION_IMPLEMENTATIONS = 'Z8K4';
96    public const Z_FUNCTION_IDENTITY = 'Z8K5';
97
98    public const Z_REFERENCE = 'Z9';
99    public const Z_REFERENCE_VALUE = 'Z9K1';
100
101    public const Z_MONOLINGUALSTRING = 'Z11';
102    public const Z_MONOLINGUALSTRING_LANGUAGE = 'Z11K1';
103    public const Z_MONOLINGUALSTRING_VALUE = 'Z11K2';
104
105    public const Z_MULTILINGUALSTRING = 'Z12';
106    public const Z_MULTILINGUALSTRING_VALUE = 'Z12K1';
107
108    public const Z_IMPLEMENTATION = 'Z14';
109    public const Z_IMPLEMENTATION_FUNCTION = 'Z14K1';
110    public const Z_IMPLEMENTATION_COMPOSITION = 'Z14K2';
111    public const Z_IMPLEMENTATION_CODE = 'Z14K3';
112    public const Z_IMPLEMENTATION_BUILTIN = 'Z14K4';
113
114    public const Z_ARGUMENTDECLARATION = 'Z17';
115    public const Z_ARGUMENTDECLARATION_TYPE = 'Z17K1';
116    public const Z_ARGUMENTDECLARATION_ID = 'Z17K2';
117    public const Z_ARGUMENTDECLARATION_LABEL = 'Z17K3';
118
119    public const Z_TESTER = 'Z20';
120    public const Z_TESTER_FUNCTION = 'Z20K1';
121    public const Z_TESTER_CALL = 'Z20K2';
122    public const Z_TESTER_VALIDATION = 'Z20K3';
123
124    public const Z_RESPONSEENVELOPE = 'Z22';
125    public const Z_RESPONSEENVELOPE_VALUE = 'Z22K1';
126    public const Z_RESPONSEENVELOPE_METADATA = 'Z22K2';
127
128    public const Z_MONOLINGUALSTRINGSET = 'Z31';
129    public const Z_MONOLINGUALSTRINGSET_LANGUAGE = 'Z31K1';
130    public const Z_MONOLINGUALSTRINGSET_VALUE = 'Z31K2';
131
132    public const Z_MULTILINGUALSTRINGSET = 'Z32';
133    public const Z_MULTILINGUALSTRINGSET_VALUE = 'Z32K1';
134
135    public const Z_KEYREFERENCE = 'Z39';
136    public const Z_KEYREFERENCE_VALUE = 'Z39K1';
137
138    public const Z_DESERIALISER = 'Z46';
139    public const Z_DESERIALISER_IDENTITY = 'Z46K1';
140    public const Z_DESERIALISER_TYPE = 'Z46K2';
141    public const Z_DESERIALISER_LANGUAGE = 'Z46K3';
142    public const Z_DESERIALISER_TARGET = 'Z46K4';
143    public const Z_DESERIALISER_CODE = 'Z46K5';
144
145    public const Z_LANGUAGE = 'Z60';
146    public const Z_LANGUAGE_CODE = 'Z60K1';
147    public const Z_LANGUAGE_SECONDARYCODES = 'Z60K2';
148
149    public const Z_PROGRAMMINGLANGUAGE = 'Z61';
150
151    public const Z_SERIALISER = 'Z64';
152    public const Z_SERIALISER_IDENTITY = 'Z64K1';
153    public const Z_SERIALISER_TYPE = 'Z64K2';
154    public const Z_SERIALISER_LANGUAGE = 'Z64K3';
155    public const Z_SERIALISER_SOURCE = 'Z64K4';
156    public const Z_SERIALISER_CODE = 'Z64K5';
157
158    public const Z_QUOTE = 'Z99';
159    public const Z_QUOTE_VALUE = 'Z99K1';
160
161    public const Z_ERRORTYPE = 'Z50';
162    public const Z_ERRORTYPE_KEYS = 'Z50K1';
163
164    // Wikidata Entity Types
165    public const Z_WIKIDATA_ITEM = 'Z6001';
166    public const Z_WIKIDATA_PROPERTY = 'Z6002';
167    public const Z_WIKIDATA_STATEMENT = 'Z6003';
168    public const Z_WIKIDATA_LEXEME_FORM = 'Z6004';
169    public const Z_WIKIDATA_LEXEME = 'Z6005';
170    public const Z_WIKIDATA_LEXEME_SENSE = 'Z6006';
171
172    public const WIKIDATA_TYPES = [
173        self::Z_WIKIDATA_ITEM,
174        self::Z_WIKIDATA_PROPERTY,
175        self::Z_WIKIDATA_STATEMENT,
176        self::Z_WIKIDATA_LEXEME_FORM,
177        self::Z_WIKIDATA_LEXEME,
178        self::Z_WIKIDATA_LEXEME_SENSE
179    ];
180
181    // Wikidata Reference Types:
182    public const Z_WIKIDATA_REFERENCE_ITEM = 'Z6091';
183    public const Z_WIKIDATA_REFERENCE_PROPERTY = 'Z6092';
184    public const Z_WIKIDATA_REFERENCE_LEXEME_FORM = 'Z6094';
185    public const Z_WIKIDATA_REFERENCE_LEXEME = 'Z6095';
186    public const Z_WIKIDATA_REFERENCE_LEXEME_ID = 'Z6095K1';
187    public const Z_WIKIDATA_REFERENCE_LEXEME_SENSE = 'Z6096';
188
189    public const WIKIDATA_REFERENCE_TYPES = [
190        self::Z_WIKIDATA_REFERENCE_ITEM,
191        self::Z_WIKIDATA_REFERENCE_PROPERTY,
192        self::Z_WIKIDATA_REFERENCE_LEXEME_FORM,
193        self::Z_WIKIDATA_REFERENCE_LEXEME,
194        self::Z_WIKIDATA_REFERENCE_LEXEME_SENSE
195    ];
196
197    // Wikidata Entity Fetch Functions:
198    public const Z_WIKIDATA_FETCH_LEXEME_FORM = 'Z6824';
199    public const Z_WIKIDATA_FETCH_LEXEME_FORM_ID = 'Z6824K1';
200    public const Z_WIKIDATA_FETCH_LEXEME = 'Z6825';
201    public const Z_WIKIDATA_FETCH_LEXEME_ID = 'Z6825K1';
202
203    public const WIKIDATA_FETCH_FUNCTIONS = [
204        self::Z_WIKIDATA_FETCH_LEXEME_FORM,
205        self::Z_WIKIDATA_FETCH_LEXEME
206    ];
207
208    // Keep in sync with function-schemata's `typesBuiltIntoWikiLambda`
209    private const BUILT_IN_TYPES = [
210        self::Z_OBJECT => 'ZObject',
211        self::Z_PERSISTENTOBJECT => 'ZPersistentObject',
212        self::Z_KEY => 'ZKey',
213        self::Z_ERROR => 'ZError',
214        self::Z_TYPE => 'ZType',
215        self::Z_STRING => 'ZString',
216        self::Z_FUNCTIONCALL => 'ZFunctionCall',
217        self::Z_FUNCTION => 'ZFunction',
218        self::Z_REFERENCE => 'ZReference',
219        self::Z_MONOLINGUALSTRING => 'ZMonoLingualString',
220        self::Z_MULTILINGUALSTRING => 'ZMultiLingualString',
221        self::Z_RESPONSEENVELOPE => 'ZResponseEnvelope',
222        self::Z_MONOLINGUALSTRINGSET => 'ZMonoLingualStringSet',
223        self::Z_MULTILINGUALSTRINGSET => 'ZMultiLingualStringSet',
224        self::Z_KEYREFERENCE => 'ZKeyReference',
225        self::Z_BOOLEAN => 'ZBoolean',
226        self::Z_QUOTE => 'ZQuote',
227    ];
228
229    public const TERMINAL_KEYS = [
230        self::Z_STRING_VALUE,
231        self::Z_REFERENCE_VALUE,
232        self::Z_QUOTE_VALUE
233    ];
234
235    /**
236     * An array of ZTypes which are prohibited from creation by any user. (T278175)
237     */
238    public const DISALLOWED_ROOT_ZOBJECTS = [
239        self::Z_PERSISTENTOBJECT,
240        self::Z_KEY,
241        self::Z_REFERENCE,
242        self::Z_ARGUMENTDECLARATION,
243        self::Z_ARGUMENTREFERENCE,
244        self::Z_KEYREFERENCE,
245        self::Z_ERROR,
246        self::Z_CODE,
247        // Wikidata types
248        ...self::WIKIDATA_TYPES,
249        ...self::WIKIDATA_REFERENCE_TYPES
250        // TODO (T309302): Uncomment when fixed Z24 insertion issue
251        // self::Z_UNIT,
252    ];
253
254    public const EXCLUDE_TYPES_FROM_ENUMS = [
255        self::Z_TYPE,
256        self::Z_FUNCTION,
257        self::Z_DESERIALISER,
258        self::Z_SERIALISER
259    ];
260
261    public const IGNORE_KEY_VALUES_FOR_LABELLING = [
262        self::Z_QUOTE_VALUE,
263        self::Z_KEYREFERENCE_VALUE,
264        self::Z_KEY_ID,
265        self::Z_PERSISTENTOBJECT_ID,
266        self::Z_TYPE_IDENTITY,
267    ];
268
269    public const SELF_REFERENTIAL_KEYS = [
270        self::Z_TYPE_IDENTITY,
271        self::Z_PERSISTENTOBJECT_ID,
272        self::Z_FUNCTION_IDENTITY
273    ];
274
275    // These consts are currently only used by ZObjectStore to prohibit creation, and are not (yet) built-in.
276    public const Z_CODE = 'Z16';
277    public const Z_CODE_LANGUAGE = 'Z16K1';
278    public const Z_CODE_CODE = 'Z16K2';
279    public const Z_ARGUMENTREFERENCE = 'Z18';
280    public const Z_UNIT = 'Z21';
281    public const Z_NULL = 'Z23';
282
283    // These are provided for ease of use
284    public const Z_VOID = 'Z24';
285    public const Z_VOID_INSTANCE = [ self::Z_OBJECT_TYPE => self::Z_UNIT ];
286
287    public const Z_BOOLEAN = 'Z40';
288    public const Z_BOOLEAN_VALUE = 'Z40K1';
289    public const Z_BOOLEAN_TRUE = 'Z41';
290    public const Z_BOOLEAN_FALSE = 'Z42';
291
292    public const IGNORE_KEY_NORMALIZATION = [
293        self::Z_OBJECT_TYPE,
294        self::Z_MONOLINGUALSTRING_LANGUAGE,
295        self::Z_MONOLINGUALSTRING_VALUE,
296        self::Z_MULTILINGUALSTRING_VALUE
297    ];
298
299    public const Z_FUNCTION_TYPED_LIST = 'Z881';
300    public const Z_FUNCTION_TYPED_LIST_TYPE = 'Z881K1';
301
302    public const Z_FUNCTION_TYPED_PAIR = 'Z882';
303    public const Z_FUNCTION_TYPED_FIRST_TYPE = 'Z882K1';
304    public const Z_FUNCTION_TYPED_SECOND_TYPE = 'Z882K2';
305
306    public const Z_FUNCTION_TYPED_MAP = 'Z883';
307    public const Z_FUNCTION_TYPED_MAP_KEY_TYPE = 'Z883K1';
308    public const Z_FUNCTION_TYPED_MAP_VALUE_TYPE = 'Z883K2';
309
310    public const Z_FUNCTION_ERRORTYPE_TO_TYPE = 'Z885';
311    public const Z_FUNCTION_ERRORTYPE_TYPE = 'Z885K1';
312
313    // These are built-in functions that return a type, so we will
314    // always find them as ZObject type values (Z1K1)
315    public const BUILT_IN_TYPE_FUNCTIONS = [
316        self::Z_FUNCTION_TYPED_LIST => 'ZTypedList',
317        self::Z_FUNCTION_TYPED_PAIR => 'ZTypedPair',
318        self::Z_FUNCTION_TYPED_MAP => 'ZTypedMap',
319        self::Z_FUNCTION_ERRORTYPE_TO_TYPE => 'ZTypedError'
320    ];
321
322    /**
323     * Initialize ZTypeRegistry
324     */
325    protected function initialize(): void {
326        // Registry for ZObjects of type ZType/Z4
327        $this->type = self::Z_TYPE;
328
329        foreach ( self::BUILT_IN_TYPES as $zKey => $classname ) {
330            $this->register( $zKey, $classname );
331        }
332    }
333
334    /**
335     * Registers the given ZType id and name in the type cache.
336     *
337     * @param string $key
338     * @param string $type
339     * @throws ZErrorException
340     */
341    public function register( string $key, string $type ): void {
342        if ( $this->isZObjectKeyCached( $key ) ) {
343            $conflictingType = $this->getZObjectKeyFromType( $key );
344            throw new ZErrorException(
345                ZErrorFactory::createZErrorInstance(
346                    ZErrorTypeRegistry::Z_ERROR_CONFLICTING_TYPE_NAMES,
347                    [
348                        'zid' => $key,
349                        'name' => $type,
350                        'existing' => $conflictingType
351                    ]
352                )
353            );
354        }
355
356        if ( $this->isZObjectTypeCached( $type ) ) {
357            $conflictingKey = $this->getZObjectTypeFromKey( $type );
358            throw new ZErrorException(
359                ZErrorFactory::createZErrorInstance(
360                    ZErrorTypeRegistry::Z_ERROR_CONFLICTING_TYPE_ZIDS,
361                    [
362                        'zid' => $key,
363                        'name' => $type,
364                        'existing' => $conflictingKey
365                    ]
366                )
367            );
368        }
369
370        if (
371            $this->isZTypeBuiltIn( $key )
372            && !class_exists( 'MediaWiki\Extension\WikiLambda\ZObjects\\' . self::BUILT_IN_TYPES[ $key ] )
373        ) {
374            throw new ZErrorException(
375                ZErrorFactory::createZErrorInstance(
376                    ZErrorTypeRegistry::Z_ERROR_CONFLICTING_TYPE_ZIDS,
377                    [
378                        'zid' => $key,
379                        'name' => $type
380                    ]
381                )
382            );
383        }
384
385        $this->registry[ $key ] = $type;
386    }
387
388    /**
389     * Removes the given ZType from the type cache, except for builtin types.
390     *
391     * @param string $key
392     */
393    public function unregister( string $key ): void {
394        if ( !array_key_exists( $key, self::BUILT_IN_TYPES ) ) {
395            unset( $this->registry[ $key ] );
396        }
397    }
398
399    /**
400     * Get the array of the keys of the ZTypes stored in the cache
401     *
402     * @return string[] Keys of the ZTypes stored in the cache
403     */
404    public function getCachedZObjectKeys(): array {
405        return array_keys( $this->registry );
406    }
407
408    /**
409     * Whether the provided ZType is 'built-in' to the WikiLambda extension, and thus its validator
410     * is provided in PHP code.
411     *
412     * @param string $key The key of the ZType to check.
413     * @return bool
414     */
415    public function isZTypeBuiltIn( string $key ): bool {
416        return array_key_exists( $key, self::BUILT_IN_TYPES );
417    }
418
419    /**
420     * Whether the provided ZFunction is 'built-in' to the WikiLambda extension, and thus its validator
421     * is provided in PHP code.
422     *
423     * @param string $key The key of the ZFunction to check.
424     * @return bool
425     */
426    public function isZFunctionBuiltIn( string $key ): bool {
427        return array_key_exists( $key, self::BUILT_IN_TYPE_FUNCTIONS );
428    }
429
430    /**
431     * Returns the class name given a built-in function Zid
432     *
433     * @param string $zid The zid of the built-in ZFunction
434     * @return ?string Class name for the built-in, or null if not known
435     */
436    public function getZFunctionBuiltInName( string $zid ): ?string {
437        return self::BUILT_IN_TYPE_FUNCTIONS[ $zid ] ?? null;
438    }
439
440    /**
441     * Whether the provided ZType is cached.
442     *
443     * @param string $key The key of the ZType to check
444     * @return bool
445     */
446    public function isZObjectKeyCached( string $key ): bool {
447        return in_array( $key, $this->getCachedZObjectKeys(), true );
448    }
449
450    /**
451     * Whether the provided ZType is known, either because it's a registered type
452     * or because it's persisted in the database. If the type is not yet cached but
453     * it's a valid type, it registers it.
454     *
455     * @param string $key The key of the ZType to check
456     * @return bool
457     */
458    public function isZObjectKeyKnown( string $key ): bool {
459        if ( $this->isZObjectKeyCached( $key ) ) {
460            return true;
461        }
462
463        $title = Title::newFromText( $key, NS_MAIN );
464
465        // TODO (T300530): This is quite expensive. Store this in a metadata DB table, instead of fetching it live?
466        $zObjectStore = WikiLambdaServices::getZObjectStore();
467        $content = $zObjectStore->fetchZObjectByTitle( $title );
468
469        if ( $content === false ) {
470            return false;
471        }
472
473        // (T374241, T375065) Check that the object is a type without running validation
474        // to avoid going into an infinite loop:
475        $zObject = $content->getObject();
476        $innerType = $zObject->{ self::Z_PERSISTENTOBJECT_VALUE }->{ self::Z_OBJECT_TYPE };
477        if ( $innerType !== self::Z_TYPE ) {
478            return false;
479        }
480
481        // We just need to store the index in the registry for newly fetched objects
482        $this->register( $key, $key );
483
484        return true;
485    }
486
487    /**
488     * Returns the ZType class name given its ZID
489     *
490     * @param string $key The key of the ZType to check
491     * @return string Class name for the ZType
492     * @throws ZErrorException
493     */
494    public function getZObjectTypeFromKey( string $key ): string {
495        if ( !$this->isZObjectKeyKnown( $key ) ) {
496            // Error Z504: Zid not found
497            throw new ZErrorException(
498                ZErrorFactory::createZErrorInstance(
499                    ZErrorTypeRegistry::Z_ERROR_ZID_NOT_FOUND,
500                    [ 'data' => $key ]
501                )
502            );
503        }
504        return $this->registry[ $key ];
505    }
506
507    /**
508     * Returns the array of names of the cached ZTypes.
509     *
510     * @return string[] Array of cached ZTypes
511     */
512    public function getCachedZObjectTypes(): array {
513        return array_values( $this->registry );
514    }
515
516    /**
517     * Whether the given ZType is saved in the cache.
518     *
519     * @param string $type Name of the ZType
520     * @return bool
521     */
522    public function isZObjectTypeCached( string $type ): bool {
523        return in_array( $type, $this->getCachedZObjectTypes(), true );
524    }
525
526    /**
527     * Whether the given ZType is known, either because it's saved in the cache or because
528     * it is persisted in the database.
529     *
530     * @param string $type Name of the ZType
531     * @return bool
532     */
533    public function isZObjectTypeKnown( string $type ): bool {
534        if ( $this->isZObjectTypeCached( $type ) ) {
535            return true;
536        }
537
538        // TODO (T300530): The registry is just a cache; also walk the DB to find if this type is registered.
539        return false;
540    }
541
542    /**
543     * Returns the ZType id of a given type.
544     *
545     * @param string $type Name of the ZType
546     * @return string ZID of the ZType
547     * @throws ZErrorException
548     */
549    public function getZObjectKeyFromType( string $type ): string {
550        if ( !$this->isZObjectTypeKnown( $type ) ) {
551            // Error Z543: ZType not found
552            throw new ZErrorException(
553                ZErrorFactory::createZErrorInstance(
554                    ZErrorTypeRegistry::Z_ERROR_ZTYPE_NOT_FOUND,
555                    [ 'type' => $type ]
556                )
557            );
558        }
559        return array_search( $type, $this->registry );
560    }
561
562    /**
563     * Checks if a given ZObject is an instance of a given type.
564     *
565     * @param ZObject $object
566     * @param string $type ZID of the type to test
567     * @return bool
568     * @throws ZErrorException
569     */
570    public function isZObjectInstanceOfType( ZObject $object, string $type ): bool {
571        if ( !$this->isZObjectKeyKnown( $type ) ) {
572            // Error Z504: ZID not found
573            throw new ZErrorException(
574                ZErrorFactory::createZErrorInstance(
575                    ZErrorTypeRegistry::Z_ERROR_ZID_NOT_FOUND,
576                    [ 'data' => $type ]
577                )
578            );
579        }
580
581        if ( $type === self::Z_OBJECT || $type === $object->getZType() ) {
582            return true;
583        }
584
585        if ( !( $object instanceof ZReference ) ) {
586            return false;
587        }
588
589        if ( !ZObjectUtils::isValidZObjectReference( $object->getZValue() ) ) {
590            return false;
591        }
592
593        $zObjectStore = WikiLambdaServices::getZObjectStore();
594        $objectTitle = Title::newFromText( $object->getZValue(), NS_MAIN );
595        $fetchedObject = $zObjectStore->fetchZObjectByTitle( $objectTitle );
596
597        return $fetchedObject && $type === $fetchedObject->getZType();
598    }
599}