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( |
||
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) { |
||
0 ignored issues
–
show
|
|||
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 |
||
0 ignored issues
–
show
|
|||
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 |
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.