Completed
Push — master ( f2b62d...870e88 )
by Peter
07:13
created

Finder::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 10
cts 10
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 2
nop 3
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\Abstracts\AbstractFinder;
18
use Maslosoft\Mangan\Adapters\Finder\MongoAdapter;
19
use Maslosoft\Mangan\Exceptions\ManganException;
20
use Maslosoft\Mangan\Helpers\FinderEvents;
21
use Maslosoft\Mangan\Helpers\PkManager;
22
use Maslosoft\Mangan\Interfaces\Adapters\FinderAdapterInterface;
23
use Maslosoft\Mangan\Interfaces\CriteriaInterface;
24
use Maslosoft\Mangan\Interfaces\EntityManagerInterface;
25
use Maslosoft\Mangan\Interfaces\FinderInterface;
26
use Maslosoft\Mangan\Interfaces\ScenariosInterface;
27
use Maslosoft\Mangan\Interfaces\ScopeManagerInterface;
28
use Maslosoft\Mangan\Meta\ManganMeta;
29
use Maslosoft\Mangan\Transformers\RawArray;
30
use MongoCursor;
31
32
/**
33
 * Finder
34
 * TODO Further refactor to split to abstract finder with:
35
 * * get/setAdapter method
36
 * * get/setScopeManager method
37
 * * get/setProfiler method
38
 * @author Piotr Maselkowski <pmaselkowski at gmail.com>
39
 */
40
class Finder extends AbstractFinder implements FinderInterface
41
{
42
43
	/**
44
	 * Model
45
	 * @var AnnotatedInterface
46
	 */
47
	public $model = null;
48
49
	/**
50
	 *
51
	 * @var FinderAdapterInterface
52
	 */
53
	private $adapter = null;
54
55
	/**
56
	 * Mangan instance
57
	 * @var Mangan
58
	 */
59
	private $mn = null;
60
61
	/**
62
	 * Scope manager instance
63
	 * @var ScopeManagerInterface
64
	 */
65
	private $sm = null;
66
67
	/**
68
	 * Finder events instance
69
	 * @var FinderEventsInterface
70
	 */
71
	private $fe = null;
72
73
	/**
74
	 * Finder criteria
75
	 * @var Criteria
76
	 */
77
	private $_criteria = null;
78
79
	/**
80
	 * Whenever to use corsors
81
	 * @var bool
82
	 */
83
	private $_useCursor = false;
84
85
	/**
86
	 * Constructor
87
	 *
88
	 * @param object $model Model instance
89
	 * @param EntityManagerInterface $em
90
	 * @param Mangan $mangan
91
	 */
92 69
	public function __construct($model, $em = null, $mangan = null)
93
	{
94 69
		$this->model = $model;
95 69
		$this->sm = new ScopeManager($model);
96 69
		if (null === $mangan)
97
		{
98 69
			$mangan = Mangan::fromModel($model);
99
		}
100 69
		$this->adapter = new MongoAdapter($model, $mangan, $em);
101 69
		$this->mn = $mangan;
102 69
		$this->fe = new FinderEvents();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Maslosoft\Mangan\Helpers\FinderEvents() of type object<Maslosoft\Mangan\Helpers\FinderEvents> is incompatible with the declared type object<Maslosoft\Mangan\FinderEventsInterface> of property $fe.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
103 69
		$this->withCursor($this->mn->useCursor);
104 69
	}
105
106
	/**
107
	 * Create model related finder.
108
	 * This will create customized finder if defined in model with Finder annotation.
109
	 * If no custom finder is defined this will return default Finder.
110
	 *
111
	 * @param AnnotatedInterface $model
112
	 * @param EntityManagerInterface $em
113
	 * @param Mangan $mangan
114
	 * @return FinderInterface
115
	 */
116 9
	public static function create(AnnotatedInterface $model, $em = null, Mangan $mangan = null)
117
	{
118 9
		$finderClass = ManganMeta::create($model)->type()->finder ?: static::class;
119 9
		return new $finderClass($model, $em, $mangan);
120
	}
121
122
	/**
123
	 * Finds a single Document with the specified condition.
124
	 *
125
	 * @param array|CriteriaInterface $criteria query criteria.
126
	 * @return AnnotatedInterface
127
	 * @Ignored
128
	 */
129 52
	public function find($criteria = null)
130
	{
131 52
		if ($this->fe->beforeFind($this->model))
132
		{
133 52
			$criteria = $this->sm->apply($criteria);
134 52
			$data = $this->adapter->findOne($criteria, $criteria->getSelect());
135 52
			return $this->populateRecord($data);
136
		}
137
		return null;
138
	}
139
140
	/**
141
	 * Finds document with the specified primary key. Primary key by default
142
	 * is defined by `_id` field. But could be any other. For simple (one column)
143
	 * keys use it's value.
144
	 *
145
	 * For composite use key-value with column names as keys
146
	 * and values for values.
147
	 *
148
	 * Example for simple pk:
149
	 * ```php
150
	 * $pk = '51b616fcc0986e30026d0748'
151
	 * ```
152
	 *
153
	 * Composite pk:
154
	 * ```php
155
	 * $pk = [
156
	 * 		'mainPk' => 1,
157
	 * 		'secondaryPk' => 2
158
	 * ];
159
	 * ```
160
	 *
161
	 * @param mixed $pkValue primary key value. Use array for composite key.
162
	 * @param array|CriteriaInterface $criteria
163
	 * @return AnnotatedInterface|null
164
	 * @Ignored
165
	 */
166 40
	public function findByPk($pkValue, $criteria = null)
167
	{
168 40
		$pkCriteria = $this->sm->getNewCriteria($criteria);
169 40
		$pkCriteria->mergeWith(PkManager::prepare($this->model, $pkValue));
170 40
		return $this->find($pkCriteria);
171
	}
172
173
	/**
174
	 * Finds document with the specified attributes.
175
	 * Attributes should be specified as key-value pairs.
176
	 * This allows easier syntax for simple queries.
177
	 *
178
	 * Example:
179
	 * ```php
180
	 * $attributes = [
181
	 * 		'name' => 'John',
182
	 * 		'title' => 'dr'
183
	 * ];
184
	 * ```
185
	 *
186
	 * @param mixed[] Array of stributes and values in form of ['attributeName' => 'value']
187
	 * @return AnnotatedInterface|null
188
	 */
189 2
	public function findByAttributes(array $attributes)
190
	{
191 2
		$criteria = $this->sm->getNewCriteria();
192 2
		foreach ($attributes as $name => $value)
193
		{
194 2
			$criteria->addCond($name, '==', $value);
195
		}
196 2
		return $this->find($criteria);
197
	}
198
199
	/**
200
	 * Finds all documents satisfying the specified condition.
201
	 * See {@link find()} for detailed explanation about $condition and $params.
202
	 *
203
	 * @param array|CriteriaInterface $criteria query criteria.
204
	 * @return AnnotatedInterface[]|Cursor
205
	 */
206 18
	public function findAll($criteria = null)
207
	{
208 18
		if ($this->fe->beforeFind($this->model))
209
		{
210 18
			$criteria = $this->sm->apply($criteria);
211 18
			$cursor = $this->adapter->findMany($criteria);
212
213 18
			if ($criteria->getSort() !== null)
214
			{
215 18
				$cursor->sort($criteria->getSort());
216
			}
217 18
			if ($criteria->getLimit() !== null)
218
			{
219 1
				$cursor->limit($criteria->getLimit());
220
			}
221 18
			if ($criteria->getOffset() !== null)
222
			{
223 1
				$cursor->skip($criteria->getOffset());
224
			}
225 18
			if ($criteria->getSelect())
226
			{
227 1
				$cursor->fields($criteria->getSelect());
228
			}
229 18
			$this->mn->getProfiler()->cursor($cursor);
0 ignored issues
show
Compatibility introduced by
$cursor of type object<Iterator> is not a sub-type of object<MongoCursor>. It seems like you assume a concrete implementation of the interface Iterator to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
230 18
			if ($this->_useCursor)
231
			{
232 1
				return new Cursor($cursor, $this->model);
233
			}
234
			else
235
			{
236 17
				return $this->populateRecords($cursor);
0 ignored issues
show
Documentation introduced by
$cursor is of type object<Iterator>, but the function expects a object<Maslosoft\Mangan\Iterator>|array.

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...
237
			}
238
		}
239
		return [];
240
	}
241
242
	/**
243
	 * Finds all documents with the specified attributes.
244
	 *
245
	 * @param mixed[] Array of stributes and values in form of ['attributeName' => 'value']
246
	 * @return AnnotatedInterface[]|Cursor - Array or cursor of Documents
247
	 * @since v1.0
248
	 */
249 1
	public function findAllByAttributes(array $attributes)
250
	{
251 1
		$criteria = $this->sm->getNewCriteria();
252 1
		foreach ($attributes as $name => $value)
253
		{
254 1
			$criteria->$name('==', $value);
255
		}
256
257 1
		return $this->findAll($criteria);
258
	}
259
260
	/**
261
	 * Finds all documents with the specified primary keys.
262
	 * In MongoDB world every document has '_id' unique field, so with this method that
263
	 * field is in use as PK by default.
264
	 * See {@link find()} for detailed explanation about $condition.
265
	 *
266
	 * @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).
267
	 * @param array|CriteriaInterface $criteria query criteria.
268
	 * @return AnnotatedInterface[]|Cursor - Array or cursor of Documents
269
	 * @since v1.0
270
	 */
271 7
	public function findAllByPk($pkValues, $criteria = null)
272
	{
273 7
		$pkCriteria = $this->sm->getNewCriteria($criteria);
274 7
		PkManager::prepareAll($this->model, $pkValues, $pkCriteria);
275
276 7
		return $this->findAll($pkCriteria);
277
	}
278
279
	/**
280
	 * Counts all documents satisfying the specified condition.
281
	 * See {@link find()} for detailed explanation about $condition and $params.
282
	 * @param array|CriteriaInterface $criteria query criteria.
283
	 * @return integer Count of all documents satisfying the specified condition.
284
	 * @since v1.0
285
	 */
286 19
	public function count($criteria = null)
287
	{
288 19
		if ($this->fe->beforeCount($this->model))
289
		{
290 19
			$criteria = $this->sm->apply($criteria);
291 19
			$count = $this->adapter->count($criteria);
292 19
			$this->fe->afterCount($this->model);
293 19
			return $count;
294
		}
295
		return 0;
296
	}
297
298
	/**
299
	 * Counts all documents found by attribute values.
300
	 *
301
	 * Example:
302
	 * ```php
303
	 * $attributes = [
304
	 * 		'name' => 'John',
305
	 * 		'title' => 'dr'
306
	 * ];
307
	 * ```
308
	 *
309
	 * @param mixed[] Array of attributes and values in form of ['attributeName' => 'value']
310
	 * @return int
311
	 * @since v1.2.2
312
	 * @Ignored
313
	 */
314 1
	public function countByAttributes(array $attributes)
315
	{
316 1
		if ($this->fe->beforeCount($this->model))
317
		{
318 1
			$criteria = $this->sm->getNewCriteria();
319 1
			foreach ($attributes as $name => $value)
320
			{
321 1
				$criteria->$name = $value;
322
			}
323
324 1
			$criteria = $this->sm->apply($criteria);
325
326 1
			$count = $this->adapter->count($criteria);
327 1
			$this->fe->afterCount($this->model);
328 1
			return $count;
329
		}
330
		return 0;
331
	}
332
333
	/**
334
	 * Checks whether there is document satisfying the specified condition.
335
	 *
336
	 * @param CriteriaInterface $criteria
337
	 * @return bool
338
	 */
339 9
	public function exists(CriteriaInterface $criteria = null)
340
	{
341 9
		if ($this->fe->beforeExists($this->model))
342
		{
343 9
			$criteria = $this->sm->apply($criteria);
344
345
			//Select only Pk Fields to not fetch possibly large document
346 9
			$pkKeys = PkManager::getPkKeys($this->model);
347 9
			if (is_string($pkKeys))
348
			{
349 7
				$pkKeys = [$pkKeys];
350
			}
351 9
			$cursor = $this->adapter->findMany($criteria, $pkKeys);
352 9
			$cursor->limit(1);
353
354
			// NOTE: Cannot use count(true) here because of hhvm mongofill compatibility, see:
355
			// https://github.com/mongofill/mongofill/issues/86
356 9
			$exists = ($cursor->count() !== 0);
357 9
			$this->fe->afterExists($this->model);
358 9
			return $exists;
359
		}
360
		return false;
361
	}
362
363
	/**
364
	 * Whenever to use cursor
365
	 * @param bool $useCursor
366
	 * @return FinderInterface
367
	 */
368 69
	public function withCursor($useCursor = true)
369
	{
370 69
		$this->_useCursor = $useCursor;
371 69
		return $this;
372
	}
373
374
	/**
375
	 * Resets all scopes and criteria applied including default scope.
376
	 *
377
	 * @return Finder
378
	 * @since v1.0
379
	 */
380
	public function resetScope()
381
	{
382
		$this->_criteria = $this->sm->getNewCriteria();
383
		return $this;
384
	}
385
386
	/**
387
	 * Creates an model with the given attributes.
388
	 * This method is internally used by the find methods.
389
	 * @param mixed[] $data attribute values (column name=>column value)
390
	 * @return AnnotatedInterface|null the newly created document. The class of the object is the same as the model class.
391
	 * Null is returned if the input data is false.
392
	 * @since v1.0
393
	 */
394 59
	protected function populateRecord($data)
395
	{
396 59
		if ($data !== null)
397
		{
398 58
			if (!empty($data['$err']))
399
			{
400
				throw new ManganException(sprintf("There is an error in query: %s", $data['$err']));
401
			}
402 58
			$model = $this->createModel($data);
403 58
			ScenarioManager::setScenario($model, ScenariosInterface::Update);
404 58
			$this->fe->afterFind($model);
405 58
			return $model;
406
		}
407
		else
408
		{
409 3
			return null;
410
		}
411
	}
412
413 58
	protected function createModel($data)
414
	{
415 58
		return RawArray::toModel($data, $this->model);
416
	}
417
418
	/**
419
	 * Creates a list of documents based on the input data.
420
	 * This method is internally used by the find methods.
421
	 * @param Iterator|array $cursor Results found to populate active records.
422
	 * @return AnnotatedInterface[] array list of active records.
423
	 * @since v1.0
424
	 */
425 17
	protected function populateRecords($cursor)
426
	{
427 17
		$records = array();
428 17
		foreach ($cursor as $data)
429
		{
430 17
			$records[] = $this->populateRecord($data);
431
		}
432 17
		return $records;
433
	}
434
435
}
436