我正在构建一个Laravel应用程序,它具有许多不同的功能。我希望能够根据特定域的要求启用或禁用它们。当前,我在配置中有一系列标志,例如:
'is_feature_1_enabled' => true,
'is_feature_2_enabled' => false,
... 等等。
然后,在我的控制器和视图中,检查那些配置值,以查看是否应该显示某些内容,允许某些操作等。我的应用到处都开始受到这类检查的污染。
有没有在Laravel应用中管理功能的最佳实践方法?
我正在构建一个Laravel应用程序,它具有许多不同的功能。我希望能够根据特定域的要求启用或禁用它们。当前,我在配置中有一系列标志,例如:
'is_feature_1_enabled' => true,
'is_feature_2_enabled' => false,
... 等等。
然后,在我的控制器和视图中,检查那些配置值,以查看是否应该显示某些内容,允许某些操作等。我的应用到处都开始受到这类检查的污染。
有没有在Laravel应用中管理功能的最佳实践方法?
Answers:
这在技术上称为功能标记-https: //martinfowler.com/articles/feature-toggles.html
取决于您的要求,配置/数据库中的标志,部署等...
但这基本上是如果在代码中并且不能清除。
Laravel软件包:
https://github.com/alfred-nutile-inc/laravel-feature-flag
https://github.com/francescomalatesta/laravel-feature
一些服务:
另请参阅https://marketingplatform.google.com/about/optimize/了解前端。
当我尝试实施多个酒店提供商时遇到了相同的问题。
我所做的是使用服务容器。
首先,您将为每个域创建具有其功能的类:
那么您将在应用服务提供商中使用绑定将要使用的类绑定到域。
$this->app->bind('Domain1',function (){
return new Domain1();
});
$this->app->bind('Domain2',function (){
return new Domain2();
});
请注意,您可以使用包含所有领域附带的功能的通用类,然后在您的类中使用该通用类
最后,您可以在控制器中检查您的域,然后使用要使用的类
app(url('/'))->methodName();
看起来您正在基于配置值对事物进行硬编码,以启用或禁用某些功能。我建议您根据命名的路由而不是配置值来控制事物。
因此,您不会在相同的条件下到处重复代码,并使代码膨胀。.以下示例代码向您展示了如何检索所有路由,并且可以匹配路由组名称以进行进一步处理以匹配您的情况。
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绑定到容器:
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);
}
}
别忘了将此中间件附加到您的路由:一个组或每个路由。
之后,您可以在控制器中TypeHint您的Feature类:
public function index(Request $request, Features $features)
{
if ($features->isFeature1Enabled()) {
//
}
}
Laravel很好用,您甚至可以将功能存储在db中,并在域之间创建关系。
我建议您使用Gates and Policies,这将使您更好地控制控制器和刀片服务器模板。这意味着您可以从数据库中注册门或对其进行硬编码。
例如,如果您在系统中具有带有按钮的导出产品功能,并且想要将该功能提供给某些用户,则可以向业务逻辑注册门。
//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
}
}
这是您的刀片模板的示例:
@can('export-products', $post)
<!-- The Current User Can export products -->
@endcan
@cannot('export-products')
<!-- The Current User Can't export products -->
@endcannot
有趣的情况,你在这里。查看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)迁移在表上添加该功能,甚至可以使用我在编码时提交的值来更新其行(您的域)。