Supposons que notre projet possède ces 2 entités Doctrine :
<?php
//src/Entity/Car
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* [@ORM](https://github.com/ORM)\Entity
*/
class Car
{
/**
* [@ORM](https://github.com/ORM)\Id
* [@ORM](https://github.com/ORM)\Column(type="integer")
* [@ORM](https://github.com/ORM)\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* [@ORM](https://github.com/ORM)\Column(type="string", length="255")
*/
protected $name;
/**
*
* [@ORM](https://github.com/ORM)\Column(type="date")
* [@Assert](https://github.com/Assert)\DateTime()
*/
protected $purchaseDate;
/**
*
* [@ORM](https://github.com/ORM)\ManyToOne(targetEntity="Category")
* [@ORM](https://github.com/ORM)\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
/**
*
* [@ORM](https://github.com/ORM)\Column(type="boolean")
*/
protected $active;
//Getters and Setters
//...
}
<?php
//src/Entity/Category
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* [@ORM](https://github.com/ORM)\Entity
*/
class Category
{
/**
* [@ORM](https://github.com/ORM)\Id
* [@ORM](https://github.com/ORM)\Column(type="integer")
* [@ORM](https://github.com/ORM)\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* [@ORM](https://github.com/ORM)\Column(type="string", length="255")
*/
protected $name;
public function __toString()
{
return $this->name;
}
//Getters and Setters
//...
}
Nous devons créer une classe contrôleur héritant la classe abstraite Ecommit\CrudBundle\Controller\AbstractCrudController.
Notre classe doit surcharger les méthodes abstraites :
<?php
//src/Controller/MyCrudController
namespace App\Controller;
use App\Entity\Car;
use Ecommit\CrudBundle\Controller\AbstractCrudController;
use Ecommit\CrudBundle\Crud\Crud;
use Symfony\Component\Routing\Annotation\Route;
class MyCrudController extends AbstractCrudController
{
protected function getCrudOptions(): array
{
$em = $this->getDoctrine()->getManager();
$queryBuilder = $em->createQueryBuilder();
$queryBuilder->from(Car::class, 'c1')
->select('c1, c2')
->innerJoin('c1.category', 'c2');
$crudConfig = $this->createCrudConfig('my_crud'); //Passé en argument: Nom du CRUD
$crud->addColumn(['id' => 'id', 'alias' => 'c1.id', 'label' => 'Id'])
->addColumn(['id' => 'name', 'alias' => 'c1.name', 'label' => 'Name'])
->addColumn(['id' => 'category', 'alias' => 'c2.name', 'label' => 'Category', 'alias_search' => 'c2.id'])
->addColumn(['id' => 'purchase_date', 'alias' => 'c1.purchaseDate', 'label' => 'Purchase date')
->addColumn(['id' => 'active', 'alias' => 'c1.active', 'label' => 'Active')
->setQueryBuilder($queryBuilder)
->setMaxPerPage([2, 5, 10], 5)
->setDefaultSort('id', Crud::ASC)
->setRoute('my_crud_ajax')
->setPersistentSettings(true); //Enregistre les paramètres d'affichage en base de données. Par défaut: false
return $crudConfig->getOptions();
}
protected function getTemplateName(string $action): string
{
return sprintf('my_crud/%s.html.twig', $action);
}
/**
* [@Route](https://github.com/Route)("/my-crud", name="my_crud")
*/
public function crudAction()
{
return $this->getCrudResponse();
}
/**
* [@Route](https://github.com/Route)("/my-crud/ajax", name="my_crud_ajax")
*/
public function ajaxCrudAction()
{
return $this->getAjaxCrudResponse();
}
}
Explications de getCrudOptions():
$this->createCrudConfig('my_crud'), nous créons une configuration de CRUD (instance de Ecommit\CrudBundle\Crud\CrudConfig) dont le nom est my_crud.addColumn(). Cette méthode prend en paramètre un tableau d'options :
aliasaliassetMaxPerPage(). Cette méthode prend 2 paramètres :
setDefaultSort. Cette méthode prend 2 paramètres :
Crud::ASC ou Crud::DESC). RequissetRoute la route Ajax utilisée pour mettre à jour notre listesetPersistentSettings l'enregistrement des propriétés d'affichage des utilisateurs en base de données. Par défaut, désactivé.{# templates/my_crud/index.html.twig #}
{% extends 'layout.html.twig' %}
{% block content %}
<div id="crud_list">
{% include 'my_crud/list.html.twig' %}
</div>
{% endblock %}
{# templates/my_crud/list.html.twig #}
{% if crud.paginator %}
<div>
<table class="result">
<thead>
<tr>
{{ crud_th('id', crud) }}
{{ crud_th('name', crud) }}
{{ crud_th('category', crud) }}
{{ crud_th('purchase_date', crud) }}
{{ crud_th('active', crud) }}
</tr>
</thead>
<tbody>
{% for car in crud.paginator %}
<tr>
{{ crud_td('id', crud, car.id) }}
{{ crud_td('name', crud, car.name) }}
{{ crud_td('category', crud, car.category.name) }}
{{ crud_td('purchase_date', crud, car.purchaseDate|date('Y-m-d')) }}
{{ crud_td('active', crud, (car.active) ? 'yes' : 'no') }}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="info-pagination">
{{ 'pagination_count_results'|trans({'count': crud.paginator.countResults}) }}<br />
{{ 'pagination_indices'|trans({'first': crud.paginator.firstIndice, 'last': crud.paginator.lastIndice}) }} -
{{ 'pagination_pages'|trans({'page': crud.paginator.page, 'lastPage': crud.paginator.lastPage}) }}
</div>
{{ crud_paginator_links(crud) }}
{% endif %}
{{ crud_display_settings(crud) }}
Vous pouvez ajouter par exemple les traductions suivantes (+intl-icu) à votre projet :
# translations/messages+intl-icu.fr.yaml
pagination_count_results: >
{count, plural,
=0 {Pas de résultat}
one {1 résultat trouvé}
other {# résultats trouvés}
}
pagination_indices: Résultats {first}-{last}
pagination_pages: Page {page}/{lastPage}
La classe Searcher représente les champs du formulaire de recherche.
Si vous ne désirez pas activer le formulaire de recherche sur le CRUD, passez ce paragraphe.
Notre classe doit hériter de Ecommit\CrudBundle\Form\Searcher\AbstractSearcher (ou implémenter Ecommit\CrudBundle\Form\Searcher\SearcherInterface) :
<?php
//src/Form/Searcher/CarSearcher
namespace App\Form\Searcher;
use App\Entity\Category;
use Ecommit\CrudBundle\Crud\SearchFormBuilder;
use Ecommit\CrudBundle\Form\Filter as Filter;
use Ecommit\CrudBundle\Form\Searcher\AbstractSearcher;
class CarSearcher extends AbstractSearcher
{
public $id;
public $name;
public $nameEmpty;
public $purchaseBeginningDate;
public $purchaseEndDate;
public $category;
public $active;
public function buildForm(SearchFormBuilder $builder, array $options): void
{
//La méthode addFilter a 3 arguments:
// - Le nom du filtre = Nom de la propriété dans cette classe (créer dans cette classe une données membre public ou avec des getters / setters
// - Le type du filtre à utiliser
// - Un tableau d'options (facultatif)
//Le lien entre un filtre et une donnée membre de cette classe se fait par le 1er argument de la méthode addFilter.
//Par défaut, le lien entre un filtre et une colonne du CRUD se fait par le 1er argument de la méthode addFilter.
//Si le nom du filtre n'est pas identique à l'ID d'une colonne du CRUD, on peut spécifier pour chaque filtre l'ID de
//la colonne du CRUD associée (voir exemples ci-dessous pour les filtres "nameEmpty", "purchaseBeginningDate" et "purchaseEndDate")
$builder->addFilter('id', Filter\IntegerFilter::class, [
'comparator' => Filter\IntegerFilter::EQUAL,
]);
$builder->addFilter('name', Filter\TextFilter::class, [
'must_begin' => true,
]);
$builder->addFilter('nameEmpty', Filter\NullFilter::class, [
//On ne souhaite pas relier le filtre "nameEmpty" à la colonne "nameEmpty" du CRUD (qui n'existe pas).
//On précise donc manuellement l'ID de la colonne
'column_id' => 'name',
'type_options' => [
'label' => 'Name empty',
],
]);
$builder->addFilter('purchaseBeginningDate', Filter\DateFilter::class, [
'comparator' => Filter\DateFilter::GREATER_EQUAL,
'column_id' => 'purchase_date',
'type_options' => [
'label' => 'Purchase date - From'
],
]);
$builder->addFilter('purchaseEndDate', Filter\DateFilter::class, [
'comparator' => Filter\DateFilter::SMALLER_EQUAL,
'column_id' => 'purchase_date',
'type_options' => [
'label' => 'Purchase date - To'
],
]);
$builder->addFilter('active', Filter\BooleanFilter::class);
$builder->addFilter('category', Filter\EntityFilter::class, [
'class' => Category::class,
'multiple' => 'true',
]);
}
}
REMARQUE: La liste des différents filtres disponibles (ainsi que leurs configurations) est disponible ici
REMARQUE: Il est aussi possible de faire des recherches plus complexes sans utiliser les filtres pré-définis. En savoir plus
Une fois la classe Searcher créée, nous devons modifier notre contrôleur :
<?php
//src/Controller/MyCrudController
namespace App\Controller;
//...
+ use App\Form\Searcher\CarSearcher;
class MyCrudController extends AbstractCrudController
{
protected function getCrudOptions(): array
{
//...
$crudConfig = $this->createCrudConfig('my_crud'); //Passé en argument: Nom du CRUD
$crudConfig->addColumn(['id' => 'id', 'alias' => 'c1.id', 'label' => 'Id'])
//...
->setRoute('my_crud_ajax')
+ ->createSearchForm(new CarSearcher())
//...
return $crudConfig->getOptions();
}
}
Nous devons ensuite créer un nouveau template Twig :
{# templates/my_crud/search.html.twig #}
{{ crud_search_form_start(crud) }}
{{ form_row(crud.searchForm.id) }}
{{ form_row(crud.searchForm.name) }}
{{ form_row(crud.searchForm.nameEmpty) }}
{{ form_row(crud.searchForm.purchaseBeginningDate) }}
{{ form_row(crud.searchForm.purchaseEndDate) }}
{{ form_row(crud.searchForm.active) }}
{{ form_row(crud.searchForm.category) }}
<div>
{{ crud_search_form_submit(crud) }}
{{ crud_search_form_reset(crud) }}
</div>
</form>
Et enfin modifier le template principal :
{# templates/my_crud/index.html.twig #}
{% extends 'layout.html.twig' %}
{% block content %}
<div id="crud_search">
{% include 'my_crud/search.html.twig' %}
</div>
<div id="crud_list">
{% include 'my_crud/list.html.twig' %}
</div>
{% endblock %}
REMARQUE: En savoir plus sur la configuration du formulaire de recherche
How can I help you explore Laravel packages today?