Completed
Pull Request — master (#783)
by
unknown
03:58
created

FormErrorHandler   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 133
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 92.59%

Importance

Changes 0
Metric Value
wmc 24
lcom 1
cbo 4
dl 0
loc 133
ccs 75
cts 81
cp 0.9259
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getSubscribingMethods() 0 18 2
A __construct() 0 5 1
B serializeFormToXml() 0 31 6
A serializeFormToJson() 0 4 1
A serializeFormToYml() 0 4 1
A serializeFormErrorToXml() 0 8 2
A serializeFormErrorToJson() 0 4 1
A serializeFormErrorToYml() 0 4 1
C convertFormToArray() 0 31 7
A getErrorMessage() 0 8 2
1
<?php
2
3
/*
4
 * Copyright 2016 Johannes M. Schmitt <[email protected]>
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace JMS\Serializer\Handler;
20
21
use JMS\Serializer\GraphNavigator;
22
use JMS\Serializer\JsonSerializationVisitor;
23
use JMS\Serializer\VisitorInterface;
24
use JMS\Serializer\XmlSerializationVisitor;
25
use JMS\Serializer\YamlSerializationVisitor;
26
use Symfony\Component\Form\Form;
27
use Symfony\Component\Form\FormError;
28
use Symfony\Component\Translation\TranslatorInterface;
29
30
class FormErrorHandler implements SubscribingHandlerInterface
31
{
32
    private $translator;
33
34
    private $translationDomain;
35
36 356
    public static function getSubscribingMethods()
37
    {
38 356
        $methods = array();
39 356
        foreach (array('xml', 'json', 'yml') as $format) {
40 356
            $methods[] = array(
41 356
                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
42 356
                'type' => 'Symfony\Component\Form\Form',
43 356
                'format' => $format,
44
            );
45 356
            $methods[] = array(
46 356
                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
47 356
                'type' => 'Symfony\Component\Form\FormError',
48 356
                'format' => $format,
49
            );
50 356
        }
51
52 356
        return $methods;
53
    }
54
55 363
    public function __construct(TranslatorInterface $translator, $translationDomain = 'validators')
56
    {
57 363
        $this->translator = $translator;
58 363
        $this->translationDomain = $translationDomain;
59 363
    }
60
61 2
    public function serializeFormToXml(XmlSerializationVisitor $visitor, Form $form, array $type)
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
62
    {
63 2
        if (null === $visitor->document) {
64 2
            $visitor->document = $visitor->createDocument(null, null, false);
65 2
            $visitor->document->appendChild($formNode = $visitor->document->createElement('form'));
66 2
            $visitor->setCurrentNode($formNode);
67 2
        } else {
68 1
            $visitor->getCurrentNode()->appendChild(
69 1
                $formNode = $visitor->document->createElement('form')
70 1
            );
71
        }
72
73 2
        $formNode->setAttribute('name', $form->getName());
74
75 2
        $formNode->appendChild($errorsNode = $visitor->document->createElement('errors'));
76 2
        foreach ($form->getErrors() as $error) {
77 1
            $errorNode = $visitor->document->createElement('entry');
78 1
            $errorNode->appendChild($this->serializeFormErrorToXml($visitor, $error, array()));
0 ignored issues
show
Documentation introduced by
$error is of type object<Symfony\Component...ormErrorIterator>|false, but the function expects a object<Symfony\Component\Form\FormError>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
79 1
            $errorsNode->appendChild($errorNode);
80 2
        }
81
82 2
        foreach ($form->all() as $child) {
83 2
            if ($child instanceof Form) {
84 1
                if (null !== $node = $this->serializeFormToXml($visitor, $child, array())) {
85 1
                    $formNode->appendChild($node);
86 1
                }
87 1
            }
88 2
        }
89
90 2
        return $formNode;
91
    }
92
93 5
    public function serializeFormToJson(JsonSerializationVisitor $visitor, Form $form, array $type)
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
94
    {
95 5
        return $this->convertFormToArray($visitor, $form);
96
    }
97
98
    public function serializeFormToYml(YamlSerializationVisitor $visitor, Form $form, array $type)
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
99
    {
100
        return $this->convertFormToArray($visitor, $form);
101
    }
102
103 2
    public function serializeFormErrorToXml(XmlSerializationVisitor $visitor, FormError $formError, array $type)
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
104
    {
105 2
        if (null === $visitor->document) {
106
            $visitor->document = $visitor->createDocument(null, null, true);
107
        }
108
109 2
        return $visitor->document->createCDATASection($this->getErrorMessage($formError));
110
    }
111
112 1
    public function serializeFormErrorToJson(JsonSerializationVisitor $visitor, FormError $formError, array $type)
0 ignored issues
show
Unused Code introduced by
The parameter $visitor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $type is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
113
    {
114 1
        return $this->getErrorMessage($formError);
115
    }
116
117
    public function serializeFormErrorToYml(YamlSerializationVisitor $visitor, FormError $formError, array $type)
0 ignored issues
show
Unused Code introduced by
The parameter $visitor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $type is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
118
    {
119
        return $this->getErrorMessage($formError);
120
    }
121
122 10
    private function getErrorMessage(FormError $error)
123
    {
124 10
        if (null !== $error->getMessagePluralization()) {
125 2
            return $this->translator->transChoice($error->getMessageTemplate(), $error->getMessagePluralization(), $error->getMessageParameters(), $this->translationDomain);
126
        }
127
128 8
        return $this->translator->trans($error->getMessageTemplate(), $error->getMessageParameters(), $this->translationDomain);
129
    }
130
131 5
    private function convertFormToArray(VisitorInterface $visitor, Form $data)
132
    {
133 5
        $isRoot = null === $visitor->getRoot();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface JMS\Serializer\VisitorInterface as the method getRoot() does only exist in the following implementations of said interface: JMS\Serializer\GenericSerializationVisitor, JMS\Serializer\JsonSerializationVisitor.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
134
135 5
        $form = new \ArrayObject();
136 5
        $errors = array();
137 5
        foreach ($data->getErrors() as $error) {
138 3
            $errors[] = $this->getErrorMessage($error);
0 ignored issues
show
Documentation introduced by
$error is of type object<Symfony\Component...ormErrorIterator>|false, but the function expects a object<Symfony\Component\Form\FormError>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
139 5
        }
140
141 5
        if ($errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
142 3
            $form['errors'] = $errors;
143 3
        }
144
145 5
        $children = array();
146 5
        foreach ($data->all() as $child) {
147 3
            if ($child instanceof Form) {
148 2
                $children[$child->getName()] = $this->convertFormToArray($visitor, $child);
149 2
            }
150 5
        }
151
152 5
        if ($children) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $children of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
153 2
            $form['children'] = $children;
154 2
        }
155
156 5
        if ($isRoot) {
157 5
            $visitor->setRoot($form);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface JMS\Serializer\VisitorInterface as the method setRoot() does only exist in the following implementations of said interface: JMS\Serializer\GenericSerializationVisitor, JMS\Serializer\JsonSerializationVisitor.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
158 5
        }
159
160 5
        return $form;
161
    }
162
}
163