Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Read-Only Fields

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

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:

... 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:

... 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:

... 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:

... 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.

... 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:

... 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:

... 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!

2
Login or Register to join the conversation
Default user avatar
Default user avatar Thierno Diop | posted 5 years ago

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

Reply

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!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony. The concepts of REST are still valid, but I recommend using API Platform in new Symfony apps.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.3.3",
        "symfony/symfony": "2.6.*", // v2.6.11
        "doctrine/orm": "~2.2,>=2.2.3,<2.5", // v2.4.7
        "doctrine/dbal": "<2.5", // v2.4.4
        "doctrine/doctrine-bundle": "~1.2", // v1.4.0
        "twig/extensions": "~1.0", // v1.2.0
        "symfony/assetic-bundle": "~2.3", // v2.6.1
        "symfony/swiftmailer-bundle": "~2.3", // v2.3.8
        "symfony/monolog-bundle": "~2.4", // v2.7.1
        "sensio/distribution-bundle": "~3.0,>=3.0.12", // v3.0.21
        "sensio/framework-extra-bundle": "~3.0,>=3.0.2", // v3.0.7
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.0
        "hautelook/alice-bundle": "0.2.*", // 0.2
        "jms/serializer-bundle": "0.13.*" // 0.13.0
    },
    "require-dev": {
        "sensio/generator-bundle": "~2.3", // v2.5.3
        "behat/behat": "~3.0", // v3.0.15
        "behat/mink-extension": "~2.0.1", // v2.0.1
        "behat/mink-goutte-driver": "~1.1.0", // v1.1.0
        "behat/mink-selenium2-driver": "~1.2.0", // v1.2.0
        "phpunit/phpunit": "~4.6.0" // 4.6.4
    }
}
userVoice