AbstractPartAggregate::get()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 3
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
ccs 5
cts 5
cp 1
crap 2
1
<?php
2
3
/**
4
 * apparat-resource
5
 *
6
 * @category    Apparat
7
 * @package     Apparat\Resource
8
 * @subpackage Apparat\Resource\Domain
9
 * @author      Joschi Kuphal <[email protected]> / @jkphl
10
 * @copyright   Copyright © 2016 Joschi Kuphal <[email protected]> / @jkphl
11
 * @license     http://opensource.org/licenses/MIT The MIT License (MIT)
12
 */
13
14
/***********************************************************************************
15
 *  The MIT License (MIT)
16
 *
17
 *  Copyright © 2016 Joschi Kuphal <[email protected]> / @jkphl
18
 *
19
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of
20
 *  this software and associated documentation files (the "Software"), to deal in
21
 *  the Software without restriction, including without limitation the rights to
22
 *  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
23
 *  the Software, and to permit persons to whom the Software is furnished to do so,
24
 *  subject to the following conditions:
25
 *
26
 *  The above copyright notice and this permission notice shall be included in all
27
 *  copies or substantial portions of the Software.
28
 *
29
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
31
 *  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
32
 *  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
33
 *  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
34
 *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35
 ***********************************************************************************/
36
37
namespace Apparat\Resource\Domain\Model\Part;
38
39
use Apparat\Resource\Domain\Model\Hydrator\AbstractMultipartHydrator;
40
41
/**
42
 * Abstract part aggregate
43
 *
44
 * @package     Apparat\Resource
45
 * @subpackage Apparat\Resource\Domain
46
 */
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()
200
    {
201 1
        return null;
202
    }
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)
323
    {
324 13
        return array_key_exists($part, $this->occurrences[$occurrence]);
325
    }
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)
335
    {
336 12
        return $this->occurrences[$occurrence][$part];
337
    }
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)
348
    {
349
        // If there are subpart identifiers: Delegate method call
350 7
        if (count($subparts)) {
351 5
            $occurrence = 0;
352 5
            $part = '';
353 5
            $subpart = $this->getImmediateSubpart($subparts, $occurrence, $part);
354 5
            $result = $subpart->delegate($method, $subparts, $arguments);
355
356
            // If it's a setter method
357 5
            if (!strncmp('set', $method, 3)) {
358
                // Exchange the modified part
359 1
                $this->occurrences[$occurrence][$part] = $result;
360
361
                // Return a self reference
362 1
                return $this;
363
            }
364
365
            // Return the method result
366 5
            return $result;
367
        }
368
369 2
        return parent::delegate($method, $subparts, $arguments);
370
    }
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()
391
    {
392 2
        return count($this->occurrences);
393
    }
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()
405
    {
406 12
        return $this->occurrences[$this->occurrenceIterator];
407
    }
408
409
    /**
410
     * Increment the internal occurrence iterator
411
     *
412
     * @return void
413
     */
414 5
    public function next()
415
    {
416 5
        ++$this->occurrenceIterator;
417 5
    }
418
419
    /**
420
     * Return the internal occurrence iterator
421
     *
422
     * @return int Internal occurrence iterator
423
     */
424 1
    public function key()
425
    {
426 1
        return $this->occurrenceIterator;
427
    }
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()
435
    {
436 12
        return isset($this->occurrences[$this->occurrenceIterator]);
437
    }
438
439
    /**
440
     * Reset the internal occurrence iterator
441
     *
442
     * @return void
443
     */
444 12
    public function rewind()
445
    {
446 12
        $this->occurrenceIterator = 0;
447 12
    }
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)
458
    {
459
460
        // If the part identifier is invalid
461 32
        if (!strlen($part) || !array_key_exists($part, $this->template)) {
462 1
            throw new InvalidArgumentException(
463 1
                sprintf('Invalid part identifier "%s"', $part),
464 1
                InvalidArgumentException::INVALID_PART_IDENTIFIER
465
            );
466
        }
467
468
        // Use the current occurrence if not specified
469 31
        if ($occurrence === null) {
470 10
            $occurrence = $this->occurrenceCurrent;
471
        }
472
473
        // Initialize the required number or occurrences
474 31
        $this->initializeOccurrences($occurrence + 1);
475
476 31
        return $occurrence;
477
    }
478
}
479