Passed
Push — master ( 4cc6c2...176af1 )
by Alexander
04:16
created

StorableStruct   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 264
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 89.91%

Importance

Changes 0
Metric Value
wmc 37
lcom 1
cbo 5
dl 0
loc 264
ccs 98
cts 109
cp 0.8991
rs 8.6
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 3
A __clone() 0 12 3
A getStorageKey() 0 12 3
B getStorage() 0 22 5
A toStorage() 0 4 1
A updateNotify() 0 8 3
A getInitialContents() 0 17 4
A setInitialContents() 0 4 1
A createStruct() 0 9 2
A initConfig() 0 7 1
A lazyConfigInit() 0 14 2
A validateConfig() 0 15 4
B onConfigChange() 0 18 5
1
<?php
2
3
namespace Flying\Struct;
4
5
use Flying\Struct\Common\SimplePropertyInterface;
6
use Flying\Struct\Metadata\StructMetadata;
7
use Flying\Struct\Storage\StorableInterface;
8
use Flying\Struct\Storage\Storage;
9
use Flying\Struct\Storage\StorageInterface;
10
11
/**
12
 * Implementation of structure with ability to store its state into storage
13
 */
14
class StorableStruct extends Struct implements StorableInterface
15
{
16
    /**
17
     * TRUE if structure is already marked as "dirty" into storage
18
     *
19
     * @var boolean
20
     */
21
    protected $markedAsDirty = false;
22
    /**
23
     * Structures storage
24
     *
25
     * @var Storage
26
     */
27
    private $storage;
28
    /**
29
     * Storage key for this structure
30
     *
31
     * @var string
32
     */
33
    private $storageKey;
34
35
    /**
36
     * Class constructor
37
     *
38
     * @param array|object $contents OPTIONAL Contents to initialize structure with
39
     * @param array|object $config   OPTIONAL Configuration for this structure
40
     * @throws \Flying\Struct\Exception
41
     * @throws \RuntimeException
42
     */
43 78
    public function __construct($contents = null, $config = null)
44
    {
45
        // Structure should be initialized with its stored contents
46 78
        parent::__construct(null, $config);
47
        // No change notification is required during object construction
48 78
        $flag = $this->skipNotify;
49 78
        $this->skipNotify = true;
50 78
        if (is_object($contents)) {
51 5
            $contents = $this->convertToArray($contents);
52 5
        }
53 78
        if (is_array($contents)) {
54 9
            $this->set($contents);
55 9
        }
56 78
        $this->skipNotify = $flag;
57 78
    }
58
59 2
    public function __clone()
60
    {
61 2
        parent::__clone();
62
        // Register newly cloned object into storage
63 2
        if ($this->getStorageKey()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getStorageKey() of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
64 2
            $this->getStorage()->register($this);
65
            // If structure had some changes before cloning - its cloned version should also be marked as "dirty"
66 2
            if ($this->markedAsDirty) {
67
                $this->getStorage()->markAsDirty($this);
68
            }
69 2
        }
70 2
    }
71
72
    /**
73
     * Get key for this structure to use in structures storage
74
     *
75
     * @return string
76
     * @throws \Flying\Struct\Exception
77
     */
78 78
    public function getStorageKey()
79
    {
80
        // Child structures should not be stored separately
81 78
        if ($this->parent) {
82 29
            return null;
83
        }
84 78
        if (!$this->storageKey) {
85 78
            $class = str_replace('\\', '_', get_class($this));
86 78
            $this->storageKey = $class . '_' . $this->getMetadata()->getHash();
87 78
        }
88 78
        return $this->storageKey;
89
    }
90
91
    /**
92
     * Get storage container
93
     *
94
     * @return StorageInterface
95
     * @throws \Flying\Struct\Exception
96
     */
97 78
    protected function getStorage()
98
    {
99 78
        if (!$this->storage) {
100
            try {
101
                /** @var $storage StorageInterface */
102 77
                $storage = $this->getConfig('storage');
103 77
                if (!$storage instanceof StorageInterface) {
104
                    /** @var $configuration Configuration */
105
                    $configuration = $this->getConfig('configuration');
106
                    $storage = $configuration->getStorage();
107
                    /** @noinspection NotOptimalIfConditionsInspection */
108
                    if (!$storage instanceof StorageInterface) {
109
                        throw new Exception('No storage is available');
110
                    }
111
                }
112 77
            } catch (\RuntimeException $e) {
113
                throw new Exception('Failed to obtain storage');
114
            }
115 77
            $this->storage = $storage;
116 77
        }
117 78
        return $this->storage;
118
    }
119
120
    /**
121
     * Get object representation suitable to put into storage
122
     *
123
     * @return mixed
124
     */
125 2
    public function toStorage()
126
    {
127 2
        return $this->toArray();
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     * @throws \Flying\Struct\Exception
133
     */
134 30
    public function updateNotify(SimplePropertyInterface $property)
135
    {
136 30
        parent::updateNotify($property);
137 30
        if ((!$this->markedAsDirty) && $this->getStorageKey()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getStorageKey() of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
138 29
            $this->getStorage()->markAsDirty($this);
139 29
            $this->markedAsDirty = true;
140 29
        }
141 30
    }
142
143
    /**
144
     * Get initial structure contents
145
     *
146
     * @param string $name      OPTIONAL Structure property name to get contents of,
147
     *                          NULL to get all available contents
148
     * @return mixed
149
     * @throws \Flying\Struct\Exception
150
     */
151 78
    protected function getInitialContents($name = null)
152
    {
153 78
        if (!is_array($this->initialContents)) {
154
            // Initial contents for structure are taken from storage
155 78
            $contents = [];
156 78
            $key = $this->getStorageKey();
157 78
            if ($key) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $key of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
158
                // Storage key is only available for top-level structure so it may be missed
159 78
                $contents = $this->getStorage()->load($key);
160 78
                if (!is_array($contents)) {
161 78
                    $contents = [];
162 78
                }
163 78
            }
164 78
            $this->initialContents = $contents;
165 78
        }
166 78
        return parent::getInitialContents($name);
167
    }
168
169
    /**
170
     * Set initial structure contents
171
     *
172
     * @param array|object $contents
173
     * @return void
174
     */
175
    protected function setInitialContents($contents)
176
    {
177
        // Given initial contents should be ignored
178
    }
179
180
    /**
181
     * Create structure from given metadata information
182
     *
183
     * @param StructMetadata $metadata
184
     * @throws Exception
185
     * @return void
186
     * @throws \RuntimeException
187
     */
188 78
    protected function createStruct(StructMetadata $metadata = null)
189
    {
190 78
        parent::createStruct($metadata);
191
        // Register ourselves into storage, but only top-level structure should be stored
192 78
        if ($this->getStorageKey()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getStorageKey() of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
193 78
            $this->getStorage()->register($this);
194 78
            $this->markedAsDirty = false;
195 78
        }
196 78
    }
197
198
    /**
199
     * {@inheritdoc}
200
     * @throws \RuntimeException
201
     */
202 1
    protected function initConfig()
203
    {
204 1
        parent::initConfig();
205 1
        $this->mergeConfig([
206 1
            'storage' => null,
207 1
        ]);
208 1
    }
209
210
    /**
211
     * Perform "lazy initialization" of configuration option with given name
212
     *
213
     * @param string $name Configuration option name
214
     * @return mixed
215
     * @throws \RuntimeException
216
     */
217 78
    protected function lazyConfigInit($name)
218
    {
219
        /** @noinspection DegradedSwitchInspection */
220
        switch ($name) {
221 78
            case 'storage':
222
                /** @var $configuration Configuration */
223 77
                $configuration = $this->getConfig('configuration');
224 77
                return $configuration->getStorage();
225
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
226 78
            default:
227 78
                return parent::lazyConfigInit($name);
228
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
229 78
        }
230
    }
231
232
    /**
233
     * Check that given value of configuration option is valid
234
     *
235
     * @param string $name Configuration option name
236
     * @param mixed $value Option value (passed by reference)
237
     * @throws \InvalidArgumentException
238
     * @return boolean
239
     */
240 78
    protected function validateConfig($name, &$value)
241
    {
242
        /** @noinspection DegradedSwitchInspection */
243
        switch ($name) {
244 78
            case 'storage':
245 78
                if (($value !== null) && (!$value instanceof StorageInterface)) {
246
                    throw new \InvalidArgumentException('Structure storage object must be instance of StorageInterface');
247
                }
248 78
                break;
249 78
            default:
250 78
                return parent::validateConfig($name, $value);
251
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
252 78
        }
253 78
        return true;
254
    }
255
256
    /**
257
     * {@inheritdoc}
258
     */
259 34
    protected function onConfigChange($name, $value)
260
    {
261
        switch ($name) {
262 34
            case 'storage':
263 1
                $this->storage = $value;
264 1
                break;
265 33
            case 'configuration':
266 33
            case 'metadata':
267
                // Since storage key depends on structure's metadata -
268
                // it should be reset upon metadata change
269 32
                $this->storageKey = null;
270 32
                break;
271 30
            case 'parent_structure':
272 29
                $this->parent = $value;
273 29
                break;
274
        }
275 34
        parent::onConfigChange($name, $value);
276 34
    }
277
}
278