Completed
Push — master ( da52cb...88c0e7 )
by Philip
09:03 queued 04:20
created

AbstractData::doCreate()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 1
ccs 0
cts 0
cp 0
c 0
b 0
f 0
nc 1
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 Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\Response;
16
17
/**
18
 * The abstract class for reading and writing data.
19
 */
20
abstract class AbstractData {
21
22
    /**
23
     * Return value on successful deletion.
24
     */
25
    const DELETION_SUCCESS = 0;
26
27
    /**
28
     * Return value on failed deletion due to existing references.
29
     */
30
    const DELETION_FAILED_STILL_REFERENCED = 1;
31
32
    /**
33
     * Return value on failed deletion due to a failed before delete event.
34
     */
35
    const DELETION_FAILED_EVENT = 2;
36
37
    /**
38
     * Holds the {@see EntityDefinition} entity definition.
39
     */
40
    protected $definition;
41
42
    /**
43
     * Holds the {@see FileProcessorInterface} file processor.
44
     */
45
    protected $fileProcessor;
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 38
    protected function hydrate(array $row) {
76 38
        $fieldNames = $this->definition->getFieldNames(true);
77 38
        $entity     = new Entity($this->definition);
78 38
        foreach ($fieldNames as $fieldName) {
79 38
            $entity->set($fieldName, $row[$fieldName]);
80 38
        }
81 38
        return $entity;
82
    }
83
84
    /**
85
     * Executes the event chain of an entity.
86
     *
87
     * @param Entity $entity
88
     * the entity having the event chain to execute
89
     * @param string $moment
90
     * the "moment" of the event, can be either "before" or "after"
91
     * @param string $action
92
     * the "action" of the event, can be either "create", "update" or "delete"
93
     *
94
     * @return boolean
95
     * true on successful execution of the full chain or false if it broke at
96
     * any point (and stopped the execution)
97
     */
98 38
    protected function shouldExecuteEvents(Entity $entity, $moment, $action) {
99 38
        if (!isset($this->events[$moment.'.'.$action])) {
100 37
            return true;
101
        }
102 7
        foreach ($this->events[$moment.'.'.$action] as $event) {
103 7
            $result = $event($entity);
104 7
            if (!$result) {
105 7
                return false;
106
            }
107 6
        }
108 6
        return true;
109
    }
110
111
    /**
112
     * Executes a function for each file field of this entity.
113
     *
114
     * @param Entity $entity
115
     * the just created entity
116
     * @param string $entityName
117
     * the name of the entity as this class here is not aware of it
118
     * @param \Closure $function
119
     * the function to perform, takes $entity, $entityName and $field as parameter
120
     */
121 8
    protected function performOnFiles(Entity $entity, $entityName, $function) {
122 8
        $fields = $this->definition->getEditableFieldNames();
123 8
        foreach ($fields as $field) {
124 8
            if ($this->definition->getType($field) == 'file') {
125 8
                $function($entity, $entityName, $field);
126 8
            }
127 8
        }
128 8
    }
129
130
131
    /**
132
     * Enriches an entity with metadata:
133
     * id, version, created_at, updated_at
134
     *
135
     * @param mixed $id
136
     * the id of the entity to enrich
137
     * @param Entity $entity
138
     * the entity to enrich
139
     */
140 38
    protected function enrichEntityWithMetaData($id, Entity $entity) {
141 38
        $entity->set('id', $id);
142 38
        $createdEntity = $this->get($entity->get('id'));
143 38
        $entity->set('version', $createdEntity->get('version'));
144 38
        $entity->set('created_at', $createdEntity->get('created_at'));
145 38
        $entity->set('updated_at', $createdEntity->get('updated_at'));
146 38
    }
147
148
    /**
149
     * Gets the many-to-many fields.
150
     *
151
     * @return array|\string[]
152
     * the many-to-many fields
153
     */
154 39
    protected function getManyFields() {
155 39
        $fields = $this->definition->getFieldNames(true);
156
        return array_filter($fields, function($field) {
157 39
            return $this->definition->getType($field) === 'many';
158 39
        });
159
    }
160
161
    /**
162
     * Gets all form fields including the many-to-many-ones.
163
     *
164
     * @return array
165
     * all form fields
166
     */
167 38
    protected function getFormFields() {
168 38
        $manyFields = $this->getManyFields();
169 38
        $formFields = [];
170 38
        foreach ($this->definition->getEditableFieldNames() as $field) {
171 38
            if (!in_array($field, $manyFields)) {
172 38
                $formFields[] = $field;
173 38
            }
174 38
        }
175 38
        return $formFields;
176
    }
177
178
    /**
179
     * Performs the cascading children deletion.
180
     *
181
     * @param integer $id
182
     * the current entities id
183
     * @param boolean $deleteCascade
184
     * whether to delete children and sub children
185
     */
186 3
    protected function deleteChildren($id, $deleteCascade) {
187 3
        foreach ($this->definition->getChildren() as $childArray) {
188 3
            $childData = $this->definition->getServiceProvider()->getData($childArray[2]);
189 3
            $children  = $childData->listEntries([$childArray[1] => $id]);
190 3
            foreach ($children as $child) {
191 2
                $result = $this->shouldExecuteEvents($child, 'before', 'delete');
192 2
                if (!$result) {
193
                    return;
194
                }
195 2
                $childData->doDelete($child, $deleteCascade);
196 2
                $this->shouldExecuteEvents($child, 'after', 'delete');
197 3
            }
198 3
        }
199 3
    }
200
201
    /**
202
     * Gets an array of reference ids for the given entities.
203
     *
204
     * @param array $entities
205
     * the entities to extract the ids
206
     * @param string $field
207
     * the reference field
208
     *
209
     * @return array
210
     * the extracted ids
211
     */
212 25
    protected function getReferenceIds(array $entities, $field) {
213
        $ids = array_map(function(Entity $entity) use ($field) {
214 25
            $id = $entity->get($field);
215 25
            return is_array($id) ? $id['id'] : $id;
216 25
        }, $entities);
217 25
        return $ids;
218
    }
219
220
    /**
221
     * Performs the persistence of the given entity as new entry in the datasource.
222
     *
223
     * @param Entity $entity
224
     * the entity to persist
225
     *
226
     * @return boolean
227
     * true on successful creation
228
     */
229
    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...
230
231
    /**
232
     * Performs the updates of an existing entry in the datasource having the same id.
233
     *
234
     * @param Entity $entity
235
     * the entity with the new data
236
     *
237
     * @return boolean
238
     * true on successful update
239
     */
240
    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...
241
242
    /**
243
     * Adds an event to fire for the given parameters. The event function must
244
     * have this signature:
245
     * function (Entity $entity)
246
     * and has to return true or false.
247
     * The events are executed one after another in the added order as long as
248
     * they return "true". The first event returning "false" will stop the
249
     * process.
250
     *
251
     * @param string $moment
252
     * the "moment" of the event, can be either "before" or "after"
253
     * @param string $action
254
     * the "action" of the event, can be either "create", "update" or "delete"
255
     * @param \Closure $function
256
     * the event function to be called if set
257
     */
258 8
    public function pushEvent($moment, $action, \Closure $function) {
259 8
        $events                            = isset($this->events[$moment.'.'.$action]) ? $this->events[$moment.'.'.$action] : [];
260 8
        $events[]                          = $function;
261 8
        $this->events[$moment.'.'.$action] = $events;
262 8
    }
263
264
265
    /**
266
     * Removes and returns the latest event for the given parameters.
267
     *
268
     * @param string $moment
269
     * the "moment" of the event, can be either "before" or "after"
270
     * @param string $action
271
     * the "action" of the event, can be either "create", "update" or "delete"
272
     *
273
     * @return \Closure|null
274
     * the popped event or null if no event was available.
275
     */
276 8
    public function popEvent($moment, $action) {
277 8
        if (array_key_exists($moment.'.'.$action, $this->events)) {
278 8
            return array_pop($this->events[$moment.'.'.$action]);
279
        }
280 1
        return null;
281
    }
282
283
284
    /**
285
     * Gets the entity with the given id.
286
     *
287
     * @param string $id
288
     * the id
289
     *
290
     * @return Entity
291
     * the entity belonging to the id or null if not existant
292
     *
293
     * @return void
294
     */
295
    abstract public function get($id);
296
297
    /**
298
     * Gets a list of entities fullfilling the given filter or all if no
299
     * selection was given.
300
     *
301
     * @param array $filter
302
     * the filter all resulting entities must fulfill, the keys as field names
303
     * @param array $filterOperators
304
     * the operators of the filter like "=" defining the full condition of the field
305
     * @param integer|null $skip
306
     * if given and not null, it specifies the amount of rows to skip
307
     * @param integer|null $amount
308
     * if given and not null, it specifies the maximum amount of rows to retrieve
309
     * @param string|null $sortField
310
     * if given and not null, it specifies the field to sort the entries
311
     * @param boolean|null $sortAscending
312
     * if given and not null, it specifies that the sort order is ascending,
313
     * descending else
314
     *
315
     * @return Entity[]
316
     * the entities fulfilling the filter or all if no filter was given
317
     */
318
    abstract public function listEntries(array $filter = [], array $filterOperators = [], $skip = null, $amount = null, $sortField = null, $sortAscending = null);
319
320
    /**
321
     * Persists the given entity as new entry in the datasource.
322
     *
323
     * @param Entity $entity
324
     * the entity to persist
325
     *
326
     * @return boolean
327
     * true on successful creation
328
     */
329 38 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...
330 38
        $result = $this->shouldExecuteEvents($entity, 'before', 'create');
331 38
        if (!$result) {
332 2
            return false;
333
        }
334 38
        $result = $this->doCreate($entity);
335 38
        $this->shouldExecuteEvents($entity, 'after', 'create');
336 38
        return $result;
337
    }
338
339
    /**
340
     * Updates an existing entry in the datasource having the same id.
341
     *
342
     * @param Entity $entity
343
     * the entity with the new data
344
     *
345
     * @return boolean
346
     * true on successful update
347
     */
348 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...
349 13
        if (!$this->shouldExecuteEvents($entity, 'before', 'update')) {
350 2
            return false;
351
        }
352 13
        $result = $this->doUpdate($entity);
353 13
        $this->shouldExecuteEvents($entity, 'after', 'update');
354 13
        return $result;
355
    }
356
357
    /**
358
     * Deletes an entry from the datasource.
359
     *
360
     * @param Entity $entity
361
     * the entity to delete
362
     *
363
     * @return integer
364
     * returns one of:
365
     * - AbstractData::DELETION_SUCCESS -> successful deletion
366
     * - AbstractData::DELETION_FAILED_STILL_REFERENCED -> failed deletion due to existing references
367
     * - AbstractData::DELETION_FAILED_EVENT -> failed deletion due to a failed before delete event
368
     */
369 4
    public function delete($entity) {
370 4
        $result = $this->shouldExecuteEvents($entity, 'before', 'delete');
371 4
        if (!$result) {
372 2
            return static::DELETION_FAILED_EVENT;
373
        }
374 4
        $result = $this->doDelete($entity, $this->definition->isDeleteCascade());
375 4
        $this->shouldExecuteEvents($entity, 'after', 'delete');
376 4
        return $result;
377
    }
378
379
    /**
380
     * Gets ids and names of a table. Used for building up the dropdown box of
381
     * reference type fields for example.
382
     *
383
     * @param string $entity
384
     * the entity
385
     * @param string $nameField
386
     * the field defining the name of the rows
387
     *
388
     * @return array
389
     * an array with the ids as key and the names as values
390
     */
391
    abstract public function getIdToNameMap($entity, $nameField);
392
393
    /**
394
     * Retrieves the amount of entities in the datasource fulfilling the given
395
     * parameters.
396
     *
397
     * @param string $table
398
     * the table to count in
399
     * @param array $params
400
     * an array with the field names as keys and field values as values
401
     * @param array $paramsOperators
402
     * the operators of the parameters like "=" defining the full condition of the field
403
     * @param boolean $excludeDeleted
404
     * false, if soft deleted entries in the datasource should be counted, too
405
     *
406
     * @return integer
407
     * the count fulfilling the given parameters
408
     */
409
    abstract public function countBy($table, array $params, array $paramsOperators, $excludeDeleted);
410
411
    /**
412
     * Checks whether a given set of ids is assigned to any entity exactly
413
     * like it is given (no subset, no superset).
414
     *
415
     * @param string $field
416
     * the many field
417
     * @param array $thatIds
418
     * the id set to check
419
     * @param string|null $excludeId
420
     * one optional own id to exclude from the check
421
     *
422
     * @return boolean
423
     * true if the set of ids exists for an entity
424
     */
425
    abstract public function hasManySet($field, array $thatIds, $excludeId = null);
426
427
    /**
428
     * Gets the {@see EntityDefinition} instance.
429
     *
430
     * @return EntityDefinition
431
     * the definition instance
432
     */
433 78
    public function getDefinition() {
434 78
        return $this->definition;
435
    }
436
437
    /**
438
     * Creates a new, empty entity instance having all fields prefilled with
439
     * null or the defined value in case of fixed fields.
440
     *
441
     * @return Entity
442
     * the newly created entity
443
     */
444 41
    public function createEmpty() {
445 41
        $entity = new Entity($this->definition);
446 41
        $fields = $this->definition->getEditableFieldNames();
447 41
        foreach ($fields as $field) {
448 41
            $value = null;
449 41
            if ($this->definition->getType($field) == 'fixed') {
450 41
                $value = $this->definition->getField($field, 'value');
451 41
            }
452 41
            $entity->set($field, $value);
453 41
        }
454 41
        $entity->set('id', null);
455 41
        return $entity;
456
    }
457
458
    /**
459
     * Creates the uploaded files of a newly created entity.
460
     *
461
     * @param Request $request
462
     * the HTTP request containing the file data
463
     * @param Entity $entity
464
     * the just created entity
465
     * @param string $entityName
466
     * the name of the entity as this class here is not aware of it
467
     *
468
     * @return boolean
469
     * true if all before events passed
470
     */
471 4 View Code Duplication
    public function createFiles(Request $request, Entity $entity, $entityName) {
0 ignored issues
show
Coding Style introduced by
function createFiles() 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...
472 4
        $result = $this->shouldExecuteEvents($entity, 'before', 'createFiles');
473 4
        if (!$result) {
474 1
            return false;
475
        }
476 4
        $fileProcessor = $this->fileProcessor;
477
        $this->performOnFiles($entity, $entityName, function($entity, $entityName, $field) use ($fileProcessor, $request) {
478 4
            $fileProcessor->createFile($request, $entity, $entityName, $field);
479 4
        });
480 4
        $this->shouldExecuteEvents($entity, 'after', 'createFiles');
481 4
        return true;
482
    }
483
484
    /**
485
     * Updates the uploaded files of an updated entity.
486
     *
487
     * @param Request $request
488
     * the HTTP request containing the file data
489
     * @param Entity $entity
490
     * the updated entity
491
     * @param string $entityName
492
     * the name of the entity as this class here is not aware of it
493
     *
494
     * @return boolean
495
     * true on successful update
496
     */
497 2 View Code Duplication
    public function updateFiles(Request $request, Entity $entity, $entityName) {
0 ignored issues
show
Coding Style introduced by
function updateFiles() 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...
498 2
        $result = $this->shouldExecuteEvents($entity, 'before', 'updateFiles');
499 2
        if (!$result) {
500 1
            return false;
501
        }
502 2
        $fileProcessor = $this->fileProcessor;
503
        $this->performOnFiles($entity, $entityName, function($entity, $entityName, $field) use ($fileProcessor, $request) {
504 2
            $fileProcessor->updateFile($request, $entity, $entityName, $field);
505 2
        });
506 2
        $this->shouldExecuteEvents($entity, 'after', 'updateFiles');
507 2
        return true;
508
    }
509
510
    /**
511
     * Deletes a specific file from an existing entity.
512
     *
513
     * @param Entity $entity
514
     * the entity to delete the file from
515
     * @param string $entityName
516
     * the name of the entity as this class here is not aware of it
517
     * @param string $field
518
     * the field of the entity containing the file to be deleted
519
     *
520
     * @return boolean
521
     * true on successful deletion
522
     */
523 2 View Code Duplication
    public function deleteFile(Entity $entity, $entityName, $field) {
0 ignored issues
show
Coding Style introduced by
function deleteFile() 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...
524 2
        $result = $this->shouldExecuteEvents($entity, 'before', 'deleteFile');
525 2
        if (!$result) {
526 1
            return false;
527
        }
528 2
        $this->fileProcessor->deleteFile($entity, $entityName, $field);
529 2
        $this->shouldExecuteEvents($entity, 'after', 'deleteFile');
530 2
        return true;
531
    }
532
533
    /**
534
     * Deletes all files of an existing entity.
535
     *
536
     * @param Entity $entity
537
     * the entity to delete the files from
538
     * @param string $entityName
539
     * the name of the entity as this class here is not aware of it
540
     *
541
     * @return boolean
542
     * true on successful deletion
543
     */
544 2 View Code Duplication
    public function deleteFiles(Entity $entity, $entityName) {
0 ignored issues
show
Coding Style introduced by
function deleteFiles() 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...
545 2
        $result = $this->shouldExecuteEvents($entity, 'before', 'deleteFiles');
546 2
        if (!$result) {
547 1
            return false;
548
        }
549 2
        $fileProcessor = $this->fileProcessor;
550 2
        $this->performOnFiles($entity, $entityName, function($entity, $entityName, $field) use ($fileProcessor) {
551 2
            $fileProcessor->deleteFile($entity, $entityName, $field);
552 2
        });
553 2
        $this->shouldExecuteEvents($entity, 'after', 'deleteFiles');
554 2
        return true;
555
    }
556
557
    /**
558
     * Renders (outputs) a file of an entity. This includes setting headers
559
     * like the file size, mimetype and name, too.
560
     *
561
     * @param Entity $entity
562
     * the entity to render the file from
563
     * @param string $entityName
564
     * the name of the entity as this class here is not aware of it
565
     * @param string $field
566
     * the field of the entity containing the file to be rendered
567
     *
568
     * @return Response
569
     * the HTTP response, likely to be a streamed one
570
     */
571 2
    public function renderFile(Entity $entity, $entityName, $field) {
572 2
        return $this->fileProcessor->renderFile($entity, $entityName, $field);
573
    }
574
575
}
576