Completed
Push — master ( bfdf74...8a4d0b )
by Stéphane
13:35
created

Entity::__construct()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 6
Bugs 1 Features 1
Metric Value
cc 3
eloc 7
c 6
b 1
f 1
nc 2
nop 1
dl 0
loc 13
rs 9.4285
ccs 8
cts 8
cp 1
crap 3
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-read \DateTime $created_at
16
 * @property-read \DateTime $updated_at
17
 */
18
abstract class Entity
19
{
20
    public static $types;
21
22
    /**
23
     * The content represented by this entity
24
     *
25
     * @var Content
26
     */
27
    protected $content; //id, created_at
28
29
    /**
30
     * The revision represented by this entity
31
     *
32
     * @var Revision
33
     */
34
    protected $revision; //language_id, updated_at
35
36
    /**
37
     * The fields in this entity
38
     *
39
     * @var array<FieldCollection>
40
     */
41
    protected $data;
42
43
    /**
44
     * Entity constructor.
45
     *
46
     * @param int $language_id The language this specific entity is in
47
     */
48 57
    public function __construct($language_id)
49
    {
50 57
        if (!is_int($language_id) || $language_id == 0) {
51 3
            throw new InvalidArgumentException("You must set a valid 'language_id'.");
52
        }
53
54 54
        $fields = $this->getFields();
55
56 54
        $this->initialize($fields);
57
58 48
        $this->type = $this->getContentType();
0 ignored issues
show
Documentation introduced by
The property type does not exist on object<Rocket\Entities\Entity>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
59 48
        $this->language_id = $language_id;
60 48
    }
61
62 54
    protected function initialize(array $fields)
63
    {
64 54
        $this->content = new Content;
65 54
        $this->revision = new Revision;
66
67 54
        foreach ($fields as $field => $settings) {
68 54
            $this->data[$field] = $this->initializeField($field, $settings);
69 48
        }
70 48
    }
71
72
    /**
73
     * @param string $field
74
     * @param array $settings
75
     * @throws InvalidFieldTypeException
76
     * @throws ReservedFieldNameException
77
     * @return FieldCollection
78
     */
79 54
    protected function initializeField($field, $settings)
80
    {
81 54
        if ($this->isContentField($field) || $this->isRevisionField($field)) {
82 3
            throw new ReservedFieldNameException(
83 3
                "The field '$field' cannot be used in '" . get_class($this) . "' as it is a reserved name"
84 3
            );
85
        }
86
87 51
        $type = $settings['type'];
88
89 51
        if (!array_key_exists($type, self::$types)) {
90 3
            throw new InvalidFieldTypeException("Unkown type '$type' in '" . get_class($this) . "'");
91
        }
92
93 48
        $settings['type'] = self::$types[$settings['type']];
94
95 48
        return FieldCollection::initField($settings);
96
    }
97
98
    abstract public function getFields();
99
100
    /**
101
     * Get the database friendly content type
102
     *
103
     * @return string
104
     */
105 54
    public static function getContentType()
106
    {
107 54
        return str_replace('\\', '', snake_case((new \ReflectionClass(get_called_class()))->getShortName()));
108
    }
109
110
    /**
111
     * Create a new revision based on the same content ID but without the content.
112
     * Very useful if you want to add a new language
113
     *
114
     * @param int $language_id
115
     * @return static
116
     */
117 3
    public function newRevision($language_id = null)
118
    {
119 3
        $created = new static($language_id ?: $this->language_id);
120 3
        $created->content = $this->content;
121
122 3
        return $created;
123
    }
124
125
    /**
126
     * Check if the field is related to the content
127
     *
128
     * @param string $field
129
     * @return bool
130
     */
131 54
    protected function isContentField($field)
132
    {
133 54
        return in_array($field, ['id', 'created_at', 'type', 'published']);
134
    }
135
136
    /**
137
     * Check if the field exists on the entity
138
     *
139
     * @param string $field
140
     * @return bool
141
     */
142 42
    public function hasField($field)
143
    {
144 42
        return array_key_exists($field, $this->data);
145
    }
146
147
    /**
148
     * @param string $field
149
     * @return FieldCollection
150
     */
151 36
    public function getField($field)
152
    {
153 36
        return $this->data[$field];
154
    }
155
156
    /**
157
     * Check if the field is related to the revision
158
     *
159
     * @param string $field
160
     * @return bool
161
     */
162 51
    protected function isRevisionField($field)
163
    {
164 51
        return in_array($field, ['language_id', 'updated_at', 'published']);
165
    }
166
167
    /**
168
     * Dynamically retrieve attributes on the model.
169
     *
170
     * @param string $key
171
     * @throws NonExistentFieldException
172
     * @return $this|bool|\Carbon\Carbon|\DateTime|mixed|static
173
     */
174 42
    public function __get($key)
175
    {
176 42
        if ($this->isContentField($key)) {
177 18
            return $this->content->getAttribute($key);
178
        }
179
180 39
        if ($this->isRevisionField($key)) {
181 6
            return $this->revision->getAttribute($key);
182
        }
183
184 36
        if ($this->hasField($key)) {
185 33
            return $this->getField($key);
186
        }
187
188 10
        if ($key == 'revisions') {
189 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...
190
        }
191
192 3
        throw new NonExistentFieldException("Field '$key' doesn't exist in '" . get_class($this) . "'");
193
    }
194
195
    /**
196
     * Dynamically set attributes on the model.
197
     *
198
     * @param string $key
199
     * @param mixed $value
200
     * @throws NonExistentFieldException
201
     */
202 48
    public function __set($key, $value)
203
    {
204 48
        if ($this->isContentField($key)) {
205 48
            $this->content->setAttribute($key, $value);
206
207 48
            return;
208
        }
209
210 48
        if ($this->isRevisionField($key)) {
211 48
            $this->revision->setAttribute($key, $value);
212
213 48
            return;
214
        }
215
216 21
        if ($this->hasField($key)) {
217 18
            $field = $this->getField($key);
218
219 18
            if (is_array($value)) {
220 12
                $field->clear();
221
222
                // This happens when the array is replaced completely by another array
223 12
                if (count($value)) {
224 3
                    foreach ($value as $k => $v) {
225 3
                        $field->offsetSet($k, $v);
226 3
                    }
227 3
                }
228
229 12
                return;
230
            }
231
232 6
            $field->offsetSet(0, $value);
233
234 6
            return;
235
        }
236
237 3
        throw new NonExistentFieldException("Field '$key' doesn't exist in '" . get_class($this) . "'");
238
    }
239
240
    /**
241
     * Find the latest valid revision for this entity
242
     *
243
     * @param int $id
244
     * @param int $language_id
245
     * @return static
246
     */
247 12
    public static function find($id, $language_id)
248
    {
249 12
        $instance = new static($language_id);
250
251 12
        $instance->content = Content::findOrFail($id);
252
253 12
        $instance->revision = Revision::where('content_id', $id)
254 12
            ->where('language_id', $language_id)
255 12
            ->where('published', true)
256 12
            ->firstOrFail();
257
258 12
        (new Collection($instance->getFields()))
259
            ->map(function ($options) {
260 12
                return $options['type'];
261 12
            })
262 12
            ->values()
263 12
            ->unique()
264
            ->map(function ($type) {
265 12
                return self::$types[$type];
266 12
            })
267
            ->each(function ($type) use ($instance) {
268 12
                $type::where('revision_id', $instance->revision->id)
269 12
                    ->get()
270
                    ->each(function (Field $value) use ($instance) {
271 12
                        $instance->data[$value->name][$value->weight] = $value;
272 12
                    });
273 12
            });
274
275 12
        return $instance;
276
    }
277
278
    /**
279
     * Save a revision
280
     */
281 15
    public function save($newRevision = false, $publishRevision = true)
282
    {
283 15
        if ($newRevision) {
284 6
            $revision = new Revision;
285 6
            $revision->language_id = $this->revision->language_id;
286
287 6
            $this->revision = $revision;
288 6
        }
289
290 15
        DB::transaction(
291
            function () use ($newRevision, $publishRevision) {
292
293 15
                $this->saveContent();
294
295 15
                $this->saveRevision($publishRevision);
296
297
                // Prepare and save fields
298 15
                foreach (array_keys($this->data) as $fieldName) {
299
                    /** @var FieldCollection $field */
300 15
                    $field = $this->data[$fieldName];
301
302 15
                    if (!$newRevision) {
303
                        $field->deleted()->each(function (Field $value) {
304 3
                            $value->delete();
305 15
                        });
306 15
                    }
307
308 15
                    $field->each(function (Field $value, $key) use ($newRevision, $fieldName) {
309 15
                        $value->weight = $key;
310 15
                        $value->name = $fieldName;
311 15
                        $this->saveField($value, $newRevision);
312 15
                    });
313
314 15
                    $field->syncOriginal();
315 15
                }
316 15
            }
317 15
        );
318 15
    }
319
320 15
    protected function saveContent()
321
    {
322 15
        $this->content->save();
323 15
    }
324
325 15
    protected function saveRevision($publishRevision)
326
    {
327
328 15
        if (!$this->revision->exists && !$publishRevision) {
329 3
            $this->revision->published = $publishRevision;
330 3
        }
331
332 15
        $this->revision->content_id = $this->content->id;
333 15
        $this->revision->save();
334
335 15
        if (!$this->content->wasRecentlyCreated && $publishRevision) {
336
            // Unpublish all other revisions
337 6
            Revision::where('content_id', $this->content->id)
338 6
                ->where('language_id', $this->revision->language_id)
339 6
                ->where('id', '!=', $this->revision->id)
340 6
                ->update(['published' => false]);
341 6
        }
342 15
    }
343
344 15
    protected function saveField(Field $field, $newRevision)
345
    {
346
        // If we create a new revision, this will
347
        // reinit the field to a non-saved field
348
        // and create a new row in the database
349 15
        if ($newRevision) {
350 6
            $field->id = null;
351 6
            $field->exists = false;
352 6
        }
353
354 15
        $field->revision_id = $this->revision->id;
355
356 15
        $field->save();
357 15
    }
358
359
    /**
360
     * Convert the Entity to an array.
361
     *
362
     * @return array
363
     */
364 16
    public function toArray()
365
    {
366
        $content = [
367 16
            '_content' => $this->content->toArray(),
368 16
            '_revision' => $this->revision->toArray(),
369 16
        ];
370
371 16
        foreach ($this->data as $field => $data) {
372 16
            $content[$field] = $data->toArray();
373 16
        }
374
375 16
        return $content;
376
    }
377
}
378