Test Failed
Pull Request — main (#21)
by Mohammad Hafijul
03:53
created

FormBuilder::input()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 25
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 9
c 4
b 0
f 0
dl 0
loc 25
rs 9.9666
cc 3
nc 4
nop 4
1
<?php
2
3
namespace Hafijul233\Form\Builders;
4
5
use BadMethodCallException;
6
use DateTime;
7
use Hafijul233\Form\Traits\ComponentTrait;
8
use Illuminate\Contracts\Routing\UrlGenerator;
9
use Illuminate\Contracts\Session\Session;
10
use Illuminate\Contracts\View\Factory;
11
use Illuminate\Http\Request;
12
use Illuminate\Support\Arr;
13
use Illuminate\Support\Collection;
14
use Illuminate\Support\Facades\Config;
15
use Illuminate\Support\HtmlString;
16
use Illuminate\Support\Traits\Macroable;
17
use Illuminate\Support\ViewErrorBag;
18
19
/**
20
 * Class FormBuilder
21
 */
22
class FormBuilder
23
{
24
    use Macroable, ComponentTrait {
0 ignored issues
show
Bug introduced by
The trait Illuminate\Support\Traits\Macroable requires the property $name which is not provided by Hafijul233\Form\Builders\FormBuilder.
Loading history...
25
        Macroable::__call as macroCall;
26
        ComponentTrait::__call as componentCall;
27
    }
28
29
    protected $payload;
30
31
    /**
32
     * The URL generator instance.
33
     *
34
     * @var UrlGenerator
35
     */
36
    protected $url;
37
38
    /**
39
     * The View factory instance.
40
     *
41
     * @var Factory
42
     */
43
    protected $view;
44
45
    /**
46
     * The CSRF token used by the form builder.
47
     *
48
     * @var string
49
     */
50
    protected $csrfToken;
51
52
    /**
53
     * Consider Request variables while auto fill.
54
     *
55
     * @var bool
56
     */
57
    protected $considerRequest = false;
58
59
    /**
60
     * The session store implementation.
61
     *
62
     * @var Session
63
     */
64
    protected $session;
65
66
    /**
67
     * The current model instance for the form.
68
     *
69
     * @var mixed
70
     */
71
    protected $model;
72
73
    /**
74
     * An array of label names we've created.
75
     *
76
     * @var array
77
     */
78
    protected $labels = [];
79
80
    protected $request;
81
82
    /**
83
     * The reserved form open attributes.
84
     *
85
     * @var array
86
     */
87
    protected $reserved = ['method', 'url', 'route', 'action', 'files'];
88
89
    /**
90
     * The form methods that should be spoofed, in uppercase.
91
     *
92
     * @var array
93
     */
94
    protected $spoofedMethods = ['DELETE', 'PATCH', 'PUT'];
95
96
    /**
97
     * The types of inputs to not fill values on by default.
98
     *
99
     * @var array
100
     */
101
    protected $skipValueTypes = ['file', 'password', 'checkbox', 'radio'];
102
103
    /**
104
     * Input Type.
105
     *
106
     * @var string|null
107
     */
108
    protected $type = 'text';
109
110
    /**
111
     * Create a new form builder instance.
112
     *
113
     * @param Factory $view
114
     * @param string|null $csrfToken
115
     * @param UrlGenerator|null $url
116
     * @param Request|null $request
117
     */
118
    public function __construct(Factory $view, string $csrfToken = null, UrlGenerator $url = null, Request $request = null)
119
    {
120
        $this->view = $view;
121
        $this->csrfToken = $csrfToken;
122
        $this->url = $url;
123
        $this->request = $request;
124
    }
125
126
    /**
127
     * Create a new model based form builder.
128
     *
129
     * @param mixed $model
130
     * @param array $options
131
     * @return HtmlString
132
     */
133
    public function model($model, array $options = []): HtmlString
134
    {
135
        $this->model = $model;
136
137
        return $this->open($options);
138
    }
139
140
    /**
141
     * Open up a new HTML form.
142
     *
143
     * @param array $options
144
     * @return HtmlString
145
     */
146
    public function open(array $options = []): HtmlString
147
    {
148
        $method = Arr::get($options, 'method', 'post');
149
150
        // We need to extract the proper method from the attributes. If the method is
151
        // something other than GET or POST we'll use POST since we will spoof the
152
        // actual method since forms don't support the reserved methods in HTML.
153
        $attributes['method'] = $this->getMethod($method);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$attributes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $attributes = array(); before regardless.
Loading history...
154
155
        $attributes['action'] = $this->getAction($options);
156
157
        $attributes['accept-charset'] = 'UTF-8';
158
159
        // If the method is PUT, PATCH or DELETE we will need to add a spoofer hidden
160
        // field that will instruct the Symfony request to pretend the method is a
161
        // different method than it actually is, for convenience from the forms.
162
        $append = $this->getAppendage($method);
163
164
        if (isset($options['files']) && $options['files']) {
165
            $options['enctype'] = 'multipart/form-data';
166
        }
167
168
        // Finally we're ready to create the final form HTML field. We will attribute
169
        // format the array of attributes. We will also add on the appendage which
170
        // is used to spoof requests for this PUT, PATCH, etc. methods on forms.
171
        $attributes = array_merge(
172
173
            $attributes, Arr::except($options, $this->reserved)
174
175
        );
176
177
        // Finally, we will concatenate all of the attributes into a single string so
178
        // we can build out the final form open statement. We'll also append on an
179
        // extra value for the hidden _method field if it's needed for the form.
180
        $attributes = $this->attributes($attributes);
181
182
        return $this->toHtmlString('<form' . $attributes . '>' . $append);
183
    }
184
185
    /**
186
     * Parse the form action method.
187
     *
188
     * @param string $method
189
     * @return string
190
     */
191
    protected function getMethod(string $method): string
192
    {
193
        $method = strtoupper($method);
194
195
        return $method !== 'GET' ? 'POST' : $method;
196
    }
197
198
    /**
199
     * Get the form action from the options.
200
     *
201
     * @param array $options
202
     * @return string
203
     */
204
    protected function getAction(array $options): string
205
    {
206
        // We will also check for a "route" or "action" parameter on the array so that
207
        // developers can easily specify a route or controller action when creating
208
        // a form providing a convenient interface for creating the form actions.
209
        if (isset($options['url'])) {
210
            return $this->getUrlAction($options['url']);
211
        }
212
213
        if (isset($options['route'])) {
214
            return $this->getRouteAction($options['route']);
215
        }
216
217
        // If an action is available, we are attempting to open a form to a controller
218
        // action route. So, we will use the URL generator to get the path to these
219
        // actions and return them from the method. Otherwise, we'll use current.
220
        elseif (isset($options['action'])) {
221
            return $this->getControllerAction($options['action']);
222
        }
223
224
        return $this->url->current();
225
    }
226
227
    /**
228
     * Get the action for a "url" option.
229
     *
230
     * @param array|string $options
231
     * @return string
232
     */
233
    protected function getUrlAction($options): string
234
    {
235
        if (is_array($options)) {
236
            return $this->url->to($options[0], array_slice($options, 1));
237
        }
238
239
        return $this->url->to($options);
240
    }
241
242
    /**
243
     * Get the action for a "route" option.
244
     *
245
     * @param array|string $options
246
     * @return string
247
     */
248
    protected function getRouteAction($options): string
249
    {
250
        if (is_array($options)) {
251
            $parameters = array_slice($options, 1);
252
253
            if (array_keys($options) === [0, 1]) {
254
                $parameters = head($parameters);
255
            }
256
257
            return $this->url->route($options[0], $parameters);
258
        }
259
260
        return $this->url->route($options);
261
    }
262
263
    /**
264
     * Get the action for an "action" option.
265
     *
266
     * @param array|string $options
267
     * @return string
268
     */
269
    protected function getControllerAction($options): string
270
    {
271
        if (is_array($options)) {
272
            return $this->url->action($options[0], array_slice($options, 1));
273
        }
274
275
        return $this->url->action($options);
276
    }
277
278
    /**
279
     * Get the form appendage for the given method.
280
     *
281
     * @param string $method
282
     * @return string
283
     */
284
    protected function getAppendage(string $method): string
285
    {
286
        [$method, $appendage] = [strtoupper($method), ''];
287
288
        // If the HTTP method is in this list of spoofed methods, we will attach the
289
        // method spoofer hidden input to the form. This allows us to use regular
290
        // form to initiate PUT and DELETE requests in addition to the typical.
291
        if (in_array($method, $this->spoofedMethods)) {
292
            $appendage .= $this->hidden('_method', $method);
293
        }
294
295
        // If the method is something other than GET we will go ahead and attach the
296
        // CSRF token to the form, as this can't hurt and is convenient to simply
297
        // always have available on every form the developers creates for them.
298
        if ($method !== 'GET') {
299
            $appendage .= $this->token();
300
        }
301
302
        return $appendage;
303
    }
304
305
    /**
306
     * Create a hidden input field.
307
     *
308
     * @param string $name
309
     * @param null $value
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $value is correct as it would always require null to be passed?
Loading history...
310
     * @param array $options
311
     * @return HtmlString
312
     */
313
    public function hidden(string $name, $value = null, array $options = []): HtmlString
314
    {
315
        return $this->input('hidden', $name, $value, $options);
316
    }
317
318
    /**
319
     * Create a form input field.
320
     *
321
     * @param string $type
322
     * @param string $name
323
     * @param null $value
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $value is correct as it would always require null to be passed?
Loading history...
324
     * @param array $options
325
     * @return HtmlString
326
     */
327
    public function input(string $type, string $name, $value = null, array $options = []): HtmlString
328
    {
329
        $this->type = $type;
330
331
        if (!isset($options['name'])) {
332
            $options['name'] = $name;
333
        }
334
335
        // We will get the appropriate value for the given field. We will look for the
336
        // value in the session for the value in the old input data then we'll look
337
        // in the model instance if one is set. Otherwise we will just use empty.
338
        $id = $this->getIdAttribute($name, $options);
339
340
        if (!in_array($type, $this->skipValueTypes)) {
341
            $value = $this->getValueAttribute($name, $value);
342
        }
343
344
        // Once we have the type, value, and ID we can merge them into the rest of the
345
        // attributes array so we can convert them into their HTML attribute format
346
        // when creating the HTML element. Then, we will return the entire input.
347
        $merge = compact('type', 'value', 'id');
348
349
        $options = array_merge($options, $merge);
350
351
        return $this->toHtmlString('<input' . $this->attributes($options) . '>');
352
    }
353
354
    /**
355
     * Get the ID attribute for a field name.
356
     *
357
     * @param string $name
358
     * @param array $attributes
359
     * @return string|null
360
     */
361
    public function getIdAttribute(string $name, array $attributes): ?string
362
    {
363
        if (array_key_exists('id', $attributes)) {
364
            return $attributes['id'];
365
        }
366
367
        if (in_array($name, $this->labels)) {
368
            return $name;
369
        }
370
371
        return null;
372
    }
373
374
    /**
375
     * Get the value that should be assigned to the field.
376
     *
377
     * @param string|null $name
378
     * @param null $value
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $value is correct as it would always require null to be passed?
Loading history...
379
     * @return mixed
380
     */
381
    public function getValueAttribute(string $name = null, $value = null)
382
    {
383
        if (is_null($name)) {
384
            return $value;
385
        }
386
387
        $old = $this->old($name);
388
389
        if (!is_null($old) && $name !== '_method') {
390
            return $old;
391
        }
392
393
        if (function_exists('app')) {
394
            $hasNullMiddleware = app("Illuminate\Contracts\Http\Kernel")
395
                ->hasMiddleware('\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull');
396
397
            if ($hasNullMiddleware
398
                && is_null($old)
399
                && is_null($value)
400
                && !is_null($this->view->shared('errors'))
401
                && count(is_countable($this->view->shared('errors')) ? $this->view->shared('errors') : []) > 0
0 ignored issues
show
Bug introduced by
It seems like is_countable($this->view...red('errors') : array() can also be of type null; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

401
                && count(/** @scrutinizer ignore-type */ is_countable($this->view->shared('errors')) ? $this->view->shared('errors') : []) > 0
Loading history...
402
            ) {
403
                return null;
404
            }
405
        }
406
407
        $request = $this->request($name);
408
        if (!is_null($request) && $name != '_method') {
409
            return $request;
410
        }
411
412
        if (!is_null($value)) {
413
            return $value;
414
        }
415
416
        if (isset($this->model)) {
417
            return $this->getModelValueAttribute($name);
418
        }
419
420
        return null;
421
    }
422
423
    /**
424
     * Get a value from the session's old input.
425
     *
426
     * @param string $name
427
     * @return mixed
428
     */
429
    public function old(string $name)
430
    {
431
        /*if (isset($this->session)) {*/
432
        $key = $this->transformKey($name);
433
        $payload = $this->session->getOldInput($key);
434
435
        if (!is_array($payload)) {
436
            return $payload;
437
        }
438
439
        if (!in_array($this->type, ['select', 'checkbox'])) {
440
            if (!isset($this->payload[$key])) {
441
                $this->payload[$key] = collect($payload);
442
            }
443
444
            if (!empty($this->payload[$key])) {
445
                return $this->payload[$key]->shift();
446
            }
447
        }
448
449
        return $payload;
450
        /*    }
451
452
            return [];*/
453
    }
454
455
    /**
456
     * Transform key from array to dot syntax.
457
     *
458
     * @param string $key
459
     * @return mixed
460
     */
461
    protected function transformKey(string $key)
462
    {
463
        return str_replace(['.', '[]', '[', ']'], ['_', '', '.', ''], $key);
464
    }
465
466
    /**
467
     * Get value from current Request
468
     *
469
     * @param $name
470
     * @return array|null|string
471
     */
472
    protected function request($name)
473
    {
474
        if (!$this->considerRequest) {
475
            return null;
476
        }
477
478
        if (!isset($this->request)) {
479
            return null;
480
        }
481
482
        return $this->request->input($this->transformKey($name));
0 ignored issues
show
Bug introduced by
The method input() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

482
        return $this->request->/** @scrutinizer ignore-call */ input($this->transformKey($name));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
483
    }
484
485
    /**
486
     * Get the model value that should be assigned to the field.
487
     *
488
     * @param string $name
489
     * @return mixed
490
     */
491
    protected function getModelValueAttribute(string $name)
492
    {
493
        $key = $this->transformKey($name);
494
495
        if ((is_string($this->model) || is_object($this->model)) && method_exists($this->model, 'getFormValue')) {
496
            return $this->model->getFormValue($key);
497
        }
498
499
        return data_get($this->model, $key);
500
    }
501
502
    /**
503
     * Transform the string to an Html serializable object
504
     *
505
     * @param $html
506
     * @return HtmlString
507
     */
508
    protected function toHtmlString($html): HtmlString
509
    {
510
        return new HtmlString($html);
511
    }
512
513
    /**
514
     * Build an HTML attribute string from an array.
515
     *
516
     * @param array $attributes
517
     * @return string
518
     */
519
    public function attributes(array $attributes): string
520
    {
521
        $html = [];
522
523
        foreach ((array)$attributes as $key => $value) {
524
            $element = $this->attributeElement($key, $value);
525
526
            if ($element != null) {
527
                $html[] = $element;
528
            }
529
        }
530
531
        return count($html) > 0 ? ' ' . implode(' ', $html) : '';
532
    }
533
534
    /**
535
     * Build a single attribute element.
536
     *
537
     * @param string $key
538
     * @param mixed $value
539
     * @return string
540
     */
541
    protected function attributeElement(string $key, $value): string
542
    {
543
        // For numeric keys we will assume that the value is a boolean attribute
544
        // where the presence of the attribute represents a true value and the
545
        // absence represents a false value.
546
        // This will convert HTML attributes such as "required" to a correct
547
        // form instead of using incorrect numerics.
548
        if (is_numeric($key)) {
549
            return $value;
550
        }
551
552
        // Treat boolean attributes as HTML properties
553
        if (is_bool($value) && $key !== 'value') {
554
            return $value ? $key : '';
555
        }
556
557
        if (is_array($value) && $key === 'class') {
558
            return 'class="' . implode(' ', $value) . '"';
559
        }
560
561
        if (!is_null($value)) {
562
            return $key . '="' . e($value, false) . '"';
563
        }
564
565
        return '';
566
    }
567
568
    /**
569
     * Generate a hidden field with the current CSRF token.
570
     *
571
     * @return string
572
     */
573
    public function token()
574
    {
575
        $token = !empty($this->csrfToken) ? $this->csrfToken : $this->session->token();
576
577
        return $this->hidden('_token', $token);
578
    }
579
580
    /**
581
     * Get the current model instance on the form builder.
582
     *
583
     * @return mixed $model
584
     */
585
    public function getModel()
586
    {
587
        return $this->model;
588
    }
589
590
    /**
591
     * Set the model instance on the form builder.
592
     *
593
     * @param mixed $model
594
     * @return void
595
     */
596
    public function setModel($model)
597
    {
598
        $this->model = $model;
599
    }
600
601
    /**
602
     * Close the current form.
603
     *
604
     * @return string
605
     */
606
    public function close()
607
    {
608
        $this->labels = [];
609
610
        $this->model = null;
611
612
        return $this->toHtmlString('</form>');
613
    }
614
615
    /**
616
     * Create a form label element.
617
     *
618
     * @param string $name
619
     * @param null $title
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $title is correct as it would always require null to be passed?
Loading history...
620
     * @param bool $required
621
     * @param array $options
622
     * @param bool $escape_html
623
     * @return HtmlString
624
     */
625
    public function label(string $name, $title = null, bool $required = false, array $options = [], $escape_html = true): HtmlString
626
    {
627
        $this->labels[] = $name;
628
629
        $options = $this->attributes($options);
630
631
        $title = $this->formatLabel($name, $title);
632
633
        if ($escape_html) {
634
            $title = $this->entities($title);
635
        }
636
637
        if ($required) {
638
            $title = '<span style="color: #dc3545; font-weight:700">*</span> ' . $title;
639
        }
640
641
        return $this->toHtmlString("<label for=\"{$name}\" {$options}>{$title}</label>");
642
    }
643
644
    /**
645
     * Format the label value.
646
     *
647
     * @param string $name
648
     * @param string|null $value
649
     * @return string
650
     */
651
    protected function formatLabel(string $name, string $value = null): string
652
    {
653
        return $value ?: ucwords(str_replace('_', ' ', $name));
654
    }
655
656
    /**
657
     * Convert an HTML string to entities.
658
     *
659
     * @param string $value
660
     * @return string
661
     */
662
    public function entities(string $value): string
663
    {
664
        return htmlentities($value, ENT_QUOTES, 'UTF-8', false);
665
    }
666
667
    /**
668
     * Create a text input field.
669
     *
670
     * @param string $name
671
     * @param string $value
672
     * @param array $options
673
     * @return HtmlString
674
     */
675
    public function text(string $name, $value = null, array $options = []): HtmlString
676
    {
677
        return $this->input('text', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

677
        return $this->input('text', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
678
    }
679
680
    /**
681
     * Create a password input field.
682
     *
683
     * @param string $name
684
     * @param array $options
685
     * @return HtmlString
686
     */
687
    public function password(string $name, array $options = []): HtmlString
688
    {
689
        return $this->input('password', $name, '', $options);
690
    }
691
692
    /**
693
     * Create a range input field.
694
     *
695
     * @param string $name
696
     * @param string $value
697
     * @param array $options
698
     * @return HtmlString
699
     */
700
    public function range(string $name, $value = null, array $options = []): HtmlString
701
    {
702
        return $this->input('range', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

702
        return $this->input('range', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
703
    }
704
705
    /**
706
     * Create a search input field.
707
     *
708
     * @param string $name
709
     * @param string $value
710
     * @param array $options
711
     * @return HtmlString
712
     */
713
    public function search(string $name, $value = null, array $options = []): HtmlString
714
    {
715
        return $this->input('search', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

715
        return $this->input('search', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
716
    }
717
718
    /**
719
     * Create an e-mail input field.
720
     *
721
     * @param string $name
722
     * @param string $value
723
     * @param array $options
724
     * @return HtmlString
725
     */
726
    public function email(string $name, $value = null, array $options = []): HtmlString
727
    {
728
        return $this->input('email', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

728
        return $this->input('email', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
729
    }
730
731
    /**
732
     * Create a tel input field.
733
     *
734
     * @param string $name
735
     * @param string $value
736
     * @param array $options
737
     * @return HtmlString
738
     */
739
    public function tel(string $name, $value = null, array $options = []): HtmlString
740
    {
741
        return $this->input('tel', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

741
        return $this->input('tel', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
742
    }
743
744
    /**
745
     * Create a number input field.
746
     *
747
     * @param string $name
748
     * @param string $value
749
     * @param array $options
750
     * @return HtmlString
751
     */
752
    public function number(string $name, $value = null, array $options = []): HtmlString
753
    {
754
        return $this->input('number', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

754
        return $this->input('number', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
755
    }
756
757
    /**
758
     * Create a date input field.
759
     *
760
     * @param string $name
761
     * @param mixed $value
762
     * @param array $options
763
     * @return HtmlString
764
     */
765
    public function date(string $name, $value = null, array $options = []): HtmlString
766
    {
767
        if ($value instanceof DateTime) {
768
            $value = $value->format('Y-m-d');
769
        }
770
771
        return $this->input('date', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

771
        return $this->input('date', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
772
    }
773
774
    /**
775
     * Create a datetime input field.
776
     *
777
     * @param string $name
778
     * @param mixed $value
779
     * @param array $options
780
     * @return HtmlString
781
     */
782
    public function datetime(string $name, $value = null, array $options = []): HtmlString
783
    {
784
        if ($value instanceof DateTime) {
785
            $value = $value->format(DateTime::RFC3339);
786
        }
787
788
        return $this->input('datetime', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

788
        return $this->input('datetime', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
789
    }
790
791
    /**
792
     * Create a datetime-local input field.
793
     *
794
     * @param string $name
795
     * @param mixed $value
796
     * @param array $options
797
     * @return HtmlString
798
     */
799
    public function datetimeLocal(string $name, $value = null, array $options = []): HtmlString
800
    {
801
        if ($value instanceof DateTime) {
802
            $value = $value->format('Y-m-d\TH:i');
803
        }
804
805
        return $this->input('datetime-local', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

805
        return $this->input('datetime-local', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
806
    }
807
808
    /**
809
     * Create a time input field.
810
     *
811
     * @param string $name
812
     * @param mixed $value
813
     * @param array $options
814
     * @return HtmlString
815
     */
816
    public function time(string $name, $value = null, array $options = []): HtmlString
817
    {
818
        if ($value instanceof DateTime) {
819
            $value = $value->format('H:i');
820
        }
821
822
        return $this->input('time', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

822
        return $this->input('time', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
823
    }
824
825
    /**
826
     * Create a url input field.
827
     *
828
     * @param string $name
829
     * @param string $value
830
     * @param array $options
831
     * @return HtmlString
832
     */
833
    public function url(string $name, $value = null, array $options = []): HtmlString
834
    {
835
        return $this->input('url', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

835
        return $this->input('url', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
836
    }
837
838
    /**
839
     * Create a week input field.
840
     *
841
     * @param string $name
842
     * @param mixed $value
843
     * @param array $options
844
     * @return HtmlString
845
     */
846
    public function week(string $name, $value = null, array $options = []): HtmlString
847
    {
848
        if ($value instanceof DateTime) {
849
            $value = $value->format('Y-\WW');
850
        }
851
852
        return $this->input('week', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

852
        return $this->input('week', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
853
    }
854
855
    /**
856
     * Create a file input field.
857
     *
858
     * @param string $name
859
     * @param array $options
860
     * @return HtmlString
861
     */
862
    public function file(string $name, array $options = []): HtmlString
863
    {
864
        return $this->input('file', $name, null, $options);
865
    }
866
867
    /**
868
     * Create a textarea input field.
869
     *
870
     * @param string $name
871
     * @param string $value
872
     * @param array $options
873
     * @return HtmlString
874
     */
875
    public function textarea(string $name, $value = null, array $options = []): HtmlString
876
    {
877
        $this->type = 'textarea';
878
879
        if (!isset($options['name'])) {
880
            $options['name'] = $name;
881
        }
882
883
        // Next we will look for the rows and cols attributes, as each of these are put
884
        // on the textarea element definition. If they are not present, we will just
885
        // assume some sane default values for these attributes for the developer.
886
        $options = $this->setTextAreaSize($options);
887
888
        $options['id'] = $this->getIdAttribute($name, $options);
889
890
        $value = (string)$this->getValueAttribute($name, $value);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders...er::getValueAttribute() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

890
        $value = (string)$this->getValueAttribute($name, /** @scrutinizer ignore-type */ $value);
Loading history...
891
892
        unset($options['size']);
893
894
        // Next we will convert the attributes into a string form. Also we have removed
895
        // the size attribute, as it was merely a short-cut for the rows and cols on
896
        // the element. Then we'll create the final textarea elements HTML for us.
897
        $options = $this->attributes($options);
898
899
        return $this->toHtmlString('<textarea' . $options . '>' . e($value, false) . '</textarea>');
900
    }
901
902
    /**
903
     * Set the text area size on the attributes.
904
     *
905
     * @param array $options
906
     * @return array
907
     */
908
    protected function setTextAreaSize(array $options): array
909
    {
910
        if (isset($options['size'])) {
911
            return $this->setQuickTextAreaSize($options);
912
        }
913
914
        // If the "size" attribute was not specified, we will just look for the regular
915
        // columns and rows attributes, using sane defaults if these do not exist on
916
        // the attributes array. We'll then return this entire options array back.
917
        $cols = Arr::get($options, 'cols', 50);
918
919
        $rows = Arr::get($options, 'rows', 10);
920
921
        return array_merge($options, compact('cols', 'rows'));
922
    }
923
924
    /**
925
     * Set the text area size using the quick "size" attribute.
926
     *
927
     * @param array $options
928
     * @return array
929
     */
930
    protected function setQuickTextAreaSize(array $options): array
931
    {
932
        $segments = explode('x', $options['size']);
933
934
        return array_merge($options, ['cols' => $segments[0], 'rows' => $segments[1]]);
935
    }
936
937
    /**
938
     * Create a select year field.
939
     *
940
     * @param string $name
941
     * @param string $begin
942
     * @param string $end
943
     * @param string|null $selected
944
     * @param array $options
945
     * @return HtmlString
946
     */
947
    public function selectYear(string $name, string $begin, string $end, string $selected = null, array $options = []): HtmlString
948
    {
949
        return $this->selectRange($name, $begin, $end, $selected, $options);
950
    }
951
952
    /**
953
     * Create a select range field.
954
     *
955
     * @param string $name
956
     * @param string $begin
957
     * @param string $end
958
     * @param string $selected
959
     * @param array $options
960
     * @return HtmlString
961
     */
962
    public function selectRange(string $name, $begin, $end, $selected = null, array $options = []): HtmlString
963
    {
964
        $range = array_combine($range = range($begin, $end), $range);
965
966
        return $this->select($name, $range, $selected, $options);
0 ignored issues
show
Bug introduced by
It seems like $selected can also be of type string; however, parameter $selected of Hafijul233\Form\Builders\FormBuilder::select() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

966
        return $this->select($name, $range, /** @scrutinizer ignore-type */ $selected, $options);
Loading history...
967
    }
968
969
    /**
970
     * Create a select box field.
971
     *
972
     * @param string $name
973
     * @param array $list
974
     * @param null $selected
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $selected is correct as it would always require null to be passed?
Loading history...
975
     * @param array $selectAttributes
976
     * @param array $optionsAttributes
977
     * @param array $optgroupsAttributes
978
     * @return HtmlString
979
     */
980
    public function select(
981
        string $name,
982
        array $list = [],
983
        $selected = null,
984
        array $selectAttributes = [],
985
        array $optionsAttributes = [],
986
        array $optgroupsAttributes = []
987
    ): HtmlString
988
    {
989
        $this->type = 'select';
990
991
        // When building a select box the "value" attribute is really the selected one
992
        // so we will use that when checking the model or session for a value which
993
        // should provide a convenient method of re-populating the forms on post.
994
        $selected = $this->getValueAttribute($name, $selected);
995
996
        $selectAttributes['id'] = $this->getIdAttribute($name, $selectAttributes);
997
998
        if (!isset($selectAttributes['name'])) {
999
            $selectAttributes['name'] = $name;
1000
        }
1001
1002
        // We will simply loop through the options and build an HTML value for each of
1003
        // them until we have an array of HTML declarations. Then we will join them
1004
        // all together into one single HTML element that can be put on the form.
1005
        $html = [];
1006
1007
        if (isset($selectAttributes['placeholder'])) {
1008
            $html[] = $this->placeholderOption($selectAttributes['placeholder'], $selected);
1009
            unset($selectAttributes['placeholder']);
1010
        }
1011
1012
        foreach ($list as $value => $display) {
1013
            $optionAttributes = $optionsAttributes[$value] ?? [];
1014
            $optgroupAttributes = $optgroupsAttributes[$value] ?? [];
1015
            $html[] = $this->getSelectOption($display, $value, $selected, $optionAttributes, $optgroupAttributes);
1016
        }
1017
1018
        // Once we have all of this HTML, we can join this into a single element after
1019
        // formatting the attributes into an HTML "attributes" string, then we will
1020
        // build out a final select statement, which will contain all the values.
1021
        $selectAttributes = $this->attributes($selectAttributes);
1022
1023
        $list = implode('', $html);
1024
1025
        return $this->toHtmlString("<select{$selectAttributes}>{$list}</select>");
1026
    }
1027
1028
    /**
1029
     * Create a placeholder select element option.
1030
     *
1031
     * @param $display
1032
     * @param $selected
1033
     * @return HtmlString
1034
     */
1035
    protected function placeholderOption($display, $selected): HtmlString
1036
    {
1037
        $selected = $this->getSelectedValue(null, $selected);
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type string expected by parameter $value of Hafijul233\Form\Builders...der::getSelectedValue(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1037
        $selected = $this->getSelectedValue(/** @scrutinizer ignore-type */ null, $selected);
Loading history...
1038
1039
        $options = [
1040
            'selected' => $selected,
1041
            'value' => '',
1042
        ];
1043
1044
        return $this->toHtmlString('<option' . $this->attributes($options) . '>' . e($display, false) . '</option>');
1045
    }
1046
1047
    /**
1048
     * Determine if the value is selected.
1049
     *
1050
     * @param string $value
1051
     * @param mixed $selected
1052
     * @return mixed
1053
     */
1054
    protected function getSelectedValue(string $value, $selected)
1055
    {
1056
        if (is_array($selected)) {
1057
            return in_array($value, $selected, true) || in_array((string)$value, $selected, true) ? 'selected' : null;
1058
        } elseif ($selected instanceof Collection) {
1059
            return $selected->contains($value) ? 'selected' : null;
1060
        }
1061
        /** @phpstan-ignore-next-line */
1062
        if (is_int($value) && is_bool($selected)) {
0 ignored issues
show
introduced by
The condition is_int($value) is always false.
Loading history...
1063
            return (bool)$value === $selected;
1064
        }
1065
1066
        return ((string)$value === (string)$selected) ? 'selected' : null;
1067
    }
1068
1069
    /**
1070
     * Get the select option for the given value.
1071
     *
1072
     * @param mixed $display
1073
     * @param string $value
1074
     * @param string $selected
1075
     * @param array $attributes
1076
     * @param array $optgroupAttributes
1077
     * @return HtmlString
1078
     */
1079
    public function getSelectOption($display, string $value, string $selected, array $attributes = [], array $optgroupAttributes = []): HtmlString
1080
    {
1081
        if (is_iterable($display)) {
1082
            return $this->optionGroup($display, $value, $selected, $optgroupAttributes, $attributes);
1083
        }
1084
1085
        return $this->option($display, $value, $selected, $attributes);
1086
    }
1087
1088
    /**
1089
     * Create an option group form element.
1090
     *
1091
     * @param array $list
1092
     * @param string $label
1093
     * @param string $selected
1094
     * @param array $attributes
1095
     * @param array $optionsAttributes
1096
     * @param int $level
1097
     * @return HtmlString
1098
     */
1099
    protected function optionGroup(array $list, string $label, string $selected, array $attributes = [], array $optionsAttributes = [], $level = 0): HtmlString
1100
    {
1101
        $html = [];
1102
        $space = str_repeat('&nbsp;', $level);
1103
        foreach ($list as $value => $display) {
1104
            $optionAttributes = $optionsAttributes[$value] ?? [];
1105
            if (is_iterable($display)) {
1106
                $html[] = $this->optionGroup($display, $value, $selected, $attributes, $optionAttributes, $level + 5);
1107
            } else {
1108
                $html[] = $this->option($space . $display, $value, $selected, $optionAttributes);
1109
            }
1110
        }
1111
1112
        return $this->toHtmlString('<optgroup label="' . e($space . $label, false) . '"' . $this->attributes($attributes) . '>' . implode('', $html) . '</optgroup>');
1113
    }
1114
1115
    /**
1116
     * Create a select element option.
1117
     *
1118
     * @param string $display
1119
     * @param string $value
1120
     * @param string $selected
1121
     * @param array $attributes
1122
     * @return HtmlString
1123
     */
1124
    protected function option(string $display, string $value, string $selected, array $attributes = []): HtmlString
1125
    {
1126
        $selected = $this->getSelectedValue($value, $selected);
1127
1128
        $options = array_merge(['value' => $value, 'selected' => $selected], $attributes);
1129
1130
        $string = '<option' . $this->attributes($options) . '>';
1131
        if ($display !== null) {
0 ignored issues
show
introduced by
The condition $display !== null is always true.
Loading history...
1132
            $string .= e($display, false) . '</option>';
1133
        }
1134
1135
        return $this->toHtmlString($string);
1136
    }
1137
1138
    /**
1139
     * Create a select month field.
1140
     *
1141
     * @param string $name
1142
     * @param string $selected
1143
     * @param array $options
1144
     * @param string $format
1145
     * @return HtmlString
1146
     */
1147
    public function selectMonth(string $name, $selected = null, $format = '%B', array $options = []): HtmlString
1148
    {
1149
        $months = Config::get('form.months', [
1150
            '1' => 'January',
1151
            '2' => 'February',
1152
            '3' => 'March',
1153
            '4' => 'April',
1154
            '5' => 'May',
1155
            '6' => 'June',
1156
            '7' => 'July',
1157
            '8' => 'August',
1158
            '9' => 'September',
1159
            '10' => 'October',
1160
            '11' => 'November',
1161
            '12' => 'December',
1162
        ]);
1163
1164
        return $this->select($name, $months, $selected, $options);
0 ignored issues
show
Bug introduced by
It seems like $selected can also be of type string; however, parameter $selected of Hafijul233\Form\Builders\FormBuilder::select() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1164
        return $this->select($name, $months, /** @scrutinizer ignore-type */ $selected, $options);
Loading history...
1165
    }
1166
1167
    /**
1168
     * Create a checkbox input field.
1169
     *
1170
     * @param string $name
1171
     * @param mixed $value
1172
     * @param bool $checked
1173
     * @param array $options
1174
     * @return HtmlString
1175
     */
1176
    public function checkbox(string $name, $value = 1, $checked = null, array $options = []): HtmlString
1177
    {
1178
        return $this->checkable('checkbox', $name, $value, $checked, $options);
0 ignored issues
show
Bug introduced by
It seems like $checked can also be of type null; however, parameter $checked of Hafijul233\Form\Builders\FormBuilder::checkable() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1178
        return $this->checkable('checkbox', $name, $value, /** @scrutinizer ignore-type */ $checked, $options);
Loading history...
1179
    }
1180
1181
    /**
1182
     * Create a checkable input field.
1183
     *
1184
     * @param string $type
1185
     * @param string $name
1186
     * @param mixed $value
1187
     * @param bool $checked
1188
     * @param array $options
1189
     * @return HtmlString
1190
     */
1191
    protected function checkable(string $type, string $name, $value, bool $checked, array $options = []): HtmlString
1192
    {
1193
        $this->type = $type;
1194
1195
        $checked = $this->getCheckedState($type, $name, $value, $checked);
1196
1197
        if ($checked) {
1198
            $options['checked'] = 'checked';
1199
        }
1200
1201
        return $this->input($type, $name, $value, $options);
1202
    }
1203
1204
    /**
1205
     * Get the check state for a checkable input.
1206
     *
1207
     * @param string $type
1208
     * @param string $name
1209
     * @param mixed $value
1210
     * @param bool $checked
1211
     * @return bool
1212
     */
1213
    protected function getCheckedState(string $type, string $name, $value, bool $checked): bool
1214
    {
1215
        switch ($type) {
1216
            case 'checkbox':
1217
                return $this->getCheckboxCheckedState($name, $value, $checked);
1218
1219
            case 'radio':
1220
                return $this->getRadioCheckedState($name, $value, $checked);
1221
1222
            default:
1223
                return $this->compareValues($name, $value);
1224
        }
1225
    }
1226
1227
    /**
1228
     * Get the check state for a checkbox input.
1229
     *
1230
     * @param string $name
1231
     * @param mixed $value
1232
     * @param bool $checked
1233
     * @return bool
1234
     */
1235
    protected function getCheckboxCheckedState(string $name, $value, bool $checked): bool
1236
    {
1237
        $request = $this->request($name);
1238
1239
        if (!$this->oldInputIsEmpty() && is_null($this->old($name)) && !$request) {
1240
            return false;
1241
        }
1242
1243
        if ($this->missingOldAndModel($name) && is_null($request)) {
1244
            return $checked;
1245
        }
1246
1247
        $posted = $this->getValueAttribute($name, $checked);
1248
1249
        if (is_array($posted)) {
1250
            return in_array($value, $posted);
1251
        } elseif ($posted instanceof Collection) {
1252
            return $posted->contains('id', $value);
1253
        } else {
1254
            return (bool)$posted;
1255
        }
1256
    }
1257
1258
    /**
1259
     * Determine if the old input is empty.
1260
     *
1261
     * @return bool
1262
     */
1263
    public function oldInputIsEmpty(): bool
1264
    {
1265
        return count((array)$this->session->getOldInput()) === 0;
1266
    }
1267
1268
    /**
1269
     * Determine if old input or model input exists for a key.
1270
     *
1271
     * @param string $name
1272
     * @return bool
1273
     */
1274
    protected function missingOldAndModel(string $name): bool
1275
    {
1276
        return is_null($this->old($name)) && is_null($this->getModelValueAttribute($name));
1277
    }
1278
1279
    /**
1280
     * Get the check state for a radio input.
1281
     *
1282
     * @param string $name
1283
     * @param mixed $value
1284
     * @param bool $checked
1285
     * @return bool
1286
     */
1287
    protected function getRadioCheckedState(string $name, $value, bool $checked): bool
1288
    {
1289
        $request = $this->request($name);
1290
1291
        if ($this->missingOldAndModel($name) && !$request) {
1292
            return $checked;
1293
        }
1294
1295
        return $this->compareValues($name, $value);
1296
    }
1297
1298
    /**
1299
     * Determine if the provide value loosely compares to the value assigned to the field.
1300
     * Use loose comparison because Laravel model casting may be in affect and therefore
1301
     * 1 == true and 0 == false.
1302
     *
1303
     * @param string $name
1304
     * @param string $value
1305
     * @return bool
1306
     */
1307
    protected function compareValues(string $name, string $value): bool
1308
    {
1309
        return $this->getValueAttribute($name) == $value;
1310
    }
1311
1312
    /**
1313
     * Create a radio button input field.
1314
     *
1315
     * @param string $name
1316
     * @param mixed $value
1317
     * @param bool $checked
1318
     * @param array $options
1319
     * @return HtmlString
1320
     */
1321
    public function radio(string $name, $value = null, $checked = null, array $options = []): HtmlString
1322
    {
1323
        if (is_null($value)) {
1324
            $value = $name;
1325
        }
1326
1327
        return $this->checkable('radio', $name, $value, $checked, $options);
0 ignored issues
show
Bug introduced by
It seems like $checked can also be of type null; however, parameter $checked of Hafijul233\Form\Builders\FormBuilder::checkable() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1327
        return $this->checkable('radio', $name, $value, /** @scrutinizer ignore-type */ $checked, $options);
Loading history...
1328
    }
1329
1330
    /**
1331
     * Create a HTML reset input element.
1332
     *
1333
     * @param string $value
1334
     * @param array $attributes
1335
     * @return HtmlString
1336
     */
1337
    public function reset(string $value, $attributes = []): HtmlString
1338
    {
1339
        return $this->input('reset', null, $value, $attributes);
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type string expected by parameter $name of Hafijul233\Form\Builders\FormBuilder::input(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1339
        return $this->input('reset', /** @scrutinizer ignore-type */ null, $value, $attributes);
Loading history...
1340
    }
1341
1342
    /**
1343
     * Create a HTML image input element.
1344
     *
1345
     * @param string $url
1346
     * @param null $name
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $name is correct as it would always require null to be passed?
Loading history...
1347
     * @param array $attributes
1348
     * @return HtmlString
1349
     */
1350
    public function image(string $url, $name = null, $attributes = []): HtmlString
1351
    {
1352
        $attributes['src'] = $this->url->asset($url);
1353
1354
        return $this->input('image', $name, null, $attributes);
0 ignored issues
show
Bug introduced by
$name of type null is incompatible with the type string expected by parameter $name of Hafijul233\Form\Builders\FormBuilder::input(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1354
        return $this->input('image', /** @scrutinizer ignore-type */ $name, null, $attributes);
Loading history...
1355
    }
1356
1357
    /**
1358
     * Create a month input field.
1359
     *
1360
     * @param string $name
1361
     * @param mixed $value
1362
     * @param array $options
1363
     * @return HtmlString
1364
     */
1365
    public function month(string $name, $value = null, array $options = []): HtmlString
1366
    {
1367
        if ($value instanceof DateTime) {
1368
            $value = $value->format('Y-m');
1369
        }
1370
1371
        return $this->input('month', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1371
        return $this->input('month', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
1372
    }
1373
1374
    /**
1375
     * Create a color input field.
1376
     *
1377
     * @param string $name
1378
     * @param string $value
1379
     * @param array $options
1380
     * @return HtmlString
1381
     */
1382
    public function color(string $name, $value = null, array $options = []): HtmlString
1383
    {
1384
        return $this->input('color', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1384
        return $this->input('color', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
1385
    }
1386
1387
    /**
1388
     * Create a submit button element.
1389
     *
1390
     * @param string $name
1391
     * @param null $value
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $value is correct as it would always require null to be passed?
Loading history...
1392
     * @param bool $button
1393
     * @param array $options
1394
     * @return HtmlString
1395
     */
1396
    public function submit($name = 'submit', $value = null, $button = false, array $options = []): HtmlString
1397
    {
1398
        if ($button) {
1399
            $options['type'] = 'submit';
1400
            $options['name'] = $name;
1401
1402
            if (empty($options['class'])) {
1403
                $options['class'] = Config::get('form.submit_class', 'btn btn-primary fw-bold');
1404
            }
1405
1406
            return $this->button($value, $options);
1407
        }
1408
1409
        return $this->input('submit', null, $value, $options);
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type string expected by parameter $name of Hafijul233\Form\Builders\FormBuilder::input(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1409
        return $this->input('submit', /** @scrutinizer ignore-type */ null, $value, $options);
Loading history...
1410
    }
1411
1412
    /**
1413
     * Create a button element.
1414
     *
1415
     * @param string $value
1416
     * @param array $options
1417
     * @return HtmlString
1418
     */
1419
    public function button($value = null, array $options = []): HtmlString
1420
    {
1421
        if (!array_key_exists('type', $options)) {
1422
            $options['type'] = 'button';
1423
        }
1424
1425
        return $this->toHtmlString('<button' . $this->attributes($options) . '>' . $value . '</button>');
1426
    }
1427
1428
    /**
1429
     * Create a datalist box field.
1430
     *
1431
     * @param string $id
1432
     * @param array $list
1433
     * @return HtmlString
1434
     */
1435
    public function datalist(string $id, $list = []): HtmlString
1436
    {
1437
        $this->type = 'datalist';
1438
1439
        $attributes['id'] = $id;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$attributes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $attributes = array(); before regardless.
Loading history...
1440
1441
        $html = [];
1442
1443
        if ($this->isAssociativeArray($list)) {
1444
            foreach ($list as $value => $display) {
1445
                $html[] = $this->option($display, $value, null, []);
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type string expected by parameter $selected of Hafijul233\Form\Builders\FormBuilder::option(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1445
                $html[] = $this->option($display, $value, /** @scrutinizer ignore-type */ null, []);
Loading history...
1446
            }
1447
        } else {
1448
            foreach ($list as $value) {
1449
                $html[] = $this->option($value, $value, null, []);
1450
            }
1451
        }
1452
1453
        $attributes = $this->attributes($attributes);
1454
1455
        $list = implode('', $html);
1456
1457
        return $this->toHtmlString("<datalist{$attributes}>{$list}</datalist>");
1458
    }
1459
1460
    /**
1461
     * Determine if an array is associative.
1462
     *
1463
     * @param array $array
1464
     * @return bool
1465
     */
1466
    protected function isAssociativeArray(array $array): bool
1467
    {
1468
        return array_values($array) !== $array;
1469
    }
1470
1471
    /**
1472
     * Create a form error display element.
1473
     *
1474
     * @param string $name
1475
     * @param bool $all
1476
     * @param array $options
1477
     * @return HtmlString
1478
     */
1479
    public function error(string $name, bool $all = false, array $options = []): HtmlString
1480
    {
1481
        if (empty($options['class'])) {
1482
            $options['class'] = Config::get('form.error_class', 'invalid-feedback');
1483
        }
1484
1485
        return $this->getErrorMessage($name, $all, $options);
1486
    }
1487
1488
    /**
1489
     * @param string $name
1490
     * @param bool $list
1491
     * @param array $options
1492
     * @return HtmlString
1493
     */
1494
    protected function getErrorMessage(string $name, bool $list = false, array $options = []): HtmlString
1495
    {
1496
        $errors = $this->request->session()->get('errors') ?? new ViewErrorBag;
1497
1498
        $errorMessage = ($list)
1499
            ? (($errors->all($name) ?? []))
1500
            : ($errors->first($name) ?? null);
1501
1502
        $message = null;
1503
        $options = $this->attributes($options);
1504
        if (is_array($errorMessage)) {
1505
            $message = "<ul id=\"{$name}-error\">";
1506
            foreach ($errorMessage as $error) {
1507
                $message .= ("<li {$options}>{$error}</li>");
1508
            }
1509
            $message .= '</ul>';
1510
        } else {
1511
            $message = ("<span id=\"{$name}-error\" {$options}>{$message}</span>");
1512
        }
1513
1514
        return $this->toHtmlString($message);
1515
    }
1516
1517
1518
    /**
1519
     * Take Request in fill process
1520
     *
1521
     * @param bool $consider
1522
     */
1523
    public function considerRequest(bool $consider = true)
1524
    {
1525
        $this->considerRequest = $consider;
1526
    }
1527
1528
    /**
1529
     * Get the session store implementation.
1530
     *
1531
     * @return  Session  $session
1532
     */
1533
    public function getSessionStore(): Session
1534
    {
1535
        return $this->session;
1536
    }
1537
1538
    /**
1539
     * Set the session store implementation.
1540
     *
1541
     * @param Session $session
1542
     * @return $this
1543
     */
1544
    public function setSessionStore(Session $session): self
1545
    {
1546
        $this->session = $session;
1547
1548
        return $this;
1549
    }
1550
1551
    /**
1552
     * Dynamically handle calls to the class.
1553
     *
1554
     * @param string $method
1555
     * @param array $parameters
1556
     * @return mixed
1557
     *
1558
     * @throws BadMethodCallException
1559
     */
1560
    public function __call(string $method, array $parameters)
1561
    {
1562
        if (static::hasComponent($method)) {
1563
            return $this->componentCall($method, $parameters);
0 ignored issues
show
Bug introduced by
The method componentCall() does not exist on Hafijul233\Form\Builders\FormBuilder. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1563
            return $this->/** @scrutinizer ignore-call */ componentCall($method, $parameters);
Loading history...
1564
        }
1565
1566
        if (static::hasMacro($method)) {
1567
            return $this->macroCall($method, $parameters);
0 ignored issues
show
Bug introduced by
The method macroCall() does not exist on Hafijul233\Form\Builders\FormBuilder. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1567
            return $this->/** @scrutinizer ignore-call */ macroCall($method, $parameters);
Loading history...
1568
        }
1569
1570
        throw new BadMethodCallException("Method {$method} does not exist.");
1571
    }
1572
}
1573