PkManager   A
last analyzed

Complexity

Total Complexity 38

Size/Duplication

Total Lines 274
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 83.13%

Importance

Changes 0
Metric Value
wmc 38
lcom 1
cbo 5
dl 0
loc 274
ccs 69
cts 83
cp 0.8313
rs 9.36
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A prepareAll() 0 22 5
A prepare() 0 22 4
A prepareFromModel() 0 4 1
A getFromModel() 0 18 3
A getPkKeys() 0 4 2
A getFromArray() 0 18 4
A applyToModel() 0 22 4
C compare() 0 52 11
A _compareNormalize() 0 16 3
A _prepareField() 0 5 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\Mangan\Criteria;
18
use Maslosoft\Mangan\Exceptions\CriteriaException;
19
use Maslosoft\Mangan\Helpers\PkManager;
20
use Maslosoft\Mangan\Helpers\Sanitizer\Sanitizer;
21
use Maslosoft\Mangan\Interfaces\CriteriaInterface;
22
use Maslosoft\Mangan\Meta\ManganMeta;
23
use MongoId;
24
use UnexpectedValueException;
25
26
/**
27
 * Primary key manager
28
 *
29
 * @author Piotr Maselkowski <pmaselkowski at gmail.com>
30
 */
31
class PkManager
32
{
33
34
	/**
35
	 * Prepare multi pk criteria
36
	 * @param AnnotatedInterface     $model
37
	 * @param mixed[]                $pkValues
38
	 * @param CriteriaInterface|null $criteria
39
	 * @return Criteria|CriteriaInterface|null
40
	 */
41 9
	public static function prepareAll($model, $pkValues, CriteriaInterface $criteria = null)
42
	{
43 9
		if (null === $criteria)
44
		{
45
			$criteria = new Criteria();
46
		}
47 9
		assert($criteria instanceof Criteria, new UnexpectedValueException(sprintf("Unsupported criteria class, currently only `%s` is supported, `%s` given", Criteria::class, get_class($criteria))));
48 9
		$conditions = [];
49 9
		foreach ($pkValues as $pkValue)
50
		{
51 9
			$c = PkManager::prepare($model, $pkValue);
52 9
			foreach ($c->getConditions() as $field => $value)
53
			{
54 9
				$conditions[$field][] = $value;
55
			}
56
		}
57 9
		foreach ($conditions as $field => $value)
58
		{
59 9
			$criteria->addCond($field, 'in', $value);
60
		}
61 9
		return $criteria;
62
	}
63
64
	/**
65
	 * Prepare pk criteria from user provided data
66
	 * @param AnnotatedInterface $model
67
	 * @param mixed|mixed[] $pkValue
68
	 * @return Criteria
69
	 * @throws CriteriaException
70
	 */
71 109
	public static function prepare(AnnotatedInterface $model, $pkValue)
72
	{
73 109
		$pkField = self::getPkKeys($model);
74 109
		$criteria = new Criteria();
75
76 109
		if (is_array($pkField))
77
		{
78 5
			foreach ($pkField as $name)
79
			{
80 5
				if (!array_key_exists($name, $pkValue))
81
				{
82
					throw new CriteriaException(sprintf('Composite primary key field `%s` not specied for model `%s`, required fields: `%s`', $name, get_class($model), implode('`, `', $pkField)));
83
				}
84 5
				self::_prepareField($model, $name, $pkValue[$name], $criteria);
85
			}
86
		}
87
		else
88
		{
89 104
			self::_prepareField($model, $pkField, $pkValue, $criteria);
90
		}
91 109
		return $criteria;
92
	}
93
94
	/**
95
	 * Create pk criteria from model data
96
	 * @param AnnotatedInterface $model
97
	 * @return Criteria
98
	 */
99 96
	public static function prepareFromModel(AnnotatedInterface $model)
100
	{
101 96
		return self::prepare($model, self::getFromModel($model));
102
	}
103
104
	/**
105
	 * Get primary key from model
106
	 * @param AnnotatedInterface $model
107
	 * @return MongoId|mixed|mixed[]
108
	 */
109 97
	public static function getFromModel(AnnotatedInterface $model)
110
	{
111 97
		$pkField = self::getPkKeys($model);
112 97
		$pkValue = [];
113 97
		$sanitizer = new Sanitizer($model);
114 97
		if (is_array($pkField))
115
		{
116 2
			foreach ($pkField as $name)
117
			{
118 2
				$pkValue[$name] = $sanitizer->write($name, $model->$name);
119
			}
120
		}
121
		else
122
		{
123 95
			$pkValue = $sanitizer->write($pkField, $model->$pkField);
124
		}
125 97
		return $pkValue;
126
	}
127
128
	/**
129
	 * Get primary key(s).
130
	 *
131
	 * Might return single string value for one primary key, or array
132
	 * for composite keys.
133
	 *
134
	 *
135
	 * @param AnnotatedInterface $model
136
	 * @return string|string[]
137
	 */
138 146
	public static function getPkKeys(AnnotatedInterface $model)
139
	{
140 146
		return ManganMeta::create($model)->type()->primaryKey ?: '_id';
141
	}
142
143
	/**
144
	 * Get pk criteria from raw array
145
	 * @param mixed[] $data
146
	 * @param AnnotatedInterface $model
147
	 * @return mixed[]
148
	 */
149 5
	public static function getFromArray($data, AnnotatedInterface $model)
150
	{
151 5
		$pkField = ManganMeta::create($model)->type()->primaryKey ?: '_id';
152 5
		$pkValue = [];
153 5
		$sanitizer = new Sanitizer($model);
154 5
		if (is_array($pkField))
155
		{
156
			foreach ($pkField as $name)
157
			{
158
				$pkValue[$name] = $sanitizer->write($name, $data[$name]);
159
			}
160
		}
161
		else
162
		{
163 5
			$pkValue = $sanitizer->write($pkField, $data[$pkField]);
164
		}
165 5
		return $pkValue;
166
	}
167
168
	/**
169
	 * Apply pk value to model
170
	 * @param AnnotatedInterface $model
171
	 * @param MongoId|mixed|mixed[] $pkValue
172
	 * @return mixed Primary key value
173
	 * @throws CriteriaException
174
	 */
175 12
	public static function applyToModel(AnnotatedInterface $model, $pkValue)
176
	{
177 12
		$pkField = self::getPkKeys($model);
178
179 12
		$sanitizer = new Sanitizer($model);
180 12
		if (is_array($pkField))
181
		{
182
			foreach ($pkField as $name)
183
			{
184
				if (!array_key_exists($name, $pkValue))
185
				{
186
					throw new CriteriaException(sprintf('Composite primary key field `%s` not specied for model `%s`, required fields: `%s`', $name, get_class($model), implode('`, `', $pkField)));
187
				}
188
				$model->$name = $sanitizer->read($name, $pkValue[$name]);
189
			}
190
		}
191
		else
192
		{
193 12
			$model->$pkField = $sanitizer->read($pkField, $pkValue);
194
		}
195 12
		return $pkValue;
196
	}
197
198
	/**
199
	 * Compare primary keys. For both params primary keys values or models can be used.
200
	 * Example use:
201
	 * <pre>
202
	 * <code>
203
	 * $model = new Model();
204
	 * $pk = ['_id' => new MongoId()];
205
	 * PkManager::compare($model, $pk);
206
	 *
207
	 * $pk1 = ['keyOne' => 1, 'keyTwo' => 2];
208
	 * $pk2 = ['keyOne' => 1, 'keyTwo' => 2];;
209
	 * PkManager::compare($pk1, $pk2);
210
	 *
211
	 * $model1 = new Model();
212
	 * $model2 = new Model();
213
	 * PkManager::compare($model1, $model2);
214
	 *
215
	 * </code>
216
	 * </pre>
217
	 * @param AnnotatedInterface|mixed[] $source
218
	 * @param AnnotatedInterface|mixed[] $target
219
	 * @return boolean true if pk's points to same document
220
	 */
221 8
	public static function compare($source, $target)
222
	{
223
		// Check if both params are models
224 8
		if ($source instanceof AnnotatedInterface && $target instanceof AnnotatedInterface)
225
		{
226
			// If different types return false
227 3
			if (!$source instanceof $target)
228
			{
229
				return false;
230
			}
231
		}
232
233 8
		$src = self::_compareNormalize($source);
234 8
		$trg = self::_compareNormalize($target);
235
236
		// Different pk's
237 8
		if (count($src) !== count($trg))
238
		{
239
			return false;
240
		}
241
242
		// Different pk keys
243 8
		if (array_keys($src) !== array_keys($trg))
244
		{
245
			return false;
246
		}
247
248
		// Compare values
249 8
		foreach ($src as $name => $srcVal)
250
		{
251
			// This is safe as keys are checked previously
252 8
			$trgVal = $trg[$name];
253
254
			// Special case for mongo id
255 8
			if ($srcVal instanceof \MongoId || $trgVal instanceof \MongoId)
256
			{
257 8
				if ((string) $srcVal !== (string) $trgVal)
258
				{
259 8
					return false;
260
				}
261 7
				continue;
262
			}
263
264
			// Finally compare values
265
			if ($srcVal !== $trgVal)
266
			{
267
				return false;
268
			}
269
		}
270
271 7
		return true;
272
	}
273
274 8
	private static function _compareNormalize($value)
275
	{
276 8
		if ($value instanceof AnnotatedInterface)
277
		{
278 3
			$value = self::getFromModel($value);
279
		}
280
281
		// Simple pk
282 8
		if (!is_array($value))
283
		{
284 8
			return [$value];
285
		}
286
287
		// Composite pk
288
		return $value;
289
	}
290
291
	/**
292
	 * Create pk criteria for single field
293
	 * @param AnnotatedInterface $model Model instance
294
	 * @param string $name
295
	 * @param mixed $value
296
	 * @param Criteria $criteria
297
	 */
298 109
	private static function _prepareField(AnnotatedInterface $model, $name, $value, Criteria &$criteria)
299
	{
300 109
		$sanitizer = new Sanitizer($model);
301 109
		$criteria->addCond($name, '==', $sanitizer->write($name, $value));
302 109
	}
303
304
}
305