Issues (35)

src/Form.php (1 issue)

Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Enjoys\Forms;
6
7
use Closure;
8
use Enjoys\Forms\Elements\Csrf;
9
use Enjoys\Forms\Interfaces\DefaultsHandlerInterface;
10
use Enjoys\Forms\Traits;
11
use Enjoys\Forms\Traits\Options;
12
use Enjoys\Session\Session;
13
use HttpSoft\ServerRequest\ServerRequestCreator;
14
use Psr\Http\Message\ServerRequestInterface;
15
use Webmozart\Assert\Assert;
16
17
use function strtoupper;
18
19
class Form
20
{
21
    use Traits\Attributes;
22
    use Options;
23
    use Traits\Container {
24
        addElement as private parentAddElement;
25
    }
26
27
    private const _ALLOWED_FORM_METHOD_ = ['GET', 'POST'];
28
29
    public const _TOKEN_CSRF_ = '_token_csrf';
30
    public const _TOKEN_SUBMIT_ = '_token_submit';
31
32
    public const ATTRIBUTES_DESC = '_desc_attributes_';
33
    public const ATTRIBUTES_VALIDATE = '_validate_attributes_';
34
    public const ATTRIBUTES_LABEL = '_label_attributes_';
35
    public const ATTRIBUTES_FIELDSET = '_fieldset_attributes_';
36
    public const ATTRIBUTES_FILLABLE_BASE = '_fillable_base_attributes_';
37
38
39
    private string $method = 'POST';
40
    private ?string $action = null;
41
    private ?string $id = null;
42
43
    private ServerRequestInterface $request;
44
    private DefaultsHandlerInterface $defaultsHandler;
45
46
    private bool $submitted = false;
47
    private Session $session;
48
49
    /**
50
     * @throws Exception\ExceptionRule
51
     */
52 93
    public function __construct(
53
        string $method = 'POST',
54
        ?string $action = null,
55
        ?string $id = null,
56
        ?ServerRequestInterface $request = null,
57
        ?DefaultsHandlerInterface $defaultsHandler = null,
58
        ?Session $session = null
59
    ) {
60 93
        $this->request = $request ?? ServerRequestCreator::createFromGlobals();
61 93
        $this->session = $session ?? new Session();
62 93
        $this->defaultsHandler = $defaultsHandler ?? new DefaultsHandler();
63
64 93
        $this->setMethod($method);
65 93
        $this->setAction($action);
66 93
        $this->setId($id);
67
    }
68
69
    /**
70
     * Возвращает true если форма отправлена и валидна.
71
     * На валидацию форма проверяется по умолчанию, если использовать параметр $validate
72
     * false, проверка будет только на отправку формы
73
     * @param bool $validate
74
     * @return bool
75
     */
76 5
    public function isSubmitted(bool $validate = true): bool
77
    {
78 5
        if ($this->submitted === false) {
79 1
            return false;
80
        }
81 4
        if ($validate !== false) {
82 3
            return $this->validate();
83
        }
84 1
        return true;
85
    }
86
87 3
    public function validate(): bool
88
    {
89 3
        return Validator::check($this->getElements());
90
    }
91
92
93
    /**
94
     * @param array|Closure():array $data
95
     * @return $this
96
     */
97 29
    public function setDefaults(array|Closure $data): Form
98
    {
99 29
        if ($this->submitted === true) {
100
            /** @var array $requestData */
101 7
            $requestData = match ($this->getMethod()) {
102 7
                'GET' => $this->getRequest()->getQueryParams(),
103 7
                'POST' => $this->getRequest()->getParsedBody(),
104 7
                default => [],
105 7
            };
106 7
            $data = array_filter(
107 7
                $requestData,
108 7
                function ($k) {
109 7
                    return !in_array($k, [self::_TOKEN_CSRF_, self::_TOKEN_SUBMIT_]);
110 7
                },
111 7
                ARRAY_FILTER_USE_KEY
112 7
            );
113
        }
114
115 29
        if ($data instanceof Closure) {
0 ignored issues
show
$data is never a sub-type of Closure.
Loading history...
116 2
            $data = $data();
117
            /** @psalm-suppress RedundantConditionGivenDocblockType */
118 2
            Assert::isArray($data);
119
        }
120
121 28
        $this->defaultsHandler->setData($data);
122 28
        return $this;
123
    }
124
125
126
    /**
127
     *
128
     * Если prepare() возвращает false, то элемент добавляется,
129
     * если true, то элемент добавлен в коллекцию не будет.
130
     * @use Element::setForm()
131
     * @use Element::prepare()
132
     * @param Element $element
133
     * @param string|null $before
134
     * @param string|null $after
135
     * @return $this
136
     */
137 93
    public function addElement(Element $element, ?string $before = null, ?string $after = null): static
138
    {
139 93
        $element->setForm($this);
140 93
        return $this->parentAddElement($element, $before, $after);
141
    }
142
143
144 93
    public function getDefaultsHandler(): DefaultsHandlerInterface
145
    {
146 93
        return $this->defaultsHandler;
147
    }
148
149 93
    public function getRequest(): ServerRequestInterface
150
    {
151 93
        return $this->request;
152
    }
153
154
    /**
155
     * @throws Exception\ExceptionRule
156
     */
157 93
    public function setMethod(string $method): void
158
    {
159 93
        if (in_array(strtoupper($method), self::_ALLOWED_FORM_METHOD_)) {
160 92
            $this->method = strtoupper($method);
161
        }
162 93
        $this->setAttribute(AttributeFactory::create('method', $this->method));
163 93
        $this->setOption('method', $this->method, false);
164 93
        $this->addElement(new Csrf($this->session));
165 93
        $this->setTokenSubmitElement();
166
    }
167
168 93
    public function getMethod(): string
169
    {
170 93
        return $this->method;
171
    }
172
173 93
    public function setAction(?string $action): self
174
    {
175 93
        $this->action = $action;
176 93
        $this->setAttribute(AttributeFactory::create('action', $this->action));
177 93
        $this->setOption('action', $this->action, false);
178 93
        $this->setTokenSubmitElement();
179 93
        return $this;
180
    }
181
182 5
    public function getAction(): ?string
183
    {
184 5
        return $this->action;
185
    }
186
187 93
    public function setId(?string $id): Form
188
    {
189 93
        $this->id = $id;
190 93
        $this->setAttribute(AttributeFactory::create('id', $this->id));
191 93
        $this->setOption('id', $this->id, false);
192 93
        $this->setTokenSubmitElement();
193 93
        return $this;
194
    }
195
196
197 2
    public function getId(): ?string
198
    {
199 2
        return $this->id;
200
    }
201
202 93
    private function setTokenSubmitElement(): void
203
    {
204 93
        $tokenSubmit = new TokenSubmit($this);
205 93
        $this->addElement($tokenSubmit->getElement());
206
207 93
        $this->submitted = $tokenSubmit->validate();
208
209
        // after every update the token submit, form needs check
210
        // and update defaults if form has been submitted.
211
        // Maybe in future will refactor this code
212 93
        if ($this->submitted === true) {
213 1
            $this->setDefaults([]);
214
        }
215
    }
216
217
//    /**
218
//     * Вывод формы в Renderer
219
//     * @param \Enjoys\Forms\Interfaces\RendererInterface $renderer
220
//     * @return mixed Возвращается любой формат, в зависимоти от renderer`а, может
221
//     * вернутся строка в html, или, например, xml или массив, все зависит от рендерера.
222
//     */
223
//    public function render(\Enjoys\Forms\Interfaces\RendererInterface $renderer): mixed
224
//    {
225
//        $renderer->setForm($this);
226
//        return $renderer->output();
227
//    }
228
}
229