Transformer::toModel()   F
last analyzed

Complexity

Conditions 14
Paths 480

Size

Total Lines 94

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 44
CRAP Score 14.0021

Importance

Changes 0
Metric Value
dl 0
loc 94
ccs 44
cts 45
cp 0.9778
rs 2.3886
c 0
b 0
f 0
cc 14
nc 480
nop 5
crap 14.0021

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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