Completed
Push — master ( 4e9dd5...abf073 )
by Peter
05:53
created

Finder::findByAttributes()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 1
crap 2
1
<?php
2
3
/**
4
 * This software package is licensed under AGPL or Commercial license.
5
 *
6
 * @package maslosoft/mangan
7
 * @licence AGPL or Commercial
8
 * @copyright Copyright (c) Piotr Masełkowski <[email protected]>
9
 * @copyright Copyright (c) Maslosoft
10
 * @copyright Copyright (c) Others as mentioned in code
11
 * @link http://maslosoft.com/mangan/
12
 */
13
14
namespace Maslosoft\Mangan;
15
16
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
17
use Maslosoft\Mangan\Exceptions\ManganException;
18
use Maslosoft\Mangan\Helpers\FinderEvents;
19
use Maslosoft\Mangan\Helpers\PkManager;
20
use Maslosoft\Mangan\Interfaces\CriteriaInterface;
21
use Maslosoft\Mangan\Interfaces\EntityManagerInterface;
22
use Maslosoft\Mangan\Interfaces\FinderInterface;
23
use Maslosoft\Mangan\Interfaces\ScenariosInterface;
24
use Maslosoft\Mangan\Meta\ManganMeta;
25
use Maslosoft\Mangan\Transformers\RawArray;
26
use MongoCursor;
27
28
/**
29
 * Finder
30
 *
31
 * @author Piotr Maselkowski <pmaselkowski at gmail.com>
32
 */
33
class Finder implements FinderInterface
34
{
35
36
	/**
37
	 * Model
38
	 * @var AnnotatedInterface
39
	 */
40
	public $model = null;
41
42
	/**
43
	 * Mangan instance
44
	 * @var Mangan
45
	 */
46
	private $mn = null;
47
48
	/**
49
	 * Entity manager instance
50
	 * @var EntityManagerInterface
51
	 */
52
	private $em = null;
53
54
	/**
55
	 * Scope manager instance
56
	 * @var ScopeManager
57
	 */
58
	private $sm = null;
59
60
	/**
61
	 * Finder criteria
62
	 * @var Criteria
63
	 */
64
	private $_criteria = null;
65
66
	/**
67
	 * Whenever to use corsors
68
	 * @var bool
69
	 */
70
	private $_useCursor = false;
71
72
	/**
73
	 * Constructor
74
	 *
75
	 * @param object $model Model instance
76
	 * @param EntityManagerInterface $em
77
	 * @param Mangan $mangan
78
	 */
79 69
	public function __construct($model, $em = null, $mangan = null)
80
	{
81 69
		$this->model = $model;
82 69
		$this->sm = new ScopeManager($model);
83 69
		if (null === $mangan)
84
		{
85 69
			$mangan = Mangan::fromModel($model);
86
		}
87 69
		$this->em = $em ?: EntityManager::create($model, $mangan);
88 69
		$this->mn = $mangan;
89 69
		$this->withCursor($this->mn->useCursor);
90 69
	}
91
92
	/**
93
	 * Create model related finder.
94
	 * This will create customized finder if defined in model with Finder annotation.
95
	 * If no custom finder is defined this will return default Finder.
96
	 *
97
	 * @param AnnotatedInterface $model
98
	 * @param EntityManagerInterface $em
99
	 * @param Mangan $mangan
100
	 * @return FinderInterface
101
	 */
102 9
	public static function create(AnnotatedInterface $model, $em = null, Mangan $mangan = null)
103
	{
104 9
		$finderClass = ManganMeta::create($model)->type()->finder ?: static::class;
105 9
		return new $finderClass($model, $em, $mangan);
106
	}
107
108
	/**
109
	 * Finds a single Document with the specified condition.
110
	 *
111
	 * @param array|CriteriaInterface $criteria query criteria.
112
	 * @return AnnotatedInterface
113
	 * @Ignored
114
	 */
115 52
	public function find($criteria = null)
116
	{
117 52
		if (FinderEvents::beforeFind($this->model))
118
		{
119 52
			$criteria = $this->sm->apply($criteria);
120 52
			$criteria->decorateWith($this->model);
121 52
			$data = $this->em->getCollection()->findOne($criteria->getConditions(), $criteria->getSelect());
122 52
			return $this->populateRecord($data);
123
		}
124
		return null;
125
	}
126
127
	/**
128
	 * Finds document with the specified primary key. Primary key by default
129
	 * is defined by `_id` field. But could be any other. For simple (one column)
130
	 * keys use it's value.
131
	 *
132
	 * For composite use key-value with column names as keys
133
	 * and values for values.
134
	 *
135
	 * Example for simple pk:
136
	 * ```php
137
	 * $pk = '51b616fcc0986e30026d0748'
138
	 * ```
139
	 *
140
	 * Composite pk:
141
	 * ```php
142
	 * $pk = [
143
	 * 		'mainPk' => 1,
144
	 * 		'secondaryPk' => 2
145
	 * ];
146
	 * ```
147
	 *
148
	 * @param mixed $pkValue primary key value. Use array for composite key.
149
	 * @param array|CriteriaInterface $criteria
150
	 * @return AnnotatedInterface|null
151
	 * @Ignored
152
	 */
153 40
	public function findByPk($pkValue, $criteria = null)
154
	{
155
156 40
		$pkCriteria = new Criteria($criteria);
157 40
		$pkCriteria->decorateWith($this->model);
158 40
		$pkCriteria->mergeWith(PkManager::prepare($this->model, $pkValue));
159 40
		return $this->find($pkCriteria);
160
	}
161
162
	/**
163
	 * Finds document with the specified attributes.
164
	 * Attributes should be specified as key-value pairs.
165
	 * This allows easier syntax for simple queries.
166
	 *
167
	 * Example:
168
	 * ```php
169
	 * $attributes = [
170
	 * 		'name' => 'John',
171
	 * 		'title' => 'dr'
172
	 * ];
173
	 * ```
174
	 *
175
	 * @param mixed[] Array of stributes and values in form of ['attributeName' => 'value']
176
	 * @return AnnotatedInterface|null
177
	 */
178 2
	public function findByAttributes(array $attributes)
179
	{
180 2
		$criteria = new Criteria();
181 2
		$criteria->decorateWith($this->model);
182 2
		foreach ($attributes as $name => $value)
183
		{
184 2
			$criteria->addCond($name, '==', $value);
185
		}
186 2
		return $this->find($criteria);
187
	}
188
189
	/**
190
	 * Finds all documents satisfying the specified condition.
191
	 * See {@link find()} for detailed explanation about $condition and $params.
192
	 *
193
	 * @param array|CriteriaInterface $criteria query criteria.
194
	 * @return AnnotatedInterface[]|Cursor
195
	 */
196 18
	public function findAll($criteria = null)
197
	{
198 18
		if (FinderEvents::beforeFind($this->model))
199
		{
200 18
			$criteria = $this->sm->apply($criteria);
201 18
			$criteria->decorateWith($this->model);
202 18
			$cursor = $this->em->getCollection()->find($criteria->getConditions());
203
204 18
			if ($criteria->getSort() !== null)
205
			{
206 18
				$cursor->sort($criteria->getSort());
207
			}
208 18
			if ($criteria->getLimit() !== null)
209
			{
210 1
				$cursor->limit($criteria->getLimit());
211
			}
212 18
			if ($criteria->getOffset() !== null)
213
			{
214 1
				$cursor->skip($criteria->getOffset());
215
			}
216 18
			if ($criteria->getSelect())
217
			{
218 1
				$cursor->fields($criteria->getSelect());
219
			}
220 18
			$this->mn->getProfiler()->cursor($cursor);
221 18
			if ($this->_useCursor)
222
			{
223 1
				return new Cursor($cursor, $this->model);
224
			}
225
			else
226
			{
227 17
				return $this->populateRecords($cursor);
228
			}
229
		}
230
		return [];
231
	}
232
233
	/**
234
	 * Finds all documents with the specified attributes.
235
	 *
236
	 * @param mixed[] Array of stributes and values in form of ['attributeName' => 'value']
237
	 * @return AnnotatedInterface[]|Cursor - Array or cursor of Documents
238
	 * @since v1.0
239
	 */
240 1
	public function findAllByAttributes(array $attributes)
241
	{
242 1
		$criteria = new Criteria();
243 1
		$criteria->decorateWith($this->model);
244 1
		foreach ($attributes as $name => $value)
245
		{
246 1
			$criteria->$name('==', $value);
247
		}
248
249 1
		return $this->findAll($criteria);
250
	}
251
252
	/**
253
	 * Finds all documents with the specified primary keys.
254
	 * In MongoDB world every document has '_id' unique field, so with this method that
255
	 * field is in use as PK by default.
256
	 * See {@link find()} for detailed explanation about $condition.
257
	 *
258
	 * @param mixed $pkValues primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value).
259
	 * @param array|CriteriaInterface $criteria query criteria.
260
	 * @return AnnotatedInterface[]|Cursor - Array or cursor of Documents
261
	 * @since v1.0
262
	 */
263 7
	public function findAllByPk($pkValues, $criteria = null)
264
	{
265 7
		$pkCriteria = new Criteria($criteria);
266 7
		$pkCriteria->decorateWith($this->model);
267 7
		PkManager::prepareAll($this->model, $pkValues, $pkCriteria);
268
269 7
		return $this->findAll($pkCriteria);
270
	}
271
272
	/**
273
	 * Counts all documents satisfying the specified condition.
274
	 * See {@link find()} for detailed explanation about $condition and $params.
275
	 * @param array|CriteriaInterface $criteria query criteria.
276
	 * @return integer Count of all documents satisfying the specified condition.
277
	 * @since v1.0
278
	 */
279 19
	public function count($criteria = null)
280
	{
281 19
		if (FinderEvents::beforeCount($this->model))
282
		{
283 19
			$criteria = $this->sm->apply($criteria);
284 19
			$criteria->decorateWith($this->model);
285 19
			$count = $this->em->getCollection()->count($criteria->getConditions());
286 19
			FinderEvents::afterCount($this->model);
287 19
			return $count;
288
		}
289
		return 0;
290
	}
291
292
	/**
293
	 * Counts all documents found by attribute values.
294
	 *
295
	 * Example:
296
	 * ```php
297
	 * $attributes = [
298
	 * 		'name' => 'John',
299
	 * 		'title' => 'dr'
300
	 * ];
301
	 * ```
302
	 *
303
	 * @param mixed[] Array of attributes and values in form of ['attributeName' => 'value']
304
	 * @return int
305
	 * @since v1.2.2
306
	 * @Ignored
307
	 */
308 1
	public function countByAttributes(array $attributes)
309
	{
310 1
		if (FinderEvents::beforeCount($this->model))
311
		{
312 1
			$criteria = new Criteria;
313 1
			foreach ($attributes as $name => $value)
314
			{
315 1
				$criteria->$name = $value;
316
			}
317
318 1
			$criteria = $this->sm->apply($criteria);
319 1
			$criteria->decorateWith($this->model);
320
321 1
			$count = $this->em->getCollection()->count($criteria->getConditions());
322 1
			FinderEvents::afterCount($this->model);
323 1
			return $count;
324
		}
325
		return 0;
326
	}
327
328
	/**
329
	 * Checks whether there is document satisfying the specified condition.
330
	 *
331
	 * @param CriteriaInterface $criteria
332
	 * @return bool
333
	 */
334 9
	public function exists(CriteriaInterface $criteria = null)
335
	{
336 9
		if (FinderEvents::beforeExists($this->model))
337
		{
338 9
			$criteria = $this->sm->apply($criteria);
339 9
			$criteria->decorateWith($this->model);
340
341
			//Select only Pk Fields to not fetch possibly large document
342 9
			$pkKeys = PkManager::getPkKeys($this->model);
343 9
			if (is_string($pkKeys))
344
			{
345 7
				$pkKeys = [$pkKeys];
346
			}
347 9
			$cursor = $this->em->getCollection()->find($criteria->getConditions(), $pkKeys);
348 9
			$cursor->limit(1);
349
350
			// NOTE: Cannot use count(true) here because of hhvm mongofill compatibility, see:
351
			// https://github.com/mongofill/mongofill/issues/86
352 9
			$exists = ($cursor->count() !== 0);
353 9
			FinderEvents::afterExists($this->model);
354 9
			return $exists;
355
		}
356
		return false;
357
	}
358
359
	/**
360
	 * Whenever to use cursor
361
	 * @param bool $useCursor
362
	 * @return FinderInterface
363
	 */
364 69
	public function withCursor($useCursor = true)
365
	{
366 69
		$this->_useCursor = $useCursor;
367 69
		return $this;
368
	}
369
370
	/**
371
	 * Resets all scopes and criteria applied including default scope.
372
	 *
373
	 * @return Finder
374
	 * @since v1.0
375
	 */
376
	public function resetScope()
377
	{
378
		$this->_criteria = new Criteria();
379
		$this->_criteria->decorateWith($this->model);
380
		return $this;
381
	}
382
383
	/**
384
	 * Creates an model with the given attributes.
385
	 * This method is internally used by the find methods.
386
	 * @param mixed[] $data attribute values (column name=>column value)
387
	 * @return AnnotatedInterface|null the newly created document. The class of the object is the same as the model class.
388
	 * Null is returned if the input data is false.
389
	 * @since v1.0
390
	 */
391 59
	protected function populateRecord($data)
392
	{
393 59
		if ($data !== null)
394
		{
395 58
			if (!empty($data['$err']))
396
			{
397
				throw new ManganException(sprintf("There is an error in query: %s", $data['$err']));
398
			}
399 58
			$model = RawArray::toModel($data, $this->model);
400 58
			ScenarioManager::setScenario($model, ScenariosInterface::Update);
401 58
			FinderEvents::afterFind($model);
402 58
			return $model;
403
		}
404
		else
405
		{
406 3
			return null;
407
		}
408
	}
409
410
	/**
411
	 * Creates a list of documents based on the input data.
412
	 * This method is internally used by the find methods.
413
	 * @param MongoCursor $cursor Results found to populate active records.
414
	 * @return AnnotatedInterface[] array list of active records.
415
	 * @since v1.0
416
	 */
417 17
	protected function populateRecords(MongoCursor $cursor)
418
	{
419 17
		$records = array();
420 17
		foreach ($cursor as $data)
421
		{
422 17
			$records[] = $this->populateRecord($data);
423
		}
424 17
		return $records;
425
	}
426
427
}
428