Chapters
40 Chapters
|
4:19:16
|
Login to bookmark this video
-
Course Code
Subscribe to download the code!
Subscribe to download the code!
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
38.
A Global "Export" Action
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Subscribe to jump to this part in the video!
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.
This tutorial is built on Symfony 6 but works great on Symfony 7!
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": ">=8.2.0",
"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.4.5
"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
}
}
41 Comments
Hey GonzaloAlonsoD,
Thank you for sharing this code snippet! Could you also add a bit more context to it? :)
Cheers!
Hola Victor, si claro.
Con el código que adjunte, reutilizo los servicios en lugar de agregar el constructor como en el ejemplo:
-- en:
Hi Victor, yes of course.
With the code I attached, I reuse the services instead of adding the constructor like in the example:
Hey GonzaloAlonsoD,
I see! Yeah, another possible solution, thanks for sharing :)
Cheers!
I have error like this: :(
Attempted to call an undefined method named "get" of class "App\Controller\EasyAdmin\QuestionCrudController".
Did you mean to call e.g. "getEntityFqcn" or "getSubscribedServices"?
In class QuestionCrudController() on export() method
Did i have bad
Hey Lechu,
The
get()method belongs to the container property, not the controller itself. Try doing this$this->container->get(...);Cheers!
Hello,
this code uses filters, seraches and orders, but how can we use filds configured in the INDEX.
In your example, you have ID, Name, Topic... fields, but in the CSV only entity fields apear.
We can manually use "$queryBuilder->select(" to peronalize it, but how can we use the $fields in columns?
Thank you!
Hey @Txurdi!
Sorry for the VERY slow reply. This was a tricky question, but fun to figure out. Out goal is, basically, to make a CSV export that looks exactly like what the user sees on the screen: same fields and same values. Here's the solution. The only caveat is that, because we're fully-hydrating entity objects, this won't work with large data sets (even the previous solution would need extra work to function with large data):
First,
CsvExporter:We now query for entity objects. THEN we process those through the
EntityFactoryin the same way that EasyAdmin does when rendering the index page. This allows us to reuse all of its normal logic for getting the value of a field.The
export()controller action only changes slightly:There are only 2 new things here, both marked. The first helps "trick" the
EntityFactoryso that when it's processing the fields, it correctly only shows the fields that appear onPAGE_INDEX. The second thing is where we pass a, sort of, emptyEntityDtoinstance that represents the current entity into theCsvExporter. This is, inside ofCsvExporter, needed so thatEntityFactorycan work its magic.Try it out and let me know what you think - I really like this solution!
Cheers!
Yes, thank you so much for this! I ran into a new issue with collection fields being truncated to 32 characters in the CSV file, which I didn't want. I got around that by adding
to the export() controller action to trick the formatCollection() method into truncating at 512 characters instead of 32. There's probably a better way to accomplish that...
I've had to deal with a few tricky fields where the formatted value was not correct in the csv. If you know the property name of the tricky field, you can just add a conditional like this within the fieldDto loop.
Then instead of
getFormattedValue(), you usegetValue()and customize the formatting.Ah good catch and nice solution! I'm not sure if there's a better solution or not - in this newer version of the code, we're purposely rendering all of the fields exactly like EasyAdmin would render them, including truncating. So, changing to DETAIL mode is an interesting work around. Otherwise, all I can think to do would be to add custom code into
CsvExporterthat would avoid using->getFormattedValue()for just some fields... but that sounds a little yucky.Cheers!
Thank you very much!
It's working like a charm!
Hello SymfonyCasts,
I have a question about choosing the best way to export CSV (and other file formats in Symfony).
This tutorial uses Goodby-csv library but Symfony has a Serializer component and a CsvEncoder class that can do similar jobs.
Why use Goodby-csv instead of either of these libraries that could convert entities into a Csv format?
Thank you.
Hey Colin,
That's a good question. You could create your own CSV exporter service by leveraging the Serializer component, but you would end up reinventing the wheel. The
Goodby-csvlibrary seems to be pretty good managing memory and has a few other convenient features. You can give it a check here https://github.com/handcraftedinthealps/goodby-csvCheers!
Hello! Is it possible to create the batch action without confirmation?
Hi Sergey-P!
As far as I can tell, no. The attributes that trigger that modal are built into the batch system - https://github.com/EasyCorp/EasyAdminBundle/blob/c0bde19c9ec3ee97e379b1c9f7d6d99e253eb7cf/src/Factory/ActionFactory.php#L147-L156 and the actual code that submits the batch actions form is built into the "submit" button on that modal - https://github.com/EasyCorp/EasyAdminBundle/blob/c0bde19c9ec3ee97e379b1c9f7d6d99e253eb7cf/assets/js/app.js#L291-L318
So, pretty sure the confirmation is required.. unless you did some serious craziness with JavaScript where you try to "undo" the modal and reimplement the form submit manually.
Cheers!
Hello, it seems that there is a problem to export the entities which have a property of type array json.
I just tried with my User entity which has the "roles" property. This causes a fatal error (other people seem to have had the same problem).
Would there be something to do to deal with it?
EDIT : Ok, I've the fix. Just encode to JSON if it's an array :
thank <3
Thanks for posting that Tien dat L.! I'm not surprised that our custom logic was a bit limited: it worked for our use-case, but obviously wasn't tested for JSON fields!
Cheers!
Hi, and I got error in Source code Finish/QuestionCrudController with : Call to a member function getAsDto() on null
Sorry I'm new to Symfony ...
fixed in configureActions() in
update(Crud::PAGE_INDEX, ACTION::DELETE, static function (Action $action) {
$action->displayIF() {};
add return $action;
}
pls update in Source code Finish
Hey Tien dat L.!
Sorry about that! You are 100% correct! I do this correctly in the video - https://symfonycasts.com/sc... - but the code got out of sync! I'm fixing that right now. I appreciate you mentioning it!
Cheers!
Hi, with export() function but got error :
Die Website ist nicht erreichbar Die Webseite unter https://localhost/de/admin?crudAction=export&crudControllerFqcn=App%5CController%5CAdmin%5CUserCrudController&menuIndex=1&signature=-OO_KGwT7k6xB-A_nK1ch5IA65UGj0_cz70VL58vX84&submenuIndex=-1 ist eventuell vorübergehend nicht verfügbar oder wurde dauerhaft an eine neue Webadresse verschoben.
Do there with EA 4.0.10, how can i fix, here is my code
public function configureActions(Actions $actions): Actions
{
$exportAction = Action::new('export')
->linkToUrl(function () {
$request = $this->requestStack->getCurrentRequest();
return $this->adminUrlGenerator->setAll($request->query->all())
->setAction('export')
->generateUrl();
})
->addCssClass('btn btn-success')
->setIcon('fa fa-download')
->createAsGlobalAction();
return parent::configureActions($actions)
->add(Crud::PAGE_INDEX, $exportAction)
}
public function export(AdminContext $context, CsvExporter $csvExporter)
{
$fields = FieldCollection::new($this->configureFields(Crud::PAGE_INDEX));
$context->getCrud()->setFieldAssets($this->getFieldAssets($fields));
$filters = $this->container->get(FilterFactory::class)->create($context->getCrud()->getFiltersConfig(), $fields, $context->getEntity());
$queryBuilder = $this->createIndexQueryBuilder($context->getSearch(), $context->getEntity(), $fields, $filters);
return $csvExporter->createResponseFromQueryBuilder($queryBuilder, $fields, 'user.csv');
}
I had the same error for an entity and it was because it had a json array type property (User::roles).
I feel like the service can't handle this kind of property
I know you're already seen it Fabrice, but for others, @kiuega posted a nice solution for this problem! https://symfonycasts.com/sc...
Hi weaverryan thank, i have other question,
now dql like " SELECT entity FROM App\Entity\Question entity " how can i custom export fields like
" SELECT id, name, slug FROM App\Entity\Question entity "
Hey Tien dat L.!
It sounds like you want to export different fields that what is exporting currently, correct? Right now, the export exports every field on Question. That's what the "SELECT entity" means: it says "grab ALL the data from the Question" entity. Then, we loop over that in CsvExporter and export everything.
If you want to only export a sub-set of fields, you could modify the query builder to only select those:
You could put that in your code right BEFORE you pass the
$queryBuilderto CsvExporter. This will change the query to only return some fields... and then the exporter will only export those fields.Cheers!
weaverryan thanks
Hello, with the export() function, there an issue with
$filters = $this->get(FilterFactory::class)With :
Attempted to call an <b>undefined method named "get"</b> of class "App\Controller\AdminQuestionCrudController".
Do there is someting new with EA 4.1?
Here is my fix, If it can help:
<b>Another tips with the CsvExporter,</b> if you have ArrayCollection of value, you can easely change the begin of the "public function createResponseFromQueryBuilder" with something like :
Hi @Ryan and @Victor
Thanks for sharing this useful feature.
I am using easy-admin and leagueCsv bundle in one of project so i have tried to make it works with league instead of Goodby here's a gist :
https://gist.github.com/lwillems/bdb662b58a0a48bc4b0d91a28434c6adI was wondering when you are calling :
$result = $queryBuilder->getQuery()->getArrayResult();Full dataset is loaded into memory ? whould it works with huge collection of entities ? to improve memory usage i have tried to use toIterable() instead, the negative side is the usage of 1 more query to get headers WDYT ?
Regards
Hey Laurent!
Awesome! Thank you for posting that!
You're absolutely correct - the way we wrote the code doesn't work with a huge data set. Huge datasets are definitely more complex :). Does simply changing to
toIterable()help? I would have thought that this still would load everything into memory - it would still have one huge query to get all the data (which it would then need to store in memory). But perhaps I'm wrong andtoIterable()helps?Cheers!
Use
$this->container->get(FilterFactory::class)Good catch on that! I was blindly copying and pasting - I should have noticed that!
The full story: in Symfony 6, the
$this->get()was removed. It's not normally something you should use at all anymore. But for (good) technical reasons, EasyAdmin still grabs things directly out of a container (technically a "service locator"). Anyways,$this->get()was a shortcut for$this->container->get(), which is why the fix shared by Fabrice works. On the latest EasyAdmin (both v3 and v4), the code has already been refactored to use$this->container->get(). We used just an old enough EasyAdmin in this tutorial to still have the old code :). Oh, and your solution of autowiring FilterFactory is also AWESOME - very clever solution!Side note: this IS correct in the video (I was using a new enough version of EasyAdmin), but wrong in the code (which was using a slightly older version). I'm fixing that right now thanks to these comments!
And thanks for posting your improvement to
CsvExporter!Cheers!
Hello ! This is just an awesome feature ! It should be part of EasyAdmin official repository, why not submit a PR ?
Hey Kiuega,
Thank you for your feedback about this feature. IIRC the bundle has/had an opened issue about it, but it requires a complex implementation and may have some edge cases with the other existent features... so it was a low-priority feature because of this. Feel free to find a related issue about it to get more info, and if the issue is still releveant and wasn't closed for some reasons I'm not aware of - please, feel free to submit a PR! It might be so that maintainers just do not have enough time to work on this feature.
Cheers!
Salut! Hope everything is going ok!
I would be grateful if you could please provide me with the code of EXPORT function. it didn't work with me! I know the tutoril is not ready but i should finish my project tomorrow! Thanks again and again for your understanding.
All the best,
Hey Lubna
I see it's really necessary for you, I can't just share it here, but if you have access to downloaded code, you can look into the
finish/directory from course code archiveCheers!
Thanks, I downloaded it!
Hi again! Sorry if I am bothering you. I would be thankful and grateful if you could please save my career by launching this tutorial. My manger asked me again to add this feature and it still does not work with me. :'( :'(
Hey Lubna
I'm afraid this video will be released within a few days. There isn't much I can do, but you can download the source code as Vladimir said, and since there is the script content released already, you can follow it along. I hope it helps
Cheers!
Thanks! no worries if I lost my current job I will apply for a new one at SensioLabs.
"Houston: no signs of life"
Start the conversation!