Completed
Push — master ( 3a65e8...9f4bbe )
by Grégoire
16s
created

AdminHelper::pathExists()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 4
nop 3
1
<?php
2
3
/*
4
 * This file is part of the Sonata Project package.
5
 *
6
 * (c) Thomas Rabaix <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sonata\AdminBundle\Admin;
13
14
use Doctrine\Common\Inflector\Inflector;
15
use Doctrine\Common\Util\ClassUtils;
16
use Sonata\AdminBundle\Exception\NoValueException;
17
use Sonata\AdminBundle\Util\FormBuilderIterator;
18
use Sonata\AdminBundle\Util\FormViewIterator;
19
use Symfony\Component\Form\FormBuilderInterface;
20
use Symfony\Component\Form\FormView;
21
22
/**
23
 * @author Thomas Rabaix <[email protected]>
24
 */
25
class AdminHelper
26
{
27
    /**
28
     * @var Pool
29
     */
30
    protected $pool;
31
32
    /**
33
     * @param Pool $pool
34
     */
35
    public function __construct(Pool $pool)
36
    {
37
        $this->pool = $pool;
38
    }
39
40
    /**
41
     * @throws \RuntimeException
42
     *
43
     * @param FormBuilderInterface $formBuilder
44
     * @param string               $elementId
45
     *
46
     * @return FormBuilderInterface|null
47
     */
48
    public function getChildFormBuilder(FormBuilderInterface $formBuilder, $elementId)
49
    {
50
        foreach (new FormBuilderIterator($formBuilder) as $name => $formBuilder) {
51
            if ($name == $elementId) {
52
                return $formBuilder;
53
            }
54
        }
55
56
        return;
57
    }
58
59
    /**
60
     * @param FormView $formView
61
     * @param string   $elementId
62
     *
63
     * @return null|FormView
64
     */
65
    public function getChildFormView(FormView $formView, $elementId)
66
    {
67
        foreach (new \RecursiveIteratorIterator(new FormViewIterator($formView), \RecursiveIteratorIterator::SELF_FIRST) as $name => $formView) {
68
            if ($name === $elementId) {
69
                return $formView;
70
            }
71
        }
72
73
        return;
74
    }
75
76
    /**
77
     * NEXT_MAJOR: remove this method.
78
     *
79
     * @deprecated
80
     *
81
     * @param string $code
82
     *
83
     * @return AdminInterface
84
     */
85
    public function getAdmin($code)
86
    {
87
        return $this->pool->getInstance($code);
88
    }
89
90
    /**
91
     * Note:
92
     *   This code is ugly, but there is no better way of doing it.
93
     *   For now the append form element action used to add a new row works
94
     *   only for direct FieldDescription (not nested one).
95
     *
96
     * @throws \RuntimeException
97
     *
98
     * @param AdminInterface $admin
99
     * @param object         $subject
100
     * @param string         $elementId
101
     *
102
     * @return array
103
     *
104
     * @throws \Exception
105
     */
106
    public function appendFormFieldElement(AdminInterface $admin, $subject, $elementId)
107
    {
108
        // retrieve the subject
109
        $formBuilder = $admin->getFormBuilder();
110
111
        $form = $formBuilder->getForm();
112
        $form->setData($subject);
113
        $form->handleRequest($admin->getRequest());
114
115
        // get the field element
116
        $childFormBuilder = $this->getChildFormBuilder($formBuilder, $elementId);
117
118
        //Child form not found (probably nested one)
119
        //if childFormBuilder was not found resulted in fatal error getName() method call on non object
120
        if (!$childFormBuilder) {
121
            $propertyAccessor = $this->pool->getPropertyAccessor();
122
            $entity = $admin->getSubject();
123
124
            $path = $this->getElementAccessPath($elementId, $entity);
125
126
            $collection = $propertyAccessor->getValue($entity, $path);
127
128
            if ($collection instanceof \Doctrine\ORM\PersistentCollection || $collection instanceof \Doctrine\ODM\MongoDB\PersistentCollection) {
0 ignored issues
show
Bug introduced by
The class Doctrine\ORM\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...
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...
129
                //since doctrine 2.4
130
                $entityClassName = $collection->getTypeClass()->getName();
131
            } elseif ($collection instanceof \Doctrine\Common\Collections\Collection) {
132
                $entityClassName = $this->getEntityClassName($admin, explode('.', preg_replace('#\[\d*?\]#', '', $path)));
133
            } else {
134
                throw new \Exception('unknown collection class');
135
            }
136
137
            $collection->add(new $entityClassName());
138
            $propertyAccessor->setValue($entity, $path, $collection);
139
140
            $fieldDescription = null;
141
        } else {
142
            // retrieve the FieldDescription
143
            $fieldDescription = $admin->getFormFieldDescription($childFormBuilder->getName());
144
145
            try {
146
                $value = $fieldDescription->getValue($form->getData());
147
            } catch (NoValueException $e) {
148
                $value = null;
149
            }
150
151
            // retrieve the posted data
152
            $data = $admin->getRequest()->get($formBuilder->getName());
153
154
            if (!isset($data[$childFormBuilder->getName()])) {
155
                $data[$childFormBuilder->getName()] = [];
156
            }
157
158
            $objectCount = count($value);
159
            $postCount = count($data[$childFormBuilder->getName()]);
160
161
            $fields = array_keys($fieldDescription->getAssociationAdmin()->getFormFieldDescriptions());
162
163
            // for now, not sure how to do that
164
            $value = [];
165
            foreach ($fields as $name) {
166
                $value[$name] = '';
167
            }
168
169
            // add new elements to the subject
170
            while ($objectCount < $postCount) {
171
                // append a new instance into the object
172
                $this->addNewInstance($form->getData(), $fieldDescription);
173
                ++$objectCount;
174
            }
175
176
            $this->addNewInstance($form->getData(), $fieldDescription);
177
        }
178
179
        $finalForm = $admin->getFormBuilder()->getForm();
180
        $finalForm->setData($subject);
181
182
        // bind the data
183
        $finalForm->setData($form->getData());
184
185
        return [$fieldDescription, $finalForm];
186
    }
187
188
    /**
189
     * Add a new instance to the related FieldDescriptionInterface value.
190
     *
191
     * @param object                    $object
192
     * @param FieldDescriptionInterface $fieldDescription
193
     *
194
     * @throws \RuntimeException
195
     */
196
    public function addNewInstance($object, FieldDescriptionInterface $fieldDescription)
197
    {
198
        $instance = $fieldDescription->getAssociationAdmin()->getNewInstance();
199
        $mapping = $fieldDescription->getAssociationMapping();
200
201
        $method = sprintf('add%s', Inflector::classify($mapping['fieldName']));
202
203
        if (!method_exists($object, $method)) {
204
            $method = rtrim($method, 's');
205
206
            if (!method_exists($object, $method)) {
207
                $method = sprintf('add%s', Inflector::classify(Inflector::singularize($mapping['fieldName'])));
208
209
                if (!method_exists($object, $method)) {
210
                    throw new \RuntimeException(
211
                        sprintf('Please add a method %s in the %s class!', $method, ClassUtils::getClass($object))
212
                    );
213
                }
214
            }
215
        }
216
217
        $object->$method($instance);
218
    }
219
220
    /**
221
     * Get access path to element which works with PropertyAccessor.
222
     *
223
     * @param string $elementId expects string in format used in form id field.
224
     *                          (uniqueIdentifier_model_sub_model or uniqueIdentifier_model_1_sub_model etc.)
225
     * @param mixed  $entity
226
     *
227
     * @return string
228
     *
229
     * @throws \Exception
230
     */
231
    public function getElementAccessPath($elementId, $entity)
232
    {
233
        $propertyAccessor = $this->pool->getPropertyAccessor();
234
235
        $idWithoutIdentifier = preg_replace('/^[^_]*_/', '', $elementId);
236
        $initialPath = preg_replace('#(_(\d+)_)#', '[$2]_', $idWithoutIdentifier);
237
238
        $parts = explode('_', $initialPath);
239
        $totalPath = '';
240
        $currentPath = '';
241
242
        foreach ($parts as $part) {
243
            $currentPath .= empty($currentPath) ? $part : '_'.$part;
244
            $separator = empty($totalPath) ? '' : '.';
245
246
            if ($propertyAccessor->isReadable($entity, $totalPath.$separator.$currentPath)) {
247
                $totalPath .= $separator.$currentPath;
248
                $currentPath = '';
249
            }
250
        }
251
252
        if (!empty($currentPath)) {
253
            throw new \Exception(
254
                sprintf('Could not get element id from %s Failing part: %s', $elementId, $currentPath)
255
            );
256
        }
257
258
        return $totalPath;
259
    }
260
261
    /**
262
     * Recursively find the class name of the admin responsible for the element at the end of an association chain.
263
     *
264
     * @param AdminInterface $admin
265
     * @param array          $elements
266
     *
267
     * @return string
268
     */
269
    protected function getEntityClassName(AdminInterface $admin, $elements)
270
    {
271
        $element = array_shift($elements);
272
        $associationAdmin = $admin->getFormFieldDescription($element)->getAssociationAdmin();
273
        if (0 == count($elements)) {
274
            return $associationAdmin->getClass();
275
        }
276
277
        return $this->getEntityClassName($associationAdmin, $elements);
278
    }
279
}
280