Completed
Push — master ( 69ef22...b74acb )
by Peter
06:58
created

Transformer::ensureClass()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 10.1554

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 23
ccs 3
cts 11
cp 0.2727
rs 8.7972
cc 4
eloc 14
nc 3
nop 1
crap 10.1554
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 143
	public static function fromModel(AnnotatedInterface $model, $fields = [])
47
	{
48 143
		$meta = static::getMeta($model);
49 143
		$calledClass = get_called_class();
50 143
		$decorator = new Decorator($model, $calledClass, $meta);
51 143
		$md = new ModelDecorator($model, $calledClass, $meta);
52 143
		$sanitizer = new Sanitizer($model, $calledClass, $meta);
53 143
		$filter = new Filter($model, $calledClass, $meta);
54 143
		$arr = [];
55 143
		foreach ($meta->fields() as $name => $fieldMeta)
56
		{
57 143
			if (!empty($fields) && !in_array($name, $fields))
58
			{
59 106
				continue;
60
			}
61 143
			if (!$filter->fromModel($model, $fieldMeta))
62
			{
63 42
				continue;
64
			}
65 143
			$model->$name = $sanitizer->write($name, $model->$name);
66 143
			$decorator->write($name, $arr);
67 143
			$model->$name = $sanitizer->read($name, $model->$name);
68
		}
69 143
		$md->write($arr);
70 143
		return FinalizingManager::fromModel($arr, static::class, $model);
71
	}
72
73
	/**
74
	 * Create document from array
75
	 *
76
	 * @param mixed[] $data
77
	 * @param string|object $className
78
	 * @param AnnotatedInterface $instance
79
	 * @return AnnotatedInterface
80
	 * @throws TransformatorException
81
	 */
82 103
	public static function toModel($data, $className = null, AnnotatedInterface $instance = null)
83
	{
84 103
		$data = (array) $data;
85 103
		if (is_object($className))
86
		{
87 83
			$className = get_class($className);
88
		}
89 103
		if (!$className)
90
		{
91 42
			if (array_key_exists('_class', $data))
92
			{
93 40
				$className = $data['_class'];
94
			}
95
			else
96
			{
97 2
				if (null !== $instance)
98
				{
99 2
					$className = get_class($instance);
100
				}
101
				else
102
				{
103
					throw new TransformatorException('Could not determine document type');
104
				}
105
			}
106
		}
107 103
		if ($instance)
108
		{
109 8
			$model = $instance;
110
		}
111
		else
112
		{
113 99
			self::ensureClass($className);
114 99
			$model = new $className;
115
		}
116 103
		$meta = static::getMeta($model);
117 103
		$calledClass = get_called_class();
118 103
		$decorator = new Decorator($model, $calledClass, $meta);
119 103
		$md = new ModelDecorator($model, $calledClass, $meta);
120 103
		$sanitizer = new Sanitizer($model, $calledClass, $meta);
121 103
		$filter = new Filter($model, $calledClass, $meta);
122
123
		// Ensure that primary keys are processed first,
124
		// as in some cases those could be processed *after* related
125
		// document(s), which results in wrong _id (or pk) being passed.
126 103
		$fieldsMeta = (array) $meta->fields();
127 103
		$pks = (array)PkManager::getPkKeys($model);
128 103
		foreach($pks as $key)
129
		{
130 103
			if(!array_key_exists($key, $fieldsMeta))
131
			{
132 17
				continue;
133
			}
134 102
			$pkMeta = $fieldsMeta[$key];
135 102
			unset($fieldsMeta[$key]);
136 102
			$fieldsMeta = array_merge([$key => $pkMeta], $fieldsMeta);
137
		}
138
139 103
		foreach ($fieldsMeta as $name => $fieldMeta)
140
		{
141
			/* @var $fieldMeta DocumentPropertyMeta */
142 103
			if (array_key_exists($name, $data))
143
			{
144
				// Value is available in passed data
145 103
				$value = $data[$name];
146
			}
147 24
			elseif (!empty($instance))
148
			{
149
				// Take value from existing instance
150
				// NOTE: We could `continue` here but value should be sanitized anyway
151 5
				$value = $model->$name;
152
			}
153
			else
154
			{
155
				// As a last resort set to default
156 20
				$value = $fieldMeta->default;
157
			}
158 103
			if (!$filter->toModel($model, $fieldMeta))
159
			{
160 26
				continue;
161
			}
162 103
			$decorator->read($name, $value);
163 103
			$model->$name = $sanitizer->read($name, $model->$name);
164
		}
165 103
		$md->read($data);
166
167 103
		return FinalizingManager::toModel(static::class, $model);
168
	}
169
170 159
	protected static function getMeta(AnnotatedInterface $model)
171
	{
172 159
		return ManganMeta::create($model);
173
	}
174
175 99
	protected static function ensureClass(&$class)
176
	{
177 99
		if (!ClassChecker::exists($class))
178
		{
179
			$event = new ClassNotFound;
180
			$event->notFound = $class;
181
			if (Event::hasHandler(AnnotatedInterface::class, NotFoundResolver::EventClassNotFound) && Event::handled(AnnotatedInterface::class, NotFoundResolver::EventClassNotFound, $event))
182
			{
183
				$class = $event->replacement;
184
			}
185
			else
186
			{
187
				$params = [
188
					$class,
189
					NotFoundResolver::class,
190
					NotFoundResolver::EventClassNotFound,
191
					AnnotatedInterface::class
192
				];
193
				$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);
194
				throw new ManganException($msg);
195
			}
196
		}
197 99
	}
198
}
199