Completed
Push — master ( ce4cd5...ff0a6e )
by Joschi
03:16
created

AbstractObject::loadRevisionData()   F

Complexity

Conditions 11
Paths 1024

Size

Total Lines 50
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 12.7233

Importance

Changes 7
Bugs 0 Features 3
Metric Value
cc 11
eloc 32
c 7
b 0
f 3
nc 1024
nop 2
dl 0
loc 50
ccs 25
cts 33
cp 0.7576
crap 12.7233
rs 3.375

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * apparat-object
5
 *
6
 * @category    Apparat
7
 * @package     Apparat\Object
8
 * @subpackage  Apparat\Object\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\Object\Domain\Model\Object;
38
39
use Apparat\Kernel\Ports\Kernel;
40
use Apparat\Object\Domain\Model\Object\Traits\DomainPropertiesTrait;
41
use Apparat\Object\Domain\Model\Object\Traits\IterableTrait;
42
use Apparat\Object\Domain\Model\Object\Traits\MetaPropertiesTrait;
43
use Apparat\Object\Domain\Model\Object\Traits\PayloadTrait;
44
use Apparat\Object\Domain\Model\Object\Traits\ProcessingInstructionsTrait;
45
use Apparat\Object\Domain\Model\Object\Traits\RelationsTrait;
46
use Apparat\Object\Domain\Model\Object\Traits\StatesTrait;
47
use Apparat\Object\Domain\Model\Object\Traits\SystemPropertiesTrait;
48
use Apparat\Object\Domain\Model\Path\RepositoryPath;
49
use Apparat\Object\Domain\Model\Path\RepositoryPathInterface;
50
use Apparat\Object\Domain\Model\Properties\AbstractDomainProperties;
51
use Apparat\Object\Domain\Model\Properties\InvalidArgumentException as PropertyInvalidArgumentException;
52
use Apparat\Object\Domain\Model\Properties\MetaProperties;
53
use Apparat\Object\Domain\Model\Properties\ProcessingInstructions;
54
use Apparat\Object\Domain\Model\Properties\Relations;
55
use Apparat\Object\Domain\Model\Properties\SystemProperties;
56
use Apparat\Object\Domain\Repository\SelectorInterface;
57
use Apparat\Object\Domain\Repository\Service;
58
59
/**
60
 * Abstract object
61
 *
62
 * @package Apparat\Object
63
 * @subpackage Apparat\Object\Domain
64
 */
65
abstract class AbstractObject implements ObjectInterface, \Iterator, \Countable
66
{
67
    /**
68
     * Use traits
69
     */
70
    use SystemPropertiesTrait, MetaPropertiesTrait, DomainPropertiesTrait, RelationsTrait,
71
        ProcessingInstructionsTrait, PayloadTrait, IterableTrait, StatesTrait;
72
73
    /**
74
     * Repository path
75
     *
76
     * @var RepositoryPathInterface
77
     */
78
    protected $path;
79
    /**
80
     * Latest revision
81
     *
82
     * @var Revision
83
     */
84
    protected $latestRevision;
85
86
    /**
87
     * Object constructor
88
     *
89
     * @param RepositoryPathInterface $path Object repository path
90
     * @param string $payload Object payload
91
     * @param array $propertyData Property data
92
     */
93 30
    public function __construct(RepositoryPathInterface $path, $payload = '', array $propertyData = [])
94
    {
95
        // If the domain property collection class is invalid
96 30
        if (!$this->domainPropertyCClass
97 30
            || !class_exists($this->domainPropertyCClass)
98 30
            || !(new \ReflectionClass($this->domainPropertyCClass))->isSubclassOf(AbstractDomainProperties::class)
99 30
        ) {
100 1
            throw new PropertyInvalidArgumentException(
101 1
                sprintf(
102 1
                    'Invalid domain property collection class "%s"',
103 1
                    $this->domainPropertyCClass
104 1
                ),
105
                PropertyInvalidArgumentException::INVALID_DOMAIN_PROPERTY_COLLECTION_CLASS
106 1
            );
107
        }
108
109
        // Save the original object path
110 29
        $this->path = $path;
111
112
        // Load the current revision data
113 29
        $this->loadRevisionData($payload, $propertyData);
114
115
        // Determine the latest revision number (considering a possible draft)
116 4
        $this->latestRevision = $this->hasDraft()
117 4
            ? Kernel::create(Revision::class, [$this->getRevision()->getRevision() + 1, true])
118 4
            : $this->getRevision();
119
120
        // Update the object path
121 4
        $this->updatePath();
122 4
    }
123
124
    /**
125
     * Load object revision data
126
     *
127
     * @param string $payload Object payload
128
     * @param array $propertyData Property data
129
     */
130 29
    protected function loadRevisionData($payload = '', array $propertyData = [])
131
    {
132 29
        $this->payload = $payload;
133
134
        // Instantiate the system properties
135 29
        $systemPropertyData = (empty($propertyData[SystemProperties::COLLECTION]) ||
136 29
            !is_array(
137 29
                $propertyData[SystemProperties::COLLECTION]
138 29
            )) ? [] : $propertyData[SystemProperties::COLLECTION];
139 29
        $this->systemProperties = Kernel::create(SystemProperties::class, [$systemPropertyData, $this]);
140
141
        // Instantiate the meta properties
142 4
        $metaPropertyData = (empty($propertyData[MetaProperties::COLLECTION]) ||
143
            !is_array(
144
                $propertyData[MetaProperties::COLLECTION]
145 4
            )) ? [] : $propertyData[MetaProperties::COLLECTION];
146
        /** @var MetaProperties $metaProperties */
147 4
        $metaProperties = Kernel::create(MetaProperties::class, [$metaPropertyData, $this]);
148 4
        $this->setMetaProperties($metaProperties, true);
149
150
        // Instantiate the domain properties
151 4
        $domainPropertyData = (empty($propertyData[AbstractDomainProperties::COLLECTION]) ||
152
            !is_array(
153
                $propertyData[AbstractDomainProperties::COLLECTION]
154 4
            )) ? [] : $propertyData[AbstractDomainProperties::COLLECTION];
155
        /** @var AbstractDomainProperties $domainProperties */
156 4
        $domainProperties = Kernel::create($this->domainPropertyCClass, [$domainPropertyData, $this]);
157 4
        $this->setDomainProperties($domainProperties, true);
158
159
        // Instantiate the processing instructions
160 4
        $procInstData = (empty($propertyData[ProcessingInstructions::COLLECTION]) ||
161
            !is_array(
162
                $propertyData[ProcessingInstructions::COLLECTION]
163 4
            )) ? [] : $propertyData[ProcessingInstructions::COLLECTION];
164
        /** @var ProcessingInstructions $procInstCollection */
165 4
        $procInstCollection = Kernel::create(ProcessingInstructions::class, [$procInstData, $this]);
166 4
        $this->setProcessingInstructions($procInstCollection, true);
167
168
        // Instantiate the object relations
169 4
        $relationData = (empty($propertyData[Relations::COLLECTION]) ||
170
            !is_array(
171
                $propertyData[Relations::COLLECTION]
172 4
            )) ? [] : $propertyData[Relations::COLLECTION];
173
        /** @var Relations $relationCollection */
174 4
        $relationCollection = Kernel::create(Relations::class, [$relationData, $this]);
175 4
        $this->setRelations($relationCollection, true);
176
177
        // Reset the object state
178 4
        $this->resetState();
179 4
    }
180
181
    /**
182
     * Return whether this object already has a draft revision
183
     */
184 4
    protected function hasDraft()
185
    {
186
        // Create the object draft resource path
187 4
        $draftPath = $this->path->setRevision(Revision::current(true));
188
189
        // Use the object manager to look for a draft resource
190
        /** @var ManagerInterface $objectManager */
191 4
        $objectManager = Kernel::create(Service::class)->getObjectManager();
192 4
        return $objectManager->objectResourceExists($draftPath);
193
    }
194
195
    /**
196
     * Update the object path
197
     */
198 4
    protected function updatePath()
199
    {
200 4
        $revision = $this->getRevision();
201 4
        $this->path = $this->path->setRevision(
202 4
            !$revision->isDraft() && ($this->getCurrentRevision()->getRevision() == $revision->getRevision())
203 4
                ? Revision::current($revision->isDraft())
204 2
                : $revision
205 4
        )->setHidden($this->isDeleted());
206 4
    }
207
208
    /**
209
     * Return this object's current revision
210
     *
211
     * @return Revision Current revision
212
     */
213 3
    protected function getCurrentRevision()
214
    {
215 3
        if ($this->latestRevision->isDraft() && ($this->latestRevision->getRevision() > 1)) {
216 1
            return Kernel::create(Revision::class, [$this->latestRevision->getRevision() - 1, false]);
217
        }
218 3
        return $this->latestRevision;
219
    }
220
221
    /**
222
     * Use a specific object revision
223
     *
224
     * @param Revision $revision Revision to be used
225
     * @return ObjectInterface Object
226
     * @throws OutOfBoundsException If a revision beyond the latest one is requested
227
     */
228 2
    public function useRevision(Revision $revision)
229
    {
230
        // If a revision beyond the latest one is requested
231 2
        if (!$revision->isCurrent() && ($revision->getRevision() > $this->latestRevision->getRevision())) {
232
            throw new OutOfBoundsException(
233
                sprintf('Invalid object revision "%s"', $revision->getRevision()),
234
                OutOfBoundsException::INVALID_OBJECT_REVISION
235
            );
236
        }
237
238
        // Determine whether the current revision was requested
239 2
        $currentRevision = $this->getCurrentRevision();
240 2
        $isCurrentRevision = $revision->isCurrent() || $currentRevision->equals($revision);
241 2
        if ($isCurrentRevision) {
242 2
            $revision = $currentRevision;
243 2
        }
244
245
        // If the requested revision is not already used
246 2
        if (!$this->getRevision()->equals($revision)) {
247
248
            /** @var ManagerInterface $objectManager */
249 1
            $objectManager = Kernel::create(Service::class)->getObjectManager();
250
            /** @var Revision $newRevision */
251 1
            $newRevision = $isCurrentRevision ? Revision::current() : $revision;
252
            /** @var RepositoryPath $newRevisionPath */
253 1
            $newRevisionPath = $this->path->setRevision($newRevision);
254
255
            // Instantiate the requested revision resource
256 1
            $revisionResource = $objectManager->loadObjectResource($newRevisionPath, SelectorInterface::ALL);
257
258
            // Load the revision resource data
259 1
            $this->loadRevisionData($revisionResource->getPayload(), $revisionResource->getPropertyData());
260
261
            // Update the object path
262 1
            $this->updatePath();
263 1
        }
264
265 2
        return $this;
266
    }
267
268
    /**
269
     * Return the object repository path
270
     *
271
     * @return RepositoryPathInterface Object repository path
272
     */
273 4
    public function getRepositoryPath()
274
    {
275 4
        return $this->path;
276
    }
277
278
    /**
279
     * Return the object property data
280
     *
281
     * @return array Object property data
282
     */
283 4
    public function getPropertyData()
284
    {
285 4
        $propertyData = array_filter([
286 4
            SystemProperties::COLLECTION => $this->systemProperties->toArray(),
287 4
            MetaProperties::COLLECTION => $this->metaProperties->toArray(),
288 4
            AbstractDomainProperties::COLLECTION => $this->domainProperties->toArray(),
289 4
            ProcessingInstructions::COLLECTION => $this->processingInstructions->toArray(),
290 4
            Relations::COLLECTION => $this->relations->toArray(),
291 4
        ], function (array $collection) {
292 4
            return (boolean)count($collection);
293 4
        });
294
295 4
        return $propertyData;
296
    }
297
298
    /**
299
     * Return the absolute object URL
300
     *
301
     * @return string
302
     */
303
    public function getAbsoluteUrl()
304
    {
305
        return getenv('APPARAT_BASE_URL').ltrim($this->path->getRepository()->getUrl(), '/').strval($this->path);
306
    }
307
308
    /**
309
     * Persist the current object revision
310
     *
311
     * @return ObjectInterface Object
312
     */
313 4
    public function persist()
314
    {
315
        // If this is not the latest revision
316 4
        if ($this->getRevision()->getRevision() !== $this->latestRevision->getRevision()) {
317 1
            throw new RuntimeException(
318 1
                sprintf(
319 1
                    'Cannot persist revision %s/%s',
320 1
                    $this->getRevision()->getRevision(),
321 1
                    $this->latestRevision->getRevision()
322 1
                ),
323
                RuntimeException::CANNOT_PERSIST_EARLIER_REVISION
324 1
            );
325
        }
326
327
        // Update the object repository
328 4
        $this->path->getRepository()->updateObject($this);
329
330
        // Reset to a clean state
331 2
        $this->resetState();
332 2
        $this->latestRevision = $this->getRevision();
333 2
        $this->updatePath();
334
335
        // Post persistence hook
336 2
        $this->postPersist();
337
338 2
        return $this;
339
    }
340
341
    /**
342
     * Post persistence hook
343
     *
344
     * @return void
345
     */
346 2
    protected function postPersist()
347
    {
348 2
    }
349
350
    /**
351
     * Publish the current object revision
352
     *
353
     * @return ObjectInterface Object
354
     */
355 2
    public function publish()
356
    {
357
        // If this is an unpublished draft
358 2
        if ($this->isDraft() & !$this->hasBeenPublished()) {
359 2
            $this->setPublishedState();
360 2
            $this->latestRevision = $this->latestRevision->setDraft(false);
361 2
            $this->updatePath();
362 2
        }
363
364 2
        return $this;
365
    }
366
367
    /**
368
     * Delete the object and all its revisions
369
     *
370
     * @return ObjectInterface Object
371
     */
372 3
    public function delete()
373
    {
374
        // If this object is not already deleted
375 3
        if (!$this->isDeleted() && !$this->hasBeenDeleted()) {
376 3
            $this->setDeletedState();
377 3
            $this->updatePath();
378 3
        }
379
380 3
        return $this;
381
    }
382
383
    /**
384
     * Undelete the object and all its revisions
385
     *
386
     * @return ObjectInterface Object
387
     */
388 2
    public function undelete()
389
    {
390
        // If this object is already deleted
391 2
        if ($this->isDeleted() && !$this->hasBeenUndeleted()) {
392 2
            $this->setUndeletedState();
393 2
            $this->updatePath();
394 2
        }
395
396 2
        return $this;
397
    }
398
399
    /**
400
     * Convert this object revision into a draft
401
     */
402 2
    protected function convertToDraft()
403
    {
404
        // Assume the latest revision as draft revision
405 2
        $draftRevision = $this->latestRevision;
406
407
        // If it equals the current revision: Spawn a draft
408 2
        if ($draftRevision->equals($this->getCurrentRevision())) {
409 2
            $draftRevision = $this->latestRevision = $draftRevision->increment()->setDraft(true);
410 2
        }
411
412
        // Set the system properties to draft mode
413 2
        $this->setSystemProperties($this->systemProperties->createDraft($draftRevision), true);
414
415
        // Update the object path
416 2
        $this->updatePath();
417 2
    }
418
}
419