Activation / désactivation des fonctionnalités dans une application Laravel


10

Je crée une application Laravel, qui a un certain nombre de fonctionnalités différentes. Je veux pouvoir les activer ou les désactiver en fonction des besoins d'un domaine particulier. Actuellement, j'ai dans ma configuration une série de drapeaux tels que:

'is_feature_1_enabled' => true,
'is_feature_2_enabled' => false,

... etc.

Ensuite, dans mes contrôleurs et mes vues, je vérifie ces valeurs de configuration pour voir si je dois ou non afficher quelque chose, autoriser certaines actions, etc. Mon application commence à être polluée par ce type de contrôles partout.

Existe-t-il une meilleure méthode de gestion des fonctionnalités dans une application Laravel?


désactiver les routes au lieu du contrôleur? ou en utilisant un middleware peut
Berto99

Fonctionnalité! == page.
StackOverflowNewbie


Cela pourrait en fait être une solution. Vous voulez faire de votre commentaire une réponse?
StackOverflowNewbie

mh pas de problème, ce n'est pas une réponse, l'important est que ça puisse vous aider
Berto99

Réponses:


4

Ceci est techniquement appelé indicateurs de fonctionnalité - https://martinfowler.com/articles/feature-toggles.html

dépend de vos besoins, drapeaux dans config / base de données, déploiement, etc ...

Mais c'est fondamentalement si c'est dans le code et ne peut pas être propre.

Forfaits Laravel:

https://github.com/alfred-nutile-inc/laravel-feature-flag

https://github.com/francescomalatesta/laravel-feature

Quelques services:

https://launchdarkly.com/

https://bullet-train.io/

https://configcat.com/

Regardez également https://marketingplatform.google.com/about/optimize/ pour le frontend.


1
Vous pouvez trouver un exemple de projet laravel avec une fonctionnalité signalant ici: github.com/configcat/php-sdk/tree/master/samples/laravel
Peter

7

J'ai rencontré le même problème lorsque j'ai essayé de mettre en œuvre plusieurs fournisseurs d'hôtels.

Ce que j'ai fait, c'est utiliser un conteneur de service.

d' abord , vous allez créer la classe pour chaque domaine avec ses caractéristiques:

  • comme Doman1.php, Domain2.php
  • puis à l'intérieur de chacun de ceux-ci vous ajouterez votre logique.

alors vous allez utiliser la liaison dans votre fournisseur de services d'application pour lier le domaine avec la classe à utiliser.

$this->app->bind('Domain1',function (){
       return new Domain1();
    });
    $this->app->bind('Domain2',function (){
        return new Domain2();
    });

Notez que vous pouvez utiliser une classe générale qui contient les fonctionnalités va avec tous les domaines, puis utilisez cette classe générale dans vos classes

Enfin, dans votre contrôleur, vous pouvez vérifier votre domaine puis utiliser la classe que vous allez utiliser

    app(url('/'))->methodName();

0

Il semble que vous codiez en dur sur la base des valeurs de configuration pour activer ou désactiver certaines fonctionnalités. Je vous recommande de contrôler les choses en fonction des routes nommées plutôt que de la valeur de configuration.

  1. Regroupez tout l'itinéraire dans son ensemble ou par fonctionnalité.
  2. Définir le nom de tous les itinéraires
  3. Contrôlez l'activité d'activation / désactivation par nom de route et enregistrez dans la base de données
  4. Utilisez le middleware Laravel pour vérifier si une fonction particulière est activée ou désactivée en obtenant le nom de l'itinéraire actuel à partir de l'objet de demande et en le faisant correspondre avec la base de données.

vous n'aurez donc pas les mêmes conditions à se répéter partout et à gonfler votre code .. voici un exemple de code vous montrant comment récupérer toutes les routes, et vous pouvez faire correspondre le nom du groupe de routes pour poursuivre le traitement en fonction de votre situation.

Route::get('routes', function() {
$routeCollection = Route::getRoutes();

echo "<table >";
    echo "<tr>";
        echo "<td width='10%'><h4>HTTP Method</h4></td>";
        echo "<td width='10%'><h4>Route</h4></td>";
        echo "<td width='80%'><h4>Corresponding Action</h4></td>";
    echo "</tr>";
    foreach ($routeCollection as $value) {
        echo "<tr>";
            echo "<td>" . $value->getMethods()[0] . "</td>";
            echo "<td>" . $value->getPath() . "</td>";
            echo "<td>" . $value->getName() . "</td>";
        echo "</tr>";
    }
echo "</table>";
});

et voici un exemple de gestionnaire de middleware où vous pouvez vérifier si une fonctionnalité particulière est active en faisant correspondre avec ce que vous avez déjà stocké dans votre base de données.

public function handle($request, Closure $next)
    {
        if(Helper::isDisabled($request->route()->getName())){
             abort(403,'This feature is disabled.');
        }
        return $next($request);
    }

1
Cela suppose que les fonctionnalités correspondent aux pages du site, non? Ce n'est pas le cas. Une fonctionnalité peut être un extrait de page (par exemple, une carte Google s'affiche dans la barre latérale), ou une sorte de fonctionnalité (par exemple, les utilisateurs peuvent exporter des données).
StackOverflowNewbie

vous avez raison, mais voulez-vous dire certains blocs qui s'affichent sur différentes pages? quelles sont vos contraintes pour l'afficher? page spécifique sage ou sur toutes les pages que vous affichez
Akram Wahid

Les fonctionnalités peuvent être une page entière, ou seulement une partie d'une page, ou simplement certaines fonctionnalités.
StackOverflowNewbie

0

En supposant que ces fonctionnalités ne sont nécessaires que pour les requêtes HTTP.

Je créerais une Featuresclasse de base par défaut avec tous les drapeaux par défaut:

Class Features {
    // Defaults
    protected $feature1_enabled = true;
    protected $feature2_enabled = true;

    public function isFeature1Enabled(): bool
    {
        return $this->feature1_enabled;
    }

    public function isFeature2Enabled(): bool
    {
        return $this->feature2_enabled;
    }
}

Ensuite, j'étendrais cette classe pour chaque domaine et définirais les remplacements nécessaires pour ce domaine:

Class Domain1 extends Features {
    // override
    protected $feature1_enabled = false;
}

Créez ensuite un middleware pour lier la classe d'entités au conteneur:

class AssignFeatureByDomain
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        switch ($_SERVER['HTTP_HOST']) {
            case 'domain1':
                app()->bind(Features::class, Domain1::class);
                break;
            default:
                abort(401, 'Domain rejected');
        }

        return $next($request);
    }
}

N'oubliez pas d'attacher ce middleware à vos itinéraires: à un groupe ou pour chaque itinéraire.

Après cela, vous pouvez taper votre classe de fonctionnalités dans vos contrôleurs:

public function index(Request $request, Features $features)
{
    if ($features->isFeature1Enabled()) {
        //
    }
}

0

Laravel est génial avec cela, vous pouvez même stocker vos fonctionnalités dans db et créer une relation entre le domaine.

Je recommanderais d'utiliser des portes et des politiques, qui vous donneront un meilleur contrôle dans vos contrôleurs et modèles de lame. Cela signifie que vous enregistrez les portes à partir de votre base de données ou que vous les codez en dur.

Par exemple, si vous avez une fonctionnalité d'exportation de produits avec un bouton dans votre système et que vous souhaitez rendre cette fonctionnalité disponible pour certains utilisateurs, vous pouvez enregistrer des portes avec la logique métier.

//Only admins can export products
Gate::define('export-products', function ($user) {
    return $user->isAdmin;
});

Ensuite, vous pouvez effectuer les opérations suivantes dans les contrôleurs

<?php

namespace App\Http\Controllers;

use App\Product;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class ProductsController extends Controller
{
    /**
     * Export products
     *
     * @param  Request  $request
     * @param  Post  $post
     * @return Response
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function export(Request $request)
    {
        $this->authorize('export-products');

        // The current user can export products
    }
}

Voici un exemple pour vos modèles de lames:

@can('export-products', $post)
    <!-- The Current User Can export products -->
@endcan

@cannot('export-products')
    <!-- The Current User Can't export products -->
@endcannot

plus d'informations disponibles sur https://laravel.com/docs/5.8/authorization


0

Cas intéressant que vous avez ici. Il peut être intéressant d'examiner une Featureinterface ou une classe abstraite contenant quelques méthodes dont vous avez généralement besoin.

interface Feature
{
    public function isEnabled(): bool;

    public function render(): string;

    // Not entirely sure if this would be a best practice but the idea is to be
    // able to call $feature->execute(...) on any feature.
    public function execute(...);

    ...
}

Vous pourriez même les diviser en ExecutableFeatureet RenderableFeature.

Plus loin, une sorte de classe d'usine pourrait être facilitée.

// Call class factory.
Feature::make('some_feature')->render();
...->isEnabled();

// Make helper method.
feature('some_feature')->render();

// Make a blade directives.
@feature('some_feature')
@featureEnabled('some_feature')

0

Ce que j'ai fait dans mon cas était de créer une nouvelle table sur la base de données, vous pouvez l'appeler Domainspar exemple.

Ajoutez toutes les fonctionnalités spécifiques, celles qui pourraient être affichées sur certains domaines mais pas dans les autres, sous forme de colonnes pour cette table sous forme de bits pour les valeurs booléennes. Comme, dans mon cas allow_multiple_bookings, use_company_card... peu importe.

Ensuite, envisagez de créer une classe Domainet son référentiel respectif, et demandez simplement ces valeurs sur votre code, en essayant de pousser autant que possible cette logique dans votre domaine (votre modèle, les services d'application, etc.).

Par exemple, je ne vérifierais pas la méthode du contrôleur pour savoir RequestBookingsi le domaine qui demande une réservation ne peut en demander qu'un ou plusieurs.

Au lieu de cela, je le fais sur un RequestBookingValidatorServicequi peut vérifier si la date de réservation est passée, l'utilisateur a une carte de crédit activée, ... ou le domaine dont provient cette action est autorisé à demander plus d'une réservation (et puis s'il a déjà tout).

Cela ajoute la commodité de la lisibilité, car vous avez poussé cette décision à vos services d'application. De plus, je trouve que chaque fois que j'ai besoin d'une nouvelle fonctionnalité, je peux utiliser les migrations Laravel (ou Symfony) pour ajouter cette fonctionnalité sur la table et je peux même mettre à jour ses lignes (vos domaines) avec les valeurs que je veux sur le même commit que j'ai codé.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.