1 | <?php |
||
47 | abstract class AbstractPartAggregate extends AbstractPart implements PartAggregateInterface |
||
48 | { |
||
49 | /** |
||
50 | * Unbound occurrences |
||
51 | * |
||
52 | * @var int |
||
53 | */ |
||
54 | const UNBOUND = -1; |
||
55 | /** |
||
56 | * Subpart template |
||
57 | * |
||
58 | * @var array |
||
59 | */ |
||
60 | protected $template = array(); |
||
61 | /** |
||
62 | * Minimum occurrences |
||
63 | * |
||
64 | * @var int |
||
65 | */ |
||
66 | protected $minimumOccurrences = 1; |
||
67 | /** |
||
68 | * Maximum occurrences |
||
69 | * |
||
70 | * @var int |
||
71 | */ |
||
72 | protected $maximumOccurrences = 1; |
||
73 | /** |
||
74 | * Occurrences |
||
75 | * |
||
76 | * @var array |
||
77 | */ |
||
78 | protected $occurrences = []; |
||
79 | /** |
||
80 | * Current occurrence index |
||
81 | * |
||
82 | * @var int |
||
83 | */ |
||
84 | protected $occurrenceCurrent = 0; |
||
85 | /** |
||
86 | * Current occurrence iterator |
||
87 | * |
||
88 | * @var int |
||
89 | */ |
||
90 | protected $occurrenceIterator = 0; |
||
91 | |||
92 | /** |
||
93 | * Part constructor |
||
94 | * |
||
95 | * @param AbstractMultipartHydrator $hydrator |
||
96 | * @param array $template Subpart template |
||
97 | * @param array|int $minOccurrences Minimum occurrences |
||
98 | * @param int $maxOccurrences Maximum occurrences |
||
99 | */ |
||
100 | 32 | public function __construct( |
|
101 | AbstractMultipartHydrator $hydrator, |
||
102 | array $template, |
||
103 | $minOccurrences = 1, |
||
104 | $maxOccurrences = 1 |
||
105 | ) { |
||
106 | 32 | self::validateOccurrences($minOccurrences, $maxOccurrences); |
|
107 | 32 | $this->template = $template; |
|
108 | 32 | $this->minimumOccurrences = intval($minOccurrences); |
|
109 | 32 | $this->maximumOccurrences = intval($maxOccurrences); |
|
110 | |||
111 | 32 | parent::__construct($hydrator); |
|
112 | |||
113 | // Initialize the occurrences |
||
114 | 32 | $this->initializeOccurrences($this->minimumOccurrences); |
|
115 | 32 | } |
|
116 | |||
117 | /** |
||
118 | * Validate minimum / maximum occurrence numbers |
||
119 | * |
||
120 | * @param int $minOccurrences Minimum occurrences |
||
121 | * @param int $maxOccurrences Maximum occurrences |
||
122 | * @return void |
||
123 | * @throws InvalidArgumentException If the minimum occurrences are less than 1 |
||
124 | * @throws InvalidArgumentException If the maximum occurrences are not unbound and less than the minimum occurrences |
||
125 | */ |
||
126 | 40 | public static function validateOccurrences($minOccurrences, $maxOccurrences) |
|
127 | { |
||
128 | // Minimum occurrences |
||
129 | 40 | $minOccurrences = intval($minOccurrences); |
|
130 | 40 | if ($minOccurrences < 1) { |
|
131 | 1 | throw new InvalidArgumentException( |
|
132 | sprintf( |
||
133 | 1 | 'Invalid part aggregate minimum occurrences "%s"', |
|
134 | $minOccurrences |
||
135 | ), |
||
136 | 1 | InvalidArgumentException::INVALID_MINIMUM_OCCURRENCES |
|
137 | ); |
||
138 | } |
||
139 | |||
140 | // Maximum occurrences |
||
141 | 39 | $maxOccurrences = intval($maxOccurrences); |
|
142 | 39 | if (($maxOccurrences < $minOccurrences) && ($maxOccurrences != self::UNBOUND)) { |
|
143 | 1 | throw new InvalidArgumentException( |
|
144 | sprintf( |
||
145 | 1 | 'Invalid part aggregate maximum occurrences "%s"', |
|
146 | $maxOccurrences |
||
147 | ), |
||
148 | 1 | InvalidArgumentException::INVALID_MAXIMUM_OCCURRENCES |
|
149 | ); |
||
150 | } |
||
151 | 38 | } |
|
152 | |||
153 | /** |
||
154 | * Initialize a particular number of occurrences |
||
155 | * |
||
156 | * @param int $occurrences Occurrences number |
||
157 | * @throws OutOfBoundsException If an invalid number of occurrences is specified |
||
158 | */ |
||
159 | 32 | protected function initializeOccurrences($occurrences) |
|
160 | { |
||
161 | // If the occurrences number is invalid |
||
162 | 32 | if (($occurrences < $this->minimumOccurrences) || |
|
163 | 32 | (($this->maximumOccurrences != self::UNBOUND) && ($occurrences > $this->maximumOccurrences)) |
|
164 | ) { |
||
165 | 1 | throw new OutOfBoundsException( |
|
166 | 1 | sprintf('Invalid occurrences number "%s"', $occurrences), |
|
167 | 1 | OutOfBoundsException::INVALID_OCCURRENCES_NUMBER |
|
168 | ); |
||
169 | } |
||
170 | |||
171 | // Initialize the particular number of occurrences |
||
172 | 32 | for ($occurrence = count($this->occurrences); $occurrence < $occurrences; ++$occurrence) { |
|
173 | 32 | $this->addOccurrence(); |
|
174 | } |
||
175 | 32 | } |
|
176 | |||
177 | /** |
||
178 | * Add an occurrence |
||
179 | * |
||
180 | * @return void |
||
181 | */ |
||
182 | abstract protected function addOccurrence(); |
||
183 | |||
184 | /** |
||
185 | * Serialize this file part |
||
186 | * |
||
187 | * @return string File part content |
||
188 | */ |
||
189 | 1 | public function __toString() |
|
190 | { |
||
191 | 1 | return $this->hydrator->dehydrate($this); |
|
192 | } |
||
193 | |||
194 | /** |
||
195 | * Return the media type of this part |
||
196 | * |
||
197 | * @return string Media type |
||
198 | */ |
||
199 | 1 | public function getMediaType() |
|
203 | |||
204 | /** |
||
205 | * Set the contents of a part |
||
206 | * |
||
207 | * @param mixed $data Contents |
||
208 | * @param array $subparts Subpart identifiers |
||
209 | * @return PartInterface Modified part |
||
210 | */ |
||
211 | 2 | public function set($data, array $subparts = []) |
|
212 | { |
||
213 | // If there are subparts: Delegate |
||
214 | 2 | if (count($subparts)) { |
|
215 | 2 | $occurrence = 0; |
|
216 | 2 | $part = ''; |
|
217 | 2 | $subpart = $this->get($subparts, $occurrence, $part)->set($data, []); |
|
218 | 2 | $this->occurrences[$occurrence][$part] = $subpart; |
|
219 | 2 | return $this; |
|
220 | } |
||
221 | |||
222 | 1 | return $this->hydrator->hydrate($data); |
|
223 | } |
||
224 | |||
225 | /** |
||
226 | * Return a nested subpart (or the part itself) |
||
227 | * |
||
228 | * @param array $subparts Subpart path identifiers |
||
229 | * @param int $occurrence Effective occurrence index |
||
230 | * @param string $part Effective part identifier |
||
231 | * @return PartInterface Nested subpart (or the part itself) |
||
232 | * @throws InvalidArgumentException If there are too few subpart identifiers given |
||
233 | * @throws InvalidArgumentException If the occurrence index is invalid |
||
234 | * @throws OutOfBoundsException If the occurrence index is out of bounds |
||
235 | */ |
||
236 | 13 | public function get(array $subparts = array(), &$occurrence = 0, &$part = '') |
|
237 | { |
||
238 | // If a subpart is requested |
||
239 | 13 | if (count($subparts)) { |
|
240 | 11 | $subpart = $this->getImmediateSubpart($subparts, $occurrence, $part); |
|
241 | 6 | return $subpart->get($subparts); |
|
242 | } |
||
243 | |||
244 | 4 | return $this; |
|
245 | } |
||
246 | |||
247 | /** |
||
248 | * Return an immediate subpart |
||
249 | * |
||
250 | * @param array $subparts Subpart path identifiers |
||
251 | * @param int $occurrence Effective occurrence index |
||
252 | * @param string $part Effective part identifier |
||
253 | * @return PartInterface Immediate subpart |
||
254 | * @throws InvalidArgumentException If there are too few subpart identifiers |
||
255 | * @throws InvalidArgumentException If the occurrence index is invalid |
||
256 | * @throws OutOfBoundsException If the occurrence index is out of bounds |
||
257 | * @throws InvalidArgumentException If the subpart identifier is unknown |
||
258 | * @throws InvalidArgumentException If the subpart does not exist |
||
259 | */ |
||
260 | 16 | protected function getImmediateSubpart(array &$subparts, &$occurrence = 0, &$part = '') |
|
261 | { |
||
262 | |||
263 | // Check if there are at least 2 subpart path identifiers available |
||
264 | 16 | if (count($subparts) < 2) { |
|
265 | 1 | throw new InvalidArgumentException( |
|
266 | sprintf( |
||
267 | 1 | 'Too few subpart identifiers ("%s")', |
|
268 | 1 | implode('/', $subparts) |
|
269 | ), |
||
270 | 1 | InvalidArgumentException::TOO_FEW_SUBPART_IDENTIFIERS |
|
271 | ); |
||
272 | } |
||
273 | |||
274 | // Validate the occurrence index |
||
275 | 15 | $occurrence = array_shift($subparts); |
|
276 | 15 | if ((strval(intval($occurrence)) != $occurrence)) { |
|
277 | 1 | throw new InvalidArgumentException( |
|
278 | 1 | sprintf('Invalid occurrence index "%s"', $occurrence), |
|
279 | 1 | InvalidArgumentException::INVALID_OCCURRENCE_INDEX |
|
280 | ); |
||
281 | } |
||
282 | |||
283 | // If the occurrence index is out of bounds |
||
284 | 14 | if ((intval($occurrence) < 0) || ($occurrence >= count($this->occurrences))) { |
|
285 | 1 | throw new OutOfBoundsException( |
|
286 | 1 | sprintf('Occurrence index "%s" out of bounds', $occurrence), |
|
287 | 1 | OutOfBoundsException::OCCURRENCE_INDEX_OUT_OF_BOUNDS |
|
288 | ); |
||
289 | } |
||
290 | |||
291 | // Validate the part identifier |
||
292 | 13 | $part = array_shift($subparts); |
|
293 | 13 | self::validatePartIdentifier($part); |
|
294 | |||
295 | // Test if the part identifier is known |
||
296 | 13 | if (!$this->isKnownPartIdentifier($occurrence, $part)) { |
|
297 | 1 | throw new InvalidArgumentException( |
|
298 | 1 | sprintf('Unknown part identifier "%s"', $part), |
|
299 | 1 | InvalidArgumentException::UNKNOWN_PART_IDENTIFIER |
|
300 | ); |
||
301 | } |
||
302 | |||
303 | // If the part is empty |
||
304 | 12 | $partInstance = $this->getOccurrencePart($occurrence, $part); |
|
305 | 12 | if (!($partInstance instanceof PartInterface)) { |
|
306 | 1 | throw new InvalidArgumentException( |
|
307 | 1 | sprintf('Part "%s" does not exist', $occurrence.'/'.$part), |
|
308 | 1 | InvalidArgumentException::PART_DOES_NOT_EXIST |
|
309 | ); |
||
310 | } |
||
311 | |||
312 | 11 | return $partInstance; |
|
313 | } |
||
314 | |||
315 | /** |
||
316 | * Test if a particular part identifier is known for a particular occurrence |
||
317 | * |
||
318 | * @param int $occurrence Occurrence index |
||
319 | * @param string $part Part identifier |
||
320 | * @return bool Is known part identifier |
||
321 | */ |
||
322 | 13 | protected function isKnownPartIdentifier($occurrence, $part) |
|
326 | |||
327 | /** |
||
328 | * Return a particular part of a particular occurrence |
||
329 | * |
||
330 | * @param int $occurrence Occurrence index |
||
331 | * @param string $part Part identifier |
||
332 | * @return PartInterface Part instance |
||
333 | */ |
||
334 | 12 | protected function getOccurrencePart(&$occurrence, &$part) |
|
338 | |||
339 | /** |
||
340 | * Delegate a method call to a subpart |
||
341 | * |
||
342 | * @param string $method Method nae |
||
343 | * @param array $subparts Subpart identifiers |
||
344 | * @param array $arguments Method arguments |
||
345 | * @return mixed Method result |
||
346 | */ |
||
347 | 7 | public function delegate($method, array $subparts, array $arguments) |
|
371 | |||
372 | /** |
||
373 | * Assign data to a particular part |
||
374 | * |
||
375 | * @param string $part Part identifier |
||
376 | * @param string $data Part data |
||
377 | * @param null|int $occurrence Occurrence to assign the part data to |
||
378 | */ |
||
379 | abstract public function assign($part, $data, $occurrence = null); |
||
380 | |||
381 | /******************************************************************************* |
||
382 | * STATIC METHODS |
||
383 | *******************************************************************************/ |
||
384 | |||
385 | /** |
||
386 | * Return the number of occurrences |
||
387 | * |
||
388 | * @return int Number of occurrences |
||
389 | */ |
||
390 | 2 | public function count() |
|
394 | |||
395 | /******************************************************************************* |
||
396 | * PRIVATE METHODS |
||
397 | *******************************************************************************/ |
||
398 | |||
399 | /** |
||
400 | * Return the current occurrence |
||
401 | * |
||
402 | * @return array Current occurrence |
||
403 | */ |
||
404 | 12 | public function current() |
|
408 | |||
409 | /** |
||
410 | * Increment the internal occurrence iterator |
||
411 | * |
||
412 | * @return void |
||
413 | */ |
||
414 | 5 | public function next() |
|
418 | |||
419 | /** |
||
420 | * Return the internal occurrence iterator |
||
421 | * |
||
422 | * @return int Internal occurrence iterator |
||
423 | */ |
||
424 | 1 | public function key() |
|
428 | |||
429 | /** |
||
430 | * Test if the current occurrence is valid |
||
431 | * |
||
432 | * @return boolean The current occurrence is valid |
||
433 | */ |
||
434 | 12 | public function valid() |
|
438 | |||
439 | /** |
||
440 | * Reset the internal occurrence iterator |
||
441 | * |
||
442 | * @return void |
||
443 | */ |
||
444 | 12 | public function rewind() |
|
448 | |||
449 | /** |
||
450 | * Prepare a part assignment |
||
451 | * |
||
452 | * @param string $part Part identifier |
||
453 | * @param null|int $occurrence Occurrence to assign the part data to |
||
454 | * @return int Occurrence index |
||
455 | * @throws InvalidArgumentException If the part identifier is invalid |
||
456 | */ |
||
457 | 32 | protected function prepareAssignment($part, $occurrence = null) |
|
478 | } |
||
479 |