|
1
|
|
|
<?php namespace Limoncello\Flute\Api; |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* Copyright 2015-2017 [email protected] |
|
5
|
|
|
* |
|
6
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
7
|
|
|
* you may not use this file except in compliance with the License. |
|
8
|
|
|
* You may obtain a copy of the License at |
|
9
|
|
|
* |
|
10
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
11
|
|
|
* |
|
12
|
|
|
* Unless required by applicable law or agreed to in writing, software |
|
13
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
14
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
15
|
|
|
* See the License for the specific language governing permissions and |
|
16
|
|
|
* limitations under the License. |
|
17
|
|
|
*/ |
|
18
|
|
|
|
|
19
|
|
|
use ArrayObject; |
|
20
|
|
|
use Closure; |
|
21
|
|
|
use Doctrine\DBAL\Driver\PDOConnection; |
|
22
|
|
|
use Doctrine\DBAL\Platforms\AbstractPlatform; |
|
23
|
|
|
use Doctrine\DBAL\Query\QueryBuilder; |
|
24
|
|
|
use Doctrine\DBAL\Types\Type; |
|
25
|
|
|
use Generator; |
|
26
|
|
|
use Limoncello\Container\Traits\HasContainerTrait; |
|
27
|
|
|
use Limoncello\Contracts\Data\ModelSchemeInfoInterface; |
|
28
|
|
|
use Limoncello\Contracts\Data\RelationshipTypes; |
|
29
|
|
|
use Limoncello\Contracts\L10n\FormatterFactoryInterface; |
|
30
|
|
|
use Limoncello\Flute\Contracts\Adapters\PaginationStrategyInterface; |
|
31
|
|
|
use Limoncello\Flute\Contracts\Adapters\RepositoryInterface; |
|
32
|
|
|
use Limoncello\Flute\Contracts\Api\CrudInterface; |
|
33
|
|
|
use Limoncello\Flute\Contracts\FactoryInterface; |
|
34
|
|
|
use Limoncello\Flute\Contracts\Http\Query\IncludeParameterInterface; |
|
35
|
|
|
use Limoncello\Flute\Contracts\Models\ModelStorageInterface; |
|
36
|
|
|
use Limoncello\Flute\Contracts\Models\PaginatedDataInterface; |
|
37
|
|
|
use Limoncello\Flute\Contracts\Models\TagStorageInterface; |
|
38
|
|
|
use Limoncello\Flute\Exceptions\InvalidArgumentException; |
|
39
|
|
|
use Limoncello\Flute\Http\Query\FilterParameterCollection; |
|
40
|
|
|
use Limoncello\Flute\L10n\Messages; |
|
41
|
|
|
use Neomerx\JsonApi\Contracts\Document\DocumentInterface; |
|
42
|
|
|
use Neomerx\JsonApi\Exceptions\ErrorCollection; |
|
43
|
|
|
use Neomerx\JsonApi\Exceptions\JsonApiException as E; |
|
44
|
|
|
use Psr\Container\ContainerInterface; |
|
45
|
|
|
|
|
46
|
|
|
/** |
|
47
|
|
|
* @package Limoncello\Flute |
|
48
|
|
|
* |
|
49
|
|
|
* @SuppressWarnings(PHPMD.TooManyMethods) |
|
50
|
|
|
* @SuppressWarnings(PHPMD.TooManyPublicMethods) |
|
51
|
|
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
|
52
|
|
|
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity) |
|
53
|
|
|
*/ |
|
54
|
|
|
class Crud implements CrudInterface |
|
55
|
|
|
{ |
|
56
|
|
|
use HasContainerTrait; |
|
57
|
|
|
|
|
58
|
|
|
/** Internal constant. Query param name. */ |
|
59
|
|
|
protected const INDEX_BIND = ':index'; |
|
60
|
|
|
|
|
61
|
|
|
/** Internal constant. Query param name. */ |
|
62
|
|
|
protected const CHILD_INDEX_BIND = ':childIndex'; |
|
63
|
|
|
|
|
64
|
|
|
/** Internal constant. Path constant. */ |
|
65
|
|
|
protected const ROOT_PATH = ''; |
|
66
|
|
|
|
|
67
|
|
|
/** Internal constant. Path constant. */ |
|
68
|
|
|
protected const PATH_SEPARATOR = DocumentInterface::PATH_SEPARATOR; |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* @var FactoryInterface |
|
72
|
|
|
*/ |
|
73
|
|
|
private $factory; |
|
74
|
|
|
|
|
75
|
|
|
/** |
|
76
|
|
|
* @var string |
|
77
|
|
|
*/ |
|
78
|
|
|
private $modelClass; |
|
79
|
|
|
|
|
80
|
|
|
/** |
|
81
|
|
|
* @var RepositoryInterface |
|
82
|
|
|
*/ |
|
83
|
|
|
private $repository; |
|
84
|
|
|
|
|
85
|
|
|
/** |
|
86
|
|
|
* @var ModelSchemeInfoInterface |
|
87
|
|
|
*/ |
|
88
|
|
|
private $modelSchemes; |
|
89
|
|
|
|
|
90
|
|
|
/** |
|
91
|
|
|
* @var PaginationStrategyInterface |
|
92
|
|
|
*/ |
|
93
|
|
|
private $paginationStrategy; |
|
94
|
|
|
|
|
95
|
|
|
/** |
|
96
|
|
|
* @param ContainerInterface $container |
|
97
|
|
|
* @param string $modelClass |
|
98
|
|
|
*/ |
|
99
|
46 |
|
public function __construct(ContainerInterface $container, string $modelClass) |
|
100
|
|
|
{ |
|
101
|
46 |
|
$this->setContainer($container); |
|
102
|
|
|
|
|
103
|
46 |
|
$this->factory = $this->getContainer()->get(FactoryInterface::class); |
|
104
|
46 |
|
$this->modelClass = $modelClass; |
|
105
|
46 |
|
$this->repository = $this->getContainer()->get(RepositoryInterface::class); |
|
106
|
46 |
|
$this->modelSchemes = $this->getContainer()->get(ModelSchemeInfoInterface::class); |
|
107
|
46 |
|
$this->paginationStrategy = $this->getContainer()->get(PaginationStrategyInterface::class); |
|
108
|
|
|
} |
|
109
|
|
|
|
|
110
|
|
|
/** |
|
111
|
|
|
* @inheritdoc |
|
112
|
|
|
*/ |
|
113
|
16 |
|
public function index( |
|
114
|
|
|
FilterParameterCollection $filterParams = null, |
|
115
|
|
|
array $sortParams = null, |
|
116
|
|
|
array $includePaths = null, |
|
117
|
|
|
array $pagingParams = null |
|
118
|
|
|
): PaginatedDataInterface { |
|
119
|
16 |
|
$modelClass = $this->getModelClass(); |
|
120
|
|
|
|
|
121
|
16 |
|
$builder = $this->getRepository()->index($modelClass); |
|
122
|
|
|
|
|
123
|
16 |
|
$errors = $this->getFactory()->createErrorCollection(); |
|
124
|
16 |
|
$filterParams === null ?: $this->getRepository()->applyFilters($errors, $builder, $modelClass, $filterParams); |
|
125
|
16 |
|
$this->checkErrors($errors); |
|
126
|
15 |
|
$sortParams === null ?: $this->getRepository()->applySorting($builder, $modelClass, $sortParams); |
|
127
|
|
|
|
|
128
|
15 |
|
list($offset, $limit) = $this->getPaginationStrategy()->parseParameters($pagingParams); |
|
129
|
15 |
|
$builder->setFirstResult($offset)->setMaxResults($limit + 1); |
|
130
|
|
|
|
|
131
|
15 |
|
$data = $this->fetchCollectionData($this->builderOnIndex($builder), $modelClass); |
|
132
|
|
|
|
|
133
|
15 |
|
$this->loadRelationships($data, $includePaths); |
|
|
|
|
|
|
134
|
|
|
|
|
135
|
15 |
|
return $data; |
|
136
|
|
|
} |
|
137
|
|
|
|
|
138
|
|
|
/** |
|
139
|
|
|
* @inheritdoc |
|
140
|
|
|
*/ |
|
141
|
1 |
|
public function indexResources(FilterParameterCollection $filterParams = null, array $sortParams = null): array |
|
142
|
|
|
{ |
|
143
|
1 |
|
$modelClass = $this->getModelClass(); |
|
144
|
|
|
|
|
145
|
1 |
|
$builder = $this->getRepository()->index($modelClass); |
|
146
|
|
|
|
|
147
|
1 |
|
$errors = $this->getFactory()->createErrorCollection(); |
|
148
|
1 |
|
$filterParams === null ?: $this->getRepository()->applyFilters($errors, $builder, $modelClass, $filterParams); |
|
149
|
1 |
|
$this->checkErrors($errors); |
|
150
|
1 |
|
$sortParams === null ?: $this->getRepository()->applySorting($builder, $modelClass, $sortParams); |
|
151
|
|
|
|
|
152
|
1 |
|
list($models) = $this->fetchCollection($this->builderOnIndex($builder), $modelClass); |
|
153
|
|
|
|
|
154
|
1 |
|
return $models; |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
/** |
|
158
|
|
|
* @inheritdoc |
|
159
|
|
|
*/ |
|
160
|
1 |
|
public function count(FilterParameterCollection $filterParams = null): ?int |
|
161
|
|
|
{ |
|
162
|
1 |
|
$modelClass = $this->getModelClass(); |
|
163
|
|
|
|
|
164
|
1 |
|
$builder = $this->getRepository()->count($modelClass); |
|
165
|
|
|
|
|
166
|
1 |
|
$errors = $this->getFactory()->createErrorCollection(); |
|
167
|
1 |
|
$filterParams === null ?: $this->getRepository()->applyFilters($errors, $builder, $modelClass, $filterParams); |
|
168
|
1 |
|
$this->checkErrors($errors); |
|
169
|
|
|
|
|
170
|
1 |
|
$result = $this->builderOnCount($builder)->execute()->fetchColumn(); |
|
171
|
|
|
|
|
172
|
1 |
|
return $result === false ? null : $result; |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* @inheritdoc |
|
177
|
|
|
*/ |
|
178
|
11 |
|
public function read( |
|
179
|
|
|
$index, |
|
180
|
|
|
FilterParameterCollection $filterParams = null, |
|
181
|
|
|
array $includePaths = null |
|
182
|
|
|
): PaginatedDataInterface { |
|
183
|
11 |
|
$model = $this->readResource($index, $filterParams); |
|
184
|
10 |
|
$data = $this->getFactory()->createPaginatedData($model); |
|
185
|
|
|
|
|
186
|
10 |
|
$this->loadRelationships($data, $includePaths); |
|
|
|
|
|
|
187
|
|
|
|
|
188
|
10 |
|
return $data; |
|
189
|
|
|
} |
|
190
|
|
|
|
|
191
|
|
|
/** |
|
192
|
|
|
* @inheritdoc |
|
193
|
|
|
*/ |
|
194
|
11 |
|
public function readResource($index, FilterParameterCollection $filterParams = null) |
|
195
|
|
|
{ |
|
196
|
11 |
|
if ($index !== null && is_scalar($index) === false) { |
|
197
|
1 |
|
throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT)); |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
10 |
|
$modelClass = $this->getModelClass(); |
|
201
|
|
|
|
|
202
|
10 |
|
$builder = $this->getRepository() |
|
203
|
10 |
|
->read($modelClass, static::INDEX_BIND) |
|
204
|
10 |
|
->setParameter(static::INDEX_BIND, $index); |
|
205
|
|
|
|
|
206
|
10 |
|
$errors = $this->getFactory()->createErrorCollection(); |
|
207
|
10 |
|
$filterParams === null ?: $this->getRepository()->applyFilters($errors, $builder, $modelClass, $filterParams); |
|
208
|
10 |
|
$this->checkErrors($errors); |
|
209
|
|
|
|
|
210
|
10 |
|
$model = $this->fetchSingle($this->builderOnRead($builder), $modelClass); |
|
211
|
|
|
|
|
212
|
10 |
|
return $model; |
|
213
|
|
|
} |
|
214
|
|
|
|
|
215
|
|
|
/** |
|
216
|
|
|
* @inheritdoc |
|
217
|
|
|
* |
|
218
|
|
|
* @SuppressWarnings(PHPMD.ElseExpression) |
|
219
|
|
|
*/ |
|
220
|
5 |
|
public function readRelationship( |
|
221
|
|
|
$index, |
|
222
|
|
|
string $name, |
|
223
|
|
|
FilterParameterCollection $filterParams = null, |
|
224
|
|
|
array $sortParams = null, |
|
225
|
|
|
array $pagingParams = null |
|
226
|
|
|
): PaginatedDataInterface { |
|
227
|
5 |
|
if ($index !== null && is_scalar($index) === false) { |
|
228
|
1 |
|
throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT)); |
|
229
|
|
|
} |
|
230
|
|
|
|
|
231
|
4 |
|
$modelClass = $this->getModelClass(); |
|
232
|
|
|
|
|
233
|
|
|
/** @var QueryBuilder $builder */ |
|
234
|
|
|
list ($builder, $resultClass, $relationshipType) = |
|
235
|
4 |
|
$this->getRepository()->readRelationship($modelClass, static::INDEX_BIND, $name); |
|
236
|
|
|
|
|
237
|
4 |
|
$errors = $this->getFactory()->createErrorCollection(); |
|
238
|
4 |
|
$filterParams === null ?: $this->getRepository()->applyFilters($errors, $builder, $resultClass, $filterParams); |
|
239
|
4 |
|
$this->checkErrors($errors); |
|
240
|
4 |
|
$sortParams === null ?: $this->getRepository()->applySorting($builder, $resultClass, $sortParams); |
|
241
|
|
|
|
|
242
|
4 |
|
$builder->setParameter(static::INDEX_BIND, $index); |
|
243
|
|
|
|
|
244
|
4 |
|
$isCollection = $relationshipType === RelationshipTypes::HAS_MANY || |
|
245
|
4 |
|
$relationshipType === RelationshipTypes::BELONGS_TO_MANY; |
|
246
|
|
|
|
|
247
|
4 |
|
if ($isCollection == true) { |
|
|
|
|
|
|
248
|
3 |
|
list($offset, $limit) = $this->getPaginationStrategy()->parseParameters($pagingParams); |
|
249
|
3 |
|
$builder->setFirstResult($offset)->setMaxResults($limit + 1); |
|
250
|
3 |
|
$data = $this->fetchCollectionData($this->builderOnReadRelationship($builder), $resultClass); |
|
251
|
|
|
} else { |
|
252
|
1 |
|
$data = $this->fetchSingleData($this->builderOnReadRelationship($builder), $resultClass); |
|
253
|
|
|
} |
|
254
|
|
|
|
|
255
|
4 |
|
return $data; |
|
256
|
|
|
} |
|
257
|
|
|
|
|
258
|
|
|
/** |
|
259
|
|
|
* @inheritdoc |
|
260
|
|
|
*/ |
|
261
|
6 |
|
public function hasInRelationship($parentId, string $name, $childId): bool |
|
262
|
|
|
{ |
|
263
|
6 |
|
if ($parentId !== null && is_scalar($parentId) === false) { |
|
264
|
1 |
|
throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT)); |
|
265
|
|
|
} |
|
266
|
5 |
|
if ($childId !== null && is_scalar($childId) === false) { |
|
267
|
1 |
|
throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT)); |
|
268
|
|
|
} |
|
269
|
|
|
|
|
270
|
4 |
|
$modelClass = $this->getModelClass(); |
|
271
|
|
|
|
|
272
|
|
|
/** @var QueryBuilder $builder */ |
|
273
|
4 |
|
list ($builder) = $this->getRepository() |
|
274
|
4 |
|
->hasInRelationship($modelClass, static::INDEX_BIND, $name, static::CHILD_INDEX_BIND); |
|
275
|
|
|
|
|
276
|
4 |
|
$builder->setParameter(static::INDEX_BIND, $parentId); |
|
277
|
4 |
|
$builder->setParameter(static::CHILD_INDEX_BIND, $childId); |
|
278
|
|
|
|
|
279
|
4 |
|
$result = $builder->execute()->fetch(); |
|
280
|
|
|
|
|
281
|
4 |
|
return $result !== false; |
|
282
|
|
|
} |
|
283
|
|
|
|
|
284
|
|
|
/** |
|
285
|
|
|
* @inheritdoc |
|
286
|
|
|
*/ |
|
287
|
2 |
|
public function readRow($index): ?array |
|
288
|
|
|
{ |
|
289
|
2 |
|
if ($index !== null && is_scalar($index) === false) { |
|
290
|
1 |
|
throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT)); |
|
291
|
|
|
} |
|
292
|
|
|
|
|
293
|
1 |
|
$modelClass = $this->getModelClass(); |
|
294
|
1 |
|
$builder = $this->getRepository() |
|
295
|
1 |
|
->read($modelClass, static::INDEX_BIND) |
|
296
|
1 |
|
->setParameter(static::INDEX_BIND, $index); |
|
297
|
1 |
|
$typedRow = $this->fetchRow($builder, $modelClass); |
|
298
|
|
|
|
|
299
|
1 |
|
return $typedRow; |
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
/** |
|
303
|
|
|
* @inheritdoc |
|
304
|
|
|
*/ |
|
305
|
6 |
|
public function delete($index): int |
|
306
|
|
|
{ |
|
307
|
6 |
|
if ($index !== null && is_scalar($index) === false) { |
|
308
|
1 |
|
throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT)); |
|
309
|
|
|
} |
|
310
|
|
|
|
|
311
|
5 |
|
$modelClass = $this->getModelClass(); |
|
312
|
|
|
|
|
313
|
5 |
|
$builder = $this->builderOnDelete( |
|
314
|
5 |
|
$this->getRepository()->delete($modelClass, static::INDEX_BIND)->setParameter(static::INDEX_BIND, $index) |
|
315
|
|
|
); |
|
316
|
|
|
|
|
317
|
5 |
|
$deleted = $builder->execute(); |
|
318
|
|
|
|
|
319
|
4 |
|
return (int)$deleted; |
|
320
|
|
|
} |
|
321
|
|
|
|
|
322
|
|
|
/** |
|
323
|
|
|
* @inheritdoc |
|
324
|
|
|
*/ |
|
325
|
5 |
|
public function create($index, array $attributes, array $toMany = []): string |
|
326
|
|
|
{ |
|
327
|
5 |
|
if ($index !== null && is_scalar($index) === false) { |
|
328
|
1 |
|
throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT)); |
|
329
|
|
|
} |
|
330
|
|
|
|
|
331
|
4 |
|
$modelClass = $this->getModelClass(); |
|
332
|
|
|
|
|
333
|
4 |
|
$allowedChanges = $this->filterAttributesOnCreate($modelClass, $attributes, $index); |
|
334
|
|
|
|
|
335
|
4 |
|
$saveMain = $this->getRepository()->create($modelClass, $allowedChanges); |
|
336
|
4 |
|
$saveMain = $this->builderSaveResourceOnCreate($saveMain); |
|
337
|
4 |
|
$saveMain->getSQL(); // prepare |
|
338
|
4 |
|
$this->inTransaction(function () use ($modelClass, $saveMain, $toMany, &$index) { |
|
339
|
4 |
|
$saveMain->execute(); |
|
340
|
|
|
// if no index given will use last insert ID as index |
|
341
|
4 |
|
$index !== null ?: $index = $saveMain->getConnection()->lastInsertId(); |
|
342
|
4 |
|
foreach ($toMany as $name => $values) { |
|
343
|
2 |
|
$indexBind = ':index'; |
|
344
|
2 |
|
$otherIndexBind = ':otherIndex'; |
|
345
|
2 |
|
$saveToMany = $this->getRepository() |
|
346
|
2 |
|
->createToManyRelationship($modelClass, $indexBind, $name, $otherIndexBind); |
|
347
|
2 |
|
$saveToMany = $this->builderSaveRelationshipOnCreate($name, $saveToMany); |
|
348
|
2 |
|
$saveToMany->setParameter($indexBind, $index); |
|
349
|
2 |
|
foreach ($values as $value) { |
|
350
|
2 |
|
$saveToMany->setParameter($otherIndexBind, $value)->execute(); |
|
351
|
|
|
} |
|
352
|
|
|
} |
|
353
|
4 |
|
}); |
|
354
|
|
|
|
|
355
|
4 |
|
return $index; |
|
356
|
|
|
} |
|
357
|
|
|
|
|
358
|
|
|
/** |
|
359
|
|
|
* @inheritdoc |
|
360
|
|
|
*/ |
|
361
|
4 |
|
public function update($index, array $attributes, array $toMany = []): int |
|
362
|
|
|
{ |
|
363
|
4 |
|
if ($index !== null && is_scalar($index) === false) { |
|
364
|
1 |
|
throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT)); |
|
365
|
|
|
} |
|
366
|
|
|
|
|
367
|
3 |
|
$updated = 0; |
|
368
|
3 |
|
$modelClass = $this->getModelClass(); |
|
369
|
|
|
|
|
370
|
3 |
|
$allowedChanges = $this->filterAttributesOnUpdate($modelClass, $attributes); |
|
371
|
|
|
|
|
372
|
3 |
|
$saveMain = $this->getRepository()->update($modelClass, $index, $allowedChanges); |
|
373
|
3 |
|
$saveMain = $this->builderSaveResourceOnUpdate($saveMain); |
|
374
|
3 |
|
$saveMain->getSQL(); // prepare |
|
375
|
3 |
|
$this->inTransaction(function () use ($modelClass, $saveMain, $toMany, $index, &$updated) { |
|
376
|
3 |
|
$updated = $saveMain->execute(); |
|
377
|
3 |
|
foreach ($toMany as $name => $values) { |
|
378
|
2 |
|
$indexBind = ':index'; |
|
379
|
2 |
|
$otherIndexBind = ':otherIndex'; |
|
380
|
|
|
|
|
381
|
2 |
|
$cleanToMany = $this->getRepository()->cleanToManyRelationship($modelClass, $indexBind, $name); |
|
382
|
2 |
|
$cleanToMany = $this->builderCleanRelationshipOnUpdate($name, $cleanToMany); |
|
383
|
2 |
|
$cleanToMany->setParameter($indexBind, $index)->execute(); |
|
384
|
|
|
|
|
385
|
2 |
|
$saveToMany = $this->getRepository() |
|
386
|
2 |
|
->createToManyRelationship($modelClass, $indexBind, $name, $otherIndexBind); |
|
387
|
2 |
|
$saveToMany = $this->builderSaveRelationshipOnUpdate($name, $saveToMany); |
|
388
|
2 |
|
$saveToMany->setParameter($indexBind, $index); |
|
389
|
2 |
|
foreach ($values as $value) { |
|
390
|
2 |
|
$updated += (int)$saveToMany->setParameter($otherIndexBind, $value)->execute(); |
|
391
|
|
|
} |
|
392
|
|
|
} |
|
393
|
3 |
|
}); |
|
394
|
|
|
|
|
395
|
3 |
|
return (int)$updated; |
|
396
|
|
|
} |
|
397
|
|
|
|
|
398
|
|
|
/** |
|
399
|
|
|
* @return FactoryInterface |
|
400
|
|
|
*/ |
|
401
|
32 |
|
protected function getFactory(): FactoryInterface |
|
402
|
|
|
{ |
|
403
|
32 |
|
return $this->factory; |
|
404
|
|
|
} |
|
405
|
|
|
|
|
406
|
|
|
/** |
|
407
|
|
|
* @return string |
|
408
|
|
|
*/ |
|
409
|
38 |
|
protected function getModelClass(): string |
|
410
|
|
|
{ |
|
411
|
38 |
|
return $this->modelClass; |
|
412
|
|
|
} |
|
413
|
|
|
|
|
414
|
|
|
/** |
|
415
|
|
|
* @return RepositoryInterface |
|
416
|
|
|
*/ |
|
417
|
38 |
|
protected function getRepository(): RepositoryInterface |
|
418
|
|
|
{ |
|
419
|
38 |
|
return $this->repository; |
|
420
|
|
|
} |
|
421
|
|
|
|
|
422
|
|
|
/** |
|
423
|
|
|
* @return ModelSchemeInfoInterface |
|
424
|
|
|
*/ |
|
425
|
31 |
|
protected function getModelSchemes(): ModelSchemeInfoInterface |
|
426
|
|
|
{ |
|
427
|
31 |
|
return $this->modelSchemes; |
|
428
|
|
|
} |
|
429
|
|
|
|
|
430
|
|
|
/** |
|
431
|
|
|
* @return PaginationStrategyInterface |
|
432
|
|
|
*/ |
|
433
|
21 |
|
protected function getPaginationStrategy(): PaginationStrategyInterface |
|
434
|
|
|
{ |
|
435
|
21 |
|
return $this->paginationStrategy; |
|
436
|
|
|
} |
|
437
|
|
|
|
|
438
|
|
|
/** |
|
439
|
|
|
* @param Closure $closure |
|
440
|
|
|
* |
|
441
|
|
|
* @return void |
|
|
|
|
|
|
442
|
|
|
*/ |
|
443
|
7 |
|
protected function inTransaction(Closure $closure): void |
|
444
|
|
|
{ |
|
445
|
7 |
|
$connection = $this->getRepository()->getConnection(); |
|
446
|
7 |
|
$connection->beginTransaction(); |
|
447
|
|
|
try { |
|
448
|
7 |
|
$isOk = ($closure() === false ? null : true); |
|
449
|
7 |
|
} finally { |
|
450
|
7 |
|
isset($isOk) === true ? $connection->commit() : $connection->rollBack(); |
|
451
|
|
|
} |
|
452
|
|
|
} |
|
453
|
|
|
|
|
454
|
|
|
/** |
|
455
|
|
|
* @param QueryBuilder $builder |
|
456
|
|
|
* @param string $class |
|
457
|
|
|
* |
|
458
|
|
|
* @return PaginatedDataInterface |
|
459
|
|
|
*/ |
|
460
|
1 |
|
protected function fetchSingleData(QueryBuilder $builder, string $class): PaginatedDataInterface |
|
461
|
|
|
{ |
|
462
|
1 |
|
$model = $this->fetchSingle($builder, $class); |
|
463
|
1 |
|
$data = $this->getFactory()->createPaginatedData($model)->markAsSingleItem(); |
|
464
|
|
|
|
|
465
|
1 |
|
return $data; |
|
466
|
|
|
} |
|
467
|
|
|
|
|
468
|
|
|
/** |
|
469
|
|
|
* @param QueryBuilder $builder |
|
470
|
|
|
* @param string $class |
|
471
|
|
|
* |
|
472
|
|
|
* @return PaginatedDataInterface |
|
473
|
|
|
*/ |
|
474
|
18 |
|
protected function fetchCollectionData(QueryBuilder $builder, string $class): PaginatedDataInterface |
|
475
|
|
|
{ |
|
476
|
18 |
|
list($models, $hasMore, $limit, $offset) = $this->fetchCollection($builder, $class); |
|
477
|
|
|
|
|
478
|
18 |
|
$data = $this->getFactory() |
|
479
|
18 |
|
->createPaginatedData($models) |
|
480
|
18 |
|
->markAsCollection() |
|
481
|
18 |
|
->setOffset($offset) |
|
482
|
18 |
|
->setLimit($limit); |
|
483
|
18 |
|
$hasMore === true ? $data->markHasMoreItems() : $data->markHasNoMoreItems(); |
|
484
|
|
|
|
|
485
|
18 |
|
return $data; |
|
486
|
|
|
} |
|
487
|
|
|
|
|
488
|
|
|
/** |
|
489
|
|
|
* @param QueryBuilder $builder |
|
490
|
|
|
* @param string $class |
|
491
|
|
|
* |
|
492
|
|
|
* @return array|null |
|
493
|
|
|
*/ |
|
494
|
1 |
|
protected function fetchRow(QueryBuilder $builder, string $class): ?array |
|
495
|
|
|
{ |
|
496
|
1 |
|
$statement = $builder->execute(); |
|
497
|
1 |
|
$statement->setFetchMode(PDOConnection::FETCH_ASSOC); |
|
498
|
1 |
|
$platform = $builder->getConnection()->getDatabasePlatform(); |
|
499
|
1 |
|
$typeNames = $this->getModelSchemes()->getAttributeTypes($class); |
|
500
|
|
|
|
|
501
|
1 |
|
$model = null; |
|
502
|
1 |
|
if (($attributes = $statement->fetch()) !== false) { |
|
503
|
1 |
|
$model = $this->readRowFromAssoc($attributes, $typeNames, $platform); |
|
504
|
|
|
} |
|
505
|
|
|
|
|
506
|
1 |
|
return $model; |
|
507
|
|
|
} |
|
508
|
|
|
|
|
509
|
|
|
/** |
|
510
|
|
|
* @param QueryBuilder $builder |
|
511
|
|
|
* @param string $class |
|
512
|
|
|
* |
|
513
|
|
|
* @return mixed|null |
|
|
|
|
|
|
514
|
|
|
*/ |
|
515
|
14 |
|
protected function fetchSingle(QueryBuilder $builder, string $class) |
|
516
|
|
|
{ |
|
517
|
14 |
|
$statement = $builder->execute(); |
|
518
|
14 |
|
$statement->setFetchMode(PDOConnection::FETCH_ASSOC); |
|
519
|
14 |
|
$platform = $builder->getConnection()->getDatabasePlatform(); |
|
520
|
14 |
|
$typeNames = $this->getModelSchemes()->getAttributeTypes($class); |
|
521
|
|
|
|
|
522
|
14 |
|
$model = null; |
|
523
|
14 |
|
if (($attributes = $statement->fetch()) !== false) { |
|
524
|
14 |
|
$model = $this->readInstanceFromAssoc($class, $attributes, $typeNames, $platform); |
|
525
|
|
|
} |
|
526
|
|
|
|
|
527
|
14 |
|
return $model; |
|
528
|
|
|
} |
|
529
|
|
|
|
|
530
|
|
|
/** |
|
531
|
|
|
* @param QueryBuilder $builder |
|
532
|
|
|
* @param string $class |
|
533
|
|
|
* |
|
534
|
|
|
* @return array |
|
535
|
|
|
*/ |
|
536
|
22 |
|
protected function fetchCollection(QueryBuilder $builder, string $class): array |
|
537
|
|
|
{ |
|
538
|
22 |
|
$statement = $builder->execute(); |
|
539
|
22 |
|
$statement->setFetchMode(PDOConnection::FETCH_ASSOC); |
|
540
|
22 |
|
$platform = $builder->getConnection()->getDatabasePlatform(); |
|
541
|
22 |
|
$typeNames = $this->getModelSchemes()->getAttributeTypes($class); |
|
542
|
|
|
|
|
543
|
22 |
|
$models = []; |
|
544
|
22 |
|
while (($attributes = $statement->fetch()) !== false) { |
|
545
|
22 |
|
$models[] = $this->readInstanceFromAssoc($class, $attributes, $typeNames, $platform); |
|
546
|
|
|
} |
|
547
|
|
|
|
|
548
|
22 |
|
return $this->normalizePagingParams($models, $builder->getMaxResults(), $builder->getFirstResult()); |
|
549
|
|
|
} |
|
550
|
|
|
|
|
551
|
|
|
/** |
|
552
|
|
|
* @param string $modelClass |
|
553
|
|
|
* @param array $attributes |
|
554
|
|
|
* @param null|string $index |
|
555
|
|
|
* |
|
556
|
|
|
* @return array |
|
557
|
|
|
*/ |
|
558
|
4 |
|
protected function filterAttributesOnCreate(string $modelClass, array $attributes, string $index = null): array |
|
559
|
|
|
{ |
|
560
|
4 |
|
$allowedAttributes = array_flip($this->getModelSchemes()->getAttributes($modelClass)); |
|
561
|
4 |
|
$allowedChanges = array_intersect_key($attributes, $allowedAttributes); |
|
562
|
4 |
|
if ($index !== null) { |
|
563
|
1 |
|
$pkName = $this->getModelSchemes()->getPrimaryKey($this->getModelClass()); |
|
564
|
1 |
|
$allowedChanges[$pkName] = $index; |
|
565
|
|
|
} |
|
566
|
|
|
|
|
567
|
4 |
|
return $allowedChanges; |
|
568
|
|
|
} |
|
569
|
|
|
|
|
570
|
|
|
/** |
|
571
|
|
|
* @param string $modelClass |
|
572
|
|
|
* @param array $attributes |
|
573
|
|
|
* |
|
574
|
|
|
* @return array |
|
575
|
|
|
*/ |
|
576
|
3 |
|
protected function filterAttributesOnUpdate(string $modelClass, array $attributes): array |
|
577
|
|
|
{ |
|
578
|
3 |
|
$allowedAttributes = array_flip($this->getModelSchemes()->getAttributes($modelClass)); |
|
579
|
3 |
|
$allowedChanges = array_intersect_key($attributes, $allowedAttributes); |
|
580
|
|
|
|
|
581
|
3 |
|
return $allowedChanges; |
|
582
|
|
|
} |
|
583
|
|
|
|
|
584
|
|
|
/** |
|
585
|
|
|
* @param QueryBuilder $builder |
|
586
|
|
|
* |
|
587
|
|
|
* @return QueryBuilder |
|
588
|
|
|
*/ |
|
589
|
1 |
|
protected function builderOnCount(QueryBuilder $builder): QueryBuilder |
|
590
|
|
|
{ |
|
591
|
1 |
|
return $builder; |
|
592
|
|
|
} |
|
593
|
|
|
|
|
594
|
|
|
/** |
|
595
|
|
|
* @param QueryBuilder $builder |
|
596
|
|
|
* |
|
597
|
|
|
* @return QueryBuilder |
|
598
|
|
|
*/ |
|
599
|
16 |
|
protected function builderOnIndex(QueryBuilder $builder): QueryBuilder |
|
600
|
|
|
{ |
|
601
|
16 |
|
return $builder; |
|
602
|
|
|
} |
|
603
|
|
|
|
|
604
|
|
|
/** |
|
605
|
|
|
* @param QueryBuilder $builder |
|
606
|
|
|
* |
|
607
|
|
|
* @return QueryBuilder |
|
608
|
|
|
*/ |
|
609
|
10 |
|
protected function builderOnRead(QueryBuilder $builder): QueryBuilder |
|
610
|
|
|
{ |
|
611
|
10 |
|
return $builder; |
|
612
|
|
|
} |
|
613
|
|
|
|
|
614
|
|
|
/** |
|
615
|
|
|
* @param QueryBuilder $builder |
|
616
|
|
|
* |
|
617
|
|
|
* @return QueryBuilder |
|
618
|
|
|
*/ |
|
619
|
4 |
|
protected function builderOnReadRelationship(QueryBuilder $builder): QueryBuilder |
|
620
|
|
|
{ |
|
621
|
4 |
|
return $builder; |
|
622
|
|
|
} |
|
623
|
|
|
|
|
624
|
|
|
/** |
|
625
|
|
|
* @param QueryBuilder $builder |
|
626
|
|
|
* |
|
627
|
|
|
* @return QueryBuilder |
|
628
|
|
|
*/ |
|
629
|
4 |
|
protected function builderSaveResourceOnCreate(QueryBuilder $builder): QueryBuilder |
|
630
|
|
|
{ |
|
631
|
4 |
|
return $builder; |
|
632
|
|
|
} |
|
633
|
|
|
|
|
634
|
|
|
/** |
|
635
|
|
|
* @param QueryBuilder $builder |
|
636
|
|
|
* |
|
637
|
|
|
* @return QueryBuilder |
|
638
|
|
|
*/ |
|
639
|
3 |
|
protected function builderSaveResourceOnUpdate(QueryBuilder $builder): QueryBuilder |
|
640
|
|
|
{ |
|
641
|
3 |
|
return $builder; |
|
642
|
|
|
} |
|
643
|
|
|
|
|
644
|
|
|
/** |
|
645
|
|
|
* @param string $relationshipName |
|
646
|
|
|
* @param QueryBuilder $builder |
|
647
|
|
|
* |
|
648
|
|
|
* @return QueryBuilder |
|
649
|
|
|
* |
|
650
|
|
|
* @SuppressWarnings(PHPMD.UnusedFormalParameter) |
|
651
|
|
|
*/ |
|
652
|
2 |
|
protected function builderSaveRelationshipOnCreate(/** @noinspection PhpUnusedParameterInspection */ |
|
653
|
|
|
$relationshipName, |
|
|
|
|
|
|
654
|
|
|
QueryBuilder $builder |
|
655
|
|
|
): QueryBuilder { |
|
656
|
2 |
|
return $builder; |
|
657
|
|
|
} |
|
658
|
|
|
|
|
659
|
|
|
/** |
|
660
|
|
|
* @param string $relationshipName |
|
661
|
|
|
* @param QueryBuilder $builder |
|
662
|
|
|
* |
|
663
|
|
|
* @return QueryBuilder |
|
664
|
|
|
* |
|
665
|
|
|
* @SuppressWarnings(PHPMD.UnusedFormalParameter) |
|
666
|
|
|
*/ |
|
667
|
2 |
|
protected function builderSaveRelationshipOnUpdate(/** @noinspection PhpUnusedParameterInspection */ |
|
668
|
|
|
$relationshipName, |
|
|
|
|
|
|
669
|
|
|
QueryBuilder $builder |
|
670
|
|
|
): QueryBuilder { |
|
671
|
2 |
|
return $builder; |
|
672
|
|
|
} |
|
673
|
|
|
|
|
674
|
|
|
/** |
|
675
|
|
|
* @param string $relationshipName |
|
676
|
|
|
* @param QueryBuilder $builder |
|
677
|
|
|
* |
|
678
|
|
|
* @return QueryBuilder |
|
679
|
|
|
* |
|
680
|
|
|
* @SuppressWarnings(PHPMD.UnusedFormalParameter) |
|
681
|
|
|
*/ |
|
682
|
2 |
|
protected function builderCleanRelationshipOnUpdate(/** @noinspection PhpUnusedParameterInspection */ |
|
683
|
|
|
$relationshipName, |
|
|
|
|
|
|
684
|
|
|
QueryBuilder $builder |
|
685
|
|
|
): QueryBuilder { |
|
686
|
2 |
|
return $builder; |
|
687
|
|
|
} |
|
688
|
|
|
|
|
689
|
|
|
/** |
|
690
|
|
|
* @param QueryBuilder $builder |
|
691
|
|
|
* |
|
692
|
|
|
* @return QueryBuilder |
|
693
|
|
|
*/ |
|
694
|
5 |
|
protected function builderOnDelete(QueryBuilder $builder): QueryBuilder |
|
695
|
|
|
{ |
|
696
|
5 |
|
return $builder; |
|
697
|
|
|
} |
|
698
|
|
|
|
|
699
|
|
|
/** |
|
700
|
|
|
* @param PaginatedDataInterface $data |
|
701
|
|
|
* @param IncludeParameterInterface[]|null $paths |
|
702
|
|
|
* |
|
703
|
|
|
* @return void |
|
704
|
|
|
* |
|
705
|
|
|
* @SuppressWarnings(PHPMD.ElseExpression) |
|
706
|
|
|
*/ |
|
707
|
25 |
|
protected function loadRelationships(PaginatedDataInterface $data, ?array $paths): void |
|
708
|
|
|
{ |
|
709
|
25 |
|
if (empty($data->getData()) === false && empty($paths) === false) { |
|
710
|
8 |
|
$modelStorage = $this->getFactory()->createModelStorage($this->getModelSchemes()); |
|
711
|
8 |
|
$modelsAtPath = $this->getFactory()->createTagStorage(); |
|
712
|
|
|
|
|
713
|
|
|
// we gonna send this storage via function params so it is an equivalent for &array |
|
714
|
8 |
|
$classAtPath = new ArrayObject(); |
|
715
|
|
|
|
|
716
|
8 |
|
$model = null; |
|
717
|
8 |
|
if ($data->isCollection() === true) { |
|
718
|
4 |
|
foreach ($data->getData() as $model) { |
|
719
|
4 |
|
$uniqueModel = $modelStorage->register($model); |
|
720
|
4 |
|
if ($uniqueModel !== null) { |
|
721
|
4 |
|
$modelsAtPath->register($uniqueModel, static::ROOT_PATH); |
|
722
|
|
|
} |
|
723
|
|
|
} |
|
724
|
|
|
} else { |
|
725
|
4 |
|
$model = $data->getData(); |
|
726
|
4 |
|
$uniqueModel = $modelStorage->register($model); |
|
727
|
4 |
|
if ($uniqueModel !== null) { |
|
728
|
4 |
|
$modelsAtPath->register($uniqueModel, static::ROOT_PATH); |
|
729
|
|
|
} |
|
730
|
|
|
} |
|
731
|
8 |
|
$classAtPath[static::ROOT_PATH] = get_class($model); |
|
732
|
|
|
|
|
733
|
8 |
|
foreach ($this->getPaths($paths) as list ($parentPath, $childPaths)) { |
|
734
|
8 |
|
$this->loadRelationshipsLayer( |
|
735
|
8 |
|
$modelsAtPath, |
|
736
|
8 |
|
$classAtPath, |
|
737
|
8 |
|
$modelStorage, |
|
738
|
8 |
|
$parentPath, |
|
739
|
8 |
|
$childPaths |
|
740
|
|
|
); |
|
741
|
|
|
} |
|
742
|
|
|
} |
|
743
|
|
|
} |
|
744
|
|
|
|
|
745
|
|
|
/** |
|
746
|
|
|
* @param IncludeParameterInterface[] $paths |
|
747
|
|
|
* |
|
748
|
|
|
* @return Generator |
|
749
|
|
|
*/ |
|
750
|
8 |
|
private function getPaths(array $paths): Generator |
|
751
|
|
|
{ |
|
752
|
|
|
// The idea is to normalize paths. It means build all intermediate paths. |
|
753
|
|
|
// e.g. if only `a.b.c` path it given it will be normalized to `a`, `a.b` and `a.b.c`. |
|
754
|
|
|
// Path depths store depth of each path (e.g. 0 for root, 1 for `a`, 2 for `a.b` and etc). |
|
755
|
|
|
// It is needed for yielding them in correct order (from top level to bottom). |
|
756
|
8 |
|
$normalizedPaths = []; |
|
757
|
8 |
|
$pathsDepths = []; |
|
758
|
8 |
|
foreach ($paths as $path) { |
|
759
|
8 |
|
assert($path instanceof IncludeParameterInterface); |
|
760
|
8 |
|
$parentDepth = 0; |
|
761
|
8 |
|
$tmpPath = static::ROOT_PATH; |
|
762
|
8 |
|
foreach ($path->getPath() as $pathPiece) { |
|
763
|
8 |
|
$parent = $tmpPath; |
|
764
|
8 |
|
$tmpPath = empty($tmpPath) === true ? |
|
765
|
8 |
|
$pathPiece : $tmpPath . static::PATH_SEPARATOR . $pathPiece; |
|
766
|
8 |
|
$normalizedPaths[$tmpPath] = [$parent, $pathPiece]; |
|
767
|
8 |
|
$pathsDepths[$parent] = $parentDepth++; |
|
768
|
|
|
} |
|
769
|
|
|
} |
|
770
|
|
|
|
|
771
|
|
|
// Here we collect paths in form of parent => [list of children] |
|
772
|
|
|
// e.g. '' => ['a', 'c', 'b'], 'b' => ['bb', 'aa'] and etc |
|
|
|
|
|
|
773
|
8 |
|
$parentWithChildren = []; |
|
774
|
8 |
|
foreach ($normalizedPaths as $path => list ($parent, $childPath)) { |
|
775
|
8 |
|
$parentWithChildren[$parent][] = $childPath; |
|
776
|
|
|
} |
|
777
|
|
|
|
|
778
|
|
|
// And finally sort by path depth and yield parent with its children. Top level paths first then deeper ones. |
|
779
|
8 |
|
asort($pathsDepths, SORT_NUMERIC); |
|
780
|
8 |
|
foreach ($pathsDepths as $parent => $depth) { |
|
781
|
8 |
|
assert($depth !== null); // suppress unused |
|
782
|
8 |
|
$childPaths = $parentWithChildren[$parent]; |
|
783
|
8 |
|
yield [$parent, $childPaths]; |
|
784
|
|
|
} |
|
785
|
|
|
} |
|
786
|
|
|
|
|
787
|
|
|
/** |
|
788
|
|
|
* @param TagStorageInterface $modelsAtPath |
|
789
|
|
|
* @param ArrayObject $classAtPath |
|
790
|
|
|
* @param ModelStorageInterface $deDup |
|
791
|
|
|
* @param string $parentsPath |
|
792
|
|
|
* @param array $childRelationships |
|
793
|
|
|
* |
|
794
|
|
|
* @return void |
|
795
|
|
|
* |
|
796
|
|
|
* @SuppressWarnings(PHPMD.CyclomaticComplexity) |
|
797
|
|
|
*/ |
|
798
|
8 |
|
private function loadRelationshipsLayer( |
|
799
|
|
|
TagStorageInterface $modelsAtPath, |
|
800
|
|
|
ArrayObject $classAtPath, |
|
801
|
|
|
ModelStorageInterface $deDup, |
|
802
|
|
|
string $parentsPath, |
|
803
|
|
|
array $childRelationships |
|
804
|
|
|
): void { |
|
805
|
8 |
|
$rootClass = $classAtPath[static::ROOT_PATH]; |
|
806
|
8 |
|
$parentClass = $classAtPath[$parentsPath]; |
|
807
|
8 |
|
$parents = $modelsAtPath->get($parentsPath); |
|
808
|
|
|
|
|
809
|
|
|
// What should we do? We have do find all child resources for $parents at paths $childRelationships (1 level |
|
810
|
|
|
// child paths) and add them to $relationships. While doing it we have to deduplicate resources with |
|
811
|
|
|
// $models. |
|
812
|
|
|
|
|
813
|
8 |
|
foreach ($childRelationships as $name) { |
|
814
|
8 |
|
$childrenPath = $parentsPath !== static::ROOT_PATH ? $parentsPath . static::PATH_SEPARATOR . $name : $name; |
|
815
|
|
|
|
|
816
|
|
|
/** @var QueryBuilder $builder */ |
|
817
|
|
|
list ($builder, $class, $relationshipType) = |
|
818
|
8 |
|
$this->getRepository()->readRelationship($parentClass, static::INDEX_BIND, $name); |
|
819
|
|
|
|
|
820
|
8 |
|
$classAtPath[$childrenPath] = $class; |
|
821
|
|
|
|
|
822
|
|
|
switch ($relationshipType) { |
|
823
|
8 |
|
case RelationshipTypes::BELONGS_TO: |
|
824
|
7 |
|
$pkName = $this->getModelSchemes()->getPrimaryKey($parentClass); |
|
825
|
7 |
|
foreach ($parents as $parent) { |
|
826
|
7 |
|
$builder->setParameter(static::INDEX_BIND, $parent->{$pkName}); |
|
827
|
7 |
|
$child = $deDup->register($this->fetchSingle($builder, $class)); |
|
828
|
7 |
|
if ($child !== null) { |
|
829
|
6 |
|
$modelsAtPath->register($child, $childrenPath); |
|
830
|
|
|
} |
|
831
|
7 |
|
$parent->{$name} = $child; |
|
832
|
|
|
} |
|
833
|
7 |
|
break; |
|
834
|
6 |
|
case RelationshipTypes::HAS_MANY: |
|
835
|
4 |
|
case RelationshipTypes::BELONGS_TO_MANY: |
|
836
|
6 |
|
list ($queryOffset, $queryLimit) = $this->getPaginationStrategy() |
|
837
|
6 |
|
->getParameters($rootClass, $parentClass, $parentsPath, $name); |
|
838
|
6 |
|
$builder->setFirstResult($queryOffset)->setMaxResults($queryLimit + 1); |
|
839
|
6 |
|
$pkName = $this->getModelSchemes()->getPrimaryKey($parentClass); |
|
840
|
6 |
|
foreach ($parents as $parent) { |
|
841
|
6 |
|
$builder->setParameter(static::INDEX_BIND, $parent->{$pkName}); |
|
842
|
|
|
list($children, $hasMore, $limit, $offset) = |
|
843
|
6 |
|
$this->fetchCollection($builder, $class); |
|
844
|
6 |
|
$deDupedChildren = []; |
|
845
|
6 |
|
foreach ($children as $child) { |
|
846
|
6 |
|
$child = $deDup->register($child); |
|
847
|
6 |
|
$modelsAtPath->register($child, $childrenPath); |
|
848
|
6 |
|
if ($child !== null) { |
|
849
|
6 |
|
$deDupedChildren[] = $child; |
|
850
|
|
|
} |
|
851
|
|
|
} |
|
852
|
|
|
|
|
853
|
6 |
|
$paginated = $this->factory->createPaginatedData($deDupedChildren) |
|
854
|
6 |
|
->markAsCollection() |
|
855
|
6 |
|
->setOffset($offset) |
|
856
|
6 |
|
->setLimit($limit); |
|
857
|
6 |
|
$hasMore === true ? $paginated->markHasMoreItems() : $paginated->markHasNoMoreItems(); |
|
858
|
|
|
|
|
859
|
6 |
|
$parent->{$name} = $paginated; |
|
860
|
|
|
} |
|
861
|
8 |
|
break; |
|
862
|
|
|
} |
|
863
|
|
|
} |
|
864
|
|
|
} |
|
865
|
|
|
|
|
866
|
|
|
/** |
|
867
|
|
|
* @param array $models |
|
868
|
|
|
* @param int|string|null $offset |
|
869
|
|
|
* @param int|string|null $limit |
|
870
|
|
|
* |
|
871
|
|
|
* @return array |
|
872
|
|
|
* |
|
873
|
|
|
* @SuppressWarnings(PHPMD.ElseExpression) |
|
874
|
|
|
*/ |
|
875
|
22 |
|
private function normalizePagingParams(array $models, $limit, $offset): array |
|
876
|
|
|
{ |
|
877
|
22 |
|
if ($limit !== null) { |
|
878
|
21 |
|
$hasMore = count($models) >= $limit; |
|
879
|
21 |
|
if ($hasMore === true) { |
|
880
|
7 |
|
array_pop($models); |
|
881
|
|
|
} |
|
882
|
21 |
|
$limit = $limit - 1; |
|
883
|
|
|
} else { |
|
884
|
1 |
|
$hasMore = false; |
|
885
|
|
|
} |
|
886
|
|
|
|
|
887
|
22 |
|
return [$models, $hasMore, $limit, $offset]; |
|
888
|
|
|
} |
|
889
|
|
|
|
|
890
|
|
|
/** |
|
891
|
|
|
* @param ErrorCollection $errors |
|
892
|
|
|
* |
|
893
|
|
|
* @return void |
|
894
|
|
|
*/ |
|
895
|
32 |
|
private function checkErrors(ErrorCollection $errors): void |
|
896
|
|
|
{ |
|
897
|
32 |
|
if (empty($errors->getArrayCopy()) === false) { |
|
898
|
1 |
|
throw new E($errors); |
|
899
|
|
|
} |
|
900
|
|
|
} |
|
901
|
|
|
|
|
902
|
|
|
/** |
|
903
|
|
|
* @param string $message |
|
904
|
|
|
* |
|
905
|
|
|
* @return string |
|
906
|
|
|
*/ |
|
907
|
8 |
|
private function getMessage(string $message): string |
|
908
|
|
|
{ |
|
909
|
|
|
/** @var FormatterFactoryInterface $factory */ |
|
910
|
8 |
|
$factory = $this->getContainer()->get(FormatterFactoryInterface::class); |
|
911
|
8 |
|
$formatter = $factory->createFormatter(Messages::RESOURCES_NAMESPACE); |
|
912
|
8 |
|
$result = $formatter->formatMessage($message); |
|
913
|
|
|
|
|
914
|
8 |
|
return $result; |
|
915
|
|
|
} |
|
916
|
|
|
|
|
917
|
|
|
/** |
|
918
|
|
|
* @param string $class |
|
919
|
|
|
* @param array $attributes |
|
920
|
|
|
* @param Type[] $typeNames |
|
921
|
|
|
* @param AbstractPlatform $platform |
|
922
|
|
|
* |
|
923
|
|
|
* @return mixed|null |
|
|
|
|
|
|
924
|
|
|
* |
|
925
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
|
926
|
|
|
*/ |
|
927
|
30 |
|
private function readInstanceFromAssoc( |
|
928
|
|
|
string $class, |
|
929
|
|
|
array $attributes, |
|
930
|
|
|
array $typeNames, |
|
931
|
|
|
AbstractPlatform $platform |
|
932
|
|
|
) { |
|
933
|
30 |
|
$instance = new $class(); |
|
934
|
30 |
|
foreach ($attributes as $name => $value) { |
|
935
|
30 |
|
if (array_key_exists($name, $typeNames) === true) { |
|
936
|
30 |
|
$type = Type::getType($typeNames[$name]); |
|
937
|
30 |
|
$value = $type->convertToPHPValue($value, $platform); |
|
938
|
|
|
} |
|
939
|
30 |
|
$instance->{$name} = $value; |
|
940
|
|
|
} |
|
941
|
|
|
|
|
942
|
30 |
|
return $instance; |
|
943
|
|
|
} |
|
944
|
|
|
|
|
945
|
|
|
/** |
|
946
|
|
|
* @param array $attributes |
|
947
|
|
|
* @param Type[] $typeNames |
|
948
|
|
|
* @param AbstractPlatform $platform |
|
949
|
|
|
* |
|
950
|
|
|
* @return array |
|
951
|
|
|
* |
|
952
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
|
953
|
|
|
*/ |
|
954
|
1 |
|
private function readRowFromAssoc(array $attributes, array $typeNames, AbstractPlatform $platform): array |
|
955
|
|
|
{ |
|
956
|
1 |
|
$row = []; |
|
957
|
1 |
|
foreach ($attributes as $name => $value) { |
|
958
|
1 |
|
if (array_key_exists($name, $typeNames) === true) { |
|
959
|
1 |
|
$type = Type::getType($typeNames[$name]); |
|
960
|
1 |
|
$value = $type->convertToPHPValue($value, $platform); |
|
961
|
|
|
} |
|
962
|
1 |
|
$row[$name] = $value; |
|
963
|
|
|
} |
|
964
|
|
|
|
|
965
|
1 |
|
return $row; |
|
966
|
|
|
} |
|
967
|
|
|
} |
|
968
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.