2 thoughts on “[ChoiceTyp]: choices [true, false, null] not work

  1. Ok, sorry for late answer

    Here is the problem:

    I configured simple choicetype child element for simple form

    Here it is:

    <?php
    
    declare(strict_types=1);
    
    namespace App\Form;
    
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
    use Symfony\Component\Form\FormBuilderInterface;
    
    class SomeForm extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options): void
        {
            $builder->add('type', ChoiceType::class, [
                'choices' => [
                    'good' => true,
                    'bad' => false,
                    'meh' => null,
                ],
            ]);
        }
    
        public function getBlockPrefix(): string
        {
            return 'prefix';
        }
    }

    Then simple action to get request

    <?php
    
    declare(strict_types=1);
    
    namespace App\Controller;
    
    use App\Form\SomeForm;
    use Symfony\Component\Form\FormFactoryInterface;
    use Symfony\Component\HttpFoundation\JsonResponse;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    
    class SomeController
    {
        private FormFactoryInterface $formFactory;
    
        public function __construct(FormFactoryInterface $formFactory)
        {
            $this->formFactory = $formFactory;
        }
    
        public function __invoke(Request $request): JsonResponse
        {
            $form = $this->formFactory->create(SomeForm::class);
    
            $form->handleRequest($request);
    
            if (!$form->isSubmitted() || !$form->isValid()) {
                return new JsonResponse(null, Response::HTTP_BAD_REQUEST);
            }
    
            return new JsonResponse($form->getData());
        }
    }

    And i want to test it, what can be simpler?

    <?php
    
    declare(strict_types=1);
    
    namespace App\Tests\Controller;
    
    use Symfony\Bundle\FrameworkBundle\KernelBrowser;
    use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
    use Symfony\Component\HttpFoundation\Request;
    
    class SomeControllerTest extends WebTestCase
    {
        public KernelBrowser $client;
    
        protected function setUp(): void
        {
            parent::setUp();
    
            $this->client = self::createClient();
        }
    
        /** @dataProvider choiceDataProvider */
        public function testGetCorrectData(?bool $type): void
        {
            $this->client->request(Request::METHOD_POST, '/some', [
                'prefix' => [
                    'type' => $type,
                ],
            ]);
    
            $responseData = $this->getResponseData();
    
            $this->assertSame($type, $responseData['type']);
        }
    
        public function choiceDataProvider(): array
        {
            return [
                [true],
                [false],
                [null],
            ];
        }
    
        private function getResponseData(): array
        {
            $response = $this->client->getResponse();
    
            return json_decode($response->getContent(), true);
        }
    }

    And here is the result of tests

    PHPUnit 9.5.2 by Sebastian Bergmann and contributors.
    
    FF.                                                                 3 / 3 (100%%)
    
    Time: 00:00.058, Memory: 12.00 MB
    
    There were 2 failures:
    
    1) App\Tests\Controller\SomeControllerTest::testGetCorrectData with data set #0 (true)
    Failed asserting that false is identical to true.
    
    D:\Projects\symfony-form-test\tests\Controller\SomeControllerTest.php:33
    
    2) App\Tests\Controller\SomeControllerTest::testGetCorrectData with data set #1 (false)
    Failed asserting that null is identical to false.

    I found the reason of this invalid behavior

    The class Symfony\Component\Form\ChoiceList\ArrayChoiceList has invalid code flow for storing [true, false, null] values

    here is the state of class at the end of __construct

    ^ Symfony\Component\Form\ChoiceList\ArrayChoiceList^ {#208
      #choices: array:3 [
        0 => true
        1 => false
        2 => null
      ]
      #structuredValues: array:3 [
        "good" => "0"
        "bad" => "1"
        "meh" => "2"
      ]
      #originalKeys: array:3 [
        0 => "good"
        1 => "bad"
        2 => "meh"
      ]
      #valueCallback: null
    }

    Whe true value passes to type and form trying to process it, this class gets "1" because form casted true to "1" and final result will be false, because at this line its get access to choice list by key, where the key is actually passed true value

    Also if i change the positions of choice list items in form configuration, behaviour will be another, but still not correct

    Finally, to get correct data in controller, i need path directly to request body

    Pls, fix. This is very annoying. I dont want make enum of some objects list, i want native types for my api

    P.S.

    If i try FIRST example at the official symfony form component docs… It also works incorrectly, what the hack?

  2. In your last example, you are now passing values like true, false and null to the submit() method. This method expects the data to be in the view format which agains means things like strings or arrays of strings. If you pass anything else, you will have to register your own transformers that transform the values into the format that is expected by the Form component.