ErrorBag::addThrowable()   B
last analyzed

Complexity

Conditions 11
Paths 30

Size

Total Lines 30
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 21
c 1
b 0
f 0
nc 30
nop 2
dl 0
loc 30
rs 7.3166

How to fix   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
namespace W2w\Lib\ApieObjectAccessNormalizer\Errors;
5
6
use Closure;
7
use ReflectionClass;
8
use Throwable;
9
use W2w\Lib\ApieObjectAccessNormalizer\Exceptions\ErrorBagAwareException;
10
use W2w\Lib\ApieObjectAccessNormalizer\Exceptions\LocalizationableException;
11
use W2w\Lib\ApieObjectAccessNormalizer\Normalizers\ApieObjectAccessNormalizer;
12
13
/**
14
 * Maps all found exceptions to an error map.
15
 *
16
 * @internal
17
 * @see ApieObjectAccessNormalizer
18
 */
19
class ErrorBag
20
{
21
    /**
22
     * @var string
23
     */
24
    private $prefix;
25
26
    /**
27
     * @var ErrorBagField[][]
28
     */
29
    private $errors = [];
30
31
    public function __construct(string $prefix)
32
    {
33
        $this->prefix = $prefix;
34
    }
35
36
    /**
37
     * @param array $index
38
     * @param string $prefix
39
     * @return ErrorBag
40
     */
41
    public static function fromArray(array $index, string $prefix = ''): ErrorBag
42
    {
43
        $result = new ErrorBag($prefix);
44
        foreach ($index as $key => $errors) {
45
            if (!is_array($errors)) {
46
                $errors = [$errors];
47
            }
48
            foreach ($errors as $error) {
49
                $result->errors[$key][] = new ErrorBagField($error);
50
            }
51
        }
52
        return $result;
53
    }
54
55
    private function merge(ErrorBag $otherBag)
56
    {
57
        foreach ($otherBag->errors as $prefix => $errors) {
58
            foreach ($errors as $error) {
59
                $this->errors[$prefix][] = $error;
60
            }
61
        }
62
    }
63
64
    /**
65
     * Adds error messages to the errors from an exception/error.
66
     * If it is a validation error, the mapping is taken over.
67
     *
68
     * @param string $fieldName
69
     * @param Throwable $throwable
70
     */
71
    public function addThrowable(string $fieldName, Throwable $throwable)
72
    {
73
        $prefix = $this->prefix ? ($this->prefix . '.' . $fieldName) : $fieldName;
74
        if ($throwable instanceof ErrorBagAwareException) {
75
            $this->merge($throwable->getErrorBag());
76
            return;
77
        }
78
        if ($validationErrors = $this->extractValidationErrors($throwable)) {
79
            $expectedPrefix = $prefix . '.';
80
            foreach ($validationErrors as $key => $validationError) {
81
                if (('' . $key) !== $prefix && strpos($key, $expectedPrefix) !== 0) {
82
                    if ($key === '') {
83
                        $key = $prefix;
84
                    } else {
85
                        $key = $expectedPrefix . $key;
86
                    }
87
                }
88
                if (!is_array($validationError)) {
89
                    $validationError = [$validationError];
90
                }
91
                foreach ($validationError as $error) {
92
                    $this->errors[$key][] = new ErrorBagField($error, null, $throwable);
93
                }
94
            }
95
            return;
96
        }
97
        $this->errors[$prefix][] = new ErrorBagField(
98
            $throwable->getMessage(),
99
            $throwable instanceof LocalizationableException ? $throwable->getI18n() : null,
100
            $throwable
101
        );
102
    }
103
104
    /**
105
     * Returns a list of error messages.
106
     *
107
     * @param Closure|null $callback
108
     * @return string[][]
109
     */
110
    public function getErrors(?Closure $callback = null): array
111
    {
112
        if (!$callback) {
113
            $callback = function (ErrorBagField $field) {
114
                return $field->getMessage();
115
            };
116
        }
117
        return array_map(
118
            function (array $errors) use (&$callback) {
119
                return array_map($callback, $errors);
120
            },
121
            $this->errors
122
        );
123
    }
124
125
    /**
126
     * Since ApieObjectAccessNormalizer catches all exceptions for debugging reasons we keep a record of the exceptions
127
     * too.
128
     *
129
     * @return Throwable[][]
130
     */
131
    public function getExceptions(): array
132
    {
133
        return array_map(
134
            function (array $errors) {
135
                return array_filter(array_map(
136
                    function (ErrorBagField $field) {
137
                        return $field->getSource();
138
                    },
139
                    $errors
140
                ));
141
            },
142
            $this->errors
143
        );
144
    }
145
146
    /**
147
     * @return bool
148
     */
149
    public function hasErrors(): bool
150
    {
151
        return !empty($this->errors);
152
    }
153
154
    /**
155
     * Tries to guess if the class is a validationexception for any arbitrary library. Most popular libraries
156
     * just have a ValidationException in use.
157
     *
158
     * @param Throwable $throwable
159
     * @return array|null
160
     */
161
    private function extractValidationErrors(Throwable $throwable): ?array
162
    {
163
        $refl = new ReflectionClass($throwable);
164
        if ($refl->getShortName() === 'ValidationException') {
165
            if (method_exists($throwable, 'getErrors')) {
166
                return (array) $throwable->getErrors();
167
            }
168
            if (method_exists($throwable, 'getError')) {
169
                return [$throwable->getError()];
170
            }
171
            if (method_exists($throwable, 'errors')) {
172
                return (array) $throwable->errors();
173
            }
174
        }
175
        return null;
176
    }
177
}
178