laravel:constants-and-configuration

安装量: 44
排名: #16602

安装

npx skills add https://github.com/jpcaparas/superpowers-laravel --skill laravel:constants-and-configuration

Avoid hardcoded values throughout your codebase. Use constants, configuration files, and enums to make your application more maintainable, refactorable, and debuggable.

The Problem with Hardcoded Values

// BAD: Magic numbers and strings scattered everywhere
if ($user->role === 'admin') { // What other roles exist?
    $cacheTime = 3600; // What does 3600 mean?
}

if ($order->status === 1) { // What does 1 represent?
    $discount = 0.15; // Why 15%?
}

Cache::remember('users_list', 600, fn() => ...); // 600 what?

Solution 1: PHP Constants and Enums

Class Constants

// app/Constants/UserRole.php
class UserRole
{
    public const ADMIN = 'admin';
    public const EDITOR = 'editor';
    public const VIEWER = 'viewer';
    public const GUEST = 'guest';

    public const ALL = [
        self::ADMIN,
        self::EDITOR,
        self::VIEWER,
        self::GUEST,
    ];

    public static function hasPermission(string $role, string $action): bool
    {
        return match($role) {
            self::ADMIN => true,
            self::EDITOR => in_array($action, ['read', 'write', 'edit']),
            self::VIEWER => $action === 'read',
            self::GUEST => false,
            default => false,
        };
    }
}

// Usage
if ($user->role === UserRole::ADMIN) {
    // Clear intent
}

PHP 8.1+ Enums

// app/Enums/OrderStatus.php
enum OrderStatus: string
{
    case PENDING = 'pending';
    case PROCESSING = 'processing';
    case SHIPPED = 'shipped';
    case DELIVERED = 'delivered';
    case CANCELLED = 'cancelled';
    case REFUNDED = 'refunded';

    public function label(): string
    {
        return match($this) {
            self::PENDING => 'Pending Payment',
            self::PROCESSING => 'Processing',
            self::SHIPPED => 'Shipped',
            self::DELIVERED => 'Delivered',
            self::CANCELLED => 'Cancelled',
            self::REFUNDED => 'Refunded',
        };
    }

    public function color(): string
    {
        return match($this) {
            self::PENDING => 'yellow',
            self::PROCESSING => 'blue',
            self::SHIPPED => 'indigo',
            self::DELIVERED => 'green',
            self::CANCELLED => 'red',
            self::REFUNDED => 'gray',
        };
    }

    public function canTransitionTo(self $newStatus): bool
    {
        return match($this) {
            self::PENDING => in_array($newStatus, [
                self::PROCESSING,
                self::CANCELLED,
            ]),
            self::PROCESSING => in_array($newStatus, [
                self::SHIPPED,
                self::CANCELLED,
            ]),
            self::SHIPPED => $newStatus === self::DELIVERED,
            self::DELIVERED => $newStatus === self::REFUNDED,
            default => false,
        };
    }
}

// Model with enum casting
class Order extends Model
{
    protected $casts = [
        'status' => OrderStatus::class,
    ];

    public function transitionTo(OrderStatus $newStatus): void
    {
        if (!$this->status->canTransitionTo($newStatus)) {
            throw new InvalidStateTransition(
                "Cannot transition from {$this->status->value} to {$newStatus->value}"
            );
        }

        $this->update(['status' => $newStatus]);
    }
}

// Usage
$order->transitionTo(OrderStatus::PROCESSING);

Solution 2: Configuration Files

Application-Wide Settings

// config/app.php
return [
    'cache_ttl' => [
        'short' => 60,      // 1 minute
        'medium' => 300,    // 5 minutes
        'long' => 3600,     // 1 hour
        'day' => 86400,     // 24 hours
    ],

    'pagination' => [
        'default' => 20,
        'max' => 100,
        'options' => [10, 20, 50, 100],
    ],

    'upload' => [
        'max_file_size' => 10 * 1024 * 1024, // 10MB
        'allowed_extensions' => ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'],
        'storage_path' => 'uploads',
    ],

    'business' => [
        'tax_rate' => 0.08,
        'shipping_threshold' => 50.00,
        'discount_tiers' => [
            'bronze' => 0.05,
            'silver' => 0.10,
            'gold' => 0.15,
            'platinum' => 0.20,
        ],
    ],
];

// Usage
Cache::remember(
    'products',
    config('app.cache_ttl.long'),
    fn() => Product::all()
);

$maxSize = config('app.upload.max_file_size');

Feature-Specific Configuration

// config/payment.php
return [
    'stripe' => [
        'key' => env('STRIPE_KEY'),
        'secret' => env('STRIPE_SECRET'),
        'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
        'webhook_tolerance' => 300, // seconds
        'currency' => 'usd',
        'minimum_amount' => 50, // cents
    ],

    'retry' => [
        'max_attempts' => 3,
        'delay_seconds' => [5, 10, 30],
    ],

    'statuses' => [
        'pending' => 'pending',
        'processing' => 'processing',
        'succeeded' => 'succeeded',
        'failed' => 'failed',
    ],
];

// Usage in service
class PaymentService
{
    public function charge(int $amount): void
    {
        if ($amount < config('payment.stripe.minimum_amount')) {
            throw new InvalidAmountException('Amount below minimum');
        }

        // Process payment
    }
}

Solution 3: Database-Driven Configuration

Settings Model

// app/Models/Setting.php
class Setting extends Model
{
    protected $fillable = ['key', 'value', 'type'];

    protected $casts = [
        'value' => 'json',
    ];

    public static function get(string $key, mixed $default = null): mixed
    {
        return Cache::remember(
            "settings.{$key}",
            config('app.cache_ttl.long'),
            fn() => static::where('key', $key)->first()?->value ?? $default
        );
    }

    public static function set(string $key, mixed $value): void
    {
        static::updateOrCreate(
            ['key' => $key],
            ['value' => $value]
        );

        Cache::forget("settings.{$key}");
    }

    protected static function booted(): void
    {
        static::saved(function ($setting) {
            Cache::forget("settings.{$setting->key}");
        });
    }
}

// Usage
$maintenanceMode = Setting::get('maintenance_mode', false);
$maxLoginAttempts = Setting::get('max_login_attempts', 5);

Solution 4: Service Constants

// app/Services/CacheService.php
class CacheService
{
    // Cache key patterns
    public const USER_KEY = 'user:%d';
    public const USER_POSTS_KEY = 'user:%d:posts';
    public const POST_KEY = 'post:%d';
    public const TRENDING_KEY = 'trending:%s:page:%d';

    // Cache tags
    public const TAG_USERS = 'users';
    public const TAG_POSTS = 'posts';
    public const TAG_COMMENTS = 'comments';

    public static function getUserKey(int $userId): string
    {
        return sprintf(self::USER_KEY, $userId);
    }

    public static function getUserPostsKey(int $userId): string
    {
        return sprintf(self::USER_POSTS_KEY, $userId);
    }

    public static function rememberUser(int $userId, Closure $callback)
    {
        return Cache::tags([self::TAG_USERS])->remember(
            self::getUserKey($userId),
            config('app.cache_ttl.medium'),
            $callback
        );
    }
}

// Usage
$user = CacheService::rememberUser($userId, fn() => User::find($userId));

Solution 5: Validation Constants

// app/Rules/ValidationRules.php
class ValidationRules
{
    public const NAME_REGEX = '/^[a-zA-Z\s\-\']+$/';
    public const PHONE_REGEX = '/^\+?[1-9]\d{1,14}$/';
    public const USERNAME_REGEX = '/^[a-zA-Z0-9_]{3,20}$/';
    public const SLUG_REGEX = '/^[a-z0-9\-]+$/';

    public const PASSWORD_MIN = 8;
    public const PASSWORD_MAX = 128;
    public const BIO_MAX = 500;
    public const COMMENT_MAX = 1000;

    public static function password(): array
    {
        return [
            'required',
            'string',
            'min:' . self::PASSWORD_MIN,
            'max:' . self::PASSWORD_MAX,
            Password::defaults(),
        ];
    }

    public static function username(): array
    {
        return [
            'required',
            'string',
            'regex:' . self::USERNAME_REGEX,
            'unique:users,username',
        ];
    }
}

// In FormRequest
public function rules(): array
{
    return [
        'username' => ValidationRules::username(),
        'password' => ValidationRules::password(),
        'bio' => ['nullable', 'string', 'max:' . ValidationRules::BIO_MAX],
    ];
}

Solution 6: HTTP Status Constants

// app/Http/Responses/ApiResponse.php
class ApiResponse
{
    // Standard HTTP codes as constants for clarity
    public const OK = 200;
    public const CREATED = 201;
    public const ACCEPTED = 202;
    public const NO_CONTENT = 204;
    public const BAD_REQUEST = 400;
    public const UNAUTHORIZED = 401;
    public const FORBIDDEN = 403;
    public const NOT_FOUND = 404;
    public const VALIDATION_ERROR = 422;
    public const SERVER_ERROR = 500;

    // Custom application codes
    public const CODE_SUCCESS = 1000;
    public const CODE_VALIDATION_FAILED = 2001;
    public const CODE_RESOURCE_NOT_FOUND = 2002;
    public const CODE_UNAUTHORIZED_ACTION = 2003;
    public const CODE_RATE_LIMITED = 2004;

    public static function success($data = null, string $message = 'Success'): JsonResponse
    {
        return response()->json([
            'success' => true,
            'code' => self::CODE_SUCCESS,
            'message' => $message,
            'data' => $data,
        ], self::OK);
    }

    public static function error(string $message, int $httpCode = self::BAD_REQUEST, int $appCode = null): JsonResponse
    {
        return response()->json([
            'success' => false,
            'code' => $appCode ?? $httpCode,
            'message' => $message,
        ], $httpCode);
    }
}

// Usage
return ApiResponse::success($user, 'User created successfully');
return ApiResponse::error('Resource not found', ApiResponse::NOT_FOUND);

Solution 7: Queue Priorities and Job Constants

// app/Jobs/JobPriority.php
class JobPriority
{
    public const CRITICAL = 'critical';
    public const HIGH = 'high';
    public const NORMAL = 'default';
    public const LOW = 'low';

    public const MAX_TRIES = [
        self::CRITICAL => 5,
        self::HIGH => 3,
        self::NORMAL => 3,
        self::LOW => 1,
    ];

    public const TIMEOUT = [
        self::CRITICAL => 300,  // 5 minutes
        self::HIGH => 180,      // 3 minutes
        self::NORMAL => 120,    // 2 minutes
        self::LOW => 60,        // 1 minute
    ];
}

// app/Jobs/ProcessPayment.php
class ProcessPayment implements ShouldQueue
{
    public $tries = JobPriority::MAX_TRIES[JobPriority::HIGH];
    public $timeout = JobPriority::TIMEOUT[JobPriority::HIGH];

    public function __construct(
        public Payment $payment
    ) {}

    public function handle(): void
    {
        // Process payment
    }

    public function queue(): string
    {
        return JobPriority::HIGH;
    }
}

Environment-Specific Constants

// app/Providers/AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Define environment-specific constants
        if (app()->environment('local', 'testing')) {
            Config::set('app.debug_mode', true);
            Config::set('app.cache_ttl.short', 1); // Shorter cache in dev
        }

        if (app()->environment('production')) {
            Config::set('app.debug_mode', false);
            Config::set('app.rate_limit', 60); // Stricter in production
        }
    }
}

Testing with Constants

test('order status transitions work correctly', function () { $order = Order::factory()->create([ 'status' => OrderStatus::PENDING ]);

// Valid transition
$order->transitionTo(OrderStatus::PROCESSING);
expect($order->status)->toBe(OrderStatus::PROCESSING);

// Invalid transition
expect(fn() => $order->transitionTo(OrderStatus::PENDING))
    ->toThrow(InvalidStateTransition::class);

});

test('cache keys are consistent', function () { $userId = 123;

$key1 = CacheService::getUserKey($userId);
$key2 = sprintf</
返回排行榜