PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.1.0
GiveWP – Donation Plugin and Fundraising Platform v4.1.0
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 2.30.0 2.31.0 2.31.1 All 253 releases
give / src / Framework / Models / EagerLoader.php
EagerLoader.php
160 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 3.5.0
58 *
59 * @param class-string<M> $modelClass
60 * @param class-string<M> $eagerLoadedModelClass
61 * @param string $relationshipKey
62 * @param string $foreignKey
63 * @param string|null $foreignAttribute
64 */
65 public function __construct(string $modelClass, string $eagerLoadedModelClass, string $relationshipKey, string $foreignKey, string $foreignAttribute = null)
66 {
67 if (!is_subclass_of($modelClass, Model::class)) {
68 throw new InvalidArgumentException("$modelClass must be an instance of " . Model::class);
69 }
70
71 if (!is_subclass_of($eagerLoadedModelClass, Model::class)) {
72 throw new InvalidArgumentException("$eagerLoadedModelClass must be an instance of " . Model::class);
73 }
74
75 $this->reflection = new ReflectionClass($modelClass);
76 $this->modelQuery = $modelClass::query();
77 $this->relationshipKey = $relationshipKey;
78 $this->eagerLoadedQuery = $eagerLoadedModelClass::query();
79 $this->foreignKey = $foreignKey;
80 $this->foreignAttribute = $foreignAttribute ?? $foreignKey;
81 }
82
83 /**
84 * @since 3.5.0
85 */
86 public function __call($name, $arguments)
87 {
88 $this->modelQuery->$name(...$arguments);
89 return $this;
90 }
91
92 /**
93 * This method wraps the `get()` method of the underlying ModelQueryBuilder.
94 * It uses the results to query the related models and pre-set the cachedRelations property.
95 *
96 * @since 3.5.0
97 *
98 * @return M|null
99 */
100 public function get()
101 {
102 $model = $this->modelQuery->get();
103
104 $eagerLoadedModels = $this->eagerLoadedQuery
105 ->where($this->foreignKey, $model->id)
106 ->getAll();
107
108 $this->setEagerLoadedModels($model, $eagerLoadedModels);
109
110 return $model;
111 }
112
113 /**
114 * This method wraps the `getAll()` method of the underlying ModelQueryBuilder.
115 * It uses the results to query the related models and pre-set the cachedRelations property.
116 *
117 * @since 3.5.0
118 *
119 * @return M[]|null
120 */
121 public function getAll()
122 {
123 $models = $this->modelQuery->getAll();
124
125 $eagerLoadedModels = $this->eagerLoadedQuery
126 ->whereIn($this->foreignKey, array_column($models, 'id'))
127 ->getAll();
128
129 foreach($models as $model) {
130 $this->setEagerLoadedModels($model, array_filter($eagerLoadedModels, function($eagerLoadedModel) use ($model) {
131 return $eagerLoadedModel->{$this->foreignAttribute} === $model->id;
132 }));
133 }
134
135 return $models;
136 }
137
138 /**
139 * The cachedRelations property is protected and cannot be accessed directly.
140 * This method uses reflection to set the cachedRelations property on the model.
141 *
142 * @since 3.5.0
143 *
144 * @param Model $model
145 * @param array $eagerLoadedModels
146 */
147 protected function setEagerLoadedModels(Model $model, array $eagerLoadedModels): void
148 {
149 $property = $this->reflection
150 ->getParentClass()
151 ->getProperty('cachedRelations');
152 $property->setAccessible(true);
153
154 $cachedRelations = $property->getValue($model);
155 $cachedRelations[$this->relationshipKey] = $eagerLoadedModels;
156
157 $property->setValue($model, $cachedRelations);
158 }
159 }
160