Skip to content

Commit 93730b0

Browse files
authored
Merge pull request #60 from jmclean/compat
Symfony multi-version compatibility
2 parents 98fe48b + f2bf542 commit 93730b0

21 files changed

+655
-178
lines changed

Form/DataTransformer/CheckboxGridTransformer.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
*/
2121
class CheckboxGridTransformer implements DataTransformerInterface
2222
{
23+
/** @var string */
24+
protected $class;
25+
2326
/** @var ChoiceListInterface */
2427
protected $xChoiceList;
2528
/** @var ChoiceListInterface */

Form/EventListener/CheckboxRowCreationListener.php

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@
1111

1212
use Infinite\FormBundle\Form\DataTransformer\AnythingToBooleanTransformer;
1313
use Infinite\FormBundle\Form\Util\LegacyFormUtil;
14+
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
15+
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface as LegacyChoiceListInterface;
1416
use Symfony\Component\Form\FormEvents;
1517
use Symfony\Component\Form\FormEvent;
1618
use Symfony\Component\Form\FormFactoryInterface;
1719
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
20+
use Symfony\Component\Form\FormInterface;
1821

1922
/**
2023
* When a checkbox grid is created, there may already be a few boxes checked. When the grid is bound,
@@ -58,27 +61,48 @@ public function preSetData(FormEvent $event)
5861
// Now that we have data available, create the checkboxes for the form. For every box that should
5962
// be checked, attach a transformer that will convert between its data object and a boolean.
6063

61-
foreach ($options['choice_list']->getRemainingViews() as $choice) {
62-
if (isset($options['cell_filter']) && !$options['cell_filter']($choice->data, $options['row']->data)) {
63-
// Blank cell - put a dummy form control here
64-
$form->add($choice->value, LegacyFormUtil::getType('Symfony\Component\Form\Extension\Core\Type\FormType'), array());
65-
} else {
66-
$builder = $this->factory->createNamedBuilder(
67-
$choice->value,
68-
LegacyFormUtil::getType('Symfony\Component\Form\Extension\Core\Type\CheckboxType'),
69-
isset($data[$choice->value]),
70-
array(
71-
'auto_initialize' => false,
72-
'required' => false,
73-
)
74-
);
64+
/** @var ChoiceListInterface|LegacyChoiceListInterface $yChoiceList */
65+
$choiceList = $options['choice_list'];
7566

76-
if (isset($data[$choice->value])) {
77-
$builder->addViewTransformer(new AnythingToBooleanTransformer($data[$choice->value]), true);
78-
}
67+
if ($choiceList instanceof LegacyChoiceListInterface) {
68+
foreach ($choiceList->getRemainingViews() as $view) {
69+
$this->addCheckbox($options, $view->data, $form, $view->value, $data);
70+
}
71+
} else {
72+
foreach ($options['choice_list']->getChoices() as $value => $choice) {
73+
$this->addCheckbox($options, $choice, $form, $value, $data);
74+
}
75+
}
76+
}
7977

80-
$form->add($builder->getForm());
78+
/**
79+
* @param array $options
80+
* @param $choice
81+
* @param FormInterface $form
82+
* @param $value
83+
* @param $data
84+
*/
85+
protected function addCheckbox($options, $choice, FormInterface $form, $value, $data)
86+
{
87+
if (isset($options['cell_filter']) && !$options['cell_filter']($choice, $options['row'])) {
88+
// Blank cell - put a dummy form control here
89+
$form->add($value, LegacyFormUtil::getType('Symfony\Component\Form\Extension\Core\Type\FormType'), array());
90+
} else {
91+
$builder = $this->factory->createNamedBuilder(
92+
$value,
93+
LegacyFormUtil::getType('Symfony\Component\Form\Extension\Core\Type\CheckboxType'),
94+
isset($data[$value]),
95+
array(
96+
'auto_initialize' => false,
97+
'required' => false,
98+
)
99+
);
100+
101+
if (isset($data[$value])) {
102+
$builder->addViewTransformer(new AnythingToBooleanTransformer($data[$value]), true);
81103
}
104+
105+
$form->add($builder->getForm());
82106
}
83107
}
84108
}

Form/EventListener/ResizePolyFormListener.php

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public function __construct(array $prototypes, array $options = array(), $allowA
7676
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
7777
$defaultType = null;
7878

79-
foreach ($prototypes as $prototype) {
79+
foreach ($prototypes as $key => $prototype) {
8080
/** @var FormInterface $prototype */
8181
$modelClass = $prototype->getConfig()->getOption('model_class');
8282
$type = $prototype->getConfig()->getType()->getInnerType();
@@ -85,13 +85,7 @@ public function __construct(array $prototypes, array $options = array(), $allowA
8585
$defaultType = $type;
8686
}
8787

88-
$typeKey = $type;
89-
90-
if ($type instanceof FormTypeInterface) {
91-
$typeKey = LegacyFormUtil::isFullClassNameRequired() ? get_class($type) : $type->getName();
92-
}
93-
94-
$this->typeMap[$typeKey] = $type;
88+
$this->typeMap[$key] = $type;
9589
$this->classMap[$modelClass] = $type;
9690
}
9791

Form/Type/CheckboxGridType.php

Lines changed: 133 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,45 +10,88 @@
1010
namespace Infinite\FormBundle\Form\Type;
1111

1212
use Infinite\FormBundle\Form\DataTransformer\CheckboxGridTransformer;
13+
use Infinite\FormBundle\Form\Util\ChoiceListViewAdapter;
1314
use Infinite\FormBundle\Form\Util\LegacyFormUtil;
1415
use Symfony\Component\Form\AbstractType;
16+
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
17+
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
18+
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
19+
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
20+
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface as LegacyChoiceListInterface;
21+
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
1522
use Symfony\Component\Form\FormBuilderInterface;
1623
use Symfony\Component\Form\FormInterface;
1724
use Symfony\Component\Form\FormView;
25+
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
26+
use Symfony\Component\OptionsResolver\Options;
1827
use Symfony\Component\OptionsResolver\OptionsResolver;
1928
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
29+
use Symfony\Component\PropertyAccess\PropertyAccess;
30+
use Symfony\Component\PropertyAccess\PropertyAccessor;
31+
use Symfony\Component\PropertyAccess\PropertyPath;
2032

2133
/**
22-
* Provides a checkbox grid for non-Doctrine objects (rows and cols set by x/y_choice_list)
34+
* Provides a checkbox grid for non-Doctrine objects (rows and cols set by x/y_choices)
2335
*/
2436
class CheckboxGridType extends AbstractType
2537
{
2638
public function buildForm(FormBuilderInterface $builder, array $options)
2739
{
28-
if ($options['x_choice_list']->getPreferredViews()) {
29-
throw new \Exception('Checkbox grid: x choice list cannot have preferred views');
30-
}
40+
$accessor = PropertyAccess::createPropertyAccessor();
3141

32-
if ($options['y_choice_list']->getPreferredViews()) {
33-
throw new \Exception('Checkbox grid: y choice list cannot have preferred views');
34-
}
42+
/** @var ChoiceListInterface|LegacyChoiceListInterface $yChoiceList */
43+
$yChoiceList = $options['y_choice_list'];
44+
45+
if ($yChoiceList instanceof LegacyChoiceListInterface) {
46+
// LegacyChoiceList is repsonsible for providing labels.
47+
foreach ($yChoiceList->getRemainingViews() as $view) {
48+
$labelBase = $view->label;
49+
$value = $view->value;
50+
$choice = $view->data;
51+
52+
$this->buildRow($builder, $options, $choice, $labelBase, $accessor, $value);
53+
}
54+
} else {
55+
foreach ($yChoiceList->getChoices() as $value => $choice) {
56+
// The $choice object itself can be used as a label if it has __toString or a label path
57+
$labelBase = $choice;
3558

36-
foreach ($options['y_choice_list']->getRemainingViews() as $choice) {
37-
$rowOptions = array(
38-
'cell_filter' => $options['cell_filter'],
39-
'choice_list' => $options['x_choice_list'],
40-
'row' => $choice,
41-
);
59+
// Although if we're using y_choices then look up the label there.
60+
if ($options['y_choices'] !== null && array_key_exists($choice, $options['y_choices'])) {
61+
$labelBase = $options['y_choices'][$choice];
62+
}
4263

43-
$builder->add($choice->value, LegacyFormUtil::getType('Infinite\FormBundle\Form\Type\CheckboxRowType'), $rowOptions);
64+
$this->buildRow($builder, $options, $choice, $labelBase, $accessor, $value);
65+
}
4466
}
4567

4668
$builder->addViewTransformer(new CheckboxGridTransformer($options));
4769
}
4870

71+
/**
72+
* @param FormBuilderInterface $builder
73+
* @param array $options
74+
* @param $choice
75+
* @param $labelBase
76+
* @param $accessor
77+
* @param $value
78+
*/
79+
protected function buildRow(FormBuilderInterface $builder, array $options, $choice, $labelBase, PropertyAccessor $accessor, $value) {
80+
$rowOptions = array(
81+
'cell_filter' => $options['cell_filter'],
82+
'choice_list' => $options['x_choice_list'],
83+
'label_path' => $options['x_label_path'],
84+
'row' => $choice,
85+
'row_label' => $options['y_label_path'] === null ? $labelBase : $accessor->getValue($choice, $options['y_label_path']),
86+
);
87+
88+
$builder->add($value, LegacyFormUtil::getType( 'Infinite\FormBundle\Form\Type\CheckboxRowType'), $rowOptions);
89+
}
90+
91+
4992
public function buildView(FormView $view, FormInterface $form, array $options)
5093
{
51-
$view->vars['headers'] = $options['x_choice_list'];
94+
$view->vars['headers'] = $this->buildChoiceListView($options['x_choice_list'], $options['x_choices'], $options['x_label_path']);
5295
}
5396

5497
public function getBlockPrefix()
@@ -58,19 +101,55 @@ public function getBlockPrefix()
58101

59102
public function configureOptions(OptionsResolver $resolver)
60103
{
104+
$defaultXChoiceList = function (Options $options) {
105+
if (!isset($options['x_choices'])) {
106+
throw new InvalidOptionsException('You must provide the x_choices option.');
107+
}
108+
109+
// SF 2.7+: choice lists are not responsible for labels.
110+
// Strip the labels until we build the choice view later.
111+
if (class_exists('Symfony\Component\Form\ChoiceList\ArrayChoiceList')) {
112+
return new ArrayChoiceList(array_keys($options['x_choices']), function ($choice) { return $choice; });
113+
}
114+
115+
// BC < SF 2.7
116+
return new SimpleChoiceList($options['x_choices']);
117+
};
118+
119+
$defaultYChoiceList = function (Options $options) {
120+
if (!isset($options['y_choices'])) {
121+
throw new InvalidOptionsException('You must provide the y_choices option.');
122+
}
123+
124+
// SF 2.7+: choice lists are not responsible for labels.
125+
// Strip the labels for now and look them up later.
126+
if (class_exists('Symfony\Component\Form\ChoiceList\ArrayChoiceList')) {
127+
return new ArrayChoiceList(array_keys($options['y_choices']), function ($choice) { return $choice; });
128+
}
129+
130+
// BC < SF 2.7
131+
return new SimpleChoiceList($options['y_choices']);
132+
};
133+
61134
$resolver->setDefaults(array(
62135
'class' => null,
63136
'cell_filter' => null,
137+
138+
'x_choices' => null,
139+
'x_choice_list' => $defaultXChoiceList,
140+
'x_label_path' => null,
141+
142+
'y_label_path' => null,
143+
'y_choices' => null,
144+
'y_choice_list' => $defaultYChoiceList,
64145
));
65146

66147
$resolver->setRequired(array(
67-
'x_choice_list',
68148
'x_path',
69-
'y_choice_list',
70149
'y_path',
71150
));
72151
}
73-
152+
74153
// BC for SF < 2.7
75154
public function setDefaultOptions(OptionsResolverInterface $resolver)
76155
{
@@ -82,4 +161,40 @@ public function getName()
82161
{
83162
return $this->getBlockPrefix();
84163
}
164+
165+
/**
166+
* @param ChoiceListInterface|LegacyChoiceListInterface $choiceList
167+
* @param array|null $originalChoices
168+
* @param string|PropertyPath|null $labelPath
169+
* @return ChoiceListView|LegacyChoiceListInterface
170+
*/
171+
protected function buildChoiceListView($choiceList, $originalChoices, $labelPath)
172+
{
173+
// BC for SF < 2.7
174+
if ($choiceList instanceof LegacyChoiceListInterface) {
175+
return $choiceList;
176+
}
177+
178+
// Build the choice list view the usual way.
179+
$accessor = PropertyAccess::createPropertyAccessor();
180+
181+
$choiceListFactory = new DefaultChoiceListFactory();
182+
$labelCallback = function ($choice) use ($accessor, $originalChoices, $labelPath) {
183+
// If we stripped the choice labels back in configureOptions then look it up now.
184+
if ($originalChoices !== null && array_key_exists($choice, $originalChoices)) {
185+
$choice = $originalChoices[$choice];
186+
}
187+
188+
if ($labelPath === null) {
189+
return (string)$choice;
190+
} else {
191+
return $accessor->getValue($choice, $labelPath);
192+
}
193+
};
194+
195+
$choiceListView = $choiceListFactory->createView($choiceList, null, $labelCallback);
196+
197+
// Wrap it in a custom class so that old form_themes can still call getRemainingViews.
198+
return new ChoiceListViewAdapter($choiceListView);
199+
}
85200
}

Form/Type/CheckboxRowType.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
2727

2828
public function buildView(FormView $view, FormInterface $form, array $options)
2929
{
30-
$view->vars['label'] = $options['row']->label;
30+
$view->vars['label'] = $options['row_label'];
3131
}
3232

3333
public function getBlockPrefix()
@@ -40,7 +40,9 @@ public function configureOptions(OptionsResolver $resolver)
4040
$resolver->setDefaults(array(
4141
'cell_filter' => null,
4242
'choice_list' => null,
43+
'label_path' => null,
4344
'row' => null,
45+
'row_label' => null,
4446
));
4547
}
4648

0 commit comments

Comments
 (0)