These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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\Form\ChoiceList; |
||
15 | |||
16 | use Doctrine\Common\Util\ClassUtils; |
||
17 | use Doctrine\ORM\QueryBuilder; |
||
18 | use ReflectionProperty; |
||
19 | use Sonata\AdminBundle\Model\ModelManagerInterface; |
||
20 | use Symfony\Component\Form\Exception\InvalidArgumentException; |
||
21 | use Symfony\Component\Form\Exception\RuntimeException; |
||
22 | use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList; |
||
23 | use Symfony\Component\PropertyAccess\PropertyAccess; |
||
24 | use Symfony\Component\PropertyAccess\PropertyAccessorInterface; |
||
25 | use Symfony\Component\PropertyAccess\PropertyPath; |
||
26 | |||
27 | @trigger_error( |
||
0 ignored issues
–
show
|
|||
28 | 'The '.__NAMESPACE__.'\ModelChoiceList class is deprecated since 3.24 and will be removed in 4.0.' |
||
29 | .' Use '.__NAMESPACE__.'\ModelChoiceLoader instead.', |
||
30 | E_USER_DEPRECATED |
||
31 | ); |
||
32 | |||
33 | /** |
||
34 | * NEXT_MAJOR: Remove this class. |
||
35 | * |
||
36 | * @deprecated Since 3.24, to be removed in 4.0. Use Sonata\AdminBundle\ModelChoiceLoader instead |
||
37 | * |
||
38 | * @author Thomas Rabaix <[email protected]> |
||
39 | */ |
||
40 | class ModelChoiceList extends SimpleChoiceList |
||
41 | { |
||
42 | /** |
||
43 | * @var ModelManagerInterface |
||
44 | */ |
||
45 | private $modelManager; |
||
46 | |||
47 | /** |
||
48 | * @var string |
||
49 | */ |
||
50 | private $class; |
||
51 | |||
52 | /** |
||
53 | * The entities from which the user can choose. |
||
54 | * |
||
55 | * This array is either indexed by ID (if the ID is a single field) |
||
56 | * or by key in the choices array (if the ID consists of multiple fields) |
||
57 | * |
||
58 | * This property is initialized by initializeChoices(). It should only |
||
59 | * be accessed through getEntity() and getEntities(). |
||
60 | * |
||
61 | * @var mixed |
||
62 | */ |
||
63 | private $entities = []; |
||
64 | |||
65 | /** |
||
66 | * Contains the query builder that builds the query for fetching the |
||
67 | * entities. |
||
68 | * |
||
69 | * This property should only be accessed through queryBuilder. |
||
70 | * |
||
71 | * @var QueryBuilder |
||
72 | */ |
||
73 | private $query; |
||
74 | |||
75 | /** |
||
76 | * The fields of which the identifier of the underlying class consists. |
||
77 | * |
||
78 | * This property should only be accessed through identifier. |
||
79 | * |
||
80 | * @var array |
||
81 | */ |
||
82 | private $identifier = []; |
||
83 | |||
84 | /** |
||
85 | * A cache for \ReflectionProperty instances for the underlying class. |
||
86 | * |
||
87 | * This property should only be accessed through getReflProperty(). |
||
88 | * |
||
89 | * @var array |
||
90 | */ |
||
91 | private $reflProperties = []; |
||
92 | |||
93 | /** |
||
94 | * @var PropertyPath |
||
95 | */ |
||
96 | private $propertyPath; |
||
97 | |||
98 | /** |
||
99 | * @var PropertyAccessorInterface |
||
100 | */ |
||
101 | private $propertyAccessor; |
||
102 | |||
103 | /** |
||
104 | * @param string $class |
||
105 | * @param string|null $property |
||
106 | * @param QueryBuilder|null $query |
||
107 | * @param array $choices |
||
108 | */ |
||
109 | public function __construct( |
||
110 | ModelManagerInterface $modelManager, |
||
111 | $class, |
||
112 | $property = null, |
||
113 | $query = null, |
||
114 | $choices = [], |
||
115 | PropertyAccessorInterface $propertyAccessor = null |
||
116 | ) { |
||
117 | $this->modelManager = $modelManager; |
||
118 | $this->class = $class; |
||
119 | $this->query = $query; |
||
120 | $this->identifier = $this->modelManager->getIdentifierFieldNames($this->class); |
||
121 | |||
122 | // The property option defines, which property (path) is used for |
||
123 | // displaying entities as strings |
||
124 | if ($property) { |
||
125 | $this->propertyPath = new PropertyPath($property); |
||
126 | $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); |
||
127 | } |
||
128 | |||
129 | parent::__construct($this->load($choices)); |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * @return array |
||
134 | */ |
||
135 | public function getIdentifier() |
||
136 | { |
||
137 | return $this->identifier; |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * Returns the according entities for the choices. |
||
142 | * |
||
143 | * If the choices were not initialized, they are initialized now. This |
||
144 | * is an expensive operation, except if the entities were passed in the |
||
145 | * "choices" option. |
||
146 | * |
||
147 | * @return array An array of entities |
||
148 | */ |
||
149 | public function getEntities() |
||
150 | { |
||
151 | return $this->entities; |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * Returns the entity for the given key. |
||
156 | * |
||
157 | * If the underlying entities have composite identifiers, the choices |
||
158 | * are initialized. The key is expected to be the index in the choices |
||
159 | * array in this case. |
||
160 | * |
||
161 | * If they have single identifiers, they are either fetched from the |
||
162 | * internal entity cache (if filled) or loaded from the database. |
||
163 | * |
||
164 | * @param string $key The choice key (for entities with composite |
||
165 | * identifiers) or entity ID (for entities with single |
||
166 | * identifiers) |
||
167 | * |
||
168 | * @return object The matching entity |
||
169 | */ |
||
170 | public function getEntity($key) |
||
171 | { |
||
172 | if (\count($this->identifier) > 1) { |
||
173 | // $key is a collection index |
||
174 | $entities = $this->getEntities(); |
||
175 | |||
176 | return $entities[$key] ?? null; |
||
177 | } elseif ($this->entities) { |
||
178 | return isset($this->entities[$key]) ? $this->entities[$key] : null; |
||
179 | } |
||
180 | |||
181 | return $this->modelManager->find($this->class, $key); |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * Returns the values of the identifier fields of an entity. |
||
186 | * |
||
187 | * Doctrine must know about this entity, that is, the entity must already |
||
188 | * be persisted or added to the identity map before. Otherwise an |
||
189 | * exception is thrown. |
||
190 | * |
||
191 | * @param object $entity The entity for which to get the identifier |
||
192 | * |
||
193 | * @throws InvalidArgumentException If the entity does not exist in Doctrine's |
||
194 | * identity map |
||
195 | * |
||
196 | * @return array |
||
197 | */ |
||
198 | public function getIdentifierValues($entity) |
||
199 | { |
||
200 | try { |
||
201 | return $this->modelManager->getIdentifierValues($entity); |
||
202 | } catch (\Exception $e) { |
||
203 | throw new InvalidArgumentException(sprintf('Unable to retrieve the identifier values for entity %s', ClassUtils::getClass($entity)), 0, $e); |
||
204 | } |
||
205 | } |
||
206 | |||
207 | /** |
||
208 | * @return ModelManagerInterface |
||
209 | */ |
||
210 | public function getModelManager() |
||
211 | { |
||
212 | return $this->modelManager; |
||
213 | } |
||
214 | |||
215 | /** |
||
216 | * @return string |
||
217 | */ |
||
218 | public function getClass() |
||
219 | { |
||
220 | return $this->class; |
||
221 | } |
||
222 | |||
223 | /** |
||
224 | * Initializes the choices and returns them. |
||
225 | * |
||
226 | * The choices are generated from the entities. If the entities have a |
||
227 | * composite identifier, the choices are indexed using ascending integers. |
||
228 | * Otherwise the identifiers are used as indices. |
||
229 | * |
||
230 | * If the entities were passed in the "choices" option, this method |
||
231 | * does not have any significant overhead. Otherwise, if a query builder |
||
232 | * was passed in the "query" option, this builder is now used to construct |
||
233 | * a query which is executed. In the last case, all entities for the |
||
234 | * underlying class are fetched from the repository. |
||
235 | * |
||
236 | * If the option "property" was passed, the property path in that option |
||
237 | * is used as option values. Otherwise this method tries to convert |
||
238 | * objects to strings using __toString(). |
||
239 | * |
||
240 | * @return array An array of choices |
||
241 | */ |
||
242 | protected function load($choices) |
||
243 | { |
||
244 | if (\is_array($choices) && \count($choices) > 0) { |
||
245 | $entities = $choices; |
||
246 | } elseif ($this->query) { |
||
247 | $entities = $this->modelManager->executeQuery($this->query); |
||
248 | } else { |
||
249 | $entities = $this->modelManager->findBy($this->class); |
||
250 | } |
||
251 | |||
252 | if (null === $entities) { |
||
253 | return []; |
||
254 | } |
||
255 | |||
256 | $choices = []; |
||
257 | $this->entities = []; |
||
258 | |||
259 | foreach ($entities as $key => $entity) { |
||
260 | if ($this->propertyPath) { |
||
261 | // If the property option was given, use it |
||
262 | $value = $this->propertyAccessor->getValue($entity, $this->propertyPath); |
||
263 | } else { |
||
264 | // Otherwise expect a __toString() method in the entity |
||
265 | try { |
||
266 | $value = (string) $entity; |
||
267 | } catch (\Exception $e) { |
||
268 | throw new RuntimeException(sprintf('Unable to convert the entity "%s" to string, provide ' |
||
269 | .'"property" option or implement "__toString()" method in your entity.', ClassUtils::getClass($entity)), 0, $e); |
||
270 | } |
||
271 | } |
||
272 | |||
273 | if (\count($this->identifier) > 1) { |
||
274 | // When the identifier consists of multiple field, use |
||
275 | // naturally ordered keys to refer to the choices |
||
276 | $choices[$key] = $value; |
||
277 | $this->entities[$key] = $entity; |
||
278 | } else { |
||
279 | // When the identifier is a single field, index choices by |
||
280 | // entity ID for performance reasons |
||
281 | $id = current($this->getIdentifierValues($entity)); |
||
282 | $choices[$id] = $value; |
||
283 | $this->entities[$id] = $entity; |
||
284 | } |
||
285 | } |
||
286 | |||
287 | return $choices; |
||
288 | } |
||
289 | |||
290 | /** |
||
291 | * Returns the \ReflectionProperty instance for a property of the |
||
292 | * underlying class. |
||
293 | * |
||
294 | * @param string $property The name of the property |
||
295 | * |
||
296 | * @return \ReflectionProperty The reflection instance |
||
297 | */ |
||
298 | private function getReflProperty(string $property): ReflectionProperty |
||
299 | { |
||
300 | if (!isset($this->reflProperties[$property])) { |
||
301 | $this->reflProperties[$property] = new \ReflectionProperty($this->class, $property); |
||
302 | $this->reflProperties[$property]->setAccessible(true); |
||
303 | } |
||
304 | |||
305 | return $this->reflProperties[$property]; |
||
306 | } |
||
307 | } |
||
308 |
If you suppress an error, we recommend checking for the error condition explicitly: