Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Agree to Terms Checkbox Field

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 $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

The User class has an agreedTermsAt property that expects a DateTime object. But, our form has an agreeTerms field that, on submit, will give us a true/false boolean value. How can we make these work together? As I so often like to say: there are two options.

First, we could be clever! There is no agreeTerms property on User. But, we could create a setAgreeTerms() method on User. When that's called, we would actually set the agreedTermsAt property to the current date. We would also need to create a getAgreeTerms() method that would return a boolean based on whether or not the agreedTermsAt property was set.

This is a fine solution. But, this is also a good example of how the form system can start to make your life harder instead of easier. When your form and your class don't look the same, sometimes you can find a simple and natural solution. But sometimes, you might need to dream up something crazy to make it all work. If the solution isn't obvious to you, move on to option two: make the field unmapped.

Let's try that: set agreeTerms to mapped false. To force this to be checked, add constraints set to a new IsTrue()... because we need the underlying value of this field to be true, not false. Set a custom message:

I know, it's silly, but you must agree to our terms

... lines 1 - 14
class UserRegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
... lines 20 - 34
->add('agreeTerms', CheckboxType::class, [
'mapped' => false,
'constraints' => [
new IsTrue([
'message' => 'I know, it\'s silly, but you must agree to our terms.'
])
]
])
;
}
... lines 45 - 51
}

Excellent! Thanks to the mapped = false, the form should at least load. Try it - refresh! Yes! Well... oh boy - our styling is so bad, the checkbox is hiding off the screen! Let's worry about that in a minute.

Thanks to the mapped => false, the data from the checkbox does not affect our User object in any way when we submit. No problem: in SecurityController, let's handle it manually with if (true === $form['agreeTerms']->getData()). Wait... that looks redundant! We already have form validation that forces the box to be checked. You're totally right! I'm just being extra careful... ya know... for legal reasons.

... lines 1 - 44
public function register(Request $request, UserPasswordEncoderInterface $passwordEncoder, GuardAuthenticatorHandler $guardHandler, LoginFormAuthenticator $formAuthenticator)
{
... lines 47 - 49
if ($form->isSubmitted() && $form->isValid()) {
... lines 51 - 56
// be absolutely sure they agree
if (true === $form['agreeTerms']->getData()) {
... line 59
}
... lines 61 - 71
}
... lines 73 - 76
}
... lines 78 - 79

Inside, we could call $user->setAgreedTermsAt() and pass the current date. Or, we can do something a bit cleaner. Find the setAgreedTermsAt() method and rename it to agreeTerms(), but with no arguments. Inside say $this->agreedTermsAt = new \DateTime().

... lines 1 - 19
class User implements UserInterface
{
... lines 22 - 264
public function agreeTerms()
{
$this->agreedTermsAt = new \DateTime();
}
}

This gives us a clean, meaningful method. In SecurityController, call that: $user->agreeTerms().

... lines 1 - 57
if (true === $form['agreeTerms']->getData()) {
$user->agreeTerms();
}
... lines 61 - 79

Ok team, let's try this. Refresh the page. Annoyingly, I still can't see the checkbox. Let's hack that for now: add a little extra padding on this div. There it is!

Register as geordi3@theenterprise.org, password engage, hit enter, and... yes! We know the datetime column was just set correctly in the database because it's required.

Here's the big takeaway: whenever you need a field on your form that doesn't exist on your entity, there may be a clever solution. But, if it's not obvious, make the field unmapped and add a little bit of glue code in your controller that does whatever you need.

Later, we'll discuss a third option: creating a custom model class for your form.

Fixing your Fixtures

Before we move on, try to reload the fixtures:

php bin/console doctrine:fixtures:load

It... explodes! Duh! I made the new agreedTermsAt field required in the database, but forgot to update it in the fixtures. No problem: open UserFixture. In the first block, add $user->agreeTerms(). Copy that, and do the same for the admin users.

... lines 1 - 9
class UserFixture extends BaseFixture
{
... lines 12 - 18
protected function loadData(ObjectManager $manager)
{
$this->createMany(10, 'main_users', function($i) use ($manager) {
... lines 22 - 24
$user->agreeTerms();
... lines 26 - 41
});
... line 43
$this->createMany(3, 'admin_users', function($i) {
... lines 45 - 48
$user->agreeTerms();
... lines 50 - 56
});
... lines 58 - 59
}
}

Cool! Try it again:

php bin/console doctrine:fixtures:load

And.... all better!

Next: let's fix the styling in our registration form by creating our very own form theme.

Leave a comment!

9
Login or Register to join the conversation
Oliver W. Avatar
Oliver W. Avatar Oliver W. | posted 1 year ago

Hi,
I am having trouble accessing the values of checkboxes. I am using a group of checkboxes - surrounded by a fieldset. In this fieldset every checkbox has the same name but each has an individual id and value. Trying to fetch the values via $request->query->get('nameofcheckbox'); I only get one of the values. But I need them all. All the values are stated in the URL but only the last one makes its way to my controller.
What am I doing wrong?
Thx
Oliver

Reply

Hey Oliver,

Hm, this sounds like you're using radio buttons instead of checkboxes. Usually, radio buttons allow you to select only one item from the list at the same time. But checkboxes allows you to select more than one item from the list at the same time. Are you sure you're using checkboxes and not radio buttons? Btw, could you show the code responsible for that form field in your form type? Probably you need to add a few specific options for them to work properly.

Cheers!

Reply
Oliver W. Avatar
Oliver W. Avatar Oliver W. | victor | posted 1 year ago

Hi Victor,

I am using CHECKboxes!

My code in the show.html.twig

            <form>
<div class="input-group mb-3">
<input type="text" name="q" class="form-control" value="{{ app.request.query.get('q') }}" placeholder="finde in Bezeichnung, Bemerkung, Menge und Wareneingang (tt.mm.jjjj) ...">
<div class="input-group-append">
<button type="submit" class="btn btn-outline-secondary">

</button>
</div>
</div>
</form>
</div>
<div class="rechts">
<button class="btn btn-outline-secondary btn-filterung"></button>
<div id="kategorieauswahl">
<form>
<div class="input-group mb-3">
<div class="kategorieauswahl">
<fieldset>
{% for kategorie in kategorien %}
<div class="auswahlelement">
<input type="checkbox" name="auswahl-kategorien" id="{{ kategorie.id }}" value="{{ kategorie.name }}"> {{ kategorie.name }}
</div>
{% endfor %}
</fieldset>
<div class="input-group-kategorie">
<button type="submit" class="btn btn-outline-secondary btn-ein">

</button>
</div>
<div class="input-group-kategorie">
<button type="submit" class="btn btn-outline-secondary btn-aus">

</button>
</div>
</div>
</div>
</form>

As you can see, I am using two forms. The selections I need come from the second one.

My code from the controller:

        //retrieve possible form variables
$q = $request->query->get('q');
$auswahlkategorien = $request->query->get('auswahl-kategorien');


if ($auswahlkategorien) {
$queryBuilder = $repository->getWithSearchForCategories("Dekoration");


$pagination = $paginator->paginate(
$queryBuilder,
$request->query->getInt('page', 1),
10
);
} else {
$queryBuilder = $repository->getWithSearchQueryBuilder($q);

$pagination = $paginator->paginate(
$queryBuilder,
$request->query->getInt('page', 1),
10
);
}

This $auswahlKategorien is the variable that shows the value of just ONE checkbox instead of all selected.

Thx
Oliver

Reply
Oliver W. Avatar

aaaaargh, I got it: I should choose different names for each checkbox!!! Thx

Reply

Hey Oliver!

Sorry for the slow reply! Ah, I missed the fact you create those checkboxes manually. Symfony usually takes care about unique names, but if you add extra fields manually in the template - you should care about unique names.

So yes, you're correct, the field name should be unique for all checkboxes otherwise the last will always overwrite the previous ones. But the radio buttons should work the reverse, they should have the same name because you can only choose the one option from the list.

Well done! I'm happy you were able to solve this problem yourself!

Cheers!

Reply
Lijana Z. Avatar
Lijana Z. Avatar Lijana Z. | posted 2 years ago

mapped => false might help me a lot when I dont know how to solve. Hopefully I will remember about this option :)

Reply

Hey Lijana Z.

It might be easier for you to remember it if you only remember that your form doesn't have to map all of its fields to a class

Cheers!

Reply
Akavir S. Avatar
Akavir S. Avatar Akavir S. | posted 3 years ago

Hello Knp !

Is it possible to call services from entity ? Like repository or encode password ?

Cheers !

Reply

Hey Akavir S.

And the answer is no! But why do you need such functionality?

Cheers!

1 Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-time-bundle": "^1.8", // 1.8.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.2.1
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.1.6
        "symfony/console": "^4.0", // v4.1.6
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/form": "^4.0", // v4.1.6
        "symfony/framework-bundle": "^4.0", // v4.1.6
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.1.6
        "symfony/serializer-pack": "^1.0", // v1.0.1
        "symfony/twig-bundle": "^4.0", // v4.1.6
        "symfony/validator": "^4.0", // v4.1.6
        "symfony/web-server-bundle": "^4.0", // v4.1.6
        "symfony/yaml": "^4.0", // v4.1.6
        "twig/extensions": "^1.5" // v1.5.2
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.1.6
        "symfony/dotenv": "^4.0", // v4.1.6
        "symfony/maker-bundle": "^1.0", // v1.8.0
        "symfony/monolog-bundle": "^3.0", // v3.3.0
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.1.6
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.1.6
    }
}