Total Complexity | 47 |
Total Lines | 352 |
Duplicated Lines | 0 % |
Changes | 3 | ||
Bugs | 0 | Features | 2 |
Complex classes like JoinableLoader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use JoinableLoader, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
27 | abstract class JoinableLoader extends AbstractLoader implements JoinableInterface |
||
28 | { |
||
29 | use ColumnsTrait; |
||
30 | use ScopeTrait; |
||
31 | |||
32 | /** |
||
33 | * Default set of relation options. Child implementation might defined their of default options. |
||
34 | * |
||
35 | * @var array |
||
36 | */ |
||
37 | protected $options = [ |
||
38 | // load relation data |
||
39 | 'load' => false, |
||
40 | |||
41 | // true or instance to enable, false or null to disable |
||
42 | 'scope' => true, |
||
43 | |||
44 | // scope to be used for the relation |
||
45 | 'method' => null, |
||
46 | |||
47 | // load method, see AbstractLoader constants |
||
48 | 'minify' => true, |
||
49 | |||
50 | // when true all loader columns will be minified (only for loading) |
||
51 | 'as' => null, |
||
52 | |||
53 | // table alias |
||
54 | 'using' => null, |
||
55 | |||
56 | // alias used by another relation |
||
57 | 'where' => null, |
||
58 | |||
59 | // where conditions (if any) |
||
60 | ]; |
||
61 | |||
62 | /** @var string */ |
||
63 | protected $name; |
||
64 | |||
65 | /** @var array */ |
||
66 | protected $schema; |
||
67 | |||
68 | /** |
||
69 | * @param ORMInterface $orm |
||
70 | * @param string $name |
||
71 | * @param string $target |
||
72 | * @param array $schema |
||
73 | */ |
||
74 | public function __construct(ORMInterface $orm, string $name, string $target, array $schema) |
||
75 | { |
||
76 | parent::__construct($orm, $target); |
||
77 | |||
78 | $this->name = $name; |
||
79 | $this->schema = $schema; |
||
80 | } |
||
81 | |||
82 | /** |
||
83 | * Relation table alias. |
||
84 | * |
||
85 | * @return string |
||
86 | */ |
||
87 | public function getAlias(): string |
||
88 | { |
||
89 | if ($this->options['using'] !== null) { |
||
90 | //We are using another relation (presumably defined by with() to load data). |
||
91 | return $this->options['using']; |
||
92 | } |
||
93 | |||
94 | if ($this->options['as'] !== null) { |
||
95 | return $this->options['as']; |
||
96 | } |
||
97 | |||
98 | throw new LoaderException('Unable to resolve loader alias'); |
||
99 | } |
||
100 | |||
101 | /** |
||
102 | * {@inheritdoc} |
||
103 | */ |
||
104 | public function withContext(LoaderInterface $parent, array $options = []): LoaderInterface |
||
105 | { |
||
106 | $options = $this->prepareOptions($options); |
||
107 | |||
108 | /** |
||
109 | * @var AbstractLoader $parent |
||
110 | * @var self $loader |
||
111 | */ |
||
112 | $loader = parent::withContext($parent, $options); |
||
113 | |||
114 | if ($loader->getSource()->getDatabase() !== $parent->getSource()->getDatabase()) { |
||
115 | if ($loader->isJoined()) { |
||
116 | throw new LoaderException('Unable to join tables located in different databases'); |
||
117 | } |
||
118 | |||
119 | // loader is not joined, let's make sure that POSTLOAD is used |
||
120 | if ($this->isLoaded()) { |
||
121 | $loader->options['method'] = self::POSTLOAD; |
||
122 | } |
||
123 | } |
||
124 | |||
125 | //Calculate table alias |
||
126 | $loader->options['as'] = $loader->calculateAlias($parent); |
||
127 | |||
128 | if (array_key_exists('scope', $options)) { |
||
129 | if ($loader->options['scope'] instanceof ScopeInterface) { |
||
130 | $loader->setScope($loader->options['scope']); |
||
131 | } elseif (is_string($loader->options['scope'])) { |
||
132 | $loader->setScope($this->orm->getFactory()->make($loader->options['scope'])); |
||
133 | } |
||
134 | } else { |
||
135 | $loader->setScope($this->getSource()->getConstrain()); |
||
|
|||
136 | } |
||
137 | |||
138 | if ($loader->isLoaded()) { |
||
139 | foreach ($loader->getEagerRelations() as $relation) { |
||
140 | $loader->loadRelation($relation, [], false, true); |
||
141 | } |
||
142 | } |
||
143 | |||
144 | return $loader; |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * {@inheritdoc} |
||
149 | */ |
||
150 | public function loadData(AbstractNode $node): void |
||
151 | { |
||
152 | if ($this->isJoined() || !$this->isLoaded()) { |
||
153 | // load data for all nested relations |
||
154 | parent::loadData($node); |
||
155 | |||
156 | return; |
||
157 | } |
||
158 | |||
159 | $references = $node->getReferences(); |
||
160 | if ($references === []) { |
||
161 | // nothing found at parent level, unable to create sub query |
||
162 | return; |
||
163 | } |
||
164 | |||
165 | //Ensure all nested relations |
||
166 | $statement = $this->configureQuery($this->initQuery(), $references)->run(); |
||
167 | |||
168 | foreach ($statement->fetchAll(StatementInterface::FETCH_NUM) as $row) { |
||
169 | try { |
||
170 | $node->parseRow(0, $row); |
||
171 | } catch (\Throwable $e) { |
||
172 | throw $e; |
||
173 | } |
||
174 | } |
||
175 | |||
176 | $statement->close(); |
||
177 | |||
178 | // load data for all nested relations |
||
179 | parent::loadData($node); |
||
180 | } |
||
181 | |||
182 | /** |
||
183 | * Indicated that loaded must generate JOIN statement. |
||
184 | * |
||
185 | * @return bool |
||
186 | */ |
||
187 | public function isJoined(): bool |
||
194 | } |
||
195 | |||
196 | /** |
||
197 | * Indication that loader want to load data. |
||
198 | * |
||
199 | * @return bool |
||
200 | */ |
||
201 | public function isLoaded(): bool |
||
202 | { |
||
203 | return $this->options['load'] || in_array($this->getMethod(), [self::INLOAD, self::POSTLOAD]); |
||
204 | } |
||
205 | |||
206 | /** |
||
207 | * Configure query with conditions, joins and columns. |
||
208 | * |
||
209 | * @param SelectQuery $query |
||
210 | * @param array $outerKeys Set of OUTER_KEY values collected by parent loader. |
||
211 | * |
||
212 | * @return SelectQuery |
||
213 | */ |
||
214 | public function configureQuery(SelectQuery $query, array $outerKeys = []): SelectQuery |
||
215 | { |
||
216 | if ($this->isLoaded()) { |
||
217 | if ($this->isJoined()) { |
||
218 | // mounting the columns to parent query |
||
219 | $this->mountColumns($query, $this->options['minify']); |
||
220 | } else { |
||
221 | // this is initial set of columns (remove all existed) |
||
222 | $this->mountColumns($query, $this->options['minify'], '', true); |
||
223 | } |
||
224 | |||
225 | if ($this->options['load'] instanceof ScopeInterface) { |
||
226 | $this->options['load']->apply($this->makeQueryBuilder($query)); |
||
227 | } |
||
228 | |||
229 | if (is_callable($this->options['load'], true)) { |
||
230 | ($this->options['load'])($this->makeQueryBuilder($query)); |
||
231 | } |
||
232 | } |
||
233 | |||
234 | return parent::configureQuery($query); |
||
235 | } |
||
236 | |||
237 | public function isDataDuplicationPossible(): bool |
||
238 | { |
||
239 | $outerKey = $this->schema[Relation::OUTER_KEY]; |
||
240 | $indexes = $this->orm->getIndexes($this->target); |
||
241 | |||
242 | if (!\in_array($outerKey, $indexes, true)) { |
||
243 | return true; |
||
244 | } |
||
245 | |||
246 | return parent::isDataDuplicationPossible(); |
||
247 | } |
||
248 | |||
249 | /** |
||
250 | * @param SelectQuery $query |
||
251 | * |
||
252 | * @return SelectQuery |
||
253 | */ |
||
254 | protected function applyConstrain(SelectQuery $query): SelectQuery |
||
261 | } |
||
262 | |||
263 | /** |
||
264 | * Get load method. |
||
265 | * |
||
266 | * @return int |
||
267 | */ |
||
268 | protected function getMethod(): int |
||
269 | { |
||
270 | return $this->options['method']; |
||
271 | } |
||
272 | |||
273 | /** |
||
274 | * Create relation specific select query. |
||
275 | * |
||
276 | * @return SelectQuery |
||
277 | */ |
||
278 | protected function initQuery(): SelectQuery |
||
279 | { |
||
280 | return $this->getSource()->getDatabase()->select()->from($this->getJoinTable()); |
||
281 | } |
||
282 | |||
283 | /** |
||
284 | * Calculate table alias. |
||
285 | * |
||
286 | * @param AbstractLoader $parent |
||
287 | * |
||
288 | * @return string |
||
289 | */ |
||
290 | protected function calculateAlias(AbstractLoader $parent): string |
||
291 | { |
||
292 | if (!empty($this->options['as'])) { |
||
293 | return $this->options['as']; |
||
294 | } |
||
295 | |||
296 | $alias = $parent->getAlias() . '_' . $this->name; |
||
297 | |||
298 | if ($this->isLoaded() && $this->isJoined()) { |
||
299 | // to avoid collisions |
||
300 | return 'l_' . $alias; |
||
301 | } |
||
302 | |||
303 | return $alias; |
||
304 | } |
||
305 | |||
306 | /** |
||
307 | * Generate sql identifier using loader alias and value from relation definition. Key name to be |
||
308 | * fetched from schema. |
||
309 | * |
||
310 | * Example: |
||
311 | * $this->getKey(Relation::OUTER_KEY); |
||
312 | * |
||
313 | * @param mixed $key |
||
314 | * |
||
315 | * @return string|null |
||
316 | */ |
||
317 | protected function localKey($key): ?string |
||
318 | { |
||
319 | if (empty($this->schema[$key])) { |
||
320 | return null; |
||
321 | } |
||
322 | |||
323 | return $this->getAlias() . '.' . $this->fieldAlias($this->schema[$key]); |
||
324 | } |
||
325 | |||
326 | /** |
||
327 | * Get parent identifier based on relation configuration key. |
||
328 | * |
||
329 | * @param mixed $key |
||
330 | * |
||
331 | * @return string |
||
332 | */ |
||
333 | protected function parentKey($key): string |
||
334 | { |
||
335 | return $this->parent->getAlias() . '.' . $this->parent->fieldAlias($this->schema[$key]); |
||
336 | } |
||
337 | |||
338 | /** |
||
339 | * @return string |
||
340 | */ |
||
341 | protected function getJoinMethod(): string |
||
342 | { |
||
343 | return $this->getMethod() == self::JOIN ? 'INNER' : 'LEFT'; |
||
344 | } |
||
345 | |||
346 | /** |
||
347 | * Joined table name and alias. |
||
348 | * |
||
349 | * @return string |
||
350 | */ |
||
351 | protected function getJoinTable(): string |
||
354 | } |
||
355 | |||
356 | /** |
||
357 | * Relation columns. |
||
358 | * |
||
359 | * @return array |
||
360 | */ |
||
361 | protected function getColumns(): array |
||
364 | } |
||
365 | |||
366 | /** |
||
367 | * @param SelectQuery $query |
||
368 | * |
||
369 | * @return QueryBuilder |
||
370 | */ |
||
371 | private function makeQueryBuilder(SelectQuery $query): QueryBuilder |
||
379 | } |
||
380 | } |
||
381 |
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.