Buy

Read-Only Fields

What if we don't want the nickname to be changeable? After all, we're using it almost like a primary key for the Programmer. Yea, I want an API client to set it on create, but I don't want them to be able to change it afterwards.

Send a new nickname in the body of the PUT request - CowgirlCoder. We want the server to just ignore that. At the end, assert that nickname still equals CowboyCoder, even though we're trying to mess with things:

94 lines src/AppBundle/Tests/Controller/Api/ProgrammerControllerTest.php
... lines 1 - 71
public function testPUTProgrammer()
{
... lines 74 - 79
$data = array(
'nickname' => 'CowgirlCoder',
... lines 82 - 83
);
... lines 85 - 89
// the nickname is immutable on edit
$this->asserter()->assertResponsePropertyEquals($response, 'nickname', 'CowboyCoder');
}
... lines 93 - 94

Run just that test:

phpunit -c app --filter testPUTProgrammer

Womp womp - we're failing: the nickname is updated on the programmer. That makes perfect sense: our form will update any of the 3 fields we configured in PogrammerType:

43 lines src/AppBundle/Form/ProgrammerType.php
... lines 1 - 8
class ProgrammerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nickname', 'text')
->add('avatarNumber', 'choice', [
... lines 16 - 27
->add('tagLine', 'textarea')
;
}
... lines 31 - 42
}

Using disabled Form Fields

So how can we make nickname only writeable when we're adding a new programmer. If you think about the HTML world, this would be like a form that had a functional nickname text box when creating, but a disabled nickname text box when editing. We can use this idea in our API by giving the nickname field a disabled option that's set to true.

In an API, this will mean that any value submitted to this field will just be ignored. If we can set this to true in edit mode only, that would do the trick!

To do that, reference a new option called is_edit:

47 lines src/AppBundle/Form/ProgrammerType.php
... lines 1 - 10
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nickname', 'text', [
// readonly if we're in edit mode
'disabled' => $options['is_edit']
])
... lines 18 - 31
;
}
... lines 34 - 47

If we're in "edit mode", then the field is disabled. To make this a valid form option, add a new entry in setDefaultOptions() and default it to false:

47 lines src/AppBundle/Form/ProgrammerType.php
... lines 1 - 34
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Programmer',
'is_edit' => false,
));
}
... lines 42 - 47

Head back to ProgrammerController::updateAction() and give createForm() a third array argument. Pass is_edit => true.

135 lines src/AppBundle/Controller/Api/ProgrammerController.php
... lines 1 - 90
public function updateAction($nickname, Request $request)
{
... lines 93 - 103
$form = $this->createForm(new ProgrammerType(), $programmer, array(
'is_edit' => true,
));
... lines 107 - 116
}
... lines 118 - 135

Ok, try the test!

phpunit -c app --filter testPUTProgrammer

Yay! That was easy!

Creating a Separate Form Class

And now that it's working, I need to force one small change on us that'll help us way in the future when we talk about API documentation. Instead of passing is_edit in the controller, we'll create a second form type class. Copy ProgrammerType to UpdateProgrammerType. Make this extend ProgrammerType and git rid of buildForm(). In setDefaultOptions(), we only need to set is_edit to true and call the parent function above this. Make sure getName() returns something unique:

23 lines src/AppBundle/Form/UpdateProgrammerType.php
... lines 1 - 2
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UpdateProgrammerType extends ProgrammerType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
parent::setDefaultOptions($resolver);
// override this!
$resolver->setDefaults(['is_edit' => true]);
}
public function getName()
{
return 'programmer_edit';
}
}

The whole purpose of this class is to act just like ProgrammerType, but set is_edit to true instead of us passing that in the controller. Both approaches are fine - but I'm planning ahead to when we use NelmioApiDocBundle: it likes 2 classes better. In the controller, use new UpdateProgrammerType and get rid of the third argument:

134 lines src/AppBundle/Controller/Api/ProgrammerController.php
... lines 1 - 91
public function updateAction($nickname, Request $request)
{
... lines 94 - 104
$form = $this->createForm(new UpdateProgrammerType(), $programmer);
... lines 106 - 115
}
... lines 117 - 134

Test out your handy-work:

phpunit -c app --filter testPUTProgrammer

Success!

Leave a comment!

  • 2016-05-22 weaverryan

    Oh really? Can you post some code? If you set "nickname" to disabled => true, the form component will prevent that field from being modified in all cases. This is exactly what we tested for (and saw) in the tutorial. Also, what version of Symfony do you have?

    Cheers!

  • 2016-05-22 Thierno Diop

    i changed the disabled to true to see if my defaultoptions was wrong but even after that the nickname field is modified