Completed
Push — master ( 7da42d...51b910 )
by Joschi
03:34
created

AbstractObject::getPropertyData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 1

Importance

Changes 4
Bugs 0 Features 1
Metric Value
cc 1
eloc 10
c 4
b 0
f 1
nc 1
nop 0
dl 0
loc 14
ccs 11
cts 11
cp 1
crap 1
rs 9.4285
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
     * Deleted state
95
     *
96
     * @var int
97
     */
98
    const STATE_DELETED = 8;
99
    /**
100
     * Repository path
101
     *
102
     * @var RepositoryPathInterface
103
     */
104
    protected $path;
105
    /**
106
     * Latest revision index
107
     *
108
     * @var Revision
109
     */
110
    protected $latestRevision;
111
    /**
112
     * Object state
113
     *
114
     * @var int
115
     */
116
    protected $state = self::STATE_CLEAN;
117
    /**
118
     * Property collection states
119
     *
120
     * @var array
121
     */
122
    protected $collectionStates = [];
123
124
    /**
125
     * Object constructor
126
     *
127
     * @param RepositoryPathInterface $path Object repository path
128
     * @param string $payload Object payload
129
     * @param array $propertyData Property data
130
     */
131 26
    public function __construct(RepositoryPathInterface $path, $payload = '', array $propertyData = [])
132
    {
133
        // If the domain property collection class is invalid
134 26
        if (!$this->domainPropertyCClass
135 26
            || !class_exists($this->domainPropertyCClass)
136 26
            || !(new \ReflectionClass($this->domainPropertyCClass))->isSubclassOf(AbstractDomainProperties::class)
137 26
        ) {
138 1
            throw new PropertyInvalidArgumentException(
139 1
                sprintf(
140 1
                    'Invalid domain property collection class "%s"',
141 1
                    $this->domainPropertyCClass
142 1
                ),
143
                PropertyInvalidArgumentException::INVALID_DOMAIN_PROPERTY_COLLECTION_CLASS
144 1
            );
145
        }
146
147
        // Right after instantiation it's always the current revision
148 25
        $this->path = $path->setRevision(Revision::current());
149
150
        // Load the current revision data
151 25
        $this->loadRevisionData($payload, $propertyData);
152
153
        // Save the latest revision index
154 24
        $this->latestRevision = $this->getRevision();
155 24
    }
156
157
    /**
158
     * Load object revision data
159
     *
160
     * @param string $payload Object payload
161
     * @param array $propertyData Property data
162
     */
163 25
    protected function loadRevisionData($payload = '', array $propertyData = [])
164
    {
165 25
        $this->payload = $payload;
166
167
        // Instantiate the system properties
168 25
        $systemPropertyData = (empty($propertyData[SystemProperties::COLLECTION]) ||
169 25
            !is_array(
170 25
                $propertyData[SystemProperties::COLLECTION]
171 25
            )) ? [] : $propertyData[SystemProperties::COLLECTION];
172 25
        $this->systemProperties = Kernel::create(SystemProperties::class, [$systemPropertyData, $this]);
173
174
        // Instantiate the meta properties
175 24
        $metaPropertyData = (empty($propertyData[MetaProperties::COLLECTION]) ||
176 23
            !is_array(
177 23
                $propertyData[MetaProperties::COLLECTION]
178 24
            )) ? [] : $propertyData[MetaProperties::COLLECTION];
179
        /** @var MetaProperties $metaProperties */
180 24
        $metaProperties = Kernel::create(MetaProperties::class, [$metaPropertyData, $this]);
181 24
        $this->setMetaProperties($metaProperties, true);
182
183
        // Instantiate the domain properties
184 24
        $domainPropertyData = (empty($propertyData[AbstractDomainProperties::COLLECTION]) ||
185 23
            !is_array(
186 23
                $propertyData[AbstractDomainProperties::COLLECTION]
187 24
            )) ? [] : $propertyData[AbstractDomainProperties::COLLECTION];
188
        /** @var AbstractDomainProperties $domainProperties */
189 24
        $domainProperties = Kernel::create($this->domainPropertyCClass, [$domainPropertyData, $this]);
190 24
        $this->setDomainProperties($domainProperties, true);
191
192
        // Instantiate the processing instructions
193 24
        $procInstData = (empty($propertyData[ProcessingInstructions::COLLECTION]) ||
194 20
            !is_array(
195 20
                $propertyData[ProcessingInstructions::COLLECTION]
196 24
            )) ? [] : $propertyData[ProcessingInstructions::COLLECTION];
197
        /** @var ProcessingInstructions $procInstCollection */
198 24
        $procInstCollection = Kernel::create(ProcessingInstructions::class, [$procInstData, $this]);
199 24
        $this->setProcessingInstructions($procInstCollection, true);
200
201
        // Instantiate the object relations
202 24
        $relationData = (empty($propertyData[Relations::COLLECTION]) ||
203 23
            !is_array(
204 23
                $propertyData[Relations::COLLECTION]
205 24
            )) ? [] : $propertyData[Relations::COLLECTION];
206
        /** @var Relations $relationCollection */
207 24
        $relationCollection = Kernel::create(Relations::class, [$relationData, $this]);
208 24
        $this->setRelations($relationCollection, true);
209
210
        // Reset the object state to clean
211 24
        $this->state = self::STATE_CLEAN;
212 24
    }
213
214
    /**
215
     * Return whether the object is in mutated state
216
     *
217
     * @return boolean Mutated state
218
     */
219 3
    public function isMutated()
220
    {
221 3
        return !!($this->state & self::STATE_MUTATED);
222
    }
223
224
    /**
225
     * Use a specific object revision
226
     *
227
     * @param Revision $revision Revision to be used
228
     * @return ObjectInterface Object
229
     * @throws OutOfBoundsException If the requested revision is invalid
230
     */
231 23
    public function useRevision(Revision $revision)
232
    {
233 23
        $isCurrentRevision = false;
234
235
        // If the requested revision is invalid
236 23
        if (!$revision->isCurrent() &&
237
            (($revision->getRevision() < 1) || ($revision->getRevision() > $this->latestRevision->getRevision()))
238 23
        ) {
239
            throw new OutOfBoundsException(
240
                sprintf('Invalid object revision "%s"', $revision->getRevision()),
241
                OutOfBoundsException::INVALID_OBJECT_REVISION
242
            );
243
        }
244
245
        // If the current revision got requested
246 23
        if ($revision->isCurrent()) {
247 23
            $isCurrentRevision = true;
248 23
            $revision = $this->latestRevision;
249 23
        }
250
251
        // If the requested revision is not already used
252 23
        if ($revision != $this->getRevision()) {
253
            /** @var ManagerInterface $objectManager */
254
            $objectManager = Kernel::create(Service::class)->getObjectManager();
255
256
            // Load the requested object revision resource
257
            /** @var Revision $newRevision */
258
            $newRevision = $isCurrentRevision ? Revision::current() : $revision;
259
            /** @var RepositoryPath $newRevisionPath */
260
            $newRevisionPath = $this->path->setRevision($newRevision);
261
            $revisionResource = $objectManager->loadObject($newRevisionPath);
262
263
            // Load the revision resource data
264
            $this->loadRevisionData($revisionResource->getPayload(), $revisionResource->getPropertyData());
265
266
            // Set the current revision path
267
            $this->path = $newRevisionPath;
268
        }
269
270 23
        return $this;
271
    }
272
273
    /**
274
     * Return the object repository path
275
     *
276
     * @return RepositoryPathInterface Object repository path
277
     */
278 24
    public function getRepositoryPath()
279
    {
280 24
        return $this->path;
281
    }
282
283
    /**
284
     * Return the object property data
285
     *
286
     * @return array Object property data
287
     */
288 5
    public function getPropertyData()
289
    {
290 5
        $propertyData = array_filter([
291 5
            SystemProperties::COLLECTION => $this->systemProperties->toArray(),
292 5
            MetaProperties::COLLECTION => $this->metaProperties->toArray(),
293 5
            AbstractDomainProperties::COLLECTION => $this->domainProperties->toArray(),
294 5
            ProcessingInstructions::COLLECTION => $this->processingInstructions->toArray(),
295 5
            Relations::COLLECTION => $this->relations->toArray(),
296 5
        ], function (array $collection) {
297 5
            return (boolean)count($collection);
298 5
        });
299
300 5
        return $propertyData;
301
    }
302
303
    /**
304
     * Return the absolute object URL
305
     *
306
     * @return string
307
     */
308 4
    public function getAbsoluteUrl()
309
    {
310 4
        return getenv('APPARAT_BASE_URL').ltrim($this->path->getRepository()->getUrl(), '/').strval($this->path);
311
    }
312
313
    /**
314
     * Persist the current object revision
315
     *
316
     * @return ObjectInterface Object
317
     */
318 1
    public function persist()
319
    {
320
        // If this is not the latest revision
321 1
        if ($this->getRevision() != $this->latestRevision) {
322
            throw new RuntimeException(
323
                sprintf(
324
                    'Cannot persist revision %s/%s',
325
                    $this->getRevision()->getRevision(),
326
                    $this->latestRevision->getRevision()
327
                ),
328
                RuntimeException::CANNOT_PERSIST_EARLIER_REVISION
329
            );
330
        }
331
332
        // Update the object repository
333 1
        $this->path->getRepository()->updateObject($this);
334
335
        // Reset state
336 1
        $this->state = self::STATE_CLEAN;
337
338 1
        return $this;
339
    }
340
341
    /**
342
     * Publish the current object revision
343
     *
344
     * @return ObjectInterface Object
345
     */
346 1
    public function publish()
347
    {
348
        // If this is a draft
349 1
        if ($this->isDraft()) {
350
            // TODO: Send signal
351
352
            // Create draft system properties
353 1
            $this->systemProperties = $this->systemProperties->publish();
354
355
            // Adapt the system properties collection state
356 1
            $this->collectionStates[SystemProperties::COLLECTION] = spl_object_hash($this->systemProperties);
357
358
            // Set the draft flag on the repository path
359 1
            $this->path = $this->path->setDraft(false);
360
361
            // Flag this object as dirty
362 1
            $this->setPublishedState();
363 1
        }
364
365 1
        return $this;
366
    }
367
368
    /**
369
     * Return the object draft mode
370
     *
371
     * @return boolean Object draft mode
372
     */
373 5
    public function isDraft()
374
    {
375 5
        return $this->systemProperties->isDraft() || $this->isPublished();
376
    }
377
378
    /**
379
     * Return whether the object is in published state
380
     *
381
     * @return boolean Published state
382
     */
383 5
    public function isPublished()
384
    {
385 5
        return !!($this->state & self::STATE_PUBLISHED);
386
    }
387
388
    /**
389
     * Set the object state to published
390
     */
391 1
    protected function setPublishedState()
392
    {
393
        // If this object is not in dirty state yet
394 1
        if (!($this->state & self::STATE_PUBLISHED)) {
395
            // TODO: Send signal
396 1
        }
397
398
        // Enable the dirty state
399 1
        $this->state |= (self::STATE_DIRTY | self::STATE_PUBLISHED);
400 1
    }
401
402
    /**
403
     * Delete the object and all its revisions
404
     *
405
     * @return ObjectInterface Object
406
     */
407
    public function delete()
408
    {
409
        // TODO: Implement delete() method.
410
        $this->setDeletionState(true);
411
        return $this;
412
    }
413
414
    /**
415
     * Undelete the object and all its revisions
416
     *
417
     * @return ObjectInterface Object
418
     */
419
    public function undelete()
420
    {
421
        // TODO: Implement undelete() method.
422
        $this->setDeletionState(false);
423
        return $this;
424
    }
425
426
    /**
427
     * Return whether the object is in dirty state
428
     *
429
     * @return boolean Dirty state
430
     */
431 3
    public function isDirty()
432
    {
433 3
        return !!($this->state & self::STATE_DIRTY);
434
    }
435
436
    /**
437
     * Set the object state to mutated
438
     */
439 4
    protected function setMutatedState()
440
    {
441
        // If this object is not in mutated state yet
442 4
        if (!($this->state & self::STATE_MUTATED) && !$this->isDraft()) {
443
            // TODO: Send signal
444 4
            $this->convertToDraft();
445 4
        }
446
447
        // Enable the mutated state
448 4
        $this->state |= self::STATE_MUTATED;
449
450
        // Enable the dirty state
451 4
        $this->setDirtyState();
452 4
    }
453
454
    /**
455
     * Convert this object revision into a draft
456
     */
457 4
    protected function convertToDraft()
458
    {
459
        // Increment the latest revision number
460 4
        $this->latestRevision = $this->latestRevision->increment();
461
462
        // Create draft system properties
463 4
        $this->systemProperties = $this->systemProperties->createDraft($this->latestRevision);
464
465
        // Adapt the system properties collection state
466 4
        $this->collectionStates[SystemProperties::COLLECTION] = spl_object_hash($this->systemProperties);
467
468
        // Set the draft flag on the repository path
469 4
        $this->path = $this->path->setDraft(true)->setRevision(Revision::current());
470
471
        // If this is not already a draft ...
472
        // Recreate the system properties
473
        // Copy the object ID
474
        // Copy the object type
475
        // Set the revision number to latest revision + 1
476
        // Set the creation date to now
477
        // Set no publication date
478
        // Set the draft flag on the repository path
479
        // Increase the latest revision by 1
480
481
        // Else if this is a draft
482
        // No action needed
483 4
    }
484
485
    /**
486
     * Set the object state to dirty
487
     */
488 7
    protected function setDirtyState()
489
    {
490
        // If this object is not in dirty state yet
491 7
        if (!($this->state & self::STATE_DIRTY)) {
492
            // TODO: Send signal
493 7
        }
494
495
        // Enable the dirty state
496 7
        $this->state |= self::STATE_DIRTY;
497
498
        // Update the modification timestamp
499 7
        $this->setSystemProperties($this->systemProperties->touch(), true);
500 7
    }
501
502
    /**
503
     * Set the object deletion state
504
     *
505
     * @param boolean $deleted Deletion state
506
     */
507
    protected function setDeletionState($deleted)
508
    {
509
        $deleted = boolval($deleted);
510
511
        // If this object is not in the requested deltion state yet
512
        if ($deleted != boolval($this->state & self::STATE_DELETED)) {
513
            // TODO: Send signal
514
        }
515
516
        // If the object should be deleted
517
        if ($deleted) {
518
            $this->state |= self::STATE_DELETED;
519
520
            // Set the deletion timestamp
521
            $this->setSystemProperties($this->systemProperties->delete(), true);
522
            return;
523
        }
524
525
        // Undelete the object
526
        $this->state &= ~self::STATE_DELETED;
527
528
        // Unset the deletion timestamp
529
        $this->setSystemProperties($this->systemProperties->undelete(), true);
530
    }
531
}
532