Completed
Push — master ( 2eadc8...285683 )
by Peter
06:33
created

Transformer   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 13

Test Coverage

Coverage 84.51%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 22
c 4
b 0
f 0
lcom 0
cbo 13
dl 0
loc 189
ccs 60
cts 71
cp 0.8451
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
B fromModel() 0 38 5
F toModel() 0 87 12
A getMeta() 0 4 1
B ensureClass() 0 23 4
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\Transformers;
15
16
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
17
use Maslosoft\Addendum\Utilities\ClassChecker;
18
use Maslosoft\Mangan\Events\ClassNotFound;
19
use Maslosoft\Mangan\Events\Event;
20
use Maslosoft\Mangan\Exceptions\ManganException;
21
use Maslosoft\Mangan\Exceptions\TransformatorException;
22
use Maslosoft\Mangan\Helpers\Decorator\Decorator;
23
use Maslosoft\Mangan\Helpers\Decorator\ModelDecorator;
24
use Maslosoft\Mangan\Helpers\Finalizer\FinalizingManager;
25
use Maslosoft\Mangan\Helpers\NotFoundResolver;
26
use Maslosoft\Mangan\Helpers\PkManager;
27
use Maslosoft\Mangan\Helpers\PropertyFilter\Filter;
28
use Maslosoft\Mangan\Helpers\Sanitizer\Sanitizer;
29
use Maslosoft\Mangan\Meta\DocumentPropertyMeta;
30
use Maslosoft\Mangan\Meta\ManganMeta;
31
32
/**
33
 * Transformer
34
 *
35
 * @author Piotr Maselkowski <pmaselkowski at gmail.com>
36
 */
37
abstract class Transformer
38
{
39
40
	/**
41
	 * Returns the given object as an associative array
42
	 * @param AnnotatedInterface|object $model
43
	 * @param string[] $fields Fields to transform
44
	 * @return array an associative array of the contents of this object
45
	 */
46 152
	public static function fromModel(AnnotatedInterface $model, $fields = [])
47
	{
48 152
		$meta = ManganMeta::create($model);
49 152
		$calledClass = get_called_class();
50 152
		$decorator = new Decorator($model, $calledClass, $meta);
51 152
		$md = new ModelDecorator($model, $calledClass, $meta);
52 152
		$sanitizer = new Sanitizer($model, $calledClass, $meta);
53 152
		$filter = new Filter($model, $calledClass, $meta);
54 152
		$arr = [];
55 152
		foreach ($meta->fields() as $name => $fieldMeta)
56
		{
57 152
			if (!empty($fields) && !in_array($name, $fields))
58
			{
59 115
				continue;
60
			}
61 152
			if (!$filter->fromModel($model, $fieldMeta))
62
			{
63 46
				continue;
64
			}
65
66
			// NOTE: Sanitizers must be ran for all
67
			// fields, as types *might* change between
68
			// transformations.
69
70
			// Set model value for writing, this might
71
			// cause data type to change. This is
72
			// required for decorators.
73 152
			$model->$name = $sanitizer->write($name, $model->$name);
74 152
			$decorator->write($name, $arr);
75
76
			// Sanitize value again to ensure that model
77
			// has value of proper type, defined for
78
			// transformer type.
79 152
			$model->$name = $sanitizer->read($name, $model->$name);
80
		}
81 152
		$md->write($arr);
82 152
		return FinalizingManager::fromModel($arr, static::class, $model);
83
	}
84
85
	/**
86
	 * Create document from array
87
	 *
88
	 * @param mixed[]            $data
89
	 * @param string|object      $className
90
	 * @param AnnotatedInterface $instance
91
	 * @return AnnotatedInterface
92
	 * @throws TransformatorException
93
	 * @throws ManganException
94
	 */
95 106
	public static function toModel($data, $className = null, AnnotatedInterface $instance = null)
96
	{
97 106
		$data = (array) $data;
98 106
		if (is_object($className))
99
		{
100 84
			$className = get_class($className);
101
		}
102 106
		if (!$className)
103
		{
104 44
			if (array_key_exists('_class', $data))
105
			{
106 42
				$className = $data['_class'];
107
			}
108
			else
109
			{
110 2
				if (null !== $instance)
111
				{
112 2
					$className = get_class($instance);
113
				}
114
				else
115
				{
116
					throw new TransformatorException('Could not determine document type');
117
				}
118
			}
119
		}
120 106
		if ($instance)
121
		{
122 8
			$model = $instance;
123
		}
124
		else
125
		{
126 102
			self::ensureClass($className);
127 102
			$model = new $className;
128
		}
129 106
		$meta = ManganMeta::create($model);
130 106
		$calledClass = get_called_class();
131 106
		$decorator = new Decorator($model, $calledClass, $meta);
132 106
		$md = new ModelDecorator($model, $calledClass, $meta);
133 106
		$sanitizer = new Sanitizer($model, $calledClass, $meta);
134 106
		$filter = new Filter($model, $calledClass, $meta);
135
136
		// Ensure that primary keys are processed first,
137
		// as in some cases those could be processed *after* related
138
		// document(s), which results in wrong _id (or pk) being passed.
139 106
		$fieldsMeta = (array) $meta->fields();
140 106
		$pks = (array)PkManager::getPkKeys($model);
141 106
		foreach($pks as $key)
142
		{
143 106
			if(!array_key_exists($key, $fieldsMeta))
144
			{
145 17
				continue;
146
			}
147 105
			$pkMeta = $fieldsMeta[$key];
148 105
			unset($fieldsMeta[$key]);
149 105
			$fieldsMeta = array_merge([$key => $pkMeta], $fieldsMeta);
150
		}
151
152 106
		foreach ($fieldsMeta as $name => $fieldMeta)
153
		{
154
			/* @var $fieldMeta DocumentPropertyMeta */
155 106
			if (array_key_exists($name, $data))
156
			{
157
				// Value is available in passed data
158 106
				$value = $data[$name];
159
			}
160 24
			elseif (!empty($instance))
161
			{
162
				// Take value from existing instance
163
				// NOTE: We could `continue` here but value should be sanitized anyway
164 5
				$value = $model->$name;
165
			}
166
			else
167
			{
168
				// As a last resort set to default
169 20
				$value = $fieldMeta->default;
170
			}
171 106
			if (!$filter->toModel($model, $fieldMeta))
172
			{
173 29
				continue;
174
			}
175 106
			$decorator->read($name, $value);
176 106
			$model->$name = $sanitizer->read($name, $model->$name);
177
		}
178 106
		$md->read($data);
179
180 106
		return FinalizingManager::toModel(static::class, $model);
181
	}
182
183
	/**
184
	 * Get metadata for model
185
	 * @deprecated Use ManganMeta::create($model) instead
186
	 * @param AnnotatedInterface $model
187
	 * @return ManganMeta
188
	 */
189
	protected static function getMeta(AnnotatedInterface $model)
190
	{
191
		return ManganMeta::create($model);
192
	}
193
194
	/**
195
	 * Ensure that `$class` exists, will
196
	 * try to use class not found resolver
197
	 * to find replacements if available.
198
	 *
199
	 * @param $class
200
	 * @throws ManganException
201
	 */
202 102
	protected static function ensureClass(&$class)
203
	{
204 102
		if (!ClassChecker::exists($class))
205
		{
206
			$event = new ClassNotFound;
207
			$event->notFound = $class;
208
			if (Event::hasHandler(AnnotatedInterface::class, NotFoundResolver::EventClassNotFound) && Event::handled(AnnotatedInterface::class, NotFoundResolver::EventClassNotFound, $event))
209
			{
210
				$class = $event->replacement;
211
			}
212
			else
213
			{
214
				$params = [
215
					$class,
216
					NotFoundResolver::class,
217
					NotFoundResolver::EventClassNotFound,
218
					AnnotatedInterface::class
219
				];
220
				$msg = vsprintf("Model class `%s` not found. Attach event handler for `%s::%s` event on `%s` if You changed name to handle this case.", $params);
221
				throw new ManganException($msg);
222
			}
223
		}
224 102
	}
225
}
226