• 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 (2/2)

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

Nous allons continuer notre démarche de nettoyage des vues qui nous sert en fait de prétexte pour explorer le conteneur de dépendances qui constitue le cœur de Laravel. Pourquoi conteneur ? Parce que c'est une sorte de boîte dans laquelle on peut mettre plein de choses. Je vous ai déjà parlé de l'injection de dépendance quand on a vu les contrôleurs. C'est le conteneur qui permet de gérer facilement ces injections. Nous avons déjà utilisé le conteneur pour gérer la résolution des dépendances. Dans ce chapitre je vais poursuivre l'exemple des macros vu au chapitre précédent mais en utilisant maintenant une classe.

La nouvelle vue

Où en sommes-nous du nettoyage du code du formulaire de création d'un article ? Dans le précédent chapitre on l'a séparé en deux avec un template (resources/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>
@endsection

Et le formulaire lui-même épuré grâce aux macros (resources/views/post/add.blade.php):

@extends('template_form')

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

@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

Ce que je vous propose maintenant c'est de remplacer les macros par des méthodes d'une classe dédiée et d'ajouter le traitement du panneau. Le but étant de se passer du template et d'aboutir à cette vue :

@extends('template')

@section('contenu')
    <br>
	<div class="col-sm-offset-3 col-sm-6">
		{!! Panel::
			head(
				'Ajout d\'un article'
			)
			->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()
			)
            ->type('primary')
		!!}
		{!! Html::button_back() !!}
	</div>
@endsection

On ne voit évidement aucune différence pour le formulaire lui-même puisque celle-ci va se situer au niveau de l'intendance. Par contre apparaît une nouvelle classe (Panel) avec des méthodes pour construire le panneau.

Évidemment l'aspect de la vue lui ne doit pas changer :

La vue de création d'un article
La vue de création d'un article

Organisation du code

Dans le précédent chapitre on a mis le code des macros dans le service provider. C'était simple et efficace. Cette solution est envisageable lorsque le code n'est pas trop fourni ou complexe, sinon il devient vite indispensable de le répartir dans plusieurs autres classes.

La notion de "service" est suffisamment générale pour inclure la plupart des tâches à réaliser dans une application. Je vous propose de créer un service pour gérer ces fonctionnalités pour les vues. On va créer les dossiers Services et Html pour mettre tout le code dont nous allons avoir besoin pour ce chapitre. Au final nous aurons cette organisation :

L’organisation des fichiers

Voyons un peu à quoi tout ça va servir :

  • FormBuilder : va contenir les méthodes control etbutton_submit, les deux qui concernent le formulaire,

  • HtmlBuilder : va contenir la méthodebutton_back,

  • HtmlServiceProvider : c'est le service provider qui va initialiser tous les composants,

  • PanelBuilder : va contenir les méthodes pour le panneau,

  • PanelFacade : va générer la façade pour le panneau, on pourra ainsi utiliser une syntaxe simplifiée.

Les builders

FormBuilder et HtmlBuilder

Pour créer ces deux builders on va juste reprendre le code des macros en le mettant dans deux classes séparées. Donc pour les deux méthodes du formulaire on aura la classe FormBuilder :

<?php

namespace App\Services\Html;

use Collective\Html\FormBuilder as CollectiveFormbuilder;
use Request;

class FormBuilder extends CollectiveFormbuilder
{

	public function control($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>')
		);
	}	

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

}

Vous remarquez que je me contente d'étendre la classeFormBuilder du package pour en conserver toutes les méthodes.

Et pour ce qui est du html la classe HtmlBuilder :

<?php

namespace App\Services\Html;

use Collective\Html\HtmlBuilder as CollectiveHtmlBuilder;

class HtmlBuilder extends CollectiveHtmlBuilder
{

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

}

Ici aussi je me contente d'étendre la classe du package pour ajouter ma méthode.

PanelBuilder

Pour le panneau on va aussi créer une classe (PanelBuilder) :

<?php

namespace App\Services\Html;

class PanelBuilder
{

  protected $type;
  protected $head;
  protected $body;
  protected $foot;

  public function __construct($type = 'default', $head = null, $body = null, $foot = null) 
  {
    $this->type = $type;
    $this->head = $head;
    $this->body = $body;
    $this->foot = $foot;
  }

  public function __toString() 
  {
    $s = '<div class="panel panel-'.$this->type.'">';
    if ($this->head) 
    {
      $s .= '<div class="panel-heading">'.$this->head.'</div>';
    }
    if ($this->body) 
    {
      $s .= '<div class="panel-body">'.$this->body.'</div>';
    }
    if ($this->foot) 
    {
      $s .= '<div class="panel-footer">'.$this->foot.'</div>';
    }
    $s .= "</div>";
    return $s;
  }

  public function type($type) 
  {
    $this->type = $type;
    return $this;
  }

  public function head($head) 
  {
    $this->head = $head;
    return $this;
  }

  public function body($body) 
  {
    $this->body = $body;
    return $this;
  }

  public function footer($foot) 
  {
    $this->foot = $foot;
    return $this;
  }

}

J'ai prévu de pouvoir transmettre les paramètres soit dans le constructeur, soit au moyen de méthode dédiées pour un peux généraliser le code.

Il faut aussi créer la façade pour la classePanel :

<?php

namespace App\Services\Html;
 
use Illuminate\Support\Facades\Facade;
 
class PanelFacade extends Facade
{
 
    protected static function getFacadeAccessor() { return 'panel'; }
 
}

La syntaxe d'une façade est toute simple, on renvoie juste la référence 'panel'. Nous allons voir bientôt d'où sort cette référence et comment on la nomme.

Service provider et façade

C'est dans le provider que va se situer l'intendance pour le framework. En voici le code :

<?php

namespace App\Services\Html;

use Illuminate\Support\ServiceProvider;

class HtmlServiceProvider extends ServiceProvider
{

	public function register()
	{
		$this->registerHtmlBuilder();
		$this->registerFormBuilder();
		$this->registerPanelBuilder();
	}

	protected function registerHtmlBuilder()
	{
        $this->app->singleton('html', function ($app) {
            return new HtmlBuilder($app['url'], $app['view']);
        });
	}

	protected function registerFormBuilder()
	{
        $this->app->singleton('form', function ($app) {
            $form = new FormBuilder($app['html'], $app['url'], $app['view'], $app['session.store']->getToken());

            return $form->setSessionStore($app['session.store']);
        });
	}

	protected function registerPanelBuilder()
	{
		$this->app->singleton('panel', function()
		{
			return new PanelBuilder;
		});
	}

}

Je vous ai déjà dit que les providers servent à enregistrer des choses : des liaisons de dépendance, des événements, des éléments de configuration. Ici nous enregistrons les méthodes pour les formulaires et le html. Il existe déjà un provider dans le package :

Le provider du package

Mais je suis obligé de surcharger la plupart de ses méthodes. 

La méthode la plus importante d'un provider estregister. Lorsqu'une requête est prise en charge par Laravel on a besoin de tout un tas de choses  : configuration des erreurs, détection de l'environnement, définition des middlewares que la requête devra traverser, vérification du CSRF... A un moment on va aussi passer en revue les providers et exécuter leur méthoderegister, les uns après les autres dans leur ordre d'inscription. On en profite pour déclarer toutes les dépendances dans le conteneur.

Le conteneur est un peu le grand sac de l'application qui permet de tout ranger et de tout retrouver. Par exemple avec cette méthode du provider :

<?php
protected function registerPanelBuilder()
{
    $this->app->singleton('panel', function()
	{
		return new PanelBuilder;
	});
}

On va dire au conteneur : tu vas te souvenir de 'panel' et si j'en ai besoin j'aurais juste à te dire 'panel' et tu me renverras une instance dePanelBuilder. Le conteneur va donc créer un lien (singleton) entre la référence 'panel' et la classePanelBuilder.

Du coup vous allez mieux comprendre le code de la façade :

<?php
class PanelFacade extends Facade
{
 
    protected static function getFacadeAccessor() { return 'panel'; }
 
}

Là on dit que si on a la façadePanel on doit aller chercher dans le conteneur l'instance de la classe qui correspond à 'panel'. Et comme on a pris le soin dans le provider de créer un lien entre cette référence et le nom de la classe ça va fonctionner !

Maintenant si j'écris :

<?php
Panel::head()

Laravel va comprendre que je parle de la façade, que je veux une instance de la classePanelBuilder et que je veux utiliser la méthodehead.

Ça fonctionnerait aussi avec ce code :

<?php
app('panel')->head()

Ici je vais chercher directement dans le conteneur une instance de la classe. Mais je pourrais aussi l'écrire ainsi :

<?php
App::make('panel')->head()

Cette fois j'utilise la façade de l'application (donc du conteneur parce que l'application en est une extension) et je demande de créer une instance de la classe référencée par 'panel'.

Il ne reste plus qu'à informer Laravel que notre provider existe. Cela se fait dansconfig/app.php. Pour installer le package on a écrit :

<?php
Collective\Html\HtmlServiceProvider::class,

On va changer cette ligne pour pointer maintenant sur notre provider :

<?php
App\Services\Html\HtmlServiceProvider::class,

Il ne nous reste plus qu'à renseigner la façade pour le panel :

<?php
'Panel'     => App\Services\Html\PanelFacade::class,

Et tout devrait bien fonctionner !

Si vous rencontrez des difficultés, surtout si vous avez une erreur sur le fait qu'une classe n'existe pas, faites :

composer dumpautoload
php artisan clear-compiled

Les composants

Une évolution récente du package LaravelCollective autorise la création de composants. C'est une simplification de l'approche qui consiste à utiliser ce genre de syntaxe :

<?php
Form::component('button_back', 'components.form.button_back', ['nom']);

Cette déclaration doit être insérée dans la méthode boot d'un‌ Service Provider (pour que la façade Form soit active).

Le premier paramètre est le nom de l'élément.

Le second paramètre est destiné à localiser le fichier contenant le template, ici il sera donc localisé en resources/views/components/form/button_back.

Le dernier paramètre est un tableau qui permet de transmettre toutes les valeurs pour renseigner la vue. On peut définir des valeurs par défaut.

On peut donc facilement créer le template à partir de tout ça, par exemple :

<a href="javascript:history.back()" class="btn btn-primary">
    <span class="glyphicon glyphicon-circle-arrow-left"></span> {{ $nom }}
</a>

Ensuite on peut directement l'utiliser dans une vue :

{{ Form::button_back('Retour') }}

Ce qui devrait se traduire au final par ce code :

<a href="javascript:history.back()" class="btn btn-primary">
    <span class="glyphicon glyphicon-circle-arrow-left"></span> Retour
</a>

C'est une approche plus légère que celle proposée dans ce chapitre mais qui présente moins de souplesse et de possibilités. Vous avez donc ‌le choix selon vos besoins.

En résumé

  • Le conteneur de dépendances est la clé du fonctionnement de Laravel.

  • On enregistre un composant avec un service provider.

  • On simplifie la syntaxe d'accès à une classe avec une façade.

 

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