Completed
Pull Request — 3.x (#5937)
by Peter
04:57 queued 25s
created

SetObjectFieldValueAction::__invoke()   D

Complexity

Conditions 15
Paths 29

Size

Total Lines 103

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 103
rs 4.7333
c 0
b 0
f 0
cc 15
nc 29
nop 1

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
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Action;
15
16
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
17
use Sonata\AdminBundle\Admin\Pool;
18
use Sonata\AdminBundle\Form\DataTransformerResolver;
19
use Sonata\AdminBundle\Form\DataTransformerResolverInterface;
20
use Sonata\AdminBundle\Templating\TemplateRegistry;
21
use Sonata\AdminBundle\Twig\Extension\SonataAdminExtension;
22
use Symfony\Component\Form\DataTransformerInterface;
23
use Symfony\Component\HttpFoundation\JsonResponse;
24
use Symfony\Component\HttpFoundation\Request;
25
use Symfony\Component\HttpFoundation\Response;
26
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
27
use Symfony\Component\PropertyAccess\PropertyPath;
28
use Symfony\Component\Validator\Validator\ValidatorInterface;
29
use Twig\Environment;
30
31
final class SetObjectFieldValueAction
32
{
33
    /**
34
     * @var Pool
35
     */
36
    private $pool;
37
38
    /**
39
     * @var Environment
40
     */
41
    private $twig;
42
43
    /**
44
     * @var ValidatorInterface
45
     */
46
    private $validator;
47
48
    /**
49
     * @var DataTransformerResolver
50
     */
51
    private $resolver;
52
53
    /**
54
     * @param ValidatorInterface           $validator
55
     * @param DataTransformerResolver|null $resolver
56
     */
57
    public function __construct(Environment $twig, Pool $pool, $validator, $resolver = null)
58
    {
59
        // NEXT_MAJOR: Move ValidatorInterface check to method signature
60
        if (!($validator instanceof ValidatorInterface)) {
61
            throw new \InvalidArgumentException(sprintf(
62
                'Argument 3 is an instance of %s, expecting an instance of %s',
63
                \get_class($validator),
64
                ValidatorInterface::class
65
            ));
66
        }
67
68
        // NEXT_MAJOR: Move DataTransformerResolver check to method signature
69
        if (!$resolver instanceof DataTransformerResolverInterface) {
70
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
71
                'Passing other type than %s in argument 4 to %s() is deprecated since sonata-project/admin-bundle 3.x and will throw %s exception in 4.0.',
72
                DataTransformerResolverInterface::class,
73
                __METHOD__,
74
                \TypeError::class
75
            ), E_USER_DEPRECATED);
76
            $resolver = new DataTransformerResolver();
77
        }
78
79
        $this->pool = $pool;
80
        $this->twig = $twig;
81
        $this->validator = $validator;
82
        $this->resolver = $resolver;
83
    }
84
85
    /**
86
     * @throws NotFoundHttpException
87
     */
88
    public function __invoke(Request $request): JsonResponse
89
    {
90
        $field = $request->get('field');
91
        $code = $request->get('code');
92
        $objectId = $request->get('objectId');
93
        $value = $originalValue = $request->get('value');
94
        $context = $request->get('context');
95
96
        $admin = $this->pool->getInstance($code);
97
        $admin->setRequest($request);
98
99
        // alter should be done by using a post method
100
        if (!$request->isXmlHttpRequest()) {
101
            return new JsonResponse('Expected an XmlHttpRequest request header', Response::HTTP_METHOD_NOT_ALLOWED);
102
        }
103
104
        if (Request::METHOD_POST !== $request->getMethod()) {
105
            return new JsonResponse(sprintf(
106
                'Invalid request method given "%s", %s expected',
107
                $request->getMethod(),
108
                Request::METHOD_POST
109
            ), Response::HTTP_METHOD_NOT_ALLOWED);
110
        }
111
112
        $rootObject = $object = $admin->getObject($objectId);
113
114
        if (!$object) {
115
            return new JsonResponse('Object does not exist', Response::HTTP_NOT_FOUND);
116
        }
117
118
        // check user permission
119
        if (false === $admin->hasAccess('edit', $object)) {
120
            return new JsonResponse('Invalid permissions', Response::HTTP_FORBIDDEN);
121
        }
122
123
        if ('list' === $context) {
124
            $fieldDescription = $admin->getListFieldDescription($field);
125
        } else {
126
            return new JsonResponse('Invalid context', Response::HTTP_BAD_REQUEST);
127
        }
128
129
        if (!$fieldDescription) {
130
            return new JsonResponse('The field does not exist', Response::HTTP_BAD_REQUEST);
131
        }
132
133
        if (!$fieldDescription->getOption('editable')) {
134
            return new JsonResponse('The field cannot be edited, editable option must be set to true', Response::HTTP_BAD_REQUEST);
135
        }
136
137
        $propertyPath = new PropertyPath($field);
138
139
        // If property path has more than 1 element, take the last object in order to validate it
140
        if ($propertyPath->getLength() > 1) {
141
            $object = $this->pool->getPropertyAccessor()->getValue($object, $propertyPath->getParent());
0 ignored issues
show
Bug introduced by
It seems like $propertyPath->getParent() can be null; however, getValue() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
142
143
            $elements = $propertyPath->getElements();
144
            $field = end($elements);
145
            $propertyPath = new PropertyPath($field);
146
        }
147
148
        if ('' === $value) {
149
            $this->pool->getPropertyAccessor()->setValue($object, $propertyPath, null);
150
        } else {
151
            $dataTransformer = $this->resolver->resolve($fieldDescription, $admin->getModelManager());
152
153
            if ($dataTransformer instanceof DataTransformerInterface) {
154
                $value = $dataTransformer->reverseTransform($value);
155
            }
156
157
            if (!$value && TemplateRegistry::TYPE_CHOICE === $fieldDescription->getType()) {
158
                return new JsonResponse(sprintf(
159
                    'Edit failed, object with id: %s not found in association: %s.',
160
                    $originalValue,
161
                    $field
162
                ), Response::HTTP_NOT_FOUND);
163
            }
164
165
            $this->pool->getPropertyAccessor()->setValue($object, $propertyPath, $value);
166
        }
167
168
        $violations = $this->validator->validate($object);
169
170
        if (\count($violations)) {
171
            $messages = [];
172
173
            foreach ($violations as $violation) {
174
                $messages[] = $violation->getMessage();
175
            }
176
177
            return new JsonResponse(implode("\n", $messages), Response::HTTP_BAD_REQUEST);
178
        }
179
180
        $admin->update($object);
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type array; however, Sonata\AdminBundle\Admin...iderInterface::update() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
181
182
        // render the widget
183
        // todo : fix this, the twig environment variable is not set inside the extension ...
184
        $extension = $this->twig->getExtension(SonataAdminExtension::class);
185
        \assert($extension instanceof SonataAdminExtension);
186
187
        $content = $extension->renderListElement($this->twig, $rootObject, $fieldDescription);
0 ignored issues
show
Bug introduced by
It seems like $rootObject defined by $object = $admin->getObject($objectId) on line 112 can also be of type null; however, Sonata\AdminBundle\Twig\...on::renderListElement() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
188
189
        return new JsonResponse($content, Response::HTTP_OK);
190
    }
191
}
192