Completed
Push — ext-mongodb ( c0ec81 )
by Peter
03:34
created

Finder   B

Complexity

Total Complexity 32

Size/Duplication

Total Lines 383
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 13
Bugs 1 Features 0
Metric Value
wmc 32
c 13
b 1
f 0
lcom 1
cbo 16
dl 0
loc 383
rs 8.123

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 3
A create() 0 5 2
A find() 0 11 2
A findByPk() 0 8 1
A findByAttributes() 0 10 2
C findAll() 0 36 7
A findAllByAttributes() 0 11 2
A findAllByPk() 0 8 1
A count() 0 6 1
A countByAttributes() 0 13 2
A exists() 0 15 1
A withCursor() 0 5 1
A resetScope() 0 6 1
A populateRecord() 0 14 2
A populateRecords() 0 9 2
A _beforeFind() 0 8 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\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
	public function __construct($model, $em = null, $mangan = null)
79
	{
80
		$this->model = $model;
81
		$this->sm = new ScopeManager($model);
82
		if (null === $mangan)
83
		{
84
			$mangan = Mangan::fromModel($model);
85
		}
86
		$this->em = $em ? : EntityManager::create($model, $mangan);
87
		$this->mn = $mangan;
88
		$this->withCursor($this->mn->useCursor);
89
	}
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
	public static function create(AnnotatedInterface $model, $em = null, Mangan $mangan = null)
102
	{
103
		$finderClass = ManganMeta::create($model)->type()->finder ? : static::class;
104
		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
	public function find($criteria = null)
115
	{
116
		if ($this->_beforeFind())
117
		{
118
			$criteria = $this->sm->apply($criteria);
119
			$criteria->decorateWith($this->model);
120
			$data = $this->em->getCollection()->findOne($criteria->getConditions(), $criteria->getSelect());
121
			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
	public function findByPk($pkValue, $criteria = null)
153
	{
154
155
		$pkCriteria = new Criteria($criteria);
156
		$pkCriteria->decorateWith($this->model);
157
		$pkCriteria->mergeWith(PkManager::prepare($this->model, $pkValue));
158
		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
	public function findByAttributes(array $attributes)
178
	{
179
		$criteria = new Criteria();
180
		$criteria->decorateWith($this->model);
181
		foreach ($attributes as $name => $value)
182
		{
183
			$criteria->addCond($name, '==', $value);
184
		}
185
		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
	public function findAll($criteria = null)
196
	{
197
		if ($this->_beforeFind())
198
		{
199
			$criteria = $this->sm->apply($criteria);
200
			$criteria->decorateWith($this->model);
201
			$cursor = $this->em->getCollection()->find($criteria->getConditions());
202
203
			if ($criteria->getSort() !== null)
204
			{
205
				$cursor->sort($criteria->getSort());
206
			}
207
			if ($criteria->getLimit() !== null)
208
			{
209
				$cursor->limit($criteria->getLimit());
210
			}
211
			if ($criteria->getOffset() !== null)
212
			{
213
				$cursor->skip($criteria->getOffset());
214
			}
215
			if ($criteria->getSelect())
216
			{
217
				$cursor->fields($criteria->getSelect());
218
			}
219
			$this->mn->getProfiler()->cursor($cursor);
0 ignored issues
show
Documentation introduced by
$cursor is of type object<Maslosoft\Mangan\Bridge\MongoCursor>, but the function expects a object<MongoCursor>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
220
			if ($this->_useCursor)
221
			{
222
				return new Cursor($cursor, $this->model);
0 ignored issues
show
Documentation introduced by
$cursor is of type object<Maslosoft\Mangan\Bridge\MongoCursor>, but the function expects a object<MongoCursor>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
223
			}
224
			else
225
			{
226
				return $this->populateRecords($cursor);
0 ignored issues
show
Documentation introduced by
$cursor is of type object<Maslosoft\Mangan\Bridge\MongoCursor>, but the function expects a object<MongoCursor>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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
	public function findAllByAttributes(array $attributes)
240
	{
241
		$criteria = new Criteria();
242
		$criteria->decorateWith($this->model);
243
		foreach ($attributes as $name => $value)
244
		{
245
			$criteria->$name('==', $value);
246
		}
247
248
		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
	public function findAllByPk($pkValues, $criteria = null)
263
	{
264
		$pkCriteria = new Criteria($criteria);
265
		$pkCriteria->decorateWith($this->model);
266
		PkManager::prepareAll($this->model, $pkValues, $pkCriteria);
267
268
		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
	public function count($criteria = null)
279
	{
280
		$criteria = $this->sm->apply($criteria);
281
		$criteria->decorateWith($this->model);
282
		return $this->em->getCollection()->count($criteria->getConditions());
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->em->getCollection...eria->getConditions()); of type integer|false adds false to the return on line 282 which is incompatible with the return type declared by the interface Maslosoft\Mangan\Interfaces\FinderInterface::count of type integer. It seems like you forgot to handle an error condition.
Loading history...
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
	public function countByAttributes(array $attributes)
302
	{
303
		$criteria = new Criteria;
304
		$criteria->decorateWith($this->model);
305
		foreach ($attributes as $name => $value)
306
		{
307
			$criteria->$name = $value;
308
		}
309
310
		$criteria = $this->sm->apply($criteria);
311
312
		return $this->em->getCollection()->count($criteria->getConditions());
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->em->getCollection...eria->getConditions()); of type integer|false adds false to the return on line 312 which is incompatible with the return type declared by the interface Maslosoft\Mangan\Interfa...face::countByAttributes of type integer. It seems like you forgot to handle an error condition.
Loading history...
313
	}
314
315
	/**
316
	 * Checks whether there is document satisfying the specified condition.
317
	 *
318
	 * @param CriteriaInterface $criteria
319
	 * @return bool
320
	 */
321
	public function exists(CriteriaInterface $criteria = null)
322
	{
323
		$criteria = $this->sm->apply($criteria);
324
		$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
		$cursor = $this->em->getCollection()->find($criteria->getConditions());
330
		$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
		return ($cursor->count() !== 0);
335
	}
336
337
	/**
338
	 * Whenever to use cursor
339
	 * @param bool $useCursor
340
	 * @return FinderInterface
341
	 */
342
	public function withCursor($useCursor = true)
343
	{
344
		$this->_useCursor = $useCursor;
345
		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
	protected function populateRecord($data)
370
	{
371
		if ($data !== null)
372
		{
373
			$model = RawArray::toModel($data, $this->model);
374
			ScenarioManager::setScenario($model, ScenariosInterface::Update);
375
			Event::trigger($model, FinderInterface::EventAfterFind);
376
			return $model;
377
		}
378
		else
379
		{
380
			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
	protected function populateRecords(MongoCursor $cursor)
392
	{
393
		$records = array();
394
		foreach ($cursor as $data)
395
		{
396
			$records[] = $this->populateRecord($data);
397
		}
398
		return $records;
399
	}
400
401
	/**
402
	 * Trigger before find event
403
	 * @return boolean
404
	 */
405
	private function _beforeFind()
406
	{
407
		if (!Event::hasHandler($this->model, FinderInterface::EventBeforeFind))
408
		{
409
			return true;
410
		}
411
		return Event::handled($this->model, FinderInterface::EventBeforeFind);
412
	}
413
414
}
415