ChoiceField.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Form;
  11. use Symfony\Component\Form\Exception\InvalidOptionsException;
  12. /**
  13. * Lets the user select between different choices.
  14. *
  15. * Available options:
  16. *
  17. * * choices: An array of key-value pairs that will represent the choices
  18. * * preferred_choices: An array of choices (by key) that should be displayed
  19. * above all other options in the field
  20. * * empty_value: If set to a non-false value, an "empty" option will
  21. * be added to the top of the countries choices. A
  22. * common value might be "Choose a country". Default: false.
  23. *
  24. * The multiple and expanded options control exactly which HTML element
  25. * that should be used to render this field:
  26. *
  27. * * expanded = false, multiple = false A drop-down select element
  28. * * expanded = false, multiple = true A multiple select element
  29. * * expanded = true, multiple = false A series of input radio elements
  30. * * expanded = true, multiple = true A series of input checkboxes
  31. *
  32. * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
  33. */
  34. class ChoiceField extends HybridField
  35. {
  36. /**
  37. * Stores the preferred choices with the choices as keys
  38. * @var array
  39. */
  40. protected $preferredChoices = array();
  41. /**
  42. * Stores the choices
  43. * You should only access this property through getChoices()
  44. * @var array
  45. */
  46. private $choices = array();
  47. protected function configure()
  48. {
  49. $this->addRequiredOption('choices');
  50. $this->addOption('preferred_choices', array());
  51. $this->addOption('multiple', false);
  52. $this->addOption('expanded', false);
  53. $this->addOption('empty_value', '');
  54. parent::configure();
  55. $choices = $this->getOption('choices');
  56. if (!is_array($choices) && !$choices instanceof \Closure) {
  57. throw new InvalidOptionsException('The choices option must be an array or a closure', array('choices'));
  58. }
  59. if (!is_array($this->getOption('preferred_choices'))) {
  60. throw new InvalidOptionsException('The preferred_choices option must be an array', array('preferred_choices'));
  61. }
  62. if (count($this->getOption('preferred_choices')) > 0) {
  63. $this->preferredChoices = array_flip($this->getOption('preferred_choices'));
  64. }
  65. if ($this->isExpanded()) {
  66. $this->setFieldMode(self::FORM);
  67. $choices = $this->getChoices();
  68. foreach ($this->preferredChoices as $choice => $_) {
  69. $this->add($this->newChoiceField($choice, $choices[$choice]));
  70. }
  71. foreach ($choices as $choice => $value) {
  72. if (!isset($this->preferredChoices[$choice])) {
  73. $this->add($this->newChoiceField($choice, $value));
  74. }
  75. }
  76. } else {
  77. $this->setFieldMode(self::FIELD);
  78. }
  79. }
  80. public function getName()
  81. {
  82. // TESTME
  83. $name = parent::getName();
  84. // Add "[]" to the name in case a select tag with multiple options is
  85. // displayed. Otherwise only one of the selected options is sent in the
  86. // POST request.
  87. if ($this->isMultipleChoice() && !$this->isExpanded()) {
  88. $name .= '[]';
  89. }
  90. return $name;
  91. }
  92. /**
  93. * Initializes the choices
  94. *
  95. * If the choices were given as a closure, the closure is executed now.
  96. *
  97. * @return array
  98. */
  99. protected function initializeChoices()
  100. {
  101. if (!$this->choices) {
  102. $this->choices = $this->getInitializedChoices();
  103. if (!$this->isRequired()) {
  104. $this->choices = array('' => $this->getOption('empty_value')) + $this->choices;
  105. }
  106. }
  107. }
  108. protected function getInitializedChoices()
  109. {
  110. $choices = $this->getOption('choices');
  111. if ($choices instanceof \Closure) {
  112. $choices = $choices->__invoke();
  113. }
  114. if (!is_array($choices)) {
  115. throw new InvalidOptionsException('The "choices" option must be an array or a closure returning an array', array('choices'));
  116. }
  117. return $choices;
  118. }
  119. /**
  120. * Returns the choices
  121. *
  122. * If the choices were given as a closure, the closure is executed on
  123. * the first call of this method.
  124. *
  125. * @return array
  126. */
  127. protected function getChoices()
  128. {
  129. $this->initializeChoices();
  130. return $this->choices;
  131. }
  132. public function getPreferredChoices()
  133. {
  134. return array_intersect_key($this->getChoices(), $this->preferredChoices);
  135. }
  136. public function getOtherChoices()
  137. {
  138. return array_diff_key($this->getChoices(), $this->preferredChoices);
  139. }
  140. public function getLabel($choice)
  141. {
  142. $choices = $this->getChoices();
  143. return isset($choices[$choice]) ? $choices[$choice] : null;
  144. }
  145. public function isChoiceGroup($choice)
  146. {
  147. return is_array($choice) || $choice instanceof \Traversable;
  148. }
  149. public function isChoiceSelected($choice)
  150. {
  151. return in_array($choice, (array) $this->getDisplayedData());
  152. }
  153. public function isMultipleChoice()
  154. {
  155. return $this->getOption('multiple');
  156. }
  157. public function isExpanded()
  158. {
  159. return $this->getOption('expanded');
  160. }
  161. /**
  162. * Returns a new field of type radio button or checkbox.
  163. *
  164. * @param string $choice The key for the option
  165. * @param string $label The label for the option
  166. */
  167. protected function newChoiceField($choice, $label)
  168. {
  169. if ($this->isMultipleChoice()) {
  170. return new CheckboxField($choice, array(
  171. 'value' => $choice,
  172. ));
  173. } else {
  174. return new RadioField($choice, array(
  175. 'value' => $choice,
  176. ));
  177. }
  178. }
  179. /**
  180. * {@inheritDoc}
  181. *
  182. * Takes care of converting the input from a single radio button
  183. * to an array.
  184. */
  185. public function submit($value)
  186. {
  187. if (!$this->isMultipleChoice() && $this->isExpanded()) {
  188. $value = null === $value ? array() : array($value => true);
  189. }
  190. parent::submit($value);
  191. }
  192. /**
  193. * Transforms a single choice or an array of choices to a format appropriate
  194. * for the nested checkboxes/radio buttons.
  195. *
  196. * The result is an array with the options as keys and true/false as values,
  197. * depending on whether a given option is selected. If this field is rendered
  198. * as select tag, the value is not modified.
  199. *
  200. * @param mixed $value An array if "multiple" is set to true, a scalar
  201. * value otherwise.
  202. * @return mixed An array if "expanded" or "multiple" is set to true,
  203. * a scalar value otherwise.
  204. */
  205. protected function transform($value)
  206. {
  207. if ($this->isExpanded()) {
  208. $value = parent::transform($value);
  209. $choices = $this->getChoices();
  210. foreach ($choices as $choice => $_) {
  211. $choices[$choice] = $this->isMultipleChoice()
  212. ? in_array($choice, (array)$value, true)
  213. : ($choice === $value);
  214. }
  215. return $choices;
  216. }
  217. return parent::transform($value);
  218. }
  219. /**
  220. * Transforms a checkbox/radio button array to a single choice or an array
  221. * of choices.
  222. *
  223. * The input value is an array with the choices as keys and true/false as
  224. * values, depending on whether a given choice is selected. The output
  225. * is an array with the selected choices or a single selected choice.
  226. *
  227. * @param mixed $value An array if "expanded" or "multiple" is set to true,
  228. * a scalar value otherwise.
  229. * @return mixed $value An array if "multiple" is set to true, a scalar
  230. * value otherwise.
  231. */
  232. protected function reverseTransform($value)
  233. {
  234. if ($this->isExpanded()) {
  235. $choices = array();
  236. foreach ($value as $choice => $selected) {
  237. if ($selected) {
  238. $choices[] = $choice;
  239. }
  240. }
  241. if ($this->isMultipleChoice()) {
  242. $value = $choices;
  243. } else {
  244. $value = count($choices) > 0 ? current($choices) : null;
  245. }
  246. }
  247. return parent::reverseTransform($value);
  248. }
  249. }