offload-project/laravel-hoist
最新稳定版本:v1.1.0
Composer 安装命令:
composer require offload-project/laravel-hoist
包简介
Feature discovery and util extension for Laravel Pennant
README 文档
README
Laravel Hoist
Feature discovery and management extension for Laravel Pennant. Automatically discover, manage, and serve feature flags with custom metadata and routing.
Requirements
- PHP 8.3+
- Laravel 11+
- Laravel Pennant 1+
Installation
composer require offload-project/laravel-hoist
Configuration
Publish the configuration file:
php artisan vendor:publish --tag=hoist-config
Edit config/hoist.php:
return [ 'feature_directories' => [ app_path('Features') => 'App\\Features', ], ];
The configuration uses an associative array where keys are directory paths and values are their corresponding namespaces.
Optionally, publish the stub files for customization:
php artisan vendor:publish --tag=hoist-stubs
Features
Feature Discovery
Automatically discover and manage Laravel Pennant features with custom metadata and routing information.
Create a Feature
php artisan hoist:feature NewFeature
This will create a new feature class in your configured feature directory (default: app/Features).
Feature Class Example
<?php declare(strict_types=1); namespace App\Features; use OffloadProject\Hoist\Contracts\Feature; class BillingFeature implements Feature { public string $name = 'billing'; public string $label = 'Billing Module'; public ?string $description = 'Advanced billing features'; public ?string $route = 'billing.index'; // Optional route name public array $tags = ['subscription', 'pro']; // Feature tags public function resolve(mixed $scope): mixed { return $scope->subscription?->isActive() ?? false; } public function metadata(): array { return [ 'category' => 'premium', 'icon' => 'credit-card', 'version' => '2.0', ]; } }
Note: The
Featureinterface is optional but recommended. Features are discovered based on having aresolve()method, but implementing the interface provides better IDE support and type safety.
Using Features
use OffloadProject\Hoist\Facades\Hoist; // Get all features $features = Hoist::all(); // Get features for a specific user with active status $userFeatures = Hoist::forModel($user); // Get array of all feature names $featureNames = Hoist::names(); // Returns: ['billing', 'dashboard', 'reporting', ...] // Access feature data foreach ($userFeatures as $feature) { echo $feature->name; // 'billing' echo $feature->label; // 'Billing Module' echo $feature->description; // 'Advanced billing features' echo $feature->href; // route('billing.index') echo $feature->active; // true/false print_r($feature->metadata); // ['category' => 'premium', ...] print_r($feature->tags); // ['subscription', 'pro'] }
Feature Discovery Service
The FeatureDiscovery service provides several methods for working with features:
discover()
Discovers all feature classes from configured directories.
use OffloadProject\Hoist\Services\FeatureDiscovery; $discovery = app(FeatureDiscovery::class); $featureClasses = $discovery->discover(); // Returns: Collection of feature class names
all()
Returns all features as FeatureData objects without checking active status.
$features = Hoist::all(); // Returns: Collection of FeatureData objects
forModel($model)
Returns all features with their active status for a specific model (typically a User).
$userFeatures = Hoist::forModel($user); // Each FeatureData object includes 'active' property
names()
Returns an array of all feature names.
$names = Hoist::names(); // Returns: ['feature-one', 'feature-two', ...]
Feature Tags
Tags provide a flexible way to categorize features for filtering. Use tags to separate feature flags from subscription features, or to group features by plan tier.
Define Tags
class DarkMode implements Feature { public string $name = 'dark-mode'; public string $label = 'Dark Mode'; public array $tags = ['flag', 'ui']; // ... } class AdvancedReporting implements Feature { public string $name = 'advanced-reporting'; public string $label = 'Advanced Reporting'; public array $tags = ['subscription', 'pro', 'enterprise']; // ... }
Filter by Tags
// Get features with a specific tag $flags = Hoist::tagged('flag'); $subscriptionFeatures = Hoist::tagged('subscription'); // Get features with ALL specified tags (AND logic) $proSubscriptions = Hoist::withTags(['subscription', 'pro']); // Get features with ANY of the specified tags (OR logic) $paidFeatures = Hoist::withAnyTags(['pro', 'enterprise']);
Filter with Model Scope
Include active status when filtering by tags:
// Get subscription features for a user with active status $features = Hoist::taggedFor('subscription', $user); // Get pro features for a user $proFeatures = Hoist::withTagsFor(['subscription', 'pro'], $user); // Get any paid tier features for a user $paidFeatures = Hoist::withAnyTagsFor(['pro', 'enterprise'], $user);
Feature Data Structure
The FeatureData class provides a structured way to access feature information:
class FeatureData { public string $name; // Feature identifier public string $label; // Human-readable name public ?string $description; // Feature description public ?string $href; // Generated route URL public ?bool $active; // Active status (when using forModel) public array $metadata; // Custom metadata public array $tags; // Feature tags for categorization }
Integration with Laravel Pennant
This package extends Laravel Pennant by providing:
- Automatic Discovery: No need to manually register features
- Rich Metadata: Add custom metadata to features
- Route Integration: Link features to routes automatically
- Structured Data: Get features as structured data objects
- Bulk Operations: Get all features and their status in one call
Using with Pennant's Native Features
You can still use all of Laravel Pennant's native features:
use Laravel\Pennant\Feature; // Standard Pennant usage if (Feature::active('billing')) { // Feature is active } // In Blade @feature('billing') <!-- Feature content --> @endfeature // Combined with Pennant Hoist $features = Hoist::forModel($user); foreach ($features as $feature) { if ($feature->active) { // Do something with active feature } }
Use Cases
Building a Feature Dashboard
public function featureDashboard(Request $request) { $features = Hoist::forModel($request->user()); return view('features.dashboard', [ 'features' => $features, ]); }
<!-- resources/views/features/dashboard.blade.php --> <div class="features-grid"> @foreach($features as $feature) <div class="feature-card {{ $feature->active ? 'active' : 'inactive' }}"> <h3>{{ $feature->label }}</h3> <p>{{ $feature->description }}</p> @if($feature->active && $feature->href) <a href="{{ $feature->href }}" class="btn"> Go to {{ $feature->label }} </a> @endif @if(!empty($feature->metadata['icon'])) <i class="icon-{{ $feature->metadata['icon'] }}"></i> @endif </div> @endforeach </div>
API Endpoint for Frontend
Route::get('/api/features', function (Request $request) { return Hoist::forModel($request->user()); });
Returns:
[
{
"name": "billing",
"label": "Billing Module",
"description": "Advanced billing features",
"href": "https://app.example.com/billing",
"active": true,
"metadata": {
"category": "premium",
"icon": "credit-card"
},
"tags": ["subscription", "pro"]
}
]
Dynamic Navigation
public function navigation(Request $request) { $features = Hoist::forModel($request->user()) ->filter(fn($f) => $f->active && $f->href) ->filter(fn($f) => $f->metadata['show_in_nav'] ?? true); return view('layouts.navigation', [ 'features' => $features, ]); }
Advanced Usage
Custom Feature Directories
You can configure multiple feature directories, each mapped to its namespace:
// config/hoist.php return [ 'feature_directories' => [ app_path('Authorization/Features') => 'App\\Authorization\\Features', app_path('Billing/Features') => 'App\\Billing\\Features', app_path('Admin/Features') => 'App\\Admin\\Features', ], ];
Each directory is mapped to its corresponding namespace, allowing you to organize features across different modules or domains while maintaining proper class resolution.
Feature Organization
Organize features by category:
app/Features/
├── Admin/
│ ├── UserManagementFeature.php
│ └── SystemSettingsFeature.php
├── Premium/
│ ├── BillingFeature.php
│ └── AnalyticsFeature.php
└── Core/
├── DashboardFeature.php
└── ProfileFeature.php
Route Handling
The href property in FeatureData is generated from the feature's $route property. The package safely handles routes:
- If
$routeisnullor empty,hrefwill benull - If
$routespecifies a route name that doesn't exist,hrefwill benull(no exception thrown) - If
$routespecifies a valid route name,hrefwill contain the generated URL
class MyFeature implements Feature { public string $name = 'my-feature'; public string $label = 'My Feature'; public ?string $route = 'dashboard.index'; // Must be a valid route name // ... }
The Feature Interface
The package provides an optional Feature interface for better type safety:
use OffloadProject\Hoist\Contracts\Feature; class MyFeature implements Feature { public string $name = 'my-feature'; public string $label = 'My Feature'; public ?string $description = null; public ?string $route = null; public array $tags = []; public function resolve(mixed $scope): mixed { return true; } public function metadata(): array { return []; } }
Features are discovered if they either:
- Implement the
Featureinterface, OR - Have a
resolve()method (for backward compatibility with plain Pennant features)
Metadata Best Practices
Use metadata for:
- Categorization: Group features by category
- UI Elements: Icons, colors, badges
- Permissions: Access levels, roles
- Versioning: Track feature versions
- Analytics: Track feature usage
public function metadata(): array { return [ 'category' => 'premium', 'icon' => 'credit-card', 'color' => 'blue', 'version' => '2.0', 'requires_subscription' => true, 'min_plan' => 'pro', ]; }
Testing
./vendor/bin/pest
License
The MIT License (MIT). Please see License File for more information.
统计信息
- 总下载量: 497
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 4
- 点击次数: 1
- 依赖项目数: 1
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-12-16