Completed
Push — master ( 91ba10...42ada5 )
by Joschi
02:37
created

AbstractObject::addAuthor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
cc 1
eloc 5
nc 1
nop 1
crap 1
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\Author\AuthorInterface;
41
use Apparat\Object\Domain\Model\Path\RepositoryPath;
42
use Apparat\Object\Domain\Model\Path\RepositoryPathInterface;
43
use Apparat\Object\Domain\Model\Properties\AbstractDomainProperties;
44
use Apparat\Object\Domain\Model\Properties\InvalidArgumentException as PropertyInvalidArgumentException;
45
use Apparat\Object\Domain\Model\Properties\MetaProperties;
46
use Apparat\Object\Domain\Model\Properties\ProcessingInstructions;
47
use Apparat\Object\Domain\Model\Properties\Relations;
48
use Apparat\Object\Domain\Model\Properties\SystemProperties;
49
use Apparat\Object\Domain\Repository\Service;
50
51
/**
52
 * Abstract object
53
 *
54
 * @package Apparat\Object
55
 * @subpackage Apparat\Object\Domain
56
 */
57
abstract class AbstractObject implements ObjectInterface
58
{
59
    /**
60
     * Clean state
61
     *
62
     * @var int
63
     */
64
    const STATE_CLEAN = 0;
65
    /**
66
     * Dirty state
67
     *
68
     * @var int
69
     */
70
    const STATE_DIRTY = 1;
71
    /**
72
     * Mutated state
73
     *
74
     * @var int
75
     */
76
    const STATE_MUTATED = 2;
77
    /**
78
     * System properties
79
     *
80
     * @var SystemProperties
81
     */
82
    protected $systemProperties;
83
    /**
84
     * Meta properties
85
     *
86
     * @var MetaProperties
87
     */
88
    protected $metaProperties;
89
    /**
90
     * Domain properties
91
     *
92
     * @var AbstractDomainProperties
93
     */
94
    protected $domainProperties;
95
    /**
96
     * Object payload
97
     *
98
     * @var string
99
     */
100
    protected $payload;
101
    /**
102
     * Repository path
103
     *
104
     * @var RepositoryPathInterface
105
     */
106
    protected $path;
107
    /**
108
     * Domain property collection class
109
     *
110
     * @var string
111
     */
112
    protected $domainPropertyCClass = AbstractDomainProperties::class;
113
    /**
114
     * Object relations
115
     *
116
     * @var Relations
117
     */
118
    protected $relations;
119
    /**
120
     * Processing instructions
121
     *
122
     * @var ProcessingInstructions
123
     */
124
    protected $processingInstructions;
125
    /**
126
     * Latest revision index
127
     *
128
     * @var Revision
129
     */
130
    protected $latestRevision;
131
    /**
132
     * Object state
133
     *
134
     * @var int
135
     */
136
    protected $state = self::STATE_CLEAN;
137
    /**
138
     * Property collection states
139
     *
140
     * @var array
141
     */
142
    protected $collectionStates = [];
143
144
    /**
145
     * Object constructor
146
     *
147
     * @param string $payload Object payload
148
     * @param array $propertyData Property data
149
     * @param RepositoryPathInterface $path Object repository path
150
     */
151 19
    public function __construct($payload = '', array $propertyData = [], RepositoryPathInterface $path = null)
152
    {
153
        // If the domain property collection class is invalid
154 19
        if (!$this->domainPropertyCClass
155 19
            || !class_exists($this->domainPropertyCClass)
156 19
            || !(new \ReflectionClass($this->domainPropertyCClass))->isSubclassOf(AbstractDomainProperties::class)
157
        ) {
158 1
            throw new PropertyInvalidArgumentException(
159
                sprintf(
160 1
                    'Invalid domain property collection class "%s"',
161 1
                    $this->domainPropertyCClass
162
                ),
163 1
                PropertyInvalidArgumentException::INVALID_DOMAIN_PROPERTY_COLLECTION_CLASS
164
            );
165
        }
166
167 18
        $this->path = $path;
168
169
        // Load the current revision data
170 18
        $this->loadRevisionData($payload, $propertyData);
171
172
        // Save the latest revision index
173 16
        $this->latestRevision = $this->getRevision();
174 16
    }
175
176
    /**
177
     * Load object revision data
178
     *
179
     * @param string $payload Object payload
180
     * @param array $propertyData Property data
181
     */
182 18
    protected function loadRevisionData($payload = '', array $propertyData = [])
183
    {
184
        // TODO Add mutation handler for object payload
185 18
        $this->payload = $payload;
186
187
        // Instantiate the system properties
188 18
        $systemPropertyData = (empty($propertyData[SystemProperties::COLLECTION]) ||
189 18
            !is_array(
190 18
                $propertyData[SystemProperties::COLLECTION]
191 18
            )) ? [] : $propertyData[SystemProperties::COLLECTION];
192 18
        $this->systemProperties = Kernel::create(SystemProperties::class, [$systemPropertyData, $this]);
193
194
        // Instantiate the meta properties
195 17
        $metaPropertyData = (empty($propertyData[MetaProperties::COLLECTION]) ||
196 16
            !is_array(
197 17
                $propertyData[MetaProperties::COLLECTION]
198 17
            )) ? [] : $propertyData[MetaProperties::COLLECTION];
199
        /** @var MetaProperties $metaPropertyCollection */
200 17
        $metaPropertyCollection = Kernel::create(MetaProperties::class, [$metaPropertyData, $this]);
201 16
        $this->setMetaProperties($metaPropertyCollection, true);
202
203
        // Instantiate the domain properties
204 16
        $domainPropertyData = (empty($propertyData[AbstractDomainProperties::COLLECTION]) ||
205 15
            !is_array(
206 16
                $propertyData[AbstractDomainProperties::COLLECTION]
207 16
            )) ? [] : $propertyData[AbstractDomainProperties::COLLECTION];
208
        // TODO Add mutation handler for domain properties
209 16
        $this->domainProperties = Kernel::create($this->domainPropertyCClass, [$domainPropertyData, $this]);
210
211
        // Instantiate the processing instructions
212 16
        $procInstData = (empty($propertyData[ProcessingInstructions::COLLECTION]) ||
213 12
            !is_array(
214 16
                $propertyData[ProcessingInstructions::COLLECTION]
215 16
            )) ? [] : $propertyData[ProcessingInstructions::COLLECTION];
216 16
        $this->processingInstructions = Kernel::create(ProcessingInstructions::class, [$procInstData, $this]);
217
218
        // Instantiate the object relations
219 16
        $relationData = (empty($propertyData[Relations::COLLECTION]) ||
220 12
            !is_array(
221 16
                $propertyData[Relations::COLLECTION]
222 16
            )) ? [] : $propertyData[Relations::COLLECTION];
223 16
        $this->relations = Kernel::create(Relations::class, [$relationData, $this]);
224
225
        // Reset the object state to clean
226 16
        $this->state = self::STATE_CLEAN;
227 16
    }
228
229
    /**
230
     * Set the meta properties collection
231
     *
232
     * @param MetaProperties $metaProperties Meta property collection
233
     * @param bool $overwrite Overwrite the existing collection (if present)
234
     */
235 16
    protected function setMetaProperties(MetaProperties $metaProperties, $overwrite = false)
236
    {
237 16
        $this->metaProperties = $metaProperties;
238 16
        $metaPropertiesState = spl_object_hash($this->metaProperties);
239
240
        // If the meta property collection state has changed
241 16
        if (!$overwrite && !empty($this->collectionStates[MetaProperties::COLLECTION]) &&
242 16
            ($metaPropertiesState !== $this->collectionStates[MetaProperties::COLLECTION])
243
        ) {
244
            // Flag this object as mutated
245 1
            $this->setMutatedState();
246
        }
247
248 16
        $this->collectionStates[MetaProperties::COLLECTION] = $metaPropertiesState;
249 16
    }
250
251
    /**
252
     * Set the object state to mutated
253
     */
254 1
    protected function setMutatedState()
255
    {
256
        // If this object is not in mutated state yet
257 1
        if (!($this->state & self::STATE_MUTATED) && !$this->isDraft()) {
258
            // Increment the latest revision number
259 1
            $this->latestRevision = $this->latestRevision->increment();
260
261
            // Create draft system properties
262 1
            $this->systemProperties = $this->systemProperties->createDraft($this->latestRevision);
263
264
            // Set the draft flag on the repository path
265 1
            $this->path = $this->path->setDraft(true);
266
267
            // If this is not already a draft ...
268
                // Recreate the system properties
269
                    // Copy the object ID
270
                    // Copy the object type
271
                    // Set the revision number to latest revision + 1
272
                    // Set the creation date to now
273
                    // Set no publication date
274
                // Set the draft flag on the repository path
275
                // Increase the latest revision by 1
276
277
            // Else if this is a draft
278
                // No action needed
279
        }
280
281
        // Enable the mutated (and dirty) state
282 1
        $this->state |= (self::STATE_DIRTY | self::STATE_MUTATED);
283 1
    }
284
285
    /**
286
     * Return the object revision
287
     *
288
     * @return Revision Object revision
289
     */
290 16
    public function getRevision()
291
    {
292 16
        return $this->systemProperties->getRevision();
293
    }
294
295
    /**
296
     * Return whether the object is in dirty state
297
     *
298
     * @return boolean Dirty state
299
     */
300
    public function isDirty()
301
    {
302
        return !!($this->state & self::STATE_DIRTY);
303
    }
304
305
    /**
306
     * Return whether the object is in mutated state
307
     *
308
     * @return boolean Mutated state
309
     */
310
    public function isMutated()
311
    {
312
        return !!($this->state & self::STATE_MUTATED);
313
    }
314
315
    /**
316
     * Return the object draft mode
317
     *
318
     * @return boolean Object draft mode
319
     */
320 2
    public function isDraft()
321
    {
322 2
        return $this->systemProperties->isDraft();
323
    }
324
325
    /**
326
     * Use a specific object revision
327
     *
328
     * @param Revision $revision Revision to be used
329
     * @return ObjectInterface Object
330
     * @throws OutOfBoundsException If the requested revision is invalid
331
     */
332 15
    public function useRevision(Revision $revision)
333
    {
334 15
        $isCurrentRevision = false;
335
336
        // If the requested revision is invalid
337 15
        if (!$revision->isCurrent() &&
338 15
            (($revision->getRevision() < 1) || ($revision->getRevision() > $this->latestRevision->getRevision()))
339
        ) {
340
            throw new OutOfBoundsException(sprintf('Invalid object revision "%s"', $revision->getRevision()),
341
                OutOfBoundsException::INVALID_OBJECT_REVISION);
342
        }
343
344
        // If the current revision got requested
345 15
        if ($revision->isCurrent()) {
346 15
            $isCurrentRevision = true;
347 15
            $revision = $this->latestRevision;
348
        }
349
350
        // If the requested revision is not already used
351 15
        if ($revision != $this->getRevision()) {
352
            /** @var ManagerInterface $objectManager */
353
            $objectManager = Kernel::create(Service::class)->getObjectManager();
354
355
            // Load the requested object revision resource
356
            /** @var Revision $newRevision */
357
            $newRevision = $isCurrentRevision ? Kernel::create(Revision::class, [Revision::CURRENT]) :
358
                $revision;
359
            /** @var RepositoryPath $newRevisionPath */
360
            $newRevisionPath = $this->path->setRevision($newRevision);
361
            $revisionResource = $objectManager->loadObject($newRevisionPath);
362
363
            // Load the revision resource data
364
            $this->loadRevisionData($revisionResource->getPayload(), $revisionResource->getPropertyData());
365
366
            // Set the current revision path
367
            $this->path = $newRevisionPath;
368
        }
369
370 15
        return $this;
371
    }
372
373
    /**
374
     * Return the object ID
375
     *
376
     * @return Id Object ID
377
     */
378 5
    public function getId()
379
    {
380 5
        return $this->systemProperties->getId();
381
    }
382
383
    /**
384
     * Return the object type
385
     *
386
     * @return Type Object type
387
     */
388 1
    public function getType()
389
    {
390 1
        return $this->systemProperties->getType();
391
    }
392
393
    /**
394
     * Return the creation date & time
395
     *
396
     * @return \DateTimeImmutable Creation date & time
397
     */
398 1
    public function getCreated()
399
    {
400 1
        return $this->systemProperties->getCreated();
401
    }
402
403
    /**
404
     * Return the publication date & time
405
     *
406
     * @return \DateTimeImmutable Publication date & time
407
     */
408 1
    public function getPublished()
409
    {
410 1
        return $this->systemProperties->getPublished();
411
    }
412
413
    /**
414
     * Return the object hash
415
     *
416
     * @return string Object hash
417
     */
418 1
    public function getHash()
419
    {
420 1
        return $this->systemProperties->getHash();
421
    }
422
423
    /**
424
     * Return the object description
425
     *
426
     * @return string Object description
427
     */
428 2
    public function getDescription()
429
    {
430 2
        return $this->metaProperties->getDescription();
431
    }
432
433
    /**
434
     * Set the description
435
     *
436
     * @param string $description Description
437
     * @return ObjectInterface Self reference
438
     */
439 1
    public function setDescription($description)
440
    {
441 1
        $this->setMetaProperties($this->metaProperties->setDescription($description));
442 1
        return $this;
443
    }
444
445
    /**
446
     * Return the object abstract
447
     *
448
     * @return string Object abstract
449
     */
450 1
    public function getAbstract()
451
    {
452 1
        return $this->metaProperties->getAbstract();
453
    }
454
455
    /**
456
     * Set the abstract
457
     *
458
     * @param string $abstract Abstract
459
     * @return ObjectInterface Self reference
460
     */
461
    public function setAbstract($abstract)
462
    {
463
        $this->setMetaProperties($this->metaProperties->setAbstract($abstract));
464
        return $this;
465
    }
466
467
    /**
468
     * Return all object keywords
469
     *
470
     * @return array Object keywords
471
     */
472 1
    public function getKeywords()
473
    {
474 1
        return $this->metaProperties->getKeywords();
475
    }
476
477
    /**
478
     * Set the keywords
479
     *
480
     * @param array $keywords Keywords
481
     * @return ObjectInterface Self reference
482
     */
483
    public function setKeywords(array $keywords)
484
    {
485
        $this->setMetaProperties($this->metaProperties->setKeywords($keywords));
486
        return $this;
487
    }
488
489
    /**
490
     * Return all object categories
491
     *
492
     * @return array Object categories
493
     */
494 1
    public function getCategories()
495
    {
496 1
        return $this->metaProperties->getCategories();
497
    }
498
499
    /**
500
     * Set the categories
501
     *
502
     * @param array $categories Categories
503
     * @return ObjectInterface Self reference
504
     */
505
    public function setCategories(array $categories)
506
    {
507
        $this->setMetaProperties($this->metaProperties->setCategories($categories));
508
        return $this;
509
    }
510
511
    /**
512
     * Return all object authors
513
     *
514
     * @return AuthorInterface[] Authors
515
     */
516 2
    public function getAuthors()
517
    {
518 2
        return $this->metaProperties->getAuthors();
519
    }
520
521
    /**
522
     * Add an object author
523
     *
524
     * @param AuthorInterface $author Author
525
     * @return ObjectInterface Self reference
526
     */
527 1
    public function addAuthor(AuthorInterface $author)
528
    {
529 1
        $authors = $this->metaProperties->getAuthors();
530 1
        $authors[] = $author;
531 1
        $this->metaProperties->setAuthors($authors);
532 1
        return $this;
533
    }
534
535
    /**
536
     * Return the object repository path
537
     *
538
     * @return RepositoryPathInterface Object repository path
539
     */
540 16
    public function getRepositoryPath()
541
    {
542 16
        return $this->path;
543
    }
544
545
    /**
546
     * Return the object property data
547
     *
548
     * @return array Object property data
549
     */
550 3
    public function getPropertyData()
551
    {
552 3
        $propertyData = array_filter([
553 3
            SystemProperties::COLLECTION => $this->systemProperties->toArray(),
554 3
            MetaProperties::COLLECTION => $this->metaProperties->toArray(),
555 3
            AbstractDomainProperties::COLLECTION => $this->domainProperties->toArray(),
556 3
            ProcessingInstructions::COLLECTION => $this->processingInstructions->toArray(),
557 3
            Relations::COLLECTION => $this->relations->toArray(),
558 3
        ], function (array $collection) {
559 3
            return (boolean)count($collection);
560 3
        });
561
562 3
        return $propertyData;
563
    }
564
565
    /**
566
     * Return the object payload
567
     *
568
     * @return string Object payload
569
     */
570 2
    public function getPayload()
571
    {
572 2
        return $this->payload;
573
    }
574
575
    /**
576
     * Return the absolute object URL
577
     *
578
     * @return string
579
     */
580 2
    public function getAbsoluteUrl()
581
    {
582 2
        return getenv('APPARAT_BASE_URL').ltrim($this->path->getRepository()->getUrl(), '/').strval($this->path);
583
    }
584
585
    /**
586
     * Get a particular property value
587
     *
588
     * Multi-level properties might be traversed by property name paths separated with colons (":").
589
     *
590
     * @param string $property Property name
591
     * @return mixed Property value
592
     */
593 2
    public function getDomainProperty($property)
594
    {
595 2
        return $this->domainProperties->getProperty($property);
596
    }
597
598
    protected function setDirtyState()
599
    {
600
601
        // If this object is not in dirty state yet
602
        if (!($this->state & self::STATE_DIRTY)) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
603
604
        }
605
606
        // Enable the dirty state
607
        $this->state |= self::STATE_DIRTY;
608
    }
609
}
610