Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
66.67% covered (warning)
66.67%
68 / 102
80.00% covered (warning)
80.00%
12 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
ZTypeRegistry
66.67% covered (warning)
66.67%
68 / 102
80.00% covered (warning)
80.00%
12 / 15
80.37
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
77.78% covered (warning)
77.78%
14 / 18
0.00% covered (danger)
0.00%
0 / 1
6.40
 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_HTML_FRAGMENT = 'Z89';
159    public const Z_HTML_FRAGMENT_VALUE = 'Z89K1';
160
161    public const Z_QUOTE = 'Z99';
162    public const Z_QUOTE_VALUE = 'Z99K1';
163
164    public const Z_ERRORTYPE = 'Z50';
165    public const Z_ERRORTYPE_KEYS = 'Z50K1';
166    public const Z_ERRORTYPE_ID = 'Z50K2';
167
168    // Wikidata Entity Types
169    public const Z_WIKIDATA_ITEM = 'Z6001';
170    public const Z_WIKIDATA_PROPERTY = 'Z6002';
171    public const Z_WIKIDATA_STATEMENT = 'Z6003';
172    public const Z_WIKIDATA_LEXEME_FORM = 'Z6004';
173    public const Z_WIKIDATA_LEXEME = 'Z6005';
174    public const Z_WIKIDATA_LEXEME_SENSE = 'Z6006';
175
176    public const WIKIDATA_TYPES = [
177        self::Z_WIKIDATA_ITEM,
178        self::Z_WIKIDATA_PROPERTY,
179        self::Z_WIKIDATA_STATEMENT,
180        self::Z_WIKIDATA_LEXEME,
181        self::Z_WIKIDATA_LEXEME_FORM,
182        self::Z_WIKIDATA_LEXEME_SENSE
183    ];
184
185    // Wikidata Reference Types:
186    public const Z_WIKIDATA_REFERENCE_ITEM = 'Z6091';
187    public const Z_WIKIDATA_REFERENCE_ITEM_ID = 'Z6091K1';
188    public const Z_WIKIDATA_REFERENCE_PROPERTY = 'Z6092';
189    public const Z_WIKIDATA_REFERENCE_PROPERTY_ID = 'Z6092K1';
190    public const Z_WIKIDATA_REFERENCE_LEXEME_FORM = 'Z6094';
191    public const Z_WIKIDATA_REFERENCE_LEXEME_FORM_ID = 'Z6094K1';
192    public const Z_WIKIDATA_REFERENCE_LEXEME = 'Z6095';
193    public const Z_WIKIDATA_REFERENCE_LEXEME_ID = 'Z6095K1';
194    public const Z_WIKIDATA_REFERENCE_LEXEME_SENSE = 'Z6096';
195    public const Z_WIKIDATA_REFERENCE_LEXEME_SENSE_ID = 'Z6096K1';
196
197    public const WIKIDATA_REFERENCE_TYPES = [
198        self::Z_WIKIDATA_REFERENCE_ITEM,
199        self::Z_WIKIDATA_REFERENCE_PROPERTY,
200        self::Z_WIKIDATA_REFERENCE_LEXEME,
201        self::Z_WIKIDATA_REFERENCE_LEXEME_FORM,
202        self::Z_WIKIDATA_REFERENCE_LEXEME_SENSE
203    ];
204
205    // Wikidata Entity Fetch Functions:
206    public const Z_WIKIDATA_FETCH_ITEM = 'Z6821';
207    public const Z_WIKIDATA_FETCH_ITEM_ID = 'Z6821K1';
208    public const Z_WIKIDATA_FETCH_PROPERTY = 'Z6822';
209    public const Z_WIKIDATA_FETCH_PROPERTY_ID = 'Z6822K1';
210    public const Z_WIKIDATA_FETCH_LEXEME_FORM = 'Z6824';
211    public const Z_WIKIDATA_FETCH_LEXEME_FORM_ID = 'Z6824K1';
212    public const Z_WIKIDATA_FETCH_LEXEME = 'Z6825';
213    public const Z_WIKIDATA_FETCH_LEXEME_ID = 'Z6825K1';
214    public const Z_WIKIDATA_FETCH_LEXEME_SENSE = 'Z6826';
215    public const Z_WIKIDATA_FETCH_LEXEME_SENSE_ID = 'Z6826K1';
216
217    // Wikidata enum
218    public const Z_WIKIDATA_ENUM = 'Z6884';
219
220    // Abstract Wiki
221    public const Z_RUN_ABSTRACT_FRAGMENT = 'Z825';
222    public const Z_RUN_ABSTRACT_FRAGMENT_QID = 'Z825K1';
223    public const Z_RUN_ABSTRACT_FRAGMENT_LANGUAGE = 'Z825K2';
224    public const Z_RUN_ABSTRACT_FRAGMENT_DATE = 'Z825K3';
225
226    public const Z_DATE_PARSER = 'Z20808';
227    public const Z_DATE_PARSER_STRING = 'Z20808K1';
228    public const Z_DATE_PARSER_LANGUAGE = 'Z20808K2';
229
230    /**
231     * DRY mapping for Wikidata entity types to their fetch function and reference key.
232     *
233     * @internal
234     */
235    public const WIKIDATA_ENTITY_TYPE_MAP = [
236        self::Z_WIKIDATA_ITEM => [
237            'fetch_function' => self::Z_WIKIDATA_FETCH_ITEM,
238            'fetch_key' => self::Z_WIKIDATA_FETCH_ITEM_ID,
239            'reference_type' => self::Z_WIKIDATA_REFERENCE_ITEM,
240            'reference_key' => self::Z_WIKIDATA_REFERENCE_ITEM_ID,
241        ],
242        self::Z_WIKIDATA_PROPERTY => [
243            'fetch_function' => self::Z_WIKIDATA_FETCH_PROPERTY,
244            'fetch_key' => self::Z_WIKIDATA_FETCH_PROPERTY_ID,
245            'reference_type' => self::Z_WIKIDATA_REFERENCE_PROPERTY,
246            'reference_key' => self::Z_WIKIDATA_REFERENCE_PROPERTY_ID,
247        ],
248        self::Z_WIKIDATA_LEXEME => [
249            'fetch_function' => self::Z_WIKIDATA_FETCH_LEXEME,
250            'fetch_key' => self::Z_WIKIDATA_FETCH_LEXEME_ID,
251            'reference_type' => self::Z_WIKIDATA_REFERENCE_LEXEME,
252            'reference_key' => self::Z_WIKIDATA_REFERENCE_LEXEME_ID,
253        ],
254        self::Z_WIKIDATA_LEXEME_FORM => [
255            'fetch_function' => self::Z_WIKIDATA_FETCH_LEXEME_FORM,
256            'fetch_key' => self::Z_WIKIDATA_FETCH_LEXEME_FORM_ID,
257            'reference_type' => self::Z_WIKIDATA_REFERENCE_LEXEME_FORM,
258            'reference_key' => self::Z_WIKIDATA_REFERENCE_LEXEME_FORM_ID,
259        ],
260    ];
261
262    /**
263     * Types that are considered parseable for Visual Editor integration.
264     * These types can be used as inputs in functions that will be discoverable
265     * in the Visual Editor menu.
266     *
267     * To add new parseable types, simply add them to this array.
268     */
269    public const PARSEABLE_INPUT_TYPES = [
270        self::Z_STRING,
271        self::Z_LANGUAGE,
272
273        // Wikidata types
274        self::Z_WIKIDATA_ITEM,
275        self::Z_WIKIDATA_LEXEME,
276        self::Z_WIKIDATA_REFERENCE_ITEM,
277        self::Z_WIKIDATA_REFERENCE_LEXEME,
278    ];
279
280    /**
281     * Types that are considered renderable for Visual Editor integration.
282     * These types can be used as outputs in functions that will be discoverable
283     * in the Visual Editor menu.
284     *
285     * To add new renderable types, simply add them to this array.
286     */
287    public const RENDERABLE_OUTPUT_TYPES = [
288        self::Z_STRING,
289        self::Z_HTML_FRAGMENT,
290    ];
291
292    // Keep in sync with function-schemata's `typesBuiltIntoWikiLambda`
293    private const BUILT_IN_TYPES = [
294        self::Z_OBJECT => 'ZObject',
295        self::Z_PERSISTENTOBJECT => 'ZPersistentObject',
296        self::Z_KEY => 'ZKey',
297        self::Z_ERROR => 'ZError',
298        self::Z_TYPE => 'ZType',
299        self::Z_STRING => 'ZString',
300        self::Z_FUNCTIONCALL => 'ZFunctionCall',
301        self::Z_FUNCTION => 'ZFunction',
302        self::Z_REFERENCE => 'ZReference',
303        self::Z_MONOLINGUALSTRING => 'ZMonoLingualString',
304        self::Z_MULTILINGUALSTRING => 'ZMultiLingualString',
305        self::Z_RESPONSEENVELOPE => 'ZResponseEnvelope',
306        self::Z_MONOLINGUALSTRINGSET => 'ZMonoLingualStringSet',
307        self::Z_MULTILINGUALSTRINGSET => 'ZMultiLingualStringSet',
308        self::Z_KEYREFERENCE => 'ZKeyReference',
309        self::Z_BOOLEAN => 'ZBoolean',
310        self::Z_LANGUAGE => 'ZNaturalLanguage',
311        self::Z_QUOTE => 'ZQuote',
312        self::Z_HTML_FRAGMENT => 'ZHTMLFragment',
313    ];
314
315    public const TERMINAL_KEYS = [
316        self::Z_STRING_VALUE,
317        self::Z_REFERENCE_VALUE,
318        self::Z_QUOTE_VALUE
319    ];
320
321    /**
322     * An array of ZTypes which are prohibited from creation by any user. (T278175)
323     */
324    public const DISALLOWED_ROOT_ZOBJECTS = [
325        self::Z_PERSISTENTOBJECT,
326        self::Z_KEY,
327        self::Z_REFERENCE,
328        self::Z_ARGUMENTDECLARATION,
329        self::Z_ARGUMENTREFERENCE,
330        self::Z_KEYREFERENCE,
331        self::Z_ERROR,
332        self::Z_CODE,
333        self::Z_ABSTRACTCONTENT,
334
335        // Wikidata types
336        ...self::WIKIDATA_TYPES,
337        ...self::WIKIDATA_REFERENCE_TYPES
338        // TODO (T309302): Uncomment when fixed Z24 insertion issue
339        // self::Z_UNIT,
340    ];
341
342    public const EXCLUDE_TYPES_FROM_ENUMS = [
343        self::Z_TYPE,
344        self::Z_ERRORTYPE,
345        self::Z_FUNCTION,
346        self::Z_DESERIALISER,
347        self::Z_SERIALISER
348    ];
349
350    public const IGNORE_KEY_VALUES_FOR_LABELLING = [
351        self::Z_QUOTE_VALUE,
352        self::Z_KEYREFERENCE_VALUE,
353        self::Z_KEY_ID,
354        self::Z_PERSISTENTOBJECT_ID,
355        self::Z_TYPE_IDENTITY,
356    ];
357
358    public const SELF_REFERENTIAL_KEYS = [
359        self::Z_TYPE_IDENTITY,
360        self::Z_PERSISTENTOBJECT_ID,
361        self::Z_FUNCTION_IDENTITY
362    ];
363
364    // These consts are currently only used by ZObjectStore to prohibit creation, and are not (yet) built-in.
365    public const Z_CODE = 'Z16';
366    public const Z_CODE_LANGUAGE = 'Z16K1';
367    public const Z_CODE_CODE = 'Z16K2';
368    public const Z_ARGUMENTREFERENCE = 'Z18';
369    public const Z_UNIT = 'Z21';
370    public const Z_NULL = 'Z23';
371    public const Z_ABSTRACTCONTENT = 'Z25';
372
373    // These are provided for ease of use
374    public const Z_VOID = 'Z24';
375    public const Z_VOID_INSTANCE = [ self::Z_OBJECT_TYPE => self::Z_UNIT ];
376
377    public const Z_BOOLEAN = 'Z40';
378    public const Z_BOOLEAN_VALUE = 'Z40K1';
379    public const Z_BOOLEAN_TRUE = 'Z41';
380    public const Z_BOOLEAN_FALSE = 'Z42';
381
382    public const IGNORE_KEY_NORMALIZATION = [
383        self::Z_OBJECT_TYPE,
384        self::Z_MONOLINGUALSTRING_LANGUAGE,
385        self::Z_MONOLINGUALSTRING_VALUE,
386        self::Z_MULTILINGUALSTRING_VALUE
387    ];
388
389    public const Z_FUNCTION_TYPED_LIST = 'Z881';
390    public const Z_FUNCTION_TYPED_LIST_TYPE = 'Z881K1';
391
392    public const Z_FUNCTION_TYPED_PAIR = 'Z882';
393    public const Z_FUNCTION_TYPED_FIRST_TYPE = 'Z882K1';
394    public const Z_FUNCTION_TYPED_SECOND_TYPE = 'Z882K2';
395
396    public const Z_FUNCTION_TYPED_MAP = 'Z883';
397    public const Z_FUNCTION_TYPED_MAP_KEY_TYPE = 'Z883K1';
398    public const Z_FUNCTION_TYPED_MAP_VALUE_TYPE = 'Z883K2';
399
400    public const Z_FUNCTION_ERRORTYPE_TO_TYPE = 'Z885';
401    public const Z_FUNCTION_ERRORTYPE_TYPE = 'Z885K1';
402
403    // These are built-in functions that return a type, so we will
404    // always find them as ZObject type values (Z1K1)
405    public const BUILT_IN_TYPE_FUNCTIONS = [
406        self::Z_FUNCTION_TYPED_LIST => 'ZTypedList',
407        self::Z_FUNCTION_TYPED_PAIR => 'ZTypedPair',
408        self::Z_FUNCTION_TYPED_MAP => 'ZTypedMap',
409        self::Z_FUNCTION_ERRORTYPE_TO_TYPE => 'ZTypedError'
410    ];
411
412    /**
413     * Initialize ZTypeRegistry
414     */
415    protected function initialize(): void {
416        // Registry for ZObjects of type ZType/Z4
417        $this->type = self::Z_TYPE;
418
419        foreach ( self::BUILT_IN_TYPES as $zKey => $classname ) {
420            $this->register( $zKey, $classname );
421        }
422    }
423
424    /**
425     * Registers the given ZType id and name in the type cache.
426     *
427     * @param string $key
428     * @param string $type
429     * @throws ZErrorException
430     */
431    public function register( string $key, string $type ): void {
432        if ( $this->isZObjectKeyCached( $key ) ) {
433            $conflictingType = $this->getZObjectKeyFromType( $key );
434            throw new ZErrorException(
435                ZErrorFactory::createZErrorInstance(
436                    ZErrorTypeRegistry::Z_ERROR_CONFLICTING_TYPE_NAMES,
437                    [
438                        'zid' => $key,
439                        'name' => $type,
440                        'existing' => $conflictingType
441                    ]
442                )
443            );
444        }
445
446        if ( $this->isZObjectTypeCached( $type ) ) {
447            $conflictingKey = $this->getZObjectTypeFromKey( $type );
448            throw new ZErrorException(
449                ZErrorFactory::createZErrorInstance(
450                    ZErrorTypeRegistry::Z_ERROR_CONFLICTING_TYPE_ZIDS,
451                    [
452                        'zid' => $key,
453                        'name' => $type,
454                        'existing' => $conflictingKey
455                    ]
456                )
457            );
458        }
459
460        if (
461            $this->isZTypeBuiltIn( $key )
462            && !class_exists( 'MediaWiki\Extension\WikiLambda\ZObjects\\' . self::BUILT_IN_TYPES[ $key ] )
463        ) {
464            throw new ZErrorException(
465                ZErrorFactory::createZErrorInstance(
466                    ZErrorTypeRegistry::Z_ERROR_CONFLICTING_TYPE_ZIDS,
467                    [
468                        'zid' => $key,
469                        'name' => $type
470                    ]
471                )
472            );
473        }
474
475        $this->registry[ $key ] = $type;
476    }
477
478    /**
479     * Removes the given ZType from the type cache, except for builtin types.
480     *
481     * @param string $key
482     */
483    public function unregister( string $key ): void {
484        if ( !array_key_exists( $key, self::BUILT_IN_TYPES ) ) {
485            unset( $this->registry[ $key ] );
486        }
487    }
488
489    /**
490     * Get the array of the keys of the ZTypes stored in the cache
491     *
492     * @return string[] Keys of the ZTypes stored in the cache
493     */
494    public function getCachedZObjectKeys(): array {
495        return array_keys( $this->registry );
496    }
497
498    /**
499     * Whether the provided ZType is 'built-in' to the WikiLambda extension, and thus its validator
500     * is provided in PHP code.
501     *
502     * @param string $key The key of the ZType to check.
503     * @return bool
504     */
505    public function isZTypeBuiltIn( string $key ): bool {
506        return array_key_exists( $key, self::BUILT_IN_TYPES );
507    }
508
509    /**
510     * Whether the provided ZFunction is 'built-in' to the WikiLambda extension, and thus its validator
511     * is provided in PHP code.
512     *
513     * @param string $key The key of the ZFunction to check.
514     * @return bool
515     */
516    public function isZFunctionBuiltIn( string $key ): bool {
517        return array_key_exists( $key, self::BUILT_IN_TYPE_FUNCTIONS );
518    }
519
520    /**
521     * Returns the class name given a built-in function Zid
522     *
523     * @param string $zid The zid of the built-in ZFunction
524     * @return ?string Class name for the built-in, or null if not known
525     */
526    public function getZFunctionBuiltInName( string $zid ): ?string {
527        return self::BUILT_IN_TYPE_FUNCTIONS[ $zid ] ?? null;
528    }
529
530    /**
531     * Whether the provided ZType is cached.
532     *
533     * @param string $key The key of the ZType to check
534     * @return bool
535     */
536    public function isZObjectKeyCached( string $key ): bool {
537        return in_array( $key, $this->getCachedZObjectKeys(), true );
538    }
539
540    /**
541     * Whether the provided ZType is known, either because it's a registered type
542     * or because it's persisted in the database. If the type is not yet cached but
543     * it's a valid type, it registers it.
544     *
545     * @param string $key The key of the ZType to check
546     * @return bool
547     */
548    public function isZObjectKeyKnown( string $key ): bool {
549        if ( $this->isZObjectKeyCached( $key ) ) {
550            return true;
551        }
552
553        $title = Title::newFromText( $key, NS_MAIN );
554
555        // TODO (T300530): This is quite expensive. Store this in a metadata DB table, instead of fetching it live?
556        $zObjectStore = WikiLambdaServices::getZObjectStore();
557        $content = $zObjectStore->fetchZObjectByTitle( $title );
558
559        if ( $content === false ) {
560            return false;
561        }
562
563        // (T374241, T375065) Check that the object is a type without running validation
564        // to avoid going into an infinite loop:
565        $zObject = $content->getObject();
566        $innerType = $zObject->{ self::Z_PERSISTENTOBJECT_VALUE }->{ self::Z_OBJECT_TYPE };
567
568        // check if it's Z_TYPE
569        if ( $innerType === self::Z_TYPE ) {
570            $this->register( $key, $key );
571            return true;
572        }
573
574        // check if it's a Z_FUNCTIONCALL to a Wikidata Enum generic type
575        if ( $innerType === self::Z_FUNCTIONCALL ) {
576            $functionZid = $zObject->{ self::Z_PERSISTENTOBJECT_VALUE }->{ self::Z_FUNCTIONCALL_FUNCTION };
577            if ( $functionZid === self::Z_WIKIDATA_ENUM ) {
578                $this->register( $key, $key );
579                return true;
580            }
581        }
582
583        return false;
584    }
585
586    /**
587     * Returns the ZType class name given its ZID
588     *
589     * @param string $key The key of the ZType to check
590     * @return string Class name for the ZType
591     * @throws ZErrorException
592     */
593    public function getZObjectTypeFromKey( string $key ): string {
594        if ( !$this->isZObjectKeyKnown( $key ) ) {
595            // Error Z504: Zid not found
596            throw new ZErrorException(
597                ZErrorFactory::createZErrorInstance(
598                    ZErrorTypeRegistry::Z_ERROR_ZID_NOT_FOUND,
599                    [ 'data' => $key ]
600                )
601            );
602        }
603        return $this->registry[ $key ];
604    }
605
606    /**
607     * Returns the array of names of the cached ZTypes.
608     *
609     * @return string[] Array of cached ZTypes
610     */
611    public function getCachedZObjectTypes(): array {
612        return array_values( $this->registry );
613    }
614
615    /**
616     * Whether the given ZType is saved in the cache.
617     *
618     * @param string $type Name of the ZType
619     * @return bool
620     */
621    public function isZObjectTypeCached( string $type ): bool {
622        return in_array( $type, $this->getCachedZObjectTypes(), true );
623    }
624
625    /**
626     * Whether the given ZType is known, either because it's saved in the cache or because
627     * it is persisted in the database.
628     *
629     * @param string $type Name of the ZType
630     * @return bool
631     */
632    public function isZObjectTypeKnown( string $type ): bool {
633        if ( $this->isZObjectTypeCached( $type ) ) {
634            return true;
635        }
636
637        // TODO (T300530): The registry is just a cache; also walk the DB to find if this type is registered.
638        return false;
639    }
640
641    /**
642     * Returns the ZType id of a given type.
643     *
644     * @param string $type Name of the ZType
645     * @return string ZID of the ZType
646     * @throws ZErrorException
647     */
648    public function getZObjectKeyFromType( string $type ): string {
649        if ( !$this->isZObjectTypeKnown( $type ) ) {
650            // Error Z543: ZType not found
651            throw new ZErrorException(
652                ZErrorFactory::createZErrorInstance(
653                    ZErrorTypeRegistry::Z_ERROR_ZTYPE_NOT_FOUND,
654                    [ 'type' => $type ]
655                )
656            );
657        }
658        return array_search( $type, $this->registry );
659    }
660
661    /**
662     * Checks if a given ZObject is an instance of a given type.
663     *
664     * @param ZObject $object
665     * @param string $type ZID of the type to test
666     * @return bool
667     * @throws ZErrorException
668     */
669    public function isZObjectInstanceOfType( ZObject $object, string $type ): bool {
670        if ( !$this->isZObjectKeyKnown( $type ) ) {
671            // Error Z504: ZID not found
672            throw new ZErrorException(
673                ZErrorFactory::createZErrorInstance(
674                    ZErrorTypeRegistry::Z_ERROR_ZID_NOT_FOUND,
675                    [ 'data' => $type ]
676                )
677            );
678        }
679
680        if ( $type === self::Z_OBJECT || $type === $object->getZType() ) {
681            return true;
682        }
683
684        if ( !( $object instanceof ZReference ) ) {
685            return false;
686        }
687
688        if ( !ZObjectUtils::isValidZObjectReference( $object->getZValue() ) ) {
689            return false;
690        }
691
692        $zObjectStore = WikiLambdaServices::getZObjectStore();
693        $objectTitle = Title::newFromText( $object->getZValue(), NS_MAIN );
694        $fetchedObject = $zObjectStore->fetchZObjectByTitle( $objectTitle );
695
696        return $fetchedObject && $type === $fetchedObject->getZType();
697    }
698}