Skip to content

Commit eb4ef22

Browse files
committed
[Docs] Export Grid data to CSV cookbook
1 parent c03f39f commit eb4ef22

File tree

13 files changed

+420
-2
lines changed

13 files changed

+420
-2
lines changed

.github/workflows/ci.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ jobs:
6060
${{ steps.composer-cache.outputs.dir }}
6161
key: ${{ github.run_id }}-${{ runner.os }}-${{ hashFiles('composer.json') }}-symfony-${{ matrix.symfony }}
6262

63+
- name: 'Remove portphp/csv if needed'
64+
if: matrix.symfony == '^8.0'
65+
run: composer remove portphp/csv --no-update
66+
6367
- name: "Install dependencies"
6468
run: composer update --no-interaction --no-scripts
6569

app/Entity/Book.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use App\Grid\BookGrid;
1717
use App\Repository\BookRepository;
18+
use App\Responder\ExportGridToCsvResponder;
1819
use Doctrine\ORM\Mapping as ORM;
1920
use Sylius\Component\Resource\Annotation\SyliusCrudRoutes;
2021
use Sylius\Component\Resource\Model\ResourceInterface;
@@ -40,6 +41,11 @@
4041
template: '@SyliusAdminUi/crud/index.html.twig',
4142
shortName: 'withoutGrid',
4243
),
44+
new Index(
45+
shortName: 'export',
46+
responder: ExportGridToCsvResponder::class,
47+
grid: BookGrid::class,
48+
),
4349
new Delete(),
4450
new BulkDelete(),
4551
new Show(),

app/Grid/BookGrid.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace App\Grid;
1515

1616
use App\Entity\Book;
17+
use Sylius\Bundle\GridBundle\Builder\Action\Action;
1718
use Sylius\Bundle\GridBundle\Builder\Action\CreateAction;
1819
use Sylius\Bundle\GridBundle\Builder\Action\DeleteAction;
1920
use Sylius\Bundle\GridBundle\Builder\Action\ShowAction;
@@ -55,6 +56,12 @@ public function buildGrid(GridBuilderInterface $gridBuilder): void
5556
->addActionGroup(
5657
MainActionGroup::create(
5758
CreateAction::create(),
59+
Action::create(name: 'export', type: 'export')
60+
->setOptions([
61+
'link' => [
62+
'route' => 'app_admin_book_export',
63+
],
64+
]),
5865
),
5966
)
6067
->addActionGroup(
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace App\Responder;
15+
16+
use Pagerfanta\PagerfantaInterface;
17+
use Port\Csv\CsvWriter;
18+
use Sylius\Component\Grid\Definition\Field;
19+
use Sylius\Component\Grid\Renderer\GridRendererInterface;
20+
use Sylius\Component\Grid\View\GridViewInterface;
21+
use Sylius\Resource\Context\Context;
22+
use Sylius\Resource\Metadata\Operation;
23+
use Sylius\Resource\State\ResponderInterface;
24+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
25+
use Symfony\Component\HttpFoundation\StreamedResponse;
26+
use Symfony\Contracts\Translation\TranslatorInterface;
27+
use Webmozart\Assert\Assert;
28+
29+
final class ExportGridToCsvResponder implements ResponderInterface
30+
{
31+
public function __construct(
32+
#[Autowire(service: 'sylius.grid.renderer')]
33+
private readonly GridRendererInterface $gridRenderer,
34+
private readonly TranslatorInterface $translator,
35+
) {
36+
}
37+
38+
/**
39+
* @param GridViewInterface $data
40+
*/
41+
public function respond(mixed $data, Operation $operation, Context $context): mixed
42+
{
43+
Assert::isInstanceOf($data, GridViewInterface::class);
44+
45+
$response = new StreamedResponse(function () use ($data) {
46+
$output = fopen('php://output', 'w');
47+
48+
if (false === $output) {
49+
throw new \RuntimeException('Unable to open output stream.');
50+
}
51+
52+
$writer = new CsvWriter();
53+
$writer->setStream($output);
54+
55+
$fields = $this->sortFields($data->getDefinition()->getFields());
56+
$this->writeHeaders($writer, $fields);
57+
$this->writeRows($writer, $fields, $data);
58+
59+
$writer->finish();
60+
});
61+
62+
$response->headers->set('Content-Type', 'text/csv; charset=UTF-8');
63+
$response->headers->set('Content-Disposition', 'attachment; filename="export.csv"');
64+
65+
return $response;
66+
}
67+
68+
/**
69+
* @param Field[] $fields
70+
*/
71+
private function writeHeaders(CsvWriter $writer, array $fields): void
72+
{
73+
$labels = array_map(fn (Field $field) => $this->translator->trans($field->getLabel()), $fields);
74+
75+
$writer->writeItem($labels);
76+
}
77+
78+
/**
79+
* @param Field[] $fields
80+
*/
81+
private function writeRows(CsvWriter $writer, array $fields, GridViewInterface $gridView): void
82+
{
83+
/** @var PagerfantaInterface $paginator */
84+
$paginator = $gridView->getData();
85+
Assert::isInstanceOf($paginator, PagerfantaInterface::class);
86+
87+
for ($currentPage = 1; $currentPage <= $paginator->getNbPages(); ++$currentPage) {
88+
$paginator->setCurrentPage($currentPage);
89+
$this->writePageResults($writer, $fields, $gridView, $paginator->getCurrentPageResults());
90+
}
91+
}
92+
93+
/**
94+
* @param Field[] $fields
95+
* @param iterable<object> $pageResults
96+
*/
97+
private function writePageResults(CsvWriter $writer, array $fields, GridViewInterface $gridView, iterable $pageResults): void
98+
{
99+
foreach ($pageResults as $resource) {
100+
$rows = [];
101+
foreach ($fields as $field) {
102+
$rows[] = $this->getFieldValue($gridView, $field, $resource);
103+
}
104+
$writer->writeItem($rows);
105+
}
106+
}
107+
108+
private function getFieldValue(GridViewInterface $gridView, Field $field, object $data): string
109+
{
110+
$renderedData = $this->gridRenderer->renderField($gridView, $field, $data);
111+
$renderedData = str_replace(\PHP_EOL, '', $renderedData);
112+
113+
return trim(strip_tags($renderedData));
114+
}
115+
116+
/**
117+
* @param Field[] $fields
118+
*
119+
* @return Field[]
120+
*/
121+
private function sortFields(array $fields): array
122+
{
123+
$sortedFields = $fields;
124+
125+
uasort($sortedFields, fn (Field $fieldA, Field $fieldB) => $fieldA->getPosition() <=> $fieldB->getPosition());
126+
127+
return $sortedFields;
128+
}
129+
}

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"laminas/laminas-stdlib": "^3.18",
2323
"pagerfanta/doctrine-orm-adapter": "^4.6",
2424
"pagerfanta/twig": "^4.6",
25+
"portphp/csv": "^2.0",
2526
"sylius/grid-bundle": "^1.13 || ^1.15@alpha",
2627
"sylius/resource-bundle": "^1.13 || ^1.14@alpha",
2728
"symfony/asset": "^6.4 || ^7.4 || ^8.0",
@@ -109,7 +110,7 @@
109110
},
110111
"extra": {
111112
"symfony": {
112-
"require": "8.0.*"
113+
"require": "7.4.*"
113114
}
114115
},
115116
"scripts": {

config/packages/sylius_grid.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
sylius_grid:
22
templates:
3+
action:
4+
export: 'shared/grid/action/export.html.twig'
35
filter:
46
'App\Grid\Filter\SpeakerFilter': '@SyliusBootstrapAdminUi/shared/grid/filter/entity.html.twig'
81 KB
Loading

docs/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* [Customizing the page titles](cookbook/admin_panel/page_titles.md)
1515
* [Customizing the metatags](cookbook/admin_panel/metatags.md)
1616
* [Using autocompletes](cookbook/admin_panel/using-autocompletes.md)
17+
* [Exporting grid data](cookbook/admin_panel/grid_export.md)
1718
* [How to use in a DDD architecture](cookbook/ddd_architecture.md)
1819
* [Architecture overview](cookbook/ddd_architecture/overview.md)
1920
* [Resource configuration](cookbook/ddd_architecture/resource_configuration.md)

docs/cookbook/admin_panel.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
* [Customizing the page titles](admin_panel/page_titles.md)
99
* [Customizing the metatags](admin_panel/metatags.md)
1010
* [Using autocompletes](admin_panel/using-autocompletes.md)
11-
11+
* [Exporting grid data](admin_panel/grid_export.md)

0 commit comments

Comments
 (0)