This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Analogue\ORM\System; |
||
4 | |||
5 | use Closure; |
||
6 | use Exception; |
||
7 | use Analogue\ORM\EntityCollection; |
||
8 | use Analogue\ORM\Relationships\Relationship; |
||
9 | use Analogue\ORM\Exceptions\EntityNotFoundException; |
||
10 | use Analogue\ORM\Drivers\DBAdapter; |
||
11 | use Illuminate\Pagination\Paginator; |
||
12 | use Illuminate\Pagination\LengthAwarePaginator; |
||
13 | use Illuminate\Database\Query\Expression; |
||
14 | |||
15 | /** |
||
16 | * Analogue Query builder. |
||
17 | * |
||
18 | * @mixin QueryAdapter|\Analogue\ORM\Drivers\IlluminateQueryAdapter |
||
19 | */ |
||
20 | class Query |
||
21 | { |
||
22 | /** |
||
23 | * Mapper Instance |
||
24 | * |
||
25 | * @var \Analogue\ORM\System\Mapper |
||
26 | */ |
||
27 | protected $mapper; |
||
28 | |||
29 | /** |
||
30 | * DB Adatper |
||
31 | * |
||
32 | * @var \Analogue\ORM\Drivers\DBAdapter |
||
33 | */ |
||
34 | protected $adapter; |
||
35 | |||
36 | /** |
||
37 | * Query Builder Instance |
||
38 | * |
||
39 | * @var \Analogue\ORM\Drivers\QueryAdapter|\Analogue\ORM\Drivers\IlluminateQueryAdapter |
||
40 | */ |
||
41 | protected $query; |
||
42 | |||
43 | /** |
||
44 | * Entity Map Instance |
||
45 | * |
||
46 | * @var \Analogue\ORM\EntityMap |
||
47 | */ |
||
48 | protected $entityMap; |
||
49 | |||
50 | /** |
||
51 | * The relationships that should be eager loaded. |
||
52 | * |
||
53 | * @var array |
||
54 | */ |
||
55 | protected $eagerLoad = []; |
||
56 | |||
57 | /** |
||
58 | * All of the registered builder macros. |
||
59 | * |
||
60 | * @var array |
||
61 | */ |
||
62 | protected $macros = []; |
||
63 | |||
64 | /** |
||
65 | * The methods that should be returned from query builder. |
||
66 | * |
||
67 | * @var array |
||
68 | */ |
||
69 | protected $passthru = [ |
||
70 | 'toSql', |
||
71 | 'lists', |
||
72 | 'pluck', |
||
73 | 'count', |
||
74 | 'min', |
||
75 | 'max', |
||
76 | 'avg', |
||
77 | 'sum', |
||
78 | 'exists', |
||
79 | 'getBindings', |
||
80 | ]; |
||
81 | |||
82 | /** |
||
83 | * Query Builder Blacklist |
||
84 | */ |
||
85 | protected $blacklist = [ |
||
86 | 'insert', |
||
87 | 'insertGetId', |
||
88 | 'lock', |
||
89 | 'lockForUpdate', |
||
90 | 'sharedLock', |
||
91 | 'update', |
||
92 | 'increment', |
||
93 | 'decrement', |
||
94 | 'delete', |
||
95 | 'truncate', |
||
96 | 'raw', |
||
97 | ]; |
||
98 | |||
99 | /** |
||
100 | * Create a new Analogue Query Builder instance. |
||
101 | * |
||
102 | * @param Mapper $mapper |
||
103 | * @param DBAdapter $adapter |
||
104 | */ |
||
105 | public function __construct(Mapper $mapper, DBAdapter $adapter) |
||
106 | { |
||
107 | $this->mapper = $mapper; |
||
108 | |||
109 | $this->adapter = $adapter; |
||
110 | |||
111 | $this->entityMap = $mapper->getEntityMap(); |
||
112 | |||
113 | // Specify the table to work on |
||
114 | $this->query = $adapter->getQuery()->from($this->entityMap->getTable()); |
||
115 | |||
116 | $this->with($this->entityMap->getEagerloadedRelationships()); |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * Run the query and return the result |
||
121 | * |
||
122 | * @param array $columns |
||
123 | * @return \Analogue\ORM\EntityCollection |
||
124 | */ |
||
125 | public function get($columns = ['*']) |
||
126 | { |
||
127 | $entities = $this->getEntities($columns); |
||
128 | |||
129 | // If we actually found models we will also eager load any relationships that |
||
130 | // have been specified as needing to be eager loaded, which will solve the |
||
131 | // n+1 query issue for the developers to avoid running a lot of queries. |
||
132 | |||
133 | if (count($entities) > 0) { |
||
134 | $entities = $this->eagerLoadRelations($entities); |
||
135 | } |
||
136 | |||
137 | return $this->entityMap->newCollection($entities); |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * Find an entity by its primary key |
||
142 | * |
||
143 | * @param string|integer $id |
||
144 | * @param array $columns |
||
145 | * @return \Analogue\ORM\Mappable |
||
146 | */ |
||
147 | public function find($id, $columns = ['*']) |
||
148 | { |
||
149 | if (is_array($id)) { |
||
150 | return $this->findMany($id, $columns); |
||
151 | } |
||
152 | |||
153 | $this->query->where($this->entityMap->getQualifiedKeyName(), '=', $id); |
||
154 | |||
155 | return $this->first($columns); |
||
156 | } |
||
157 | |||
158 | /** |
||
159 | * Find many entities by their primary keys. |
||
160 | * |
||
161 | * @param array $id |
||
162 | * @param array $columns |
||
163 | * @return EntityCollection |
||
164 | */ |
||
165 | public function findMany($id, $columns = ['*']) |
||
166 | { |
||
167 | if (empty($id)) { |
||
168 | return new EntityCollection; |
||
169 | } |
||
170 | |||
171 | $this->query->whereIn($this->entityMap->getQualifiedKeyName(), $id); |
||
172 | |||
173 | return $this->get($columns); |
||
174 | } |
||
175 | |||
176 | /** |
||
177 | * Find a model by its primary key or throw an exception. |
||
178 | * |
||
179 | * @param mixed $id |
||
180 | * @param array $columns |
||
181 | * @throws \Analogue\ORM\Exceptions\EntityNotFoundException |
||
182 | * @return mixed|self |
||
183 | */ |
||
184 | View Code Duplication | public function findOrFail($id, $columns = ['*']) |
|
0 ignored issues
–
show
|
|||
185 | { |
||
186 | if (!is_null($entity = $this->find($id, $columns))) { |
||
187 | return $entity; |
||
188 | } |
||
189 | |||
190 | throw (new EntityNotFoundException)->setEntity(get_class($this->entityMap)); |
||
191 | } |
||
192 | |||
193 | |||
194 | /** |
||
195 | * Execute the query and get the first result. |
||
196 | * |
||
197 | * @param array $columns |
||
198 | * @return \Analogue\ORM\Entity |
||
199 | */ |
||
200 | public function first($columns = ['*']) |
||
201 | { |
||
202 | return $this->take(1)->get($columns)->first(); |
||
0 ignored issues
–
show
The method
take does not exist on object<Analogue\ORM\System\Query> ? Since you implemented __call , maybe consider adding a @method annotation.
If you implement This is often the case, when class ParentClass {
private $data = array();
public function __call($method, array $args) {
if (0 === strpos($method, 'get')) {
return $this->data[strtolower(substr($method, 3))];
}
throw new \LogicException(sprintf('Unsupported method: %s', $method));
}
}
/**
* If this class knows which fields exist, you can specify the methods here:
*
* @method string getName()
*/
class SomeClass extends ParentClass { }
![]() |
|||
203 | } |
||
204 | |||
205 | /** |
||
206 | * Execute the query and get the first result or throw an exception. |
||
207 | * |
||
208 | * @param array $columns |
||
209 | * @throws EntityNotFoundException |
||
210 | * @return \Analogue\ORM\Entity |
||
211 | */ |
||
212 | View Code Duplication | public function firstOrFail($columns = ['*']) |
|
0 ignored issues
–
show
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. ![]() |
|||
213 | { |
||
214 | if (!is_null($entity = $this->first($columns))) { |
||
215 | return $entity; |
||
216 | } |
||
217 | |||
218 | throw (new EntityNotFoundException)->setEntity(get_class($this->entityMap)); |
||
219 | } |
||
220 | |||
221 | /** |
||
222 | * Pluck a single column from the database. |
||
223 | * |
||
224 | * @param string $column |
||
225 | * @return mixed |
||
226 | */ |
||
227 | public function pluck($column) |
||
228 | { |
||
229 | $result = $this->first([$column]); |
||
230 | |||
231 | if ($result) { |
||
232 | return $result->{$column}; |
||
233 | } |
||
234 | } |
||
235 | |||
236 | /** |
||
237 | * Chunk the results of the query. |
||
238 | * |
||
239 | * @param int $count |
||
240 | * @param callable $callback |
||
241 | * @return void |
||
242 | */ |
||
243 | public function chunk($count, callable $callback) |
||
244 | { |
||
245 | $results = $this->forPage($page = 1, $count)->get(); |
||
0 ignored issues
–
show
The method
forPage does not exist on object<Analogue\ORM\System\Query> ? Since you implemented __call , maybe consider adding a @method annotation.
If you implement This is often the case, when class ParentClass {
private $data = array();
public function __call($method, array $args) {
if (0 === strpos($method, 'get')) {
return $this->data[strtolower(substr($method, 3))];
}
throw new \LogicException(sprintf('Unsupported method: %s', $method));
}
}
/**
* If this class knows which fields exist, you can specify the methods here:
*
* @method string getName()
*/
class SomeClass extends ParentClass { }
![]() |
|||
246 | |||
247 | while (count($results) > 0) { |
||
248 | // On each chunk result set, we will pass them to the callback and then let the |
||
249 | // developer take care of everything within the callback, which allows us to |
||
250 | // keep the memory low for spinning through large result sets for working. |
||
251 | call_user_func($callback, $results); |
||
252 | |||
253 | $page++; |
||
254 | |||
255 | $results = $this->forPage($page, $count)->get(); |
||
0 ignored issues
–
show
The method
forPage does not exist on object<Analogue\ORM\System\Query> ? Since you implemented __call , maybe consider adding a @method annotation.
If you implement This is often the case, when class ParentClass {
private $data = array();
public function __call($method, array $args) {
if (0 === strpos($method, 'get')) {
return $this->data[strtolower(substr($method, 3))];
}
throw new \LogicException(sprintf('Unsupported method: %s', $method));
}
}
/**
* If this class knows which fields exist, you can specify the methods here:
*
* @method string getName()
*/
class SomeClass extends ParentClass { }
![]() |
|||
256 | } |
||
257 | } |
||
258 | |||
259 | /** |
||
260 | * Get an array with the values of a given column. |
||
261 | * |
||
262 | * @param string $column |
||
263 | * @param string $key |
||
264 | * @return array |
||
265 | */ |
||
266 | public function lists($column, $key = null) |
||
267 | { |
||
268 | return $this->query->lists($column, $key); |
||
269 | } |
||
270 | |||
271 | /** |
||
272 | * Get a paginator for the "select" statement. |
||
273 | * |
||
274 | * @param int $perPage |
||
275 | * @param array $columns |
||
276 | * @return LengthAwarePaginator |
||
277 | */ |
||
278 | public function paginate($perPage = null, $columns = ['*']) |
||
279 | { |
||
280 | $total = $this->query->getCountForPagination(); |
||
281 | |||
282 | $this->query->forPage( |
||
283 | $page = Paginator::resolveCurrentPage(), |
||
284 | $perPage = $perPage ?: $this->entityMap->getPerPage() |
||
285 | ); |
||
286 | |||
287 | return new LengthAwarePaginator($this->get($columns)->all(), $total, $perPage, $page, [ |
||
288 | 'path' => Paginator::resolveCurrentPath() |
||
289 | ]); |
||
290 | } |
||
291 | |||
292 | /** |
||
293 | * Get a paginator for a grouped statement. |
||
294 | * |
||
295 | * @param \Illuminate\Pagination\Factory $paginator |
||
296 | * @param int $perPage |
||
297 | * @param array $columns |
||
298 | * @return \Illuminate\Pagination\Paginator |
||
299 | */ |
||
300 | protected function groupedPaginate($paginator, $perPage, $columns) |
||
301 | { |
||
302 | $results = $this->get($columns)->all(); |
||
303 | |||
304 | return $this->query->buildRawPaginator($paginator, $results, $perPage); |
||
305 | } |
||
306 | |||
307 | /** |
||
308 | * Get a paginator for an ungrouped statement. |
||
309 | * |
||
310 | * @param \Illuminate\Pagination\Factory $paginator |
||
311 | * @param int $perPage |
||
312 | * @param array $columns |
||
313 | * @return \Illuminate\Pagination\Paginator |
||
314 | */ |
||
315 | protected function ungroupedPaginate($paginator, $perPage, $columns) |
||
316 | { |
||
317 | $total = $this->query->getPaginationCount(); |
||
318 | |||
319 | // Once we have the paginator we need to set the limit and offset values for |
||
320 | // the query so we can get the properly paginated items. Once we have an |
||
321 | // array of items we can create the paginator instances for the items. |
||
322 | $page = $paginator->getCurrentPage($total); |
||
323 | |||
324 | $this->query->forPage($page, $perPage); |
||
325 | |||
326 | return $paginator->make($this->get($columns)->all(), $total, $perPage); |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * Paginate the given query into a simple paginator. |
||
331 | * |
||
332 | * @param int $perPage |
||
333 | * @param array $columns |
||
334 | * @return \Illuminate\Contracts\Pagination\Paginator |
||
335 | */ |
||
336 | public function simplePaginate($perPage = null, $columns = ['*']) |
||
337 | { |
||
338 | $page = Paginator::resolveCurrentPage(); |
||
339 | |||
340 | $perPage = $perPage ?: $this->entityMap->getPerPage(); |
||
341 | |||
342 | $this->skip(($page - 1) * $perPage)->take($perPage + 1); |
||
0 ignored issues
–
show
The method
skip does not exist on object<Analogue\ORM\System\Query> ? Since you implemented __call , maybe consider adding a @method annotation.
If you implement This is often the case, when class ParentClass {
private $data = array();
public function __call($method, array $args) {
if (0 === strpos($method, 'get')) {
return $this->data[strtolower(substr($method, 3))];
}
throw new \LogicException(sprintf('Unsupported method: %s', $method));
}
}
/**
* If this class knows which fields exist, you can specify the methods here:
*
* @method string getName()
*/
class SomeClass extends ParentClass { }
![]() |
|||
343 | |||
344 | return new Paginator($this->get($columns)->all(), $perPage, $page, ['path' => Paginator::resolveCurrentPath()]); |
||
345 | } |
||
346 | |||
347 | /** |
||
348 | * Add a basic where clause to the query. |
||
349 | * |
||
350 | * @param string $column |
||
351 | * @param string $operator |
||
352 | * @param mixed $value |
||
353 | * @param string $boolean |
||
354 | * @return $this |
||
355 | */ |
||
356 | public function where($column, $operator = null, $value = null, $boolean = 'and') |
||
0 ignored issues
–
show
|
|||
357 | { |
||
358 | if ($column instanceof Closure) { |
||
359 | $query = $this->newQueryWithoutScopes(); |
||
360 | |||
361 | call_user_func($column, $query); |
||
362 | |||
363 | $this->query->addNestedWhereQuery($query->getQuery(), $boolean); |
||
364 | } else { |
||
365 | call_user_func_array([$this->query, 'where'], func_get_args()); |
||
366 | } |
||
367 | |||
368 | return $this; |
||
369 | } |
||
370 | |||
371 | /** |
||
372 | * Add an "or where" clause to the query. |
||
373 | * |
||
374 | * @param string $column |
||
375 | * @param string $operator |
||
376 | * @param mixed $value |
||
377 | * @return \Analogue\ORM\System\Query |
||
378 | */ |
||
379 | public function orWhere($column, $operator = null, $value = null) |
||
380 | { |
||
381 | return $this->where($column, $operator, $value, 'or'); |
||
382 | } |
||
383 | |||
384 | /** |
||
385 | * Add a relationship count condition to the query. |
||
386 | * |
||
387 | * @param string $relation |
||
388 | * @param string $operator |
||
389 | * @param int $count |
||
390 | * @param string $boolean |
||
391 | * @param \Closure $callback |
||
392 | * @return \Analogue\ORM\System\Query |
||
393 | */ |
||
394 | public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', $callback = null) |
||
395 | { |
||
396 | $entity = $this->mapper->newInstance(); |
||
397 | |||
398 | $relation = $this->getHasRelationQuery($relation, $entity); |
||
399 | |||
400 | $query = $relation->getRelationCountQuery($relation->getRelatedMapper()->getQuery(), $this); |
||
0 ignored issues
–
show
The method
getRelatedMapper does not exist on object<Analogue\ORM\System\Query> ? Since you implemented __call , maybe consider adding a @method annotation.
If you implement This is often the case, when class ParentClass {
private $data = array();
public function __call($method, array $args) {
if (0 === strpos($method, 'get')) {
return $this->data[strtolower(substr($method, 3))];
}
throw new \LogicException(sprintf('Unsupported method: %s', $method));
}
}
/**
* If this class knows which fields exist, you can specify the methods here:
*
* @method string getName()
*/
class SomeClass extends ParentClass { }
![]() The method
getRelationCountQuery does not exist on object<Analogue\ORM\System\Query> ? Since you implemented __call , maybe consider adding a @method annotation.
If you implement This is often the case, when class ParentClass {
private $data = array();
public function __call($method, array $args) {
if (0 === strpos($method, 'get')) {
return $this->data[strtolower(substr($method, 3))];
}
throw new \LogicException(sprintf('Unsupported method: %s', $method));
}
}
/**
* If this class knows which fields exist, you can specify the methods here:
*
* @method string getName()
*/
class SomeClass extends ParentClass { }
![]() |
|||
401 | |||
402 | if ($callback) { |
||
403 | call_user_func($callback, $query); |
||
404 | } |
||
405 | |||
406 | return $this->addHasWhere($query, $relation, $operator, $count, $boolean); |
||
0 ignored issues
–
show
$relation is of type object<Analogue\ORM\System\Query> , but the function expects a object<Analogue\ORM\Relationships\Relationship> .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() |
|||
407 | } |
||
408 | |||
409 | /** |
||
410 | * Add a relationship count condition to the query with where clauses. |
||
411 | * |
||
412 | * @param string $relation |
||
413 | * @param \Closure $callback |
||
414 | * @param string $operator |
||
415 | * @param int $count |
||
416 | * @return \Analogue\ORM\System\Query |
||
417 | */ |
||
418 | public function whereHas($relation, Closure $callback, $operator = '>=', $count = 1) |
||
419 | { |
||
420 | return $this->has($relation, $operator, $count, 'and', $callback); |
||
421 | } |
||
422 | |||
423 | /** |
||
424 | * Add a relationship count condition to the query with an "or". |
||
425 | * |
||
426 | * @param string $relation |
||
427 | * @param string $operator |
||
428 | * @param int $count |
||
429 | * @return \Analogue\ORM\System\Query |
||
430 | */ |
||
431 | public function orHas($relation, $operator = '>=', $count = 1) |
||
432 | { |
||
433 | return $this->has($relation, $operator, $count, 'or'); |
||
434 | } |
||
435 | |||
436 | /** |
||
437 | * Add a relationship count condition to the query with where clauses and an "or". |
||
438 | * |
||
439 | * @param string $relation |
||
440 | * @param \Closure $callback |
||
441 | * @param string $operator |
||
442 | * @param int $count |
||
443 | * @return \Analogue\ORM\System\Query |
||
444 | */ |
||
445 | public function orWhereHas($relation, Closure $callback, $operator = '>=', $count = 1) |
||
446 | { |
||
447 | return $this->has($relation, $operator, $count, 'or', $callback); |
||
448 | } |
||
449 | |||
450 | /** |
||
451 | * Add the "has" condition where clause to the query. |
||
452 | * |
||
453 | * @param \Analogue\ORM\System\Query $hasQuery |
||
454 | * @param \Analogue\ORM\Relationships\Relationship $relation |
||
455 | * @param string $operator |
||
456 | * @param int $count |
||
457 | * @param string $boolean |
||
458 | * @return \Analogue\ORM\System\Query |
||
459 | */ |
||
460 | protected function addHasWhere(Query $hasQuery, Relationship $relation, $operator, $count, $boolean) |
||
461 | { |
||
462 | $this->mergeWheresToHas($hasQuery, $relation); |
||
463 | |||
464 | if (is_numeric($count)) { |
||
465 | $count = new Expression($count); |
||
466 | } |
||
467 | |||
468 | return $this->where(new Expression('(' . $hasQuery->toSql() . ')'), $operator, $count, $boolean); |
||
0 ignored issues
–
show
The method
toSql does not exist on object<Analogue\ORM\System\Query> ? Since you implemented __call , maybe consider adding a @method annotation.
If you implement This is often the case, when class ParentClass {
private $data = array();
public function __call($method, array $args) {
if (0 === strpos($method, 'get')) {
return $this->data[strtolower(substr($method, 3))];
}
throw new \LogicException(sprintf('Unsupported method: %s', $method));
}
}
/**
* If this class knows which fields exist, you can specify the methods here:
*
* @method string getName()
*/
class SomeClass extends ParentClass { }
![]() |
|||
469 | } |
||
470 | |||
471 | /** |
||
472 | * Merge the "wheres" from a relation query to a has query. |
||
473 | * |
||
474 | * @param \Analogue\ORM\System\Query $hasQuery |
||
475 | * @param \Analogue\ORM\Relationships\Relationship $relation |
||
476 | * @return void |
||
477 | */ |
||
478 | protected function mergeWheresToHas(Query $hasQuery, Relationship $relation) |
||
479 | { |
||
480 | // Here we have the "has" query and the original relation. We need to copy over any |
||
481 | // where clauses the developer may have put in the relationship function over to |
||
482 | // the has query, and then copy the bindings from the "has" query to the main. |
||
483 | $relationQuery = $relation->getBaseQuery(); |
||
484 | |||
485 | $hasQuery->mergeWheres( |
||
0 ignored issues
–
show
The method
mergeWheres() does not exist on Analogue\ORM\System\Query . Did you maybe mean mergeWheresToHas() ?
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. ![]() |
|||
486 | $relationQuery->wheres, $relationQuery->getBindings() |
||
487 | ); |
||
488 | |||
489 | $this->query->mergeBindings($hasQuery->getQuery()); |
||
490 | } |
||
491 | |||
492 | /** |
||
493 | * Get the "has relation" base query instance. |
||
494 | * |
||
495 | * @param string $relation |
||
496 | * @param $entity |
||
497 | * @return \Analogue\ORM\System\Query |
||
498 | */ |
||
499 | protected function getHasRelationQuery($relation, $entity) |
||
500 | { |
||
501 | return Relationship::noConstraints(function () use ($relation, $entity) { |
||
502 | return $this->entityMap->$relation($entity); |
||
503 | }); |
||
504 | } |
||
505 | |||
506 | /** |
||
507 | * Get the table for the current query object |
||
508 | * |
||
509 | * @return string |
||
510 | */ |
||
511 | public function getTable() |
||
512 | { |
||
513 | return $this->entityMap->getTable(); |
||
514 | } |
||
515 | |||
516 | /** |
||
517 | * Set the relationships that should be eager loaded. |
||
518 | * |
||
519 | * @param mixed $relations |
||
520 | * @return $this |
||
521 | */ |
||
522 | public function with($relations) |
||
523 | { |
||
524 | if (is_string($relations)) { |
||
525 | $relations = func_get_args(); |
||
526 | } |
||
527 | |||
528 | $eagers = $this->parseRelations($relations); |
||
529 | |||
530 | $this->eagerLoad = array_merge($this->eagerLoad, $eagers); |
||
531 | |||
532 | return $this; |
||
533 | } |
||
534 | |||
535 | /** |
||
536 | * Parse a list of relations into individuals. |
||
537 | * |
||
538 | * @param array $relations |
||
539 | * @return array |
||
540 | */ |
||
541 | protected function parseRelations(array $relations) |
||
542 | { |
||
543 | $results = []; |
||
544 | |||
545 | foreach ($relations as $name => $constraints) { |
||
546 | // If the "relation" value is actually a numeric key, we can assume that no |
||
547 | // constraints have been specified for the eager load and we'll just put |
||
548 | // an empty Closure with the loader so that we can treat all the same. |
||
549 | if (is_numeric($name)) { |
||
550 | $f = function () {}; |
||
551 | |||
552 | list($name, $constraints) = [$constraints, $f]; |
||
553 | } |
||
554 | |||
555 | // We need to separate out any nested includes. Which allows the developers |
||
556 | // to load deep relationships using "dots" without stating each level of |
||
557 | // the relationship with its own key in the array of eager load names. |
||
558 | $results = $this->parseNested($name, $results); |
||
559 | |||
560 | $results[$name] = $constraints; |
||
561 | } |
||
562 | |||
563 | return $results; |
||
564 | } |
||
565 | |||
566 | |||
567 | /** |
||
568 | * Parse the nested relationships in a relation. |
||
569 | * |
||
570 | * @param string $name |
||
571 | * @param array $results |
||
572 | * @return array |
||
573 | */ |
||
574 | protected function parseNested($name, $results) |
||
575 | { |
||
576 | $progress = []; |
||
577 | |||
578 | // If the relation has already been set on the result array, we will not set it |
||
579 | // again, since that would override any constraints that were already placed |
||
580 | // on the relationships. We will only set the ones that are not specified. |
||
581 | foreach (explode('.', $name) as $segment) { |
||
582 | $progress[] = $segment; |
||
583 | |||
584 | if (!isset($results[$last = implode('.', $progress)])) { |
||
585 | $results[$last] = function () {}; |
||
586 | } |
||
587 | } |
||
588 | |||
589 | return $results; |
||
590 | } |
||
591 | |||
592 | /** |
||
593 | * Get the relationships being eagerly loaded. |
||
594 | * |
||
595 | * @return array |
||
596 | */ |
||
597 | public function getEagerLoads() |
||
598 | { |
||
599 | return $this->eagerLoad; |
||
600 | } |
||
601 | |||
602 | /** |
||
603 | * Set the relationships being eagerly loaded. |
||
604 | * |
||
605 | * @param array $eagerLoad |
||
606 | * @return void |
||
607 | */ |
||
608 | public function setEagerLoads(array $eagerLoad) |
||
609 | { |
||
610 | $this->eagerLoad = $eagerLoad; |
||
611 | } |
||
612 | |||
613 | /** |
||
614 | * Eager load the relationships for the entities. |
||
615 | * |
||
616 | * @param array $entities |
||
617 | * @return array |
||
618 | */ |
||
619 | public function eagerLoadRelations($entities) |
||
620 | { |
||
621 | foreach ($this->eagerLoad as $name => $constraints) { |
||
622 | // For nested eager loads we'll skip loading them here and they will be set as an |
||
623 | // eager load on the query to retrieve the relation so that they will be eager |
||
624 | // loaded on that query, because that is where they get hydrated as models. |
||
625 | if (strpos($name, '.') === false) { |
||
626 | $entities = $this->loadRelation($entities, $name, $constraints); |
||
627 | } |
||
628 | } |
||
629 | |||
630 | return $entities; |
||
631 | } |
||
632 | |||
633 | /** |
||
634 | * Eagerly load the relationship on a set of entities. |
||
635 | * |
||
636 | * @param array $entities |
||
637 | * @param string $name |
||
638 | * @param \Closure $constraints |
||
639 | * @return array |
||
640 | */ |
||
641 | protected function loadRelation(array $entities, $name, Closure $constraints) |
||
642 | { |
||
643 | // First we will "back up" the existing where conditions on the query so we can |
||
644 | // add our eager constraints. Then we will merge the wheres that were on the |
||
645 | // query back to it in order that any where conditions might be specified. |
||
646 | $relation = $this->getRelation($name); |
||
647 | |||
648 | $relation->addEagerConstraints($entities); |
||
649 | |||
650 | call_user_func($constraints, $relation); |
||
651 | |||
652 | $entities = $relation->initRelation($entities, $name); |
||
653 | |||
654 | // Once we have the results, we just match those back up to their parent models |
||
655 | // using the relationship instance. Then we just return the finished arrays |
||
656 | // of models which have been eagerly hydrated and are readied for return. |
||
657 | |||
658 | $results = $relation->getEager(); |
||
659 | |||
660 | return $relation->match($entities, $results, $name); |
||
661 | } |
||
662 | |||
663 | /** |
||
664 | * Get the relation instance for the given relation name. |
||
665 | * |
||
666 | * @param string $relation |
||
667 | * @return \Analogue\ORM\Relationships\Relationship |
||
668 | */ |
||
669 | public function getRelation($relation) |
||
670 | { |
||
671 | // We want to run a relationship query without any constrains so that we will |
||
672 | // not have to remove these where clauses manually which gets really hacky |
||
673 | // and is error prone while we remove the developer's own where clauses. |
||
674 | $query = Relationship::noConstraints(function () use ($relation) { |
||
675 | return $this->entityMap->$relation($this->getEntityInstance()); |
||
676 | }); |
||
677 | |||
678 | $nested = $this->nestedRelations($relation); |
||
679 | |||
680 | // If there are nested relationships set on the query, we will put those onto |
||
681 | // the query instances so that they can be handled after this relationship |
||
682 | // is loaded. In this way they will all trickle down as they are loaded. |
||
683 | if (count($nested) > 0) { |
||
684 | $query->getQuery()->with($nested); |
||
685 | } |
||
686 | |||
687 | return $query; |
||
688 | } |
||
689 | |||
690 | /** |
||
691 | * Get the deeply nested relations for a given top-level relation. |
||
692 | * |
||
693 | * @param string $relation |
||
694 | * @return array |
||
695 | */ |
||
696 | protected function nestedRelations($relation) |
||
697 | { |
||
698 | $nested = []; |
||
699 | |||
700 | // We are basically looking for any relationships that are nested deeper than |
||
701 | // the given top-level relationship. We will just check for any relations |
||
702 | // that start with the given top relations and adds them to our arrays. |
||
703 | foreach ($this->eagerLoad as $name => $constraints) { |
||
704 | if ($this->isNested($name, $relation)) { |
||
705 | $nested[substr($name, strlen($relation . '.'))] = $constraints; |
||
706 | } |
||
707 | } |
||
708 | |||
709 | return $nested; |
||
710 | } |
||
711 | |||
712 | /** |
||
713 | * Determine if the relationship is nested. |
||
714 | * |
||
715 | * @param string $name |
||
716 | * @param string $relation |
||
717 | * @return bool |
||
718 | */ |
||
719 | protected function isNested($name, $relation) |
||
720 | { |
||
721 | $dots = str_contains($name, '.'); |
||
722 | |||
723 | return $dots && starts_with($name, $relation . '.'); |
||
724 | } |
||
725 | |||
726 | /** |
||
727 | * Add the Entity primary key if not in requested columns |
||
728 | * |
||
729 | * @param array $columns |
||
730 | * @return array |
||
731 | */ |
||
732 | protected function enforceIdColumn($columns) |
||
733 | { |
||
734 | if (!in_array($this->entityMap->getKeyName(), $columns)) { |
||
735 | $columns[] = $this->entityMap->getKeyName(); |
||
736 | } |
||
737 | return $columns; |
||
738 | } |
||
739 | |||
740 | /** |
||
741 | * Get the hydrated models without eager loading. |
||
742 | * |
||
743 | * @param array $columns |
||
744 | * @return \Analogue\ORM\EntityCollection |
||
745 | */ |
||
746 | public function getEntities($columns = ['*']) |
||
747 | { |
||
748 | // As we need the primary key to feed the |
||
749 | // entity cache, we need it loaded on each |
||
750 | // request |
||
751 | $columns = $this->enforceIdColumn($columns); |
||
752 | |||
753 | // Run the query |
||
754 | $results = $this->query->get($columns); |
||
755 | |||
756 | $builder = new EntityBuilder($this->mapper, array_keys($this->getEagerLoads())); |
||
757 | |||
758 | return $builder->build($results); |
||
759 | } |
||
760 | |||
761 | /** |
||
762 | * Get a new instance for the entity |
||
763 | * |
||
764 | * @param array $attributes |
||
765 | * @return \Analogue\ORM\Entity |
||
766 | */ |
||
767 | public function getEntityInstance(array $attributes = []) |
||
768 | { |
||
769 | return $this->mapper->newInstance($attributes); |
||
770 | } |
||
771 | |||
772 | /** |
||
773 | * Extend the builder with a given callback. |
||
774 | * |
||
775 | * @param string $name |
||
776 | * @param \Closure $callback |
||
777 | * @return void |
||
778 | */ |
||
779 | public function macro($name, Closure $callback) |
||
780 | { |
||
781 | $this->macros[$name] = $callback; |
||
782 | } |
||
783 | |||
784 | /** |
||
785 | * Get the given macro by name. |
||
786 | * |
||
787 | * @param string $name |
||
788 | * @return \Closure |
||
789 | */ |
||
790 | public function getMacro($name) |
||
791 | { |
||
792 | return array_get($this->macros, $name); |
||
793 | } |
||
794 | |||
795 | /** |
||
796 | * Get a new query builder for the model's table. |
||
797 | * |
||
798 | * @return \Analogue\ORM\System\Query |
||
799 | */ |
||
800 | public function newQuery() |
||
801 | { |
||
802 | $builder = new Query($this->mapper, $this->adapter); |
||
803 | |||
804 | return $this->applyGlobalScopes($builder); |
||
0 ignored issues
–
show
The method
applyGlobalScopes does not exist on object<Analogue\ORM\System\Query> ? Since you implemented __call , maybe consider adding a @method annotation.
If you implement This is often the case, when class ParentClass {
private $data = array();
public function __call($method, array $args) {
if (0 === strpos($method, 'get')) {
return $this->data[strtolower(substr($method, 3))];
}
throw new \LogicException(sprintf('Unsupported method: %s', $method));
}
}
/**
* If this class knows which fields exist, you can specify the methods here:
*
* @method string getName()
*/
class SomeClass extends ParentClass { }
![]() |
|||
805 | } |
||
806 | |||
807 | /** |
||
808 | * Get a new query builder without any scope applied. |
||
809 | * |
||
810 | * @return \Analogue\ORM\System\Query |
||
811 | */ |
||
812 | public function newQueryWithoutScopes() |
||
813 | { |
||
814 | return new Query($this->mapper, $this->adapter); |
||
815 | } |
||
816 | |||
817 | /** |
||
818 | * Get the Mapper instance for this Query Builder |
||
819 | * |
||
820 | * @return \Analogue\ORM\System\Mapper |
||
821 | */ |
||
822 | public function getMapper() |
||
823 | { |
||
824 | return $this->mapper; |
||
825 | } |
||
826 | |||
827 | /** |
||
828 | * Get the underlying query adapter |
||
829 | * |
||
830 | * (REFACTOR: this method should move out, we need to provide the client classes |
||
831 | * with the adapter instead.) |
||
832 | * |
||
833 | * @return \Analogue\ORM\Drivers\QueryAdapter|\Analogue\ORM\Drivers\IlluminateQueryAdapter |
||
834 | */ |
||
835 | public function getQuery() |
||
836 | { |
||
837 | return $this->query; |
||
838 | } |
||
839 | |||
840 | /** |
||
841 | * Dynamically handle calls into the query instance. |
||
842 | * |
||
843 | * @param string $method |
||
844 | * @param array $parameters |
||
845 | * @throws Exception |
||
846 | * @return mixed |
||
847 | */ |
||
848 | public function __call($method, $parameters) |
||
849 | { |
||
850 | if (isset($this->macros[$method])) { |
||
851 | array_unshift($parameters, $this); |
||
852 | |||
853 | return call_user_func_array($this->macros[$method], $parameters); |
||
854 | } |
||
855 | |||
856 | if (in_array($method, $this->blacklist)) { |
||
857 | throw new Exception("Method $method doesn't exist"); |
||
858 | } |
||
859 | |||
860 | $result = call_user_func_array([$this->query, $method], $parameters); |
||
861 | |||
862 | return in_array($method, $this->passthru) ? $result : $this; |
||
863 | } |
||
864 | } |
||
865 |
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.