Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Completed
Pull Request — master (#957)
by Henrique
03:26
created

Factory::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 3
crap 1
1
<?php
2
3
/*
4
 * This file is part of Respect/Validation.
5
 *
6
 * (c) Alexandre Gomes Gaigalas <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the "LICENSE.md"
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Respect\Validation;
15
16
use ReflectionClass;
17
use ReflectionObject;
18
use Respect\Validation\Exceptions\ComponentException;
19
use Respect\Validation\Exceptions\InvalidClassException;
20
use Respect\Validation\Exceptions\ValidationException;
21
use function array_map;
22
use function array_merge;
23
use function array_unique;
24
use function class_exists;
25
use function lcfirst;
26
27
/**
28
 * Factory of objects.
29
 *
30
 * @author Henrique Moody <[email protected]>
31
 */
32
final class Factory
33
{
34
    private const DEFAULT_RULES_NAMESPACES = [
35
        'Respect\\Validation\\Rules',
36
        'Respect\\Validation\\Rules\\Locale',
37
    ];
38
39
    private const DEFAULT_EXCEPTIONS_NAMESPACES = [
40
        'Respect\\Validation\\Exceptions',
41
        'Respect\\Validation\\Exceptions\\Locale',
42
    ];
43
44
    /**
45
     * Default instance of the Factory.
46
     *
47
     * @var Factory
48
     */
49
    private static $defaultInstance;
50
51
    /**
52
     * @var string[]
53
     */
54
    private $rulesNamespaces = [];
55
56
    /**
57
     * @var string[]
58
     */
59
    private $exceptionsNamespaces = [];
60
61
    /**
62
     * @var callable
63
     */
64
    private $translator;
65
66
    /**
67
     * Initializes the factory with the defined namespaces.
68
     *
69
     * If the default namespace is not in the array, it will be add to the end
70
     * of the array.
71
     *
72
     * @param string[] $rulesNamespaces
73
     * @param string[] $exceptionsNamespaces
74
     */
75 240
    public function __construct(
76
        array $rulesNamespaces,
77
        array $exceptionsNamespaces,
78
        callable $translator
79
    ) {
80 240
        $this->rulesNamespaces = $this->filterNamespaces($rulesNamespaces, self::DEFAULT_RULES_NAMESPACES);
81 240
        $this->exceptionsNamespaces = $this->filterNamespaces($exceptionsNamespaces, self::DEFAULT_EXCEPTIONS_NAMESPACES);
82 240
        $this->translator = $translator;
83 240
    }
84
85
    /**
86
     * Define the default instance of the Factory.
87
     *
88
     * @param Factory $defaultInstance
89
     */
90 3
    public static function setDefaultInstance(self $defaultInstance): void
91
    {
92 3
        self::$defaultInstance = $defaultInstance;
93 3
    }
94
95
    /**
96
     * Returns the default instance of the Factory.
97
     *
98
     * @return Factory
99
     */
100 242
    public static function getDefaultInstance(): self
101
    {
102 242
        if (null === self::$defaultInstance) {
103 237
            self::$defaultInstance = new self(
104 237
                self::DEFAULT_RULES_NAMESPACES,
105 237
                self::DEFAULT_EXCEPTIONS_NAMESPACES,
106 237
                function (string $message): string {
107 182
                    return $message;
108 237
                }
109
            );
110
        }
111
112 242
        return self::$defaultInstance;
113
    }
114
115
    /**
116
     * Creates a rule.
117
     *
118
     * @param string $ruleName
119
     * @param array $arguments
120
     *
121
     * @throws ComponentException
122
     *
123
     * @return Validatable
124
     */
125 242
    public function rule(string $ruleName, array $arguments = []): Validatable
126
    {
127 242
        foreach ($this->rulesNamespaces as $namespace) {
128 242
            $className = sprintf('%s\\%s', $namespace, ucfirst($ruleName));
129 242
            if (!class_exists($className)) {
130 1
                continue;
131
            }
132
133 241
            return $this->createReflectionClass($className, Validatable::class)->newInstanceArgs($arguments);
134
        }
135
136 1
        throw new ComponentException(sprintf('"%s" is not a valid rule name', $ruleName));
137
    }
138
139
    /**
140
     * Creates an exception.
141
     *
142
     *
143
     * @param Validatable $validatable
144
     * @param mixed $input
145
     * @param array $extraParams
146
     *
147
     * @throws ComponentException
148
     *
149
     * @return ValidationException
150
     */
151 185
    public function exception(Validatable $validatable, $input, array $extraParams = []): ValidationException
152
    {
153 185
        $reflection = new ReflectionObject($validatable);
154 185
        $ruleName = $reflection->getShortName();
155 185
        $params = ['input' => $input] + $extraParams + $this->extractPropertiesValues($validatable, $reflection);
156 185
        $id = lcfirst($ruleName);
157 185
        if ($validatable->getName()) {
158 22
            $id = $params['name'] = $validatable->getName();
159
        }
160 185
        foreach ($this->exceptionsNamespaces as $namespace) {
161 185
            $exceptionName = sprintf('%s\\%sException', $namespace, $ruleName);
162 185
            if (!class_exists($exceptionName)) {
163 4
                continue;
164
            }
165
166 185
            return $this->createValidationException($exceptionName, $id, $input, $params);
167
        }
168
169
        return $this->createValidationException(ValidationException::class, $id, $input, $params);
170
    }
171
172
    /**
173
     * Creates a reflection based on class name.
174
     *
175
     *
176
     * @param string $name
177
     * @param string $parentName
178
     *
179
     * @throws InvalidClassException
180
     *
181
     * @return ReflectionClass
182
     */
183 241
    private function createReflectionClass(string $name, string $parentName): ReflectionClass
184
    {
185 241
        $reflection = new ReflectionClass($name);
186 241
        if (!$reflection->isSubclassOf($parentName) && $parentName !== $name) {
187
            throw new InvalidClassException(sprintf('"%s" must be an instance of "%s"', $name, $parentName));
188
        }
189
190 241
        if (!$reflection->isInstantiable()) {
191
            throw new InvalidClassException(sprintf('"%s" must be instantiable', $name));
192
        }
193
194 241
        return $reflection;
195
    }
196
197
    /**
198
     * Filters namespaces.
199
     *
200
     * Ensure namespaces are in the right format and contain the default namespaces.
201
     *
202
     * @param array $namespaces
203
     * @param array $defaultNamespaces
204
     *
205
     * @return array
206
     */
207
    private function filterNamespaces(array $namespaces, array $defaultNamespaces): array
208
    {
209 240
        $filter = function (string $namespace): string {
210 240
            return trim($namespace, '\\');
211 240
        };
212
213 240
        return array_unique(
214 240
            array_merge(
215 240
                array_map($filter, $namespaces),
216 240
                array_map($filter, $defaultNamespaces)
217
            )
218
        );
219
    }
220
221
    /**
222
     * Creates a Validation exception.
223
     *
224
     * @param string $exceptionName
225
     * @param string $id
226
     * @param mixed $input
227
     * @param array $params
228
     *
229
     * @throws InvalidClassException
230
     *
231
     * @return ValidationException
232
     */
233 185
    private function createValidationException(string $exceptionName, string $id, $input, array $params): ValidationException
234
    {
235
        /* @var ValidationException $exception */
236 185
        $exception = $this->createReflectionClass($exceptionName, ValidationException::class)
237 185
            ->newInstance($input, $id, $params, $this->translator);
238 185
        if (isset($params['template'])) {
239 4
            $exception->updateTemplate($params['template']);
240
        }
241
242 185
        return $exception;
243
    }
244
245
    /**
246
     * @param Validatable $validatable
247
     * @param ReflectionClass $reflection
248
     *
249
     * @return array
250
     */
251 185
    private function extractPropertiesValues(Validatable $validatable, ReflectionClass $reflection): array
252
    {
253 185
        $values = [];
254 185
        foreach ($reflection->getProperties() as $property) {
255 185
            $property->setAccessible(true);
256
257 185
            $values[$property->getName()] = $property->getValue($validatable);
258
        }
259
260 185
        if (($parentReflection = $reflection->getParentClass())) {
261 185
            return $values + $this->extractPropertiesValues($validatable, $parentReflection);
262
        }
263
264 185
        return $values;
265
    }
266
}
267