CRUD "Produit"....


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

Le contrôleur : gestion des produits

création du contrôleur ProduitV1Controller


symfony console make:controller ProduitV1Controller  --no-template


namespace App\Controller\Admin;

affichage des produits

le contrôleur

Dans le contrôleur :

    /**
     * @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  

la vue

{% 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' => '']
];

mise en place de style

<!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>



En cas de problème, vider le cache

symfony console cache:clear
{#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>

le modèle : créer 2 tables

Privilé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

créer une entité TypeProduit


Utiliser la commande ci-dessous pour créer une entité :

symfony console make:entity TypeProduit
    /**
     * @ORM\Column(type="string", length=255)
     */
    private $libelle;
public function __toString()
{
    return $this->getLibelle();
}

créer une entité Produit


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;
    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

ajouter des enregistrements : les fixtures


    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();
        }
    }
symfony console doctrine:fixtures:load

ATTENTION : les erreurs classiques


affichage de cette table dans le contrôleur


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")}}


Ajouter un enregistrement

<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 %} > &nbsp;
    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 %}
    {
        // 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;
    }

supprimer un enregistrement

{
        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]);
    }
<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>







afficher les détails des produits par catégorie

    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]);
    }

autre exemple de fonctions d'agrégation





Modifier un enregistrement

Exercice 1 :


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 :




voici les routes

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                     

amélioration

Le chef de projet vous demande de changer les noms des routes :


/**
 * @Route(name="admin_", path="/admin")
 */

utilisation de messages "flash"

Essayer d'ajouter des messages "flash" lors de la suppression de la création ou de la modification d'un produit

....
$this->addFlash('info_produit','produit ajouté !');

....
$this->addFlash('info_produit','produit supprimé !');

....
$this->addFlash('info_produit','produit modifié !');
{% for message in app.flashes('info_produit') %}
    <div class="alert alert-info">
        <strong>Info!</strong> {{ message }}
    </div>
{% endfor %}



ANNEXES :

contrôleur : show

contrôleur : show

contrôleur : add

contrôleur : add

contrôleur : delete (suppression d'un enregistrement), details

contrôleur : delete (suppression d'un enregistrement), details

contrôleur : edit (modifier d'un enregistrement)

contrôleur : edit (modifier d'un enregistrement)

contrôleur : validator

contrôleur : validator