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
|
|
|
use CRUDlex\EntityDefinition; |
18
|
|
|
use CRUDlex\Entity; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* The abstract class for reading and writing data. |
22
|
|
|
*/ |
23
|
|
|
abstract class AbstractData { |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Return value on successful deletion. |
27
|
|
|
*/ |
28
|
|
|
const DELETION_SUCCESS = 0; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Return value on failed deletion due to existing references. |
32
|
|
|
*/ |
33
|
|
|
const DELETION_FAILED_STILL_REFERENCED = 1; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Return value on failed deletion due to a failed before delete event. |
37
|
|
|
*/ |
38
|
|
|
const DELETION_FAILED_EVENT = 2; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Holds the {@see EntityDefinition} entity definition. |
42
|
|
|
*/ |
43
|
|
|
protected $definition; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Holds the {@see FileProcessorInterface} file processor. |
47
|
|
|
*/ |
48
|
|
|
protected $fileProcessor; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Holds the events. |
52
|
|
|
*/ |
53
|
|
|
protected $events; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Performs the actual deletion. |
57
|
|
|
* |
58
|
|
|
* @param Entity $entity |
59
|
|
|
* the id of the entry to delete |
60
|
|
|
* @param boolean $deleteCascade |
61
|
|
|
* whether to delete children and subchildren |
62
|
|
|
* |
63
|
|
|
* @return integer |
64
|
|
|
* true on successful deletion |
65
|
|
|
*/ |
66
|
|
|
abstract protected function doDelete(Entity $entity, $deleteCascade); |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Creates an {@see Entity} from the raw data array with the field name |
70
|
|
|
* as keys and field values as values. |
71
|
|
|
* |
72
|
|
|
* @param array $row |
73
|
|
|
* the array with the raw data |
74
|
|
|
* |
75
|
|
|
* @return Entity |
76
|
|
|
* the entity containing the array data then |
77
|
|
|
*/ |
78
|
|
|
protected function hydrate(array $row) { |
79
|
|
|
$fieldNames = $this->definition->getFieldNames(); |
80
|
|
|
$entity = new Entity($this->definition); |
81
|
|
|
foreach ($fieldNames as $fieldName) { |
82
|
|
|
$entity->set($fieldName, $row[$fieldName]); |
83
|
|
|
} |
84
|
|
|
return $entity; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Executes the event chain of an entity. |
89
|
|
|
* |
90
|
|
|
* @param Entity $entity |
91
|
|
|
* the entity having the event chain to execute |
92
|
|
|
* @param string $moment |
93
|
|
|
* the "moment" of the event, can be either "before" or "after" |
94
|
|
|
* @param string $action |
95
|
|
|
* the "action" of the event, can be either "create", "update" or "delete" |
96
|
|
|
* |
97
|
|
|
* @return boolean |
98
|
|
|
* true on successful execution of the full chain or false if it broke at |
99
|
|
|
* any point (and stopped the execution) |
100
|
|
|
*/ |
101
|
|
|
protected function shouldExecuteEvents(Entity $entity, $moment, $action) { |
102
|
|
|
if (!isset($this->events[$moment.'.'.$action])) { |
103
|
|
|
return true; |
104
|
|
|
} |
105
|
|
|
foreach ($this->events[$moment.'.'.$action] as $event) { |
106
|
|
|
$result = $event($entity); |
107
|
|
|
if (!$result) { |
108
|
|
|
return false; |
109
|
|
|
} |
110
|
|
|
} |
111
|
|
|
return true; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Executes a function for each file field of this entity. |
116
|
|
|
* |
117
|
|
|
* @param Entity $entity |
118
|
|
|
* the just created entity |
119
|
|
|
* @param string $entityName |
120
|
|
|
* the name of the entity as this class here is not aware of it |
121
|
|
|
* @param \Closure $function |
122
|
|
|
* the function to perform, takes $entity, $entityName and $field as parameter |
123
|
|
|
*/ |
124
|
|
|
protected function performOnFiles(Entity $entity, $entityName, $function) { |
125
|
|
|
$fields = $this->definition->getEditableFieldNames(); |
126
|
|
|
foreach ($fields as $field) { |
127
|
|
|
if ($this->definition->getType($field) == 'file') { |
128
|
|
|
$function($entity, $entityName, $field); |
129
|
|
|
} |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Adds an event to fire for the given parameters. The event function must |
135
|
|
|
* have this signature: |
136
|
|
|
* function (Entity $entity) |
137
|
|
|
* and has to return true or false. |
138
|
|
|
* The events are executed one after another in the added order as long as |
139
|
|
|
* they return "true". The first event returning "false" will stop the |
140
|
|
|
* process. |
141
|
|
|
* |
142
|
|
|
* @param string $moment |
143
|
|
|
* the "moment" of the event, can be either "before" or "after" |
144
|
|
|
* @param string $action |
145
|
|
|
* the "action" of the event, can be either "create", "update" or "delete" |
146
|
|
|
* @param anonymous function $function |
147
|
|
|
* the event function to be called if set |
148
|
|
|
*/ |
149
|
|
|
public function pushEvent($moment, $action, $function) { |
150
|
|
|
$events = isset($this->events[$moment.'.'.$action]) ? $this->events[$moment.'.'.$action] : array(); |
151
|
|
|
$events[] = $function; |
152
|
|
|
$this->events[$moment.'.'.$action] = $events; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Removes and returns the latest event for the given parameters. |
158
|
|
|
* |
159
|
|
|
* @param string $moment |
160
|
|
|
* the "moment" of the event, can be either "before" or "after" |
161
|
|
|
* @param string $action |
162
|
|
|
* the "action" of the event, can be either "create", "update" or "delete" |
163
|
|
|
* |
164
|
|
|
* @return anonymous function |
165
|
|
|
* the popped event or null if no event was available. |
166
|
|
|
*/ |
167
|
|
|
public function popEvent($moment, $action) { |
168
|
|
|
if (array_key_exists($moment.'.'.$action, $this->events)) { |
169
|
|
|
return array_pop($this->events[$moment.'.'.$action]); |
170
|
|
|
} |
171
|
|
|
return null; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* Gets the entity with the given id. |
177
|
|
|
* |
178
|
|
|
* @param string $id |
179
|
|
|
* the id |
180
|
|
|
* |
181
|
|
|
* @return Entity |
182
|
|
|
* the entity belonging to the id or null if not existant |
183
|
|
|
* |
184
|
|
|
* @return void |
185
|
|
|
*/ |
186
|
|
|
abstract public function get($id); |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Gets a list of entities fullfilling the given filter or all if no |
190
|
|
|
* selection was given. |
191
|
|
|
* |
192
|
|
|
* @param array $filter |
193
|
|
|
* the filter all resulting entities must fulfill, the keys as field names |
194
|
|
|
* @param array $filterOperators |
195
|
|
|
* the operators of the filter like "=" defining the full condition of the field |
196
|
|
|
* @param integer|null $skip |
197
|
|
|
* if given and not null, it specifies the amount of rows to skip |
198
|
|
|
* @param integer|null $amount |
199
|
|
|
* if given and not null, it specifies the maximum amount of rows to retrieve |
200
|
|
|
* @param string|null $sortField |
201
|
|
|
* if given and not null, it specifies the field to sort the entries |
202
|
|
|
* @param boolean|null $sortAscending |
203
|
|
|
* if given and not null, it specifies that the sort order is ascending, |
204
|
|
|
* descending else |
205
|
|
|
* |
206
|
|
|
* @return Entity[] |
207
|
|
|
* the entities fulfilling the filter or all if no filter was given |
208
|
|
|
*/ |
209
|
|
|
abstract public function listEntries(array $filter = array(), array $filterOperators = array(), $skip = null, $amount = null, $sortField = null, $sortAscending = null); |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Persists the given entity as new entry in the datasource. |
213
|
|
|
* |
214
|
|
|
* @param Entity $entity |
215
|
|
|
* the entity to persist |
216
|
|
|
* |
217
|
|
|
* @return boolean |
218
|
|
|
* true on successful creation |
219
|
|
|
*/ |
220
|
|
|
abstract public function create(Entity $entity); |
|
|
|
|
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* Updates an existing entry in the datasource having the same id. |
224
|
|
|
* |
225
|
|
|
* @param Entity $entity |
226
|
|
|
* the entity with the new data |
227
|
|
|
* |
228
|
|
|
* @return void |
229
|
|
|
*/ |
230
|
|
|
abstract public function update(Entity $entity); |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* Deletes an entry from the datasource having the given id. |
234
|
|
|
* |
235
|
|
|
* @param Entity $entity |
236
|
|
|
* the id of the entry to delete |
237
|
|
|
* |
238
|
|
|
* @return integer |
239
|
|
|
* returns one of: |
240
|
|
|
* - AbstractData::DELETION_SUCCESS -> successful deletion |
241
|
|
|
* - AbstractData::DELETION_FAILED_STILL_REFERENCED -> failed deletion due to existing references |
242
|
|
|
* - AbstractData::DELETION_FAILED_EVENT -> failed deletion due to a failed before delete event |
243
|
|
|
*/ |
244
|
|
|
public function delete($entity) { |
245
|
|
|
return $this->doDelete($entity, $this->definition->isDeleteCascade()); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* Gets ids and names of a table. Used for building up the dropdown box of |
250
|
|
|
* reference type fields. |
251
|
|
|
* |
252
|
|
|
* @param string $table |
253
|
|
|
* the table |
254
|
|
|
* @param string nameField |
255
|
|
|
* the field defining the name of the rows |
256
|
|
|
* |
257
|
|
|
* @return array |
258
|
|
|
* an array with the ids as key and the names as values |
259
|
|
|
*/ |
260
|
|
|
abstract public function getReferences($table, $nameField); |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* Retrieves the amount of entities in the datasource fulfilling the given |
264
|
|
|
* parameters. |
265
|
|
|
* |
266
|
|
|
* @param string $table |
267
|
|
|
* the table to count in |
268
|
|
|
* @param array $params |
269
|
|
|
* an array with the field names as keys and field values as values |
270
|
|
|
* @param array $paramsOperators |
271
|
|
|
* the operators of the parameters like "=" defining the full condition of the field |
272
|
|
|
* @param boolean $excludeDeleted |
273
|
|
|
* false, if soft deleted entries in the datasource should be counted, too |
274
|
|
|
* |
275
|
|
|
* @return integer |
276
|
|
|
* the count fulfilling the given parameters |
277
|
|
|
*/ |
278
|
|
|
abstract public function countBy($table, array $params, array $paramsOperators, $excludeDeleted); |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* Adds the id and name of referenced entities to the given entities. Each |
282
|
|
|
* reference field is before the raw id of the referenced entity and after |
283
|
|
|
* the fetch, it's an array with the keys id and name. |
284
|
|
|
* |
285
|
|
|
* @param Entity[] &$entities |
286
|
|
|
* the entities to fetch the references for |
287
|
|
|
* |
288
|
|
|
* @return void |
289
|
|
|
*/ |
290
|
|
|
abstract public function fetchReferences(array &$entities = null); |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Gets the {@see EntityDefinition} instance. |
294
|
|
|
* |
295
|
|
|
* @return EntityDefinition |
296
|
|
|
* the definition instance |
297
|
|
|
*/ |
298
|
|
|
public function getDefinition() { |
299
|
|
|
return $this->definition; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Creates a new, empty entity instance having all fields prefilled with |
304
|
|
|
* null or the defined value in case of fixed fields. |
305
|
|
|
* |
306
|
|
|
* @return Entity |
307
|
|
|
* the newly created entity |
308
|
|
|
*/ |
309
|
|
|
public function createEmpty() { |
310
|
|
|
$entity = new Entity($this->definition); |
311
|
|
|
$fields = $this->definition->getEditableFieldNames(); |
312
|
|
|
foreach ($fields as $field) { |
313
|
|
|
$value = null; |
314
|
|
|
if ($this->definition->getType($field) == 'fixed') { |
315
|
|
|
$value = $this->definition->getFixedValue($field); |
316
|
|
|
} |
317
|
|
|
$entity->set($field, $value); |
318
|
|
|
} |
319
|
|
|
$entity->set('id', null); |
320
|
|
|
return $entity; |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
/** |
324
|
|
|
* Creates the uploaded files of a newly created entity. |
325
|
|
|
* |
326
|
|
|
* @param Request $request |
327
|
|
|
* the HTTP request containing the file data |
328
|
|
|
* @param Entity $entity |
329
|
|
|
* the just created entity |
330
|
|
|
* @param string $entityName |
331
|
|
|
* the name of the entity as this class here is not aware of it |
332
|
|
|
*/ |
333
|
|
View Code Duplication |
public function createFiles(Request $request, Entity $entity, $entityName) { |
|
|
|
|
334
|
|
|
$result = $this->shouldExecuteEvents($entity, 'before', 'createFiles'); |
335
|
|
|
if (!$result) { |
336
|
|
|
return false; |
337
|
|
|
} |
338
|
|
|
$fileProcessor = $this->fileProcessor; |
339
|
|
|
$this->performOnFiles($entity, $entityName, function($entity, $entityName, $field) use ($fileProcessor, $request) { |
340
|
|
|
$fileProcessor->createFile($request, $entity, $entityName, $field); |
341
|
|
|
}); |
342
|
|
|
$this->shouldExecuteEvents($entity, 'after', 'createFiles'); |
343
|
|
|
return true; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* Updates the uploaded files of an updated entity. |
348
|
|
|
* |
349
|
|
|
* @param Request $request |
350
|
|
|
* the HTTP request containing the file data |
351
|
|
|
* @param Entity $entity |
352
|
|
|
* the updated entity |
353
|
|
|
* @param string $entityName |
354
|
|
|
* the name of the entity as this class here is not aware of it |
355
|
|
|
* |
356
|
|
|
* @return boolean |
357
|
|
|
* true on successful update |
358
|
|
|
*/ |
359
|
|
View Code Duplication |
public function updateFiles(Request $request, Entity $entity, $entityName) { |
|
|
|
|
360
|
|
|
$result = $this->shouldExecuteEvents($entity, 'before', 'updateFiles'); |
361
|
|
|
if (!$result) { |
362
|
|
|
return false; |
363
|
|
|
} |
364
|
|
|
$fileProcessor = $this->fileProcessor; |
365
|
|
|
$this->performOnFiles($entity, $entityName, function($entity, $entityName, $field) use ($fileProcessor, $request) { |
366
|
|
|
$fileProcessor->updateFile($request, $entity, $entityName, $field); |
367
|
|
|
}); |
368
|
|
|
$this->shouldExecuteEvents($entity, 'after', 'updateFiles'); |
369
|
|
|
return true; |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* Deletes a specific file from an existing entity. |
374
|
|
|
* |
375
|
|
|
* @param Entity $entity |
376
|
|
|
* the entity to delete the file from |
377
|
|
|
* @param string $entityName |
378
|
|
|
* the name of the entity as this class here is not aware of it |
379
|
|
|
* @param string $field |
380
|
|
|
* the field of the entity containing the file to be deleted |
381
|
|
|
* |
382
|
|
|
* @return boolean |
383
|
|
|
* true on successful deletion |
384
|
|
|
*/ |
385
|
|
|
public function deleteFile(Entity $entity, $entityName, $field) { |
|
|
|
|
386
|
|
|
$result = $this->shouldExecuteEvents($entity, 'before', 'deleteFile'); |
387
|
|
|
if (!$result) { |
388
|
|
|
return false; |
389
|
|
|
} |
390
|
|
|
$this->fileProcessor->deleteFile($entity, $entityName, $field); |
391
|
|
|
$this->shouldExecuteEvents($entity, 'after', 'deleteFile'); |
392
|
|
|
return true; |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
/** |
396
|
|
|
* Deletes all files of an existing entity. |
397
|
|
|
* |
398
|
|
|
* @param Entity $entity |
399
|
|
|
* the entity to delete the files from |
400
|
|
|
* @param string $entityName |
401
|
|
|
* the name of the entity as this class here is not aware of it |
402
|
|
|
* |
403
|
|
|
* @return boolean |
404
|
|
|
* true on successful deletion |
405
|
|
|
*/ |
406
|
|
View Code Duplication |
public function deleteFiles(Entity $entity, $entityName) { |
|
|
|
|
407
|
|
|
$result = $this->shouldExecuteEvents($entity, 'before', 'deleteFiles'); |
408
|
|
|
if (!$result) { |
409
|
|
|
return false; |
410
|
|
|
} |
411
|
|
|
$fileProcessor = $this->fileProcessor; |
412
|
|
|
$this->performOnFiles($entity, $entityName, function($entity, $entityName, $field) use ($fileProcessor) { |
413
|
|
|
$fileProcessor->deleteFile($entity, $entityName, $field); |
414
|
|
|
}); |
415
|
|
|
$this->shouldExecuteEvents($entity, 'after', 'deleteFiles'); |
416
|
|
|
return true; |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
/** |
420
|
|
|
* Renders (outputs) a file of an entity. This includes setting headers |
421
|
|
|
* like the file size, mimetype and name, too. |
422
|
|
|
* |
423
|
|
|
* @param Entity $entity |
424
|
|
|
* the entity to render the file from |
425
|
|
|
* @param string $entityName |
426
|
|
|
* the name of the entity as this class here is not aware of it |
427
|
|
|
* @param string $field |
428
|
|
|
* the field of the entity containing the file to be rendered |
429
|
|
|
* |
430
|
|
|
* @return Response |
431
|
|
|
* the HTTP response, likely to be a streamed one |
432
|
|
|
*/ |
433
|
|
|
public function renderFile(Entity $entity, $entityName, $field) { |
434
|
|
|
return $this->fileProcessor->renderFile($entity, $entityName, $field); |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
} |
438
|
|
|
|
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.