Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.37% covered (success)
97.37%
74 / 76
92.86% covered (success)
92.86%
13 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
ZTypedList
97.37% covered (success)
97.37%
74 / 76
92.86% covered (success)
92.86%
13 / 14
29
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%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 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 $listType The ZID of the type of ZObjects this list contains
65     * @return ZFunctionCall
66     */
67    public static function buildType( $listType ): ZFunctionCall {
68        return new ZFunctionCall(
69            new ZReference( ZTypeRegistry::Z_FUNCTION_TYPED_LIST ),
70            [ ZTypeRegistry::Z_FUNCTION_TYPED_LIST_TYPE => $listType ]
71        );
72    }
73
74    /**
75     * @inheritDoc
76     */
77    public function isValid(): bool {
78        foreach ( $this->data as $key => $value ) {
79            if ( !( $value instanceof ZObject ) ) {
80                return false;
81            }
82            if ( !$value->isValid() ) {
83                return false;
84            }
85            if (
86                ( self::getElementType()->getZValue() !== ZTypeRegistry::Z_OBJECT )
87                && ( self::getElementType()->getZValue() !== $value->getZType() )
88            ) {
89                return false;
90            }
91        }
92        return true;
93    }
94
95    /**
96     * Add the new elements, given as an array of ZObjects,
97     * to the end of the list.
98     *
99     * @param array $newElements
100     * @param bool $checkTypes
101     */
102    public function appendArray( array $newElements, bool $checkTypes = true ) {
103        if ( $checkTypes ) {
104            $this->checkNewElementTypes( $newElements );
105        }
106        $this->data = array_merge( $this->data, $newElements );
107    }
108
109    /**
110     * Add the new elements, given as a ZTypedList,
111     * to the end of the list.
112     *
113     * @param ZTypedList $newElements
114     * @param bool $checkTypes
115     */
116    public function appendZTypedList( ZTypedList $newElements, bool $checkTypes = true ) {
117        $array = $newElements->getAsArray();
118        if ( $checkTypes ) {
119            $this->checkNewElementTypes( $array );
120        }
121        $this->data = array_merge( $this->data, $array );
122    }
123
124    /**
125     * @inheritDoc
126     */
127    public function getSerialized( $form = self::FORM_CANONICAL ) {
128        if ( $form === self::FORM_CANONICAL ) {
129            return self::getSerializedCanonical();
130        } else {
131            return self::getSerializedNormal( $this->data );
132        }
133    }
134
135    /**
136     * Convert this ZTypedList into its serialized canonical representation
137     *
138     * @return array
139     */
140    private function getSerializedCanonical() {
141        $type = $this->type->getValueByKey(
142            ZTypeRegistry::Z_FUNCTION_TYPED_LIST_TYPE
143        )->getSerialized();
144
145        $items = array_map( static function ( $value ) {
146            return $value->getSerialized();
147        }, $this->data );
148
149        return array_merge( [ $type ], $items );
150    }
151
152    /**
153     * Convert this ZTypedList into its serialized normal representation
154     *
155     * @param array $list
156     * @return \stdClass
157     */
158    private function getSerializedNormal( $list ) {
159        if ( count( $list ) === 0 ) {
160            return (object)self::returnEmptyTypedList( self::FORM_NORMAL );
161        }
162
163        $serialized = self::returnEmptyTypedList( self::FORM_NORMAL );
164        $serialized[ 'K1' ] = $list[0]->getSerialized( self::FORM_NORMAL );
165        $serialized[ 'K2' ] = self::getSerializedNormal( array_slice( $list, 1 ) ?? [] );
166        return (object)$serialized;
167    }
168
169    /**
170     * Return an empty ZTypedList
171     *
172     * @param int $form
173     * @return array
174     */
175    private function returnEmptyTypedList( $form ): array {
176        return [
177            ZTypeRegistry::Z_OBJECT_TYPE => $this->type->getSerialized( $form )
178        ];
179    }
180
181    /**
182     * Get the array of ZObjects represented by this list
183     *
184     * @return array
185     */
186    public function getAsArray(): array {
187        return $this->getZValue();
188    }
189
190    /**
191     * Returns the type of the elements of this ZTypedList
192     *
193     * @return ZObject The type of this ZObject
194     */
195    public function getElementType(): ZObject {
196        return $this->type->getValueByKey( ZTypeRegistry::Z_FUNCTION_TYPED_LIST_TYPE );
197    }
198
199    /**
200     * Returns true if it contains an empty list
201     *
202     * @return bool Whether it's an empty list
203     */
204    public function isEmpty(): bool {
205        return !count( $this->data );
206    }
207
208    /**
209     * Check the type of each array element for compatibility with the ZTypedList's element type.
210     *
211     * @param array $newElements
212     * @throws ZErrorException When an array element has an incompatible type
213     */
214    private function checkNewElementTypes( array $newElements ) {
215        $elementType = $this->getElementType();
216        foreach ( $newElements as $index => $element ) {
217            if ( !ZObjectUtils::isCompatibleType( $elementType, $element ) ) {
218                // This element is of an unacceptable type
219                throw new ZErrorException(
220                    ZErrorFactory::createZErrorInstance(
221                        ZErrorTypeRegistry::Z_ERROR_ARRAY_TYPE_MISMATCH,
222                        [
223                            'key' => $index,
224                            'expected' => $this->getElementType()->getZValue(),
225                            'data' => $element->getValueByKey( ZTypeRegistry::Z_OBJECT_TYPE ),
226                        ]
227                    )
228                );
229            }
230        }
231    }
232}