Completed
Push — master ( a18119...0d7bb7 )
by Peter
05:52
created

Finder::withCursor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 1
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 Iterator;
17
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
18
use Maslosoft\Mangan\Abstracts\AbstractFinder;
19
use Maslosoft\Mangan\Adapters\Finder\MongoAdapter;
20
use Maslosoft\Mangan\Exceptions\ManganException;
21
use Maslosoft\Mangan\Helpers\FinderEvents;
22
use Maslosoft\Mangan\Helpers\PkManager;
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\Meta\ManganMeta;
28
use Maslosoft\Mangan\Traits\Finder\FinderHelpers;
29
use Maslosoft\Mangan\Transformers\RawArray;
30
31
/**
32
 * Finder
33
 * TODO Further refactor to split to abstract finder with:
34
 * * get/setAdapter method
35
 * * get/setScopeManager method
36
 * * get/setProfiler method
37
 * @author Piotr Maselkowski <pmaselkowski at gmail.com>
38
 */
39
class Finder extends AbstractFinder implements FinderInterface
40
{
41
42
	use FinderHelpers;
43
44
	/**
45
	 * Model
46
	 * @var AnnotatedInterface
47
	 */
48
	public $model = null;
49
50
	/**
51
	 * Mangan instance
52
	 * @var Mangan
53
	 */
54
	private $mn = null;
55
56
	/**
57
	 * Finder criteria
58
	 * @var Criteria
59
	 */
60
	private $_criteria = null;
61
62
	/**
63
	 * Constructor
64
	 *
65
	 * @param object $model Model instance
66
	 * @param EntityManagerInterface $em
67
	 * @param Mangan $mangan
68
	 */
69 69
	public function __construct($model, $em = null, $mangan = null)
70
	{
71 69
		$this->model = $model;
72 69
		$this->sm = new ScopeManager($model);
0 ignored issues
show
Bug introduced by
The property sm does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
73 69
		if (null === $mangan)
74
		{
75 69
			$mangan = Mangan::fromModel($model);
76
		}
77 69
		$this->adapter = new MongoAdapter($model, $mangan, $em);
78 69
		$this->mn = $mangan;
79 69
		$this->fe = new FinderEvents();
0 ignored issues
show
Bug introduced by
The property fe does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
80 69
		$this->withCursor($this->mn->useCursor);
81 69
	}
82
83
	/**
84
	 * Create model related finder.
85
	 * This will create customized finder if defined in model with Finder annotation.
86
	 * If no custom finder is defined this will return default Finder.
87
	 *
88
	 * @param AnnotatedInterface $model
89
	 * @param EntityManagerInterface $em
90
	 * @param Mangan $mangan
91
	 * @return FinderInterface
92
	 */
93 9
	public static function create(AnnotatedInterface $model, $em = null, Mangan $mangan = null)
94
	{
95 9
		$finderClass = ManganMeta::create($model)->type()->finder ?: static::class;
96 9
		return new $finderClass($model, $em, $mangan);
97
	}
98
99
	/**
100
	 * Finds a single Document with the specified condition.
101
	 *
102
	 * @param array|CriteriaInterface $criteria query criteria.
103
	 * @return AnnotatedInterface
104
	 * @Ignored
105
	 */
106 52
	public function find($criteria = null)
107
	{
108 52
		if ($this->fe->beforeFind($this->model))
109
		{
110 52
			$criteria = $this->sm->apply($criteria);
111 52
			$data = $this->adapter->findOne($criteria, $criteria->getSelect());
112 52
			return $this->populateRecord($data);
113
		}
114
		return null;
115
	}
116
117
	/**
118
	 * Finds document with the specified primary key. Primary key by default
119
	 * is defined by `_id` field. But could be any other. For simple (one column)
120
	 * keys use it's value.
121
	 *
122
	 * For composite use key-value with column names as keys
123
	 * and values for values.
124
	 *
125
	 * Example for simple pk:
126
	 * ```php
127
	 * $pk = '51b616fcc0986e30026d0748'
128
	 * ```
129
	 *
130
	 * Composite pk:
131
	 * ```php
132
	 * $pk = [
133
	 * 		'mainPk' => 1,
134
	 * 		'secondaryPk' => 2
135
	 * ];
136
	 * ```
137
	 *
138
	 * @param mixed $pkValue primary key value. Use array for composite key.
139
	 * @param array|CriteriaInterface $criteria
140
	 * @return AnnotatedInterface|null
141
	 * @Ignored
142
	 */
143 40
	public function findByPk($pkValue, $criteria = null)
144
	{
145 40
		$pkCriteria = $this->sm->getNewCriteria($criteria);
146 40
		$pkCriteria->mergeWith(PkManager::prepare($this->model, $pkValue));
147 40
		return $this->find($pkCriteria);
148
	}
149
150
	/**
151
	 * Finds document with the specified attributes.
152
	 * Attributes should be specified as key-value pairs.
153
	 * This allows easier syntax for simple queries.
154
	 *
155
	 * Example:
156
	 * ```php
157
	 * $attributes = [
158
	 * 		'name' => 'John',
159
	 * 		'title' => 'dr'
160
	 * ];
161
	 * ```
162
	 *
163
	 * @param mixed[] Array of stributes and values in form of ['attributeName' => 'value']
164
	 * @return AnnotatedInterface|null
165
	 */
166 2
	public function findByAttributes(array $attributes)
167
	{
168 2
		$criteria = $this->sm->getNewCriteria();
169 2
		foreach ($attributes as $name => $value)
170
		{
171 2
			$criteria->addCond($name, '==', $value);
172
		}
173 2
		return $this->find($criteria);
174
	}
175
176
	/**
177
	 * Finds all documents satisfying the specified condition.
178
	 * See {@link find()} for detailed explanation about $condition and $params.
179
	 *
180
	 * @param array|CriteriaInterface $criteria query criteria.
181
	 * @return AnnotatedInterface[]|Cursor
182
	 */
183 18
	public function findAll($criteria = null)
184
	{
185 18
		if ($this->fe->beforeFind($this->model))
186
		{
187 18
			$criteria = $this->sm->apply($criteria);
188 18
			$cursor = $this->adapter->findMany($criteria);
189
190 18
			if ($criteria->getSort() !== null)
191
			{
192 18
				$cursor->sort($criteria->getSort());
193
			}
194 18
			if ($criteria->getLimit() !== null)
195
			{
196 1
				$cursor->limit($criteria->getLimit());
197
			}
198 18
			if ($criteria->getOffset() !== null)
199
			{
200 1
				$cursor->skip($criteria->getOffset());
201
			}
202 18
			if ($criteria->getSelect())
203
			{
204 1
				$cursor->fields($criteria->getSelect());
205
			}
206 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...
207 18
			if ($this->isWithCursor())
208
			{
209 1
				return new Cursor($cursor, $this->model);
210
			}
211
			else
212
			{
213 17
				return $this->populateRecords($cursor);
214
			}
215
		}
216
		return [];
217
	}
218
219
	/**
220
	 * Finds all documents with the specified attributes.
221
	 *
222
	 * @param mixed[] Array of stributes and values in form of ['attributeName' => 'value']
223
	 * @return AnnotatedInterface[]|Cursor - Array or cursor of Documents
224
	 * @since v1.0
225
	 */
226 1
	public function findAllByAttributes(array $attributes)
227
	{
228 1
		$criteria = $this->sm->getNewCriteria();
229 1
		foreach ($attributes as $name => $value)
230
		{
231 1
			$criteria->$name('==', $value);
232
		}
233
234 1
		return $this->findAll($criteria);
235
	}
236
237
	/**
238
	 * Finds all documents with the specified primary keys.
239
	 * In MongoDB world every document has '_id' unique field, so with this method that
240
	 * field is in use as PK by default.
241
	 * See {@link find()} for detailed explanation about $condition.
242
	 *
243
	 * @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).
244
	 * @param array|CriteriaInterface $criteria query criteria.
245
	 * @return AnnotatedInterface[]|Cursor - Array or cursor of Documents
246
	 * @since v1.0
247
	 */
248 7
	public function findAllByPk($pkValues, $criteria = null)
249
	{
250 7
		$pkCriteria = $this->sm->getNewCriteria($criteria);
251 7
		PkManager::prepareAll($this->model, $pkValues, $pkCriteria);
252
253 7
		return $this->findAll($pkCriteria);
254
	}
255
256
	/**
257
	 * Counts all documents satisfying the specified condition.
258
	 * See {@link find()} for detailed explanation about $condition and $params.
259
	 * @param array|CriteriaInterface $criteria query criteria.
260
	 * @return integer Count of all documents satisfying the specified condition.
261
	 * @since v1.0
262
	 */
263 19
	public function count($criteria = null)
264
	{
265 19
		if ($this->fe->beforeCount($this->model))
266
		{
267 19
			$criteria = $this->sm->apply($criteria);
268 19
			$count = $this->adapter->count($criteria);
269 19
			$this->fe->afterCount($this->model);
270 19
			return $count;
271
		}
272
		return 0;
273
	}
274
275
	/**
276
	 * Counts all documents found by attribute values.
277
	 *
278
	 * Example:
279
	 * ```php
280
	 * $attributes = [
281
	 * 		'name' => 'John',
282
	 * 		'title' => 'dr'
283
	 * ];
284
	 * ```
285
	 *
286
	 * @param mixed[] Array of attributes and values in form of ['attributeName' => 'value']
287
	 * @return int
288
	 * @since v1.2.2
289
	 * @Ignored
290
	 */
291 1
	public function countByAttributes(array $attributes)
292
	{
293 1
		if ($this->fe->beforeCount($this->model))
294
		{
295 1
			$criteria = $this->sm->getNewCriteria();
296 1
			foreach ($attributes as $name => $value)
297
			{
298 1
				$criteria->$name = $value;
299
			}
300
301 1
			$criteria = $this->sm->apply($criteria);
302
303 1
			$count = $this->adapter->count($criteria);
304 1
			$this->fe->afterCount($this->model);
305 1
			return $count;
306
		}
307
		return 0;
308
	}
309
310
	/**
311
	 * Checks whether there is document satisfying the specified condition.
312
	 *
313
	 * @param CriteriaInterface $criteria
314
	 * @return bool
315
	 */
316 9
	public function exists(CriteriaInterface $criteria = null)
317
	{
318 9
		if ($this->fe->beforeExists($this->model))
319
		{
320 9
			$criteria = $this->sm->apply($criteria);
321
322
			//Select only Pk Fields to not fetch possibly large document
323 9
			$pkKeys = PkManager::getPkKeys($this->model);
324 9
			if (is_string($pkKeys))
325
			{
326 7
				$pkKeys = [$pkKeys];
327
			}
328 9
			$cursor = $this->adapter->findMany($criteria, $pkKeys);
329 9
			$cursor->limit(1);
330
331
			// NOTE: Cannot use count(true) here because of hhvm mongofill compatibility, see:
332
			// https://github.com/mongofill/mongofill/issues/86
333 9
			$exists = ($cursor->count() !== 0);
334 9
			$this->fe->afterExists($this->model);
335 9
			return $exists;
336
		}
337
		return false;
338
	}
339
340
	/**
341
	 * Resets all scopes and criteria applied including default scope.
342
	 *
343
	 * @return Finder
344
	 * @since v1.0
345
	 */
346
	public function resetScope()
347
	{
348
		$this->_criteria = $this->sm->getNewCriteria();
349
		return $this;
350
	}
351
352
	/**
353
	 * Creates an model with the given attributes.
354
	 * This method is internally used by the find methods.
355
	 * @param mixed[] $data attribute values (column name=>column value)
356
	 * @return AnnotatedInterface|null the newly created document. The class of the object is the same as the model class.
357
	 * Null is returned if the input data is false.
358
	 * @since v1.0
359
	 */
360 59
	protected function populateRecord($data)
361
	{
362 59
		if ($data !== null)
363
		{
364 58
			if (!empty($data['$err']))
365
			{
366
				throw new ManganException(sprintf("There is an error in query: %s", $data['$err']));
367
			}
368 58
			$model = $this->createModel($data);
369 58
			ScenarioManager::setScenario($model, ScenariosInterface::Update);
370 58
			$this->fe->afterFind($model);
371 58
			return $model;
372
		}
373
		else
374
		{
375 3
			return null;
376
		}
377
	}
378
379 58
	protected function createModel($data)
380
	{
381 58
		return RawArray::toModel($data, $this->model);
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 Iterator|array $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($cursor)
392
	{
393 17
		$records = array();
394 17
		foreach ($cursor as $data)
395
		{
396 17
			$records[] = $this->populateRecord($data);
397
		}
398 17
		return $records;
399
	}
400
401
}
402