Passed
Pull Request — master (#170)
by Wilmer
02:15
created

ErrorSummary::firstErrorsOfAttributes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Form\Widget;
6
7
use InvalidArgumentException;
8
use Yiisoft\Form\FormModelInterface;
9
use Yiisoft\Form\Helper\HtmlFormErrors;
10
use Yiisoft\Html\Html;
11
use Yiisoft\Html\Tag\CustomTag;
12
use Yiisoft\Html\Tag\P;
13
use Yiisoft\Html\Tag\Ul;
14
use Yiisoft\Widget\Widget;
15
16
use function array_unique;
17
use function array_values;
18
19
/**
20
 * The error summary widget displays a summary of the errors in a form.
21
 *
22
 * @psalm-suppress MissingConstructor
23
 */
24
final class ErrorSummary extends Widget
25
{
26
    private array $attributes = [];
27
    private bool $encode = true;
28
    private array $firstErrorsOfAttributes = [];
29
    private FormModelInterface $formModel;
30
    private string $footer = '';
31
    private array $footerAttributes = [];
32
    private string $header = 'Please fix the following errors:';
33
    private array $headerAttributes = [];
34
    private bool $showAllErrors = false;
35
    /** @psalm-param non-empty-string */
36
    private string $tag = 'div';
37
38
    /**
39
     * The HTML attributes. The following special options are recognized.
40
     *
41
     * @param array $value
42
     *
43
     * @return static
44
     *
45
     * See {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
46
     */
47 4
    public function attributes(array $value): self
48
    {
49 4
        $new = clone $this;
50 4
        $new->attributes = $value;
51 4
        return $new;
52
    }
53
54
    /**
55
     * Whether content should be HTML-encoded.
56
     *
57
     * @param bool $value
58
     *
59
     * @return static
60
     */
61 1
    public function encode(bool $value): self
62
    {
63 1
        $new = clone $this;
64 1
        $new->encode = $value;
65 1
        return $new;
66
    }
67
68
    /**
69
     * Specific attributes to be filtered out when rendering the error summary.
70
     * Attributes can only be filtered when showAllErrors is `false`.
71
     *
72
     * @param array $values The attributes to be included in error summary.
73
     *
74
     * @return static
75
     */
76 4
    public function firstErrorsOfAttributes(string ...$values): self
77
    {
78 4
        $new = clone $this;
79 4
        $new->firstErrorsOfAttributes = $values;
80 4
        return $new;
81
    }
82
83
    /**
84
     * Set the footer text for the error summary
85
     *
86
     * @param string $value
87
     *
88
     * @return static
89
     */
90 4
    public function footer(string $value): self
91
    {
92 4
        $new = clone $this;
93 4
        $new->footer = $value;
94 4
        return $new;
95
    }
96
97
    /**
98
     * Set footer attributes for the error summary.
99
     *
100
     * @param array $values Attribute values indexed by attribute names.
101
     *
102
     * @return static
103
     *
104
     * See {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
105
     */
106 3
    public function footerAttributes(array $values): self
107
    {
108 3
        $new = clone $this;
109 3
        $new->footerAttributes = $values;
110 3
        return $new;
111
    }
112
113
    /**
114
     * Set the header text for the error summary
115
     *
116
     * @param string $value
117
     *
118
     * return static
119
     */
120 3
    public function header(string $value): self
121
    {
122 3
        $new = clone $this;
123 3
        $new->header = $value;
124 3
        return $new;
125
    }
126
127
    /**
128
     * Set header attributes for the error summary.
129
     *
130
     * @param array $values Attribute values indexed by attribute names.
131
     *
132
     * @return static
133
     *
134
     * See {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
135
     */
136 3
    public function headerAttributes(array $values): self
137
    {
138 3
        $new = clone $this;
139 3
        $new->headerAttributes = $values;
140 3
        return $new;
141
    }
142
143
    /**
144
     * Set the model for the error summary.
145
     *
146
     * @param FormModelInterface $formModel
147
     *
148
     * @return static
149
     */
150 6
    public function model(FormModelInterface $formModel): self
151
    {
152 6
        $new = clone $this;
153 6
        $new->formModel = $formModel;
154 6
        return $new;
155
    }
156
157
    /**
158
     * Whether to show all errors.
159
     *
160
     * @param bool $value
161
     *
162
     * @return static
163
     */
164 4
    public function showAllErrors(bool $value): self
165
    {
166 4
        $new = clone $this;
167 4
        $new->showAllErrors = $value;
168 4
        return $new;
169
    }
170
171
    /**
172
     * Set the container tag name for the error summary.
173
     *
174
     * Empty to render error messages without container {@see Html::tag()}.
175
     *
176
     * @param string $value
177
     *
178
     * @return static
179
     */
180 2
    public function tag(string $value): self
181
    {
182 2
        $new = clone $this;
183 2
        $new->tag = $value;
184 2
        return $new;
185
    }
186
187
    /**
188
     * Return array of the validation errors.
189
     *
190
     * @return array of the validation errors.
191
     */
192 5
    private function collectErrors(): array
193
    {
194 5
        $errors = HtmlFormErrors::getErrorSummaryFirstErrors($this->formModel);
195 5
        $errorMessages = [];
196
197 5
        if ($this->showAllErrors) {
198 2
            $errors = HtmlFormErrors::getErrorSummary($this->formModel);
199 3
        } elseif ($this->firstErrorsOfAttributes !== []) {
200 1
            $errors = array_intersect_key($errors, array_flip($this->firstErrorsOfAttributes));
201
        }
202
203
        /**
204
         * If there are the same error messages for different attributes, array_unique will leave gaps between
205
         * sequential keys. Applying array_values to reorder array keys.
206
         */
207 5
        $lines = array_values(array_unique($errors));
208
209 5
        if ($this->encode) {
210
            /** @var string $line */
211 5
            foreach ($lines as $line) {
212 4
                if (!empty($line)) {
213 4
                    $errorMessages[] = Html::encode($line);
214
                }
215
            }
216
        }
217
218 5
        return $errorMessages;
219
    }
220
221
    /**
222
     * Generates a summary of the validation errors.
223
     *
224
     * @return string the generated error summary
225
     */
226 6
    protected function run(): string
227
    {
228 6
        $attributes = $this->attributes;
229 6
        $content = '';
230
231 6
        if ($this->tag === '') {
232 1
            throw new InvalidArgumentException('Tag name cannot be empty.');
233
        }
234
235 5
        $content .=  P::tag()->attributes($this->headerAttributes)->content($this->header)->render() . PHP_EOL;
236
237
        /** @var array<string, string> */
238 5
        $lines = $this->collectErrors();
239 5
        $content .= Ul::tag()->strings($lines)->render();
240
241 5
        if ($this->footer !== '') {
242 2
            $content .= PHP_EOL . P::tag()->attributes($this->footerAttributes)->content($this->footer)->render();
243
        }
244
245 5
        return $lines !== []
246 4
            ? CustomTag::name($this->tag)
247 4
                ->attributes($attributes)
248 4
                ->encode(false)
249 4
                ->content(PHP_EOL . $content . PHP_EOL)
250 4
                ->render()
251 5
            : '';
252
    }
253
}
254