我正在构建一个Laravel应用程序,它具有许多不同的功能。我希望能够根据特定域的要求启用或禁用它们。目前,我的配置中有一系列标志,如:
'is_feature_1_enabled' => true,
'is_feature_2_enabled' => false,
等等
然后,在我的控制器和视图中,我检查这些配置值,看看我是否应该显示一些东西,允许某些操作,等等。我的应用程序开始被这些检查所污染。
是否有管理Laravel应用程序功能的最佳实践方法
这在技术上被称为功能标志-https://martinfowler.com/articles/feature-toggles.html
取决于您的需求、配置/数据库中的标志、推出等…
但它基本上是代码中的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/用于前端。
我在尝试实现多个酒店提供商时遇到了同样的问题。
我所做的是使用服务容器。
首先您将为每个域创建类。使用他的功能:
- 像Doman1.php,Domain2.php
- 然后在每一个里面,你会添加你的逻辑
然后您将在应用程序服务提供商中使用绑定来将域与要使用的类绑定。
$this->app->bind('Domain1',function (){
return new Domain1();
});
$this->app->bind('Domain2',function (){
return new Domain2();
});
注意您可以使用包含所有域的功能的通用类,然后在您的类中使用该通用类
最后,在你的控制器中,你可以检查你的域,然后使用你将使用的类
app(url('/'))->methodName();
看起来像是在根据配置值对事物进行硬编码,以启用或禁用某些功能。我建议您根据命名路由而不是配置值来控制事情。
- 将所有路线作为一个整体或按功能分组
- 定义所有管线的名称
- 通过路由名称和数据库中的记录控制启用/禁用活动
- 使用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);
}
假设这些功能仅用于HTTP请求。
我会创建一个带有所有默认标志的默认Features
基类:
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 Domain1 extends Features {
// override
protected $feature1_enabled = false;
}
然后创建一个中间件将Features类绑定到容器:
class AssignFeatureByDomain
{
/**
* Handle an incoming request.
*
* @param IlluminateHttpRequest $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);
}
}
不要忘记将这个中间件连接到您的路由:连接到一个组或每个路由。
之后,您可以在控制器中键入提示您的Features类:
public function index(Request $request, Features $features)
{
if ($features->isFeature1Enabled()) {
//
}
}
Laravel非常擅长这一点,您甚至可以将您的功能存储在数据库中,并在域之间创建关系。
我建议您使用Gates和Policies,这将使您能够更好地控制控制器和刀片模板。这意味着您可以从数据库中注册门或对其进行硬编码。
例如,如果您的系统中有一个带有按钮的导出产品功能,并且您想让某些用户可以使用该功能,则可以使用业务逻辑注册门。
//Only admins can export products
Gate::define('export-products', function ($user) {
return $user->isAdmin;
});
然后您可以在控制器中执行以下操作
<?php
namespace AppHttpControllers;
use AppProduct;
use IlluminateHttpRequest;
use AppHttpControllersController;
class ProductsController extends Controller
{
/**
* Export products
*
* @param Request $request
* @param Post $post
* @return Response
* @throws IlluminateAuthAccessAuthorizationException
*/
public function export(Request $request)
{
$this->authorize('export-products');
// The current user can export products
}
}
以下是您的刀片模板示例:
@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
接口或抽象类可能会很有趣。
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(...);
...
}
你甚至可以把它们分成ExecutableFeature
和RenderableFeature
。
更进一步,可以制造某种工厂级,让生活更轻松。
// 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')
在我的案例中,我在数据库上创建了一个新表,例如,您可以将其称为Domains
。
添加所有特定功能,这些功能可以在某些域上显示,但不能在其他域中显示,作为该表的列,作为布尔值的位。比如,在我的情况下,allow_multiple_bookings
,use_company_card
。。。无论什么
然后,考虑创建一个类Domain
及其相应的存储库,只需在代码中询问这些值,并尝试将尽可能多的逻辑推送到您的域(模型、应用程序服务等)中。
例如,如果请求预订的域只能请求一个或多个,我不会检查RequestBooking
的控制器方法。
相反,我在RequestBookingValidatorService
上做这件事,它可以检查预订日期时间是否已经过去,用户是否有启用的信用卡。。。或者此操作来自的域可以请求多个预订(如果已经有预订的话)。
这增加了可读性的便利性,因为您已经将此决定推送给了应用程序服务。此外,我发现每当我需要一个新功能时,我都可以使用Laravel(或Symfony)迁移将该功能添加到表中,甚至可以在我编码的同一提交中使用我想要的值更新它的行(您的域)。