StorableStruct::__construct()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 12
cts 12
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 4
nop 2
crap 3
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() !== null) {
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|null
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 === false && $this->getStorageKey() !== null) {
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 !== null) {
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() !== null) {
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 78
            default:
226 78
                return parent::lazyConfigInit($name);
227 78
        }
228
    }
229
230
    /**
231
     * Check that given value of configuration option is valid
232
     *
233
     * @param string $name Configuration option name
234
     * @param mixed $value Option value (passed by reference)
235
     * @throws \InvalidArgumentException
236
     * @return boolean
237
     */
238 78
    protected function validateConfig($name, &$value)
239
    {
240
        /** @noinspection DegradedSwitchInspection */
241
        switch ($name) {
242 78
            case 'storage':
243 78
                if (($value !== null) && (!$value instanceof StorageInterface)) {
244
                    throw new \InvalidArgumentException('Structure storage object must be instance of StorageInterface');
245
                }
246 78
                break;
247 78
            default:
248 78
                return parent::validateConfig($name, $value);
249 78
        }
250 78
        return true;
251
    }
252
253
    /**
254
     * {@inheritdoc}
255
     */
256 34
    protected function onConfigChange($name, $value)
257
    {
258
        switch ($name) {
259 34
            case 'storage':
260 1
                $this->storage = $value;
261 1
                break;
262 33
            case 'configuration':
263 33
            case 'metadata':
264
                // Since storage key depends on structure's metadata -
265
                // it should be reset upon metadata change
266 32
                $this->storageKey = null;
267 32
                break;
268 30
            case 'parent_structure':
269 29
                $this->parent = $value;
270 29
                break;
271
        }
272 34
        parent::onConfigChange($name, $value);
273 34
    }
274
}
275