Completed
Push — master ( 24ed3a...5b7e22 )
by Philip
02:21
created

AbstractData::getManyFields()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 0
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
     * Fetches to the rows belonging many-to-many entries and adds them to the rows.
163
     *
164
     * @param array $rows
165
     * the rows to enrich
166
     * @return array
167
     * the enriched rows
168
     */
169
    protected function enrichWithMany(array $rows) {
170
        $manyFields = $this->getManyFields();
171
        $idToData   = [];
172
        foreach ($rows as $row) {
173
            foreach ($manyFields as $manyField) {
174
                $row[$manyField] = [];
175
            }
176
            $idToData[$row['id']] = $row;
177
        }
178
        foreach ($manyFields as $manyField) {
179
            $this->enrichWithManyField($idToData, $manyField);
0 ignored issues
show
Bug introduced by
The method enrichWithManyField() does not exist on CRUDlex\AbstractData. Did you maybe mean enrichWithMany()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

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