Completed
Push — master ( 0a999d...b4b35b )
by Peter
13:24 queued 02:45
created

Validator::validate()   C

Complexity

Conditions 13
Paths 46

Size

Total Lines 78
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 13.0042

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 78
ccs 33
cts 34
cp 0.9706
rs 5.1663
c 1
b 0
f 0
cc 13
eloc 37
nc 46
nop 1
crap 13.0042

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;
15
16
use InvalidArgumentException;
17
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
18
use Maslosoft\Mangan\Helpers\Validator\Factory;
19
use Maslosoft\Mangan\Interfaces\ValidatableInterface;
20
use Maslosoft\Mangan\Meta\ManganMeta;
21
22
/**
23
 * Validator
24
 *
25
 * @author Piotr Maselkowski <pmaselkowski at gmail.com>
26
 */
27
class Validator implements ValidatableInterface
28
{
29
30
	const EventBeforeValidate = 'beforeValidate';
31
	const EventAfterValidate = 'afterValidate';
32
33
	/**
34
	 * Model instance
35
	 * @var AnnotatedInterface
36
	 */
37
	private $model = null;
38
39
	/**
40
	 * Metadata for model
41
	 * @var ManganMeta
42
	 */
43
	private $meta = null;
44
45
	/**
46
	 * Array of error messages.
47
	 * Keys are field names, secondary keys are numeric
48
	 * @var string[][]
49
	 */
50
	private $errors = [];
51
52 112
	public function __construct(AnnotatedInterface $model)
53
	{
54 112
		$this->model = $model;
55 112
		$this->meta = ManganMeta::create($this->model);
56
57
		// Ensure that errors array is initialized - even if does not have validators
58 112
		foreach (array_keys($this->meta->fields()) as $name)
59
		{
60 112
			$this->errors[$name] = [];
61
		}
62 112
	}
63
64
	/**
65
	 * Validate model, optionally only selected fields
66
	 * @param string[] $fields
67
	 * @return boolean
68
	 */
69 79
	public function validate($fields = [])
70
	{
71 79
		$valid = [];
72 79
		if (empty($fields))
73
		{
74 79
			$fields = array_keys($this->meta->fields());
75
		}
76 79
		foreach ($fields as $name)
77
		{
78 79
			$fieldMeta = $this->meta->field($name);
79
80
			// Reset errors
81 79
			$this->errors[$name] = [];
82
83
			// Check if meta for field exists
84 79
			if (empty($fieldMeta))
85
			{
86
				throw new InvalidArgumentException(sprintf("Unknown field `%s` in model `%s`", $name, get_class($this->model)));
87
			}
88
89
			// Validate sub documents
90
			// Skip fields that are not updatable
91 79
			if ($fieldMeta->owned && $fieldMeta->updatable)
92
			{
93 26
				if (is_array($this->model->$name))
94
				{
95
					// Handle arrays of documents
96 11
					foreach ($this->model->$name as $fieldIndex => $model)
97
					{
98 11
						$validator = new Validator($model);
99 11
						$isValid = $validator->validate();
100 11
						$valid[] = (int) $isValid;
101 11
						if (!$isValid)
102
						{
103
							$errors = [
104
								$name => [
105 3
									$fieldIndex => $validator->getErrors()
106
								]
107
							];
108 11
							$this->setErrors($errors);
109
						}
110
					}
111
				}
112 19
				elseif (!empty($this->model->$name))
113
				{
114
					// Handle single documents
115 19
					$validator = new Validator($this->model->$name);
116 19
					$isValid = $validator->validate();
117 19
					$valid[] = (int) $isValid;
118 19
					if (!$isValid)
119
					{
120
						$errors = [
121 3
							$name => $validator->getErrors()
122
						];
123 3
						$this->setErrors($errors);
124
					}
125
				}
126
			}
127
128
			// Skip field without validators
129 79
			if (empty($fieldMeta->validators))
130
			{
131 78
				continue;
132
			}
133 18
			$valid[] = (int) $this->validateEntity($name, $fieldMeta->validators);
134
		}
135
136
		// Model validators
137 79
		$typeValidators = $this->meta->type()->validators;
138 79
		if (!empty($typeValidators))
139
		{
140 1
			$typeName = $this->meta->type()->name;
141
			// Reset errors
142 1
			$this->errors[$typeName] = [];
143 1
			$valid[] = (int) $this->validateEntity($typeName, $typeValidators);
144
		}
145 79
		return count($valid) === array_sum($valid);
146
	}
147
148 19
	private function validateEntity($name, $validators)
149
	{
150 19
		$valid = [];
151 19
		foreach ($validators as $validatorMeta)
152
		{
153
			// Filter out validators based on scenarios
154 19
			if (!empty($validatorMeta->on))
155
			{
156 2
				$on = (array) $validatorMeta->on;
157 2
				$enabled = false;
158 2
				foreach ($on as $scenario)
159
				{
160 2
					if ($scenario === ScenarioManager::getScenario($this->model))
161
					{
162 2
						$enabled = true;
163 2
						break;
164
					}
165
				}
166 2
				if (!$enabled)
167
				{
168 2
					continue;
169
				}
170
			}
171 19
			if (!empty($validatorMeta->except))
172
			{
173 1
				$except = (array) $validatorMeta->except;
174 1
				$enabled = true;
175 1
				foreach ($except as $scenario)
176
				{
177 1
					if ($scenario === ScenarioManager::getScenario($this->model))
178
					{
179 1
						$enabled = false;
180 1
						break;
181
					}
182
				}
183 1
				if (!$enabled)
184
				{
185 1
					continue;
186
				}
187
			}
188
189
190
			// Create validator and validate
191 19
			$validator = Factory::create($this->model, $validatorMeta, $name);
192 19
			if ($validator->isValid($this->model, $name))
193
			{
194 15
				$valid[] = true;
195
			}
196
			else
197
			{
198 19
				$valid[] = false;
199 19
				$this->errors[$name] = array_merge($this->errors[$name], $validator->getErrors());
200
201
				// Set errors to model instance if it implements ValidatableInterface
202 19
				if ($this->model instanceof ValidatableInterface)
203
				{
204 19
					$this->model->setErrors($this->errors);
205
				}
206
			}
207
		}
208 19
		return count($valid) === array_sum($valid);
209
	}
210
211 8
	public function getErrors()
212
	{
213 8
		return $this->errors;
214
	}
215
216 9
	public function setErrors($errors)
217
	{
218 9
		foreach ($errors as $field => $errors)
219
		{
220 9
			$this->errors[$field] = $errors;
221
		}
222 9
	}
223
224
}
225