Completed
Push — master ( 0a999d...b4b35b )
by Peter
13:24 queued 02:45
created

AbstractFinder   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 374
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 91.3%

Importance

Changes 0
Metric Value
wmc 31
lcom 1
cbo 9
dl 0
loc 374
ccs 84
cts 92
cp 0.913
rs 9.8
c 0
b 0
f 0

22 Methods

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