Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.40% covered (success)
97.40%
75 / 77
92.86% covered (success)
92.86%
13 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
ZTypedList
97.40% covered (success)
97.40%
75 / 77
92.86% covered (success)
92.86%
13 / 14
30
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 getDefinition
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
1
 buildType
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 isValid
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
6.40
 appendArray
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 appendZTypedList
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getSerialized
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getSerializedCanonical
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 getSerializedNormal
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 returnEmptyTypedList
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getAsArray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getElementType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isEmpty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 checkNewElementTypes
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * WikiLambda ZTypedList
4 *
5 * @file
6 * @ingroup Extensions
7 * @copyright 2020– WikiLambda team; see AUTHORS.txt
8 * @license MIT
9 */
10
11namespace MediaWiki\Extension\WikiLambda\ZObjects;
12
13use MediaWiki\Extension\WikiLambda\Registry\ZErrorTypeRegistry;
14use MediaWiki\Extension\WikiLambda\Registry\ZTypeRegistry;
15use MediaWiki\Extension\WikiLambda\ZErrorException;
16use MediaWiki\Extension\WikiLambda\ZErrorFactory;
17use MediaWiki\Extension\WikiLambda\ZObjectUtils;
18
19class ZTypedList extends ZObject {
20
21    /**
22     * Create a new ZTypedList instance given an array (canonical form)
23     * or an object with K1 (head) and K2 (tail)
24     *
25     * @param ZFunctionCall $functionCall
26     * @param array|ZObject|null $head
27     * @param ZTypedList|null $tail
28     */
29    public function __construct( $functionCall, $head = null, $tail = null ) {
30        $this->type = $functionCall;
31        if ( $head === null ) {
32            $this->data = [];
33        } elseif ( is_array( $head ) && ( $tail === null ) ) {
34            $this->data = $head;
35        } else {
36            $tailAsArray = ( $tail === null ) ? [] : $tail->getAsArray();
37            $this->data = array_merge( [ $head ], $tailAsArray );
38        }
39    }
40
41    /**
42     * @inheritDoc
43     */
44    public static function getDefinition(): array {
45        return [
46            'type' => [
47                'type' => ZTypeRegistry::Z_FUNCTIONCALL,
48                'value' => ZTypeRegistry::Z_FUNCTION_TYPED_LIST,
49            ],
50            'keys' => [
51                'K1' => [
52                    'type' => ZTypeRegistry::Z_OBJECT,
53                ],
54                'K2' => [
55                    'type' => ZTypeRegistry::Z_FUNCTION_TYPED_LIST,
56                ],
57            ],
58        ];
59    }
60
61    /**
62     * Build the function call that defines the type of this ZTypedList
63     *
64     * @param string|ZFunctionCall|ZReference $listType The ZID of the type of ZObjects this list contains as a string
65     *   or ZReference, or a ZFunctionCall returning a ZType
66     * @return ZFunctionCall
67     */
68    public static function buildType( $listType ): ZFunctionCall {
69        return new ZFunctionCall(
70            new ZReference( ZTypeRegistry::Z_FUNCTION_TYPED_LIST ),
71            [ ZTypeRegistry::Z_FUNCTION_TYPED_LIST_TYPE => $listType ]
72        );
73    }
74
75    /**
76     * @inheritDoc
77     */
78    public function isValid(): bool {
79        foreach ( $this->data as $key => $value ) {
80            if ( !( $value instanceof ZObject ) ) {
81                return false;
82            }
83            if ( !$value->isValid() ) {
84                return false;
85            }
86            if (
87                ( self::getElementType()->getZValue() !== ZTypeRegistry::Z_OBJECT )
88                && ( self::getElementType()->getZValue() !== $value->getZType() )
89            ) {
90                return false;
91            }
92        }
93        return true;
94    }
95
96    /**
97     * Add the new elements, given as an array of ZObjects,
98     * to the end of the list.
99     *
100     * @param array $newElements
101     * @param bool $checkTypes
102     */
103    public function appendArray( array $newElements, bool $checkTypes = true ) {
104        if ( $checkTypes ) {
105            $this->checkNewElementTypes( $newElements );
106        }
107        $this->data = array_merge( $this->data, $newElements );
108    }
109
110    /**
111     * Add the new elements, given as a ZTypedList,
112     * to the end of the list.
113     *
114     * @param ZTypedList $newElements
115     * @param bool $checkTypes
116     */
117    public function appendZTypedList( ZTypedList $newElements, bool $checkTypes = true ) {
118        $array = $newElements->getAsArray();
119        if ( $checkTypes ) {
120            $this->checkNewElementTypes( $array );
121        }
122        $this->data = array_merge( $this->data, $array );
123    }
124
125    /**
126     * @inheritDoc
127     */
128    public function getSerialized( $form = self::FORM_CANONICAL ) {
129        if ( $form === self::FORM_CANONICAL ) {
130            return self::getSerializedCanonical();
131        } else {
132            return self::getSerializedNormal( $this->data );
133        }
134    }
135
136    /**
137     * Convert this ZTypedList into its serialized canonical representation
138     *
139     * @return array
140     */
141    private function getSerializedCanonical() {
142        $typeObject = $this->type->getValueByKey(
143            ZTypeRegistry::Z_FUNCTION_TYPED_LIST_TYPE
144        );
145        // Note: In some occurences, this is a stdClass not a ZObject
146        $type = ( $typeObject instanceof ZObject ) ? $typeObject->getSerialized() : $typeObject;
147
148        $items = array_map( static function ( $value ) {
149            return $value->getSerialized();
150        }, $this->data );
151
152        return array_merge( [ $type ], $items );
153    }
154
155    /**
156     * Convert this ZTypedList into its serialized normal representation
157     *
158     * @param array $list
159     * @return \stdClass
160     */
161    private function getSerializedNormal( $list ) {
162        if ( count( $list ) === 0 ) {
163            return (object)self::returnEmptyTypedList( self::FORM_NORMAL );
164        }
165
166        $serialized = self::returnEmptyTypedList( self::FORM_NORMAL );
167        $serialized[ 'K1' ] = $list[0]->getSerialized( self::FORM_NORMAL );
168        $serialized[ 'K2' ] = self::getSerializedNormal( array_slice( $list, 1 ) ?? [] );
169        return (object)$serialized;
170    }
171
172    /**
173     * Return an empty ZTypedList
174     *
175     * @param int $form
176     * @return array
177     */
178    private function returnEmptyTypedList( $form ): array {
179        return [
180            ZTypeRegistry::Z_OBJECT_TYPE => $this->type->getSerialized( $form )
181        ];
182    }
183
184    /**
185     * Get the array of ZObjects represented by this list
186     *
187     * @return array
188     */
189    public function getAsArray(): array {
190        return $this->getZValue();
191    }
192
193    /**
194     * Returns the type of the elements of this ZTypedList
195     *
196     * @return ZObject The type of this ZObject
197     */
198    public function getElementType(): ZObject {
199        return $this->type->getValueByKey( ZTypeRegistry::Z_FUNCTION_TYPED_LIST_TYPE );
200    }
201
202    /**
203     * Returns true if it contains an empty list
204     *
205     * @return bool Whether it's an empty list
206     */
207    public function isEmpty(): bool {
208        return !count( $this->data );
209    }
210
211    /**
212     * Check the type of each array element for compatibility with the ZTypedList's element type.
213     *
214     * @param array $newElements
215     * @throws ZErrorException When an array element has an incompatible type
216     */
217    private function checkNewElementTypes( array $newElements ) {
218        $elementType = $this->getElementType();
219        foreach ( $newElements as $index => $element ) {
220            if ( !ZObjectUtils::isCompatibleType( $elementType, $element ) ) {
221                // This element is of an unacceptable type
222                throw new ZErrorException(
223                    ZErrorFactory::createZErrorInstance(
224                        ZErrorTypeRegistry::Z_ERROR_ARRAY_TYPE_MISMATCH,
225                        [
226                            'key' => $index,
227                            'expected' => $this->getElementType()->getZValue(),
228                            'data' => $element->getValueByKey( ZTypeRegistry::Z_OBJECT_TYPE ),
229                        ]
230                    )
231                );
232            }
233        }
234    }
235}