Completed
Push — master ( 5b7e22...1f9a7e )
by Philip
02:26
created

AbstractData::enrichWithMany()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 6
nop 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
    protected function hydrate(array $row) {
76
        $fieldNames = $this->definition->getFieldNames(true);
77
        $entity     = new Entity($this->definition);
78
        foreach ($fieldNames as $fieldName) {
79
            $entity->set($fieldName, $row[$fieldName]);
80
        }
81
        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
    protected function shouldExecuteEvents(Entity $entity, $moment, $action) {
99
        if (!isset($this->events[$moment.'.'.$action])) {
100
            return true;
101
        }
102
        foreach ($this->events[$moment.'.'.$action] as $event) {
103
            $result = $event($entity);
104
            if (!$result) {
105
                return false;
106
            }
107
        }
108
        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
    protected function performOnFiles(Entity $entity, $entityName, $function) {
122
        $fields = $this->definition->getEditableFieldNames();
123
        foreach ($fields as $field) {
124
            if ($this->definition->getType($field) == 'file') {
125
                $function($entity, $entityName, $field);
126
            }
127
        }
128
    }
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
    protected function enrichEntityWithMetaData($id, Entity $entity) {
141
        $entity->set('id', $id);
142
        $createdEntity = $this->get($entity->get('id'));
143
        $entity->set('version', $createdEntity->get('version'));
144
        $entity->set('created_at', $createdEntity->get('created_at'));
145
        $entity->set('updated_at', $createdEntity->get('updated_at'));
146
    }
147
148
    /**
149
     * Gets the many-to-many fields.
150
     *
151
     * @return array|\string[]
152
     * the many-to-many fields
153
     */
154
    protected function getManyFields() {
155
        $fields = $this->definition->getFieldNames(true);
156
        return array_filter($fields, function($field) {
157
            return $this->definition->getType($field) === 'many';
158
        });
159
    }
160
161
    /**
162
     * Gets all form fields including the many-to-many-ones.
163
     *
164
     * @return array
165
     * all form fields
166
     */
167
    protected function getFormFields() {
168
        $manyFields = $this->getManyFields();
169
        $formFields = [];
170
        foreach ($this->definition->getEditableFieldNames() as $field) {
171
            if (!in_array($field, $manyFields)) {
172
                $formFields[] = $field;
173
            }
174
        }
175
        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
    protected function deleteChildren($id, $deleteCascade) {
187
        foreach ($this->definition->getChildren() as $childArray) {
188
            $childData = $this->definition->getServiceProvider()->getData($childArray[2]);
189
            $children  = $childData->listEntries([$childArray[1] => $id]);
190
            foreach ($children as $child) {
191
                $childData->doDelete($child, $deleteCascade);
192
            }
193
        }
194
    }
195
196
    /**
197
     * Gets an array of reference ids for the given entities.
198
     *
199
     * @param array $entities
200
     * the entities to extract the ids
201
     * @param string $field
202
     * the reference field
203
     *
204
     * @return array
205
     * the extracted ids
206
     */
207
    protected function getReferenceIds(array $entities, $field) {
208
        $ids = array_map(function(Entity $entity) use ($field) {
209
            $id = $entity->get($field);
210
            return is_array($id) ? $id['id'] : $id;
211
        }, $entities);
212
        return $ids;
213
    }
214
215
    /**
216
     * Adds an event to fire for the given parameters. The event function must
217
     * have this signature:
218
     * function (Entity $entity)
219
     * and has to return true or false.
220
     * The events are executed one after another in the added order as long as
221
     * they return "true". The first event returning "false" will stop the
222
     * process.
223
     *
224
     * @param string $moment
225
     * the "moment" of the event, can be either "before" or "after"
226
     * @param string $action
227
     * the "action" of the event, can be either "create", "update" or "delete"
228
     * @param \Closure $function
229
     * the event function to be called if set
230
     */
231
    public function pushEvent($moment, $action, \Closure $function) {
232
        $events                            = isset($this->events[$moment.'.'.$action]) ? $this->events[$moment.'.'.$action] : [];
233
        $events[]                          = $function;
234
        $this->events[$moment.'.'.$action] = $events;
235
    }
236
237
238
    /**
239
     * Removes and returns the latest event for the given parameters.
240
     *
241
     * @param string $moment
242
     * the "moment" of the event, can be either "before" or "after"
243
     * @param string $action
244
     * the "action" of the event, can be either "create", "update" or "delete"
245
     *
246
     * @return \Closure|null
247
     * the popped event or null if no event was available.
248
     */
249
    public function popEvent($moment, $action) {
250
        if (array_key_exists($moment.'.'.$action, $this->events)) {
251
            return array_pop($this->events[$moment.'.'.$action]);
252
        }
253
        return null;
254
    }
255
256
257
    /**
258
     * Gets the entity with the given id.
259
     *
260
     * @param string $id
261
     * the id
262
     *
263
     * @return Entity
264
     * the entity belonging to the id or null if not existant
265
     *
266
     * @return void
267
     */
268
    abstract public function get($id);
269
270
    /**
271
     * Gets a list of entities fullfilling the given filter or all if no
272
     * selection was given.
273
     *
274
     * @param array $filter
275
     * the filter all resulting entities must fulfill, the keys as field names
276
     * @param array $filterOperators
277
     * the operators of the filter like "=" defining the full condition of the field
278
     * @param integer|null $skip
279
     * if given and not null, it specifies the amount of rows to skip
280
     * @param integer|null $amount
281
     * if given and not null, it specifies the maximum amount of rows to retrieve
282
     * @param string|null $sortField
283
     * if given and not null, it specifies the field to sort the entries
284
     * @param boolean|null $sortAscending
285
     * if given and not null, it specifies that the sort order is ascending,
286
     * descending else
287
     *
288
     * @return Entity[]
289
     * the entities fulfilling the filter or all if no filter was given
290
     */
291
    abstract public function listEntries(array $filter = [], array $filterOperators = [], $skip = null, $amount = null, $sortField = null, $sortAscending = null);
292
293
    /**
294
     * Persists the given entity as new entry in the datasource.
295
     *
296
     * @param Entity $entity
297
     * the entity to persist
298
     *
299
     * @return boolean
300
     * true on successful creation
301
     */
302
    abstract 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...
303
304
    /**
305
     * Updates an existing entry in the datasource having the same id.
306
     *
307
     * @param Entity $entity
308
     * the entity with the new data
309
     *
310
     * @return void
311
     */
312
    abstract public function update(Entity $entity);
313
314
    /**
315
     * Deletes an entry from the datasource.
316
     *
317
     * @param Entity $entity
318
     * the entity to delete
319
     *
320
     * @return integer
321
     * returns one of:
322
     * - AbstractData::DELETION_SUCCESS -> successful deletion
323
     * - AbstractData::DELETION_FAILED_STILL_REFERENCED -> failed deletion due to existing references
324
     * - AbstractData::DELETION_FAILED_EVENT -> failed deletion due to a failed before delete event
325
     */
326
    public function delete($entity) {
327
        return $this->doDelete($entity, $this->definition->isDeleteCascade());
328
    }
329
330
    /**
331
     * Gets ids and names of a table. Used for building up the dropdown box of
332
     * reference type fields for example.
333
     *
334
     * @param string $entity
335
     * the entity
336
     * @param string $nameField
337
     * the field defining the name of the rows
338
     *
339
     * @return array
340
     * an array with the ids as key and the names as values
341
     */
342
    abstract public function getIdToNameMap($entity, $nameField);
343
344
    /**
345
     * Retrieves the amount of entities in the datasource fulfilling the given
346
     * parameters.
347
     *
348
     * @param string $table
349
     * the table to count in
350
     * @param array $params
351
     * an array with the field names as keys and field values as values
352
     * @param array $paramsOperators
353
     * the operators of the parameters like "=" defining the full condition of the field
354
     * @param boolean $excludeDeleted
355
     * false, if soft deleted entries in the datasource should be counted, too
356
     *
357
     * @return integer
358
     * the count fulfilling the given parameters
359
     */
360
    abstract public function countBy($table, array $params, array $paramsOperators, $excludeDeleted);
361
362
    /**
363
     * Checks whether a given set of ids is assigned to any entity exactly
364
     * like it is given (no subset, no superset).
365
     *
366
     * @param string $field
367
     * the many field
368
     * @param array $thatIds
369
     * the id set to check
370
     * @param string|null $excludeId
371
     * one optional own id to exclude from the check
372
     *
373
     * @return boolean
374
     * true if the set of ids exists for an entity
375
     */
376
    abstract public function hasManySet($field, array $thatIds, $excludeId = null);
377
378
    /**
379
     * Gets the {@see EntityDefinition} instance.
380
     *
381
     * @return EntityDefinition
382
     * the definition instance
383
     */
384
    public function getDefinition() {
385
        return $this->definition;
386
    }
387
388
    /**
389
     * Creates a new, empty entity instance having all fields prefilled with
390
     * null or the defined value in case of fixed fields.
391
     *
392
     * @return Entity
393
     * the newly created entity
394
     */
395
    public function createEmpty() {
396
        $entity = new Entity($this->definition);
397
        $fields = $this->definition->getEditableFieldNames();
398
        foreach ($fields as $field) {
399
            $value = null;
400
            if ($this->definition->getType($field) == 'fixed') {
401
                $value = $this->definition->getField($field, 'value');
402
            }
403
            $entity->set($field, $value);
404
        }
405
        $entity->set('id', null);
406
        return $entity;
407
    }
408
409
    /**
410
     * Creates the uploaded files of a newly created entity.
411
     *
412
     * @param Request $request
413
     * the HTTP request containing the file data
414
     * @param Entity $entity
415
     * the just created entity
416
     * @param string $entityName
417
     * the name of the entity as this class here is not aware of it
418
     *
419
     * @return boolean
420
     * true if all before events passed
421
     */
422 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...
423
        $result = $this->shouldExecuteEvents($entity, 'before', 'createFiles');
424
        if (!$result) {
425
            return false;
426
        }
427
        $fileProcessor = $this->fileProcessor;
428
        $this->performOnFiles($entity, $entityName, function($entity, $entityName, $field) use ($fileProcessor, $request) {
429
            $fileProcessor->createFile($request, $entity, $entityName, $field);
430
        });
431
        $this->shouldExecuteEvents($entity, 'after', 'createFiles');
432
        return true;
433
    }
434
435
    /**
436
     * Updates the uploaded files of an updated entity.
437
     *
438
     * @param Request $request
439
     * the HTTP request containing the file data
440
     * @param Entity $entity
441
     * the updated entity
442
     * @param string $entityName
443
     * the name of the entity as this class here is not aware of it
444
     *
445
     * @return boolean
446
     * true on successful update
447
     */
448 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...
449
        $result = $this->shouldExecuteEvents($entity, 'before', 'updateFiles');
450
        if (!$result) {
451
            return false;
452
        }
453
        $fileProcessor = $this->fileProcessor;
454
        $this->performOnFiles($entity, $entityName, function($entity, $entityName, $field) use ($fileProcessor, $request) {
455
            $fileProcessor->updateFile($request, $entity, $entityName, $field);
456
        });
457
        $this->shouldExecuteEvents($entity, 'after', 'updateFiles');
458
        return true;
459
    }
460
461
    /**
462
     * Deletes a specific file from an existing entity.
463
     *
464
     * @param Entity $entity
465
     * the entity to delete the file from
466
     * @param string $entityName
467
     * the name of the entity as this class here is not aware of it
468
     * @param string $field
469
     * the field of the entity containing the file to be deleted
470
     *
471
     * @return boolean
472
     * true on successful deletion
473
     */
474
    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...
475
        $result = $this->shouldExecuteEvents($entity, 'before', 'deleteFile');
476
        if (!$result) {
477
            return false;
478
        }
479
        $this->fileProcessor->deleteFile($entity, $entityName, $field);
480
        $this->shouldExecuteEvents($entity, 'after', 'deleteFile');
481
        return true;
482
    }
483
484
    /**
485
     * Deletes all files of an existing entity.
486
     *
487
     * @param Entity $entity
488
     * the entity to delete the files from
489
     * @param string $entityName
490
     * the name of the entity as this class here is not aware of it
491
     *
492
     * @return boolean
493
     * true on successful deletion
494
     */
495 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...
496
        $result = $this->shouldExecuteEvents($entity, 'before', 'deleteFiles');
497
        if (!$result) {
498
            return false;
499
        }
500
        $fileProcessor = $this->fileProcessor;
501
        $this->performOnFiles($entity, $entityName, function($entity, $entityName, $field) use ($fileProcessor) {
502
            $fileProcessor->deleteFile($entity, $entityName, $field);
503
        });
504
        $this->shouldExecuteEvents($entity, 'after', 'deleteFiles');
505
        return true;
506
    }
507
508
    /**
509
     * Renders (outputs) a file of an entity. This includes setting headers
510
     * like the file size, mimetype and name, too.
511
     *
512
     * @param Entity $entity
513
     * the entity to render the file from
514
     * @param string $entityName
515
     * the name of the entity as this class here is not aware of it
516
     * @param string $field
517
     * the field of the entity containing the file to be rendered
518
     *
519
     * @return Response
520
     * the HTTP response, likely to be a streamed one
521
     */
522
    public function renderFile(Entity $entity, $entityName, $field) {
523
        return $this->fileProcessor->renderFile($entity, $entityName, $field);
524
    }
525
526
}
527