• 15 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 11/05/2020

La relation 1:n

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Pour le moment nous n'avons manipulé qu'une table avec Eloquent. Dans le présent chapitre nous allons utiliser deux tables et les mettre en relation. La relation la plus répandue et la plus simple entre deux tables est celle qui fait correspondre un enregistrement d'une table à plusieurs enregistrements de l'autre table, on parle de relation de un à plusieurs ou encore de relation de type 1:n. Nous verrons également dans ce chapitre comment créer un middleware.

Comme exemple pour ce chapitre, je vais prendre le cas d'un petit blog personnel avec :

  • un affichage des articles,

  • des visiteurs qui pourront consulter les articles,

  • des utilisateurs enregistrés qui pourront aussi rédiger des articles (donc possibilité de se connecter et se déconnecter),

  • des administrateurs qui pourront aussi supprimer des articles.

Pour ne pas trop alourdir le code, je ne vais pas prévoir la modification des articles.

Les données

Les migrations

Nous allons continuer à utiliser la table  users que nous avons vue aux chapitres précédents. Nous allons créer une nouvelle table  posts destinée à mémoriser les articles. Si vous avez déjà créé la table  users avec des enregistrements supprimez cette table, nous allons la recréer.

Nous avons déjà défini la migration de la table  users et vous devez avoir le fichier dans le dossier  database/migrations. Je vous en rappelle le code :

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration {

	public function up()
	{
		Schema::create('users', function(Blueprint $table)
		{
			$table->increments('id');
			$table->string('name')->unique();
			$table->string('email')->unique();
			$table->string('password', 60);
			$table->boolean('admin')->default(false);
			$table->rememberToken();
			$table->timestamps();
		});
	}

	public function down()
	{
		Schema::drop('users');
	}

}

On va créer une migration aussi pour la table  posts :

php artisan make:migration create_posts_table

Et on va compléter ainsi le code :

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePostsTable extends Migration {

    public function up()
    {
    	Schema::create('posts', function(Blueprint $table) {
			$table->increments('id');
			$table->timestamps();
			$table->string('titre', 80);
			$table->text('contenu');
			$table->integer('user_id')->unsigned();
			$table->foreign('user_id')
				  ->references('id')
				  ->on('users')
				  ->onDelete('restrict')
				  ->onUpdate('restrict');
		});
	}

	public function down()
	{
		Schema::table('posts', function(Blueprint $table) {
			$table->dropForeign('posts_user_id_foreign');
		});
		Schema::drop('posts');
	}

}

Normalement vous devez avoir ces 3 migrations :

Les 3 migrations

Lancez la migration :

php artisan migrate

Vous devez ainsi vous retrouver avec les trois tables dans votre base ainsi que la table  migrations :

Les 4 tables

La population

Nous allons voir maintenant comment remplir nos tables avec des enregistrements pour faire nos essais. Nous allons pour cela créer deux fichiers dans le dossier  database/seeds. Normalement vous devez déjà avoir dans ce dossier le fichier  DatabaseSeeder.php : 

Le fichier DatabaseSeeder
Le fichier DatabaseSeeder

Avec ce code :

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        // $this->call(UserTableSeeder::class);
    }
}

La méthode  run est destinée à exécuter les fichiers pour la population. Vous avez déjà la ligne commentée de lancement pour la table  users. Nous allons donc la dé-commenter et ajouter le code pour la table posts :

<?php
public function run()
{
    $this->call(UserTableSeeder::class);
    $this->call(PostTableSeeder::class);
}

Mettez bien les lignes dans cet ordre, vous comprendrez bientôt pourquoi c'est nécessaire.

Ensuite on va créer le fichier  UserTableSeeder.php pour la population de la table  users :

<?php

use Illuminate\Database\Seeder;

class UserTableSeeder extends Seeder {

    public function run()
	{
		DB::table('users')->delete();

		for($i = 0; $i < 10; ++$i)
		{
			DB::table('users')->insert([
				'name' => 'Nom' . $i,
				'email' => 'email' . $i . '@blop.fr',
				'password' => bcrypt('password' . $i),
				'admin' => rand(0, 1)
			]);
		}
	}
}

On va créer ainsi 10 utilisateurs.

On prévoit aussi le fichier  PostTableSeeder.php avec ce code :

<?php

use Illuminate\Database\Seeder;
use Carbon\Carbon;

class PostTableSeeder extends Seeder {

    private function randDate()
	{
		return Carbon::createFromDate(null, rand(1, 12), rand(1, 28));
	}

	public function run()
	{
		DB::table('posts')->delete();

		for($i = 0; $i < 100; ++$i)
		{
			$date = $this->randDate();
			DB::table('posts')->insert([
				'titre' => 'Titre' . $i,
				'contenu' => 'Contenu' . $i . ' Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
				'user_id' => rand(1, 10),
				'created_at' => $date,
				'updated_at' => $date
			]);
		}
	}
}

On va ainsi créer 100 articles affectés de façon aléatoire aux 10 utilisateurs. Nous allons voir bientôt comment s'effectue la liaison entre les deux.

Vous devez avoir maintenant ces fichiers dans le dossier des populations :

Les fichiers de population
Les fichiers de population

Il suffit maintenant d'utiliser la commande d'Artisan pour lancer la population :

php artisan db:seed

Normalement vous devez avoir les deux tables remplies à l'issue de cette commande.

Les tables doivent maintenant être garnies, par exemple pour les utilisateurs (users) :

La table users
La table users

La relation

On a la situation suivante :

  •  un utilisateur peut écrire plusieurs articles,

  •  un article est écrit par un seul utilisateur.

Il faut trouver un moyen pour référencer cette relation dans les tables. Le principe est simple : on prévoit dans la tableposts une ligne destinée à recevoir l'identifiant de l'utilisateur rédacteur de l'article. On appelle cette ligne une clé étrangère parce qu'on enregistre ici la clé d'une autre table. Voici une représentation visuelle de cette relation :

La clé étrangère
La clé étrangère

Vous voyez la relation dessinée entre la clé  id dans la table  users et la clé étrangère  user_id dans la table  posts. La migration que l'on a créée ci-dessus est destinée aussi à informer la base de cette relation. Regardez ce code :

<?php
$table->foreign('user_id')
      ->references('id')
	  ->on('users')
	  ->onDelete('restrict')
	  ->onUpdate('restrict');

Dans la table on déclare une clé étrangère (foreign) nommée  user_id qui référence (references) la ligne  id dans la table (on)  users. En cas de suppression (onDelete) ou de modification (onUpdate) on a une restriction (restrict). Que signifient ces deux dernières conditions ?

Imaginez que vous avez un utilisateur avec l'id 5 qui a deux articles, donc dans la table  posts on a deux enregistrements avec  user_id qui a la valeur 5. Si on supprime l'utilisateur que va-t-il se passer ? On risque de se retrouver avec nos deux enregistrements dans la table  posts avec une clé étrangère qui ne correspond à aucun enregistrement dans la table  users. En mettant "restrict" on empêche la suppression d'un utilisateur qui a des articles. On doit donc commencer par supprimer ses articles avant de le supprimer lui-même. On dit que la base assure l'intégrité référentielle. Elle n'acceptera pas non plus qu'on utilise pour user_id une valeur qui n'existe pas dans la table  users.

Une autre possibilité est "cascade" à la place de "restrict". Dans ce cas si vous supprimez un utilisateur ça supprimera en cascade les articles de cet utilisateur. C'est une option qui est rarement utilisée parce qu'elle peut s'avérer dangereuse, surtout dans une base comportant de multiples tables en relation. Mais c'est aussi une stratégie très efficace parce que c'est le moteur de la base de données qui se charge de gérer les enregistrements en relation, vous n'avez ainsi pas à vous en soucier au niveau du code.

On pourrait aussi ne pas signaler à la base qu'il existe une relation et la gérer seulement dans notre code. Mais c'est encore plus dangereux parce que la moindre erreur de gestion des enregistrements dans votre code risque d'avoir des conséquences importantes dans votre base avec de multiples incohérences.

Les modèles

Nous avons déjà un modèle  User (app/User.php). Il va juste falloir ajouter une méthode pour pouvoir facilement aller trouver les articles d'un utilisateur. Ajoutez ce code dans le modèle  User :

<?php
public function posts() 
{
    return $this->hasMany('App\Post');
}

On déclare ici qu'un utilisateur a plusieurs (hasMany) articles (posts). On aura ainsi une méthode pratique pour récupérer les articles d'un utilisateur.

Il nous faut aussi le modèle Post :

<?php 

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{

    protected $fillable = ['titre','contenu','user_id'];

	public function user() 
	{
		return $this->belongsTo('App\User');
	}

}

Ici on a la méthode  user (au singulier) qui permet de trouver l'utilisateur auquel appartient (belongsTo) l'article. C'est donc la réciproque de la méthode précédente.

Voici une schématisation de cette relation avec les deux méthodes :

Les deux méthodes de la relation
Les deux méthodes de la relation

Les deux méthodes mises en place permettent de récupérer facilement un enregistrement lié. Par exemple pour avoir tous les articles de l'utilisateur qui a l'id 1 :

<?php
$articles = App\User::find(1)->posts;

De la même manière on peut trouver l'utilisateur qui a écrit l'article d'id 1 :

<?php
$user = App\Post::find(1)->user;

Vous voyez que le codage devient limpide avec ces méthodes :).

Le contrôleur et les routes

Le contrôleur

Maintenant que tout est en place au niveau des données voyons un peu la gestion de tout ça. On va créer un contrôleur pour les articles qu'on va appeler  PostController. Ce contrôleur devra gérer plusieurs chose :

  • la réception de la requête pour afficher les articles du blog et la réponse adaptée,

  • la réception de la requête pour le formulaire pour créer un nouvel article et son envoi,

  • la réception de la soumission du formulaire de création d'un nouvel article (réservé à un utilisateur connecté) et son enregistrement,

  • la réception de la demande de suppression d'un article (réservé à un administrateur) et sa suppression.

Pour simplifier je ne vais pas prévoir la possibilité de modifier un article. 

Je vais utiliser un contrôleur de ressource. Voici son code :

<?php

namespace App\Http\Controllers;

use App\Repositories\PostRepository;
use App\Http\Requests\PostRequest;

class PostController extends Controller
{

    protected $postRepository;

    protected $nbrPerPage = 4;

    public function __construct(PostRepository $postRepository)
	{
		$this->middleware('auth', ['except' => 'index']);
		$this->middleware('admin', ['only' => 'destroy']);

		$this->postRepository = $postRepository;
	}

	public function index()
	{
		$posts = $this->postRepository->getPaginate($this->nbrPerPage);
		$links = $posts->render();

		return view('posts.liste', compact('posts', 'links'));
	}

	public function create()
	{
		return view('posts.add');
	}

	public function store(PostRequest $request)
	{
		$inputs = array_merge($request->all(), ['user_id' => $request->user()->id]);

		$this->postRepository->store($inputs);

		return redirect(route('post.index'));
	}

	public function destroy($id)
	{
		$this->postRepository->destroy($id);

		return redirect()->back();
	}

}

Comme à l’accoutumée j'injecte la requête de formulaire et le repository.

Notez l'utilisation des middleware pour filtrer les utilisateurs, nous allons voir cela un peu plus loin.

Les routes

On a vu dans le chapitre sur les ressources comment créer les routes de ce genre de contrôleur. Il va juste falloir indiquer qu'on ne veut pas utiliser les 7 méthodes disponibles mais juste certaines :

<?php
Route::resource('post', 'PostController', ['except' => ['show', 'edit', 'update']]);

Avec  except j'indique que je ne veux pas de route pour les 3 méthodes citées. Voici ce que ça donne en utilisant artisan pour visualiser les routes (php artisan route:list) :

Les routes
Les routes

Le repository

Pour la gestion on va placer les fichiers dans le dossier  app/Repositories comme nous l'avons déjà fait pour la gestion des utilisateurs :

Le repository

Avec ce code :

<?php

namespace App\Repositories;

use App\Post;

class PostRepository
{

    protected $post;

    public function __construct(Post $post)
	{
		$this->post = $post;
	}

	public function getPaginate($n)
	{
		return $this->post->with('user')
		->orderBy('posts.created_at', 'desc')
		->paginate($n);
	}

	public function store($inputs)
	{
		$this->post->create($inputs);
	}

	public function destroy($id)
	{
		$this->post->findOrFail($id)->delete();
	}

}

Nous allons voir plus loin l'utilité de toutes ces méthodes.

Les middlewares

On a vu que dans le contrôleur on applique deux middlewares :

  • auth : accès réservé aux utilisateurs authentifiés à part pour la méthode  index pour afficher le blog,

  • admin : accès réservé aux administrateurs pour la méthode  destroy.

Le premier middleware est déjà prévu dans Laravel, par contre le second n'existe pas. Il faut donc le créer.

Encore une fois nous allons utiliser Artisan :

php artisan make:middleware Admin

On trouve le fichier bien rangé :

Le middleware pour l'administration

On obtient ce code :

<?php 

namespace App\Http\Middleware;

use Closure;

class Admin
{

    /**
	 * Handle an incoming request.
	 *
	 * @param  \Illuminate\Http\Request  $request
	 * @param  \Closure  $next
	 * @return mixed
	 */
	public function handle($request, Closure $next)
	{
		//
	}

}

On doit écrire notre code, ce qui donne :

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\RedirectResponse;

class Admin
{

	/**
	 * Handle an incoming request.
	 *
	 * @param  \Illuminate\Http\Request  $request
	 * @param  \Closure  $next
	 * @return mixed
	 */
	public function handle($request, Closure $next)
	{
		if ($request->user()->admin)
		{
			return $next($request);
		}
		return new RedirectResponse(url('post'));
	}

}

Si l'utilisateur n'est pas un administrateur on redirige sur l'affichage du blog. Remarquez qu'on ne vérifie pas à ce niveau qu'on a un utilisateur authentifié parce que dans le constructeur du contrôleur le filtre auth est placé avant le filtre admin. Si c'était l'inverse on tomberait évidemment sur une erreur en cas de tentative d'accès à l'url pour la suppression d'un article.

‌On a créé le middleware mais ça ne suffit pas, il faut maintenant un lien entre le nom qu'on veut donner au filtre et la classe qu'on vient de créer. Regardez dans le fichier  app/Http/Kernel.php ces lignes de code :

<?php
protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];

Vous trouvez ici tous les middlewares déclarés, il suffit d'ajouter le nouveau :

<?php
protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'admin' => \App\Http\Middleware\Admin::class,
];

La validation

Voyons maintenant la validation. Il faut encore créer une requête de formulaire :

php artisan make:request PostRequest

Elle se place dans le dossier :

La requête de formulaire

Et on complète ainsi le code :

<?php

namespace App\Http\Requests;

use App\Http\Requests\Request;

class PostRequest extends Request
{

	public function authorize()
	{
		return true;
	}

	public function rules()
	{
		return [
			'titre' => 'required|max:80',
			'contenu' => 'required'
		];
	}

}

Fonctionnement

La liste des articles

La liste des articles est obtenue avec l'url (verbeget) : 

.../post

Qui arrive sur la méthodeindex du contrôleur :

<?php
public function index()
{
    $posts = $this->postRepository->getPaginate($this->nbrPerPage);
	$links = $posts->render();

	return view('posts.liste', compact('posts', 'links'));
}

Ici on envoie le nombre d'articles par page (placé dans la propriété  $nbrPerPage) à la méthode  getPaginate du repository :

<?php
public function getPaginate($n)
{
    return $this->post->with('user')
	->orderBy('posts.created_at', 'desc')
	->paginate($n);
}

On veut les articles avec (with) l'utilisateur (user), dans l'ordre des dates de création (posts.created_at) descendant (desc) avec une pagination de n articles ($n).

L'ajout d'un article

La demande du formulaire de création d'un article se fait avec l'url (verbe  get) :

.../post/create

Le contrôleur ren‌voie directement la vue :

<?php
public function create()
{
    return view('posts.add');
}

Le retour du formulaire se fait avec l'url (verbe  post) :

.../post

On arrive sur la méthode  store du contrôleur :

<?php
public function store(PostRequest $request)
{
    $inputs = array_merge($request->all(), ['user_id' => $request->user()->id]);

	$this->postRepository->store($inputs);

	return redirect(route('post.index'));
}

On injecte la requête de formulaire, je n'insiste pas parce qu'il n'y a rien de nouveau à ce niveau. On récupère les entrées du formulaire pour le titre et le contenu. Pour l'identifiant de l'utilisateur on sait qu'il est forcément connecté alors on récupère cet identifiant avec la requête. Si la validation se passe bien on envoie à la méthode  store du repository :

<?php
public function store($inputs)
{
    $this->post->create($inputs);
}

Suppression d'un article

Enfin on supprime un article avec l'url (verbe  delete) :

.../post/id

Où id représente l'identifiant de l'article à supprimer. On tombe sur la méthode  destroy  du contrôleur :

<?php
public function destroy($id)
{
    $this->postRepository->destroy($id);

	return redirect()->back();
}

Qui envoie à la méthode  destroy du repository :

<?php
public function destroy($id)
{
    $this->post->findOrFail($id)->delete();
}

Là on supprime l'article avec la méthode  delete du modèle.

Les vues

Voyons à présent les vues. On va un peu modifier notre template (resources/views/template.blade.php ) :

<!DOCTYPE html>
<html lang="fr">
    <head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title>Mon joli site</title>
		{!! Html::style('https://netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css') !!}
		{!! Html::style('https://netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css') !!}
		<!--[if lt IE 9]>
			{{ Html::style('https://oss.maxcdn.com/libs/html5shiv/3.7.2/html5shiv.js') }}
			{{ Html::style('https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js') }}
		<![endif]-->
		<style> textarea { resize: none; } </style>
	</head>
  <body>
    <header class="jumbotron">
      <div class="container">
        <h1 class="page-header">{!! link_to_route('post.index', 'Mon joli blog') !!}</h1>
        @yield('header')
      </div>
    </header>
    <div class="container">
      @yield('contenu')
    </div>
  </body>
</html>

Nous avons besoin d'une vue pour afficher les articles du blog et quelques boutons pour la gestion (resources/views/posts/liste.blade.php) :

@extends('template')

@section('header')
	@if(Auth::check())
		<div class="btn-group pull-right">
			{!! link_to_route('post.create', 'Créer un article', [], ['class' => 'btn btn-info']) !!}
			{!! link_to('logout', 'Deconnexion', ['class' => 'btn btn-warning']) !!}
		</div>
	@else
		{!! link_to('login', 'Se connecter', ['class' => 'btn btn-info pull-right']) !!}
	@endif
@endsection

@section('contenu')
	@if(isset($info))
		<div class="row alert alert-info">{{ $info }}</div>
	@endif
	{!! $links !!}
	@foreach($posts as $post)
		<article class="row bg-primary">
			<div class="col-md-12">
				<header>
					<h1>{{ $post->titre }}</h1>
				</header>
				<hr>
				<section>
					<p>{{ $post->contenu }}</p>
					@if(Auth::check() and Auth::user()->admin)
						{!! Form::open(['method' => 'DELETE', 'route' => ['post.destroy', $post->id]]) !!}
							{!! Form::submit('Supprimer cet article', ['class' => 'btn btn-danger btn-xs ', 'onclick' => 'return confirm(\'Vraiment supprimer cet article ?\')']) !!}
						{!! Form::close() !!}
					@endif
					<em class="pull-right">
						<span class="glyphicon glyphicon-pencil"></span> {{ $post->user->name }} le {!! $post->created_at->format('d-m-Y') !!}
					</em>
				</section>
			</div>
		</article>
		<br>
	@endforeach
	{!! $links !!}
@endsection

Avec cet aspect pour un utilisateur non connecté :

L'aspect du blog

Le bouton « Se connecter » envoie sur l'url :

.../login

Ce qui correspond à ce que nous avons vu au chapitre précédent et doit aboutir sur le formulaire de connexion si vous avez tout en place comme nous l'avons prévu précédemment :

Le formulaire de connexion

Pour que votre application fonctionne bien avec le contrôleur  AuthController il faut modifier la propriété  redirectTo dans ce contrôleur :

<?php
protected $redirectTo = 'post';

Ainsi vous serez bien redirigé vers le blog en cas de connexion.

De la même manière il faut prévoir une redirection après déconnexion dans le même contrôleur :

<?php
protected $redirectAfterLogout = 'post';

La génération des articles dans la vue se fait avec un  foreach :

@foreach($posts as $post)
    ...
@endforeach

Si vous vous connectez avec un utilisateur qui n'est pas administrateur (regardez dans votre table pour en trouver un, comme la population est aléatoire on ne sait pas à l'avance qui l'est et qui ne l'est pas). Vous retournez au blog avec deux boutons supplémentaires :

Les boutons pour un utilisateur de base
Les boutons pour un utilisateur de base

On utilise une condition pour adapter la page :

@if(Auth::check())
    ...
@else
	...
@endif

La méthode  check de la classe  Auth permet de savoir si l'utilisateur est connecté.

Le premier bouton « Créer un article » génère l'url :

.../post/create

Ce qui a pour effet d'obtenir le formulaire de création que nous allons bientôt voir.

Le second bouton « Deconnexion » génère l'url :

.../logout

Qui correspond aussi a ce que nous avons vu dans le chapitre précédent.

Si l'utilisateur connecté est un administrateur alors il a en plus pour chaque article un bouton de suppression :

Le bouton de suppression

On utilise encore une condition pour détecter un administrateur :

@if(Auth::check() and Auth::user()->admin)
    ...
@endif

Il faut que l'utilisateur soit connecté (check) et que ce soit un administrateur (user()->admin).

J'ai prévu une confirmation de la suppression avec un peu de Javascript.

Voici maintenant la vue pour le formulaire de création d'un article (resources/views/posts/add.blade.php) :

@extends('template')

@section('contenu')
	<br>
	<div class="col-sm-offset-3 col-sm-6">
		<div class="panel panel-info">
			<div class="panel-heading">Ajout d'un article</div>
			<div class="panel-body"> 
				{!! Form::open(['route' => 'post.store']) !!}
					<div class="form-group {!! $errors->has('titre') ? 'has-error' : '' !!}">
						{!! Form::text('titre', null, ['class' => 'form-control', 'placeholder' => 'Titre']) !!}
						{!! $errors->first('titre', '<small class="help-block">:message</small>') !!}
					</div>
					<div class="form-group {!! $errors->has('contenu') ? 'has-error' : '' !!}">
						{!! Form::textarea ('contenu', null, ['class' => 'form-control', 'placeholder' => 'Contenu']) !!}
						{!! $errors->first('contenu', '<small class="help-block">:message</small>') !!}
					</div>
					{!! Form::submit('Envoyer !', ['class' => 'btn btn-info pull-right']) !!}
				{!! Form::close() !!}
			</div>
		</div>
		<a href="javascript:history.back()" class="btn btn-primary">
			<span class="glyphicon glyphicon-circle-arrow-left"></span> Retour
		</a>
	</div>
@endsection

Il n'y a rien de bien nouveau dans cette vue. Voici son apparence :

Formulaire de création d'un article
Formulaire de création d'un article

On peut entrer le titre et le contenu qui sera ensuite validé dans le contrôleur et enregistré si tout se passe bien.

En résumé

  • Une relation de type 1:n nécessite la création d'une clé étrangère côté n.

  • On peut remplir les tables d'enregistrements avec la population.

  • Une relation dans la base nécessite la mise en place de méthodes spéciales dans les modèles.

  • Avec les middlewares il est facile de gérer l'accès aux méthodes des contrôleurs.

Exemple de certificat de réussite
Exemple de certificat de réussite