• 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

Des vues propres (1/2)

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

On a vu comment créer des classes bien organisées et se contentant d'effectuer leur tâche. Par contre au niveau des vues c'est une autre histoire. En général on utilise un framework CSS, par exemple Bootstrap dont je me suis servi dans les exemples de ce cours. Mais que se passe-t-il le jour où ce framework évolue ou si on décide d'en changer ?

Les macros

Le problème

Regardez la vue que nous avons créée dans l'exemple de blog personnel pour la 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>
					<div class="form-group {{ $errors->has('tags') ? 'has-error' : '' }}">
						{!! Form::text('tags', null, array('class' => 'form-control', 'placeholder' => 'Entrez les tags séparés par des virgules')) !!}
						{!! $errors->first('tags', '<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

Quel est le degré de dépendance entre Bootstrap et ce code ? Petit point :

  • mise en page avec la grille,

  • utilisation du composant panel,

  • utilisation des classes btn, btn-info, form-control, form-group... pour le formulaire,

  • utilisation de la classe has-error pour le retour de validation,

  • ...

Si je décide de changer de framework je dois tout recommencer. Si je n'ai qu'un formulaire de ce genre ce n'est pas trop grave, si j'en ai 10 ou 20 ça devient embarrassant. De même si le framework évolue (ce qui est fréquent par exemple avec Bootstrap) et que je désire suivre cette évolution je vais être confronté au même problème.

L'idéal serait de pouvoir construire ma vue sans aucun lien avec un framework avec des méthodes neutres. Comment réaliser cela ? Il y a plusieurs façons de le faire mais la plus simple est certainement l'utilisation de macros.

Les macros

Une macro est un outil de substitution : on utilise un texte et ça en génère un autre avec possibilité de passer des paramètres. Laravel permet la création de macros. Regardez dans les dossiers du framework pour trouver ce fichier :

Le fichier pour les macros
Le fichier pour les macros

Si vous regardez le code vous allez voir qu'il s'agit d'un trait qui expose quelques méthode dont :

<?php
public static function macro($name, callable $macro)
{
    static::$macros[$name] = $macro;
}

En utilisant ce trait on peut donc enregistrer des macros dans la propriété statique $macros. Il y a également des méthodes magiques permettant d'utiliser les macros mémorisées.

Il se trouve que le composant LaravelCollective\Html utilise ce trait. Par exemple dans la classe FormBuilder on trouve :

<?php
class FormBuilder {

    use Macroable, Componentable {
        Macroable::__call as macroCall;
        Componentable::__call as componentCall;
    }

Il en est de même pour la classe HtmlBuilder. Nous allons donc utiliser cette possibilité pour rendre notre vue plus propre...

On veut arriver avec une vue ainsi conçue : 

@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']) !!}
					{!! Form::control('text', $errors, 'titre', 'Titre') !!}
					{!! Form::control('textarea', $errors, 'contenu', 'Contenu') !!}
					{!! Form::control('text', $errors, 'tags', 'Entrez les tags séparés par des virgules') !!}
					{!! Form::button_submit('Envoyer !') !!}
				{!! Form::close() !!}
			</div>
		</div>
		{!! Html::button_back() !!}
	</div>
@endsection

Nous n'avons plus aucune trace de notre framework au niveau du formulaire.

Maintenant la question est : où mettre les macros ?

Le service provider

Un service provider est le lieu idéal pour enregistrer des services. Laravel comporte de nombreux services providers. Nous allons en créer un pour nos macros. Encore une fois artisan va nous faciliter la tâche :

php artisan make:provider HtmlMacrosServiceProvider

On trouve notre provider dans le dossier correspondant :

Le provider pour les macros

Avec ce code :

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class HtmlMacrosServiceProvider extends ServiceProvider
{

    /**
	 * Bootstrap the application services.
	 *
	 * @return void
	 */
	public function boot()
	{
		//
	}

	/**
	 * Register the application services.
	 *
	 * @return void
	 */
	public function register()
	{
		//
	}

}

La méthode register permet d'enregistrer tout ce dont on a besoin, donc nos macros.

On va donc créer nos macros ici :

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Collective\Html\FormBuilder;
use Collective\Html\HtmlBuilder;

class HtmlMacrosServiceProvider extends ServiceProvider
{

	public function register()
	{
		$this->registerFormControl();
		$this->registerFormSubmit();
		$this->registerHtmlButtonBack();
	}

	private function registerFormControl()
	{
		FormBuilder::macro('control', function($type, $errors, $nom, $placeholder)
		{
			$valeur = \Request::old($nom) ? \Request::old($nom) : null;
			$attributes = ['class' => 'form-control', 'placeholder' => $placeholder];
			return sprintf('
				<div class="form-group %s">
					%s
					%s
				</div>',
				$errors->has($nom) ? 'has-error' : '',
				call_user_func_array(['Form', $type], [$nom, $valeur, $attributes]),
				$errors->first($nom, '<small class="help-block">:message</small>')
			);
		});		
	}

	private function registerFormSubmit()
	{
		FormBuilder::macro('button_submit', function($texte)
		{
			return FormBuilder::submit($texte, ['class' => 'btn btn-info pull-right']);
		});		
	}

	private function registerHtmlButtonBack()
	{	
		HtmlBuilder::macro('button_back', function()
		{
			return '<a href="javascript:history.back()" class="btn btn-primary">
					<span class="glyphicon glyphicon-circle-arrow-left"></span> Retour
				</a>';
		});			
	}

}

Ce n'est qu'un exemple et le codage pourrait être différent, c'est le principe qui importe. On pourrait aussi localiser ailleurs les macros et se contenter de les appeler à partir du provider. C'est alors juste une question d'organisation du code. Il est évident que les providers ne sont pas faits pour accueillir un code volumineux, comme toute classe d'ailleurs. Dans le cadre de ce cours on va se contenter de cette présentation. 

Il ne reste plus qu'à informer Laravel que notre provider existe dans config/app.php  :

<?php
/*
 * Application Service Providers...
 */
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
Collective\Html\HtmlServiceProvider::class,
App\Providers\HtmlMacrosServiceProvider::class,

Normalement le formulaire de création d'un article devrait encore fonctionner :

Le formulaire généré par les macros
Le formulaire généré par les macros

On a ainsi pu assainir la vue au niveau du formulaire.

Les templates

Mais il reste encore des éléments du framework dans notre vue : la grille et le panel. Là une macro ne serait pas vraiment adaptée, il vaut mieux créer un template pour ce formulaire qui pourra servir pour d'autres formulaires (app/views/template_form.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">
				@yield('titre')
			</div>
			<div class="panel-body"> 
				@yield('formulaire')
			</div>
		</div>
		{!! Html::button_back() !!}
	</div>
@stop

Je vous rappelle le contenu du template principal (app/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>

Maintenant on peut avoir une vue très épurée pour notre formulaire :

@extends('template_form')

@section('titre')
    Ajout d'un article
@stop

@section('formulaire')
    {!! Form::open(['route' => 'post.store']) !!}
		{!! Form::control('text', $errors, 'titre', 'Titre') !!}
		{!! Form::control('textarea', $errors, 'contenu', 'Contenu') !!}
		{!! Form::control('text', $errors, 'tags', 'Entrez les tags séparés par des virgules') !!}
		{!! Form::button_submit('Envoyer !') !!}
	{!! Form::close() !!}
@endsection

Plus aucune trace de framework. Quelles que soient les modifications d'interface ultérieures cette vue n'aura pas à être modifiée. Si on change de framework on a juste le template général et celui des formulaires à modifier.

Vous devez bien organiser vos templates pour avoir du code efficace, ce qui dépend évidemment de votre application.

Si vous avez beaucoup de vues il est judicieux de créer des dossiers pour les classer. Par exemple vous pouvez créer un dossier "templates".

Une autre organisation du code peut amener à créer des vues partielles à insérer dans la vue finale, on parle ainsi de "partials". C'est très utilisé par exemple pour les barres de message d'erreur. Il existe ainsi de nombreuse façons d'organiser les vues, l'essentiel est d'arriver à un code simple, lisible et facile à maintenir.

En résumé

  • L'utilisation de macros permet d'avoir des vues indépendantes du framework utilisé.

  • Un service provider permet de faire des initialisations.

  • La création de templates permet de bien organiser le code des vues.

  • L'utilisation de dossiers pour classer les vues est souvent nécessaire lorsqu'il y a beaucoup de vues.

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