Completed
Push — master ( e562e6...7da42d )
by Joschi
03:38
created

AbstractObject   C

Complexity

Total Complexity 42

Size/Duplication

Total Lines 410
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 19

Test Coverage

Coverage 88.71%

Importance

Changes 42
Bugs 1 Features 14
Metric Value
c 42
b 1
f 14
dl 0
loc 410
ccs 110
cts 124
cp 0.8871
rs 5.7322
wmc 42
lcom 1
cbo 19

16 Methods

Rating   Name   Duplication   Size   Complexity  
A isMutated() 0 4 1
B __construct() 0 25 4
A getAbsoluteUrl() 0 4 1
A isDraft() 0 4 2
A isPublished() 0 4 1
F loadRevisionData() 0 50 11
A getRepositoryPath() 0 4 1
A getPropertyData() 0 14 1
A isDirty() 0 4 1
C useRevision() 0 41 7
A persist() 0 22 2
A publish() 0 21 2
A setPublishedState() 0 10 2
B convertToDraft() 0 27 1
A setMutatedState() 0 14 3
A setDirtyState() 0 13 2

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\MetaPropertiesTrait;
42
use Apparat\Object\Domain\Model\Object\Traits\PayloadTrait;
43
use Apparat\Object\Domain\Model\Object\Traits\ProcessingInstructionsTrait;
44
use Apparat\Object\Domain\Model\Object\Traits\RelationsTrait;
45
use Apparat\Object\Domain\Model\Object\Traits\SystemPropertiesTrait;
46
use Apparat\Object\Domain\Model\Path\RepositoryPath;
47
use Apparat\Object\Domain\Model\Path\RepositoryPathInterface;
48
use Apparat\Object\Domain\Model\Properties\AbstractDomainProperties;
49
use Apparat\Object\Domain\Model\Properties\InvalidArgumentException as PropertyInvalidArgumentException;
50
use Apparat\Object\Domain\Model\Properties\MetaProperties;
51
use Apparat\Object\Domain\Model\Properties\ProcessingInstructions;
52
use Apparat\Object\Domain\Model\Properties\Relations;
53
use Apparat\Object\Domain\Model\Properties\SystemProperties;
54
use Apparat\Object\Domain\Repository\Service;
55
56
/**
57
 * Abstract object
58
 *
59
 * @package Apparat\Object
60
 * @subpackage Apparat\Object\Domain
61
 */
62
abstract class AbstractObject implements ObjectInterface
63
{
64
    /**
65
     * Use traits
66
     */
67
    use SystemPropertiesTrait, MetaPropertiesTrait, DomainPropertiesTrait, RelationsTrait,
68
        ProcessingInstructionsTrait, PayloadTrait;
69
    /**
70
     * Clean state
71
     *
72
     * @var int
73
     */
74
    const STATE_CLEAN = 0;
75
    /**
76
     * Dirty state
77
     *
78
     * @var int
79
     */
80
    const STATE_DIRTY = 1;
81
    /**
82
     * Mutated state
83
     *
84
     * @var int
85
     */
86
    const STATE_MUTATED = 2;
87
    /**
88
     * Published state
89
     *
90
     * @var int
91
     */
92
    const STATE_PUBLISHED = 4;
93
    /**
94
     * Repository path
95
     *
96
     * @var RepositoryPathInterface
97
     */
98
    protected $path;
99
    /**
100
     * Latest revision index
101
     *
102
     * @var Revision
103
     */
104
    protected $latestRevision;
105
    /**
106
     * Object state
107
     *
108
     * @var int
109
     */
110
    protected $state = self::STATE_CLEAN;
111
    /**
112
     * Property collection states
113
     *
114
     * @var array
115
     */
116
    protected $collectionStates = [];
117
118
    /**
119
     * Object constructor
120
     *
121
     * @param RepositoryPathInterface $path Object repository path
122
     * @param string $payload Object payload
123
     * @param array $propertyData Property data
124
     */
125 26
    public function __construct(RepositoryPathInterface $path, $payload = '', array $propertyData = [])
126
    {
127
        // If the domain property collection class is invalid
128 26
        if (!$this->domainPropertyCClass
129 26
            || !class_exists($this->domainPropertyCClass)
130 26
            || !(new \ReflectionClass($this->domainPropertyCClass))->isSubclassOf(AbstractDomainProperties::class)
131
        ) {
132 1
            throw new PropertyInvalidArgumentException(
133
                sprintf(
134 1
                    'Invalid domain property collection class "%s"',
135 1
                    $this->domainPropertyCClass
136
                ),
137 1
                PropertyInvalidArgumentException::INVALID_DOMAIN_PROPERTY_COLLECTION_CLASS
138
            );
139
        }
140
141
        // Right after instantiation it's always the current revision
142 25
        $this->path = $path->setRevision(Revision::current());
143
144
        // Load the current revision data
145 25
        $this->loadRevisionData($payload, $propertyData);
146
147
        // Save the latest revision index
148 24
        $this->latestRevision = $this->getRevision();
149 24
    }
150
151
    /**
152
     * Load object revision data
153
     *
154
     * @param string $payload Object payload
155
     * @param array $propertyData Property data
156
     */
157 25
    protected function loadRevisionData($payload = '', array $propertyData = [])
158
    {
159 25
        $this->payload = $payload;
160
161
        // Instantiate the system properties
162 25
        $systemPropertyData = (empty($propertyData[SystemProperties::COLLECTION]) ||
163 25
            !is_array(
164 25
                $propertyData[SystemProperties::COLLECTION]
165 25
            )) ? [] : $propertyData[SystemProperties::COLLECTION];
166 25
        $this->systemProperties = Kernel::create(SystemProperties::class, [$systemPropertyData, $this]);
167
168
        // Instantiate the meta properties
169 24
        $metaPropertyData = (empty($propertyData[MetaProperties::COLLECTION]) ||
170 23
            !is_array(
171 24
                $propertyData[MetaProperties::COLLECTION]
172 24
            )) ? [] : $propertyData[MetaProperties::COLLECTION];
173
        /** @var MetaProperties $metaProperties */
174 24
        $metaProperties = Kernel::create(MetaProperties::class, [$metaPropertyData, $this]);
175 24
        $this->setMetaProperties($metaProperties, true);
176
177
        // Instantiate the domain properties
178 24
        $domainPropertyData = (empty($propertyData[AbstractDomainProperties::COLLECTION]) ||
179 23
            !is_array(
180 24
                $propertyData[AbstractDomainProperties::COLLECTION]
181 24
            )) ? [] : $propertyData[AbstractDomainProperties::COLLECTION];
182
        /** @var AbstractDomainProperties $domainProperties */
183 24
        $domainProperties = Kernel::create($this->domainPropertyCClass, [$domainPropertyData, $this]);
184 24
        $this->setDomainProperties($domainProperties, true);
185
186
        // Instantiate the processing instructions
187 24
        $procInstData = (empty($propertyData[ProcessingInstructions::COLLECTION]) ||
188 20
            !is_array(
189 24
                $propertyData[ProcessingInstructions::COLLECTION]
190 24
            )) ? [] : $propertyData[ProcessingInstructions::COLLECTION];
191
        /** @var ProcessingInstructions $procInstCollection */
192 24
        $procInstCollection = Kernel::create(ProcessingInstructions::class, [$procInstData, $this]);
193 24
        $this->setProcessingInstructions($procInstCollection, true);
194
195
        // Instantiate the object relations
196 24
        $relationData = (empty($propertyData[Relations::COLLECTION]) ||
197 23
            !is_array(
198 24
                $propertyData[Relations::COLLECTION]
199 24
            )) ? [] : $propertyData[Relations::COLLECTION];
200
        /** @var Relations $relationCollection */
201 24
        $relationCollection = Kernel::create(Relations::class, [$relationData, $this]);
202 24
        $this->setRelations($relationCollection, true);
203
204
        // Reset the object state to clean
205 24
        $this->state = self::STATE_CLEAN;
206 24
    }
207
208
    /**
209
     * Return whether the object is in mutated state
210
     *
211
     * @return boolean Mutated state
212
     */
213 3
    public function isMutated()
214
    {
215 3
        return !!($this->state & self::STATE_MUTATED);
216
    }
217
218
    /**
219
     * Use a specific object revision
220
     *
221
     * @param Revision $revision Revision to be used
222
     * @return ObjectInterface Object
223
     * @throws OutOfBoundsException If the requested revision is invalid
224
     */
225 23
    public function useRevision(Revision $revision)
226
    {
227 23
        $isCurrentRevision = false;
228
229
        // If the requested revision is invalid
230 23
        if (!$revision->isCurrent() &&
231 23
            (($revision->getRevision() < 1) || ($revision->getRevision() > $this->latestRevision->getRevision()))
232
        ) {
233
            throw new OutOfBoundsException(
234
                sprintf('Invalid object revision "%s"', $revision->getRevision()),
235
                OutOfBoundsException::INVALID_OBJECT_REVISION
236
            );
237
        }
238
239
        // If the current revision got requested
240 23
        if ($revision->isCurrent()) {
241 23
            $isCurrentRevision = true;
242 23
            $revision = $this->latestRevision;
243
        }
244
245
        // If the requested revision is not already used
246 23
        if ($revision != $this->getRevision()) {
247
            /** @var ManagerInterface $objectManager */
248
            $objectManager = Kernel::create(Service::class)->getObjectManager();
249
250
            // Load the requested object revision resource
251
            /** @var Revision $newRevision */
252
            $newRevision = $isCurrentRevision ? Revision::current() : $revision;
253
            /** @var RepositoryPath $newRevisionPath */
254
            $newRevisionPath = $this->path->setRevision($newRevision);
255
            $revisionResource = $objectManager->loadObject($newRevisionPath);
256
257
            // Load the revision resource data
258
            $this->loadRevisionData($revisionResource->getPayload(), $revisionResource->getPropertyData());
259
260
            // Set the current revision path
261
            $this->path = $newRevisionPath;
262
        }
263
264 23
        return $this;
265
    }
266
267
    /**
268
     * Return the object repository path
269
     *
270
     * @return RepositoryPathInterface Object repository path
271
     */
272 24
    public function getRepositoryPath()
273
    {
274 24
        return $this->path;
275
    }
276
277
    /**
278
     * Return the object property data
279
     *
280
     * @return array Object property data
281
     */
282 5
    public function getPropertyData()
283
    {
284 5
        $propertyData = array_filter([
285 5
            SystemProperties::COLLECTION => $this->systemProperties->toArray(),
286 5
            MetaProperties::COLLECTION => $this->metaProperties->toArray(),
287 5
            AbstractDomainProperties::COLLECTION => $this->domainProperties->toArray(),
288 5
            ProcessingInstructions::COLLECTION => $this->processingInstructions->toArray(),
289 5
            Relations::COLLECTION => $this->relations->toArray(),
290 5
        ], function (array $collection) {
291 5
            return (boolean)count($collection);
292 5
        });
293
294 5
        return $propertyData;
295
    }
296
297
    /**
298
     * Return the absolute object URL
299
     *
300
     * @return string
301
     */
302 4
    public function getAbsoluteUrl()
303
    {
304 4
        return getenv('APPARAT_BASE_URL').ltrim($this->path->getRepository()->getUrl(), '/').strval($this->path);
305
    }
306
307
    /**
308
     * Persist the current object revision
309
     *
310
     * @return ObjectInterface Object
311
     */
312 1
    public function persist()
313
    {
314
        // If this is not the latest revision
315 1
        if ($this->getRevision() != $this->latestRevision) {
316
            throw new RuntimeException(
317
                sprintf(
318
                    'Cannot persist revision %s/%s',
319
                    $this->getRevision()->getRevision(),
320
                    $this->latestRevision->getRevision()
321
                ),
322
                RuntimeException::CANNOT_PERSIST_EARLIER_REVISION
323
            );
324
        }
325
326
        // Update the object repository
327 1
        $this->path->getRepository()->updateObject($this);
328
329
        // Reset state
330 1
        $this->state = self::STATE_CLEAN;
331
332 1
        return $this;
333
    }
334
335
    /**
336
     * Publish the current object revision
337
     *
338
     * @return ObjectInterface Object
339
     */
340 1
    public function publish()
341
    {
342
        // If this is a draft
343 1
        if ($this->isDraft()) {
344
            // TODO: Send signal
345
346
            // Create draft system properties
347 1
            $this->systemProperties = $this->systemProperties->publish();
348
349
            // Adapt the system properties collection state
350 1
            $this->collectionStates[SystemProperties::COLLECTION] = spl_object_hash($this->systemProperties);
351
352
            // Set the draft flag on the repository path
353 1
            $this->path = $this->path->setDraft(false);
354
355
            // Flag this object as dirty
356 1
            $this->setPublishedState();
357
        }
358
359 1
        return $this;
360
    }
361
362
    /**
363
     * Return the object draft mode
364
     *
365
     * @return boolean Object draft mode
366
     */
367 5
    public function isDraft()
368
    {
369 5
        return $this->systemProperties->isDraft() || $this->isPublished();
370
    }
371
372
    /**
373
     * Return whether the object is in published state
374
     *
375
     * @return boolean Published state
376
     */
377 5
    public function isPublished()
378
    {
379 5
        return !!($this->state & self::STATE_PUBLISHED);
380
    }
381
382
    /**
383
     * Set the object state to published
384
     */
385 1
    protected function setPublishedState()
386
    {
387
        // If this object is not in dirty state yet
388 1
        if (!($this->state & self::STATE_PUBLISHED)) {
389
            // TODO: Send signal
390
        }
391
392
        // Enable the dirty state
393 1
        $this->state |= (self::STATE_DIRTY | self::STATE_PUBLISHED);
394 1
    }
395
396
    /**
397
     * Return whether the object is in dirty state
398
     *
399
     * @return boolean Dirty state
400
     */
401 3
    public function isDirty()
402
    {
403 3
        return !!($this->state & self::STATE_DIRTY);
404
    }
405
406
    /**
407
     * Set the object state to mutated
408
     */
409 4
    protected function setMutatedState()
410
    {
411
        // If this object is not in mutated state yet
412 4
        if (!($this->state & self::STATE_MUTATED) && !$this->isDraft()) {
413
            // TODO: Send signal
414 4
            $this->convertToDraft();
415
        }
416
417
        // Enable the mutated state
418 4
        $this->state |= self::STATE_MUTATED;
419
420
        // Enable the dirty state
421 4
        $this->setDirtyState();
422 4
    }
423
424
    /**
425
     * Convert this object revision into a draft
426
     */
427 4
    protected function convertToDraft()
428
    {
429
        // Increment the latest revision number
430 4
        $this->latestRevision = $this->latestRevision->increment();
431
432
        // Create draft system properties
433 4
        $this->systemProperties = $this->systemProperties->createDraft($this->latestRevision);
434
435
        // Adapt the system properties collection state
436 4
        $this->collectionStates[SystemProperties::COLLECTION] = spl_object_hash($this->systemProperties);
437
438
        // Set the draft flag on the repository path
439 4
        $this->path = $this->path->setDraft(true)->setRevision(Revision::current());
440
441
        // If this is not already a draft ...
442
        // Recreate the system properties
443
        // Copy the object ID
444
        // Copy the object type
445
        // Set the revision number to latest revision + 1
446
        // Set the creation date to now
447
        // Set no publication date
448
        // Set the draft flag on the repository path
449
        // Increase the latest revision by 1
450
451
        // Else if this is a draft
452
        // No action needed
453 4
    }
454
455
    /**
456
     * Set the object state to dirty
457
     */
458 7
    protected function setDirtyState()
459
    {
460
        // If this object is not in dirty state yet
461 7
        if (!($this->state & self::STATE_DIRTY)) {
462
            // TODO: Send signal
463
        }
464
465
        // Enable the dirty state
466 7
        $this->state |= self::STATE_DIRTY;
467
468
        // Update the modification timestamp
469 7
        $this->setSystemProperties($this->systemProperties->touch(), true);
470 7
    }
471
}
472