Completed
Push — master ( 57b7f2...4f95ff )
by Grégoire
28:13 queued 01:46
created

AdminHelper::getEntityClassName()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
cc 3
nc 4
nop 2
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\Admin;
15
16
use Doctrine\Common\Collections\Collection;
17
use Doctrine\ODM\MongoDB\PersistentCollection;
18
use Doctrine\ORM\PersistentCollection as DoctrinePersistentCollection;
19
use Sonata\AdminBundle\Exception\NoValueException;
20
use Sonata\AdminBundle\Manipulator\ObjectManipulator;
21
use Sonata\AdminBundle\Util\FormBuilderIterator;
22
use Sonata\AdminBundle\Util\FormViewIterator;
23
use Symfony\Component\Form\FormBuilderInterface;
24
use Symfony\Component\Form\FormView;
25
26
/**
27
 * @final since sonata-project/admin-bundle 3.52
28
 *
29
 * @author Thomas Rabaix <[email protected]>
30
 */
31
class AdminHelper
32
{
33
    /**
34
     * @var string
35
     */
36
    private const FORM_FIELD_DELETE = '_delete';
37
38
    /**
39
     * @var Pool
40
     */
41
    protected $pool;
42
43
    public function __construct(Pool $pool)
44
    {
45
        $this->pool = $pool;
46
    }
47
48
    /**
49
     * @throws \RuntimeException
50
     */
51
    public function getChildFormBuilder(FormBuilderInterface $formBuilder, string $elementId): ?FormBuilderInterface
52
    {
53
        foreach (new FormBuilderIterator($formBuilder) as $name => $formBuilder) {
54
            if ($name === $elementId) {
55
                return $formBuilder;
56
            }
57
        }
58
59
        return null;
60
    }
61
62
    public function getChildFormView(FormView $formView, string $elementId): ?FormView
63
    {
64
        foreach (new \RecursiveIteratorIterator(new FormViewIterator($formView), \RecursiveIteratorIterator::SELF_FIRST) as $name => $formView) {
65
            if ($name === $elementId) {
66
                return $formView;
67
            }
68
        }
69
70
        return null;
71
    }
72
73
    /**
74
     * Note:
75
     *   This code is ugly, but there is no better way of doing it.
76
     *
77
     * @throws \RuntimeException
78
     * @throws \Exception
79
     */
80
    public function appendFormFieldElement(AdminInterface $admin, object $subject, string $elementId): array
81
    {
82
        // child rows marked as toDelete
83
        $toDelete = [];
84
85
        $formBuilder = $admin->getFormBuilder();
86
87
        // get the field element
88
        $childFormBuilder = $this->getChildFormBuilder($formBuilder, $elementId);
89
90
        if ($childFormBuilder) {
91
            $formData = $admin->getRequest()->get($formBuilder->getName(), []);
92
            if (\array_key_exists($childFormBuilder->getName(), $formData)) {
93
                $formData = $admin->getRequest()->get($formBuilder->getName(), []);
94
                $i = 0;
95
                foreach ($formData[$childFormBuilder->getName()] as $name => &$field) {
96
                    $toDelete[$i] = false;
97
                    if (\array_key_exists(self::FORM_FIELD_DELETE, $field)) {
98
                        $toDelete[$i] = true;
99
                        unset($field[self::FORM_FIELD_DELETE]);
100
                    }
101
                    ++$i;
102
                }
103
            }
104
            $admin->getRequest()->request->set($formBuilder->getName(), $formData);
105
        }
106
107
        $form = $formBuilder->getForm();
108
        $form->setData($subject);
109
        $form->handleRequest($admin->getRequest());
110
111
        //Child form not found (probably nested one)
112
        //if childFormBuilder was not found resulted in fatal error getName() method call on non object
113
        if (!$childFormBuilder) {
114
            $propertyAccessor = $this->pool->getPropertyAccessor();
115
116
            $path = $this->getElementAccessPath($elementId, $subject);
117
118
            $collection = $propertyAccessor->getValue($subject, $path);
119
120
            if ($collection instanceof DoctrinePersistentCollection || $collection instanceof PersistentCollection) {
0 ignored issues
show
Bug introduced by
The class Doctrine\ODM\MongoDB\PersistentCollection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
121
                //since doctrine 2.4
122
                $modelClassName = $collection->getTypeClass()->getName();
123
            } elseif ($collection instanceof Collection) {
124
                $modelClassName = $this->getModelClassName($admin, explode('.', preg_replace('#\[\d*?\]#', '', $path)));
125
            } else {
126
                throw new \Exception('unknown collection class');
127
            }
128
129
            $collection->add(new $modelClassName());
130
            $propertyAccessor->setValue($subject, $path, $collection);
131
132
            $fieldDescription = null;
133
        } else {
134
            // retrieve the FieldDescription
135
            $fieldDescription = $admin->getFormFieldDescription($childFormBuilder->getName());
136
137
            try {
138
                $value = $fieldDescription->getValue($form->getData());
139
            } catch (NoValueException $e) {
140
                $value = null;
141
            }
142
143
            // retrieve the posted data
144
            $data = $admin->getRequest()->get($formBuilder->getName());
145
146
            if (!isset($data[$childFormBuilder->getName()])) {
147
                $data[$childFormBuilder->getName()] = [];
148
            }
149
150
            $objectCount = null === $value ? 0 : \count($value);
151
            $postCount = \count($data[$childFormBuilder->getName()]);
152
153
            $associationAdmin = $fieldDescription->getAssociationAdmin();
154
155
            // add new elements to the subject
156
            while ($objectCount < $postCount) {
157
                // append a new instance into the object
158
                ObjectManipulator::addInstance($form->getData(), $associationAdmin->getNewInstance(), $fieldDescription);
159
                ++$objectCount;
160
            }
161
162
            $newInstance = ObjectManipulator::addInstance($form->getData(), $associationAdmin->getNewInstance(), $fieldDescription);
163
164
            $associationAdmin->setSubject($newInstance);
165
        }
166
167
        $finalForm = $admin->getFormBuilder()->getForm();
168
        $finalForm->setData($subject);
169
170
        // bind the data
171
        $finalForm->setData($form->getData());
172
173
        // back up delete field
174
        if (\count($toDelete) > 0) {
175
            $i = 0;
176
            foreach ($finalForm->get($childFormBuilder->getName()) as $childField) {
177
                if ($childField->has(self::FORM_FIELD_DELETE)) {
178
                    $childField->get(self::FORM_FIELD_DELETE)->setData($toDelete[$i] ?? false);
179
                }
180
                ++$i;
181
            }
182
        }
183
184
        return [$fieldDescription, $finalForm];
185
    }
186
187
    /**
188
     * Get access path to element which works with PropertyAccessor.
189
     *
190
     * @param string $elementId expects string in format used in form id field.
191
     *                          (uniqueIdentifier_model_sub_model or uniqueIdentifier_model_1_sub_model etc.)
192
     * @param mixed  $model
193
     *
194
     * @throws \Exception
195
     */
196
    public function getElementAccessPath(string $elementId, $model): string
197
    {
198
        $propertyAccessor = $this->pool->getPropertyAccessor();
199
200
        $idWithoutIdentifier = preg_replace('/^[^_]*_/', '', $elementId);
201
        $initialPath = preg_replace('#(_(\d+)_)#', '[$2]_', $idWithoutIdentifier);
202
203
        $parts = explode('_', $initialPath);
204
        $totalPath = '';
205
        $currentPath = '';
206
207
        foreach ($parts as $part) {
208
            $currentPath .= empty($currentPath) ? $part : '_'.$part;
209
            $separator = empty($totalPath) ? '' : '.';
210
211
            if ($propertyAccessor->isReadable($model, $totalPath.$separator.$currentPath)) {
212
                $totalPath .= $separator.$currentPath;
213
                $currentPath = '';
214
            }
215
        }
216
217
        if (!empty($currentPath)) {
218
            throw new \Exception(sprintf(
219
                'Could not get element id from %s Failing part: %s',
220
                $elementId,
221
                $currentPath
222
            ));
223
        }
224
225
        return $totalPath;
226
    }
227
228
    /**
229
     * Recursively find the class name of the admin responsible for the element at the end of an association chain.
230
     */
231
    protected function getModelClassName(AdminInterface $admin, array $elements): string
232
    {
233
        $element = array_shift($elements);
234
        $associationAdmin = $admin->getFormFieldDescription($element)->getAssociationAdmin();
235
        if (0 === \count($elements)) {
236
            return $associationAdmin->getClass();
237
        }
238
239
        return $this->getModelClassName($associationAdmin, $elements);
240
    }
241
}
242