Completed
Pull Request — 3.x (#5937)
by Peter
03:35
created

SetObjectFieldValueAction::__invoke()   D

Complexity

Conditions 15
Paths 29

Size

Total Lines 98

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 98
rs 4.7769
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(
61
                'Argument 3 is an instance of '.\get_class($validator).', expecting an instance of '
62
                .ValidatorInterface::class
63
            );
64
        }
65
66
        // NEXT_MAJOR: Move DataTransformerResolver check to method signature
67
        if (!$resolver instanceof DataTransformerResolverInterface) {
68
            @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...
69
                '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.',
70
                DataTransformerResolverInterface::class,
71
                __METHOD__,
72
                \TypeError::class
73
            ), E_USER_DEPRECATED);
74
            $resolver = new DataTransformerResolver();
75
        }
76
77
        $this->pool = $pool;
78
        $this->twig = $twig;
79
        $this->validator = $validator;
80
        $this->resolver = $resolver;
81
    }
82
83
    /**
84
     * @throws NotFoundHttpException
85
     */
86
    public function __invoke(Request $request): JsonResponse
87
    {
88
        $field = $request->get('field');
89
        $code = $request->get('code');
90
        $objectId = $request->get('objectId');
91
        $value = $originalValue = $request->get('value');
92
        $context = $request->get('context');
93
94
        $admin = $this->pool->getInstance($code);
95
        $admin->setRequest($request);
96
97
        // alter should be done by using a post method
98
        if (!$request->isXmlHttpRequest()) {
99
            return new JsonResponse('Expected an XmlHttpRequest request header', Response::HTTP_METHOD_NOT_ALLOWED);
100
        }
101
102
        if (Request::METHOD_POST !== $request->getMethod()) {
103
            return new JsonResponse(sprintf('Invalid request method given "%s", %s expected', $request->getMethod(), Request::METHOD_POST), Response::HTTP_METHOD_NOT_ALLOWED);
104
        }
105
106
        $rootObject = $object = $admin->getObject($objectId);
107
108
        if (!$object) {
109
            return new JsonResponse('Object does not exist', Response::HTTP_NOT_FOUND);
110
        }
111
112
        // check user permission
113
        if (false === $admin->hasAccess('edit', $object)) {
114
            return new JsonResponse('Invalid permissions', Response::HTTP_FORBIDDEN);
115
        }
116
117
        if ('list' === $context) {
118
            $fieldDescription = $admin->getListFieldDescription($field);
119
        } else {
120
            return new JsonResponse('Invalid context', Response::HTTP_BAD_REQUEST);
121
        }
122
123
        if (!$fieldDescription) {
124
            return new JsonResponse('The field does not exist', Response::HTTP_BAD_REQUEST);
125
        }
126
127
        if (!$fieldDescription->getOption('editable')) {
128
            return new JsonResponse('The field cannot be edited, editable option must be set to true', Response::HTTP_BAD_REQUEST);
129
        }
130
131
        $propertyPath = new PropertyPath($field);
132
133
        // If property path has more than 1 element, take the last object in order to validate it
134
        if ($propertyPath->getLength() > 1) {
135
            $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...
136
137
            $elements = $propertyPath->getElements();
138
            $field = end($elements);
139
            $propertyPath = new PropertyPath($field);
140
        }
141
142
        if ('' === $value) {
143
            $this->pool->getPropertyAccessor()->setValue($object, $propertyPath, null);
144
        } else {
145
            $dataTransformer = $this->resolver->resolve($fieldDescription, $admin->getModelManager());
146
147
            if ($dataTransformer instanceof DataTransformerInterface) {
148
                $value = $dataTransformer->reverseTransform($value);
149
            }
150
151
            if (!$value && TemplateRegistry::TYPE_CHOICE === $fieldDescription->getType()) {
152
                return new JsonResponse(sprintf(
153
                    'Edit failed, object with id: %s not found in association: %s.',
154
                    $originalValue,
155
                    $field
156
                ), Response::HTTP_NOT_FOUND);
157
            }
158
159
            $this->pool->getPropertyAccessor()->setValue($object, $propertyPath, $value);
160
        }
161
162
        $violations = $this->validator->validate($object);
163
164
        if (\count($violations)) {
165
            $messages = [];
166
167
            foreach ($violations as $violation) {
168
                $messages[] = $violation->getMessage();
169
            }
170
171
            return new JsonResponse(implode("\n", $messages), Response::HTTP_BAD_REQUEST);
172
        }
173
174
        $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...
175
176
        // render the widget
177
        // todo : fix this, the twig environment variable is not set inside the extension ...
178
        $extension = $this->twig->getExtension(SonataAdminExtension::class);
179
180
        $content = $extension->renderListElement($this->twig, $rootObject, $fieldDescription);
181
182
        return new JsonResponse($content, Response::HTTP_OK);
183
    }
184
}
185