Completed
Push — master ( b0af80...96bdb3 )
by Philip
05:40
created

AbstractData::delete()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 7
cts 7
cp 1
rs 9.6666
c 0
b 0
f 0
nc 2
cc 2
eloc 7
nop 1
crap 2
1
<?php
2
3
/*
4
 * This file is part of the CRUDlex package.
5
 *
6
 * (c) Philip Lehmann-Böhm <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace CRUDlex;
13
14
use League\Flysystem\FilesystemInterface;
15
16
/**
17
 * The abstract class for reading and writing data.
18
 */
19
abstract class AbstractData {
20
21
    /**
22
     * Return value on successful deletion.
23
     */
24
    const DELETION_SUCCESS = 0;
25
26
    /**
27
     * Return value on failed deletion due to existing references.
28
     */
29
    const DELETION_FAILED_STILL_REFERENCED = 1;
30
31
    /**
32
     * Return value on failed deletion due to a failed before delete event.
33
     */
34
    const DELETION_FAILED_EVENT = 2;
35
36
    /**
37
     * Holds the {@see EntityDefinition} entity definition.
38
     */
39
    protected $definition;
40
41
    /**
42
     * Holds the filesystem.
43
     * @var FilesystemInterface
44
     */
45
    protected $filesystem;
46
47
    /**
48
     * Holds the events.
49
     */
50
    protected $events;
51
52
    /**
53
     * Performs the actual deletion.
54
     *
55
     * @param Entity $entity
56
     * the id of the entry to delete
57
     * @param boolean $deleteCascade
58
     * whether to delete children and subchildren
59
     *
60
     * @return integer
61
     * true on successful deletion
62
     */
63
    abstract protected function doDelete(Entity $entity, $deleteCascade);
64
65
    /**
66
     * Creates an {@see Entity} from the raw data array with the field name
67
     * as keys and field values as values.
68
     *
69
     * @param array $row
70
     * the array with the raw data
71
     *
72
     * @return Entity
73
     * the entity containing the array data then
74
     */
75 34
    protected function hydrate(array $row) {
76 34
        $fieldNames = $this->definition->getFieldNames(true);
77 34
        $entity     = new Entity($this->definition);
78 34
        foreach ($fieldNames as $fieldName) {
79 34
            $entity->set($fieldName, $row[$fieldName]);
80
        }
81 34
        return $entity;
82
    }
83
84
    /**
85
     * Enriches an entity with metadata:
86
     * id, version, created_at, updated_at
87
     *
88
     * @param mixed $id
89
     * the id of the entity to enrich
90
     * @param Entity $entity
91
     * the entity to enrich
92
     */
93 34
    protected function enrichEntityWithMetaData($id, Entity $entity) {
94 34
        $entity->set('id', $id);
95 34
        $createdEntity = $this->get($entity->get('id'));
96 34
        $entity->set('version', $createdEntity->get('version'));
97 34
        $entity->set('created_at', $createdEntity->get('created_at'));
98 34
        $entity->set('updated_at', $createdEntity->get('updated_at'));
99 34
    }
100
101
    /**
102
     * Gets the many-to-many fields.
103
     *
104
     * @return array|\string[]
105
     * the many-to-many fields
106
     */
107 35
    protected function getManyFields() {
108 35
        $fields = $this->definition->getFieldNames(true);
109
        return array_filter($fields, function($field) {
110 35
            return $this->definition->getType($field) === 'many';
111 35
        });
112
    }
113
114
    /**
115
     * Gets all form fields including the many-to-many-ones.
116
     *
117
     * @return array
118
     * all form fields
119
     */
120 34
    protected function getFormFields() {
121 34
        $manyFields = $this->getManyFields();
122 34
        $formFields = [];
123 34
        foreach ($this->definition->getEditableFieldNames() as $field) {
124 34
            if (!in_array($field, $manyFields)) {
125 34
                $formFields[] = $field;
126
            }
127
        }
128 34
        return $formFields;
129
    }
130
131
    /**
132
     * Performs the cascading children deletion.
133
     *
134
     * @param integer $id
135
     * the current entities id
136
     * @param boolean $deleteCascade
137
     * whether to delete children and sub children
138
     *
139
     * @return integer
140
     * returns one of:
141
     * - AbstractData::DELETION_SUCCESS -> successful deletion
142
     * - AbstractData::DELETION_FAILED_STILL_REFERENCED -> failed deletion due to existing references
143
     * - AbstractData::DELETION_FAILED_EVENT -> failed deletion due to a failed before delete event
144
     */
145 3
    protected function deleteChildren($id, $deleteCascade) {
146 3
        foreach ($this->definition->getChildren() as $childArray) {
147 3
            $childData = $this->definition->getServiceProvider()->getData($childArray[2]);
148 3
            $children  = $childData->listEntries([$childArray[1] => $id]);
149 3
            foreach ($children as $child) {
150 2
                $result = $childData->shouldExecuteEvents($child, 'before', 'delete');
151 2
                if (!$result) {
152 1
                    return static::DELETION_FAILED_EVENT;
153
                }
154 2
                $childData->doDelete($child, $deleteCascade);
155 3
                $childData->shouldExecuteEvents($child, 'after', 'delete');
156
            }
157
        }
158 3
        return static::DELETION_SUCCESS;
159
    }
160
161
    /**
162
     * Gets an array of reference ids for the given entities.
163
     *
164
     * @param array $entities
165
     * the entities to extract the ids
166
     * @param string $field
167
     * the reference field
168
     *
169
     * @return array
170
     * the extracted ids
171
     */
172
    protected function getReferenceIds(array $entities, $field) {
173 21
        $ids = array_map(function(Entity $entity) use ($field) {
174 21
            $id = $entity->get($field);
175 21
            return is_array($id) ? $id['id'] : $id;
176 21
        }, $entities);
177 21
        return $ids;
178
    }
179
180
    /**
181
     * Performs the persistence of the given entity as new entry in the datasource.
182
     *
183
     * @param Entity $entity
184
     * the entity to persist
185
     *
186
     * @return boolean
187
     * true on successful creation
188
     */
189
    abstract protected function doCreate(Entity $entity);
0 ignored issues
show
Coding Style introduced by
function doCreate() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
190
191
    /**
192
     * Performs the updates of an existing entry in the datasource having the same id.
193
     *
194
     * @param Entity $entity
195
     * the entity with the new data
196
     *
197
     * @return boolean
198
     * true on successful update
199
     */
200
    abstract protected function doUpdate(Entity $entity);
0 ignored issues
show
Coding Style introduced by
function doUpdate() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
201
202
    /**
203
     * Executes the event chain of an entity.
204
     *
205
     * @param Entity $entity
206
     * the entity having the event chain to execute
207
     * @param string $moment
208
     * the "moment" of the event, can be either "before" or "after"
209
     * @param string $action
210
     * the "action" of the event, can be either "create", "update" or "delete"
211
     *
212
     * @return boolean
213
     * true on successful execution of the full chain or false if it broke at
214
     * any point (and stopped the execution)
215
     */
216 34
    public function shouldExecuteEvents(Entity $entity, $moment, $action) {
217 34
        if (!isset($this->events[$moment.'.'.$action])) {
218 33
            return true;
219
        }
220 8
        foreach ($this->events[$moment.'.'.$action] as $event) {
221 8
            $result = $event($entity);
222 8
            if (!$result) {
223 8
                return false;
224
            }
225
        }
226 7
        return true;
227
    }
228
229
    /**
230
     * Adds an event to fire for the given parameters. The event function must
231
     * have this signature:
232
     * function (Entity $entity)
233
     * and has to return true or false.
234
     * The events are executed one after another in the added order as long as
235
     * they return "true". The first event returning "false" will stop the
236
     * process.
237
     *
238
     * @param string $moment
239
     * the "moment" of the event, can be either "before" or "after"
240
     * @param string $action
241
     * the "action" of the event, can be either "create", "update" or "delete"
242
     * @param \Closure $function
243
     * the event function to be called if set
244
     */
245 9
    public function pushEvent($moment, $action, \Closure $function) {
246 9
        $events                            = isset($this->events[$moment.'.'.$action]) ? $this->events[$moment.'.'.$action] : [];
247 9
        $events[]                          = $function;
248 9
        $this->events[$moment.'.'.$action] = $events;
249 9
    }
250
251
252
    /**
253
     * Removes and returns the latest event for the given parameters.
254
     *
255
     * @param string $moment
256
     * the "moment" of the event, can be either "before" or "after"
257
     * @param string $action
258
     * the "action" of the event, can be either "create", "update" or "delete"
259
     *
260
     * @return \Closure|null
261
     * the popped event or null if no event was available.
262
     */
263 9
    public function popEvent($moment, $action) {
264 9
        if (array_key_exists($moment.'.'.$action, $this->events)) {
265 9
            return array_pop($this->events[$moment.'.'.$action]);
266
        }
267 1
        return null;
268
    }
269
270
271
    /**
272
     * Gets the entity with the given id.
273
     *
274
     * @param string $id
275
     * the id
276
     *
277
     * @return Entity
278
     * the entity belonging to the id or null if not existant
279
     *
280
     * @return void
281
     */
282
    abstract public function get($id);
283
284
    /**
285
     * Gets a list of entities fullfilling the given filter or all if no
286
     * selection was given.
287
     *
288
     * @param array $filter
289
     * the filter all resulting entities must fulfill, the keys as field names
290
     * @param array $filterOperators
291
     * the operators of the filter like "=" defining the full condition of the field
292
     * @param integer|null $skip
293
     * if given and not null, it specifies the amount of rows to skip
294
     * @param integer|null $amount
295
     * if given and not null, it specifies the maximum amount of rows to retrieve
296
     * @param string|null $sortField
297
     * if given and not null, it specifies the field to sort the entries
298
     * @param boolean|null $sortAscending
299
     * if given and not null, it specifies that the sort order is ascending,
300
     * descending else
301
     *
302
     * @return Entity[]
303
     * the entities fulfilling the filter or all if no filter was given
304
     */
305
    abstract public function listEntries(array $filter = [], array $filterOperators = [], $skip = null, $amount = null, $sortField = null, $sortAscending = null);
306
307
    /**
308
     * Persists the given entity as new entry in the datasource.
309
     *
310
     * @param Entity $entity
311
     * the entity to persist
312
     *
313
     * @return boolean
314
     * true on successful creation
315
     */
316 34 View Code Duplication
    public function create(Entity $entity) {
0 ignored issues
show
Coding Style introduced by
function create() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
317 34
        $result = $this->shouldExecuteEvents($entity, 'before', 'create');
318 34
        if (!$result) {
319 2
            return false;
320
        }
321 34
        $result = $this->doCreate($entity);
322 34
        $this->shouldExecuteEvents($entity, 'after', 'create');
323 34
        return $result;
324
    }
325
326
    /**
327
     * Updates an existing entry in the datasource having the same id.
328
     *
329
     * @param Entity $entity
330
     * the entity with the new data
331
     *
332
     * @return boolean
333
     * true on successful update
334
     */
335 13 View Code Duplication
    public function update(Entity $entity) {
0 ignored issues
show
Coding Style introduced by
function update() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
336 13
        if (!$this->shouldExecuteEvents($entity, 'before', 'update')) {
337 2
            return false;
338
        }
339 13
        $result = $this->doUpdate($entity);
340 13
        $this->shouldExecuteEvents($entity, 'after', 'update');
341 13
        return $result;
342
    }
343
344
    /**
345
     * Deletes an entry from the datasource.
346
     *
347
     * @param Entity $entity
348
     * the entity to delete
349
     *
350
     * @return integer
351
     * returns one of:
352
     * - AbstractData::DELETION_SUCCESS -> successful deletion
353
     * - AbstractData::DELETION_FAILED_STILL_REFERENCED -> failed deletion due to existing references
354
     * - AbstractData::DELETION_FAILED_EVENT -> failed deletion due to a failed before delete event
355
     */
356 4
    public function delete($entity) {
357 4
        $result = $this->shouldExecuteEvents($entity, 'before', 'delete');
358 4
        if (!$result) {
359 2
            return static::DELETION_FAILED_EVENT;
360
        }
361 4
        $result = $this->doDelete($entity, $this->definition->isDeleteCascade());
362 4
        $this->shouldExecuteEvents($entity, 'after', 'delete');
363 4
        return $result;
364
    }
365
366
    /**
367
     * Gets ids and names of a table. Used for building up the dropdown box of
368
     * reference type fields for example.
369
     *
370
     * @param string $entity
371
     * the entity
372
     * @param string $nameField
373
     * the field defining the name of the rows
374
     *
375
     * @return array
376
     * an array with the ids as key and the names as values
377
     */
378
    abstract public function getIdToNameMap($entity, $nameField);
379
380
    /**
381
     * Retrieves the amount of entities in the datasource fulfilling the given
382
     * parameters.
383
     *
384
     * @param string $table
385
     * the table to count in
386
     * @param array $params
387
     * an array with the field names as keys and field values as values
388
     * @param array $paramsOperators
389
     * the operators of the parameters like "=" defining the full condition of the field
390
     * @param boolean $excludeDeleted
391
     * false, if soft deleted entries in the datasource should be counted, too
392
     *
393
     * @return integer
394
     * the count fulfilling the given parameters
395
     */
396
    abstract public function countBy($table, array $params, array $paramsOperators, $excludeDeleted);
397
398
    /**
399
     * Checks whether a given set of ids is assigned to any entity exactly
400
     * like it is given (no subset, no superset).
401
     *
402
     * @param string $field
403
     * the many field
404
     * @param array $thatIds
405
     * the id set to check
406
     * @param string|null $excludeId
407
     * one optional own id to exclude from the check
408
     *
409
     * @return boolean
410
     * true if the set of ids exists for an entity
411
     */
412
    abstract public function hasManySet($field, array $thatIds, $excludeId = null);
413
414
    /**
415
     * Gets the {@see EntityDefinition} instance.
416
     *
417
     * @return EntityDefinition
418
     * the definition instance
419
     */
420 74
    public function getDefinition() {
421 74
        return $this->definition;
422
    }
423
424
    /**
425
     * Creates a new, empty entity instance having all fields prefilled with
426
     * null or the defined value in case of fixed fields.
427
     *
428
     * @return Entity
429
     * the newly created entity
430
     */
431 37
    public function createEmpty() {
432 37
        $entity = new Entity($this->definition);
433 37
        $fields = $this->definition->getEditableFieldNames();
434 37
        foreach ($fields as $field) {
435 37
            $value = null;
436 37
            if ($this->definition->getType($field) == 'fixed') {
437 37
                $value = $this->definition->getField($field, 'value');
438
            }
439 37
            $entity->set($field, $value);
440
        }
441 37
        $entity->set('id', null);
442 37
        return $entity;
443
    }
444
445
446
}
447