Completed
Push — master ( 119440...87e4b9 )
by Peter
16:02
created

EntityManager::replace()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 25
ccs 0
cts 15
cp 0
rs 8.439
cc 5
eloc 14
nc 4
nop 1
crap 30
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\Events\Event;
18
use Maslosoft\Mangan\Events\EventDispatcher;
19
use Maslosoft\Mangan\Events\ModelEvent;
20
use Maslosoft\Mangan\Exceptions\ManganException;
21
use Maslosoft\Mangan\Helpers\CollectionNamer;
22
use Maslosoft\Mangan\Helpers\PkManager;
23
use Maslosoft\Mangan\Interfaces\CriteriaInterface;
24
use Maslosoft\Mangan\Interfaces\EntityManagerInterface;
25
use Maslosoft\Mangan\Interfaces\ScenariosInterface;
26
use Maslosoft\Mangan\Meta\ManganMeta;
27
use Maslosoft\Mangan\Options\EntityOptions;
28
use Maslosoft\Mangan\Signals\AfterDelete;
29
use Maslosoft\Mangan\Signals\AfterSave;
30
use Maslosoft\Mangan\Signals\BeforeDelete;
31
use Maslosoft\Mangan\Signals\BeforeSave;
32
use Maslosoft\Mangan\Transformers\RawArray;
33
use Maslosoft\Mangan\Transformers\SafeArray;
34
use Maslosoft\Signals\Signal;
35
use MongoCollection;
36
37
/**
38
 * EntityManager
39
 * 
40
 * @author Piotr Maselkowski <pmaselkowski at gmail.com>
41
 */
42
class EntityManager implements EntityManagerInterface
43
{
44
45
	/**
46
	 * Model
47
	 * @var AnnotatedInterface
48
	 */
49
	public $model = null;
50
51
	/**
52
	 *
53
	 * @var EventDispatcher
54
	 */
55
	public $ed = null;
56
57
	/**
58
	 *
59
	 * @var ScopeManager
60
	 */
61
	private $sm = null;
62
63
	/**
64
	 *
65
	 * @var
66
	 */
67
	public $meta = null;
68
69
	/**
70
	 * Options
71
	 * @var EntityOptions
72
	 */
73
	public $options = null;
74
75
	/**
76
	 * Current collection name
77
	 * @var string
78
	 */
79
	public $collectionName = '';
80
81
	/**
82
	 * Validator instance
83
	 * @var Validator
84
	 */
85
	private $validator = null;
86
87
	/**
88
	 * Current collection
89
	 * @var MongoCollection
90
	 */
91
	private $_collection = null;
92
93
	/**
94
	 * Create entity manager
95
	 * @param AnnotatedInterface $model
96
	 * @param Mangan $mangan
97
	 * @throws ManganException
98
	 */
99 85
	public function __construct(AnnotatedInterface $model, Mangan $mangan = null)
100
	{
101 85
		$this->model = $model;
102 85
		$this->sm = new ScopeManager($model);
103 85
		$this->options = new EntityOptions($model);
104 85
		$this->collectionName = CollectionNamer::nameCollection($model);
105 85
		$this->meta = ManganMeta::create($model);
106 85
		$this->validator = new Validator($model);
107 85
		if (null === $mangan)
108 85
		{
109 84
			$mangan = Mangan::fromModel($model);
110 84
		}
111 85
		if (!$this->collectionName)
112 85
		{
113
			throw new ManganException(sprintf('Invalid collection name for model: `%s`', $this->meta->type()->name));
114
		}
115 85
		$this->_collection = new MongoCollection($mangan->getDbInstance(), $this->collectionName);
116 85
	}
117
118
	/**
119
	 * Create model related entity manager.
120
	 * This will create customized entity manger if defined in model with EntityManager annotation.
121
	 * If no custom entity manager is defined this will return default EntityManager.
122
	 * @param AnnotatedInterface $model
123
	 * @param Mangan $mangan
124
	 * @return EntityManagerInterface
125
	 */
126 77
	public static function create($model, Mangan $mangan = null)
127
	{
128 77
		$emClass = ManganMeta::create($model)->type()->entityManager ? : static::class;
129 77
		return new $emClass($model, $mangan);
130
	}
131
132
	/**
133
	 * Set attributes en masse.
134
	 * Attributes will be filtered according to SafeAnnotation.
135
	 * Only attributes marked as safe will be set, other will be ignored.
136
	 *
137
	 * @param mixed[] $atributes
138
	 */
139
	public function setAttributes($atributes)
140
	{
141
		SafeArray::toModel($atributes, $this->model, $this->model);
142
	}
143
144
	/**
145
	 * Inserts a row into the table based on this active record attributes.
146
	 * If the table's primary key is auto-incremental and is null before insertion,
147
	 * it will be populated with the actual value after insertion.
148
	 *
149
	 * Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
150
	 * After the record is inserted to DB successfully, its {@link isNewRecord} property will be set false,
151
	 * and its {@link scenario} property will be set to be 'update'.
152
	 *
153
	 * @param AnnotatedInterface $model if want to insert different model than set in constructor
154
	 *
155
	 * @return boolean whether the attributes are valid and the record is inserted successfully.
156
	 * @throws ManganException if the record is not new
157
	 * @throws ManganException on fail of insert or insert of empty document
158
	 * @throws ManganException on fail of insert, when safe flag is set to true
159
	 * @throws ManganException on timeout of db operation , when safe flag is set to true
160
	 * @since v1.0
161
	 */
162 30
	public function insert(AnnotatedInterface $model = null)
163
	{
164 30
		$model = $model ? : $this->model;
165 30
		if ($this->_beforeSave($model, EntityManagerInterface::EventBeforeInsert))
166 30
		{
167 30
			$rawData = RawArray::fromModel($model);
168 30
			$rawResult = $this->_collection->insert($rawData, $this->options->getSaveOptions());
169 30
			$result = $this->_result($rawResult, true);
170
171
			if ($result)
172 30
			{
173 30
				$this->_afterSave($model, EntityManagerInterface::EventAfterInsert);
174 30
				return true;
175
			}
176
			throw new ManganException('Can\t save the document to disk, or attempting to save an empty document.');
177
		}
178
		return false;
179
	}
180
181
	/**
182
	 * Updates the row represented by this active document.
183
	 * All loaded attributes will be saved to the database.
184
	 * Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
185
	 *
186
	 * @param array $attributes list of attributes that need to be saved. Defaults to null,
187
	 * meaning all attributes that are loaded from DB will be saved.
188
189
	 * @return boolean whether the update is successful
190
	 * @throws ManganException if the record is new
191
	 * @throws ManganException on fail of update
192
	 * @throws ManganException on timeout of db operation , when safe flag is set to true
193
	 * @since v1.0
194
	 */
195 5
	public function update(array $attributes = null)
196
	{
197 5
		if ($this->_beforeSave($this->model, EntityManagerInterface::EventBeforeUpdate))
198 5
		{
199 5
			$criteria = PkManager::prepareFromModel($this->model);
200 5
			$result = $this->updateOne($criteria, $attributes);
201
			if ($result)
202 5
			{
203 5
				$this->_afterSave($this->model, EntityManagerInterface::EventAfterUpdate);
204 5
				return true;
205
			}
206
			throw new ManganException('Can\t save the document to disk, or attempting to save an empty document.');
207
		}
208
		return false;
209
	}
210
211
	/**
212
	 * Updates one document with the specified criteria and attributes
213
	 *
214
	 * This is more *raw* update:
215
	 *
216
	 * * Does not raise any events or signals
217
	 * * Does not perform any validation
218
	 *
219
	 * @param array|CriteriaInterface $criteria query criteria.
220
	 * @param array $attributes list of attributes that need to be saved. Defaults to null,
221
	 * @param bool Whether tu force update/upsert document
222
	 * meaning all attributes that are loaded from DB will be saved.
223
	 * @since v1.0
224
	 */
225 56
	public function updateOne($criteria = null, array $attributes = null, $modify = false)
226
	{
227 56
		$criteria = $this->sm->apply($criteria);
228 56
		$rawData = RawArray::fromModel($this->model);
229
230
		// filter attributes if set in param
231 56
		if ($attributes !== null)
232 56
		{
233 1
			$modify = true;
234 1
			foreach ($rawData as $key => $value)
235
			{
236 1
				if (!in_array($key, $attributes))
237 1
				{
238 1
					unset($rawData[$key]);
239 1
				}
240 1
			}
241 1
		}
242
		else
243
		{
244 56
			$fields = array_keys(ManganMeta::create($this->model)->fields());
245 56
			$setFields = array_keys($rawData);
246 56
			$diff = array_diff($fields, $setFields);
247
248 56
			if (!empty($diff))
249 56
			{
250 5
				$modify = true;
251 5
			}
252
		}
253
		if ($modify)
254 56
		{
255
			// Id could be altered, so skip it as it cannot be changed
256 6
			unset($rawData['_id']);
257 6
			$data = ['$set' => $rawData];
258 6
		}
259
		else
260
		{
261 53
			$data = $rawData;
262
		}
263
264 56
		$result = $this->getCollection()->update($criteria->getConditions(), $data, $this->options->getSaveOptions(['multiple' => false, 'upsert' => true]));
265 56
		return $this->_result($result);
266
	}
267
268
	/**
269
	 * Atomic, in-place update method.
270
	 *
271
	 * @since v1.3.6
272
	 * @param Modifier $modifier updating rules to apply
273
	 * @param CriteriaInterface $criteria condition to limit updating rules
274
	 * @return boolean
275
	 */
276 1
	public function updateAll(Modifier $modifier, CriteriaInterface $criteria = null)
277
	{
278 1
		if ($modifier->canApply())
279 1
		{
280 1
			$criteria = $this->sm->apply($criteria);
281 1
			$result = $this->getCollection()->update($criteria->getConditions(), $modifier->getModifiers(), $this->options->getSaveOptions([
282 1
						'upsert' => false,
283
						'multiple' => true
284 1
			]));
285 1
			return $this->_result($result);
286
		}
287
		else
288
		{
289
			return false;
290
		}
291
	}
292
293
	/**
294
	 * Replaces the current document.
295
	 *
296
	 * **NOTE: This will overwrite entire document.**
297
	 * Any filtered out properties will be removed as well.
298
	 *
299
	 * The record is inserted as a documnent into the database collection, if exists it will be replaced.
300
	 *
301
	 * Validation will be performed before saving the record. If the validation fails,
302
	 * the record will not be saved. You can call {@link getErrors()} to retrieve the
303
	 * validation errors.
304
	 *
305
	 * @param boolean $runValidation whether to perform validation before saving the record.
306
	 * If the validation fails, the record will not be saved to database.
307
	 * @param AnnotatedInterface $model if want to insert different model than set in constructor
0 ignored issues
show
Bug introduced by
There is no parameter named $model. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
308
	 * @return boolean whether the saving succeeds
309
	 * @since v1.0
310
	 */
311
	public function replace($runValidation = true)
312
	{
313
		if (!$runValidation || $this->validator->validate())
314
		{
315
			$model = $this->model;
316
			if ($this->_beforeSave($model))
317
			{
318
				$data = RawArray::fromModel($model);
319
				$rawResult = $this->_collection->save($data, $this->options->getSaveOptions());
320
				$result = $this->_result($rawResult, true);
321
322
				if ($result)
323
				{
324
					$this->_afterSave($model);
325
					return true;
326
				}
327
				throw new ManganException("Can't save the document to disk, or attempting to save an empty document");
328
			}
329
			return false;
330
		}
331
		else
332
		{
333
			return false;
334
		}
335
	}
336
337
	/**
338
	 * Saves the current document.
339
	 *
340
	 * The record is inserted as a document into the database collection or updated if exists.
341
	 *
342
	 * Filtered out properties will remain in database - it is partial safe.
343
	 *
344
	 * Validation will be performed before saving the record. If the validation fails,
345
	 * the record will not be saved. You can call {@link getErrors()} to retrieve the
346
	 * validation errors.
347
	 *
348
	 * @param boolean $runValidation whether to perform validation before saving the record.
349
	 * If the validation fails, the record will not be saved to database.
350
	 * @param AnnotatedInterface $model if want to insert different model than set in constructor
0 ignored issues
show
Bug introduced by
There is no parameter named $model. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
351
	 * @return boolean whether the saving succeeds
352
	 * @since v1.0
353
	 */
354 55
	public function save($runValidation = true)
355
	{
356 55
		return $this->upsert($runValidation);
357
	}
358
359
	/**
360
	 * Updates or inserts the current document. This will try to update existing fields.
361
	 * Will keep already stored data if present in document.
362
	 *
363
	 * If document does not exist, a new one will be inserted.
364
	 *
365
	 * @param boolean $runValidation
366
	 * @return boolean
367
	 * @throws ManganException
368
	 */
369 57
	public function upsert($runValidation = true)
370
	{
371 57
		if (!$runValidation || $this->validator->validate())
372 57
		{
373 57
			$model = $this->model;
374 57
			if ($this->_beforeSave($model))
375 57
			{
376 54
				$criteria = PkManager::prepareFromModel($this->model);
377 54
				foreach ($criteria->getConditions() as $field => $value)
378
				{
379 54
					if (empty($this->model->$field))
380 54
					{
381 32
						$this->model->$field = $value;
382 32
					}
383 54
				}
384 54
				$result = $this->updateOne($criteria);
385
386
				if ($result)
387 54
				{
388 54
					$this->_afterSave($model);
389 54
					return true;
390
				}
391
				throw new ManganException("Can't save the document to disk, or attempting to save an empty document");
392
			}
393 3
			return false;
394
		}
395
		else
396
		{
397 2
			return false;
398
		}
399
	}
400
401
	/**
402
	 * Reloads document from database.
403
	 * It return true if document is reloaded and false if it's no longer exists.
404
	 *
405
	 * @return boolean
406
	 */
407 2
	public function refresh()
408
	{
409 2
		$conditions = PkManager::prepareFromModel($this->model)->getConditions();
410 2
		$data = $this->getCollection()->findOne($conditions);
411 2
		if (null !== $data)
412 2
		{
413 2
			RawArray::toModel($data, $this->model, $this->model);
414 2
			return true;
415
		}
416
		else
417
		{
418 2
			return false;
419
		}
420
	}
421
422
	/**
423
	 * Deletes the document from database.
424
	 * @return boolean whether the deletion is successful.
425
	 * @throws ManganException if the record is new
426
	 */
427 8
	public function delete()
428
	{
429 8
		if ($this->_beforeDelete())
430 8
		{
431 7
			$result = $this->deleteOne(PkManager::prepareFromModel($this->model));
432
433 7
			if ($result !== false)
434 7
			{
435 7
				$this->_afterDelete();
436 7
				return true;
437
			}
438
			else
439
			{
440 1
				return false;
441
			}
442
		}
443
		else
444
		{
445 1
			return false;
446
		}
447
	}
448
449
	/**
450
	 * Deletes one document with the specified primary keys.
451
	 * <b>Does not raise beforeDelete</b>
452
	 * See {@link find()} for detailed explanation about $condition and $params.
453
	 * @param array|CriteriaInterface $criteria query criteria.
454
	 * @since v1.0
455
	 */
456 7
	public function deleteOne($criteria = null)
457
	{
458 7
		$criteria = $this->sm->apply($criteria);
459
460 7
		$result = $this->getCollection()->remove($criteria->getConditions(), $this->options->getSaveOptions([
461
					'justOne' => true
462 7
		]));
463 7
		return $this->_result($result);
464
	}
465
466
	/**
467
	 * Deletes document with the specified primary key.
468
	 * See {@link find()} for detailed explanation about $condition and $params.
469
	 * @param mixed $pkValue primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value).
470
	 * @param array|CriteriaInterface $criteria query criteria.
471
	 * @since v1.0
472
	 */
473 2
	public function deleteByPk($pkValue, $criteria = null)
474
	{
475 2
		if ($this->_beforeDelete())
476 2
		{
477 1
			$criteria = $this->sm->apply($criteria);
478 1
			$criteria->mergeWith(PkManager::prepare($this->model, $pkValue));
479
480 1
			$result = $this->getCollection()->remove($criteria->getConditions(), $this->options->getSaveOptions([
481
						'justOne' => true
482 1
			]));
483 1
			return $this->_result($result);
484
		}
485 1
		return false;
486
	}
487
488
	/**
489
	 * Deletes documents with the specified primary keys.
490
	 * See {@link find()} for detailed explanation about $condition and $params.
491
	 * @param mixed[] $pkValues Primary keys array
492
	 * @param array|CriteriaInterface $criteria query criteria.
493
	 * @since v1.0
494
	 */
495 2
	public function deleteAllByPk($pkValues, $criteria = null)
496
	{
497 2
		if ($this->_beforeDelete())
498 2
		{
499 1
			$criteria = $this->sm->apply($criteria);
500 1
			$criteria->mergeWith(PkManager::prepareAll($this->model, $pkValues, $criteria));
501 1
			$result = $this->getCollection()->remove($criteria->getConditions(), $this->options->getSaveOptions([
502
						'justOne' => false
503 1
			]));
504 1
			return $this->_result($result);
505
		}
506 1
		return false;
507
	}
508
509
	/**
510
	 * Deletes documents with the specified primary keys.
511
	 *
512
	 * **Does not raise beforeDelete event and does not emit signals**
513
	 *
514
	 * See {@link find()} for detailed explanation about $condition and $params.
515
	 *
516
	 * @param array|CriteriaInterface $criteria query criteria.
517
	 * @since v1.0
518
	 */
519 85
	public function deleteAll($criteria = null)
520
	{
521 3
		$criteria = $this->sm->apply($criteria);
522
523 3
		$result = $this->getCollection()->remove($criteria->getConditions(), $this->options->getSaveOptions([
524
					'justOne' => false
525 3
		]));
526 3
		return $this->_result($result);
527 85
	}
528
529 80
	public function getCollection()
530
	{
531 80
		return $this->_collection;
532
	}
533
534
	/**
535
	 * Make status uniform
536
	 * @param bool|array $result
537
	 * @param bool $insert Set to true for inserts
538
	 * @return bool Return true if secceed
539
	 */
540 81
	private function _result($result, $insert = false)
541
	{
542 81
		if (is_array($result))
543 81
		{
544
			if ($insert)
545 81
			{
546 30
				return (bool) $result['ok'];
547
			}
548 56
			return (bool) $result['n'];
549
		}
550
		return $result;
551
	}
552
553
// <editor-fold defaultstate="collapsed" desc="Event and Signal handling">
554
555
	/**
556
	 * Take care of EventBeforeSave
557
	 * @see EventBeforeSave
558
	 * @return boolean
559
	 */
560 83
	private function _beforeSave($model, $event = null)
561
	{
562 83
		$result = Event::Valid($model, EntityManagerInterface::EventBeforeSave);
563
		if ($result)
564 83
		{
565 80
			if (!empty($event))
566 80
			{
567 35
				Event::trigger($model, $event);
568 35
			}
569 80
			(new Signal)->emit(new BeforeSave($model));
570 80
		}
571 83
		return $result;
572
	}
573
574
	/**
575
	 * Take care of EventAfterSave
576
	 * @see EventAfterSave
577
	 */
578 80
	private function _afterSave($model, $event = null)
579
	{
580 80
		Event::trigger($model, EntityManagerInterface::EventAfterSave);
581 80
		if (!empty($event))
582 80
		{
583 35
			Event::trigger($model, $event);
584 35
		}
585 80
		(new Signal)->emit(new AfterSave($model));
586 80
		ScenarioManager::setScenario($model, ScenariosInterface::Update);
587 80
	}
588
589
	/**
590
	 * This method is invoked before deleting a record.
591
	 * The default implementation raises the {@link onBeforeDelete} event.
592
	 * You may override this method to do any preparation work for record deletion.
593
	 * Make sure you call the parent implementation so that the event is raised properly.
594
	 * @return boolean whether the record should be deleted. Defaults to true.
595
	 * @since v1.0
596
	 */
597 10
	private function _beforeDelete()
598
	{
599 10
		$result = Event::valid($this->model, EntityManagerInterface::EventBeforeDelete);
600
		if ($result)
601 10
		{
602 9
			(new Signal)->emit(new BeforeDelete($this->model));
603 9
			ScenarioManager::setScenario($this->model, ScenariosInterface::Delete);
604 9
		}
605 10
		return $result;
606
	}
607
608
	/**
609
	 * This method is invoked after deleting a record.
610
	 * The default implementation raises the {@link onAfterDelete} event.
611
	 * You may override this method to do postprocessing after the record is deleted.
612
	 * Make sure you call the parent implementation so that the event is raised properly.
613
	 * @since v1.0
614
	 */
615 7
	private function _afterDelete()
616
	{
617 7
		Event::trigger($this->model, EntityManagerInterface::EventAfterDelete, new ModelEvent($this->model));
618 7
		(new Signal)->emit(new AfterDelete($this->model));
619 7
	}
620
621
// </editor-fold>
622
}
623