Completed
Push — master ( f4221b...caffc5 )
by Joschi
06:49
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\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 string $payload Object payload
122
     * @param array $propertyData Property data
123
     * @param RepositoryPathInterface $path Object repository path
124
     */
125
    public function __construct($payload = '', array $propertyData = [], RepositoryPathInterface $path = null)
126 23
    {
127
        // If the domain property collection class is invalid
128
        if (!$this->domainPropertyCClass
129 23
            || !class_exists($this->domainPropertyCClass)
130 23
            || !(new \ReflectionClass($this->domainPropertyCClass))->isSubclassOf(AbstractDomainProperties::class)
131 23
        ) {
132
            throw new PropertyInvalidArgumentException(
133 1
                sprintf(
134
                    'Invalid domain property collection class "%s"',
135 1
                    $this->domainPropertyCClass
136 1
                ),
137
                PropertyInvalidArgumentException::INVALID_DOMAIN_PROPERTY_COLLECTION_CLASS
138 1
            );
139
        }
140
141
        // Right after instantiation it's always the current revision
142
        $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...
143 22
144
        // Load the current revision data
145
        $this->loadRevisionData($payload, $propertyData);
146 22
147
        // Save the latest revision index
148
        $this->latestRevision = $this->getRevision();
149 21
    }
150 21
151
    /**
152
     * Load object revision data
153
     *
154
     * @param string $payload Object payload
155
     * @param array $propertyData Property data
156
     */
157
    protected function loadRevisionData($payload = '', array $propertyData = [])
158 22
    {
159
        $this->payload = $payload;
160 22
161
        // Instantiate the system properties
162
        $systemPropertyData = (empty($propertyData[SystemProperties::COLLECTION]) ||
163 22
            !is_array(
164 22
                $propertyData[SystemProperties::COLLECTION]
165 22
            )) ? [] : $propertyData[SystemProperties::COLLECTION];
166 22
        $this->systemProperties = Kernel::create(SystemProperties::class, [$systemPropertyData, $this]);
167 22
168
        // Instantiate the meta properties
169
        $metaPropertyData = (empty($propertyData[MetaProperties::COLLECTION]) ||
170 21
            !is_array(
171 20
                $propertyData[MetaProperties::COLLECTION]
172 21
            )) ? [] : $propertyData[MetaProperties::COLLECTION];
173 21
        /** @var MetaProperties $metaPropertyCollection */
174
        $metaPropertyCollection = Kernel::create(MetaProperties::class, [$metaPropertyData, $this]);
175 21
        $this->setMetaProperties($metaPropertyCollection, true);
176 21
177
        // Instantiate the domain properties
178
        $domainPropertyData = (empty($propertyData[AbstractDomainProperties::COLLECTION]) ||
179 21
            !is_array(
180 20
                $propertyData[AbstractDomainProperties::COLLECTION]
181 21
            )) ? [] : $propertyData[AbstractDomainProperties::COLLECTION];
182 21
        /** @var AbstractDomainProperties $domainPropertyCollection */
183
        $domainPropertyCollection = Kernel::create($this->domainPropertyCClass, [$domainPropertyData, $this]);
184 21
        $this->setDomainProperties($domainPropertyCollection, true);
185 21
186
        // Instantiate the processing instructions
187
        $procInstData = (empty($propertyData[ProcessingInstructions::COLLECTION]) ||
188 21
            !is_array(
189 17
                $propertyData[ProcessingInstructions::COLLECTION]
190 21
            )) ? [] : $propertyData[ProcessingInstructions::COLLECTION];
191 21
        /** @var ProcessingInstructions $procInstCollection */
192
        $procInstCollection = Kernel::create(ProcessingInstructions::class, [$procInstData, $this]);
193 21
        $this->setProcessingInstructions($procInstCollection, true);
194 21
195
        // Instantiate the object relations
196
        $relationData = (empty($propertyData[Relations::COLLECTION]) ||
197 21
            !is_array(
198 20
                $propertyData[Relations::COLLECTION]
199 21
            )) ? [] : $propertyData[Relations::COLLECTION];
200 21
        /** @var Relations $relationCollection */
201
        $relationCollection = Kernel::create(Relations::class, [$relationData, $this]);
202 21
        $this->setRelations($relationCollection, true);
203 21
204
        // Reset the object state to clean
205
        $this->state = self::STATE_CLEAN;
206 21
    }
207 21
208
    /**
209
     * Return whether the object is in mutated state
210
     *
211
     * @return boolean Mutated state
212
     */
213
    public function isMutated()
214 3
    {
215
        return !!($this->state & self::STATE_MUTATED);
216 3
    }
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
    public function useRevision(Revision $revision)
226 20
    {
227
        $isCurrentRevision = false;
228 20
229
        // If the requested revision is invalid
230
        if (!$revision->isCurrent() &&
231 20
            (($revision->getRevision() < 1) || ($revision->getRevision() > $this->latestRevision->getRevision()))
232 20
        ) {
233
            throw new OutOfBoundsException(sprintf('Invalid object revision "%s"', $revision->getRevision()),
234
                OutOfBoundsException::INVALID_OBJECT_REVISION);
235
        }
236
237
        // If the current revision got requested
238
        if ($revision->isCurrent()) {
239 20
            $isCurrentRevision = true;
240 20
            $revision = $this->latestRevision;
241 20
        }
242
243
        // If the requested revision is not already used
244
        if ($revision != $this->getRevision()) {
245 20
            /** @var ManagerInterface $objectManager */
246
            $objectManager = Kernel::create(Service::class)->getObjectManager();
247
248
            // Load the requested object revision resource
249
            /** @var Revision $newRevision */
250
            $newRevision = $isCurrentRevision ? Revision::current() : $revision;
251
            /** @var RepositoryPath $newRevisionPath */
252
            $newRevisionPath = $this->path->setRevision($newRevision);
253
            $revisionResource = $objectManager->loadObject($newRevisionPath);
254
255
            // Load the revision resource data
256
            $this->loadRevisionData($revisionResource->getPayload(), $revisionResource->getPropertyData());
257
258
            // Set the current revision path
259
            $this->path = $newRevisionPath;
260
        }
261
262
        return $this;
263 20
    }
264
265
    /**
266
     * Return the object repository path
267
     *
268
     * @return RepositoryPathInterface Object repository path
269
     */
270
    public function getRepositoryPath()
271 21
    {
272
        return $this->path;
273 21
    }
274
275
    /**
276
     * Return the object property data
277
     *
278
     * @return array Object property data
279
     */
280
    public function getPropertyData()
281 5
    {
282
        $propertyData = array_filter([
283 5
            SystemProperties::COLLECTION => $this->systemProperties->toArray(),
284 5
            MetaProperties::COLLECTION => $this->metaProperties->toArray(),
285 5
            AbstractDomainProperties::COLLECTION => $this->domainProperties->toArray(),
286 5
            ProcessingInstructions::COLLECTION => $this->processingInstructions->toArray(),
287 5
            Relations::COLLECTION => $this->relations->toArray(),
288 5
        ], function (array $collection) {
289 5
            return (boolean)count($collection);
290 5
        });
291 5
292
        return $propertyData;
293 5
    }
294
295
    /**
296
     * Return the absolute object URL
297
     *
298
     * @return string
299 3
     */
300
    public function getAbsoluteUrl()
301
    {
302 3
        return getenv('APPARAT_BASE_URL').ltrim($this->path->getRepository()->getUrl(), '/').strval($this->path);
303
    }
304 3
305
    /**
306
     * Persist the current object revision
307
     *
308 3
     * @return ObjectInterface Object
309 3
     */
310
    public function persist()
311
    {
312
        // If this is not the latest revision
313
        if ($this->getRevision() != $this->latestRevision) {
314
            throw new RuntimeException(
315
                sprintf(
316 4
                    'Cannot persist revision %s/%s',
317
                    $this->getRevision()->getRevision(),
318 4
                    $this->latestRevision->getRevision()
319
                ),
320
                RuntimeException::CANNOT_PERSIST_EARLIER_REVISION
321
            );
322
        }
323
324
        // Update the object repository
325
        $this->path->getRepository()->updateObject($this);
326 4
327
        // Reset state
328 4
        $this->state = self::STATE_CLEAN;
329
330
        return $this;
331
    }
332
333
    /**
334 3
     * Publish the current object revision
335
     *
336
     * @return ObjectInterface Object
337 3
     */
338
    public function publish()
339
    {
340 3
        // If this is a draft
341
        if ($this->isDraft()) {
342
            // TODO: Send signal
343 3
344
            // Create draft system properties
345
            $this->systemProperties = $this->systemProperties->publish();
346 3
347
            // Adapt the system properties collection state
348
            $this->collectionStates[SystemProperties::COLLECTION] = spl_object_hash($this->systemProperties);
349
350
            // Set the draft flag on the repository path
351
            $this->path = $this->path->setDraft(false);
352
353
            // Flag this object as dirty
354
            $this->setPublishedState();
355
        }
356
357
        return $this;
358
    }
359
360 3
    /**
361
     * Set the object state to published
362
     */
363
    protected function setPublishedState()
364
    {
365
        // If this object is not in dirty state yet
366
        if (!($this->state & self::STATE_PUBLISHED)) {
367 4
            // TODO: Send signal
368
        }
369 4
370
        // Enable the dirty state
371
        $this->state |= (self::STATE_DIRTY | self::STATE_PUBLISHED);
372
    }
373
374
    /**
375
     * Return whether the object is in dirty state
376
     *
377 1
     * @return boolean Dirty state
378
     */
379
    public function isDirty()
380 1
    {
381
        return !!($this->state & self::STATE_DIRTY);
382
    }
383
384
    /**
385
     * Set the object state to mutated
386
     */
387
    protected function setMutatedState()
388
    {
389
        // If this object is not in mutated state yet
390
        if (!($this->state & self::STATE_MUTATED) && !$this->isDraft()) {
391
            // TODO: Send signal
392 1
            $this->convertToDraft();
393
        }
394
395 1
        // Enable the mutated (and dirty) state
396
        $this->state |= (self::STATE_DIRTY | self::STATE_MUTATED);
397 1
    }
398
399
    /**
400
     * Return the object draft mode
401
     *
402
     * @return boolean Object draft mode
403
     */
404
    public function isDraft()
405 1
    {
406
        return $this->systemProperties->isDraft() || $this->isPublished();
407
    }
408 1
409
    /**
410
     * Return whether the object is in published state
411
     *
412 1
     * @return boolean Published state
413
     */
414
    public function isPublished()
415 1
    {
416
        return !!($this->state & self::STATE_PUBLISHED);
417
    }
418 1
419
    /**
420
     * Convert this object revision into a draft
421 1
     */
422
    protected function convertToDraft()
423
    {
424 1
        // Increment the latest revision number
425
        $this->latestRevision = $this->latestRevision->increment();
426
427
        // Create draft system properties
428
        $this->systemProperties = $this->systemProperties->createDraft($this->latestRevision);
429
430 1
        // Adapt the system properties collection state
431
        $this->collectionStates[SystemProperties::COLLECTION] = spl_object_hash($this->systemProperties);
432
433 1
        // Set the draft flag on the repository path
434
        $this->path = $this->path->setDraft(true)->setRevision(Revision::current());
435
436
        // If this is not already a draft ...
437
        // Recreate the system properties
438 1
        // Copy the object ID
439 1
        // Copy the object type
440
        // Set the revision number to latest revision + 1
441
        // Set the creation date to now
442
        // Set no publication date
443
        // Set the draft flag on the repository path
444
        // Increase the latest revision by 1
445
446 3
        // Else if this is a draft
447
        // No action needed
448 3
    }
449
450
    /**
451
     * Set the object state to dirty
452
     */
453
    protected function setDirtyState()
454 3
    {
455
        // If this object is not in dirty state yet
456
        if (!($this->state & self::STATE_DIRTY)) {
457 3
            // TODO: Send signal
458
        }
459
460
        // Enable the dirty state
461
        $this->state |= self::STATE_DIRTY;
462 3
    }
463
}
464