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) { |
|
|
|
|
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
|
|
|
|
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 thecomposer.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
orrequire-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 you have not tested against this specific condition, such errors might go unnoticed.