EmbedHelper::handleForm()   F
last analyzed

Complexity

Conditions 34
Paths 6069

Size

Total Lines 164
Code Lines 94

Duplication

Lines 0
Ratio 0 %

Importance

Changes 19
Bugs 3 Features 4
Metric Value
cc 34
eloc 94
c 19
b 3
f 4
nc 6069
nop 6
dl 0
loc 164
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Gerard van Helden <[email protected]>
4
 * @copyright Zicht Online <http://zicht.nl>
5
 */
6
namespace Zicht\Bundle\FrameworkExtraBundle\Helper;
7
8
use Symfony\Component\DependencyInjection\Container;
9
use Symfony\Component\Form\FormError;
10
use Symfony\Component\Form\FormView;
11
use Symfony\Component\HttpFoundation\Response;
12
use Symfony\Component\HttpFoundation\Request;
13
use Symfony\Component\HttpFoundation\RedirectResponse;
14
use Symfony\Component\Form\Form;
15
use Symfony\Component\Form\FormInterface;
16
use Symfony\Component\Form\FormErrorIterator;
17
use Symfony\Component\HttpFoundation\Session\SessionInterface;
18
use Zicht\Bundle\FrameworkExtraBundle\Http\JsonResponse;
19
use Zicht\Bundle\FrameworkExtraBundle\Url\UrlCheckerService;
20
21
/**
22
 * Helper class to facilitate embedded forms in ESI with redirection handling.
23
 */
24
class EmbedHelper
25
{
26
    /**
27
     * Service container
28
     *
29
     * @var \Symfony\Component\DependencyInjection\Container
30
     */
31
    protected $container;
32
33
    /**
34
     * Whether or not to consider exceptions thrown by the handler as formerrors.
35
     *
36
     * @var bool
37
     */
38
    protected $isMarkExceptionsAsFormErrors;
39
40
41
    /**
42
     * Construct the helper with the service container.
43
     *
44
     * @param \Symfony\Component\DependencyInjection\Container $container
45
     * @param bool $markExceptionsAsFormErrors
46
     */
47
    public function __construct(Container $container, $markExceptionsAsFormErrors = false)
48
    {
49
        $this->container = $container;
50
        $this->isMarkExceptionsAsFormErrors = $markExceptionsAsFormErrors;
51
    }
52
53
    /**
54
     * Get the top most (root) element of the form view
55
     *
56
     * @param FormView $formView
57
     * @return mixed
58
     */
59
    public static function getFormRoot($formView)
60
    {
61
        $parent = $formView;
62
        while (isset($parent->parent)) {
63
            $parent = $parent->parent;
64
        }
65
        return $parent;
66
    }
67
68
69
    /**
70
     * Generate an embedded url, adding the embedded parameters to the url
71
     *
72
     * @param string $route
73
     * @param array $params
74
     * @return string
75
     */
76
    public function url($route, $params)
77
    {
78
        // use array filter to remove keys with null values
79
        $params += array_filter($this->getEmbedParams());
80
81
        return $this->container->get('router')->generate($route, $params);
82
    }
83
84
85
    /**
86
     * Returns the parameters to add to an embedded url from the current request.
87
     *
88
     * @return array
89
     */
90
    public function getEmbedParams($checkSafety = true)
91
    {
92
        $params = array('return_url' => null, 'success_url' => null, 'do' => null);
93
94
        if ($returnUrl = $this->container->get('request')->get('return_url')) {
95
            $params['return_url'] = $checkSafety ? $this->container->get(UrlCheckerService::class)->getSafeUrl($returnUrl) : $returnUrl;
96
        }
97
        if ($successUrl = $this->container->get('request')->get('success_url')) {
98
            $params['success_url'] = $checkSafety ? $this->container->get(UrlCheckerService::class)->getSafeUrl($successUrl) : $successUrl;
99
        }
100
        // eg: do=change
101
        if ($doAction = $this->container->get('request')->get('do')) {
102
            $params['do'] = $doAction;
103
        }
104
105
        return $params;
106
    }
107
108
109
    /**
110
     * Handles a form and executes a callback to do definite handling.
111
     *
112
     * The embed helper utilizes a way to store the form state of a form after submitting, so when including a form
113
     * in an ESI, the form state is kept until the next display of the form. When using the handleForm with
114
     * XmlHttpRequests, the form state is not stored, since it is assumed the response will be used to display the
115
     * data directly.
116
     *
117
     * The return value is either a Response object that can be returned as the result of the action, or it is an
118
     * array which can be used in a template.
119
     *
120
     * @param \Symfony\Component\Form\Form $form
121
     * @param \Symfony\Component\HttpFoundation\Request $request
122
     * @param \callback $handlerCallback
123
     * @param string $formTargetRoute
124
     * @param array $formTargetParams
125
     * @param array $extraViewVars
126
     * @return array|Response
127
     * @throws \Exception
128
     */
129
    public function handleForm(
130
        Form $form,
131
        Request $request,
132
        $handlerCallback,
133
        $formTargetRoute,
134
        $formTargetParams = array(),
135
        $extraViewVars = array()
136
    ) {
137
        $formId = $this->getFormId($form);
138
        if ($request->hasPreviousSession()) {
139
            // cannot store errors iterator in session because somewhere there is a closure that can't be serialized
140
            // therefore convert the errors iterator to array, on get from session convert to iterator
141
            // see [1]
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
142
            $formState = $request->getSession()->get($formId);
143
            $formState['form_errors'] = is_array($formState['form_errors']) ? $formState['form_errors'] : array();
144
            $formState['form_errors'] = new FormErrorIterator($form, $formState['form_errors']);
145
        } else {
146
            $formState = null;
147
        }
148
        $formStatus = '';
149
150
151
        // This only binds the form, so the event listeners are triggered, but no actual submit-handling is done.
152
        // This is useful for AJAX requests which need to modify the form based on submitted data.
153
        if ($request->get('_submit_type') === 'bind') {
154
            $form->submit($request);
155
        } elseif ($request->getMethod() == 'POST') {
156
            $form->submit($request);
157
158
            $returnUrl     = $this->container->get(UrlCheckerService::class)->getSafeUrl($request->get('return_url'));
159
            $successUrl    = $this->container->get(UrlCheckerService::class)->getSafeUrl($request->get('success_url'));
160
161
            $handlerResult = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $handlerResult is dead and can be removed.
Loading history...
162
163
164
            // if it is valid, we can use the callback to handle the actual handling
165
            if ($form->isValid()) {
166
                try {
167
                    $handlerResult = call_user_func($handlerCallback, $request, $form, $this->container);
168
                } catch (\Exception $e) {
169
                    if (!$this->isMarkExceptionsAsFormErrors) {
170
                        throw $e;
171
                    } else {
172
                        $form->addError($this->convertExceptionToFormError($e));
173
                    }
174
                }
175
176
                if ($handlerResult) {
177
                    // any lingering errors may be removed now.
178
                    unset($formState['has_errors']);
179
                    unset($formState['data']);
180
                    unset($formState['form_errors']);
181
                    $formStatus = 'ok';
182
183
                    if ($handlerResult && $handlerResult instanceof Response) {
184
                        return $handlerResult;
185
                    } elseif (is_array($handlerResult)) {
186
                        $extraViewVars = $handlerResult + $extraViewVars;
187
                    }
188
                    if ($successUrl) {
189
                        $returnUrl = $successUrl;
190
191
                        if ($request->isXmlHttpRequest()) {
192
                            return new JsonResponse(array('success_url' => $successUrl));
0 ignored issues
show
Deprecated Code introduced by
The class Zicht\Bundle\FrameworkEx...undle\Http\JsonResponse has been deprecated: use Symfony's one instead ( Ignorable by Annotation )

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

192
                            return /** @scrutinizer ignore-deprecated */ new JsonResponse(array('success_url' => $successUrl));
Loading history...
193
                        }
194
                    } else {
195
                        // we set a convenience flash message if there was no success url, because
196
                        // we will probably return to the return url re-displaying the form.
197
                        $this->setFlashMessage($form, 'confirmed', $request->getSession());
198
                    }
199
                } else {
200
                    $formStatus = 'errors';
201
202
                    $formState['has_errors']  = true;
203
                    $formState['data']        = $request->request->get($form->getName());
204
                    $formState['form_errors'] = $form->getErrors();
205
                }
206
            } else {
207
                $formStatus = 'errors';
208
209
                $formState['has_errors']  = true;
210
                $formState['data']        = $request->request->get($form->getName());
211
                $formState['form_errors'] = $form->getErrors();
212
            }
213
            // redirect to the return url, if available
214
            if ($returnUrl && !$request->isXmlHttpRequest()) {
215
                $response = new RedirectResponse($returnUrl);
216
            }
217
        } elseif (!empty($formState['has_errors'])) {
218
            $formStatus = 'errors';
219
220
            // see if there were any errors left in the session from a previous post, so we show them
221
            if (!empty($formState['data']) && is_array($formState['data'])) {
222
                $form->submit($formState['data']);
223
                unset($formState['data']);
224
            }
225
            if (!empty($formState['form_errors'])) {
226
                foreach ($formState['form_errors'] as $error) {
227
                    $form->addError($error);
228
                }
229
            }
230
            // and we only show them once.
231
            unset($formState['has_errors']);
232
            unset($formState['form_errors']);
233
        }
234
235
        if ($formState && !$request->isXmlHttpRequest()) {
236
            if (!empty($formState['form_errors'])) {
237
238
                // 1. You cannot serialize or un-serialize PDO instances
239
                // 2. We do not want to store cause and origin in the session since these can become quite large
240
                foreach ($formState['form_errors'] as $key => $error) {
241
                    $refObject = new \ReflectionObject($error);
242
                    $refCauseProperty = $refObject->getProperty('cause');
243
                    $refCauseProperty->setAccessible(true);
244
                    $refCauseProperty->setValue($error, null);
245
                    $refOriginProperty = $refObject->getProperty('origin');
246
                    $refOriginProperty->setAccessible(true);
247
                    $refOriginProperty->setValue($error, null);
248
                }
249
            }
250
251
            // see [1] for explanation
252
            if (!isset($formState['form_errors'])) {
253
                $formState['form_errors'] = [];
254
            } elseif ($formState['form_errors'] instanceof \Traversable) {
255
                $formState['form_errors'] = iterator_to_array($formState['form_errors']);
256
            }
257
258
            $request->getSession()->set($formId, $formState);
259
        } elseif ($request->hasPreviousSession()) {
260
            $request->getSession()->remove($formId);
261
        }
262
263
        $viewVars = $extraViewVars;
264
265
        if (empty($response)) {
266
            if ($request->get('extension')) {
267
                $formTargetParams += array(
268
                    'extension' => $request->get('extension')
269
                );
270
            }
271
            $viewVars['form_status'] = $formStatus;
272
273
            $viewVars['form_url'] = $this->url($formTargetRoute, $formTargetParams);
274
            $viewVars['form']     = $form->createView();
275
276
            $prefix = '';
277
            if ($root = self::getFormRoot($viewVars['form'])) {
278
                $prefix = sprintf('form_messages.%s.', strtolower($root->vars['name']));
279
            }
280
281
            $viewVars['messages'] = [];
282
            if ($request->hasPreviousSession() && ($messages = $this->container->get('session')->getFlashBag()->get($formId))) {
283
                foreach ($messages as $value) {
284
                    $viewVars['messages'][] = $prefix . $value;
285
                }
286
            }
287
288
289
            return $viewVars;
290
        }
291
292
        return $response;
293
    }
294
295
296
    /**
297
     * Returns the ID the form's state is stored by in the session
298
     *
299
     * @param \Symfony\Component\Form\FormInterface $form
300
     * @return mixed
301
     */
302
    public function getFormId(FormInterface $form)
303
    {
304
        if (is_object($form->getData())) {
305
            $ret = preg_replace('/\W/', '_', get_class($form->getData()));
306
        } else {
307
            if ($form->getName()) {
308
                return (string)$form->getName();
309
            } else {
310
                return preg_replace('/\W/', '_', get_class($form));
311
            }
312
        }
313
        
314
        return $ret;
315
    }
316
317
318
    /**
319
     * @param bool $markExceptionsAsFormErrors
320
     */
321
    public function setMarkExceptionsAsFormErrors($markExceptionsAsFormErrors)
322
    {
323
        $this->isMarkExceptionsAsFormErrors = $markExceptionsAsFormErrors;
324
    }
325
326
327
    /**
328
     * Provides a way of customizing error messages based on type of exception, etc.
329
     *
330
     * @param \Exception $exception
331
     * @return FormError
332
     */
333
    protected function convertExceptionToFormError($exception)
334
    {
335
        return new FormError($exception->getMessage());
336
    }
337
338
    /**
339
     * A generic way to set flash messages.
340
     *
341
     * When using this make sure you set the following parameter in your parameters.yml to avoid stacking of messages
342
     * when they are not shown or rendered in templates
343
     *
344
     *     session.flashbag.class: Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag
345
     *
346
     * Messages will be pushed to a viewVars called 'messages' see $this->handleForm
347
     *
348
     * @param Form $form
349
     * @param string $message
350
     */
351
    public function setFlashMessage(Form $form, $message, SessionInterface $session = null)
352
    {
353
        if (null === $session) {
354
            trigger_error(
355
                "Please do not rely on the container's instance of the session, but fetch it from the Request",
356
                E_USER_DEPRECATED
357
            );
358
            $session = $this->container->get('session');
359
        }
360
        $session->getFlashBag()->set($this->getFormId($form), $message);
361
    }
362
}
363