Shopping : la maintenance

Pour toute application on a forcément à faire de la maintenance : évolution du code, mises à jour… Il faut pouvoir rendre le site inaccessible par moment pour effectuer des changements impactants mais il faut quand même pouvoir accéder au site en tant qu’administrateur. D’autre part mettre en cache certaines choses (les routes et la configuration) améliore les performances. Nous allons nous occuper de ça dans cet article.

Vous pouvez télécharger un ZIP du projet ici.

Le mode maintenance

Laravel permet de mettre une application en mode maintenance avec une commande :

php artisan down

On tombe alors sur une page avec ce message :

On ne peut pas vraiment dire que c’est explicite et en plus comment maintenant accéder au site ?

On revient en mode normal avec cette commande :

php artisan up

On peut aussi préciser le texte de la page :

php artisan down --message="Site en maintenance. Un peu de patience"

On peut ajouter une adresse IP autorisée :

php artisan down --message="Site en maintenance. Un peu de patience" --allow=127.0.0.1

On peut aussi complètement changer le look de la page d’erreur en créant une page resources/views/errors/503.blade.php.

Mais toutes ces commande avec Artisan ne sont pas pratique, et il faut un accès SSH, alors on va faciliter tout ça avec une interface propre.

Contrôleur et route

On crée un nouveau contrôleur :

php artisan make:controller Back\MaintenanceController --resource

On ne garde que les méthodes edit et update.

On ajoute les deux routes :

Route::prefix('admin')->middleware('admin')->namespace('Back')->group(function () {
    ...
    Route::name('maintenance.edit')->get('maintenance/modification', 'MaintenanceController@edit');
    Route::name('maintenance.update')->put('maintenance', 'MaintenanceController@update');
});

On code la méthode edit :

public function edit(Request $request)
{
    $active = app()->isDownForMaintenance();
    $ip = $request->ip();
    $message = config('messages.maintenance');     

    return view('back.maintenance.edit', compact('active', 'ip', 'message'));
}

On fait plusieurs choses :

  • on vérifie si le site est en mode maintenance
  • on lit l’adresse IP
  • on prépare le message à afficher sur la page de maintenance

On ajoute le message dans config.messages (en anticipant pour les messages de mise à jour) :

return [
    ...
    'maintenance' => 'La boutique est en mode maintenance.',
    'maintenanceupdated' => 'Le mode maintenance a bien été mis à jour.',
    'cacheupdated' => 'Le cache a bien été mis à jour.',
];

Un composant pour la vue

On crée un nouveau composant :

php artisan make:component CheckboxCustom

<?php

namespace App\View\Components;

use Illuminate\View\Component;

class CheckboxCustom extends Component
{
    public $name;
    public $label;
    public $value;
    public $off;
    public $on;

    /**
     * Create a new component instance.
     *
     * @return void
     */
    public function __construct($name, $label, $off, $on, $value = '')
    {
        $this->name = $name;
        $this->label = $label;
        $this->off = $off;
        $this->on = $on;
        $this->value = $value;
    }

    /**
     * Get the view / contents that represent the component.
     *
     * @return \Illuminate\View\View|string
     */
    public function render()
    {
        return view('components.checkbox-custom');
    }
}

<div class="form-group">
  <div class="custom-control custom-switch custom-switch-off-{{ $off }} custom-switch-on-{{ $on }}">
    <input type="checkbox" class="custom-control-input" 
      id="{{ $name }}" 
      name="{{ $name }}" 
      @if(old($name, $value)) checked @endif>
    <label class="custom-control-label" for="{{ $name }}">{{ $label }}</label>
  </div>
</div>

La vue du formulaire

On crée une vue pour le formulaire :

@extends('back.layout')

@section('main') 
  <div class="container-fluid"> 
    @if(session()->has('alert'))
      <div class="alert alert-warning alert-dismissible fade show" role="alert">
        {{ session('alert') }}
        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
    @endif
    <div class="row">
      <div class="col-sm-12">
        <div class="card">
          <h5 class="card-header">Mode maintenance</h5>
        
          <form method="POST" action="{{ route('maintenance.update') }}">
            <div class="card-body">
              @method('PUT')
              @csrf

              <x-inputbs4
                name="ip"
                type="text"
                label="Adresse IP autorisée"
                :value="$ip"
                :required="true"
              ></x-inputbs4>

              <x-inputbs4
                name="message"
                type="text"
                label="Message à afficher"
                :value="$message"
                :required="true"
              ></x-inputbs4>

              <x-checkbox-custom
                name="active"
                label="Mode maintenance"
                off="success"
                on="danger"
                :value="$active"
              ></x-checkbox-custom>

              <div class="form-group row mb-0">
                <div class="col-md-12">
                   <button type="submit" class="btn btn-primary">Enregistrer</button>
                </div>
              </div>
              
            </div>            
          </form>

        </div>
      </div>
    </div>
  </div>
@endsection

On complète config.titles pour le titre de la page :

return [

    ...

    'maintenance' => [
        'edit' => 'Maintenance',
    ],
];

Et on obtient le formulaire avec l’url …/admin/maintenance/modification :

La mise à jour

Pour la mise à jour des données on code la méthode update du contrôleur :

use Illuminate\Support\Facades\Artisan;

...

public function update(Request $request)
{
    $request->validate([
        'ip' => 'required|ip',
        'message' => 'required|string|max:255',
    ]);

    Artisan::call($request->has('active') ? 'down --allow=' . $request->ip . ' --message="' . $request->message . '"' : 'up');

    return back()->withInput()->with('alert', config('messages.maintenanceupdated'));
}

On fait la validation pour mes deux entrées. On utilise Artisan pour la mise à jour. Au retour on affiche un message :

La mise en cache

On va maintenant s’occuper de la mise en cache des routes et de la configuration. Là aussi on dispose des commandes config:cache et route: cache avec leur réciproque config:clear et route:clear. Mais on va faire quelque chose de plus convivial.

Dans la méthode edit du contrôleur on ajoute des données pour le cache :

public function edit(Request $request)
{
    $active = app()->isDownForMaintenance();
    $ip = $request->ip();
    $message = config('messages.maintenance');

    $path = base_path('bootstrap/cache/');        
    $config = file_exists($path . 'config.php');
    $route = file_exists($path . 'routes-v7.php');

    return view('back.maintenance.edit', compact('active', 'ip', 'message', 'config', 'route'));
}

Les fichiers du cache sont ici :

Leur présence indique que le cache est actif, c’est ce qu’on vérifie dans la méthode.

On crée la méthode cache dans le contrôleur pour la mise à jour :

public function cache(Request $request)
{
    Artisan::call($request->has('config') ? 'config:cache' : 'config:clear');
    Artisan::call($request->has('route') ? 'route:cache' : 'route:clear');

    $request->session()->flash('alert', config('messages.cacheupdated'));

    return back();
}

Avec sa route :

Route::prefix('admin')->middleware('admin')->namespace('Back')->group(function () {
    ...
    Route::name('cache.update')->put('cache', 'MaintenanceController@cache');
});

Dans la vue on ajoute la partie pour le cache :

@extends('back.layout')

@section('main') 
    ...

      <div class="col-sm-12">
        <div class="card">
          <h5 class="card-header">Cache</h5>
        
          <form method="POST" action="{{ route('cache.update') }}">
            <div class="card-body">
              @method('PUT')
              @csrf

              <x-checkbox-custom
                name="config"
                label="Cache de la configuration"
                off="info"
                on="warning"
                :value="$config"
              ></x-checkbox-custom>

              <x-checkbox-custom
                name="route"
                label="Cache des routes"
                off="info"
                on="warning"
                :value="$route"
              ></x-checkbox-custom>

              <div class="form-group row mb-0">
                <div class="col-md-12">
                   <button type="submit" class="btn btn-primary">Enregistrer</button>
                </div>
              </div>
              
            </div>            
          </form>

        </div>
      </div>
    </div>
  </div>
@endsection

On affiche un message à la mise à jour :

Attention lorsque vous mettez en cache : les mises à jour du code ne sont plus actives !

Le menu

Il ne nous reste plus qu’à compléter le menu dans back.layout pour accéder à notre formulaire :

<li class="nav-item has-treeview {{ menuOpen(
      'shop.edit',
      'shop.update',
      'pays.index',
      'pays.edit',
      'pays.create',
      'plages.edit',
      'colissimos.edit',
      'etats.index', 
      'etats.edit', 
      'etats.create', 
      'etats.destroy.alert',
      'pages.index',
      'pages.edit',
      'pages.create',
      'pages.destroy.alert',
      'maintenance.edit'
  ) }}">
  <a href="#" class="nav-link {{ currentRouteActive(
      'shop.edit',
      'shop.update',
      'pays.index',
      'pays.edit',
      'pays.create',
      'plages.edit',
      'colissimos.edit',
      'etats.index', 
      'etats.edit', 
      'etats.create', 
      'etats.destroy.alert',
      'pages.index',
      'pages.edit',
      'pages.create',
      'pages.destroy.alert',
      'maintenance.edit'
    ) }}">
    <i class="nav-icon fas fa-cogs"></i>
    <p>
      Administration
      <i class="right fas fa-angle-left"></i>
    </p>
  </a>
  <ul class="nav nav-treeview">

    ...

    <x-menu-item :href="route('maintenance.edit')" :sub=true :active="currentRouteActive('maintenance.edit')">
      Maintenance
    </x-menu-item>

  </ul>
</li>

Conclusion

Avec cet article s’achève sans doute cette série sur la création d’une application de commerce en ligne en espérant qu’elle vous a plue.

Print Friendly, PDF & Email

10 commentaires sur “Shopping : la maintenance

  1. Bonjour BestMomo,
    je tiens vraiment à vous remercier pour ce cours qui m’a permis d’apprendre tellement de choses. je l’ai suivi et appliqué du début jusqu’à la fin. Encore une fois merci. Continuer comme ça! vous êtes vraiment le Best!

  2. Bonsoir.

    Débutant avec Laravel, j’ai suivi votre cours sur https://openclassrooms.com/ que j’ai trouvé excellent : clair et complet.
    Cette suite d’articles m’a permis de mieux comprendre l’architecture d’une app Laravel, et j’y ai appris également énormément.
    Beaucoup de contenu utile, réutilisable (composants de vue, admin …).

    Merci !

  3. Sa a été pour moi un Regal j’ai appris beaucoup en très peu de temps et vraiment merci. A chaque leçon il y’a toujours quelque chose a découvrir que ce soit dans la syntaxe ou l’utilisation de plug-in Vuejs en passant dans la découverte de nouveau paquetage sans oublié se cours sur merise qui ne fesait pas partir du projet, cette boutique en ligne me confirme la grande usine a gaz qu’on peut utiliser dans tous nos projet temps au quotidien que dans de grande entreprise.
    Avec cette écosystèmes qui est grandissant autour de laravel (livewire, alpineJs) si ce n’est pour citer ceux la nous attendrons nous a de nouveaux cours comme fut le cas avec Vuejs et bootstrap ?
    PS: j’espère que oui.

      1. bonjour
        merci et tres bon travail bestmomo
        pour Cache des routes quand je l’active j’ai cette erreur :
        LogicException
        Unable to prepare route [home] for serialization. Another route has already been assigned name [home].

        et quand j’active Mode maintenance
        rien ne se passe !!

        merci

Laisser un commentaire