承接 andydefer/laravel-directive 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

andydefer/laravel-directive

最新稳定版本:v3.19.0

Composer 安装命令:

composer require andydefer/laravel-directive

包简介

A flexible CLI command system for Laravel that breaks free from Artisan's constraints. Directives introduces a clean separation between what your command does (business logic) and how it's presented (output/UI).

README 文档

README

Un système de commandes CLI flexible pour Laravel qui se libère des contraintes d'Artisan. Directives introduit une séparation nette entre la logique métier et la présentation.

PHP Version Laravel Version License

Table des matières

Installation

composer require andydefer/laravel-directive

Prérequis

  • PHP 8.2 ou supérieur
  • Laravel 10.x, 11.x ou 12.x

Publication de la configuration (optionnel)

php artisan vendor:publish --tag=directive-config --force

Configuration

// config/directive.php
return [
    'path' => getcwd() . '/app/Directives',
];
Variable d'environnement Description Défaut
DIRECTIVE_PATH Chemin personnalisé des directives ./app/Directives
DIRECTIVE_DEBUG Mode debug false

Premiers pas

Lister les directives

./vendor/bin/directive --list

Afficher l'aide

./vendor/bin/directive --help

Créer votre première directive

<?php
// app/Directives/HelloDirective.php

namespace App\Directives;

use AndyDefer\Directive\AbstractDirective;
use AndyDefer\Directive\Enums\ExitCode;

final class HelloDirective extends AbstractDirective
{
    public function getSignature(): string
    {
        return 'hello {name?}';
    }

    public function getDescription(): string
    {
        return 'Say hello to someone';
    }

    public function execute(): ExitCode
    {
        $name = $this->argument('name') ?? 'World';
        $this->line("Hello, {$name}!");
        
        return ExitCode::SUCCESS;
    }
}

Exécuter

./vendor/bin/directive hello "John Doe"
# Sortie: Hello, John Doe!

Format des signatures

Règles fondamentales

Règle Explication
Délimiteur autorisé Uniquement - (tiret)
Caractères autorisés Lettres (a-z, A-Z) et chiffres (0-9)
Premier caractère Doit être une lettre
Pas de tirets consécutifs user--list est interdit
Pas de tiret final user- est interdit

✅ Exemples valides

Signature Classe générée
user-list UserListDirective
cache-clear CacheClearDirective
api-user-profile ApiUserProfileDirective

❌ Exemples invalides

Signature Raison
user:list Caractère : interdit
create_user Underscore _ interdit
user- Tiret final interdit
user--list Tirets consécutifs

Ordre des paramètres (strict)

Ordre Type Syntaxe Exemple
1 Arguments requis {name} {name} {email}
2 Arguments avec valeur par défaut {role=user} {role=admin}
3 Arguments optionnels {count?} {limit?}
4 Arguments variadiques {files*} {files*}
5 Options {--force} ou {-v} {--verbose} {-f}
// ✅ Ordre correct
public function getSignature(): string
{
    return 'user-create {name} {email} {role=user} {count?} {files*} {--force} {-v}';
}

// ❌ Ordre incorrect
public function getSignature(): string
{
    return 'user-create {name?} {email}'; // Requis après optionnel
}

Arguments variadiques

Capture tous les arguments restants sous forme de tableau.

public function getSignature(): string
{
    return 'process {name} {files*}';
}

Syntaxe avec crochets (recommandée)

./directive process John [file1.txt, file2.txt, file3.txt] --verbose

Exemple

final class ProcessFilesDirective extends AbstractDirective
{
    public function getSignature(): string
    {
        return 'process {name} {files*} {--verbose}';
    }

    public function execute(): ExitCode
    {
        $name = $this->argument('name');
        $files = $this->getVariadicArguments();

        $this->info("Processing files for {$name}");

        foreach ($files as $file) {
            $this->line("  - {$file}");
        }

        return ExitCode::SUCCESS;
    }
}
Méthode Description
getVariadicArguments(): StringTypedCollection Retourne tous les arguments variadiques
hasVariadicArguments(): bool Vérifie leur présence

Les méthodes de base

getSignature()

public function getSignature(): string
{
    return 'user-create {name} {email} {--role=admin}';
}
Élément Syntaxe Description
Argument requis {name} Paramètre positionnel obligatoire
Argument optionnel {name?} Paramètre positionnel optionnel
Argument avec défaut {count=10} Valeur par défaut
Argument variadique {files*} Capture tous les restants
Option avec valeur {--role=} Option attend une valeur
Option flag {--force} true/false

getDescription()

public function getDescription(): string
{
    return 'Create a new user account';
}

execute()

public function execute(): ExitCode
{
    $this->info('User created!');
    return ExitCode::SUCCESS;
}

getAliases()

use AndyDefer\DomainStructures\Collections\Utility\StringTypedCollection;

public function getAliases(): StringTypedCollection
{
    $aliases = new StringTypedCollection();
    $aliases->add('user-add');
    $aliases->add('create-user');
    return $aliases;
}

Arguments et options

Accès aux arguments

public function execute(): ExitCode
{
    $name = $this->argument('name');   // string ou null
    $email = $this->argument('email');
    
    if ($name === null) {
        $this->error('Name is required');
        return ExitCode::INVALID_ARGUMENT;
    }
    
    $this->line("Name: {$name}");
    return ExitCode::SUCCESS;
}

Accès aux arguments variadiques

public function execute(): ExitCode
{
    $files = $this->getVariadicArguments();
    
    if ($this->hasVariadicArguments()) {
        foreach ($files as $file) {
            $this->line("Processing: {$file}");
        }
    }
    
    return ExitCode::SUCCESS;
}

Vérifier l'existence

if ($this->hasArgument('count')) {
    $count = $this->argument('count');
}

if ($this->hasOption('verbose')) {
    $this->info('Verbose mode');
}

Accès aux options

public function execute(): ExitCode
{
    $force = $this->option('force');   // bool
    $role = $this->option('role');     // string ou null
    
    if ($force) {
        $this->warn('Force mode enabled');
    }
    
    return ExitCode::SUCCESS;
}

Interaction utilisateur

Afficher des messages

$this->line('Simple text');     // texte brut
$this->info('Success!');        // vert
$this->error('Error!');         // rouge
$this->warn('Warning!');        // jaune
$this->newLine();               // ligne vide
$this->separator();             // ligne de séparation (---)

Poser une question

$name = $this->ask('What is your name?');

Demander une confirmation

if ($this->confirm('Continue?')) {
    $this->info('Continuing...');
}

Afficher un tableau

use AndyDefer\Directive\Collections\RowCollection;
use AndyDefer\DomainStructures\Collections\Utility\StringTypedCollection;

$headers = new StringTypedCollection();
$headers->add('ID', 'Name', 'Email');

$rows = new RowCollection();
$row = new RowCollection();
$row->add(1, 'John Doe', 'john@example.com');
$rows->add($row);

$this->table($headers, $rows);

Sortie :

| ID | Name     | Email             |
|----|----------|-------------------|
| 1  | John Doe | john@example.com  |

Charger Laravel optionnellement

Par défaut, les directives s'exécutent sans Laravel pour des performances optimales.

Activer Laravel

final class UserListDirective extends AbstractDirective
{
    public function shouldBootLaravel(): bool
    {
        return true;
    }
    
    public function execute(): ExitCode
    {
        $users = User::all(); // Eloquent fonctionne !
        
        foreach ($users as $user) {
            $this->line("{$user->id}: {$user->name}");
        }
        
        return ExitCode::SUCCESS;
    }
}

Vérifier la disponibilité

public function execute(): ExitCode
{
    if (!$this->hasLaravel()) {
        $this->error('Laravel not available!');
        return ExitCode::FAILURE;
    }
    
    $this->info('Laravel is available!');
    return ExitCode::SUCCESS;
}

Accéder à l'instance Laravel

$app = $this->getLaravel();
$version = $app->version();

Le bootstrap de Laravel se fait une seule fois par exécution.

Commandes intégrées

Commande Alias Description
./vendor/bin/directive --list -l Liste toutes les directives
./vendor/bin/directive --help -h Affiche l'aide
./vendor/bin/directive --version -v Affiche la version

Codes de sortie

Code Constante Description
0 ExitCode::SUCCESS Succès
1 ExitCode::FAILURE Erreur générale
3 ExitCode::NOT_FOUND Directive non trouvée
4 ExitCode::INVALID_ARGUMENT Argument invalide
public function execute(): ExitCode
{
    if ($this->argument('name') === null) {
        $this->error('Name is required');
        return ExitCode::INVALID_ARGUMENT;
    }
    
    try {
        // Logique...
        return ExitCode::SUCCESS;
    } catch (\Exception $e) {
        $this->error($e->getMessage());
        return ExitCode::FAILURE;
    }
}

Tester vos directives

Le package fournit DirectiveTestingService pour tester vos directives dans un environnement isolé.

Test basique

<?php
namespace Tests\Unit\Directives;

use AndyDefer\Directive\Contexts\DirectiveContext;
use AndyDefer\Directive\Contexts\LaravelBootstrapperContext;
use AndyDefer\Directive\Enums\ExitCode;
use AndyDefer\Directive\Records\DirectiveBlueprintRecord;
use AndyDefer\Directive\Services\DirectiveTestingService;
use AndyDefer\DomainStructures\Collections\Utility\StringTypedCollection;
use PHPUnit\Framework\TestCase;
use App\Directives\HelloDirective;

final class HelloDirectiveTest extends TestCase
{
    private DirectiveTestingService $service;

    protected function setUp(): void
    {
        parent::setUp();
        $this->service = new DirectiveTestingService();
    }

    protected function tearDown(): void
    {
        $this->service->destroy();
        parent::tearDown();
    }

    public function test_directive_returns_success(): void
    {
        // Créer le contexte pour la directive
        $context = new DirectiveContext(
            laravelBootstrapper: new LaravelBootstrapperContext(),
            blueprint: new DirectiveBlueprintRecord(HelloDirective::class, 'hello', 'Say hello'),
            aliases: new StringTypedCollection(),
            shouldBootLaravel: false
        );
        
        $directive = new HelloDirective($context, $this->service->getInteraction());
        $this->service->registerDirective($directive);
        
        $response = $this->service->runDirective('hello', ['John']);
        
        $this->assertSame(ExitCode::SUCCESS, $response->exitCode);
        $this->assertStringContainsString('Hello, John!', $response->output);
    }
}

Directive temporaire avec closure

public function test_temporary_directive(): void
{
    $executed = false;
    
    $this->service->createTestDirective('test-closure', function ($d) use (&$executed) {
        $executed = true;
        $d->line('Executed!');
        return ExitCode::SUCCESS;
    });
    
    $response = $this->service->runDirective('test-closure');
    
    $this->assertTrue($executed);
    $this->assertSame(ExitCode::SUCCESS, $response->exitCode);
}

Test avec Laravel

protected function setUp(): void
{
    parent::setUp();
    $config = new DirectiveTestingConfig();
    $context = new DirectiveTestingContext(bootLaravel: true);
    $context->setConfig($config);
    $this->service = new DirectiveTestingService($context);
}

Méthodes du service

Méthode Description
registerDirective(AbstractDirective $directive) Enregistre une directive
registerDirectives(array $directives) Enregistre plusieurs directives
clearRegisteredDirectives() Supprime toutes les directives
createTestDirective(string $signature, callable $execute) Crée une directive temporaire (gère le contexte automatiquement)
runDirective(string $signature, array $arguments = []) Exécute une directive

createTestDirective() crée automatiquement le DirectiveContext nécessaire, vous n'avez pas à le gérer manuellement.

Exemples complets

Directive de backup avec arguments variadiques

<?php
namespace App\Directives;

use AndyDefer\Directive\AbstractDirective;
use AndyDefer\Directive\Enums\ExitCode;

final class BackupDirective extends AbstractDirective
{
    public function getSignature(): string
    {
        return 'backup {source} {destination} {excludes*} {--compress} {--format=zip}';
    }

    public function getDescription(): string
    {
        return 'Backup files and directories';
    }

    public function execute(): ExitCode
    {
        $source = $this->argument('source');
        $destination = $this->argument('destination');
        $excludes = $this->getVariadicArguments();
        $compress = $this->option('compress');
        $format = $this->option('format') ?? 'zip';

        $this->info("Backup from {$source} to {$destination}");
        
        if ($compress) {
            $this->info("Compression enabled");
        }
        
        $this->info("Format: {$format}");
        
        if ($excludes->isNotEmpty()) {
            $this->info("Excluding: " . implode(', ', $excludes->toArray()));
        }

        return ExitCode::SUCCESS;
    }
}

// Usage: ./directive backup /var/www /backup [node_modules, .git, cache] --compress

Directive avec base de données (Laravel)

<?php
namespace App\Directives;

use AndyDefer\Directive\AbstractDirective;
use AndyDefer\Directive\Collections\RowCollection;
use AndyDefer\Directive\Enums\ExitCode;
use AndyDefer\DomainStructures\Collections\Utility\StringTypedCollection;
use App\Models\User;

final class UserStatsDirective extends AbstractDirective
{
    public function getSignature(): string
    {
        return 'user-stats {--active}';
    }

    public function getDescription(): string
    {
        return 'Display user statistics';
    }

    public function shouldBootLaravel(): bool
    {
        return true;
    }

    public function execute(): ExitCode
    {
        if (!$this->hasLaravel()) {
            return ExitCode::FAILURE;
        }

        $total = User::count();
        $this->info("Total users: {$total}");
        
        $query = User::query();
        if ($this->option('active')) {
            $query->where('is_active', true);
        }
        
        $headers = new StringTypedCollection();
        $headers->add('ID', 'Name', 'Email');
        
        $rows = new RowCollection();
        foreach ($query->get() as $user) {
            $row = new RowCollection();
            $row->add($user->id, $user->name, $user->email);
            $rows->add($row);
        }
        
        $this->table($headers, $rows);
        
        return ExitCode::SUCCESS;
    }
}

Directive interactive

<?php
namespace App\Directives;

use AndyDefer\Directive\AbstractDirective;
use AndyDefer\Directive\Enums\ExitCode;

final class SetupDirective extends AbstractDirective
{
    public function getSignature(): string
    {
        return 'app-setup';
    }

    public function getDescription(): string
    {
        return 'Interactive setup wizard';
    }

    public function execute(): ExitCode
    {
        $this->info('Welcome to the setup wizard!');
        
        $appName = $this->ask('Application name');
        $env = $this->ask('Environment (local/production)');
        
        if (!$this->confirm("Create config for {$appName} in {$env}?")) {
            $this->warn('Cancelled');
            return ExitCode::SUCCESS;
        }
        
        $this->info("Configuration created!");
        return ExitCode::SUCCESS;
    }
}

Pourquoi ce package ?

Limitations d'Artisan

Problème Solution avec Directives
Héritage unique obligatoire Pas de contrainte
Logique et présentation mélangées Séparation claire
Tests difficiles (ask() impossible à mocker) Services mockables
Pas d'extensibilité pour les packages Découverte automatique
Arguments non typés Accès typé
Pas d'arguments variadiques Support des variadiques
Couplage fort Architecture propre

Avantages

  • Séparation des responsabilités : Logique métier découplée
  • Testabilité exceptionnelle : Chaque directive est mockable
  • Extensibilité : Découverte automatique dans vendor/*/src/Directives/
  • Laravel à la demande : Bootstrap optionnel
  • Validation stricte : Format et ordre des signatures
  • Typage fort : Arguments et options typés
  • Arguments variadiques : Capture avec {files*}
  • Découverte automatique : Aucune configuration requise

Licence

MIT © Andy Defer

统计信息

  • 总下载量: 146
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 4
  • 依赖项目数: 5
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-05-22

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固