Completed
Push — master ( 507c4a...51e00b )
by Peter
07:40
created

ParentChildTrashHandlers::getPk()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 4
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 https://maslosoft.com/mangan/
12
 */
13
14
namespace Maslosoft\Mangan\Helpers;
15
16
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
17
use Maslosoft\Addendum\Utilities\ClassChecker;
18
use Maslosoft\Mangan\Criteria;
19
use Maslosoft\Mangan\Events\Event;
20
use Maslosoft\Mangan\Events\ModelEvent;
21
use Maslosoft\Mangan\Events\RestoreEvent;
22
use Maslosoft\Mangan\Helpers\Sanitizer\Sanitizer;
23
use Maslosoft\Mangan\Interfaces\EntityManagerInterface;
24
use Maslosoft\Mangan\Interfaces\TrashInterface;
25
use UnexpectedValueException;
26
27
/**
28
 * OwneredTrashHandlers
29
 * Use this class to create trash handlers for owned items.
30
 *
31
 * This class provides event handlers to properly manage trash, however it is
32
 * optional, so owned and trashable can be handled by some custom methods.
33
 * These handles are not automatically registered.
34
 *
35
 * NOTE: Register **only once per type**, or it will not work properly.
36
 *
37
 * @author Piotr Maselkowski <pmaselkowski at gmail.com>
38
 */
39
class ParentChildTrashHandlers
40
{
41
42
	/**
43
	 * Register event handlers for parent of parent-child relation.
44
	 *
45
	 * @param AnnotatedInterface|string $parent
46
	 * @param string $childClass
47
	 */
48 1
	public function registerParent($parent, $childClass)
49
	{
50 1
		if (!ClassChecker::exists($childClass))
51
		{
52
			throw new UnexpectedValueException(sprintf('Class `%s` not found', $childClass));
53
		}
54
		// Delete all of this child items after removing from trash
55 1
		$beforeDelete = function(ModelEvent $event) use($parent, $childClass)
56
		{
57 1
			$model = $event->sender;
58 1
			$event->isValid = true;
59
60 1
			if (is_a($model, $parent))
61
			{
62
				$child = new $childClass;
63
				$criteria = new Criteria(null, $child);
64
				$criteria->parentId = $this->getPk($model);
65
66
				$event->isValid = $child->deleteAll($criteria);
67
			}
68 1
			return $event->isValid;
69 1
		};
70 1
		$beforeDelete->bindTo($this);
71 1
		Event::on($parent, EntityManagerInterface::EventBeforeDelete, $beforeDelete);
72
73
		// Trash all child items from parent item
74 1
		$afterTrash = function(ModelEvent $event)use($parent, $childClass)
75
		{
76 1
			$model = $event->sender;
77 1
			$event->isValid = true;
78 1
			if (is_a($model, $parent))
79
			{
80 1
				$child = new $childClass;
81 1
				$criteria = new Criteria(null, $child);
82 1
				$criteria->parentId = $this->getPk($model);
83
84 1
				$items = $child->findAll($criteria);
85
86
				// No items found, so skip
87 1
				if (empty($items))
88
				{
89
					$event->isValid = true;
90
					return $event->isValid;
91
				}
92
93
				// Trash in loop all items
94 1
				foreach ($items as $item)
95
				{
96 1
					if (!$item->trash())
97
					{
98
						$event->isValid = false;
99 1
						return $event->isValid;
100
					}
101
				}
102
			}
103 1
			return $event->isValid;
104 1
		};
105 1
		$afterTrash->bindTo($this);
106 1
		Event::on($parent, TrashInterface::EventAfterTrash, $afterTrash);
107
108
		// Restore all child items from parent, but only those after it was trashed.
109
		// This will keep previously trashed items in trash
110 1
		$afterRestore = function(RestoreEvent $event)use($parent, $childClass)
111
		{
112 1
			$model = $event->sender;
113 1
			if (is_a($model, $parent))
114
			{
115 1
				$child = new $childClass;
116 1
				$trash = $event->getTrash();
117 1
				$criteria = new Criteria(null, $trash);
118
119
				// Conditions decorator do not work with dots so sanitize manually.
120 1
				$s = new Sanitizer($child);
121
122 1
				$id = $s->write('parentId', $this->getPk($model));
123 1
				$criteria->addCond('data.parentId', '==', $id);
124
125
				// Restore only child items trashed when parent was trashed.
126
				// Skip earlier items
127 1
				assert(isset($trash->createDate), sprintf('When implementing `%s`, `createDate` field is required and must be set to date of deletion', TrashInterface::class));
128 1
				$criteria->addCond('createDate', 'gte', $trash->createDate);
129
130 1
				$trashedItems = $trash->findAll($criteria);
131 1
				if (empty($trashedItems))
132
				{
133
					$event->isValid = true;
134
					return $event->isValid;
135
				}
136
137
				// Restore all items
138 1
				$restored = [];
139 1
				foreach ($trashedItems as $trashedItem)
140
				{
141 1
					$restored[] = (int) $trashedItem->restore();
142
				}
143 1
				if(array_sum($restored) !== count($restored))
144
				{
145
					$event->isValid = false;
146
					return $event->isValid;
147
				}
148
			}
149 1
			$event->isValid = true;
150 1
			return $event->isValid;
151 1
		};
152 1
		$afterRestore->bindTo($this);
153 1
		Event::on($parent, TrashInterface::EventAfterRestore, $afterRestore);
154 1
	}
155
156
	/**
157
	 * Register event handlers for child item of parent-child relation.
158
	 *
159
	 * @param AnnotatedInterface|string $child
160
	 * @param string $parentClass
161
	 * @throws UnexpectedValueException
162
	 */
163 1
	public function registerChild($child, $parentClass)
164
	{
165 1
		assert(ClassChecker::exists($parentClass), new UnexpectedValueException(sprintf('Class `%s` not found', $parentClass)));
166
167
		// Prevent restoring item if parent does not exists
168 1
		$beforeRestore = function(ModelEvent $event)use($child, $parentClass)
169
		{
170 1
			$model = $event->sender;
171
172 1
			if (is_a($model, $child))
173
			{
174 1
				$parent = new $parentClass;
175 1
				$criteria = new Criteria(null, $parent);
176 1
				assert(isset($model->parentId));
177 1
				$criteria->_id = $model->parentId;
178 1
				if (!$parent->exists($criteria))
179
				{
180 1
					$event->isValid = false;
181 1
					return $event->isValid;
182
				}
183
			}
184 1
			$event->isValid = true;
185 1
			return $event->isValid;
186 1
		};
187 1
		Event::on($child, TrashInterface::EventBeforeRestore, $beforeRestore);
188 1
	}
189
190 1
	private function getPk(AnnotatedInterface $model)
191
	{
192 1
		$pk = PkManager::getFromModel($model);
193 1
		assert(!is_array($pk), 'Composite PK of `%s` not allowed for parentId');
194 1
		return $pk;
195
	}
196
197
}
198