CollectionSnapshot   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 110
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 7
Bugs 0 Features 5
Metric Value
wmc 16
c 7
b 0
f 5
lcom 1
cbo 4
dl 0
loc 110
rs 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 0 45 11
A snapshot() 0 8 3
A getOriginalKey() 0 7 2
1
<?php
2
/**
3
 * This file is part of the Totem package
4
 *
5
 * For the full copyright and license information, please view the LICENSE file
6
 * that was distributed with this source code.
7
 *
8
 * @copyright Baptiste Clavié <[email protected]>
9
 * @license   http://www.opensource.org/licenses/MIT-License MIT License
10
 */
11
12
namespace Totem\Snapshot;
13
14
use Traversable;
15
use ReflectionClass;
16
use InvalidArgumentException;
17
18
use Symfony\Component\PropertyAccess\PropertyPath;
19
use Symfony\Component\PropertyAccess\PropertyAccess;
20
21
use Totem\AbstractSnapshot;
22
23
/**
24
 * Represents a snapshot of a collection
25
 *
26
 * A collection is an array of numerical indexes of array elements
27
 *
28
 * BE CAREFUL, as this collection is _not_ recursive. Its elements will be
29
 * translated as either an ArraySnapshot, or an ObjectSnapshot if it fits, but
30
 * none of its child will be translated as a new CollectionSnapshot.
31
 *
32
 * @author Baptiste Clavié <[email protected]>
33
 */
34
class CollectionSnapshot extends AbstractSnapshot
35
{
36
    /**
37
     * Collection of the new keys (extracted from the primary key) => old keys
38
     *
39
     * @var integer[]
40
     */
41
    private $link = [];
42
43
    /**
44
     * Construct the snapshot
45
     *
46
     * The following options are taken into account :
47
     * - snapshotClass : snapshot class to use for each elements. If none are
48
     *                   given, the normalize() method will transform this into
49
     *                   either an ArraySnapshot or an ObjectSnapshot, depending
50
     *                   on the situation.
51
     *
52
     * @param mixed $data    Either an array or a traversable, data to take a snapshot of
53
     * @param mixed $primary Property path compatible value to use to get the primary key of each elements
54
     * @param array $options Array of options
55
     *
56
     * @throws InvalidArgumentException the $data is not an array or a Traversable
57
     * @throws InvalidArgumentException the $data is not an at least 2 dimensional array
58
     * @throws InvalidArgumentException the snapshotClass in the options is not loadable
59
     * @throws InvalidArgumentException the snapshotClass in the options is not a valid snapshot class
60
     * @throws InvalidArgumentException one of the elements of the collection does not have a $primary key
61
     */
62
    public function __construct($data, $primary, array $options = [])
63
    {
64
        $this->data = [];
65
        $this->raw  = $data;
66
67
        $primary = new PropertyPath($primary);
68
69
        $snapshot = null;
70
        $accessor = PropertyAccess::createPropertyAccessorBuilder()->enableExceptionOnInvalidIndex()->getPropertyAccessor();
71
72
        if (isset($options['snapshotClass'])) {
73
            if (!class_exists($options['snapshotClass'])) {
74
                throw new InvalidArgumentException(sprintf('The snapshot class "%s" does not seem to be loadable', $options['snapshotClass']));
75
            }
76
77
            $refl = new ReflectionClass($options['snapshotClass']);
78
79
            if (!$refl->isInstantiable() || !$refl->isSubclassOf('Totem\\AbstractSnapshot')) {
80
                throw new InvalidArgumentException('A Snapshot Class should be instantiable and extends abstract class Totem\\AbstractSnapshot');
81
            }
82
83
            $snapshot = $options['snapshotClass'];
84
        }
85
86
        if (!is_array($data) && !$data instanceof Traversable) {
87
            throw new InvalidArgumentException(sprintf('An array or a Traversable was expected to take a snapshot of a collection, "%s" given', is_object($data) ? get_class($data) : gettype($data)));
88
        }
89
90
        foreach ($data as $key => $value) {
91
            if (!is_int($key)) {
92
                throw new InvalidArgumentException('The given array / Traversable is not a collection as it contains non numeric keys');
93
            }
94
95
            if (!$accessor->isReadable($value, $primary)) {
96
                throw new InvalidArgumentException(sprintf('The key "%s" is not defined or readable in one of the elements of the collection', $primary));
97
            }
98
99
            $primaryKey = $accessor->getValue($value, $primary);
100
101
            $this->link[$primaryKey] = $key;
102
            $this->data[$primaryKey] = $this->snapshot($value, $snapshot);
103
        }
104
105
        parent::normalize();
106
    }
107
108
    /**
109
     * Snapshots a value
110
     *
111
     * If the value is already a snapshot, it won't be snapshotted ; otherwise,
112
     * if the class is null, then the value will be left as is.
113
     *
114
     * @param mixed  $value Value to snapshot
115
     * @param string $class Class to use to snapshot the value
116
     *
117
     * @return mixed A snapshot if the value was snapshotted, the original value otherwise
118
     */
119
    private function snapshot($value, $class = null)
120
    {
121
        if (null === $class || $value instanceof AbstractSnapshot) {
122
            return $value;
123
        }
124
125
        return new $class($value);
126
    }
127
128
    /**
129
     * Returns the original key for the primary key $primary
130
     *
131
     * @param mixed $primary Primary key to search
132
     *
133
     * @return integer original key
134
     * @throws InvalidArgumentException primary key not found
135
     */
136
    public function getOriginalKey($primary) {
137
        if (!isset($this->link[$primary])) {
138
            throw new InvalidArgumentException(sprintf('The primary key "%s" is not in the computed dataset', $primary));
139
        }
140
141
        return $this->link[$primary];
142
    }
143
}
144
145