AbstractFinder   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 374
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 92.39%

Importance

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