Completed
Push — master ( f580c3...778708 )
by Peter
06:01
created

Transformer::fromModel()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 5

Importance

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