Nous allons poursuivre notre utilisation du framework Laravel pour mettre en place le mécanisme d’authentification proposé par Laravel. Certaines utilisations et fonctions disponibles dans l’application seront conditionnées par une demande d’identification et d’authentification.
Le TP aura pour but de faire une présentation en ligne d’un catalogue de bières que les utilisateurs pourront découvrir, noter et commenter. Les utilisateurs pourront aussi ajouter de nouvelles bières.
Nous allons avoir besoin d’un outil de développement (IDE) qui va accélérer notre production de code.
L’utilisation de PHPStorm n’est pas obligatoire, mais les enseignants utiliseront ce logiciel pour illustrer leurs propos pendant les séances de TPs.
laravel
:Dans ce cas il faut dans un premier temps ajouter la commande laravel
dans votre environnement personnel (à faire une fois).
composer global require "laravel/installer"
puis ajouter la commande dans votre PATH
à l’aide de la commande :
export PATH=$HOME/.composer/vendor/bin:$PATH
Si vous ne voulez pas le faire à chaque fois que vous ouvrez un terminal, il faut ajouter la commande précédente dans votre fichier ~/.bashrc
.
La commande laravel
est maintenant disponible. Pour créer un nouveau projet Laravel, il faut utiliser la commande
laravel new tp_bieres
Si le réseau ne supporte pas la charge, demandez à l’enseignant de vous fournir l’archive de départ. Après l’avoir décompressée, vous avez un répertoire qui contient le même résultat que celui obtenu à l’aide des commandes précédentes.
Déplacez vous dans le répertoire créé et tapez la commande suivante :
Rappels:
A chaque modification du fichier de configuration
.env
, il est nécessaire de relancer le serveur pour que la modification soit prise en compte.Si
composer
ne fonctionne pas sur votre machine, il faut récupérer composer.phar à l’aide de la commande suivantecd racine/de/votre/projet curl -sS https://getcomposer.org/installer | php
- Si des classes sont ajoutées dans un nouveau namespace, il peut être utile de ré-initialiser l’autoloader
composer dump-autoload (php composer.phar dump-autoload)
Le répertoire tp_bieres
contient votre application créée à l’aide du framework Laravel. Sans aucune intervention supplémentaire, vous pouvez tester votre application à l’aide de la commande suivante.
cd /le/chemin/de/tp_bieres
php artisan serve
qui vous place dans le répertoire racine de votre application web. La commande php artisan serve
lance un serveur web qui écoute sur le port 8000
par défaut. Il va permettre de tester le résultat de votre développement. Vous pouvez tester que l’initialisation de votre projet est correcte en tapant l’URL http://localhost:8000 dans votre navigateur.
La commande
php artisan
sera très souvent utilisée lancez la commandephp artisan
pour connaitre les options possibles
Pour la suite du TP, nous allons valider les mécanismes d’authentification. Laravel propose une commande qui valide les mécanismes d’authentification par défaut.
php artisan make:auth
Cette commande réalise les modifications suivantes :
login
,inscription
(register),routes/web.php
,Mettre en place l’authentification
Tester votre application
Nous allons générer les tables et y placer des données de manière automatique. Pour cela nous allons utiliser les outils mis à disposition par Laravel.
Comme dans le TP précédent, vous devez modifier le fichier .env à la racine de votre projet pour connecter votre application avec une base de données.
Le fichier de configuration .env
permet en particulier de spécifier
Voici un extrait du fichier .env
qui concerne les informations de connexion avec un SGBD :
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=lar_bieres
DB_USERNAME=duchmol
DB_PASSWORD=secret
La création et les données de la table bieres
peuvent être récupérer à partir de l’archive bieres.sql.zip qu’il faudra décompresser avant de l’utiliser.
A l’aide de votre client SQL vous allez exécuter le fichier bieres.sql
. Le résultat ajoute un catalogue de bières belges et françaises dans une table bieres
de votre base de données. Pour chacune des bières on allouera aléatoirement un utilisateur lorsque ceux-ci auront été créés.
Il faut aussi créer la classe modèle à l’aide de la commande :
```
php artisan make:model Biere
```
Pour les autres tables
Nous allons utiliser les commandes proposées par Laravel pour optimiser la création des tables et ajouter des données dans les tables. Pour chaque table nous allons :Créer une classe modèle
php artisan make:model Commentaire
qui crée un fichier Commentaire.php
dans le répertoire app de votre projet.
Créer un script de création de la table dans la base de données.
Le script doit spécifier les colonnes (nom, type) de la table à créer.
php artisan make:migration create_commentaires_table
qui crée un fichier xxx_create_commentaires_table.php
avec xxx
une datation de la création du fichier.
Créer un fichier contenant une fonction qui fabrique un enregistrement qui sera ajouté dans la table.
Ce fichier sera placé dans le répertoire database/factories
de votre projet
Créer une classe qui va déclencher la création des enregistrements de la classe modèle dans la table de la base de données
php artisan make:seeder CommentairesTableSeeder
qui crée un fichier CommentairesTableSeeder.php
dans le répertoire database/seeds
User
La classe modèle User
existe déjà dans le répertoire app
ainsi que le script xxx_create_users_table.php
dans le répertoire database/migration
et le fichier UserFactory.php
dans le répertoire database/factories
.
Nous allons légèrement modifier le fichier xxx_create_users_table.php
en ajoutant la ligne :
le fichier UserFactory.php
nous allons ajouter la ligne
et le fichier de la classe modèle User.php
Lors de la phase d’enregistrement, nous aurons besoin de donner une valeur par défaut. Pour cela nous allons modifier le fichier app/Http/Controllers/Auth/RegisterController.php
et ajouter dans la fonction create()
la ligne
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'avatar' => 'img/avatar.png', // ligne à ajouter
'password' => Hash::make($data['password']),
]);
Cela nous permettra d’associer une image à chaque utilisateur.
Pour créer plusieurs utilisateurs, nous allons créer la classe UsersTableSeeder
dans le fichier UsersTableSeeder
à l’aide de la commande
php artisan make:seeder UsersTableSeeder
Puis remplacer le code de la fonction run()
avec celui proposé ci-dessous :
Ce qui aura comme effet de créer 20 utilisateurs.
commentaires
Pour les commentaires, il faut mettre en place toutes les commandes.
php artisan make:model Commentaire
php artisan make:migration create_commentaires_table
php artisan make:seeder CommentairesTableSeeder
Modification du fichier xxx_create_commentaires_table.php
public function up() {
Schema::create(
'commentaires',
function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('user_id')->unsigned();
$table->bigInteger('biere_id');
$table->dateTime('add_at');
$table->string('titre');
$table->longText('commentaire');
$table->timestamps();
}
);
}
public function down() {
Schema::dropIfExists('commentaires');
}
Création du fichier CommentaireFactory.php
use App\Commentaire;
use Faker\Generator as Faker;
$factory->define(Commentaire::class, function (Faker $faker) {
return [
'titre' => $faker->words(3,true),
'commentaire' => $faker->paragraphs(2, true),
'add_at' => $faker->dateTimeInInterval(
$startDate = '-6 months',
$interval = '+ 180 days',
$timezone = date_default_timezone_get()),
];
});
Modification du fichier CommentairesTableSeeder.php
public function run() {
$bieres_ids = Biere::all('id')->pluck('id')->toArray();
$users_ids = User::all('id')->pluck('id')->toArray();
$faker = Faker\Factory::create('fr_FR');
$couple = [];
for ($i = 0; $i < 1000; $i++) {
$com = [];
$com[] = $faker->randomElement($array = $users_ids);
$com[] = $faker->randomElement($array = $bieres_ids);
$couple[] = $com;
}
foreach ($couple as $com) {
factory(Commentaire::class)->create(['user_id' => $com[0], 'biere_id' => $com[1]]);
}
}
Cela va créer 1000 commentaires aléatoires.
notations
Pour les notations, il faut mettre en place toutes les commandes.
php artisan make:model Notation
php artisan make:migration create_notations_table
php artisan make:seeder NotationsTableSeeder
Modification du fichier xxx_create_notations_table.php
public function up() {
Schema::create(
'notations',
function (Blueprint $table) {
$table->bigInteger('user_id')->unsigned();
$table->bigInteger('biere_id');
$table->dateTime('add_at');
$table->tinyInteger('note');
$table->timestamps();
$table->primary(['user_id', 'biere_id']);
}
);
}
public function down() {
Schema::dropIfExists('notations');
}
Création du fichier NotationFactory.php
use App\Notation;
use Faker\Generator as Faker;
$factory->define(Notation::class, function (Faker $faker) {
return [
'note' => $faker->randomElement($array = [0, 1, 2, 3, 4, 5]),
'add_at' => $faker->dateTimeInInterval(
$startDate = '-6 months',
$interval = '+ 180 days',
$timezone = date_default_timezone_get()),
];
});
Modification du fichier NotationsTableSeeder.php
public function run() {
$bieres_ids = Biere::all('id')->pluck('id')->toArray();
$users_ids = User::all('id')->pluck('id')->toArray();
$faker = Faker\Factory::create('fr_FR');
$couple = [];
for ($i = 0; $i < 1000; $i++) {
$com = [];
$com[] = $faker->randomElement($array = $users_ids);
$com[] = $faker->unique()->randomElement($array = $bieres_ids);
$couple[] = $com;
}
foreach ($couple as $com) {
factory(Notation::class)->create(['user_id' => $com[0], 'biere_id' => $com[1]]);
}
}
Cela va créer 1000 notations aléatoires.
bieres
Comme indiqué en introduction, pour compléter le modèle, il faut associer un user
à une biere
(l’utilisateur qui a ajouté la bière dans la base de données). Pour cela nous allons créer un fichier BieresTableSeeder.php
dans le répertoire database/seeds
avec le contenu suivant :
public function run() {
$faker = Faker\Factory::create('fr_FR');
$bieres_ids = Biere::all('id')->pluck('id')->toArray();
$users_ids = User::all('id')->pluck('id')->toArray();
foreach ($bieres_ids as $biere_id) {
DB::table('bieres')
->where('id', $biere_id)
->update(['user_id' => $faker->randomElement($array = $users_ids)]);
}
}
Pour mettre en place le modèle d’information, il reste à utiliser les commandes proposées par Laravel.
La création des tables
php artisan migrate:refresh
Chargement des données dans les tables
php artisan db:seed
qui, par défaut recherche la classe DataBaseSeeder dans le répertoire database/seeds
. Nous allons la modifier pour que son appel provoque le chargement de l’ensemble des tables :
Dans le diagramme de classes qui représente notre système d’information, vous aurez noté qu’il existe des associations entre classes. En effet une bière peut être commentée plusieurs fois par des utilisateurs différents (ou pas), une bière peut être notée par plusieurs utilisateurs (une note par utilisateur). Enfin un utilisateur peut être le créateur de plusieurs bières. Lors des traitements il nous faudra travailler avec ces associations. Laravel à l’aide d’Eloquent permet de faire la liaison entre les tables SQL et les classes modèles.
User
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password','avatar',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function bieres() {
return $this->hasMany('App\Bieres');
}
}
Commentaire
<?php
namespace App;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
class Commentaire extends Model {
protected $fillable = [
'commentaire',
'add_at',
];
public function biere() {
return $this->belongsTo('App\Biere');
}
public function user() {
return $this->belongsTo('App\User');
}
public static function diff(Commentaire $commentaire) {
$dt = Carbon::now();
$dc = Carbon::instance(new \DateTime($commentaire->add_at));
return $dc->diffForHumans($dt);
}
public function __toString() {
return sprintf("id: %d titre: %s commentaire: %s biere: %s user: %s", $this->id, $this->titre, $this->commentaire, $this->biere->nom, $this->user->name);
}
}
Notation
Biere
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Biere extends Model {
public $timestamps = false;
public function commentaires() {
return $this->hasMany('App\Commentaire');
}
public function notations() {
return $this->hasMany('App\Notation');
}
public function user() {
return $this->belongsTo('App\User');
}
}
Mettre en place le système d’information.
Tester votre application et notamment que vous pouvez vous connecter.
Dans la suite nous allons utiliser un thème gratuit Material Kit proposé par le site Creative Tim.
Quelques adaptations ont été apportées pour s’intégrer dans le contexte Laravel.
resource
de votre projet.npm install
(attention cela peut prendre du temps)npm run dev
La dernière commande doit s’exécuter sans erreur. Si ce n’est pas le cas le thème graphique n’est pas bien configuré.
Le code des vues proposées par la suite est basé sur l’utilisation du thème graphique Material Kit.
Cette page est statique, elle ne contient pas d’accès au système d’information. Dans cette page on indiquera que les informations utilisées pour réaliser le TP ont été obtenues sur le site du guide des bières.
Par exemple
@extends('layouts.master')
@section('content')
<div class="page-header header-filter clear-filter green-filter " data-parallax="true"
style="background-image: url('{{url('/img/houblon-alsace.jpg')}}');">
<div class="container">
<div class="row">
<div class="col-md-8 ml-auto mr-auto">
<div class="brand">
<h1>La bière qu'il vous faut.</h1>
<h3>Pouvez vous imaginer la bière que vous allez déguster...</h3>
</div>
</div>
</div>
</div>
</div>
<div class="main main-raised">
<div class="section section-basic">
<div class="container">
<div class="title">
<h2>A Propos</h2>
<h3 class="text-center">Toutes les informations sur les bières ont été obtenues à partir du site "<a href="http://www.guidedesbieres.com"><span style="font-style: italic; font-weight: bold">Guide des bières</span></a>"</h3>
</div>
</div>
</div>
</div>
@endsection
Cette page est statique, elle ne contient pas d’accès au système d’information.
@extends('layouts.master')
@section('content')
<div class="page-header header-filter clear-filter green-filter " data-parallax="true"
style="background-image: url('{{url('/img/houblon-alsace.jpg')}}');">
<div class="container">
<div class="row">
<div class="col-md-8 ml-auto mr-auto">
<div class="brand">
<h1>La bière qu'il vous faut.</h1>
<h3>Pouvez vous imaginer la bière que vous allez déguster...</h3>
</div>
</div>
</div>
</div>
</div>
<div class="main main-raised">
<div class="section section-basic">
<div class="container">
<div class="title">
<h2>Contact</h2>
</div>
</div>
</div>
</div>
@endsection
Mettre en place le thème graphique.
Créer le contrôleur pour les pages statiques.
Ajouter les méthodes pour les pages statiques.
Modifier le fichier web.php
pour les pages statiques.
Modifier le fichier navbar.blade.php
pour les pages statiques.
Créer le fichier apropos.blade.php
qui affiche la page d’A propos.
Créer le fichier contact.blade.php
qui affiche la page Contact.
Créer le fichier accueil.blade.php
qui affiche la page d’accueil avec un échantillon de bières.
La page d’accueil affiche un échantillon des bières disponibles dans la base de données.
Dans le listing qui suit, on trouve un exemple d’une page d’accueil qui ne nécessite aucun droit (visible par un utilisateur anonyme).
@extends('layouts.master')
@section('content')
<div class="page-header header-filter clear-filter green-filter " data-parallax="true"
style="background-image: url('{{url('/img/houblon-alsace.jpg')}}');">
<div class="container">
<div class="row">
<div class="col-md-8 ml-auto mr-auto">
<div class="brand">
<h1>La bière qu'il vous faut.</h1>
<h3>Pouvez vous imaginer la bière que vous allez déguster...</h3>
</div>
</div>
</div>
</div>
</div>
<div class="main main-raised">
<div class="section section-basic">
<div class="container">
<div class="title">
<h2>Laissez-vous tenter ... <b>(Avec modération)</b></h2>
</div>
@foreach($bieres as $biere)
@component('biere.component', ['biere' => $biere])
@endcomponent
@endforeach
</div>
</div>
</div>
@endsection
Nous allons utiliser un composant biere.composant.blade.php
qui affiche une bière
<div class="card text-center" >
<h4 class="title title-modern">{{$biere->nom}}</h4>
<div class="header">
<div class="avatar">
<img src="{{url("http://www.guidedesbieres.com".$biere->image)}}" alt="{{$biere->nom}}"
class="img-raised rounded img-fluid">
</div>
</div>
<div class="content">
<div class="price">
<h6>{{$biere->brasserie}}</h6>
<h4>Fermentation {{$biere->fermentation}} {{$biere->alcool}}</h4>
</div>
<p class="description">{{$biere->description}}</p>
</div>
<div class="filter"></div>
<div class="footer btn-center">
<button class="btn btn-neutral btn-round btn-fill" >Détails</button>
</div>
</div>
Créer la fonction accueil qui va récupérer 5 ou 6 bières de manière aléatoire dans la base de données.
Pour travailler avec notre base de bières, nous allons créer un contrôleur BieresController
et ajouter les routes qui correspondent
Créer le contrôleur BieresController
.
Ajouter les routes dans le fichier web.php
.
Dans un premier temps la liste des bières va afficher toutes les bières dans la même page. Ce type d’affichage devoir très rapidement inconfortable pour l’utilisateur lorsque que le nombre d’éléments à afficher devient grand.
Dans le code qui suit vous trouverez un exemple de vue qui affiche la liste des bières. Il faut noter qu’un script javascript a été ajouter, celui-ci permet de déclencher l’affichage d’une vue complète des informations qui concernent une bière et qui sera développée plus loin dans le TP.
@extends('layouts.master')
@section('content')
<div class="main main-raised">
<div class="container">
<h1 style="margin-top: 10rem">Liste des Bières</h1>
@if(!(count($bieres) === 0))
<table class="table">
<thead style="background-color: #ddd; font-weight: bold;">
<th></th>
<th class="header">Nom</th>
<th class="header">Type</th>
<th class="header">Frementation</th>
<th class="header">Pays</th>
<th class="header">Alcool</th>
<th class="header">Actions</th>
</thead>
<tbody>
@foreach ($bieres as $biere)
<tr class="une-biere" data-url="{{route('bieres.show',$biere->id)}}">
<td><img style="height: 70px" src="{{url("http://www.guidedesbieres.com".$biere->image)}}"
alt="Circle Image"
class="rounded-circle img-fluid"></td>
<td>{{$biere->nom}}</td>
<td>{{$biere->type}}</td>
<td>{{$biere->fermentation}}</td>
<td>{{$biere->pays}}</td>
<td>{{$biere->alcool}}</td>
<td>
<div class="btn-group">
<a href="#" class="btn btn-warning" role="button">
<span class="far fa-edit fa-2x" aria-hidden="true"></span>
</a>
<a href="#" class="btn btn-danger" role="button">
<span class="far fa-trash-alt fa-2x" aria-hidden="true"></span>
</a>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
@else
<div class="alert alert-warning" role="alert">Aucune bière dans la base</div>
<a href="{{route('statique.apropos')}}" class="btn btn-warning"> retour à l'accueil</a>
@endif
</div>
</div>
@endsection
@section('scripts')
<script type="text/javascript">
$(document).on('click', 'tr.une-biere', function () {
console.log($(this)[0].attributes['data-url'].value);
let my_url = $(this)[0].attributes['data-url'].value;
window.location.replace(my_url);
});
</script>
@endsection
Mettre en oeuvre cette version de la liste des bières.
Nous allons mettre en oeuvre un affichage des bières par page. Pour cela nous allons utiliser la fonction paginate
que propose Laravel.
De plus cette fonction est associée avec une génération automatique de liens qui permettent de se déplacer de page en page.
<!-- affiche les boutons pour se déplacer de page en page
<nav aria-label="Page navigation example" class=" justify-content-end" style="margin-bottom: 2rem">
{{ $bieres->links() }}
</nav>
Mettre en oeuvre la version paginée de la liste des bières.
Il est très simple d’ajouter une version de la liste triée, pour cela nous allons ajouter à la page un mécanisme qui permettra à l’utilisateur d’indiquer un critère de tri.
Par exemple en utilisant un bouton dropdown :
<div class="dropdown">
<a class="btn btn-secondary dropdown-toggle" href="#" role="button" id="dropdownMenuLink"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Triée par
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<a class="dropdown-item" href="{{route('bieres.index')}}">aucun</a>
<a class="dropdown-item" href="{{route('bieres.index',['sort'=>'nom'])}}">Nom</a>
<a class="dropdown-item" href="{{route('bieres.index',['sort'=>'pays'])}}">Pays</a>
</div>
</div>
En ajoutant un formulaire de recherche, il est possible de limiter la liste aux noms de bières qui vérifient le nom indiqué par l’utilisateur.
Par exemple en utilisant un bandeau comme celui-ci :
<nav class="navbar navbar-expand-lg bg-light">
<div class="container">
<div class="navbar-translate">
<button class="navbar-toggler" type="button" data-toggle="collapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="sr-only">Toggle navigation</span>
<span class="navbar-toggler-icon"></span>
<span class="navbar-toggler-icon"></span>
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="collapse navbar-collapse">
<div class="dropdown">
<a class="btn btn-secondary dropdown-toggle" href="#" role="button" id="dropdownMenuLink"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Triée par
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<a class="dropdown-item" href="{{route('bieres.index')}}">aucun</a>
<a class="dropdown-item" href="{{route('bieres.index',['sort'=>'nom'])}}">Nom</a>
<a class="dropdown-item" href="{{route('bieres.index',['sort'=>'pays'])}}">Pays</a>
</div>
</div>
<form class="form-inline ml-auto">
<div class="form-group has-success">
<input type="text" name="nom" class="form-control" placeholder="Recherche">
</div>
<button type="submit" class="btn btn-white btn-raised btn-fab btn-round">
<i class="fas fa-search"></i>
</button>
</form>
</div>
</div>
</nav>
Pour que le tri et/ou la recherche fonctionne avec la pagination, il faut modifier l’url générée par la fonction $bieres->links()
. Voici un exemple de fonction index
dans le contrôleur BieresController
qui permet de gérer ces modifications.
public function index(Request $request) {
$sort = $request->input('sort');
$nom = $request->input('nom');
if (isset($nom)) {
$bieres = Biere::where('nom', 'like', '%'.$nom.'%')->paginate(15);
$bieres->withPath('bieres?nom='.$nom);
} elseif (isset($sort)) {
$bieres = Biere::orderBy($sort)->paginate(15);
$bieres->withPath('bieres?sort='.$sort);
} else {
$bieres = Biere::paginate(15);
}
return view('biere.index', ['bieres' => $bieres]);
}
Mettre en oeuvre la version paginée avec tri et recherche par nom de bière.
Dans cette page nous allons afficher toutes les informations qui concernent une bière avec la liste des commentaires et la note.
La route pour y arriver est ‘bieres.show
’ qui sera déclenchée suite au clic sur le bouton Détails
de la page d’accueil ou la sélection dans la liste des bières (voir plus loin).
La vue s’appelle show.blade.php
est se trouve dans le répertoire views/biere
show
dans le contrôleur BiereController
devra récupérer
Voici un exemple de code pour la vue show.blade.php
@extends('layouts.master')
@section('content')
<div class="page-header header-filter clear-filter green-filter " data-parallax="true"
style="background-image: url('{{url('/img/une-biere.jpg')}}');">
<div class="container">
<div class="row">
<div class="col-md-8 ml-auto mr-auto">
<div class="brand">
<h1>{{$biere->nom}}</h1>
<h3>{{$biere->brasserie}}</h3>
</div>
</div>
</div>
</div>
</div>
<div class="main main-raised">
<div class="row container">
<div class="col-3">
<img src="{{url("http://www.guidedesbieres.com".$biere->image)}}" alt="{{$biere->nom}}"
class="img-thumbnail">
</div>
<div class="col-9">
<div class="row">
<div class="col-3">
<h4 class="title text-primary">Nom : </h4>
</div>
<div class="col-9">
<h3 class="title">{{$biere->nom}}</h3>
</div>
</div>
<div class="row">
<div class="col-3">
<h4 class="title text-primary">Brasserie : </h4>
</div>
<div class="col-3">
<h3 class="title">{{$biere->brasserie}}</h3>
</div>
<div class="col-3">
<h4 class="title text-primary">Pays : </h4>
</div>
<div class="col-3">
<h3 class="title">{{$biere->pays}}</h3>
</div>
</div>
<div class="row">
<div class="col-3">
<h4 class="title text-primary">Type : </h4>
</div>
<div class="col-3">
<h3 class="title">{{$biere->type}}</h3>
</div>
<div class="col-3">
<h4 class="title text-primary">Fermentation : </h4>
</div>
<div class="col-3">
<h3 class="title">{{$biere->fermentation}}</h3>
</div>
</div>
<div class="row">
<div class="col-3">
<h4 class="title text-primary">Description : </h4>
</div>
<div class="col-9">
<p>{{$biere->description}}</p>
</div>
</div>
</div>
</div>
<div class="container">
<h1 class="title">Notation</h1>
@if($notations['nbAvis'] !== 0)
@component('notation.component', ['note' => $notations['moyenne']*20, 'avis' => $notations['nbAvis'], 'avisParNote' => $notations['avisParNote']])
@endcomponent
@else
<div class="alert alert-primary" role="alert">Soyez le premier à évaluer cette bière</div>
@endif
<h1 class="title">Commentaires</h1>
@if(!(count($commentaires) === 0))
<h3 class="text-center">{{count($commentaires)}} Commentaire(s)</h3>
@foreach($commentaires as $commentaire)
<div class="row">
@component('commentaire.component', ['commentaire' => $commentaire])
@endcomponent
</div>
@endforeach
@else
<div class="alert alert-primary" role="alert">Soyez le premier à poster un commentaire</div>
@endif
</div>
</div>
@endsection
qui utilise le composant component.blade.php
dans le répertoire views/notation
<h3 class="text-center">Note moyenne : {{$note/20}}/5 ({{$avis}} avis)</h3>
<div class="star-ratings-sprite"><span style="width:{{$note}}%" class="star-ratings-sprite-rating"></span></div>
<hr style="border:3px solid #f1f1f1">
<div class="row">
<div class="col-1">
5 <i class="fas fa-star"></i>
</div>
<div class="col-6">
<div class="progress progress-line-primary">
<div class="progress-bar progress-bar-primary" role="progressbar"
aria-valuenow="{{$avisParNote[5]/$avis*100}}" aria-valuemin="0" aria-valuemax="100"
style="width: {{$avisParNote[5]/$avis*100}}%;">
<span class="sr-only">{{$avisParNote[5]/$avis*100}}% avis</span>
</div>
</div>
</div>
<div class="col-3">
{{$avisParNote[5]}} avis
</div>
</div>
<div class="row">
<div class="col-1">
4 <i class="fas fa-star"></i>
</div>
<div class="col-6">
<div class="progress progress-line-success">
<div class="progress-bar progress-bar-success" role="progressbar"
aria-valuenow="{{$avisParNote[4]/$avis*100}}"
aria-valuemin="0" aria-valuemax="100" style="width: {{$avisParNote[4]/$avis*100}}%;">
<span class="sr-only">{{$avisParNote[4]/$avis*100}}% avis</span>
</div>
</div>
</div>
<div class="col-3">
{{$avisParNote[4]}} avis
</div>
</div>
<div class="row">
<div class="col-1">
3 <i class="fas fa-star"></i>
</div>
<div class="col-6">
<div class="progress progress-line-info">
<div class="progress-bar progress-bar-info" role="progressbar" aria-valuenow="{{$avisParNote[3]/$avis*100}}"
aria-valuemin="0" aria-valuemax="100" style="width: {{$avisParNote[3]/$avis*100}}%;">
<span class="sr-only">{{$avisParNote[3]/$avis*100}}% avis</span>
</div>
</div>
</div>
<div class="col-3">
{{$avisParNote[3]}} avis
</div>
</div>
<div class="row">
<div class="col-1">
2 <i class="fas fa-star"></i>
</div>
<div class="col-6">
<div class="progress progress-line-success">
<div class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="{{$avisParNote[2]/$avis*100}}"
aria-valuemin="0" aria-valuemax="100" style="width: {{$avisParNote[2]/$avis*100}}%;">
<span class="sr-only">{{$avisParNote[2]/$avis*100}}% avis</span>
</div>
</div>
</div>
<div class="col-3">
{{$avisParNote[2]}} avis
</div>
</div>
<div class="row">
<div class="col-1">
1 <i class="fas fa-star"></i>
</div>
<div class="col-6">
<div class="progress progress-line-warning">
<div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="{{$avisParNote[1]/$avis*100}}"
aria-valuemin="0" aria-valuemax="100" style="width: {{$avisParNote[1]/$avis*100}}%;">
<span class="sr-only">{{$avisParNote[1]/$avis*100}}% avis</span>
</div>
</div>
</div>
<div class="col-3">
{{$avisParNote[1]}} avis
</div>
</div>
<div class="row">
<div class="col-1">
0 <i class="fas fa-star"></i>
</div>
<div class="col-6">
<div class="progress progress-line-danger">
<div class="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="{{$avisParNote[0]/$avis*100}}"
aria-valuemin="0" aria-valuemax="100" style="width: {{$avisParNote[0]/$avis*100}}%;">
<span class="sr-only">{{$avisParNote[0]/$avis*100}}% avis</span>
</div>
</div>
</div>
<div class="col-3">
{{$avisParNote[0]}} avis
</div>
</div>
<hr style="border:3px solid #f1f1f1">
et le composant component.blade.php
dans le répertoire views/commentaire
<div class="card" >
<div style="background-image: url({{asset('/img/quotes.png')}}); background-repeat: no-repeat; background-size: 150px 150px">
<div class="card-body">
<h3 class="card-title">{{$commentaire->titre}}</h3> <h4 class="muted">{{\App\Commentaire::diff($commentaire)}}</h4>
<p class="card-description">
{{$commentaire->commentaire}}
</p>
<div class="card-footer">
<div class="author">
<a href="{{route('home')}}">
<img src="{{asset('/img/avatar.png')}}" alt="..."
class="avatar img-raised">
<span>{{$commentaire->user->name}}</span>
</a>
</div>
</div>
</div>
</div>
</div>
Mettre en oeuvre la vue qui affiche les détails d’une bière.
Nous allons modifier le style des pages login
et register
avec par exemple pour
views/auth/login.blade.php
@extends('layouts.master')
@section('content')
<div class="page-header header-filter"
style="background-image: url('{{asset('/img/draft-beer-login.jpg')}}'); background-size: cover; background-position: top center;">
<div class="container">
<div class="row">
<div class="col-lg-4 col-md-6 ml-auto mr-auto">
<div class="card card-login">
<form class="form" method="POST" action="{{ route('login') }}">
@csrf
<div class="card-header card-header-primary text-center">
<h4 class="card-title">Connexion</h4>
<div class="social-line">
</div>
</div>
<p class="description text-center">TP 5</p>
<div class="card-body">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-user fa-2x"></i>
</span>
</div>
<input type="text" class="form-control" placeholder="Votre nom...">
</div>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-envelope fa-2x"></i>
</span>
</div>
<input id="email" type="email" placeholder="Adresse mail"
class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}"
name="email" value="{{ old('email') }}" required autofocus>
@if ($errors->has('email'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-lock fa-2x"></i>
</span>
</div>
<input id="password" type="password" placeholder="Mot de passe"
class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}"
name="password" required>
@if ($errors->has('password'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="footer text-center">
<button type="submit" class="btn btn-primary">
Connexion
</button>
<div>
<a class="btn btn-link" href="{{ route('password.request') }}">
Mot de passe oublié
</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
views/auth/register.blade.php
@extends('layouts.master')
@section('content')
<div class="page-header header-filter"
style="background-image: url('{{asset('/img/draft-beer-login.jpg')}}'); background-size: cover; background-position: top center;">
<div class="container">
<div class="row">
<div class="col-lg-4 col-md-6 ml-auto mr-auto">
<div class="card card-login">
<form method="POST" action="{{ route('register') }}">
@csrf
<div class="card-header card-header-primary text-center">
<h4 class="card-title">Enregistrement</h4>
<div class="social-line">
</div>
</div>
<p class="description text-center">TP 5</p>
<div class="card-body">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-user fa-2x"></i>
</span>
</div>
<input id="name" type="text" placeholder="Votre nom..." class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" value="{{ old('name') }}" required autofocus>
@if ($errors->has('name'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('name') }}</strong>
</span>
@endif
</div>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-envelope fa-2x"></i>
</span>
</div>
<input id="email" type="email" placeholder="Adresse mail" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required>
@if ($errors->has('email'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-lock fa-2x"></i>
</span>
</div>
<input id="password" type="password" placeholder="Mot de passe"
class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}"
name="password" required>
@if ($errors->has('password'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-lock fa-2x"></i>
</span>
</div>
<input id="password-confirm" type="password" placeholder="Comfirme mot de passe" class="form-control" name="password_confirmation" required>
</div>
</div>
<div class="footer text-center">
<button type="submit" class="btn btn-primary">
Valide
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
Mettre en oeuvre les vues modifiées pour l’authentification.
Nous allons donner la possibilité d’ajouter une bière aux visiteurs connectés. Pour cela il faut
create
du contrôleur BieresController
et la rendre accessible uniquement quand la requête provient d’un utilisateur connecté.store
du contrôleur BieresController
pour valider la saisie et sauvegarder la bière dans la base de données.Pour protéger l’exécution d’une méthode nous allons mettre en place une politique d’autorisations pour le modèle Biere
.
Création d’une classe BierePolicy
à l’aide de la commande
Enregistrer la politique d’autorisations dans l’application Dans la classe AuthServiceProvider
dans le répertoire app/Providers
il faut modifier la variable $policies
:
Pour pouvoir créer une nouvelle bière, il suffit d’être identifié, dans la méthode create
de la classe BierePolicy
nous aurons donc le code suivant :
``` php
public function create(User $user) {
// renvoie true si un utilisateur est connecté et false sinon
return Auth::check();
}
```
Pour pouvoir modifier une bière, il faut être connecté et être l’utilisateur créateur de la bière.
``` php
public function update(User $user, Biere $biere) {
return $user->id === $biere->user_id;
}
```
Les actions create
et update
doivent être protégées, pour cela il suffit d’utiliser la politique d’autorisation qui a été développée.
``` php
public function create() {
$this->authorize('create', Biere::class);
return view('biere.create',['user'=>Auth::User()]);
}
```
Si la requête n’est pas autorisée, l’utilisateur sera rédigé vers une page d’erreur (requête non autorisée), sinon la vue create.blade.php
dans le répertoire views/biere
sera affichée.
Voici un extrait de la vue create.blade.php
:
@extends('layouts.master')
@section('content')
<div class="main main-raised">
<h1 style="margin-top: 10rem">Creation d'une Bière</h1>
<form method="post" action="{{route('bieres.store')}}">
@csrf
<input type="hidden" name="user_id" value="{{$user->id}}">
<div class="container">
<div class="row">
<div class="col-lg-3 col-sm-4">
<div class="form-group">
<label for="nom" class="bmd-label-floating">Nom de la bière</label>
<input name="nom" type="text" class="form-control" id="nom" value="{{old('nom')}}" required>
<span class="bmd-help">Saisir le nom de la bière.</span>
</div>
</div>
<div class="col-lg-3 col-sm-4">
<div class="form-group">
<label for="brasserie" class="bmd-label-floating">Brasserie</label>
<input name="brasserie" type="text" class="form-control" id="brasserie" value="{{old('brasserie')}}" required>
<span class="bmd-help">Indiquez le nom de la brasserie.</span>
</div>
</div>
<div class="col-lg-3 col-sm-4">
<div class="form-group">
<label for="pays" class="bmd-label-floating">Pays</label>
<input name="pays" type="text" class="form-control" id="pays" value="{{old('pays')}}" required>
<span class="bmd-help">Pays où est brassé la bière.</span>
</div>
</div>
</div>
{{-- --}}
<div class="row">
<div class="col-lg-12">
<div class="form-group">
<h6>Description</h6>
<textarea name="description" id="description" cols="80" rows="10" required>{{old('description')}}</textarea>
</div>
</div>
</div>
<div class="row buttons-row">
<div class="col-md-3 col-sm-3">
<button type="submit" class="btn btn-outline-primary btn-block btn-round">Sauve</button>
</div>
</div>
</div>
</form>
</div>
@endsection
Le bouton qui permet d’ajouter une bière ne doit être afficher que lorsque l’utilisateur qui visualise la page en a le droit.
Nous allons modifier la vue index.blade.php
du répertoire views/biere
, nous allons ajouter le bouton dans la balise nav
et utiliser la directive blade @auth
pour cela :
<nav class="navbar navbar-expand-lg bg-light">
<div class="container">
<div class="navbar-translate">
<button class="navbar-toggler" type="button" data-toggle="collapse" aria-expanded="false"
aria-label="Toggle navigation">
<span class="sr-only">Toggle navigation</span>
<span class="navbar-toggler-icon"></span>
<span class="navbar-toggler-icon"></span>
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="collapse navbar-collapse">
<div class="dropdown">
<a class="btn btn-secondary dropdown-toggle" href="#" role="button" id="dropdownMenuLink"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Triée par
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<a class="dropdown-item" href="{{route('bieres.index')}}">aucun</a>
<a class="dropdown-item" href="{{route('bieres.index',['sort'=>'nom'])}}">Nom</a>
<a class="dropdown-item" href="{{route('bieres.index',['sort'=>'pays'])}}">Pays</a>
</div>
</div>
@auth
<div>
<p><a href="{{route('bieres.create')}}" class="btn btn-primary" role="button">Ajouter
une bière</a>
</p>
</div>
@endauth
<form class="form-inline ml-auto">
<div class="form-group has-success">
<input type="text" name="nom" class="form-control" placeholder="Recherche">
</div>
<button type="submit" class="btn btn-white btn-raised btn-fab btn-round">
<i class="fas fa-search"></i>
</button>
</form>
</div>
</div>
</nav>
Mettre en oeuvre l’ajout d’une bière.
Pour modifier une bière nous allons reprendre le travail fait dans la section précédente.
edit
update
Nous allons afficher un bouton qui permet de modifier une bière uniquement lorsque l’utilisateur connecté est le créateur de la bière.
Pour cela nous allons modifier la vue index.blade.php
du répertoire views/biere
et utiliser la directive @can
de blade qui va utiliser la fonction update
de la classe BierePolicy
.
Voici un extrait de la vue index.blade.php
:
<!-- les autres éléments de la vue -->
<td>
@can('update',$biere)
<div class="btn-group">
<a href="{{route('bieres.edit',['id'=>$biere->id])}}" class="btn btn-warning" role="button">
<span class="far fa-edit fa-2x" aria-hidden="true"></span>
</a>
</div>
@else
<div></div>
@endcan
</td>
<!-- la suite de la vue -->
Il faut ensuite créer la vue edit.blade.php
dans le répertoire views/biere
. Voici un extrait de cette vue :
@extends('layouts.master')
@section('content')
<div class="main main-raised">
<h1 style="margin-top: 10rem">Edition d'une Bière</h1>
<form method="post" action="{{route('bieres.update',['id'=>$biere->id])}}">
@csrf
@method('put')
<div class="container">
{{-- les champs de saisie du formulaire --}}
</div>
</form>
</div>
@endsection
et traiter le formulaire dans la méthode update
du contrôleur BieresController
.
Mettre en oeuvre la modification d’une bière.
L’application n’est pas encore terminée, il faut ajouter les possibilités
Il n’y a pas de nouveau concept à introduire pour ajouter ces possibilités, elles vous sont laissées comme exercices d’approfondissement.
IUT de Lens Département Informatique
2018