Completed
Push — master ( 0d7bb7...25abba )
by Peter
05:59
created

Finder   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 346
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 91.18%

Importance

Changes 0
Metric Value
wmc 34
lcom 1
cbo 13
dl 0
loc 346
ccs 93
cts 102
cp 0.9118
rs 9.2
c 0
b 0
f 0

15 Methods

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