Completed
Push — master ( abf17e...c2a4dc )
by Joschi
03:15
created

AbstractObject::getRelations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 2
c 1
b 0
f 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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\Model\Relation\RelationInterface;
55
use Apparat\Object\Domain\Repository\Service;
56
57
/**
58
 * Abstract object
59
 *
60
 * @package Apparat\Object
61
 * @subpackage Apparat\Object\Domain
62
 */
63
abstract class AbstractObject implements ObjectInterface
64
{
65
    /**
66
     * Use traits
67
     */
68
    use SystemPropertiesTrait, MetaPropertiesTrait, DomainPropertiesTrait, RelationsTrait,
69
        ProcessingInstructionsTrait, PayloadTrait;
70
    /**
71
     * Clean state
72
     *
73
     * @var int
74
     */
75
    const STATE_CLEAN = 0;
76
    /**
77
     * Dirty state
78
     *
79
     * @var int
80
     */
81
    const STATE_DIRTY = 1;
82
    /**
83
     * Mutated state
84
     *
85
     * @var int
86
     */
87
    const STATE_MUTATED = 2;
88
    /**
89
     * Published state
90
     *
91
     * @var int
92
     */
93
    const STATE_PUBLISHED = 4;
94
    /**
95
     * Repository path
96
     *
97
     * @var RepositoryPathInterface
98
     */
99
    protected $path;
100
    /**
101
     * Latest revision index
102
     *
103
     * @var Revision
104
     */
105
    protected $latestRevision;
106
    /**
107
     * Object state
108
     *
109
     * @var int
110
     */
111
    protected $state = self::STATE_CLEAN;
112
    /**
113
     * Property collection states
114
     *
115
     * @var array
116
     */
117
    protected $collectionStates = [];
118
119
    /**
120
     * Object constructor
121
     *
122
     * @param string $payload Object payload
123
     * @param array $propertyData Property data
124
     * @param RepositoryPathInterface $path Object repository path
125
     */
126 22
    public function __construct($payload = '', array $propertyData = [], RepositoryPathInterface $path = null)
127
    {
128
        // If the domain property collection class is invalid
129 22
        if (!$this->domainPropertyCClass
130 22
            || !class_exists($this->domainPropertyCClass)
131 22
            || !(new \ReflectionClass($this->domainPropertyCClass))->isSubclassOf(AbstractDomainProperties::class)
132
        ) {
133 1
            throw new PropertyInvalidArgumentException(
134
                sprintf(
135 1
                    'Invalid domain property collection class "%s"',
136 1
                    $this->domainPropertyCClass
137
                ),
138 1
                PropertyInvalidArgumentException::INVALID_DOMAIN_PROPERTY_COLLECTION_CLASS
139
            );
140
        }
141
142
        // Right after instantiation it's always the current revision
143 21
        $this->path = $path->setRevision(Revision::current());
0 ignored issues
show
Bug introduced by
It seems like $path is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
144
145
        // Load the current revision data
146 21
        $this->loadRevisionData($payload, $propertyData);
147
148
        // Save the latest revision index
149 20
        $this->latestRevision = $this->getRevision();
150 20
    }
151
152
    /**
153
     * Load object revision data
154
     *
155
     * @param string $payload Object payload
156
     * @param array $propertyData Property data
157
     */
158 21
    protected function loadRevisionData($payload = '', array $propertyData = [])
159
    {
160 21
        $this->payload = $payload;
161
162
        // Instantiate the system properties
163 21
        $systemPropertyData = (empty($propertyData[SystemProperties::COLLECTION]) ||
164 21
            !is_array(
165 21
                $propertyData[SystemProperties::COLLECTION]
166 21
            )) ? [] : $propertyData[SystemProperties::COLLECTION];
167 21
        $this->systemProperties = Kernel::create(SystemProperties::class, [$systemPropertyData, $this]);
168
169
        // Instantiate the meta properties
170 20
        $metaPropertyData = (empty($propertyData[MetaProperties::COLLECTION]) ||
171 19
            !is_array(
172 20
                $propertyData[MetaProperties::COLLECTION]
173 20
            )) ? [] : $propertyData[MetaProperties::COLLECTION];
174
        /** @var MetaProperties $metaPropertyCollection */
175 20
        $metaPropertyCollection = Kernel::create(MetaProperties::class, [$metaPropertyData, $this]);
176 20
        $this->setMetaProperties($metaPropertyCollection, true);
177
178
        // Instantiate the domain properties
179 20
        $domainPropertyData = (empty($propertyData[AbstractDomainProperties::COLLECTION]) ||
180 19
            !is_array(
181 20
                $propertyData[AbstractDomainProperties::COLLECTION]
182 20
            )) ? [] : $propertyData[AbstractDomainProperties::COLLECTION];
183
        /** @var AbstractDomainProperties $domainPropertyCollection */
184 20
        $domainPropertyCollection = Kernel::create($this->domainPropertyCClass, [$domainPropertyData, $this]);
185 20
        $this->setDomainProperties($domainPropertyCollection, true);
186
187
        // Instantiate the processing instructions
188 20
        $procInstData = (empty($propertyData[ProcessingInstructions::COLLECTION]) ||
189 16
            !is_array(
190 20
                $propertyData[ProcessingInstructions::COLLECTION]
191 20
            )) ? [] : $propertyData[ProcessingInstructions::COLLECTION];
192
        /** @var ProcessingInstructions $procInstCollection */
193 20
        $procInstCollection = Kernel::create(ProcessingInstructions::class, [$procInstData, $this]);
194 20
        $this->setProcessingInstructions($procInstCollection, true);
195
196
        // Instantiate the object relations
197 20
        $relationData = (empty($propertyData[Relations::COLLECTION]) ||
198 19
            !is_array(
199 20
                $propertyData[Relations::COLLECTION]
200 20
            )) ? [] : $propertyData[Relations::COLLECTION];
201
        /** @var Relations $relationCollection */
202 20
        $relationCollection = Kernel::create(Relations::class, [$relationData, $this]);
203 20
        $this->setRelations($relationCollection, true);
204
205
        // Reset the object state to clean
206 20
        $this->state = self::STATE_CLEAN;
207 20
    }
208
209
    /**
210
     * Return whether the object is in mutated state
211
     *
212
     * @return boolean Mutated state
213
     */
214 3
    public function isMutated()
215
    {
216 3
        return !!($this->state & self::STATE_MUTATED);
217
    }
218
219
    /**
220
     * Use a specific object revision
221
     *
222
     * @param Revision $revision Revision to be used
223
     * @return ObjectInterface Object
224
     * @throws OutOfBoundsException If the requested revision is invalid
225
     */
226 19
    public function useRevision(Revision $revision)
227
    {
228 19
        $isCurrentRevision = false;
229
230
        // If the requested revision is invalid
231 19
        if (!$revision->isCurrent() &&
232 19
            (($revision->getRevision() < 1) || ($revision->getRevision() > $this->latestRevision->getRevision()))
233
        ) {
234
            throw new OutOfBoundsException(sprintf('Invalid object revision "%s"', $revision->getRevision()),
235
                OutOfBoundsException::INVALID_OBJECT_REVISION);
236
        }
237
238
        // If the current revision got requested
239 19
        if ($revision->isCurrent()) {
240 19
            $isCurrentRevision = true;
241 19
            $revision = $this->latestRevision;
242
        }
243
244
        // If the requested revision is not already used
245 19
        if ($revision != $this->getRevision()) {
246
            /** @var ManagerInterface $objectManager */
247
            $objectManager = Kernel::create(Service::class)->getObjectManager();
248
249
            // Load the requested object revision resource
250
            /** @var Revision $newRevision */
251
            $newRevision = $isCurrentRevision ? Revision::current() : $revision;
252
            /** @var RepositoryPath $newRevisionPath */
253
            $newRevisionPath = $this->path->setRevision($newRevision);
254
            $revisionResource = $objectManager->loadObject($newRevisionPath);
255
256
            // Load the revision resource data
257
            $this->loadRevisionData($revisionResource->getPayload(), $revisionResource->getPropertyData());
258
259
            // Set the current revision path
260
            $this->path = $newRevisionPath;
261
        }
262
263 19
        return $this;
264
    }
265
266
    /**
267
     * Return the object repository path
268
     *
269
     * @return RepositoryPathInterface Object repository path
270
     */
271 20
    public function getRepositoryPath()
272
    {
273 20
        return $this->path;
274
    }
275
276
    /**
277
     * Return the object property data
278
     *
279
     * @return array Object property data
280
     */
281 5
    public function getPropertyData()
282
    {
283 5
        $propertyData = array_filter([
284 5
            SystemProperties::COLLECTION => $this->systemProperties->toArray(),
285 5
            MetaProperties::COLLECTION => $this->metaProperties->toArray(),
286 5
            AbstractDomainProperties::COLLECTION => $this->domainProperties->toArray(),
287 5
            ProcessingInstructions::COLLECTION => $this->processingInstructions->toArray(),
288 5
            Relations::COLLECTION => $this->relations->toArray(),
289 5
        ], function (array $collection) {
290 5
            return (boolean)count($collection);
291 5
        });
292
293 5
        return $propertyData;
294
    }
295
296
    /**
297
     * Set the object state to mutated
298
     */
299 3
    protected function setMutatedState()
300
    {
301
        // If this object is not in mutated state yet
302 3
        if (!($this->state & self::STATE_MUTATED) && !$this->isDraft()) {
303
            // TODO: Send signal
304 3
            $this->convertToDraft();
305
        }
306
307
        // Enable the mutated (and dirty) state
308 3
        $this->state |= (self::STATE_DIRTY | self::STATE_MUTATED);
309 3
    }
310
311
    /**
312
     * Return the object draft mode
313
     *
314
     * @return boolean Object draft mode
315
     */
316 4
    public function isDraft()
317
    {
318 4
        return $this->systemProperties->isDraft() || $this->isPublished();
319
    }
320
321
    /**
322
     * Return whether the object is in published state
323
     *
324
     * @return boolean Published state
325
     */
326 4
    public function isPublished()
327
    {
328 4
        return !!($this->state & self::STATE_PUBLISHED);
329
    }
330
331
    /**
332
     * Convert this object revision into a draft
333
     */
334 3
    protected function convertToDraft()
335
    {
336
        // Increment the latest revision number
337 3
        $this->latestRevision = $this->latestRevision->increment();
338
339
        // Create draft system properties
340 3
        $this->systemProperties = $this->systemProperties->createDraft($this->latestRevision);
341
342
        // Adapt the system properties collection state
343 3
        $this->collectionStates[SystemProperties::COLLECTION] = spl_object_hash($this->systemProperties);
344
345
        // Set the draft flag on the repository path
346 3
        $this->path = $this->path->setDraft(true)->setRevision(Revision::current());
347
348
        // If this is not already a draft ...
349
        // Recreate the system properties
350
        // Copy the object ID
351
        // Copy the object type
352
        // Set the revision number to latest revision + 1
353
        // Set the creation date to now
354
        // Set no publication date
355
        // Set the draft flag on the repository path
356
        // Increase the latest revision by 1
357
358
        // Else if this is a draft
359
        // No action needed
360 3
    }
361
362
    /**
363
     * Return the absolute object URL
364
     *
365
     * @return string
366
     */
367 4
    public function getAbsoluteUrl()
368
    {
369 4
        return getenv('APPARAT_BASE_URL').ltrim($this->path->getRepository()->getUrl(), '/').strval($this->path);
370
    }
371
372
    /**
373
     * Persist the current object revision
374
     *
375
     * @return ObjectInterface Object
376
     */
377 1
    public function persist()
378
    {
379
        // If this is not the latest revision
380 1
        if ($this->getRevision() != $this->latestRevision) {
381
            throw new RuntimeException(
382
                sprintf(
383
                    'Cannot persist revision %s/%s',
384
                    $this->getRevision()->getRevision(),
385
                    $this->latestRevision->getRevision()
386
                ),
387
                RuntimeException::CANNOT_PERSIST_EARLIER_REVISION
388
            );
389
        }
390
391
        // Update the object repository
392 1
        $this->path->getRepository()->updateObject($this);
393
394
        // Reset state
395 1
        $this->state = self::STATE_CLEAN;
396
397 1
        return $this;
398
    }
399
400
    /**
401
     * Publish the current object revision
402
     *
403
     * @return ObjectInterface Object
404
     */
405 1
    public function publish()
406
    {
407
        // If this is a draft
408 1
        if ($this->isDraft()) {
409
            // TODO: Send signal
410
411
            // Create draft system properties
412 1
            $this->systemProperties = $this->systemProperties->publish();
413
414
            // Adapt the system properties collection state
415 1
            $this->collectionStates[SystemProperties::COLLECTION] = spl_object_hash($this->systemProperties);
416
417
            // Set the draft flag on the repository path
418 1
            $this->path = $this->path->setDraft(false);
419
420
            // Flag this object as dirty
421 1
            $this->setPublishedState();
422
        }
423
424 1
        return $this;
425
    }
426
427
    /**
428
     * Set the object state to published
429
     */
430 1
    protected function setPublishedState()
431
    {
432
        // If this object is not in dirty state yet
433 1
        if (!($this->state & self::STATE_PUBLISHED)) {
434
            // TODO: Send signal
435
        }
436
437
        // Enable the dirty state
438 1
        $this->state |= (self::STATE_DIRTY | self::STATE_PUBLISHED);
439 1
    }
440
441
    /**
442
     * Return whether the object is in dirty state
443
     *
444
     * @return boolean Dirty state
445
     */
446 3
    public function isDirty()
447
    {
448 3
        return !!($this->state & self::STATE_DIRTY);
449
    }
450
451
    /**
452
     * Set the object state to dirty
453
     */
454 3
    protected function setDirtyState()
455
    {
456
        // If this object is not in dirty state yet
457 3
        if (!($this->state & self::STATE_DIRTY)) {
458
            // TODO: Send signal
459
        }
460
461
        // Enable the dirty state
462 3
        $this->state |= self::STATE_DIRTY;
463 3
    }
464
465
    /**
466
     * Add an object relation
467
     *
468
     * @param string|RelationInterface $relation Serialized or instantiated object relation
469
     * @param string|null $relationType Relation type
470
     * @return ObjectInterface
471
     */
472 1
    public function addRelation($relation, $relationType = null) {
473 1
        $this->setRelations($this->relations->addRelation($relation, $relationType));
474 1
        return $this;
475
    }
476
477
    /**
478
     * Delete an object relation
479
     *
480
     * @param RelationInterface $relation Object relation
481
     * @return ObjectInterface
482
     */
483 1
    public function deleteRelation(RelationInterface $relation)
484
    {
485 1
        $this->setRelations($this->relations->deleteRelation($relation));
486 1
        return $this;
487
    }
488
489
    /**
490
     * Get all relations (optional: Of a particular type)
491
     *
492
     * @param string|null $relationType Optional: Relation type
493
     * @return array Object relations
494
     */
495 1
    public function getRelations($relationType = null) {
496 1
        return $this->relations->getRelations($relationType);
497
    }
498
499
    /**
500
     * Find and return particular relations
501
     *
502
     * @param array $criteria Relation criteria
503
     * @return RelationInterface[] Relations
504
     */
505 1
    public function findRelations(array $criteria) {
506 1
        return $this->relations->findRelations($criteria);
507
    }
508
}
509