PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.17.0
GiveWP – Donation Plugin and Fundraising Platform v4.17.0
4.17.0 4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 All 256 releases
give / src / Framework / Models / EagerLoader.php

EagerLoader.php in GiveWP – Donation Plugin and Fundraising Platform 4.17.0, at src/Framework/Models/EagerLoader.php

161 lines 4.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Give\Framework\Models;
4
5 use Give\Framework\Exceptions\Primitives\InvalidArgumentException;
6 use ReflectionClass;
7
8 /**
9 * Eager load model relationships for more performant queries.
10 *
11 * As opposed to "lazy" loading, which queries the database for a relationship when it is accessed,
12 * "eager" loading queries the database for the relationship of all queried models, with a single query.
13 * This prevents a "N+1" problem, where a query is executed for each model, but using query optimization.
14 *
15 * @since 3.5.0
16 *
17 * @template M
18 */
19 class EagerLoader
20 {
21 /**
22 * @since 3.5.0
23 * @var ReflectionClass<M>
24 */
25 protected $reflection;
26
27 /**
28 * @since 3.5.0
29 * @var ModelQueryBuilder
30 */
31 protected $modelQuery;
32
33 /**
34 * @since 3.5.0
35 * @var ModelQueryBuilder
36 */
37 protected $eagerLoadedQuery;
38
39 /**
40 * @since 3.5.0
41 * @var string
42 */
43 protected $relationshipKey;
44
45 /**
46 * @since 3.5.0
47 * @var string
48 */
49 protected $foreignKey;
50
51 /**
52 * @var mixed
53 */
54 protected $foreignAttribute;
55
56 /**
57 * @since 4.17.0 Declare the nullable parameter explicitly.
58 * @since 3.5.0
59 *
60 * @param class-string<M> $modelClass
61 * @param class-string<M> $eagerLoadedModelClass
62 * @param string $relationshipKey
63 * @param string $foreignKey
64 * @param string|null $foreignAttribute
65 */
66 public function __construct(string $modelClass, string $eagerLoadedModelClass, string $relationshipKey, string $foreignKey, ?string $foreignAttribute = null)
67 {
68 if (!is_subclass_of($modelClass, Model::class)) {
69 throw new InvalidArgumentException("$modelClass must be an instance of " . Model::class);
70 }
71
72 if (!is_subclass_of($eagerLoadedModelClass, Model::class)) {
73 throw new InvalidArgumentException("$eagerLoadedModelClass must be an instance of " . Model::class);
74 }
75
76 $this->reflection = new ReflectionClass($modelClass);
77 $this->modelQuery = $modelClass::query();
78 $this->relationshipKey = $relationshipKey;
79 $this->eagerLoadedQuery = $eagerLoadedModelClass::query();
80 $this->foreignKey = $foreignKey;
81 $this->foreignAttribute = $foreignAttribute ?? $foreignKey;
82 }
83
84 /**
85 * @since 3.5.0
86 */
87 public function __call($name, $arguments)
88 {
89 $this->modelQuery->$name(...$arguments);
90 return $this;
91 }
92
93 /**
94 * This method wraps the `get()` method of the underlying ModelQueryBuilder.
95 * It uses the results to query the related models and pre-set the cachedRelations property.
96 *
97 * @since 3.5.0
98 *
99 * @return M|null
100 */
101 public function get()
102 {
103 $model = $this->modelQuery->get();
104
105 $eagerLoadedModels = $this->eagerLoadedQuery
106 ->where($this->foreignKey, $model->id)
107 ->getAll();
108
109 $this->setEagerLoadedModels($model, $eagerLoadedModels);
110
111 return $model;
112 }
113
114 /**
115 * This method wraps the `getAll()` method of the underlying ModelQueryBuilder.
116 * It uses the results to query the related models and pre-set the cachedRelations property.
117 *
118 * @since 3.5.0
119 *
120 * @return M[]|null
121 */
122 public function getAll()
123 {
124 $models = $this->modelQuery->getAll();
125
126 $eagerLoadedModels = $this->eagerLoadedQuery
127 ->whereIn($this->foreignKey, array_column($models, 'id'))
128 ->getAll();
129
130 foreach($models as $model) {
131 $this->setEagerLoadedModels($model, array_filter($eagerLoadedModels, function($eagerLoadedModel) use ($model) {
132 return $eagerLoadedModel->{$this->foreignAttribute} === $model->id;
133 }));
134 }
135
136 return $models;
137 }
138
139 /**
140 * The cachedRelations property is protected and cannot be accessed directly.
141 * This method uses reflection to set the cachedRelations property on the model.
142 *
143 * @since 3.5.0
144 *
145 * @param Model $model
146 * @param array $eagerLoadedModels
147 */
148 protected function setEagerLoadedModels(Model $model, array $eagerLoadedModels): void
149 {
150 $property = $this->reflection
151 ->getParentClass()
152 ->getProperty('cachedRelations');
153 $property->setAccessible(true);
154
155 $cachedRelations = $property->getValue($model);
156 $cachedRelations[$this->relationshipKey] = $eagerLoadedModels;
157
158 $property->setValue($model, $cachedRelations);
159 }
160 }
161