Completed
Push — master ( 48e8c3...3ca25d )
by Stéphane
14:33
created

Entity::find()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 1

Importance

Changes 2
Bugs 1 Features 1
Metric Value
cc 1
eloc 18
c 2
b 1
f 1
nc 1
nop 2
dl 0
loc 30
ccs 19
cts 19
cp 1
crap 1
rs 8.8571
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 42
    public function __construct($language_id)
49
    {
50 42
        if (!is_int($language_id) || $language_id == 0) {
51 3
            throw new InvalidArgumentException("You must set a valid 'language_id'.");
52
        }
53
54 39
        $fields = $this->getFields();
55
56 39
        $this->initialize($fields);
57
58 33
        $this->revision->language_id = $language_id;
59 33
    }
60
61 39
    protected function initialize(array $fields)
62
    {
63 39
        $this->content = new Content;
64 39
        $this->revision = new Revision;
65
66 39
        foreach ($fields as $field => $settings) {
67 39
            $this->data[$field] = $this->initializeField($field, $settings);
68 33
        }
69 33
    }
70
71
    /**
72
     * @param string $field
73
     * @param array $settings
74
     * @return static
75
     * @throws InvalidFieldTypeException
76
     * @throws ReservedFieldNameException
77
     */
78 39
    protected function initializeField($field, $settings)
79
    {
80 39
        if ($this->isContentField($field) || $this->isRevisionField($field)) {
81 3
            throw new ReservedFieldNameException(
82 3
                "The field '$field' cannot be used in '" . get_class($this) . "' as it is a reserved name"
83 3
            );
84
        }
85
86 36
        $type = $settings['type'];
87
88 36
        if (!array_key_exists($type, self::$types)) {
89 3
            throw new InvalidFieldTypeException("Unkown type '$type' in '" . get_class($this) . "'");
90
        }
91
92 33
        $settings['type'] = self::$types[$settings['type']];
93
94 33
        return FieldCollection::initField($settings);
95
    }
96
97
    abstract public function getFields();
98
99
    /**
100
     * Get the database friendly content type
101
     *
102
     * @return string
103
     */
104 6
    public static function getContentType()
105
    {
106 6
        return str_replace('\\', '', snake_case((new \ReflectionClass(get_called_class()))->getShortName()));
107
    }
108
109
    /**
110
     * Create a new revision based on the same content ID but without the content.
111
     * Very useful if you want to add a new language
112
     *
113
     * @param int $language_id
114
     * @return static
115
     */
116 3
    public function newRevision($language_id = null)
117
    {
118 3
        $created = new static($language_id ?: $this->language_id);
119 3
        $created->content = $this->content;
120
121 3
        return $created;
122
    }
123
124
    /**
125
     * Check if the field is related to the content
126
     *
127
     * @param string $field
128
     * @return bool
129
     */
130 39
    protected function isContentField($field)
131
    {
132 39
        return in_array($field, ['id', 'created_at']);
133
    }
134
135
    /**
136
     * Check if the field exists on the entity
137
     *
138
     * @param string $field
139
     * @return bool
140
     */
141 30
    public function hasField($field)
142
    {
143 30
        return array_key_exists($field, $this->data);
144
    }
145
146
    /**
147
     * @param string $field
148
     * @return FieldCollection
149
     */
150 24
    public function getField($field)
151
    {
152 24
        return $this->data[$field];
153
    }
154
155
    /**
156
     * Check if the field is related to the revision
157
     *
158
     * @param string $field
159
     * @return bool
160
     */
161 36
    protected function isRevisionField($field)
162
    {
163 36
        return in_array($field, ['language_id', 'updated_at']);
164
    }
165
166
    /**
167
     * Dynamically retrieve attributes on the model.
168
     *
169
     * @param string $key
170
     * @throws NonExistentFieldException
171
     * @return $this|bool|\Carbon\Carbon|\DateTime|mixed|static
172
     */
173 30
    public function __get($key)
174
    {
175 30
        if ($this->isContentField($key)) {
176 6
            return $this->content->getAttribute($key);
177
        }
178
179 30
        if ($this->isRevisionField($key)) {
180 6
            return $this->revision->getAttribute($key);
181
        }
182
183 27
        if ($this->hasField($key)) {
184 24
            return $this->getField($key);
185
        }
186
187 3
        throw new NonExistentFieldException("Field '$key' doesn't exist in '" . get_class($this) . "'");
188
    }
189
190
    /**
191
     * Dynamically set attributes on the model.
192
     *
193
     * @param string $key
194
     * @param mixed $value
195
     * @throws NonExistentFieldException
196
     */
197 15
    public function __set($key, $value)
198
    {
199 15
        if ($this->isContentField($key)) {
200 3
            $this->content->setAttribute($key, $value);
201
202 3
            return;
203
        }
204
205 15
        if ($this->isRevisionField($key)) {
206
            $this->revision->setAttribute($key, $value);
207
208
            return;
209
        }
210
211 15
        if ($this->hasField($key)) {
212 12
            if ($value == []) {
213 9
                $this->getField($key)->clear();
214
215 9
                return;
216
            }
217
218 3
            $this->getField($key)->offsetSet(0, $value);
219
220 3
            return;
221
        }
222
223 3
        throw new NonExistentFieldException("Field '$key' doesn't exist in '" . get_class($this) . "'");
224
    }
225
226
    /**
227
     * Find the latest valid revision for this entity
228
     *
229
     * @param int $id
230
     * @param int $language_id
231
     * @return static
232
     */
233 3
    public static function find($id, $language_id)
234
    {
235 3
        $instance = new static($language_id);
236
237 3
        $instance->content = Content::findOrFail($id);
238
239
        // TODO :: logic to get valid revision, this will retrieve only one revision
240 3
        $instance->revision = Revision::where('content_id', $id)->where('language_id', $language_id)->firstOrFail();
241
242 3
        (new Collection($instance->getFields()))
243
            ->map(function ($options) {
244 3
                    return $options['type'];
245 3
            })
246 3
            ->values()
247 3
            ->unique()
248
            ->map(function ($type) {
249 3
                    return self::$types[$type];
250 3
            })
251 3
            ->each(
252
                function ($type) use ($instance) {
253 3
                    $type::where('revision_id', $instance->revision->id)
254 3
                        ->get()
255
                        ->each(function (Field $value) use ($instance) {
256 3
                                $instance->data[$value->name][$value->weight] = $value;
257 3
                        });
258 3
                }
259 3
            );
260
261 3
        return $instance;
262
    }
263
264
    /**
265
     * Save a revision
266
     */
267 6
    public function save($newRevision = false)
268
    {
269 6
        if ($newRevision) {
270
            $revision = new Revision;
271
            $revision->language_id = $this->revision->language_id;
272
273
            $this->revision = $revision;
274
        }
275
276 6
        DB::transaction(
277
            function () use ($newRevision) {
278
                // Save content
279 6
                $this->content->save();
280
281
                // Create revision ID
282 6
                $this->revision->content_id = $this->content->id;
283 6
                $this->revision->save();
284
285
                // Prepare and save fields
286 6
                foreach (array_keys($this->data) as $fieldName) {
287 6
                    $field = $this->data[$fieldName];
288
289
                    //TODO :: remove deleted fields
290
291 6
                    $field->each(
292 6
                        function (Field $value, $key) use ($newRevision, $fieldName) {
293
294 6
                            if ($newRevision) {
295
                                $value->id = null;
296
                                $value->created_at = null;
297
                            }
298
299 6
                            $value->weight = $key;
300 6
                            $value->revision_id = $this->revision->id;
301 6
                            $value->name = $fieldName;
302
303 6
                            $value->save();
304 6
                        }
305 6
                    );
306 6
                }
307 6
            }
308 6
        );
309 6
    }
310
311
    /**
312
     * Convert the Entity to an array.
313
     *
314
     * @return array
315
     */
316 6
    public function toArray()
317
    {
318
        $content = [
319 6
            '_content' => $this->content->toArray(),
320 6
            '_revision' => $this->revision->toArray(),
321 6
        ];
322
323 6
        foreach ($this->data as $field => $data) {
324 6
            $content[$field] = $data->toArray();
325 6
        }
326
327 6
        return $content;
328
    }
329
}
330