Completed
Push — master ( 308150...7a3b42 )
by Peter
08:01
created

Validator::validate()   C

Complexity

Conditions 13
Paths 46

Size

Total Lines 72
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 13.0042

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 72
ccs 33
cts 34
cp 0.9706
rs 5.5073
cc 13
eloc 34
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 http://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 105
	public function __construct(AnnotatedInterface $model)
53
	{
54 105
		$this->model = $model;
55 105
		$this->meta = ManganMeta::create($this->model);
56
57
		// Ensure that errors array is initialized - even if does not have validators
58 105
		foreach (array_keys($this->meta->fields()) as $name)
59
		{
60 105
			$this->errors[$name] = [];
61
		}
62 105
	}
63
64
	/**
65
	 * Validate model, optionally only selected fields
66
	 * @param string[] $fields
67
	 * @return boolean
68
	 */
69 77
	public function validate($fields = [])
70
	{
71 77
		$valid = [];
72 77
		if (empty($fields))
73
		{
74 77
			$fields = array_keys($this->meta->fields());
75
		}
76 77
		foreach ($fields as $name)
77
		{
78 77
			$fieldMeta = $this->meta->field($name);
79
80
			// Reset errors
81 77
			$this->errors[$name] = [];
82
83
			// Check if meta for field exists
84 77
			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 77
			if ($fieldMeta->owned && $fieldMeta->updatable)
92
			{
93 24
				if (is_array($this->model->$name))
94
				{
95
					// Handle arrays of documents
96 10
					foreach ($this->model->$name as $model)
97
					{
98 10
						$validator = new Validator($model);
99 10
						$isValid = $validator->validate();
100 10
						$valid[] = (int) $isValid;
101 10
						if (!$isValid)
102
						{
103 2
							$errors = $validator->getErrors();
104 10
							$this->setErrors($errors);
105
						}
106
					}
107
				}
108 18
				elseif (!empty($this->model->$name))
109
				{
110
					// Handle single documents
111 18
					$validator = new Validator($this->model->$name);
112 18
					$isValid = $validator->validate();
113 18
					$valid[] = (int) $isValid;
114 18
					if (!$isValid)
115
					{
116 2
						$errors = $validator->getErrors();
117 2
						$this->setErrors($errors);
118
					}
119
				}
120
			}
121
122
			// Skip field without validators
123 77
			if (empty($fieldMeta->validators))
124
			{
125 76
				continue;
126
			}
127 16
			$valid[] = (int) $this->validateEntity($name, $fieldMeta->validators);
128
		}
129
130
		// Model validators
131 77
		$typeValidators = $this->meta->type()->validators;
132 77
		if (!empty($typeValidators))
133
		{
134 1
			$typeName = $this->meta->type()->name;
135
			// Reset errors
136 1
			$this->errors[$typeName] = [];
137 1
			$valid[] = (int) $this->validateEntity($typeName, $typeValidators);
138
		}
139 77
		return count($valid) === array_sum($valid);
140
	}
141
142 17
	private function validateEntity($name, $validators)
143
	{
144 17
		$valid = [];
145 17
		foreach ($validators as $validatorMeta)
146
		{
147
			// Filter out validators based on scenarios
148 17
			if (!empty($validatorMeta->on))
149
			{
150 2
				$on = (array) $validatorMeta->on;
151 2
				$enabled = false;
152 2
				foreach ($on as $scenario)
153
				{
154 2
					if ($scenario === ScenarioManager::getScenario($this->model))
155
					{
156 2
						$enabled = true;
157 2
						break;
158
					}
159
				}
160 2
				if (!$enabled)
161
				{
162 2
					continue;
163
				}
164
			}
165 17
			if (!empty($validatorMeta->except))
166
			{
167 1
				$except = (array) $validatorMeta->except;
168 1
				$enabled = true;
169 1
				foreach ($except as $scenario)
170
				{
171 1
					if ($scenario === ScenarioManager::getScenario($this->model))
172
					{
173 1
						$enabled = false;
174 1
						break;
175
					}
176
				}
177 1
				if (!$enabled)
178
				{
179 1
					continue;
180
				}
181
			}
182
183
184
			// Create validator and validate
185 17
			$validator = Factory::create($this->model, $validatorMeta, $name);
186 17
			if ($validator->isValid($this->model, $name))
187
			{
188 13
				$valid[] = true;
189
			}
190
			else
191
			{
192 17
				$valid[] = false;
193 17
				$this->errors[$name] = array_merge($this->errors[$name], $validator->getErrors());
194
195
				// Set errors to model instance if it implements ValidatableInterface
196 17
				if ($this->model instanceof ValidatableInterface)
197
				{
198 17
					$this->model->setErrors($this->errors);
199
				}
200
			}
201
		}
202 17
		return count($valid) === array_sum($valid);
203
	}
204
205 6
	public function getErrors()
206
	{
207 6
		return $this->errors;
208
	}
209
210 7
	public function setErrors($errors)
211
	{
212 7
		foreach ($errors as $field => $errors)
213
		{
214 7
			$this->errors[$field] = $errors;
215
		}
216 7
	}
217
218
}
219