Completed
Push — feature/middleware ( 45b992...e11f5b )
by Romain
02:19
created

AbstractFormValidator::initializeValidator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 2
nop 1
1
<?php
2
/*
3
 * 2017 Romain CANON <[email protected]>
4
 *
5
 * This file is part of the TYPO3 FormZ project.
6
 * It is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU General Public License, either
8
 * version 3 of the License, or any later version.
9
 *
10
 * For the full copyright and license information, see:
11
 * http://www.gnu.org/licenses/gpl-3.0.html
12
 */
13
14
namespace Romm\Formz\Validation\Form;
15
16
use Romm\Formz\Core\Core;
17
use Romm\Formz\Error\FormResult;
18
use Romm\Formz\Exceptions\InvalidArgumentTypeException;
19
use Romm\Formz\Form\Definition\Field\Field;
20
use Romm\Formz\Form\FormInterface;
21
use Romm\Formz\Form\FormObject\FormObject;
22
use Romm\Formz\Form\FormObject\FormObjectFactory;
23
use Romm\Formz\Form\FormObject\FormObjectProxy;
24
use Romm\Formz\Service\FormService;
25
use Romm\Formz\Validation\Validator\Form\DataObject\FormValidatorDataObject;
26
use TYPO3\CMS\Core\Utility\GeneralUtility;
27
use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator as ExtbaseAbstractValidator;
28
29
/**
30
 * This is the abstract form validator, which must be inherited by any custom
31
 * form validator in order to work properly.
32
 *
33
 * Please note that a default form validator already exists if you need a form
34
 * which does not require any particular action: `DefaultFormValidator`.
35
 *
36
 * A form validator should be called to validate any form instance (which is a
37
 * child of `AbstractForm`). Usually, this is used in controller actions to
38
 * validate a form sent by the user. Example:
39
 *
40
 * /**
41
 *  * Action called when the Example form is submitted.
42
 *  *
43
 *  * @param $exForm
44
 *  * @validate $exForm Romm.Formz:Form\DefaultFormValidator
45
 *  * /
46
 *  public function submitFormAction(ExampleForm $exForm) { ... }
47
 *
48
 *******************************************************************************
49
 *
50
 * You may use you own custom form validator in order to be able to use the
51
 * following features:
52
 *
53
 * - Pre-validation custom process:
54
 *   By extending the method `beforeValidationProcess()`, you are able to handle
55
 *   anything you want just before the form validation begins to loop on every
56
 *   field. This can be used for instance to (de)activate the validation of
57
 *   certain fields under very specific circumstances.
58
 *
59
 * - In real time custom process:
60
 *   After each field went trough a validation process, a magic method is called
61
 *   to allow very low level custom process. The magic method name looks like:
62
 *   "{lowerCamelCaseFieldName}Validated". For instance, when the "email" field
63
 *   just went trough the validation process, the method `emailValidated()` is
64
 *   called.
65
 *
66
 * - Post-validation custom process:
67
 *   After the validation was done on every field of the form, this method is
68
 *   called to allow you high level process. For instance, let's assume your
69
 *   form is used to calculate a price estimation depending on information
70
 *   submitted in the form; when the form went trough the validation process and
71
 *   got no error, you can run the price estimation, and if any error occurs you
72
 *   are still able to add an error to `$this->result` (in a controller you do
73
 *   not have access to it anymore).
74
 */
75
abstract class AbstractFormValidator extends ExtbaseAbstractValidator
76
{
77
    /**
78
     * @inheritdoc
79
     */
80
    protected $supportedOptions = [
81
        'name'  => ['', 'Name of the form.', 'string'],
82
        'dummy' => [false, 'Dummy mode?', 'bool'],
83
        'form'  => [null, 'Form instance', FormInterface::class]
84
    ];
85
86
    /**
87
     * @var FormResult
88
     */
89
    protected $result;
90
91
    /**
92
     * @var FormInterface
93
     */
94
    protected $form;
95
96
    /**
97
     * @var FormObject
98
     */
99
    protected $formObject;
100
101
    /**
102
     * @var FormValidatorExecutor
103
     */
104
    private $formValidatorExecutor;
105
106
    /**
107
     * @var FormValidatorDataObject
108
     */
109
    protected $dataObject;
110
111
    /**
112
     * Initializes all class variables.
113
     *
114
     * @param FormInterface $form
115
     * @throws InvalidArgumentTypeException
116
     */
117
    protected function initializeValidator($form)
118
    {
119
        if (false === $form instanceof FormInterface) {
120
            throw InvalidArgumentTypeException::validatingWrongFormType(get_class($form));
121
        }
122
123
        $this->form = $form;
124
        $this->getFormObject();
125
        $this->getFormResult();
126
        $this->formValidatorExecutor = $this->getFormValidatorExecutor();
127
128
        $this->getDataObject()->addFieldValidationCallback(function (Field $field) {
129
            $this->afterFieldValidation($field);
130
        });
131
    }
132
133
    /**
134
     * Checks the given form instance, and launches the validation if it is a
135
     * correct form.
136
     *
137
     * @param FormInterface $form The form instance to be validated.
138
     * @return FormResult
139
     */
140
    public function validate($form)
141
    {
142
        $this->initializeValidator($form);
143
144
        if (false === $this->isDummy()) {
145
            $proxy = $this->getProxy($form);
146
            $proxy->markFormAsValidated();
147
            $proxy->markFormAsSubmitted();
148
        }
149
150
        $this->isValid($form);
151
152
        return $this->result;
153
    }
154
155
    /**
156
     * Runs the whole validation workflow.
157
     *
158
     * @param FormInterface $form
159
     */
160
    final public function isValid($form)
161
    {
162
        $this->formValidatorExecutor->applyBehaviours();
163
        $this->formValidatorExecutor->checkFieldsActivation();
164
165
        $this->beforeValidationProcess();
166
167
        $this->formValidatorExecutor->validateFields();
168
169
        $this->afterValidationProcess();
170
171
        if ($this->result->hasErrors()) {
172
            // Storing the form for possible third party further usage.
173
            FormService::addFormWithErrors($form);
174
        }
175
    }
176
177
    /**
178
     * Override this function in your child class to handle some pre-validation
179
     * process.
180
     */
181
    protected function beforeValidationProcess()
182
    {
183
    }
184
185
    /**
186
     * Override this function in your child class to handle some post-validation
187
     * process.
188
     */
189
    protected function afterValidationProcess()
190
    {
191
    }
192
193
    /**
194
     * After each field has been validated, a matching method can be called if
195
     * it exists in the child class.
196
     *
197
     * The syntax is `{lowerCamelCaseFieldName}Validated()`.
198
     *
199
     * Example: for field `firstName` - `firstNameValidated()`.
200
     *
201
     * @param Field $field
202
     */
203
    protected function afterFieldValidation(Field $field)
204
    {
205
        $functionName = lcfirst($field->getName() . 'Validated');
206
207
        if (method_exists($this, $functionName)) {
208
            call_user_func([$this, $functionName]);
209
        }
210
    }
211
212
    /**
213
     * If the form validator is a dummy, a new instance of `FormResult` is
214
     * created and returned, preventing
215
     *
216
     * @return FormResult
217
     */
218
    protected function getFormResult()
219
    {
220
        if (null === $this->result) {
221
            $this->result = $this->isDummy()
222
                ? GeneralUtility::makeInstance(FormResult::class)
223
                : $this->getFormObject()->getFormResult();
224
        }
225
226
        return $this->result;
227
    }
228
229
    /**
230
     * @return bool
231
     */
232
    protected function isDummy()
233
    {
234
        return true === $this->options['dummy'];
235
    }
236
237
    /**
238
     * @return FormValidatorExecutor
239
     */
240
    protected function getFormValidatorExecutor()
241
    {
242
        /** @var FormValidatorExecutor $formValidatorExecutor */
243
        $formValidatorExecutor = Core::instantiate(FormValidatorExecutor::class, $this->getFormObject(), $this->getDataObject());
244
245
        return $formValidatorExecutor;
246
    }
247
248
    /**
249
     * @return FormObject
250
     */
251
    protected function getFormObject()
252
    {
253
        if (null === $this->formObject) {
254
            $form = $this->form ?: $this->options['form'];
255
256
            if (null === $form) {
257
                // @todo
258
            }
259
260
            $this->formObject = FormObjectFactory::get()->registerAndGetFormInstance($form, $this->options['name']);
0 ignored issues
show
Bug introduced by
It seems like registerAndGetFormInstance() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
261
        }
262
263
        return $this->formObject;
264
    }
265
266
    /**
267
     * @param FormInterface $form
268
     * @return FormObjectProxy
269
     */
270
    protected function getProxy(FormInterface $form)
271
    {
272
        return FormObjectFactory::get()->getProxy($form);
0 ignored issues
show
Bug introduced by
It seems like getProxy() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
273
    }
274
275
    /**
276
     * @return FormValidatorDataObject
277
     */
278
    public function getDataObject()
279
    {
280
        if (null === $this->dataObject) {
281
            $this->dataObject = Core::instantiate(FormValidatorDataObject::class, $this->getFormResult());
282
        }
283
284
        return $this->dataObject;
285
    }
286
}
287