Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

configureCrud()

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

If you look at the index page of any of the crud sections, it doesn't sort by default. It just... lists things in whatever order they come out of the database. It would nice to change that: to sort by a specific column whenever we go to a section.

So far, we've talked about configuring assets, fields and actions. But "how a crud section sorts"... doesn't fall into any of these categories. Nope, for the first time, we need to configure something on the "crud section" as a whole.

Go to one of your crud controllers and open its base class. We know that there are a number of methods that we can override to control things... and we've already done that for many of these. But we have not yet used configureCrud(). As its name suggests, this is all about controlling settings on the entire crud section.

And just like with most of the other methods, we can override this in our dashboard controller to make changes to all crud sections, or override it in one specific crud controller.

Sorting All Crud Sections

Let's see if we can set the default sorting across our entire admin to sort by id descending. To do that, open DashboardController and, anywhere inside, go to Code -> Generate - or command+N on a Mac - select override methods and choose configureCrud().

... lines 1 - 21
class DashboardController extends AbstractDashboardController
{
... lines 24 - 58
public function configureCrud(): Crud
{
... lines 61 - 64
}
... lines 66 - 77
}

The Crud object has a bunch of methods on it... including one called setDefaultSort(). That sounds handy! Pass that the array of the fields we want to sort by. So, id set to DESC.

... lines 1 - 58
public function configureCrud(): Crud
{
return parent::configureCrud()
->setDefaultSort([
'id' => 'DESC',
]);
}
... lines 66 - 79

Back over at the browser, when we click on "Questions"... beautiful! By default, it now sorts by id. Really, all sections now sort by id.

Sorting Differently in One Section

And what if we want to sort Questions in a different way than the default? I bet you already know how we would do that. So let's make things more interesting. Every Question is owned by a User. What if we wanted to show the questions whose users are enabled first? Can we do that?

First, since we want to only apply this to the questions section, we need to make this change inside of QuestionCrudController. Go to the bottom and... same thing: override configureCrud()... and call the exact same method as before: setDefaultSort(). Now we can say, askedBy - that's the property on Question that is a relation to User - askedBy.enabled. Set this to DESC.

And then, after sorting by enabled first, sort the rest by createdAt DESC.

... lines 1 - 14
class QuestionCrudController extends AbstractCrudController
{
... lines 17 - 21
public function configureCrud(Crud $crud): Crud
{
return parent::configureCrud($crud)
->setDefaultSort([
'askedBy.enabled' => 'DESC',
'createdAt' => 'DESC',
]);
}
... lines 30 - 65
}

Let's check it! Click "Questions". Because we're sorting across multiple fields, you don't see any header highlighted as the currently-sorted column. But... it looks right, at least based on the "Created At" dates.

To know for sure, click the database link on the web debug toolbar. Then... search this page for "ORDER BY". There it is! Click "View formatted query". And...yes! It's ordering by user.enabled and then createdAt. Pretty cool.

Disabling Sorting on a Field

So we now have default sorting... though the user can, of course, still click any header to sort by whichever column they want. But sometimes, sorting by a specific field doesn't make sense! You can see that it's already disabled for "answers".

And if we go over to... let's see... the Users section, there's also no sort for the avatar field, which also seems reasonable.

If you want to control this a bit further, you can. Like, let's pretend that we don't want people sorting by the name of the question. This is something we can configure on the field itself.

In QuestionCrudController, for the name field, call setSortable(false).

... lines 1 - 30
public function configureFields(string $pageName): iterable
{
... lines 33 - 41
yield Field::new('name')
->setSortable(false);
... lines 44 - 65
}
... lines 67 - 68

And... just like that, the arrow is gone.

Inlining Controls for an Admin Section

Before we move on - because there isn't a ton that we need to control on the CRUD-level, let me show you one more thing. Head to the Topics section. This entity is really simple... so we have plenty of space here to show these fields.

Normally, the "actions" on the index page are hidden under this dropdown. But, we can render them inline.

To do that, head to TopicCrudController... go down... and override configureCrud(). On the Crud object, call ->showEntityActionsInlined().

... lines 1 - 10
class TopicCrudController extends AbstractCrudController
{
... lines 13 - 17
public function configureCrud(Crud $crud): Crud
{
return parent::configureCrud($crud)
->showEntityActionsInlined();
}
... lines 23 - 39
}

That's it. Now... yea! That looks better.

Next: I want to talk about using a what-you-see-is-what-you-get editor. There's actually a simple one built into Easy Admin. But we're going to go further and install our own. Doing that will require some custom JavaScript.

Leave a comment!

20
Login or Register to join the conversation
mofogasy Avatar
mofogasy Avatar mofogasy | posted 1 month ago

Hi, I have a complex (for me) problem:
In my project I have an entity with latitude, longitude and address properties. I use leaflet to enable completing these field by clickin on a map.
So I use


public function configureCrud(...)...
{
return $crud
->overrideTemplate('...mapZone.html.twig');
}


since each field depends on the map.

First I don't know if it is a good approach for my problem.

How to pass the data in my mapZone.html.twig ? How can I save my input from the twig ?
Should I use a form ? In that case, where can I create it to render it ? How to validate the form when submitted ?
Should I use a standard controller to use a form ? If so, how to connect it with the crudcontroller ?

Reply

Hey mofogasy!

I think what you basically want to do is:

A) Create a Stimulus controller that will handle initializing leaflet and triggering the functionality. This would contain the code where, in click, you find the 3 fields (latitude, longitude and address) and fill in their values.

B) For those 3 fields, I think there is no reason for the user to see them, so I would make them each a HiddenField. To make your life easier in step (A) (specifically, finding the 3 fields), you could make each of them a "target" for your controller, like we do here for the question field: https://symfonycasts.com/sc...

C) To initialize the Stimulus controller, hmm. I think (?) you could use https://symfony.com/bundles... to add an attr option to your form:


->setFormOptions([
'attr' => ['data-controller' => 'your-controller-name'],
])

I believe that will cause the data-controller attribute to be added to your form tag, which will initialize your controller. That Stimulus controller will now initialize leaflet, which will handle setting the hidden fields on click.

D) How to handle validation? Well, first, my guess is that validation won't happen very often: if a user is clicking a map, then latitude, longitude and address should be "correct" for normal users. But, of course, we should still validate just in case. To do this, I would probably use the Callback validation on your entity - https://symfony.com/doc/cur... - that allows you to write some custom code that has access to all of the fields... in case you want to look at them all at once. If there are any problems, just fail, but don't call ->atPath(). If you don't call ->atPath(), then the error should be attached to the "top level" of the form. So even though the fields are hidden, an error will show up at the top of the form.

Let me know if that helps!

Cheers!

Reply
mofogasy Avatar

Well, I could not find the a way to custom each collection item. I am stuck
thanks

Reply

Sorry I couldn't help more - your previous reply landed while I was on vacation, and this stuff is deep and complex.

Reply
mofogasy Avatar

don't worry,
for now i'll try with javascript DOM first. My code will be trash but I ll keep it until I find out where I can customize collectionfield

Reply
mofogasy Avatar

Hi, I manage to progress a bit.
I found a very convinient file for my problem : vendor/easycorp/easyadmin-bundle/src/Resources/views/crud/new.html.twig
I pasted this file's content in mapZone.html.twig.
Now, I have the map and the appropriate form on the page.
With only javascript I can dymanically fill the inputs when I click on the map :

document.getElementById("ZoneTournee_points_reperes_latitude").value = event.latlng.lat

Now I am moving to the next level. In fact I need to add several markers at a time. I have AdresseRepere entity which represent the locations, it's related to ZoneTournee entity which is a collection of AdresseRepere (it's an area/polygon).

My ZoneTourneeCrudController has

yield CollectionField::new('points_reperes')
->setEntryType(AdresseRepereType::class)
->addCssClass("numtournee");

but now I am a bit limited with javascript tricks: by inspecting the browser , I could manage to catch "add new item" button and trigger it when I click on the map :


document.querySelector('.form-group.field-collection .field-collection-add-button').click();

Since this class is generated by easyadmin, do you have a tip to make it cleaner (like adding id attribute to this "add new item" button) ?
Filling works great.
But removing is still in process.
I cannot access a specific "remove the item" button since they all have same class. I have to get the child of nextsibling of the button's parent to get a numbered item attribute which can be tough.

Do you have a trick to get esasyadmin to add index attribute (id) to the global div of a new collection field item ?

Reply
mofogasy Avatar

Hi, thank you for the answer.
Well, I kinda dislike stimulus because it rarely gives feedback when there's something wrong in the code. And it messes other webpack modules: for example, bootstrap dropdown stopped working because of my stimulus. But I couldn't specify the cause because no error.
I ll definitly give it a try. I 'll give my solution later if it works

Reply
Christophe R. Avatar
Christophe R. Avatar Christophe R. | posted 3 months ago

Hello, Is it possible to access the Entity when editing in configureCrud() to set a special help info?
The idea is to display a different message with the type of my Entity (image, video, text)
->setHelp('edit', 'Template.Help.'.$entity->getType()

Reply

Hey Julien,

I see your point... well, that's not something that easy to do. As you see, the signature of configureCrud() does not allow to inject the current entity object, but you probably could try to do some tricks to do what you want. First of all, you can override some templates to add more custom code base on some conditions, like entity type, etc. Overriding templates might be the most correct way usually.

But if it does not fit you, we can try to get access to the AdminContextProvider service in order to get the AdminContext from it. I believe you can do it with $this->container->get(AdminContextProvider::class)->getContenxt(). With AdminContext you can get the current entity with $adminContext->getEntity()->getInstance(). But probably you even don't need the actual entity because getEntity() gives you the entityDto that contains a lot of useful information that might be enough for you, you can try to dump it first.

I hope this helps!

Cheers!

Reply
Christophe R. Avatar
Christophe R. Avatar Christophe R. | victor | posted 3 months ago

Ok thank you for your quick answer. Maybe create a custom template for edit should be better indeed. i wanted to be sure there is no other easy way^^

Reply

Hey Julien,

No easy way, but there might be some workarounds I mentioned :) Good luck with template overriding!

Cheers!

Reply
Nick F. Avatar

I think sorting by the number of objects in a collection field should be the default setting, but is there a simple way to implement that without making a custom filter or creating a completely separate database column on the owning entity just to store the collection count?

Reply

Hey Nick F.!

Hmm, that's actually a great question - I'm not sure. Even in SQL, this is a bit trickier, as it requires a JOIN over to the collection table, then a COUNT... on that collection. My guess (?) is that there is not a simple way to do this. I'd try 'COUNT(articles)' => 'DESC'.... but I don't feel lucky enough to expect that to work :p.

Otherwise, you could always take manual control over the "index query builder" - e.g. https://symfonycasts.com/sc... - and then add the order by manually. You'd still need to figure out how to do it correctly with the query builder, but that should be possible. Here's some info - https://stackoverflow.com/q... - specifically solution 3 on that answer is, I think, what you'd need. Use ->addSelect('COUNT(something.id) AS HIDDEN mycount') to "add" the count into the select.

Let me know if that helps :).

Cheers!

Reply
Tomáš S. Avatar
Tomáš S. Avatar Tomáš S. | posted 6 months ago

Hello,

It is possible for specific EntityCrudController change on Crud::PAGE_INDEX a button text for Action::NEW?

For my specific entity better named button "Create Entity" instead of "Add Entity"

Thank you

Reply

Hey Tomas,

Yes, you can... you need to rename the translation for that button. The original translation is located in "src/Resources/translations/EasyAdminBundle.en.php" of the bundle code, see: 'new' => 'Add %entity_label_singular%' there. So, basically you need to override that translation in your code, the full key of that translation method should be "action.new".

Cheers!

Reply
Tomáš S. Avatar

Hello Victor,

thank you for your help. This probably changes button label on whole application. I want to change only on specific EntityCrudController. There is word "Create" more appropriate than word "New".

Regards!

Reply

Hey Tomas,

in that case, you can override the label directly on the configureFields() method of your controller.
Cheers!

Reply
Tomáš S. Avatar

Hello Diego,

Thank you for help me. I thing the method configureFields is for specifying which field should be displayed and how. I have inspected source code of this method and I dont know how to do. Could you please be more specific how to rename Action Button for creating new record on a specific CRUD page?

Cheers!

Reply

Oh, sorry, I got confused. In the configureActions() method, you can do something like this


$actions->getAsDto(Crud::PAGE_NEW)
->getAction(Crud::PAGE_INDEX, Action::NEW)
->setLabel('New label here');
Reply
Tomáš S. Avatar

Hey Diego,

It is working! Your are awesome.

Here is complete method which I have and works



public function configureActions(Actions $actions): Actions
{
$actions
->getAsDto(Crud::PAGE_NEW)
->getAction(Crud::PAGE_INDEX, Action::NEW)
->setLabel('__ea__action.create');

return parent::configureActions($actions)
->remove(Crud::PAGE_INDEX, Action::DELETE)
->remove(Crud::PAGE_INDEX, Action::EDIT);
}

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": ">=8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.4
        "doctrine/doctrine-bundle": "^2.1", // 2.5.5
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.2.1
        "doctrine/orm": "^2.7", // 2.10.4
        "easycorp/easyadmin-bundle": "^4.0", // v4.0.2
        "handcraftedinthealps/goodby-csv": "^1.4", // 1.4.0
        "knplabs/knp-markdown-bundle": "dev-symfony6", // dev-symfony6
        "knplabs/knp-time-bundle": "^1.11", // 1.17.0
        "sensio/framework-extra-bundle": "^6.0", // v6.2.5
        "stof/doctrine-extensions-bundle": "^1.4", // v1.7.0
        "symfony/asset": "6.0.*", // v6.0.1
        "symfony/console": "6.0.*", // v6.0.2
        "symfony/dotenv": "6.0.*", // v6.0.2
        "symfony/flex": "^2.0.0", // v2.0.1
        "symfony/framework-bundle": "6.0.*", // v6.0.2
        "symfony/mime": "6.0.*", // v6.0.2
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/runtime": "6.0.*", // v6.0.0
        "symfony/security-bundle": "6.0.*", // v6.0.2
        "symfony/stopwatch": "6.0.*", // v6.0.0
        "symfony/twig-bundle": "6.0.*", // v6.0.1
        "symfony/ux-chartjs": "^2.0", // v2.0.1
        "symfony/webpack-encore-bundle": "^1.7", // v1.13.2
        "symfony/yaml": "6.0.*", // v6.0.2
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.7
        "twig/twig": "^2.12|^3.0" // v3.3.7
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.1
        "symfony/debug-bundle": "6.0.*", // v6.0.2
        "symfony/maker-bundle": "^1.15", // v1.36.4
        "symfony/var-dumper": "6.0.*", // v6.0.2
        "symfony/web-profiler-bundle": "6.0.*", // v6.0.2
        "zenstruck/foundry": "^1.1" // v1.16.0
    }
}