]>
Commit | Line | Data |
---|---|---|
4f5b44bd NL |
1 | <?php |
2 | ||
3 | /* | |
4 | * This file is part of the Symfony package. | |
5 | * | |
6 | * (c) Fabien Potencier <fabien@symfony.com> | |
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 Symfony\Component\Form\Extension\Core\ChoiceList; | |
13 | ||
14 | use Symfony\Component\Form\Exception\StringCastException; | |
15 | use Symfony\Component\Form\Exception\InvalidArgumentException; | |
16 | use Symfony\Component\PropertyAccess\PropertyPath; | |
17 | use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; | |
18 | use Symfony\Component\PropertyAccess\PropertyAccess; | |
19 | use Symfony\Component\PropertyAccess\PropertyAccessorInterface; | |
20 | ||
21 | /** | |
22 | * A choice list for object choices. | |
23 | * | |
24 | * Supports generation of choice labels, choice groups and choice values | |
25 | * by calling getters of the object (or associated objects). | |
26 | * | |
27 | * <code> | |
28 | * $choices = array($user1, $user2); | |
29 | * | |
30 | * // call getName() to determine the choice labels | |
31 | * $choiceList = new ObjectChoiceList($choices, 'name'); | |
32 | * </code> | |
33 | * | |
34 | * @author Bernhard Schussek <bschussek@gmail.com> | |
35 | */ | |
36 | class ObjectChoiceList extends ChoiceList | |
37 | { | |
38 | /** | |
39 | * @var PropertyAccessorInterface | |
40 | */ | |
41 | private $propertyAccessor; | |
42 | ||
43 | /** | |
44 | * The property path used to obtain the choice label. | |
45 | * | |
46 | * @var PropertyPath | |
47 | */ | |
48 | private $labelPath; | |
49 | ||
50 | /** | |
51 | * The property path used for object grouping. | |
52 | * | |
53 | * @var PropertyPath | |
54 | */ | |
55 | private $groupPath; | |
56 | ||
57 | /** | |
58 | * The property path used to obtain the choice value. | |
59 | * | |
60 | * @var PropertyPath | |
61 | */ | |
62 | private $valuePath; | |
63 | ||
64 | /** | |
65 | * Creates a new object choice list. | |
66 | * | |
67 | * @param array|\Traversable $choices The array of choices. Choices may also be given | |
68 | * as hierarchy of unlimited depth by creating nested | |
69 | * arrays. The title of the sub-hierarchy can be | |
70 | * stored in the array key pointing to the nested | |
71 | * array. The topmost level of the hierarchy may also | |
72 | * be a \Traversable. | |
73 | * @param string $labelPath A property path pointing to the property used | |
74 | * for the choice labels. The value is obtained | |
75 | * by calling the getter on the object. If the | |
76 | * path is NULL, the object's __toString() method | |
77 | * is used instead. | |
78 | * @param array $preferredChoices A flat array of choices that should be | |
79 | * presented to the user with priority. | |
80 | * @param string $groupPath A property path pointing to the property used | |
81 | * to group the choices. Only allowed if | |
82 | * the choices are given as flat array. | |
83 | * @param string $valuePath A property path pointing to the property used | |
84 | * for the choice values. If not given, integers | |
85 | * are generated instead. | |
86 | * @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths. | |
87 | */ | |
88 | public function __construct($choices, $labelPath = null, array $preferredChoices = array(), $groupPath = null, $valuePath = null, PropertyAccessorInterface $propertyAccessor = null) | |
89 | { | |
90 | $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::getPropertyAccessor(); | |
91 | $this->labelPath = null !== $labelPath ? new PropertyPath($labelPath) : null; | |
92 | $this->groupPath = null !== $groupPath ? new PropertyPath($groupPath) : null; | |
93 | $this->valuePath = null !== $valuePath ? new PropertyPath($valuePath) : null; | |
94 | ||
95 | parent::__construct($choices, array(), $preferredChoices); | |
96 | } | |
97 | ||
98 | /** | |
99 | * Initializes the list with choices. | |
100 | * | |
101 | * Safe to be called multiple times. The list is cleared on every call. | |
102 | * | |
103 | * @param array|\Traversable $choices The choices to write into the list. | |
104 | * @param array $labels Ignored. | |
105 | * @param array $preferredChoices The choices to display with priority. | |
106 | * | |
107 | * @throws InvalidArgumentException When passing a hierarchy of choices and using | |
108 | * the "groupPath" option at the same time. | |
109 | */ | |
110 | protected function initialize($choices, array $labels, array $preferredChoices) | |
111 | { | |
112 | if (null !== $this->groupPath) { | |
113 | $groupedChoices = array(); | |
114 | ||
115 | foreach ($choices as $i => $choice) { | |
116 | if (is_array($choice)) { | |
117 | throw new InvalidArgumentException('You should pass a plain object array (without groups) when using the "groupPath" option.'); | |
118 | } | |
119 | ||
120 | try { | |
121 | $group = $this->propertyAccessor->getValue($choice, $this->groupPath); | |
122 | } catch (NoSuchPropertyException $e) { | |
123 | // Don't group items whose group property does not exist | |
124 | // see https://github.com/symfony/symfony/commit/d9b7abb7c7a0f28e0ce970afc5e305dce5dccddf | |
125 | $group = null; | |
126 | } | |
127 | ||
128 | if (null === $group) { | |
129 | $groupedChoices[$i] = $choice; | |
130 | } else { | |
131 | if (!isset($groupedChoices[$group])) { | |
132 | $groupedChoices[$group] = array(); | |
133 | } | |
134 | ||
135 | $groupedChoices[$group][$i] = $choice; | |
136 | } | |
137 | } | |
138 | ||
139 | $choices = $groupedChoices; | |
140 | } | |
141 | ||
142 | $labels = array(); | |
143 | ||
144 | $this->extractLabels($choices, $labels); | |
145 | ||
146 | parent::initialize($choices, $labels, $preferredChoices); | |
147 | } | |
148 | ||
149 | /** | |
150 | * Creates a new unique value for this choice. | |
151 | * | |
152 | * If a property path for the value was given at object creation, | |
153 | * the getter behind that path is now called to obtain a new value. | |
154 | * Otherwise a new integer is generated. | |
155 | * | |
156 | * @param mixed $choice The choice to create a value for | |
157 | * | |
158 | * @return integer|string A unique value without character limitations. | |
159 | */ | |
160 | protected function createValue($choice) | |
161 | { | |
162 | if ($this->valuePath) { | |
163 | return (string) $this->propertyAccessor->getValue($choice, $this->valuePath); | |
164 | } | |
165 | ||
166 | return parent::createValue($choice); | |
167 | } | |
168 | ||
169 | private function extractLabels($choices, array &$labels) | |
170 | { | |
171 | foreach ($choices as $i => $choice) { | |
172 | if (is_array($choice)) { | |
173 | $labels[$i] = array(); | |
174 | $this->extractLabels($choice, $labels[$i]); | |
175 | } elseif ($this->labelPath) { | |
176 | $labels[$i] = $this->propertyAccessor->getValue($choice, $this->labelPath); | |
177 | } elseif (method_exists($choice, '__toString')) { | |
178 | $labels[$i] = (string) $choice; | |
179 | } else { | |
180 | throw new StringCastException(sprintf('A "__toString()" method was not found on the objects of type "%s" passed to the choice field. To read a custom getter instead, set the argument $labelPath to the desired property path.', get_class($choice))); | |
181 | } | |
182 | } | |
183 | } | |
184 | } |