Passed
Pull Request — master (#20)
by Kurita
02:06
created

EagerLoader::hasLimitOffset()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 2
nop 1
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 9 and the first side effect is on line 2.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
App::uses('CakeText', 'Utility');
3
4
/**
5
 * EagerLoader class
6
 *
7
 * @internal
8
 */
9
class EagerLoader {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
10
11
	private static $instances = array(); // @codingStandardsIgnoreLine
12
13
	private $id; // @codingStandardsIgnoreLine
14
15
	private $metas = array(); // @codingStandardsIgnoreLine
16
17
	private $containOptions = array(  // @codingStandardsIgnoreLine
18
		'conditions' => 1,
19
		'fields' => 1,
20
		'order' => 1,
21
		'limit' => 1,
22
		'offset' => 1,
23
	);
24
25
/**
26
 * Constructor
27
 */
28
	public function __construct() {
29
		ClassRegistry::init('EagerLoader.EagerLoaderModel');
30
31
		if (class_exists('CakeText')) {
32
			$this->id = CakeText::uuid();
33
		} else {
34
			App::uses('String', 'Utility');
35
			$this->id = String::uuid();
36
		}
37
	}
38
39
/**
40
 * Handles beforeFind event
41
 *
42
 * @param Model $model Model
43
 * @param array $query Query
44
 * @return array Modified query
45
 */
46
	public static function handleBeforeFind(Model $model, $query) {
47
		if (is_array($query)) {
48
			if (isset($query['contain'])) {
49
				if ($query['contain'] === false) {
50
					$query['recursive'] = -1;
51
				} else {
52
					$EagerLoader = new EagerLoader();
53
					$EagerLoader->collectGarbage($model);
54
					self::$instances[$EagerLoader->id] = $EagerLoader;
55
					$query = $EagerLoader->transformQuery($model, $query);
56
				}
57
			}
58
		}
59
		return $query;
60
	}
61
62
/**
63
 * Gabage collection
64
 *
65
 * @param Model $model Model
66
 * @return void
67
 */
68
	private function collectGarbage(Model $model) { // @codingStandardsIgnoreLine
69
		if (count(self::$instances) >= 1000) {
70
			array_shift(self::$instances);
71
		}
72
73
		$db = $model->getDataSource();
74
		if (!$db instanceof DboSource) {
0 ignored issues
show
Bug introduced by
The class DboSource does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
75
			return;
76
		}
77
78
		$class = get_class($db);
79
		$cache =& $class::$methodCache['fields'];
80
		if (!is_array($cache) || count($cache) >= 1000) {
81
			$cache = array();
82
		}
83
	}
84
85
/**
86
 * Handles afterFind event
87
 *
88
 * @param Model $model Model
89
 * @param array $results Results
90
 * @return array Modified results
91
 * @throws UnexpectedValueException
92
 */
93
	public static function handleAfterFind(Model $model, $results) {
94
		if (is_array($results)) {
95
			$id = Hash::get($results, '0.EagerLoaderModel.id');
96
			if ($id) {
97
				if (empty(self::$instances[$id])) {
98
					throw new UnexpectedValueException(sprintf('EagerLoader "%s" is not found', $id));
99
				}
100
101
				$EagerLoader = self::$instances[$id];
102
				unset(self::$instances[$id]);
103
104
				$results = $EagerLoader->transformResults($model, $results);
105
			}
106
		}
107
		return $results;
108
	}
109
110
/**
111
 * Modifies the passed query to fetch the top level attachable associations.
112
 *
113
 * @param Model $model Model
114
 * @param array $query Query
115
 * @return array Modified query
116
 */
117
	private function transformQuery(Model $model, array $query) { // @codingStandardsIgnoreLine
118
		$contain = $this->reformatContain($query['contain']);
119
		foreach ($contain['contain'] as $key => $val) {
120
			$this->parseContain($model, $key, $val);
121
		}
122
123
		$query = $this->attachAssociations($model, $model->alias, $query);
124
125
		$db = $model->getDataSource();
126
		$value = $db->value($this->id);
127
		$name = $db->name('EagerLoaderModel' . '__' . 'id');
128
		$query['fields'][] = "($value) AS $name";
129
		$query['callbacks'] = true;
130
131
		return $query;
132
	}
133
134
/**
135
 * Modifies the results
136
 *
137
 * @param Model $model Model
138
 * @param array $results Results
139
 * @return array Modified results
140
 */
141
	private  function transformResults(Model $model, array $results) { // @codingStandardsIgnoreLine
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
142
		foreach ($results as &$result) {
143
			unset($result['EagerLoaderModel']);
144
		}
145
		return $this->loadExternal($model, $model->alias, $results);
146
	}
147
148
/**
149
 * Modifies the query to fetch attachable associations.
150
 *
151
 * @param Model $model Model
152
 * @param string $path The target path of the model, such as 'User.Article'
153
 * @param array $query Query
154
 * @return array Modified query
155
 */
156
	private function attachAssociations(Model $model, $path, array $query) { // @codingStandardsIgnoreLine
157
		$query = $this->normalizeQuery($model, $query);
158
159
		foreach ($this->metas($path) as $meta) {
160
			extract($meta);
161
			if ($external) {
162
				$query = $this->addField($query, "$parentAlias.$parentKey");
163
			} else {
164
				$query = $this->buildJoinQuery($target, $query, 'LEFT', array("$parentAlias.$parentKey" => "$alias.$targetKey"), $options);
165
			}
166
		}
167
168
		$query['recursive'] = -1;
169
		$query['contain'] = false;
170
171
		return $query;
172
	}
173
174
/**
175
 * Fetches meta data
176
 *
177
 * @param string $path Path of the association
178
 * @return array
179
 */
180
	private function metas($path) { // @codingStandardsIgnoreLine
181
		if (isset($this->metas[$path])) {
182
			return $this->metas[$path];
183
		}
184
		return array();
185
	}
186
187
/**
188
 * Fetches external associations
189
 *
190
 * @param Model $model Model
191
 * @param string $path The target path of the external primary model, such as 'User.Article'
192
 * @param array $results The results of the parent model
193
 * @return array
194
 */
195
	protected function loadExternal(Model $model, $path, array $results) { // @codingStandardsIgnoreLine
196
		if ($results) {
197
			foreach ($this->metas($path) as $meta) {
198
				extract($meta);
199
				if ($external) {
200
					$results = $this->mergeExternalExternal($model, $results, $meta);
201
				} else {
202
					$results = $this->mergeInternalExternal($model, $results, $meta);
203
				}
204
			}
205
		}
206
		return $results;
207
	}
208
209
/**
210
 * Merges results of external associations of an external association
211
 *
212
 * @param Model $model Model
213
 * @param array $results Results
214
 * @param array $meta Meta data to be used for eager loading
215
 * @return array
216
 */
217
	private function mergeExternalExternal(Model $model, array $results, array $meta) { // @codingStandardsIgnoreLine
0 ignored issues
show
Unused Code introduced by
The parameter $model is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
218
		extract($meta);
219
220
		$db = $target->getDataSource();
221
222
		$assocAlias = $alias;
223
		$assocKey = $targetKey;
224
225
		$options = $this->attachAssociations($target, $aliasPath, $options);
226
		if ($has && $belong) {
227
			$assocAlias = $habtmAlias;
228
			$assocKey = $habtmParentKey;
229
230
			$options = $this->buildJoinQuery($habtm, $options, 'INNER', array(
231
				"$alias.$targetKey" => "$habtmAlias.$habtmTargetKey",
232
			), $options);
233
		}
234
235
		$options = $this->addField($options, "$assocAlias.$assocKey");
236
237
		$ids = Hash::extract($results, "{n}.$parentAlias.$parentKey");
238
		$ids = array_unique($ids);
239
240
		if (!empty($finderQuery)) {
241
			$assocResults = array();
242
			foreach ($ids as $id) {
243
				$eachQuery = str_replace('{$__cakeID__$}', $db->value($id), $finderQuery);
244
				$eachAssocResults = $db->fetchAll($eachQuery, $target->cacheQueries);
245
				$eachAssocResults = Hash::insert($eachAssocResults, "{n}.EagerLoaderModel.assoc_id", $id);
246
				$assocResults = array_merge($assocResults, $eachAssocResults);
247
			}
248
		} elseif ($this->hasLimitOffset($options)) {
249
			$assocResults = array();
250
			foreach ($ids as $id) {
251
				$eachOptions = $options;
252
				$eachOptions['conditions'][] = array("$assocAlias.$assocKey" => $id);
253
				$eachAssocResults = $db->read($target, $eachOptions);
254
				$eachAssocResults = Hash::insert($eachAssocResults, "{n}.EagerLoaderModel.assoc_id", $id);
255
				$assocResults = array_merge($assocResults, $eachAssocResults);
256
			}
257
		} else {
258
			$options['fields'][] = '(' . $db->name($assocAlias . '.' . $assocKey) . ') AS ' . $db->name('EagerLoaderModel' . '__' . 'assoc_id');
259
			$options['conditions'][] = array("$assocAlias.$assocKey" => $ids);
260
			$assocResults = $db->read($target, $options);
261
		}
262
263
		$assocResults = $this->filterResults($parent, $alias, $assocResults);
264
		$assocResults = $this->loadExternal($target, $aliasPath, $assocResults);
265
266
		if ($has && $belong) {
267
			foreach ($assocResults as &$assocResult) {
268
				$assocResult[$alias][$habtmAlias] = $assocResult[$habtmAlias];
269
				unset($assocResult[$habtmAlias]);
270
			}
271
			unset($assocResult);
272
		}
273
274
		foreach ($results as &$result) {
275
			if (!isset($result[$parentAlias][$parentKey])) {
276
				continue;
277
			}
278
279
			$assoc = array();
280
			foreach ($assocResults as $assocResult) {
281
				if ((string)$result[$parentAlias][$parentKey] === (string)$assocResult['EagerLoaderModel']['assoc_id']) {
282
					$assoc[] = $assocResult[$alias];
283
				}
284
			}
285
			if (!$many) {
286
				$assoc = $assoc ? current($assoc) : array();
287
			}
288
			$result = $this->mergeAssocResult($result, $assoc, $propertyPath);
289
		}
290
291
		return $results;
292
	}
293
294
/**
295
 * Merges results of external associations of an internal association
296
 *
297
 * @param Model $model Model
298
 * @param array $results Results
299
 * @param array $meta Meta data to be used for eager loading
300
 * @return array
301
 */
302
	private function mergeInternalExternal(Model $model, array $results, array $meta) { // @codingStandardsIgnoreLine
303
		extract($meta);
304
305
		$assocResults = array();
306
		foreach ($results as $n => &$result) {
307
			if ($result[$alias][$targetKey] === null) {
308
				// Remove NULL association created by LEFT JOIN
309
				if (empty($eager)) {
310
					$assocResults[$n] = array( $alias => array() );
311
				}
312
			} else {
313
				$assocResults[$n] = array( $alias => $result[$alias] );
314
			}
315
			unset($result[$alias]);
316
		}
317
		unset($result);
318
319
		if (!empty($eager) && !isset($model->$alias)) {
320
			$assocResults = $this->filterResults($parent, $alias, $assocResults);
321
		}
322
		$assocResults = $this->loadExternal($target, $aliasPath, $assocResults);
323
324
		foreach ($results as $n => &$result) {
325
			if (isset($assocResults[$n][$alias])) {
326
				$assoc = $assocResults[$n][$alias];
327
				$result = $this->mergeAssocResult($result, $assoc, $propertyPath);
328
			}
329
		}
330
		unset($result);
331
332
		return $results;
333
	}
334
335
/**
336
 * Merges associated result
337
 *
338
 * @param array $result Results
339
 * @param array $assoc Associated results
340
 * @param string $propertyPath Path of the results
341
 * @return array
342
 */
343
	private function mergeAssocResult(array $result, array $assoc, $propertyPath) { // @codingStandardsIgnoreLine
344
		return Hash::insert($result, $propertyPath, $assoc + (array)Hash::get($result, $propertyPath));
345
	}
346
347
/**
348
 * Reformat `contain` array
349
 *
350
 * @param array|string $contain The value of `contain` option of the query
351
 * @return array
352
 */
353
	private function reformatContain($contain) { // @codingStandardsIgnoreLine
354
		$result = array(
355
			'options' => array(),
356
			'contain' => array(),
357
		);
358
359
		$contain = (array)$contain;
360
		foreach ($contain as $key => $val) {
361
			if (is_int($key)) {
362
				$key = $val;
363
				$val = array();
364
			}
365
366
			if (!isset($this->containOptions[$key])) {
367
				if (strpos($key, '.') !== false) {
368
					$expanded = Hash::expand(array($key => $val));
369
					list($key, $val) = each($expanded);
370
				}
371
				$ref =& $result['contain'][$key];
372
				$ref = Hash::merge((array)$ref, $this->reformatContain($val));
373
			} else {
374
				$result['options'][$key] = $val;
375
			}
376
		}
377
378
		return $result;
379
	}
380
381
/**
382
 * Normalizes the query
383
 *
384
 * @param Model $model Model
385
 * @param array $query Query
386
 * @return array Normalized query
387
 */
388
	private function normalizeQuery(Model $model, array $query) { // @codingStandardsIgnoreLine
389
		$db = $model->getDataSource();
390
391
		$query += array(
392
			'fields' => array(),
393
			'conditions' => array(),
394
			'order' => array()
395
		);
396
397
		if (!$query['fields']) {
398
			$query['fields'] = $db->fields($model, null, array(), false);
399
		}
400
401
		$query['fields'] = (array)$query['fields'];
402
		foreach ($query['fields'] as &$field) {
403
			if ($model->isVirtualField($field)) {
404
				$fields = $db->fields($model, null, array($field), false);
405
				$field = $fields[0];
406
			} else {
407
				$field = $this->normalizeField($model, $field);
408
			}
409
		}
410
		unset($field);
411
412
		$query['conditions'] = (array)$query['conditions'];
413
		foreach ($query['conditions'] as $key => $val) {
414
			if ($model->hasField($key)) {
415
				unset($query['conditions'][$key]);
416
				$key = $this->normalizeField($model, $key);
417
				$query['conditions'][] = array($key => $val);
418
			} elseif ($model->isVirtualField($key)) {
419
				unset($query['conditions'][$key]);
420
				$conditions = $db->conditionKeysToString(array($key => $val), true, $model);
421
				$query['conditions'][] = $db->expression($conditions[0]);
422
			}
423
		}
424
425
		$order = array();
426
		foreach ((array)$query['order'] as $key => $val) {
427
			if (is_int($key)) {
428
				$val = $this->normalizeField($model, $val);
429
			} else {
430
				$key = $this->normalizeField($model, $key);
431
			}
432
			$order += array($key => $val);
433
		}
434
		$query['order'] = $order;
435
436
		return $query;
437
	}
438
439
/**
440
 * Normalize field
441
 *
442
 * @param Model $model Model
443
 * @param string $field Name of the field
444
 * @return string
445
 */
446
	private function normalizeField(Model $model, $field) { // @codingStandardsIgnoreLine
447
		if ($model->hasField($field)) {
448
			$field = $model->alias . '.' . $field;
449
		} elseif ($model->isVirtualField($field)) {
450
			$db = $model->getDataSource();
451
			$field = $model->getVirtualField($field);
452
			$field = $db->dispatchMethod('_quoteFields', array($field));
453
			$field = '(' . $field . ')';
454
		}
455
		return $field;
456
	}
457
458
/**
459
 * Modifies the query to apply joins.
460
 *
461
 * @param Model $target Model to be joined
462
 * @param array $query Query
463
 * @param string $joinType The type for join
464
 * @param array $keys Key fields being used for join
465
 * @param array $options Extra options for join
466
 * @return array Modified query
467
 */
468
	private function buildJoinQuery(Model $target, array $query, $joinType, array $keys, array $options) { // @codingStandardsIgnoreLine
469
		$db = $target->getDataSource();
470
471
		$options = $this->normalizeQuery($target, $options);
472
		$query['fields'] = array_merge($query['fields'], $options['fields']);
473
		$query = $this->normalizeQuery($target, $query);
474
475
		foreach ($keys as $lhs => $rhs) {
476
			$query = $this->addField($query, $lhs);
477
			$query = $this->addField($query, $rhs);
478
			$options['conditions'][] = array($lhs => $db->identifier($rhs));
479
		}
480
481
		$query['joins'][] = array(
482
			'type' => $joinType,
483
			'table' => $target,
484
			'alias' => $target->alias,
485
			'conditions' => $options['conditions'],
486
		);
487
		return $query;
488
	}
489
490
/**
491
 * Adds a field into the `fields` option of the query
492
 *
493
 * @param array $query Query
494
 * @param string $field Name of the field
495
 * @return Modified query
496
 */
497
	private function addField(array $query, $field) { // @codingStandardsIgnoreLine
498
		if (!in_array($field, $query['fields'], true)) {
499
			$query['fields'][] = $field;
500
		}
501
		return $query;
502
	}
503
504
/**
505
 * Parse the `contain` option of the query recursively
506
 *
507
 * @param Model $parent Parent model of the contained model
508
 * @param string $alias Alias of the contained model
509
 * @param array $contain Reformatted `contain` option for the deep associations
510
 * @param array|null $context Context
511
 * @return array
512
 * @throws InvalidArgumentException
513
 */
514
	private function parseContain(Model $parent, $alias, array $contain, $context = null) { // @codingStandardsIgnoreLine
515
		if ($context === null) {
516
			$context = array(
517
				'root' => $parent->alias,
518
				'aliasPath' => $parent->alias,
519
				'propertyPath' => '',
520
				'forceExternal' => false,
521
			);
522
		}
523
524
		$aliasPath = $context['aliasPath'] . '.' . $alias;
525
		$propertyPath = ($context['propertyPath'] ? $context['propertyPath'] . '.' : '') . $alias;
526
527
		$types = $parent->getAssociated();
528
		if (!isset($types[$alias])) {
529
			throw new InvalidArgumentException(sprintf('Model "%s" is not associated with model "%s"', $parent->alias, $alias));
530
		}
531
532
		$parentAlias = $parent->alias;
533
		$target = $parent->$alias;
534
		$type = $types[$alias];
535
		$relation = $parent->{$type}[$alias];
536
537
		$options = $contain['options'] + array_intersect_key(Hash::filter($relation), $this->containOptions);
538
539
		$has = (stripos($type, 'has') !== false);
540
		$many = (stripos($type, 'many') !== false);
541
		$belong = (stripos($type, 'belong') !== false);
542
543
		if ($has && $belong) {
544
			$parentKey = $parent->primaryKey;
545
			$targetKey = $target->primaryKey;
546
			$habtmAlias = $relation['with'];
547
			$habtm = $parent->$habtmAlias;
548
			$habtmParentKey = $relation['foreignKey'];
549
			$habtmTargetKey = $relation['associationForeignKey'];
550
		} elseif ($has) {
551
			$parentKey = $parent->primaryKey;
552
			$targetKey = $relation['foreignKey'];
553
		} else {
554
			$parentKey = $relation['foreignKey'];
555
			$targetKey = $target->primaryKey;
556
		}
557
558
		if (!empty($relation['external'])) {
559
			$external = true;
560
		}
561
562
		if (!empty($relation['finderQuery'])) {
563
			$finderQuery = $relation['finderQuery'];
564
		}
565
566
		$meta = compact(
567
			'alias', 'parent', 'target',
568
			'parentAlias', 'parentKey',
569
			'targetKey', 'aliasPath', 'propertyPath',
570
			'options', 'has', 'many', 'belong', 'external', 'finderQuery',
571
			'habtm', 'habtmAlias', 'habtmParentKey', 'habtmTargetKey'
572
		);
573
574
		if ($this->isExternal($context, $meta)) {
575
			$meta['propertyPath'] = ($context['propertyPath'] ? $parentAlias . '.' : '') . $alias;
576
			$meta['external'] = true;
577
578
			$context['root'] = $aliasPath;
579
			$context['propertyPath'] = $alias;
580
581
			$path = $context['aliasPath'];
582
		} else {
583
			$meta['external'] = false;
584
			if ($context['root'] !== $context['aliasPath']) {
585
				$meta['eager'] = true;
586
			}
587
588
			$context['propertyPath'] = $propertyPath;
589
590
			$path = $context['root'];
591
		}
592
593
		$this->metas[$path][] = $meta;
594
595
		$context['aliasPath'] = $aliasPath;
596
		$context['forceExternal'] = !empty($finderQuery);
597
598
		foreach ($contain['contain'] as $key => $val) {
599
			$this->parseContain($target, $key, $val, $context);
600
		}
601
602
		return $this->metas;
603
	}
604
605
/**
606
 * Returns whether the target is external or not
607
 *
608
 * @param array $context Context
609
 * @param array $meta Meta data to be used for eager loading
610
 * @return bool
611
 */
612
	private function isExternal(array $context, array $meta) { // @codingStandardsIgnoreLine
613
		extract($meta);
614
615
		if ($parent->useDbConfig !== $target->useDbConfig) {
616
			return true;
617
		}
618
		if (!empty($external)) {
619
			return true;
620
		}
621
		if (!empty($many)) {
622
			return true;
623
		}
624
		if (!empty($finderQuery)) {
625
			return true;
626
		}
627
		if ($this->hasLimitOffset($options)) {
628
			return true;
629
		}
630
		if ($context['forceExternal']) {
631
			return true;
632
		}
633
634
		$metas = $this->metas($context['root']);
635
		$aliases = Hash::extract($metas, '{n}.alias');
636
		if (in_array($alias, $aliases, true)) {
637
			return true;
638
		}
639
640
		return false;
641
	}
642
643
/**
644
 * Returns where `limit` or `offset` option exists
645
 *
646
 * @param array $options Options
647
 * @return bool
648
 */
649
	private function hasLimitOffset($options) { // @codingStandardsIgnoreLine
650
		return !empty($options['limit']) || !empty($options['offset']);
651
	}
652
653
/**
654
 * Triggers afterFind() method
655
 *
656
 * @param Model $parent Model
657
 * @param string $alias Alias
658
 * @param array $results Results
659
 * @return array
660
 */
661
	private function filterResults(Model $parent, $alias, array $results) { // @codingStandardsIgnoreLine
662
		$db = $parent->getDataSource();
0 ignored issues
show
Unused Code introduced by
$db is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
663
664
		$target = $parent->$alias;
665
666
		foreach ($results as $key => &$result) {
667
			$data = $target->afterFind(array(array($alias => $result[$alias])), false);
668
			if (isset($data[0][$alias])) {
669
				$result[$alias] = $data[0][$alias];
670
			} else {
671
				unset($results[$key]);
672
			}
673
		}
674
675
		return $results;
676
	}
677
}
678