Préparation : Créer un nouveau projet S3_tp2
symfony new S3tp2 --no-git --full
cd S3tp2
composer require orm-fixtures --dev
composer require admin
symfony console make:controller ProduitV1Controller --no-template
src/Controller/Admin
ProduitV1Controller
ci-dessus dans le dossiersrc/Controller/Admin
namespace App\Controller\Admin;
Dans le contrôleur :
produit_show
/**
* @Route("/produit", name="produit_index", methods={"GET"})
* @Route("/", name="produit_index2", methods={"GET"})
*/
public function index()
{
return $this->redirectToRoute('produit_show');
}
/**
* @Route("/produit/show", name="produit_show", methods={"GET"})
*/
public function showProduits(Request $request)
{
$produits=NULL;
return $this->render('admin/produit/showProduits.html.twig', ['produits' => $produits]);
}
use Symfony\Component\HttpFoundation\Request; // objet REQUEST
use Symfony\Component\HttpFoundation\Response; // objet RESPONSE
{% extends "base.html.twig" %}
{% block body %}
<div class="container">
<div class="row">
<a href="{#{ path('produit_add') }#}" class="btn btn-primary m-1 p-1"> Ajouter un produit </a>
</div>
<div>
<table class="table">
<caption>Recapitulatifs des produits</caption>
<thead class="thead-dark">
<tr><th>nom</th><th>id</th><th>type</th><th>prix</th><th>nom photo</th><th>photo</th>
<th>opération</th>
</tr>
</thead>
<tbody>
{% if produits is not empty %}
{% for produit in produits %}
<tr>
<td>{{produit.nom}} {# produit.getNom()#} </td>
<td>{{produit.id}}</td><td>{{produit.typeProduit.libelle | default('pas de type')}}</td><td>{{produit.prix}} €</td><td>
{{produit.photo}}</td><td>
<img style="width:40px;height:40px" src="{{asset('images/')}}{{produit.photo}}" alt="image du produit" >
</td>
<td>
<a href="{#{ path('produit_edit', {id: produit.id}) }#}" class="btn btn-primary">modifier</a>
<form action="{#{ path('produit_delete') }#}" method="POST" style="display:inline">
<input type="hidden" name="token" value="{#{ csrf_token('produit_delete') }#}">
<input type="hidden" name="id" value="{{ produit.id }}">
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="btn btn-warning">Supprimer</button>
</form>
</td>
</tr>
{% endfor %}
{% else %}
<tr class="table-warning"><td>Pas de Produit</td></tr>
{% endif %}
</tbody>
</table>
</div>
</div>
{% endblock %}
$produits = [
['id' => 1,'nom' => 'Enveloppes (50p)', 'prix' => '2', 'typeProduit' => 'Fourniture de bureau', 'photo' => ''],
['id' => 2,'nom' => 'Stylo noir', 'prix' => '1', 'typeProduit' => 'Fourniture de bureau', 'photo' => ''],
['id' => 3,'nom' => 'Boite de rangement', 'prix' => '3', 'typeProduit' => 'Fourniture de bureau', 'photo' => ''],
['id' => 4,'nom' => 'Chaise', 'prix' => '40', 'typeProduit' => 'Mobilier', 'photo' => ''],
['id' => 5,'nom' => 'Tables', 'prix' => '200', 'typeProduit' => 'Mobilier', 'photo' => '']
];
/public
template/admin/layout.html.twig
et copier le code ci-dessous :<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>S3 web</title>
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('assets/css/bootstrap.css') }}" >
{% endblock %}
</head>
<body>
{#{% include('admin/_nav.html.twig') %}#}
{% block body %}
<h2 style="color:blue">layout.html.twig</h2>
{% endblock %}
{% block javascripts %}
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="{{ asset('assets/js/jquery-3.5.1.slim.min.js') }}"></script>
<script src="{{ asset('assets/js/popper.min.js') }}" ></script>
<script src="{{ asset('assets/js/bootstrap.js') }}" ></script>
{% endblock %}
</body>
</html>
template/admin/produit/showProduits.html.twig
le fichier dont hérite : base.html.twig
par {% extends "admin/layout.html.twig" %}
En cas de problème, vider le cache
symfony console cache:clear
template/admin/_nav.html.twig
et copier le code ci-dessous :{#https://getbootstrap.com/docs/4.0/components/navbar/#}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">TP web <span style="color:#ff0000;"> Menu Admin </span></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" id="DropdownCommandes" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Commandes
</a>
<div class="dropdown-menu" aria-labelledby="DropdownCommandes">
<a class="dropdown-item" href="{#{ path('gestion_commande_show') }#}">Gestions des commandes</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" id="DropdownClient" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Clients
</a>
<div class="dropdown-menu" aria-labelledby="DropdownClients">
<a class="dropdown-item" href="">Gestions des clients</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" id="DropdownEvenements" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Produits
</a>
<div class="dropdown-menu" aria-labelledby="DropdownEvenements">
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{#{ path('produit_show') }#}">Gestion des produits</a>
<a class="dropdown-item" href="{#{ path('produit_details') }#}"> Détails des types de produit </a>
</div>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<!-- Authentication Links -->
{% if is_granted('IS_AUTHENTICATED_FULLY') %}
<span class="navbar-text">
Bonjour {{ app.user.username }}
{% if is_granted('ROLE_ADMIN') %} (role : Admin )
{% elseif is_granted('ROLE_CLIENT') %} (role : Client ) {% endif %}
</span>
<a class="btn btn-outline-success" type="button" href="{{path('logout') }}">
Se déconnecter
</a>
{% else %}
<a class="btn btn-outline-success" type="button" href="{#{path('login') }#}">
Se connecter
</a>
<a class="btn btn-outline-success" type="button" href="{#{path('changePassword') }#}">
mot de passe oublié
</a>
{% endif %}
</ul>
</div>
</nav>
layout.html.twig
sur l'instruction qui inclue la barre de navigation et testerPrivilégier l'utilisation de l'assistant pour créer les entités
DATABASE_URL=mysql://votreLogin:votreMotDePasse@localhost:3306/BDD_votreLogin
# DATABASE_URL=mysql://votreLogin:votreMotDePasse@serveurmysql:3306/BDD_votreLogin
Utiliser la commande ci-dessous pour créer une entité :
symfony console make:entity TypeProduit
/**
* @ORM\Column(type="string", length=255)
*/
private $libelle;
__toString
public function __toString()
{
return $this->getLibelle();
}
Utiliser la commande ci-dessous pour créer une entité :
symfony console make:entity Produit
/**
* @ORM\Column(type="string", length=255)
*/
private $nom;
/**
* @ORM\Column(type="decimal", precision=8, scale=2, nullable=true)
*/
private $prix;
/**
* @ORM\Column(type="date", nullable=true)
*/
private $dateLancement;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $photo;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $stock;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
private $disponible;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\TypeProduit", inversedBy="produits")
*/
private $typeProduit;
__toString
public function __toString()
{
return $this->getNom();
}
symfony console make:entity --regenerate
## copier le schéma dans la base de données (convertir les entités en table)
symfony console doctrine:schema:update --dump-sql
symfony console doctrine:schema:update --force
# éventuellement si la base de données n'existe pas encore
symfony console doctrine:database:create
Pour ajouter quelques enregistrements dans la table, utiliser les fixtures qui permettent d’ajouter des enregistrements dans une table
compléter le fichier de fixtures : src/DataFixtures/AppFixtures.php : Ajouter dans les fixtures une méthode pour ajouter les types de produits et les produits.
private function loadTypeProduits(ObjectManager $manager)
{
$typesProduits = [
['id' => 1,'libelle' => 'Fourniture de bureau'],
['id' => 2,'libelle' => 'Mobilier'],
['id' => 3,'libelle' => 'Mobilier Jardin'],
['id' => 4,'libelle' => 'Arrosage'],
['id' => 5,'libelle' => 'Outils'],
['id' => 6,'libelle' => 'Divers']
];
foreach ($typesProduits as $type)
{
$type_new = new TypeProduit();
$type_new->setLibelle($type['libelle']);
echo $type_new."\n";
$manager->persist($type_new);
$manager->flush();
}
}
private function loadProduits(ObjectManager $manager)
{
$produits = [
[ 'nom' => 'Enveloppes (50p)', 'prix' => '2', 'stock' => '3', 'disponible' => true, 'typeProduit' => 'Fourniture de bureau', 'photo' => null],
[ 'nom' => 'Stylo noir', 'prix' => '1', 'stock' => '13', 'disponible' => false, 'typeProduit' => 'Fourniture de bureau', 'photo' => 'stylo.jpeg'],
[ 'nom' => 'Boite de rangement', 'prix' => '3', 'stock' => '12', 'disponible' => true, 'typeProduit' => 'Fourniture de bureau', 'photo' => 'boites.jpeg'],
[ 'nom' => 'Chaise', 'prix' => '40', 'stock' => '3', 'disponible' => true, 'typeProduit' => 'Mobilier', 'photo' => 'chaise.jpeg'],
[ 'nom' => 'Tables', 'prix' => '200', 'stock' => '3', 'disponible' => true, 'typeProduit' => 'Mobilier', 'photo' => 'table.jpeg'],
[ 'nom' => 'Salon de Jardin alu', 'prix' => '149', 'stock' => '3', 'disponible' => true, 'typeProduit' => 'Mobilier Jardin', 'photo' => 'salonJardin2.jpg'],
[ 'nom' => 'Table+6 fauteuilles de Jardin', 'prix' => '790', 'stock' => '3', 'disponible' => true, 'typeProduit' => 'Mobilier Jardin', 'photo' => 'tableFauteuilsJardin1.jpg'],
[ 'nom' => 'Set Table + 4 bancs', 'prix' => '229', 'stock' => '3', 'disponible' => true, 'typeProduit' => 'Mobilier Jardin', 'photo' => 'setTableChaises.jpg'],
[ 'nom' => 'arrosoir bleu', 'prix' => '13.50', 'stock' => '3', 'disponible' => true, 'typeProduit' => 'Arrosage', 'photo' => 'arrosoir1.jpg'],
[ 'nom' => 'arrosoir griotte', 'prix' => '9.90', 'stock' => '3', 'disponible' => true, 'typeProduit' => 'Arrosage', 'photo' => 'arrosoir2.jpg'],
[ 'nom' => 'tuyau arrosage', 'prix' => '31.90', 'stock' => '3', 'disponible' => true, 'typeProduit' => 'Arrosage', 'photo' => 'tuyauArrosage1.jpg'],
[ 'nom' => 'tournevis', 'prix' => '23.90', 'stock' => '3', 'disponible' => true, 'typeProduit' => 'Outils', 'photo' => 'lotTourneVis.jpg'],
[ 'nom' => 'marteau menuisier', 'prix' => '7.80', 'stock' => '3', 'disponible' => true, 'typeProduit' => 'Outils', 'photo' => 'marteau.jpg'],
[ 'nom' => 'pince multiprise', 'prix' => '21.80', 'stock' => '3', 'disponible' => true, 'typeProduit' => 'Outils', 'photo' => 'pinceMultiprise.jpg'],
[ 'nom' => 'perceuse', 'prix' => '149.80', 'stock' => '3', 'disponible' => true, 'typeProduit' => 'Outils', 'photo' => 'perceuse.jpg'],
];
foreach ($produits as $produit)
{
$new_produit = new Produit();
$new_produit->setNom($produit['nom']);
$new_produit->setPrix($produit['prix']);
$new_produit->setPhoto($produit['photo']);
$new_produit->setStock($produit['stock']);
if($produit['stock'] <10)
$new_produit->setDateLancement(\DateTime::createFromFormat('Y-m-d','2020-10-19'));
else
$new_produit->setDateLancement(Null);
$new_produit->setDisponible($produit['disponible']);
$type_produit = $manager->getRepository(TypeProduit::class)->findOneBy(["libelle" => $produit['typeProduit']] );
if($type_produit != null)
$new_produit->setTypeProduit($type_produit);
echo $new_produit."\n";
$manager->persist($new_produit);
$manager->flush();
}
}
appeler ces 2 méthodes dans la fonction load en passant le manager en paramètre
charger les fixtures dans la base de données (attention : ceci vide toutes les tables)
symfony console doctrine:fixtures:load
ATTENTION : les erreurs classiques
doctrine:schema:update --force
use App\Entity\Entité;
$produits = $this->getDoctrine()->getRepository(Produit::class)->findBy([],['typeProduit' => 'ASC','stock' =>'ASC']);
)
Remarque : la vue récupère un objet, elle peut (dans une certaine limite) accéder à un attribut d'un objet relié : {{ produit.typeProduit.libelle | default("id Null")}}
templates/admin/produit/_formProduit.html.twig
<div class="form-group">
<label>Nom :</label>
<input name="nom" type="text" size="18" value="{{donnees.nom|default('')}}" class="form-control {% if erreurs.nom is defined %}is-invalid{% endif %}" >
{% if erreurs.nom is defined %}
<div class="invalid-feedback">{{erreurs.nom}}</div>
{% endif %}
</div>
<div class="form-group">
<label>Type :</label>
<select name="typeProduit_id" class="form-control {% if erreurs.typeProduit_id is defined %}is-invalid{% endif %}">
{% if donnees.typeProduit.id is not defined or donnees.typeProduit.id is empty %}
<option value="">Veuillez sélectionner un produit</option>
{% endif %}
{% for typeProduit in typeProduits %}
<option value="{{ typeProduit.id }}"
{% if donnees.typeProduit.id is defined and typeProduit.id == donnees.typeProduit.id %}selected{% endif %}
{% if donnees.typeProduit_id is defined and typeProduit.id == donnees.typeProduit_id %}selected{% endif %}
>
{{ typeProduit.libelle }}
</option>
{% endfor %}
</select>
{% if erreurs.typeProduit_id is defined %}
<div class="invalid-feedback">{{erreurs.typeProduit_id}}</div>
{% endif %}
</div>
<div class="form-group">
<label>Prix :</label>
<input name="prix" type="text" size="18" value="{{donnees.prix|default('')}}" class="form-control {% if erreurs.prix is defined %}is-invalid{% endif %}">
{% if erreurs.prix is defined %}
<div class="invalid-feedback">{{erreurs.prix}}</div>
{% endif %}
</div>
<div class="form-group">
<label>Stock :</label>
<input name="stock" type="text" size="18" value="{{donnees.stock|default('')}}" class="form-control {% if erreurs.stock is defined %}is-invalid{% endif %}">
{% if erreurs.stock is defined %}
<div class="invalid-feedback">{{erreurs.stock}}</div>
{% endif %}
</div>
<div class="form-group">
<label>Photo :</label>
<input name="photo" type="text" size="18" value="{{donnees.photo|default('')}}" class="form-control {% if erreurs.photo is defined %}is-invalid{% endif %}">
{% if erreurs.photo is defined %}
<div class="invalid-feedback">{{erreurs.photo}}</div>
{% endif %}
</div>
<div class="form-group">
<label>date Lancement :</label>
<input name="dateLancement" type="text" size="18"
value="{{ (dateLancement is defined) and (dateLancement is not null) ? dateLancement|date("d/m/Y") : donnees.dateLancement | default('') }}"
class="form-control {% if erreurs.dateLancement is defined %}is-invalid{% endif %}">
{% if erreurs.dateLancement is defined %}
<div class="invalid-feedback">{{erreurs.dateLancement}}</div>
{% endif %}
</div>
<div class="form-group">
<label>Disponible :</label>
oui<input name="disponible" value="true" type="radio" {% if donnees.disponible is not defined or donnees.disponible != "false" %} checked {% endif %} >
non <input name="disponible" value="false" type="radio" {% if donnees.disponible is defined and donnees.disponible == "false" %} checked {% endif %} >
</div>
{% if button is defined and button == 'Modifier' %}
<input type="hidden" name="_method" value="PUT">
<input type="hidden" name="id" value="{{ donnees.id | default('')}}">
{% endif %}
<input type="hidden" name="token" value="{{ csrf_token('form_produit') }}">
<button type="submit" class="btn btn-primary">{{ button|default('Valider') }}</button>
{% extends "admin/layout.html.twig" %}
{% block body %}
<div class="container">
<form method="post" action="{{ path('produit_add') }}">
<legend>Créer un produit</legend>
{{ include('_formProduit.html.twig', {button: 'Créer'}) }}
</form>
</div>
{% endblock %}
"/produit/add"
. Autoriser les methods "GET" et "POST"
. Puis intégrer, compléter et modifier le code ci-dessous dans cette méthode : {
// A modifier : Utiliser la méthode findBy du Repository : TypeProduitRepository (trier les types de produits par libelle)
$typeProduits= null;
// fin A modifier
if($request->getMethod() == 'GET'){
return $this->render('admin/produit/addProduit.html.twig', ['typeProduits'=> $typeProduits]);
}
if(!$this->isCsrfTokenValid('form_produit', $request->get('token'))) {
throw new InvalidCsrfTokenException('Invalid CSRF token formulaire produit');
} // ne pas oublier : use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
$donnees['nom']=$_POST['nom'];
$donnees['prix']=$_POST['prix'];
$donnees['dateLancement']=$request->request->get('dateLancement');
$donnees['stock']=$request->request->get('stock');
$donnees['disponible']=$request->request->get('disponible');
$donnees['photo']=$request->request->get('photo');
$donnees['typeProduit_id']=$request->request->get('typeProduit_id'); //htmlentities htmlspecialchar
$erreurs=$this->validatorProduit($donnees);
dump($erreurs);
if( empty($erreurs))
{
// A modifier
// créer une entité Produit (instance de) et utiliser les setters de cette entité pour modifier les valeurs puis persister cette entité
// fin A modifier
return $this->redirectToRoute('produit_show');
}
// A modifier : Utiliser la méthode findBy du Repository : TypeProduitRepository (trier les types de produits par libelle)
$typeProduits= null;
// fin A modifier
return $this->render('admin/produit/addProduit.html.twig', ['donnees'=>$donnees,'erreurs'=>$erreurs,'typeProduits'=> $typeProduits]);
}
public function validatorProduit($donnees)
{
$erreurs=array();
if (! preg_match("/^[A-Za-z ]{1,}/",$donnees['nom'])) $erreurs['nom']='nom composé de 2 lettres minimum';
if(! is_numeric($donnees['prix'])) $erreurs['prix'] = 'saisir une valeur numérique';
$dateConvert=\DateTime::createFromFormat('d/m/Y',$donnees['dateLancement']);
if($dateConvert==NULL)
$erreurs['dateLancement']='la date doit être au format JJ/MM/AAAA';
else{
if($dateConvert->format('d/m/Y') !== $donnees['dateLancement'])
$erreurs['dateLancement']='la date n\'est pas valide (format jj/mm/aaaa)';
}
if(! is_numeric($donnees['stock'])) $erreurs['stock'] = 'saisir une valeur';
if (! preg_match("/[A-Za-z0-9]{2,}.(jpeg|jpg|png)/",$donnees['photo']))
$erreurs['photo']='nom de fichier incorrect (extension jpeg , jpg ou png)';
// /* if($donnees['photo'] != "")
// {
// $file = './assets/images/'.$donnees['photo'];
// if (! file_exists($file)) {
// $erreurs['photo']='la photo qui n existe pas le dossier assets/images/';
// }
// }*/
if(isset($donnees['id']) and ! is_numeric($donnees['id']) ) $erreurs['id']='type id incorrect';
if(! is_numeric($donnees['typeProduit_id'])) $erreurs['typeProduit_id'] = 'saisir une valeur';
if($donnees['disponible'] != "true" AND $donnees['disponible'] != "false") $erreurs['disponible'] = 'pb disponible';
return $erreurs;
}
"/produit/delete"
. Autoriser les methods "GET" et "DELETE"
. Puis intégrer, compléter et modifier le code ci-dessous dans cette méthode :{
if(!$this->isCsrfTokenValid('produit_deleteAmodifier', $request->get('token'))) {
throw new InvalidCsrfTokenException('Invalid CSRF token formulaire produit');
}
$entityManager = $this->getDoctrine()->getManager();
$id= $request->request->get('id');
$produit = $entityManager->getRepository(Produit::class)->find($id);
if (!$produit) throw $this->createNotFoundException('No produit found for id '.$id);
//$donnees=$entityManager->getRepository(AutreTableJointe::class)->findBy(['produit' => $produit]);
//$donnees2=$entityManager->getRepository(AutreTableJointe2::class)->findBy(['produit' => $produit]);
// if(empty($donnees)){
$entityManager->remove($produit);
$entityManager->flush();
return $this->redirectToRoute('produit_show');
// }
// else return $this->render('admin/produit/ErrorDeleteProduit.html.twig',['nombre' => $nombre]);
}
Remarque : dans le traitement du token ci-dessus, il faut adapter la valeur de l'identifiant du token avec celle mise dans le formulaire
Modifier la vue templates/produit/showProduits.html.twig
<form action="{{ path('produit_delete') }}" method="POST" style="display:inline">
<input type="hidden" name="token" value="{{ csrf_token('produit_delete') }}">
<input type="hidden" name="id" value="{{ produit.id }}">
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="btn btn-warning">Supprimer</button>
</form>
Utiliser le dossier Repository : Un "Repository" est une classe dans laquelle, on crée ses requêtes (pour les requêtes de type select un peu compliquées) avec le langage DQL (Doctrine Query Language), avec un objet QueryBuilder ou en SQL (requête paramétrée)
public function getDetailsProduits()
{
// http://symfony.com/doc/current/doctrine/associations.html#joining-related-records
// $query = $this->getEntityManager()
// ->createQuery("SELECT count(p.id) as nbProduits, avg(p.prix) as PrixMoyen, min(p.prix) as PrixMin,
// max(p.prix) as PrixMax, t.libelle
// FROM App:Produit as p
// JOIN App:TypeProduit as t
// WHERE p.typeProduit=t.id
// GROUP BY t.libelle
// ORDER BY p.nom"); // ?????????
//
// return $query->getResult();
$qb=$this->createQueryBuilder('p'); // il lui faut une lettre, i : item en base de données donc schéma
$qb->select('count(p.id) as nbProduits', 'avg(p.prix) as PrixMoyen', 'min(p.prix) as PrixMin')
->addSelect('max(p.prix) as PrixMax', 't.libelle')
->join( 'App:TypeProduit', 't')
->where('p.typeProduit=t.id')
->groupBy('t.libelle');
// ->addOrderBy('p.nom', 'ASC'); // ?????????
return $qb->getQuery()->getResult();
}
L'exemple ci-dessus calcule le nombre et le prix moyen par type de produit des produits.
/**
* @Route("/produit/detailsTypeProduit", name="produit_details", methods={"GET"})
*/
public function detailsProduit(Request $request)
{
$entityManager = $this->getDoctrine()->getManager();
$typeProduits=$entityManager->getRepository("App:Produit")->getDetailsProduits();
dump($typeProduits);
return $this->render('admin/produit/detailsTypeProduit.html.twig', ['typeProduits' => $typeProduits]);
}
Créer une vue pour afficher un tableau avec les types de produits, leur prix moyen, minimum et maximum et leur nombre.
Ajouter dans le menu de la vue layout.html.twig
du dossier admin
un lien pour afficher cette vue.
autre exemple de fonctions d'agrégation
GET
PUT
(on ré-écrit toutes les données donc plutôt PUT que PATCH)
dans cette méthode :
$produit = $entityManager->getRepository(Produit::class)->find($id);
if (!$produit) throw $this->createNotFoundException('No produit found for id '.$id);
sinon (par défaut car la vue est chargée)
Remarque :
Name Method Scheme Host Path
------------------------------ ---------- -------- ------ -----------------------------------
produit_index GET ANY ANY /produit
produit_index2 GET ANY ANY /
produit_show GET ANY ANY /produits/show
produit_add GET|POST ANY ANY /produit/add
produit_delete DELETE ANY ANY /produit/delete
admin_produit_details GET ANY ANY /produit/details/{id}
produit_edit GET ANY ANY /produit/edit/{id}
produit_edit_valid PUT ANY ANY /produit/edit
Le chef de projet vous demande de changer les noms des routes :
/admin
admin_
path('produit_
par path('admin_produit_
redirectToRoute('produit_
par et redirectToRoute('admin_produit_
/**
* @Route(name="admin_", path="/admin")
*/
symfony console debug:router
et testerEssayer d'ajouter des messages "flash" lors de la suppression de la création ou de la modification d'un produit
Il suffit de recopier la documentation : doc. symfony message flash
Ajouter dans le contrôleur dans les bonnes méthodes une ligne de code pour informer l'utilisateur qu'un produit a bien été ajouté ou supprimé :
....
$this->addFlash('info_produit','produit ajouté !');
....
$this->addFlash('info_produit','produit supprimé !');
....
$this->addFlash('info_produit','produit modifié !');
<body>
le code pour afficher tous les messages flash{% for message in app.flashes('info_produit') %}
<div class="alert alert-info">
<strong>Info!</strong> {{ message }}
</div>
{% endfor %}
ANNEXES :
contrôleur : show
contrôleur : add
contrôleur : delete (suppression d'un enregistrement), details
contrôleur : edit (modifier d'un enregistrement)
contrôleur : validator