Completed
Push — master ( 70422a...a8cd59 )
by Joschi
03:30
created

AbstractObject   D

Complexity

Total Complexity 46

Size/Duplication

Total Lines 356
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 21

Test Coverage

Coverage 99.25%

Importance

Changes 50
Bugs 2 Features 15
Metric Value
c 50
b 2
f 15
dl 0
loc 356
rs 4.5442
ccs 133
cts 134
cp 0.9925
wmc 46
lcom 1
cbo 21

15 Methods

Rating   Name   Duplication   Size   Complexity  
F loadRevisionData() 0 50 11
A hasDraft() 0 10 1
A updatePath() 0 9 3
A getCurrentRevision() 0 7 3
A getRepositoryPath() 0 4 1
A getPropertyData() 0 14 1
A getAbsoluteUrl() 0 4 1
A postPersist() 0 3 1
C useRevision() 0 39 7
B persist() 0 27 2
A publish() 0 11 2
A delete() 0 10 3
A undelete() 0 10 3
A convertToDraft() 0 16 2
B __construct() 0 30 5

How to fix   Complexity   

Complex Class

Complex classes like AbstractObject often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractObject, and based on these observations, apply Extract Interface, too.

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