Completed
Pull Request — 3.x (#5937)
by Peter
05:42 queued 01:36
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\Pool;
17
use Sonata\AdminBundle\Form\DataTransformerResolver;
18
use Sonata\AdminBundle\Form\DataTransformerResolverInterface;
19
use Sonata\AdminBundle\Templating\TemplateRegistry;
20
use Sonata\AdminBundle\Twig\Extension\SonataAdminExtension;
21
use Symfony\Component\Form\DataTransformerInterface;
22
use Symfony\Component\HttpFoundation\JsonResponse;
23
use Symfony\Component\HttpFoundation\Request;
24
use Symfony\Component\HttpFoundation\Response;
25
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
26
use Symfony\Component\PropertyAccess\PropertyPath;
27
use Symfony\Component\Validator\Validator\ValidatorInterface;
28
use Twig\Environment;
29
30
final class SetObjectFieldValueAction
31
{
32
    /**
33
     * @var Pool
34
     */
35
    private $pool;
36
37
    /**
38
     * @var Environment
39
     */
40
    private $twig;
41
42
    /**
43
     * @var ValidatorInterface
44
     */
45
    private $validator;
46
47
    /**
48
     * @var DataTransformerResolver
49
     */
50
    private $resolver;
51
52
    /**
53
     * @param ValidatorInterface           $validator
54
     * @param DataTransformerResolver|null $resolver
55
     */
56
    public function __construct(Environment $twig, Pool $pool, $validator, $resolver = null)
57
    {
58
        // NEXT_MAJOR: Move ValidatorInterface check to method signature
59
        if (!($validator instanceof ValidatorInterface)) {
60
            throw new \InvalidArgumentException(sprintf(
61
                'Argument 3 is an instance of %s, expecting an instance of %s',
62
                \get_class($validator),
63
                ValidatorInterface::class
64
            ));
65
        }
66
67
        // NEXT_MAJOR: Move DataTransformerResolver check to method signature
68
        if (!$resolver instanceof DataTransformerResolverInterface) {
69
            @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...
70
                '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.',
71
                DataTransformerResolverInterface::class,
72
                __METHOD__,
73
                \TypeError::class
74
            ), E_USER_DEPRECATED);
75
            $resolver = new DataTransformerResolver();
76
        }
77
78
        $this->pool = $pool;
79
        $this->twig = $twig;
80
        $this->validator = $validator;
81
        $this->resolver = $resolver;
82
    }
83
84
    /**
85
     * @throws NotFoundHttpException
86
     */
87
    public function __invoke(Request $request): JsonResponse
88
    {
89
        $field = $request->get('field');
90
        $code = $request->get('code');
91
        $objectId = $request->get('objectId');
92
        $value = $originalValue = $request->get('value');
93
        $context = $request->get('context');
94
95
        $admin = $this->pool->getInstance($code);
96
        $admin->setRequest($request);
97
98
        // alter should be done by using a post method
99
        if (!$request->isXmlHttpRequest()) {
100
            return new JsonResponse('Expected an XmlHttpRequest request header', Response::HTTP_METHOD_NOT_ALLOWED);
101
        }
102
103
        if (Request::METHOD_POST !== $request->getMethod()) {
104
            return new JsonResponse(sprintf(
105
                'Invalid request method given "%s", %s expected',
106
                $request->getMethod(),
107
                Request::METHOD_POST
108
            ), Response::HTTP_METHOD_NOT_ALLOWED);
109
        }
110
111
        $rootObject = $object = $admin->getObject($objectId);
112
113
        if (!$object) {
114
            return new JsonResponse('Object does not exist', Response::HTTP_NOT_FOUND);
115
        }
116
117
        // check user permission
118
        if (false === $admin->hasAccess('edit', $object)) {
119
            return new JsonResponse('Invalid permissions', Response::HTTP_FORBIDDEN);
120
        }
121
122
        if ('list' === $context) {
123
            $fieldDescription = $admin->getListFieldDescription($field);
124
        } else {
125
            return new JsonResponse('Invalid context', Response::HTTP_BAD_REQUEST);
126
        }
127
128
        if (!$fieldDescription) {
129
            return new JsonResponse('The field does not exist', Response::HTTP_BAD_REQUEST);
130
        }
131
132
        if (!$fieldDescription->getOption('editable')) {
133
            return new JsonResponse('The field cannot be edited, editable option must be set to true', Response::HTTP_BAD_REQUEST);
134
        }
135
136
        $propertyPath = new PropertyPath($field);
137
138
        // If property path has more than 1 element, take the last object in order to validate it
139
        if ($propertyPath->getLength() > 1) {
140
            $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...
141
142
            $elements = $propertyPath->getElements();
143
            $field = end($elements);
144
            $propertyPath = new PropertyPath($field);
145
        }
146
147
        if ('' === $value) {
148
            $this->pool->getPropertyAccessor()->setValue($object, $propertyPath, null);
149
        } else {
150
            $dataTransformer = $this->resolver->resolve($fieldDescription, $admin->getModelManager());
151
152
            if ($dataTransformer instanceof DataTransformerInterface) {
153
                $value = $dataTransformer->reverseTransform($value);
154
            }
155
156
            if (!$value && TemplateRegistry::TYPE_CHOICE === $fieldDescription->getType()) {
157
                return new JsonResponse(sprintf(
158
                    'Edit failed, object with id: %s not found in association: %s.',
159
                    $originalValue,
160
                    $field
161
                ), Response::HTTP_NOT_FOUND);
162
            }
163
164
            $this->pool->getPropertyAccessor()->setValue($object, $propertyPath, $value);
165
        }
166
167
        $violations = $this->validator->validate($object);
168
169
        if (\count($violations)) {
170
            $messages = [];
171
172
            foreach ($violations as $violation) {
173
                $messages[] = $violation->getMessage();
174
            }
175
176
            return new JsonResponse(implode("\n", $messages), Response::HTTP_BAD_REQUEST);
177
        }
178
179
        $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...
180
181
        // render the widget
182
        // todo : fix this, the twig environment variable is not set inside the extension ...
183
        $extension = $this->twig->getExtension(SonataAdminExtension::class);
184
        \assert($extension instanceof SonataAdminExtension);
185
186
        $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 111 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...
187
188
        return new JsonResponse($content, Response::HTTP_OK);
189
    }
190
}
191