> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Console Artisan

> Comment créer des commandes CLI personnalisées avec la console Artisan de Laravel.

## Qu'est-ce qu'Artisan

Artisan est l'interface en ligne de commande (CLI) livrée avec Laravel.
Il s'agit du script `artisan` à la racine du projet, qui propose de nombreuses commandes pour accélérer le développement et l'exploitation.

Pour lister les commandes disponibles :

```shell theme={null}
php artisan list
```

Pour l'aide d'une commande :

```shell theme={null}
php artisan help migrate
```

<Info>
  Avec Laravel Sail, utilisez `sail artisan` à la place. La commande s'exécute dans le conteneur Docker.

  ```shell theme={null}
  ./vendor/bin/sail artisan list
  ```
</Info>

## Tinker

[Laravel Tinker](https://github.com/laravel/tinker) est un REPL qui permet d'interagir en ligne de commande avec toute l'application : modèles Eloquent, jobs, événements, etc.

```shell theme={null}
php artisan tinker
```

Écrivez directement du PHP.

```php theme={null}
// Récupérer un utilisateur
>>> App\Models\User::find(1)
// Créer un enregistrement via une factory
>>> App\Models\User::factory()->create()
// Appeler une classe de service
>>> app(App\Services\OrderService::class)->process(1)
```

<Tip>
  Tinker modifie réellement les données. Faites attention en production.
  Convient à la vérification et au débogage en local ou en staging.
</Tip>

## Créer une commande personnalisée

### Générer la commande

Utilisez `make:command` pour générer le squelette.

```shell theme={null}
php artisan make:command ImportProducts
```

`app/Console/Commands/ImportProducts.php` est généré.

### Structure de base

La classe générée possède trois éléments clés : `$signature`, `$description` et `handle()`.

```php theme={null}
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class ImportProducts extends Command
{
    /**
     * Nom de la commande et définition des arguments/options.
     */
    protected $signature = 'import:products';

    /**
     * Description de la commande (affichée par php artisan list).
     */
    protected $description = 'Importer les données produits depuis un CSV';

    /**
     * Logique d'exécution.
     */
    public function handle(): void
    {
        $this->info('Démarrage de l\'import...');
        // Placez le traitement ici
        $this->info('Terminé.');
    }
}
```

<Info>
  Sous Laravel 13, vous pouvez aussi utiliser des attributs PHP.

  ```php theme={null}
  use Illuminate\Console\Attributes\Description;
  use Illuminate\Console\Attributes\Signature;

  #[Signature('import:products')]
  #[Description('Importer les données produits depuis un CSV')]
  class ImportProducts extends Command
  {
      public function handle(): void
      {
          // ...
      }
  }
  ```
</Info>

### Définir arguments et options

`$signature` regroupe le nom, les arguments et les options.

```php theme={null}
protected $signature = 'import:products
                        {file : chemin du CSV à importer}
                        {--limit= : nombre maximum d\'enregistrements}
                        {--dry-run : n\'enregistre pas en base (vérification)}';
```

| Notation              | Type                 | Description                       |
| --------------------- | -------------------- | --------------------------------- |
| `{file}`              | Argument obligatoire | Erreur si omis                    |
| `{file?}`             | Argument optionnel   | `null` si omis                    |
| `{file=products.csv}` | Argument avec défaut | Valeur par défaut                 |
| `{--queue}`           | Option booléenne     | `true` si présente, sinon `false` |
| `{--limit=}`          | Option à valeur      | `--limit=100`                     |
| `{--limit=50}`        | Option avec défaut   | `50` si omise                     |
| `{--Q\|queue}`        | Raccourci            | `-Q` accepté                      |

### Lecture des arguments et options

Dans `handle()`, utilisez `argument()` et `option()`.

```php theme={null}
public function handle(): void
{
    $file    = $this->argument('file');
    $limit   = $this->option('limit');
    $dryRun  = $this->option('dry-run');

    $this->info("Fichier : {$file}");

    if ($dryRun) {
        $this->warn('Mode dry-run : rien ne sera enregistré');
    }
}
```

### `input()` — accesseurs typés

`input()` permet d'accéder aux arguments/options avec les mêmes accesseurs typés que pour une requête HTTP.

```php theme={null}
use App\Enums\ReportType;

public function handle(): void
{
    // Récupérer sous forme de CommandInput
    $from = $this->input()->date('from');
    $type = $this->input()->enum('type', ReportType::class);
    $limit = $this->input()->integer('limit');
}
```

Pour ne récupérer qu'un nom précis, passez-le directement (cherche à la fois dans arguments et options).

```php theme={null}
$queue = $this->input('queue', 'default');
```

<Tip>
  Alors que `argument()` et `option()` retournent des chaînes, `input()` convertit dans le type approprié. Idéal pour manipuler dates, nombres ou enums en toute sécurité.
</Tip>

## Interaction utilisateur

### Écrire dans la console

Vous disposez de méthodes pour afficher des messages colorés.

```php theme={null}
$this->info('Traitement réussi');    // Vert
$this->warn('Action irréversible');  // Jaune
$this->error('Une erreur est survenue'); // Rouge
$this->line('Texte simple');         // Sans couleur
$this->comment('Note');              // Gris clair
```

### Poser une question

Recevez des entrées de manière interactive.

```php theme={null}
// Saisie texte
$name = $this->ask('Nom du responsable');

// Avec valeur par défaut
$env = $this->ask('Environnement d\'exécution', 'production');

// Champ masqué (mot de passe)
$password = $this->secret('Clé d\'API');

// Oui / Non
if (! $this->confirm('Importer en base de production ?')) {
    $this->info('Annulé.');
    return;
}

// Choix
$format = $this->choice('Format de sortie', ['csv', 'json', 'xml'], 0);
```

### Barre de progression

Affichez la progression pour de gros traitements.

```php theme={null}
use App\Models\Product;

// Passer une collection : la barre s'affiche automatiquement
$this->withProgressBar(Product::cursor(), function (Product $product) {
    $this->processProduct($product);
});
```

Pour un contrôle plus fin :

```php theme={null}
$total = Product::count();
$bar = $this->output->createProgressBar($total);
$bar->start();

Product::cursor()->each(function (Product $product) use ($bar) {
    $this->processProduct($product);
    $bar->advance();
});

$bar->finish();
$this->newLine();
```

## Cas d'usage

### Commande d'import de données

Exemple d'import de produits depuis un CSV.

<Steps>
  <Step title="Générer la commande">
    ```shell theme={null}
    php artisan make:command ImportProducts
    ```
  </Step>

  <Step title="Implémentation">
    ```php theme={null}
    <?php

    namespace App\Console\Commands;

    use App\Models\Product;
    use Illuminate\Console\Command;

    class ImportProducts extends Command
    {
        protected $signature = 'import:products
                                {file : chemin du CSV}
                                {--limit= : nombre maximum d\'enregistrements}
                                {--dry-run : vérification (pas d\'enregistrement)}';

        protected $description = 'Importer les données produits depuis un CSV';

        public function handle(): void
        {
            $filePath = $this->argument('file');
            $limit    = $this->option('limit') ? (int) $this->option('limit') : null;
            $dryRun   = $this->option('dry-run');

            if (! file_exists($filePath)) {
                $this->error("Fichier introuvable : {$filePath}");
                return;
            }

            // Lecture CSV avec les fonctions natives PHP
            $handle = fopen($filePath, 'r');
            $headers = fgetcsv($handle); // Première ligne = en-têtes
            $rows = [];
            while (($row = fgetcsv($handle)) !== false) {
                $rows[] = array_combine($headers, $row);
                if ($limit !== null && count($rows) >= $limit) {
                    break;
                }
            }
            fclose($handle);

            $count = 0;

            $this->withProgressBar($rows, function (array $row) use ($dryRun, &$count) {
                if (! $dryRun) {
                    Product::updateOrCreate(
                        ['sku' => $row['sku']],
                        [
                            'name'  => $row['name'],
                            'price' => $row['price'],
                        ]
                    );
                }
                $count++;
            });

            $this->newLine();

            if ($dryRun) {
                $this->warn("{$count} enregistrements ciblés (dry-run : rien n'a été sauvegardé)");
            } else {
                $this->info("{$count} enregistrements importés.");
            }
        }
    }
    ```
  </Step>

  <Step title="Exécuter la commande">
    ```shell theme={null}
    # Import standard
    php artisan import:products storage/products.csv

    # Avec limite
    php artisan import:products storage/products.csv --limit=100

    # Vérification (dry-run)
    php artisan import:products storage/products.csv --dry-run
    ```
  </Step>
</Steps>

### Commande de maintenance périodique

Exemple : suppression de vieilles données.

```php theme={null}
<?php

namespace App\Console\Commands;

use App\Models\Order;
use Illuminate\Console\Command;

class PruneOldOrders extends Command
{
    protected $signature = 'orders:prune {--days=90 : supprimer plus vieilles que N jours}';

    protected $description = 'Supprimer les commandes terminées plus anciennes que N jours';

    public function handle(): void
    {
        $days = (int) $this->option('days');

        if (! $this->confirm("Supprimer les commandes terminées de plus de {$days} jours ?")) {
            $this->info('Annulé.');
            return;
        }

        $deleted = Order::where('status', 'completed')
            ->where('created_at', '<', now()->subDays($days))
            ->delete();

        $this->info("{$deleted} commandes supprimées.");
    }
}
```

### Combinaison avec la planification

Les commandes créées peuvent être planifiées dans `routes/console.php`.
Voir [Planification de tâches](/fr/scheduling).

```php theme={null}
use Illuminate\Support\Facades\Schedule;

// Tous les jours à 2h du matin
Schedule::command('orders:prune --days=90')->dailyAt('2:00');

// Chaque lundi à 9h
Schedule::command('import:products storage/weekly.csv')->weeklyOn(1, '9:00');
```

## Tester les commandes

La fonctionnalité de test de Laravel permet de vérifier vos commandes Artisan.

```php theme={null}
<?php

namespace Tests\Feature\Commands;

use App\Models\Order;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PruneOldOrdersTest extends TestCase
{
    use RefreshDatabase;

    public function test_it_deletes_old_completed_orders(): void
    {
        // Commande terminée de plus de 90 jours
        Order::factory()->create([
            'status'     => 'completed',
            'created_at' => now()->subDays(100),
        ]);

        // Commande récente (ne doit pas être supprimée)
        Order::factory()->create([
            'status'     => 'completed',
            'created_at' => now()->subDays(10),
        ]);

        $this->artisan('orders:prune', ['--days' => 90])
            ->expectsConfirmation('Supprimer les commandes terminées de plus de 90 jours ?', 'yes')
            ->expectsOutput('1 commandes supprimées.')
            ->assertExitCode(0);

        $this->assertDatabaseCount('orders', 1);
    }

    public function test_it_cancels_when_user_declines(): void
    {
        Order::factory()->create(['status' => 'completed', 'created_at' => now()->subDays(100)]);

        $this->artisan('orders:prune', ['--days' => 90])
            ->expectsConfirmation('Supprimer les commandes terminées de plus de 90 jours ?', 'no')
            ->expectsOutput('Annulé.')
            ->assertExitCode(0);

        $this->assertDatabaseCount('orders', 1);
    }
}
```

<Info>
  Assertions disponibles avec `artisan()` :

  | Méthode                           | Description                            |
  | --------------------------------- | -------------------------------------- |
  | `expectsOutput('...')`            | Vérifie qu'un texte est affiché        |
  | `expectsQuestion('?', 'réponse')` | Simule une saisie pour `ask()`         |
  | `expectsConfirmation('?', 'yes')` | Simule une réponse à `confirm()`       |
  | `expectsChoice('?', 'choix')`     | Simule un choix pour `choice()`        |
  | `assertExitCode(0)`               | Vérifie le code de sortie (0 = succès) |
  | `assertFailed()`                  | Vérifie que le code n'est pas 0        |
</Info>

## Commandes fréquentes

```shell theme={null}
# Créer une commande
php artisan make:command CommandName

# Lister les commandes
php artisan list

# Aide d'une commande
php artisan help command:name

# Démarrer Tinker
php artisan tinker
```


## Related topics

- [Laravel Prompts](/fr/prompts.md)
- [Processus](/fr/processes.md)
- [Service providers différés](/fr/advanced/deferred-provider.md)
- [Laravel Console Starter](/fr/packages/laravel-console-starter/index.md)
- [Tests de console](/fr/console-tests.md)
