在 Laravel 应用中启用/禁用功能

Enabling / Disabling Features in a Laravel App

我正在构建一个 Laravel 应用程序,它具有许多不同的功能。我希望能够根据特定域的要求启用或禁用它们。目前,我的配置中有一系列标志,例如:

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

...等等。

然后在我的控制器和视图中,我检查这些配置值以查看我是否应该显示某些内容、允许某些操作等。我的应用开始被这些检查到处污染。

在 Laravel 应用程序中是否有管理功能的最佳实践方法?

看起来您正在根据配置值进行硬编码以启用或禁用某些功能。我建议您根据命名路由而不是配置值来控制事物。

  1. 将所有路线作为一个整体或按功能分组。
  2. 为所有路由定义名称
  3. 通过路由名称控制enable/disable activity并记录在数据库中
  4. 使用 Laravel 中间件通过从请求对象获取当前路由名称并将其与数据库匹配来检查特定功能是启用还是禁用..

所以你不会在每个地方重复相同的条件并使你的代码膨胀.. 这是一个示例代码,向您展示如何检索所有路由,您可以匹配路由组名称以进一步处理以匹配您的情况。

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>";
});

这是一个示例中间件处理程序,您可以在其中通过与数据库中已存储的内容进行匹配来检查特定功能是否处于活动状态。

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

这在技术上称为功能标志 - https://martinfowler.com/articles/feature-toggles.html

取决于您的要求、config/database 中的标志、部署等...

但它基本上是代码中的 if 并且不能干净。

Laravel 个包:

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

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

一些服务:

https://launchdarkly.com/

https://bullet-train.io/

https://configcat.com/

另请参阅前端 https://marketingplatform.google.com/about/optimize/

我在尝试实施多个酒店提供商时遇到了同样的问题。

我做的是使用服务容器。

首先您将为每个域创建class具有他的特征:

  • 喜欢Doman1.php,Domain2.php
  • 然后在每一个里面添加你的逻辑。

然后您将在您的应用程序服务提供商中使用绑定将域与 class 绑定以供使用。

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

注意 你可以使用通用 class 来保持所有域的特性然后在你的 classes 中使用通用 class

最后,在您的控制器中,您可以检查您的域,然后使用您要使用的 class

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

假设只有 HTTP 请求需要这些功能。

我将创建一个默认的 Features 基础 class,其中包含所有默认标志:

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;
    }
}

然后我会为每个域扩展 class 并设置该域所需的覆盖:

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

然后创建一个中间件来将功能 Class 绑定到容器:

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);
    }
}

不要忘记将此中间件附加到您的路由:附加到组或每条路由。

在此之后,您可以在控制器中输入提示功能 class:

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

Laravel 非常适合这个,您甚至可以将您的功能存储在数据库中,并在域之间创建关系。

我建议使用 Gates 和 Policies,这将使您更好地控制控制器和 blade 模板。这意味着您从数据库注册门或对其进行硬编码。

例如,如果您的系统中有一个带按钮的出口产品功能,并且您想让某些用户可以使用该功能,您可以使用业务逻辑注册入口。

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

然后您可以在控制器中执行以下操作

<?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
    }
}

这是您的 blade 模板的示例:

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

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

更多信息请访问 https://laravel.com/docs/5.8/authorization

你这里的案例很有趣。查看包含一些您通常需要的方法的 Feature 接口或抽象 class 可能会很有趣。

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(...);

    ...
}

您甚至可以将它们分为 ExecutableFeatureRenderableFeature

进一步研究某种工厂 class 可以让生活更轻松。

// 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')

我在我的案例中所做的是在数据库上创建一个新的 table,例如您可以将其命名为 Domains

添加所有特定功能,那些可以在某些域中显示但不能在其余域中显示的功能,作为 table 的列作为布尔值的位。就像,就我而言,allow_multiple_bookingsuse_company_card...随便什么。

然后,考虑创建一个 class Domain 及其各自的存储库,并在您的代码中询问这些值,尝试将尽可能多的逻辑推入您的域(您的模型,应用服务等)。

例如,如果请求预订的域只能请求一个或多个,我不会检查 RequestBooking 的控制器方法。

相反,我在 RequestBookingValidatorService 上执行此操作,它可以检查预订日期时间是否已过,用户是否启用了信用卡,...或者允许此操作来自的域请求更多超过一个预订(如果已经有的话)。

这增加了可读性的便利性,因为您已将此决定推送到您的应用程序服务。此外,我发现每当我需要一个新功能时,我都可以使用 Laravel(或 Symfony)迁移在 table 上添加该功能,我什至可以用我的值更新它的行(你的域)想要我编码的同一个提交。