Login to bookmark this video
Buy Access to Course
10.

Forms Without a Data Class

|

Share this awesome video!

|

Keep on Learning!

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

Login Subscribe

We're almost at the finish line of our journey with Symfony Forms. But before we wrap it up, let's dive into something both fun and practical. If you navigate to the /parts page, you'll see a simple search input. It's a basic HTML form, and that's all good. But here's a thought: can we recreate this using Symfony Forms? You bet!

Creating a Form Without an Entity

When we discuss Symfony Forms, we usually link them to an entity or a data class. However, this isn't set in stone. Symfony Forms can stand alone, without any object mapping happening behind the scenes. All the form data is neatly stored in plain PHP arrays. Our search form is a great example of this.

Sure, we could build this form right in the controller using the form builder. But to keep things neat and tidy, let's stick with the form type again. Plus, we'll pick up a few handy tricks along the way.

Head to your terminal and run the following command:

symfony console make:form

Name the form PartSearchType. This time around, when it asks for an entity or data class, leave it blank. Next up, locate the generated PartSearchType in the src/Form directory and open it.

You'll see a placeholder field named field_name:

25 lines | src/Form/PartSearchType.php
// ... lines 1 - 8
class PartSearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('field_name')
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
// Configure your form options here
]);
}
}

Swap that out with query to match the name of the legacy search input:

25 lines | src/Form/PartSearchType.php
// ... lines 1 - 8
class PartSearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('query')
;
}
// ... lines 17 - 23
}

Using the Form in the Controller

Now, head over to the index() action in src/Controller/PartController.php. At the start, create a form with $this->createForm() passing PartSearchType::class and store it in a variable named $searchForm. When rendering the template below, pass it as an additional parameter with 'searchForm' => $searchForm:

28 lines | src/Controller/PartController.php
// ... lines 1 - 4
use App\Form\PartSearchType;
// ... lines 6 - 11
final class PartController extends AbstractController
{
// ... line 14
public function index(StarshipPartRepository $repository, Request $request,): Response
{
$searchForm = $this->createForm(PartSearchType::class);
// ... lines 18 - 21
return $this->render('part/index.html.twig', [
// ... line 23
'searchForm' => $searchForm,
]);
}
}

In templates/part/index.html.twig, render the whole form with {{ form(searchForm) }}:

50 lines | templates/part/index.html.twig
// ... lines 1 - 4
{% block body %}
<div class="flex justify-end mt-6 mb-6">
<div class="relative w-full max-w-md">
<form method="get" action="{{ path('app_part_index') }}">
// ... lines 10 - 18
</form>
{{ form(searchForm) }}
</div>
</div>
// ... lines 24 - 48
{% endblock %}

For now, let's keep the original so that we can compare them. We'll tidy this up later.

After refreshing your browser, you'll notice two search inputs. The new kid on the block has a label, while the old one is label-free. Let's sort that out first.

Hiding the Form Field Label

Back in PartSearchType, for the query field, pass null for the type, and an array for options. Within it, add the label option. You can set this to any string you want the label to be. Or, just set it to false. This instructs Symfony not to render the label at all:

27 lines | src/Form/PartSearchType.php
// ... lines 1 - 8
class PartSearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('query', null, [
'label' => false,
])
;
}
// ... lines 19 - 25
}

While we're here, let's polish things up a bit. Add the attr option for attributes, and inside that, add placeholder set to Search... to match the legacy form. On the next line, class. Grab the CSS classes from the original form and paste them here:

31 lines | src/Form/PartSearchType.php
// ... lines 1 - 8
class PartSearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('query', null, [
'label' => false,
'attr' => [
'placeholder' => 'Search...',
'class' => 'w-full p-3 pl-10 border border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500',
],
])
;
}
// ... lines 23 - 29
}

Jump back to your browser and refresh the page. It should now be a spitting image of the legacy search field, minus the search icon, which we'll take care of later.

If you try to submit the form now, you'll notice it uses the POST method, which is the default behavior. However, our search form uses GET, which is more fitting for a search feature.

What's Next?

Next, we'll switch the form method from POST to GET and learn how to manage it properly in the controller.