Completed
Push — master ( 8a4d0b...126fd8 )
by Stéphane
12:10
created

Entity::unpublishOtherRevisions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 7
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 12
ccs 8
cts 8
cp 1
crap 2
rs 9.4285
1
<?php namespace Rocket\Entities;
2
3
use Illuminate\Support\Collection;
4
use Illuminate\Support\Facades\DB;
5
use InvalidArgumentException;
6
use Rocket\Entities\Exceptions\InvalidFieldTypeException;
7
use Rocket\Entities\Exceptions\NonExistentFieldException;
8
use Rocket\Entities\Exceptions\ReservedFieldNameException;
9
10
/**
11
 * Entity manager
12
 *
13
 * @property int $id The content ID
14
 * @property int $language_id The language in which this entity is
15
 * @property string $type The type of the Entity
16
 * @property boolean $published Is this content published
17
 * @property array<Rocket\Entities\Revision> $revisions all revisions in this content
18
 * @property-read \DateTime $created_at
19
 * @property-read \DateTime $updated_at
20
 */
21
abstract class Entity
22
{
23
    /**
24
     * @var array<class> The list of field types, filled with the configuration
25
     */
26
    public static $types;
27
28
    /**
29
     * The content represented by this entity
30
     *
31
     * @var Content
32
     */
33
    protected $content; //id, created_at, type, published
34
35
    /**
36
     * The revision represented by this entity
37
     *
38
     * @var Revision
39
     */
40
    protected $revision; //language_id, updated_at, type, published
41
42
    /**
43
     * The fields in this entity
44
     *
45
     * @var array<FieldCollection>
46
     */
47
    protected $data;
48
49
    /**
50
     * Entity constructor.
51
     *
52
     * @param int $language_id The language this specific entity is in
53
     */
54 57
    public function __construct($language_id)
55
    {
56 57
        if (!is_int($language_id) || $language_id == 0) {
57 3
            throw new InvalidArgumentException("You must set a valid 'language_id'.");
58
        }
59
60 54
        $fields = $this->getFields();
61
62 54
        $this->initialize($fields);
63
64 48
        $this->type = $this->getContentType();
65 48
        $this->language_id = $language_id;
66 48
    }
67
68
    /**
69
     * Creates the Content, Revision and FieldCollections
70
     *
71
     * @param array $fields The fields and their configurations
72
     * @throws InvalidFieldTypeException
73
     * @throws ReservedFieldNameException
74
     */
75 54
    protected function initialize(array $fields)
76
    {
77 54
        $this->content = new Content;
78 54
        $this->revision = new Revision;
79
80 54
        foreach ($fields as $field => $settings) {
81 54
            $this->data[$field] = $this->initializeField($field, $settings);
82 48
        }
83 48
    }
84
85
    /**
86
     * Validate configuration and prepare a FieldCollection
87
     *
88
     * @param string $field
89
     * @param array $settings
90
     * @throws InvalidFieldTypeException
91
     * @throws ReservedFieldNameException
92
     * @return FieldCollection
93
     */
94 54
    protected function initializeField($field, $settings)
95
    {
96 54
        if ($this->isContentField($field) || $this->isRevisionField($field)) {
97 3
            throw new ReservedFieldNameException(
98 3
                "The field '$field' cannot be used in '" . get_class($this) . "' as it is a reserved name"
99 3
            );
100
        }
101
102 51
        $type = $settings['type'];
103
104 51
        if (!array_key_exists($type, self::$types)) {
105 3
            throw new InvalidFieldTypeException("Unkown type '$type' in '" . get_class($this) . "'");
106
        }
107
108 48
        $settings['type'] = self::$types[$settings['type']];
109
110 48
        return FieldCollection::initField($settings);
111
    }
112
113
    /**
114
     * Return the fields in this entity
115
     *
116
     * @return array
117
     */
118
    abstract public function getFields();
119
120
    /**
121
     * Get the database friendly content type
122
     *
123
     * @return string
124
     */
125 54
    public static function getContentType()
126
    {
127 54
        return str_replace('\\', '', snake_case((new \ReflectionClass(get_called_class()))->getShortName()));
128
    }
129
130
    /**
131
     * Create a new revision based on the same content ID but without the content.
132
     * Very useful if you want to add a new language
133
     *
134
     * @param int $language_id
135
     * @return static
136
     */
137 3
    public function newRevision($language_id = null)
138
    {
139 3
        $created = new static($language_id ?: $this->language_id);
140 3
        $created->content = $this->content;
141
142 3
        return $created;
143
    }
144
145
    /**
146
     * Check if the field is related to the content
147
     *
148
     * @param string $field
149
     * @return bool
150
     */
151 54
    protected function isContentField($field)
152
    {
153 54
        return in_array($field, ['id', 'created_at', 'type', 'published']);
154
    }
155
156
    /**
157
     * Check if the field exists on the entity
158
     *
159
     * @param string $field
160
     * @return bool
161
     */
162 42
    public function hasField($field)
163
    {
164 42
        return array_key_exists($field, $this->data);
165
    }
166
167
    /**
168
     * @param string $field
169
     * @return FieldCollection
170
     */
171 36
    public function getField($field)
172
    {
173 36
        return $this->data[$field];
174
    }
175
176
    /**
177
     * Check if the field is related to the revision
178
     *
179
     * @param string $field
180
     * @return bool
181
     */
182 51
    protected function isRevisionField($field)
183
    {
184 51
        return in_array($field, ['language_id', 'updated_at', 'published']);
185
    }
186
187
    /**
188
     * Dynamically retrieve attributes on the model.
189
     *
190
     * @param string $key
191
     * @throws NonExistentFieldException
192
     * @return $this|bool|\Carbon\Carbon|\DateTime|mixed|static
193
     */
194 42
    public function __get($key)
195
    {
196 42
        if ($this->isContentField($key)) {
197 18
            return $this->content->getAttribute($key);
198
        }
199
200 39
        if ($this->isRevisionField($key)) {
201 6
            return $this->revision->getAttribute($key);
202
        }
203
204 36
        if ($this->hasField($key)) {
205 33
            return $this->getField($key);
206
        }
207
208 10
        if ($key == 'revisions') {
209 7
            return $this->content->revisions;
0 ignored issues
show
Documentation introduced by
The property revisions does not exist on object<Rocket\Entities\Content>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
210
        }
211
212 3
        throw new NonExistentFieldException("Field '$key' doesn't exist in '" . get_class($this) . "'");
213
    }
214
215
    /**
216
     * Dynamically set attributes on the model.
217
     *
218
     * @param string $key
219
     * @param mixed $value
220
     * @throws NonExistentFieldException
221
     */
222 48
    public function __set($key, $value)
223
    {
224 48
        if ($this->isContentField($key)) {
225 48
            $this->content->setAttribute($key, $value);
226
227 48
            return;
228
        }
229
230 48
        if ($this->isRevisionField($key)) {
231 48
            $this->revision->setAttribute($key, $value);
232
233 48
            return;
234
        }
235
236 21
        if ($this->hasField($key)) {
237 18
            $field = $this->getField($key);
238
239 18
            if (is_array($value)) {
240 12
                $field->clear();
241
242
                // This happens when the array is replaced completely by another array
243 12
                if (count($value)) {
244 3
                    foreach ($value as $k => $v) {
245 3
                        $field->offsetSet($k, $v);
246 3
                    }
247 3
                }
248
249 12
                return;
250
            }
251
252 6
            $field->offsetSet(0, $value);
253
254 6
            return;
255
        }
256
257 3
        throw new NonExistentFieldException("Field '$key' doesn't exist in '" . get_class($this) . "'");
258
    }
259
260
    /**
261
     * Find the latest valid revision for this entity
262
     *
263
     * @param int $id
264
     * @param int $language_id
265
     * @return static
266
     */
267 12
    public static function find($id, $language_id)
268
    {
269 12
        $instance = new static($language_id);
270
271 12
        $instance->content = Content::findOrFail($id);
272
273 12
        $instance->revision = Revision::where('content_id', $id)
274 12
            ->where('language_id', $language_id)
275 12
            ->where('published', true)
276 12
            ->firstOrFail();
277
278 12
        (new Collection($instance->getFields()))
279
            ->map(function ($options) {
280 12
                return $options['type'];
281 12
            })
282 12
            ->values()
283 12
            ->unique()
284
            ->map(function ($type) {
285 12
                return self::$types[$type];
286 12
            })
287
            ->each(function ($type) use ($instance) {
288 12
                $type::where('revision_id', $instance->revision->id)
289 12
                    ->get()
290
                    ->each(function (Field $value) use ($instance) {
291 12
                        $instance->data[$value->name][$value->weight] = $value;
292 12
                    });
293 12
            });
294
295 12
        return $instance;
296
    }
297
298
    /**
299
     * Save a revision
300
     *
301
     * @param bool $newRevision Should we create a new revision, false by default
302
     * @param bool $publishRevision Should we immediately publish this revision, true by default
303
     */
304 15
    public function save($newRevision = false, $publishRevision = true)
305
    {
306 15
        if ($newRevision) {
307 6
            $revision = new Revision;
308 6
            $revision->language_id = $this->revision->language_id;
309
310 6
            $this->revision = $revision;
311 6
        }
312
313 15
        DB::transaction(
314
            function () use ($newRevision, $publishRevision) {
315 15
                $this->saveContent();
316
317 15
                $this->saveRevision($publishRevision);
318
319
                // Prepare and save fields
320 15
                foreach (array_keys($this->data) as $fieldName) {
321
                    /** @var FieldCollection $field */
322 15
                    $field = $this->data[$fieldName];
323
324 15
                    if (!$newRevision) {
325
                        $field->deleted()->each(function (Field $value) {
326 3
                            $value->delete();
327 15
                        });
328 15
                    }
329
330 15
                    $field->each(function (Field $value, $key) use ($newRevision, $fieldName) {
331 15
                        $value->weight = $key;
332 15
                        $value->name = $fieldName;
333 15
                        $this->saveField($value, $newRevision);
334 15
                    });
335
336 15
                    $field->syncOriginal();
337 15
                }
338 15
            }
339 15
        );
340 15
    }
341
342
    /**
343
     * Save the content
344
     */
345 15
    protected function saveContent()
346
    {
347 15
        $this->content->save();
348 15
    }
349
350
    /**
351
     * Save the revision
352
     *
353
     * @param bool $publishRevision Should we immediately publish this revision, true by default
354
     */
355 15
    protected function saveRevision($publishRevision)
356
    {
357 15
        if (!$this->revision->exists && !$publishRevision) {
358 3
            $this->revision->published = $publishRevision;
359 3
        }
360
361 15
        $this->revision->content_id = $this->content->id;
362 15
        $this->revision->save();
363
364 15
        if ($publishRevision) {
365 15
            $this->unpublishOtherRevisions();
366 15
        }
367 15
    }
368
369
    /**
370
     * Unpublish the revisions other than this one.
371
     * Only for the same content_id and language_id
372
     */
373 15
    protected function unpublishOtherRevisions()
374
    {
375 15
        if ($this->content->wasRecentlyCreated) {
376 15
            return;
377
        }
378
379
        // Unpublish all other revisions
380 6
        Revision::where('content_id', $this->content->id)
381 6
            ->where('language_id', $this->revision->language_id)
382 6
            ->where('id', '!=', $this->revision->id)
383 6
            ->update(['published' => false]);
384 6
    }
385
386
    /**
387
     * Save a single field instance
388
     *
389
     * @param Field $field The field instance to save
390
     * @param boolean $newRevision Should we create a new revision?
391
     */
392 15
    protected function saveField(Field $field, $newRevision)
393
    {
394
        // If we create a new revision, this will
395
        // reinit the field to a non-saved field
396
        // and create a new row in the database
397 15
        if ($newRevision) {
398 6
            $field->id = null;
399 6
            $field->exists = false;
400 6
        }
401
402 15
        $field->revision_id = $this->revision->id;
403
404 15
        $field->save();
405 15
    }
406
407
    /**
408
     * Convert the Entity to an array.
409
     *
410
     * @return array
411
     */
412 16
    public function toArray()
413
    {
414
        $content = [
415 16
            '_content' => $this->content->toArray(),
416 16
            '_revision' => $this->revision->toArray(),
417 16
        ];
418
419 16
        foreach ($this->data as $field => $data) {
420 16
            $content[$field] = $data->toArray();
421 16
        }
422
423 16
        return $content;
424
    }
425
}
426