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

Passed
Pull Request — master (#957)
by Henrique
02:30
created

Factory::getDefaultInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.9765

Importance

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