diff options
Diffstat (limited to 'vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php')
-rw-r--r-- | vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php new file mode 100644 index 00000000..9a3fdef1 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php | |||
@@ -0,0 +1,274 @@ | |||
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\Type; | ||
13 | |||
14 | use Symfony\Component\Form\AbstractType; | ||
15 | use Symfony\Component\Form\Extension\Core\View\ChoiceView; | ||
16 | use Symfony\Component\Form\FormBuilderInterface; | ||
17 | use Symfony\Component\Form\FormInterface; | ||
18 | use Symfony\Component\Form\FormView; | ||
19 | use Symfony\Component\Form\Exception\LogicException; | ||
20 | use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList; | ||
21 | use Symfony\Component\Form\Extension\Core\EventListener\FixRadioInputListener; | ||
22 | use Symfony\Component\Form\Extension\Core\EventListener\FixCheckboxInputListener; | ||
23 | use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener; | ||
24 | use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer; | ||
25 | use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToBooleanArrayTransformer; | ||
26 | use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer; | ||
27 | use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToBooleanArrayTransformer; | ||
28 | use Symfony\Component\OptionsResolver\Options; | ||
29 | use Symfony\Component\OptionsResolver\OptionsResolverInterface; | ||
30 | |||
31 | class ChoiceType extends AbstractType | ||
32 | { | ||
33 | /** | ||
34 | * Caches created choice lists. | ||
35 | * @var array | ||
36 | */ | ||
37 | private $choiceListCache = array(); | ||
38 | |||
39 | /** | ||
40 | * {@inheritdoc} | ||
41 | */ | ||
42 | public function buildForm(FormBuilderInterface $builder, array $options) | ||
43 | { | ||
44 | if (!$options['choice_list'] && !is_array($options['choices']) && !$options['choices'] instanceof \Traversable) { | ||
45 | throw new LogicException('Either the option "choices" or "choice_list" must be set.'); | ||
46 | } | ||
47 | |||
48 | if ($options['expanded']) { | ||
49 | // Initialize all choices before doing the index check below. | ||
50 | // This helps in cases where index checks are optimized for non | ||
51 | // initialized choice lists. For example, when using an SQL driver, | ||
52 | // the index check would read in one SQL query and the initialization | ||
53 | // requires another SQL query. When the initialization is done first, | ||
54 | // one SQL query is sufficient. | ||
55 | $preferredViews = $options['choice_list']->getPreferredViews(); | ||
56 | $remainingViews = $options['choice_list']->getRemainingViews(); | ||
57 | |||
58 | // Check if the choices already contain the empty value | ||
59 | // Only add the empty value option if this is not the case | ||
60 | if (null !== $options['empty_value'] && 0 === count($options['choice_list']->getIndicesForValues(array('')))) { | ||
61 | $placeholderView = new ChoiceView(null, '', $options['empty_value']); | ||
62 | |||
63 | // "placeholder" is a reserved index | ||
64 | // see also ChoiceListInterface::getIndicesForChoices() | ||
65 | $this->addSubForms($builder, array('placeholder' => $placeholderView), $options); | ||
66 | } | ||
67 | |||
68 | $this->addSubForms($builder, $preferredViews, $options); | ||
69 | $this->addSubForms($builder, $remainingViews, $options); | ||
70 | |||
71 | if ($options['multiple']) { | ||
72 | $builder->addViewTransformer(new ChoicesToBooleanArrayTransformer($options['choice_list'])); | ||
73 | $builder->addEventSubscriber(new FixCheckboxInputListener($options['choice_list']), 10); | ||
74 | } else { | ||
75 | $builder->addViewTransformer(new ChoiceToBooleanArrayTransformer($options['choice_list'], $builder->has('placeholder'))); | ||
76 | $builder->addEventSubscriber(new FixRadioInputListener($options['choice_list'], $builder->has('placeholder')), 10); | ||
77 | } | ||
78 | } else { | ||
79 | if ($options['multiple']) { | ||
80 | $builder->addViewTransformer(new ChoicesToValuesTransformer($options['choice_list'])); | ||
81 | } else { | ||
82 | $builder->addViewTransformer(new ChoiceToValueTransformer($options['choice_list'])); | ||
83 | } | ||
84 | } | ||
85 | |||
86 | if ($options['multiple'] && $options['by_reference']) { | ||
87 | // Make sure the collection created during the client->norm | ||
88 | // transformation is merged back into the original collection | ||
89 | $builder->addEventSubscriber(new MergeCollectionListener(true, true)); | ||
90 | } | ||
91 | } | ||
92 | |||
93 | /** | ||
94 | * {@inheritdoc} | ||
95 | */ | ||
96 | public function buildView(FormView $view, FormInterface $form, array $options) | ||
97 | { | ||
98 | $view->vars = array_replace($view->vars, array( | ||
99 | 'multiple' => $options['multiple'], | ||
100 | 'expanded' => $options['expanded'], | ||
101 | 'preferred_choices' => $options['choice_list']->getPreferredViews(), | ||
102 | 'choices' => $options['choice_list']->getRemainingViews(), | ||
103 | 'separator' => '-------------------', | ||
104 | 'empty_value' => null, | ||
105 | )); | ||
106 | |||
107 | // The decision, whether a choice is selected, is potentially done | ||
108 | // thousand of times during the rendering of a template. Provide a | ||
109 | // closure here that is optimized for the value of the form, to | ||
110 | // avoid making the type check inside the closure. | ||
111 | if ($options['multiple']) { | ||
112 | $view->vars['is_selected'] = function ($choice, array $values) { | ||
113 | return false !== array_search($choice, $values, true); | ||
114 | }; | ||
115 | } else { | ||
116 | $view->vars['is_selected'] = function ($choice, $value) { | ||
117 | return $choice === $value; | ||
118 | }; | ||
119 | } | ||
120 | |||
121 | // Check if the choices already contain the empty value | ||
122 | // Only add the empty value option if this is not the case | ||
123 | if (null !== $options['empty_value'] && 0 === count($options['choice_list']->getIndicesForValues(array('')))) { | ||
124 | $view->vars['empty_value'] = $options['empty_value']; | ||
125 | } | ||
126 | |||
127 | if ($options['multiple'] && !$options['expanded']) { | ||
128 | // Add "[]" to the name in case a select tag with multiple options is | ||
129 | // displayed. Otherwise only one of the selected options is sent in the | ||
130 | // POST request. | ||
131 | $view->vars['full_name'] = $view->vars['full_name'].'[]'; | ||
132 | } | ||
133 | } | ||
134 | |||
135 | /** | ||
136 | * {@inheritdoc} | ||
137 | */ | ||
138 | public function finishView(FormView $view, FormInterface $form, array $options) | ||
139 | { | ||
140 | if ($options['expanded']) { | ||
141 | // Radio buttons should have the same name as the parent | ||
142 | $childName = $view->vars['full_name']; | ||
143 | |||
144 | // Checkboxes should append "[]" to allow multiple selection | ||
145 | if ($options['multiple']) { | ||
146 | $childName .= '[]'; | ||
147 | } | ||
148 | |||
149 | foreach ($view as $childView) { | ||
150 | $childView->vars['full_name'] = $childName; | ||
151 | } | ||
152 | } | ||
153 | } | ||
154 | |||
155 | /** | ||
156 | * {@inheritdoc} | ||
157 | */ | ||
158 | public function setDefaultOptions(OptionsResolverInterface $resolver) | ||
159 | { | ||
160 | $choiceListCache =& $this->choiceListCache; | ||
161 | |||
162 | $choiceList = function (Options $options) use (&$choiceListCache) { | ||
163 | // Harden against NULL values (like in EntityType and ModelType) | ||
164 | $choices = null !== $options['choices'] ? $options['choices'] : array(); | ||
165 | |||
166 | // Reuse existing choice lists in order to increase performance | ||
167 | $hash = md5(json_encode(array($choices, $options['preferred_choices']))); | ||
168 | |||
169 | if (!isset($choiceListCache[$hash])) { | ||
170 | $choiceListCache[$hash] = new SimpleChoiceList($choices, $options['preferred_choices']); | ||
171 | } | ||
172 | |||
173 | return $choiceListCache[$hash]; | ||
174 | }; | ||
175 | |||
176 | $emptyData = function (Options $options) { | ||
177 | if ($options['multiple'] || $options['expanded']) { | ||
178 | return array(); | ||
179 | } | ||
180 | |||
181 | return ''; | ||
182 | }; | ||
183 | |||
184 | $emptyValue = function (Options $options) { | ||
185 | return $options['required'] ? null : ''; | ||
186 | }; | ||
187 | |||
188 | $emptyValueNormalizer = function (Options $options, $emptyValue) { | ||
189 | if ($options['multiple']) { | ||
190 | // never use an empty value for this case | ||
191 | return null; | ||
192 | } elseif (false === $emptyValue) { | ||
193 | // an empty value should be added but the user decided otherwise | ||
194 | return null; | ||
195 | } elseif ($options['expanded'] && '' === $emptyValue) { | ||
196 | // never use an empty label for radio buttons | ||
197 | return 'None'; | ||
198 | } | ||
199 | |||
200 | // empty value has been set explicitly | ||
201 | return $emptyValue; | ||
202 | }; | ||
203 | |||
204 | $compound = function (Options $options) { | ||
205 | return $options['expanded']; | ||
206 | }; | ||
207 | |||
208 | $resolver->setDefaults(array( | ||
209 | 'multiple' => false, | ||
210 | 'expanded' => false, | ||
211 | 'choice_list' => $choiceList, | ||
212 | 'choices' => array(), | ||
213 | 'preferred_choices' => array(), | ||
214 | 'empty_data' => $emptyData, | ||
215 | 'empty_value' => $emptyValue, | ||
216 | 'error_bubbling' => false, | ||
217 | 'compound' => $compound, | ||
218 | // The view data is always a string, even if the "data" option | ||
219 | // is manually set to an object. | ||
220 | // See https://github.com/symfony/symfony/pull/5582 | ||
221 | 'data_class' => null, | ||
222 | )); | ||
223 | |||
224 | $resolver->setNormalizers(array( | ||
225 | 'empty_value' => $emptyValueNormalizer, | ||
226 | )); | ||
227 | |||
228 | $resolver->setAllowedTypes(array( | ||
229 | 'choice_list' => array('null', 'Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface'), | ||
230 | )); | ||
231 | } | ||
232 | |||
233 | /** | ||
234 | * {@inheritdoc} | ||
235 | */ | ||
236 | public function getName() | ||
237 | { | ||
238 | return 'choice'; | ||
239 | } | ||
240 | |||
241 | /** | ||
242 | * Adds the sub fields for an expanded choice field. | ||
243 | * | ||
244 | * @param FormBuilderInterface $builder The form builder. | ||
245 | * @param array $choiceViews The choice view objects. | ||
246 | * @param array $options The build options. | ||
247 | */ | ||
248 | private function addSubForms(FormBuilderInterface $builder, array $choiceViews, array $options) | ||
249 | { | ||
250 | foreach ($choiceViews as $i => $choiceView) { | ||
251 | if (is_array($choiceView)) { | ||
252 | // Flatten groups | ||
253 | $this->addSubForms($builder, $choiceView, $options); | ||
254 | } else { | ||
255 | $choiceOpts = array( | ||
256 | 'value' => $choiceView->value, | ||
257 | 'label' => $choiceView->label, | ||
258 | 'translation_domain' => $options['translation_domain'], | ||
259 | ); | ||
260 | |||
261 | if ($options['multiple']) { | ||
262 | $choiceType = 'checkbox'; | ||
263 | // The user can check 0 or more checkboxes. If required | ||
264 | // is true, he is required to check all of them. | ||
265 | $choiceOpts['required'] = false; | ||
266 | } else { | ||
267 | $choiceType = 'radio'; | ||
268 | } | ||
269 | |||
270 | $builder->add($i, $choiceType, $choiceOpts); | ||
271 | } | ||
272 | } | ||
273 | } | ||
274 | } | ||