Issues (590)

src/Query/QueryRepositoryExtension.php (4 issues)

1
<?php
2
3
namespace Bdf\Prime\Query;
4
5
use BadMethodCallException;
6
use Bdf\Prime\Collection\Indexer\EntityIndexer;
7
use Bdf\Prime\Connection\ConnectionInterface;
8
use Bdf\Prime\Connection\Result\ResultSetInterface;
9
use Bdf\Prime\Events;
10
use Bdf\Prime\Exception\EntityNotFoundException;
11
use Bdf\Prime\Exception\PrimeException;
12
use Bdf\Prime\Mapper\Mapper;
13
use Bdf\Prime\Mapper\Metadata;
14
use Bdf\Prime\Query\Contract\Whereable;
15
use Bdf\Prime\Relations\Relation;
16
use Bdf\Prime\Repository\EntityRepository;
17
use Bdf\Prime\Repository\RepositoryInterface;
18
19
/**
20
 * QueryRepositoryExtension
21
 *
22
 * @template E as object
23
 */
24
class QueryRepositoryExtension extends QueryCompatExtension
25
{
26
    /**
27
     * @var RepositoryInterface<E>
28
     */
29
    protected $repository;
30
31
    /**
32
     * @var Metadata
33
     */
34
    protected $metadata;
35
36
    /**
37
     * @var Mapper<E>
38
     */
39
    protected $mapper;
40
41
    /**
42
     * Array of relations to associate on entities
43
     * Contains relations and subrelations. Can be load
44
     * only on select queries
45
     *
46
     * @var array
47
     */
48
    protected $withRelations = [];
49
50
    /**
51
     * Array of relations to discard
52
     *
53
     * @var array
54
     */
55
    protected $withoutRelations = [];
56
57
    /**
58
     * Collect entities by attribute
59
     *
60
     * @var array
61
     */
62
    protected $byOptions;
63
64
65
    /**
66
     * QueryRepositoryExtension constructor.
67
     *
68
     * @param RepositoryInterface<E> $repository
69
     */
70 352
    public function __construct(RepositoryInterface $repository)
71
    {
72 352
        $this->repository = $repository;
73 352
        $this->metadata = $repository->metadata();
74 352
        $this->mapper = $repository->mapper();
75
    }
76
77
    /**
78
     * Gets associated repository
79
     *
80
     * @param ReadCommandInterface<ConnectionInterface, E> $query
81
     * @param null|string $name
82
     *
83
     * @return RepositoryInterface|null
84
     */
85 85
    public function repository(ReadCommandInterface $query, $name = null)
0 ignored issues
show
The parameter $query is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

85
    public function repository(/** @scrutinizer ignore-unused */ ReadCommandInterface $query, $name = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
86
    {
87 85
        if ($name === null) {
88 85
            return $this->repository;
89
        }
90
91
        return $this->repository->repository($name);
92
    }
93
94
    /**
95
     * Get one entity by identifier
96
     *
97
     * @param ReadCommandInterface<ConnectionInterface, E>&Whereable $query
98
     * @param mixed         $id
99
     * @param null|string|array  $attributes
100
     *
101
     * @return E|null
102
     */
103 141
    public function get(ReadCommandInterface $query, $id, $attributes = null)
104
    {
105 141
        if (empty($id)) {
106 1
            return null;
107
        }
108
109 140
        if (!is_array($id)) {
110 139
            list($identifierName) = $this->metadata->primary['attributes'];
111 139
            $id = [$identifierName => $id];
112
        }
113
114 140
        return $query->where($id)->first($attributes);
0 ignored issues
show
The method where() does not exist on Bdf\Prime\Query\ReadCommandInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Bdf\Prime\Query\ReadCommandInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

114
        return $query->/** @scrutinizer ignore-call */ where($id)->first($attributes);
Loading history...
115
    }
116
117
    /**
118
     * Get one entity or throws entity not found
119
     *
120
     * @param ReadCommandInterface<ConnectionInterface, E>&Whereable $query
121
     * @param mixed $id
122
     * @param null|string|array $attributes
123
     *
124
     * @return E
125
     *
126
     * @throws EntityNotFoundException  If entity is not found
127
     */
128 3
    public function getOrFail(ReadCommandInterface $query, $id, $attributes = null)
129
    {
130 3
        $entity = $this->get($query, $id, $attributes);
131
132 3
        if ($entity !== null) {
133 2
            return $entity;
134
        }
135
136 2
        throw new EntityNotFoundException('Cannot resolve entity identifier "'.implode('", "', (array)$id).'"');
137
    }
138
139
    /**
140
     * Get one entity or return a new one if not found in repository
141
     *
142
     * @param ReadCommandInterface<ConnectionInterface, E>&Whereable $query
143
     * @param mixed $id
144
     * @param null|string|array $attributes
145
     *
146
     * @return E
147
     */
148 2
    public function getOrNew(ReadCommandInterface $query, $id, $attributes = null)
149
    {
150 2
        $entity = $this->get($query, $id, $attributes);
151
152 2
        if ($entity !== null) {
153 2
            return $entity;
154
        }
155
156 2
        return $this->repository->entity();
157
    }
158
159
    /**
160
     * Relations to load.
161
     *
162
     * Relations with their sub relations
163
     * <code>
164
     * $query->with([
165
     *     'customer.packs',
166
     *     'permissions'
167
     * ]);
168
     * </code>
169
     *
170
     * Use char '#' for polymorphic sub relation
171
     * <code>
172
     * $query->with('target#customer.packs');
173
     * </code>
174
     *
175
     * @param ReadCommandInterface<ConnectionInterface, E> $query
176
     * @param string|array $relations
177
     *
178
     * @return ReadCommandInterface<ConnectionInterface, E>
179
     */
180 237
    public function with(ReadCommandInterface $query, $relations)
181
    {
182 237
        $this->withRelations = Relation::sanitizeRelations((array)$relations);
183
184 237
        return $query;
185
    }
186
187
    /**
188
     * Relations to discard
189
     *
190
     * @param ReadCommandInterface<ConnectionInterface, E> $query
191
     * @param string|array $relations
192
     *
193
     * @return ReadCommandInterface<ConnectionInterface, E>
194
     */
195 240
    public function without(ReadCommandInterface $query, $relations)
196
    {
197 240
        $this->withoutRelations = Relation::sanitizeWithoutRelations((array)$relations);
198
199 240
        return $query;
200
    }
201
202
    /**
203
     * Indexing entities by an attribute value
204
     * Use combine for multiple entities with same attribute value
205
     *
206
     * @param ReadCommandInterface<ConnectionInterface, E> $query
207
     * @param string  $attribute
208
     * @param boolean $combine
209
     *
210
     * @return ReadCommandInterface<ConnectionInterface, E>
211
     */
212 217
    public function by(ReadCommandInterface $query, $attribute, $combine = false)
213
    {
214 217
        $this->byOptions = [
215 217
            'attribute' => $attribute,
216 217
            'combine'   => $combine,
217 217
        ];
218
219 217
        return $query;
220
    }
221
222
    /**
223
     * Post processor for hydrating entities
224
     *
225
     * @param ResultSetInterface<array<string, mixed>> $data
226
     *
227
     * @return array
228
     * @throws PrimeException
229
     */
230 464
    public function processEntities(ResultSetInterface $data)
231
    {
232
        /** @var EntityRepository $repository */
233 464
        $repository = $this->repository;
234 464
        $hasLoadEvent = $repository->hasListeners(Events::POST_LOAD);
235
236
        // Save into local vars to ensure that value will not be changed during execution
237 464
        $withRelations = $this->withRelations;
238 464
        $withoutRelations = $this->withoutRelations;
239 464
        $byOptions = $this->byOptions;
240
241 464
        $entities = new EntityIndexer($this->mapper, $byOptions ? [$byOptions['attribute']] : []);
0 ignored issues
show
$byOptions ? array($byOp...'attribute']) : array() of type array is incompatible with the type Bdf\Prime\Collection\Indexer\list expected by parameter $indexes of Bdf\Prime\Collection\Ind...yIndexer::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

241
        $entities = new EntityIndexer($this->mapper, /** @scrutinizer ignore-type */ $byOptions ? [$byOptions['attribute']] : []);
Loading history...
242
243
        // Force loading of eager relations
244 464
        if (!empty($this->metadata->eagerRelations)) {
245 79
            $withRelations = array_merge($this->metadata->eagerRelations, $withRelations);
246
247
            // Skip relation that should not be loaded.
248 79
            foreach ($withoutRelations as $relationName => $nestedRelations) {
249
                // Only a leaf concerns this query.
250 45
                if (empty($nestedRelations)) {
251 45
                    unset($withRelations[$relationName]);
252
                }
253
            }
254
        }
255
256 464
        foreach ($data as $result) {
257 454
            $entities->push($entity = $this->mapper->prepareFromRepository($result, $repository->connection()->platform()));
258
259 454
            if ($hasLoadEvent) {
260 191
                $repository->notify(Events::POST_LOAD, [$entity, $repository]);
261
            }
262
        }
263
264 464
        foreach ($withRelations as $relationName => $relationInfos) {
265 159
            $repository->relation($relationName)->load(
266 159
                $entities,
267 159
                $relationInfos['relations'],
268 159
                $relationInfos['constraints'],
269 159
                $withoutRelations[$relationName] ?? []
270 159
            );
271
        }
272
273
        switch (true) {
274
            case $byOptions === null:
275 385
                return $entities->all();
276
277 239
            case $byOptions['combine']:
278 68
                return $entities->by($byOptions['attribute']);
279
280
            default:
281 211
                return $entities->byOverride($byOptions['attribute']);
282
        }
283
    }
284
285
    /**
286
     * Scope call
287
     * run a scope defined in repository
288
     *
289
     * @param string $name          Scope name
290
     * @param array  $arguments
291
     *
292
     * @return mixed
293
     */
294 1
    public function __call($name, $arguments)
295
    {
296
        /** @var EntityRepository $this->repository */
297 1
        $scopes = $this->repository->scopes();
0 ignored issues
show
The method scopes() does not exist on Bdf\Prime\Repository\RepositoryInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Bdf\Prime\Repository\RepositoryInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

297
        /** @scrutinizer ignore-call */ 
298
        $scopes = $this->repository->scopes();
Loading history...
298
299 1
        if (!isset($scopes[$name])) {
300
            throw new BadMethodCallException('Scope "' . get_class($this->mapper) . '::' . $name . '" not found');
301
        }
302
303 1
        return $scopes[$name](...$arguments);
304
    }
305
306
    /**
307
     * Configure the query
308
     *
309
     * @param ReadCommandInterface $query
310
     *
311
     * @return void
312
     */
313 669
    public function apply(ReadCommandInterface $query): void
314
    {
315 669
        $query->setExtension($this);
316 669
        $query->post([$this, 'processEntities'], false);
317
    }
318
}
319