Completed
Push — master ( 442024...b252dd )
by Peter
89:33 queued 70:52
created

Finder::find()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2.0078

Importance

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