blob: 1b108fd914dcd77b4827fefcfb454450cc7b6a9f [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3namespace LdapRecord\Models\Relations;
4
5use LdapRecord\Models\Entry;
6use LdapRecord\Models\Model;
7use LdapRecord\Query\Collection;
8use LdapRecord\Query\Model\Builder;
9
10/**
11 * @method bool exists($models = null) Determine if the relation contains all of the given models, or any models
12 * @method bool contains($models) Determine if any of the given models are contained in the relation
13 */
14abstract class Relation
15{
16 /**
17 * The underlying LDAP query.
18 *
19 * @var Builder
20 */
21 protected $query;
22
23 /**
24 * The parent model instance.
25 *
26 * @var Model
27 */
28 protected $parent;
29
30 /**
31 * The related models.
32 *
33 * @var array
34 */
35 protected $related;
36
37 /**
38 * The relation key.
39 *
40 * @var string
41 */
42 protected $relationKey;
43
44 /**
45 * The foreign key.
46 *
47 * @var string
48 */
49 protected $foreignKey;
50
51 /**
52 * The default relation model.
53 *
54 * @var string
55 */
56 protected $default = Entry::class;
57
58 /**
59 * Constructor.
60 *
61 * @param Builder $query
62 * @param Model $parent
63 * @param mixed $related
64 * @param string $relationKey
65 * @param string $foreignKey
66 */
67 public function __construct(Builder $query, Model $parent, $related, $relationKey, $foreignKey)
68 {
69 $this->query = $query;
70 $this->parent = $parent;
71 $this->related = (array) $related;
72 $this->relationKey = $relationKey;
73 $this->foreignKey = $foreignKey;
74
75 $this->initRelation();
76 }
77
78 /**
79 * Handle dynamic method calls to the relationship.
80 *
81 * @param string $method
82 * @param array $parameters
83 *
84 * @return mixed
85 */
86 public function __call($method, $parameters)
87 {
88 if (in_array($method, ['exists', 'contains'])) {
89 return $this->get('objectclass')->$method(...$parameters);
90 }
91
92 $result = $this->query->$method(...$parameters);
93
94 if ($result === $this->query) {
95 return $this;
96 }
97
98 return $result;
99 }
100
101 /**
102 * Get the results of the relationship.
103 *
104 * @return Collection
105 */
106 abstract public function getResults();
107
108 /**
109 * Execute the relationship query.
110 *
111 * @param array|string $columns
112 *
113 * @return Collection
114 */
115 public function get($columns = ['*'])
116 {
117 return $this->getResultsWithColumns($columns);
118 }
119
120 /**
121 * Get the results of the relationship while selecting the given columns.
122 *
123 * If the query columns are empty, the given columns are applied.
124 *
125 * @param array $columns
126 *
127 * @return Collection
128 */
129 protected function getResultsWithColumns($columns)
130 {
131 if (is_null($this->query->columns)) {
132 $this->query->select($columns);
133 }
134
135 return $this->getResults();
136 }
137
138 /**
139 * Get the first result of the relationship.
140 *
141 * @param array|string $columns
142 *
143 * @return Model|null
144 */
145 public function first($columns = ['*'])
146 {
147 return $this->get($columns)->first();
148 }
149
150 /**
151 * Prepare the relation query.
152 *
153 * @return static
154 */
155 public function initRelation()
156 {
157 $this->query
158 ->clearFilters()
159 ->withoutGlobalScopes()
160 ->setModel($this->getNewDefaultModel());
161
162 return $this;
163 }
164
165 /**
166 * Get the underlying query for the relation.
167 *
168 * @return Builder
169 */
170 public function getQuery()
171 {
172 return $this->query;
173 }
174
175 /**
176 * Get the parent model of the relation.
177 *
178 * @return Model
179 */
180 public function getParent()
181 {
182 return $this->parent;
183 }
184
185 /**
186 * Get the relation attribute key.
187 *
188 * @return string
189 */
190 public function getRelationKey()
191 {
192 return $this->relationKey;
193 }
194
195 /**
196 * Get the related model classes for the relation.
197 *
198 * @return array
199 */
200 public function getRelated()
201 {
202 return $this->related;
203 }
204
205 /**
206 * Get the relation foreign attribute key.
207 *
208 * @return string
209 */
210 public function getForeignKey()
211 {
212 return $this->foreignKey;
213 }
214
215 /**
216 * Get the class name of the default model.
217 *
218 * @return string
219 */
220 public function getDefaultModel()
221 {
222 return $this->default;
223 }
224
225 /**
226 * Get a new instance of the default model on the relation.
227 *
228 * @return Model
229 */
230 public function getNewDefaultModel()
231 {
232 $model = new $this->default();
233
234 $model->setConnection($this->parent->getConnectionName());
235
236 return $model;
237 }
238
239 /**
240 * Get the foreign model by the given value.
241 *
242 * @param string $value
243 *
244 * @return Model|null
245 */
246 protected function getForeignModelByValue($value)
247 {
248 return $this->foreignKeyIsDistinguishedName()
249 ? $this->query->find($value)
250 : $this->query->findBy($this->foreignKey, $value);
251 }
252
253 /**
254 * Returns the escaped foreign key value for use in an LDAP filter from the model.
255 *
256 * @param Model $model
257 *
258 * @return string
259 */
260 protected function getEscapedForeignValueFromModel(Model $model)
261 {
262 return $this->query->escape(
263 $this->getForeignValueFromModel($model)
264 )->both();
265 }
266
267 /**
268 * Get the relation parents foreign value.
269 *
270 * @return string
271 */
272 protected function getParentForeignValue()
273 {
274 return $this->getForeignValueFromModel($this->parent);
275 }
276
277 /**
278 * Get the foreign key value from the model.
279 *
280 * @param Model $model
281 *
282 * @return string
283 */
284 protected function getForeignValueFromModel(Model $model)
285 {
286 return $this->foreignKeyIsDistinguishedName()
287 ? $model->getDn()
288 : $this->getFirstAttributeValue($model, $this->foreignKey);
289 }
290
291 /**
292 * Get the first attribute value from the model.
293 *
294 * @param Model $model
295 * @param string $attribute
296 *
297 * @return string|null
298 */
299 protected function getFirstAttributeValue(Model $model, $attribute)
300 {
301 return $model->getFirstAttribute($attribute);
302 }
303
304 /**
305 * Transforms the results by converting the models into their related.
306 *
307 * @param Collection $results
308 *
309 * @return Collection
310 */
311 protected function transformResults(Collection $results)
312 {
313 $related = [];
314
315 foreach ($this->related as $relation) {
316 $related[$relation] = $relation::$objectClasses;
317 }
318
319 return $results->transform(function (Model $entry) use ($related) {
320 $model = $this->determineModelFromRelated($entry, $related);
321
322 return class_exists($model) ? $entry->convert(new $model()) : $entry;
323 });
324 }
325
326 /**
327 * Determines if the foreign key is a distinguished name.
328 *
329 * @return bool
330 */
331 protected function foreignKeyIsDistinguishedName()
332 {
333 return in_array($this->foreignKey, ['dn', 'distinguishedname']);
334 }
335
336 /**
337 * Determines the model from the given relations.
338 *
339 * @param Model $model
340 * @param array $related
341 *
342 * @return string|bool
343 */
344 protected function determineModelFromRelated(Model $model, array $related)
345 {
346 // We must normalize all the related models object class
347 // names to the same case so we are able to properly
348 // determine the owning model from search results.
349 return array_search(
350 $this->normalizeObjectClasses($model->getObjectClasses()),
351 array_map([$this, 'normalizeObjectClasses'], $related)
352 );
353 }
354
355 /**
356 * Sort and normalize the object classes.
357 *
358 * @param array $classes
359 *
360 * @return array
361 */
362 protected function normalizeObjectClasses($classes)
363 {
364 sort($classes);
365
366 return array_map('strtolower', $classes);
367 }
368}