Completed
Push — master ( 0c0c31...5de0c3 )
by Peter
10:08
created

Validator::validate()   D

Complexity

Conditions 16
Paths 156

Size

Total Lines 95

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 39
CRAP Score 16.004

Importance

Changes 0
Metric Value
dl 0
loc 95
ccs 39
cts 40
cp 0.975
rs 4.189
c 0
b 0
f 0
cc 16
nc 156
nop 1
crap 16.004

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