---
title: "Castor, le task runner des devs PHP ?"
excerpt: "Découvrons Castor, un task runner pour les développeurs PHP. Il ce positionne comme un outil simple et efficace pour gérer les tâches récurrentes de vos projets, et comme alternative à des outils comme Makefile ou Taskfile."
publishDate: 2024-03-15T00:00:00.000Z
tags: ["php", "castor", "task-runner"]
canonical: "https://yoandev.co/castor-le-task-runner-des-devs-php"
---

<YouTube id="Xf7wtHdn33s" />

## Un task runner ?

Un *task runner* est un outil qui permet de gérer et centraliser les tâches récurrentes d'un projet. Cela peut être des tâches de compilation, de tests, de déploiement, etc.

C'est un outil hyper intéressant pour les équipes de développement. Il permet de gagner du temps, de standardiser les commandes, de les documenter, etc. C'est un must-have pour tous projet sérieux (et c'est une aide précieuse pour l'onboarding des nouveaux membres de l'équipe).

J'en avais déjà parlé dans une précédente vidéo, en abordant l'interet d'un fichier `Makefile` pour les projets Symfony, et personnellement, j'utilise une variation de ce fichier dans l'ensemble des projets sur lesquels j'interviens.
(Le dépot est disponible [ici](https://github.com/yoanbernabeu/Symfony-And-Docker-Makefile-Taskfile))

## Castor ?

Alors qu'il existe déjà des outils comme `Makefile` ou `Taskfile`, pourquoi en créer un autre ? Pour améliorer la DX (Developer Experience) !

Castor est ecrit en PHP et il permet de bénéficier de l'ecosystème PHP pour nous simplifier la vie. Il propose de nombreuses fonctionnalités telle que que l'analyse transparente des arguments, le support de l'autocomplétion, et une série de fonctions intégrées pour simplifier le traitement des tâches courantes (exécution de commandes, parallélisation, logs, etc).

Et puis, si vous êtes dev PHP pourquoi apprendre un autre langage pour ça ? Autant rester dans votre zone de confort 🤣

Je profite de cet article pour remercier [JoliCode](https://jolicode.com/) pour le travail effectué sur cet outil (et tant d'autres).

## Installation

Castos est dispo pour Linux, MacOS et Windows. (La suite de l'article sera basée sur une installation Linux)

```bash
curl "https://github.com/jolicode/castor/releases/latest/download/castor.linux-amd64.phar" -Lfso $HOME/.local/bin/castor && \
    chmod u+x $HOME/.local/bin/castor && \
    castor --version || \
    (echo "Could not install castor. Is the target directory writeable?" && (exit 1))
```

> N'hésitez pas à consulter la [documentation officielle](https://castor.jolicode.com/getting-started/installation/) pour plus d'informations.

## Initier un fichier pour Castor

Pour initier un fichier Castor, il suffit d'éxécuter la commande suivante :

```bash
castor
```

L'outil va alors vous proposer de créer un fichier `castor.php` avec un exemple de tâche.

```php
<?php

use Castor\Attribute\AsTask;

use function Castor\io;
use function Castor\capture;

#[AsTask(description: 'Welcome to Castor!')]
function hello(): void
{
    $currentUser = capture('whoami');

    io()->title(sprintf('Hello %s!', $currentUser));
}
```

En parrallèle, Castor va également créer un fichier `.castor.stub.php` à la racine de votre projet. Ce fichier permet de définir des variables globales pour votre projet.

Ce fichier contient certaines définitions de classes et méthodes de Castor et de certaines de ses dépendances.

Cela s'avère utile lorsque vous installez Castor depuis un PHAR (c'est le cas dans notre exemple). Sans ce fichier, votre IDE signalerait qu'il ne comprend pas certaines classes et ne fournirait pas d'autocomplétion dans vos fichiers castor.

La doc nous invite à ajouter ce fichier à notre `.gitignore` pour éviter de le partager avec les autres membres de l'équipe.

## Exécuter une tâche

Pour exécuter une tâche, il suffit de lancer la commande suivante :

```bash
castor hello
```

Ce qui devrait vous afficher un message de bienvenue de ce type :

```bash
> castor hello       

Hello yoan!
===========
```

## Testons les possibilités de Castor

### Les fonctions IO

Castor propose une série de fonctions pour gérer les entrées/sorties. Par exemple, `io()->title()` permet d'afficher un titre, `io()->section()` pour afficher une section, `io()->text()` pour afficher du texte, etc.

> En arrière plan, Castor utilise la librairie [Symfony Console](https://symfony.com/doc/current/components/console.html), si vous êtes familier avec cette librairie, vous ne serez pas dépaysé.

```php
#[AsTask(description: 'Test IO functions')]
function testIo(): void
{
    io()->title('Title');
    io()->section('Section');
    io()->text('Text');
    io()->success('Success');
    io()->error('Error');

    // Bar de progression
    io()->progressStart(100);
    for ($i = 0; $i < 100; ++$i) {
        io()->progressAdvance();
        usleep(1000);
    }
    io()->progressFinish();
}
```

### Passer des arguments à une tâche

Il est possible de passer des arguments à une tâche. Par exemple, pour passer un argument `name`et `age` à une tâche `test-input` :

```php
#[AsTask(description: 'Test Input functions')]
function testInput(
    string $name,
    int $age
): void
{
    $name = input()->getArgument('name');
    $age = input()->getArgument('age');

    io()->text(sprintf('Salut %s, tu as %d ans.', $name, $age));
}
```

Et pour lancer la tâche :

```bash
castor test-input yoan 39
```

### Exécuter des commandes externes

* Exécuter simplement une commande

Il est possible d'exécuter des commandes externes (c'est même le coeur de cible d'un task runner). Par exemple, pour exécuter la commande `ls -la` :

```php
// ...
use function Castor\run;
// ...

#[AsTask(description: 'Test command execution')]
function testCommand(): void
{
    run('ls -la');
}
```

* Gérer la bonne exécution de la commande

Il est possible de gérer la bonne exécution de la commande. Par exemple, pour exécuter la commande `ls -la` et gérer la sortie :

```php
// ...
use function Castor\run;
// ...

#[AsTask(description: 'Test command execution')]
function testCommand2(): void
{
    $result = run('ls -la');

    if ($result->isSuccessful()) {
        io()->success('Command executed successfully');
    } else {
        io()->error('Command failed');
    }
}
```

* Gérer le repertoire de travail

Il est possible de gérer le répertoire de travail. Par exemple, pour exécuter la commande `ls -la` dans le répertoire `/tmp` :

```php
#[AsTask(description: 'Test command execution')]
function testCommand3(): void
{
    run('ls -la', path: '/tmp');
}
```

### Parallélisation

Castor propose également des fonctions pour exécuter des tâches en parallèle. Par exemple, pour exécuter 5 commandes en parallèle :

```php
// ...
use function Castor\parallel;
// ...
#[AsTask(description: 'Test parallel execution')]
function testParallel(): void
{
    parallel(
        fn() => run('sleep 10 && echo "Lorem"'),
        fn() => run('sleep 5 && echo "Ipsum"'),
        fn() => run('sleep 1 && echo "Dolor"'),
        fn() => run('sleep 8 && echo "Sit"'),
        fn() => run('sleep 1 && echo "World"')
    );
}
```

### Logs

Castor propose également des fonctions pour gérer les logs. Par exemple, pour logger un message :

```php
// ...
use function Castor\log;
// ...
#[AsTask(description: 'Test logs')]
function testLogs(): void
{
    log('This is an info message', 'info');
    log('This is an notice message', 'notice');
    log('This is an warning message', 'warning');
    log('This is an error message', 'error');
}
```

Si on exécute la tâche `testLogs`, on obtient le résultat suivant :

```bash
> castor test-log     
14:12:54 WARNING   [castor] This is an warning message
14:12:54 ERROR     [castor] This is an error message
```

Pour voir plus de logs, il faut lancer la commande avec des flags pour préciser le niveau de log. Par exemple :

```bash
> castor test-log # Pour afficher les logs de niveau "warning" et supérieur
> castor test-log -v # Pour afficher les logs de niveau "notice" et supérieur
> castor test-log -vv # Pour afficher les logs de niveau "info" et supérieur
```

### Notifications

Castor propose également des fonctions pour gérer les notifications. Par exemple, pour envoyer une notification :

```php
// ...
use function Castor\notify;
// ...

#[AsTask(description: 'Test notify')]
function testNotify(): void
{
    notify('This is a notification');
}
```

Pratique pour être notifié lorsqu'une tâche (longue) est terminée !

## Compiler votre projet ?

Ok, c'est bien beau, mais on ne pourrait pas compiler notre CLI en un seul fichier ? Evidemment, les développeurs de Castor ont pensé à tout !

### Packager votre projet en un seul fichier .phar

> Le fichier `.phar` (PHP Archive) est un format de fichier pour PHP qui permet de packager un projet en un seul fichier. Composer est un exemple de projet qui utilise ce format.

Il est possible de packager votre projet en un seul fichier `.phar`.

Pour cela, il faut au préalable installer Castor avec composer :

```bash
composer require jolicode/castor
```

Il faut également installer au préalable Box, un outil pour packager des projets PHP en un seul fichier `.phar` :

```bash
composer global require humbug/box
```

Ensuite, il suffit de lancer la commande suivante pour packager votre projet :

```bash
vendor/bin/castor repack
```

Et voilà, vous avez un fichier `my-app.linux.phar` qui contient votre projet est prêt à être distribué !

Vous pouvez alors lancer votre projet avec la commande suivante :

```bash
php my-app.linux.phar hello
```

### Compiler le projet en un binaire ?

Ne nous arrêtons pas en si bon chemin, il est également possible de compiler votre projet en un binaire. C'est pas top ça ? Et encore une fois ça va être super simple !

```bash
vendor/bin/castor compile my-app.linux.phar
```

La commande va alors générer un fichier `my-app.linux.x86_64` qui est un binaire prêt à être distribué.

Testons le binaire :

```bash
chmod +x my-app.linux.x86_64
./my-app.linux.x86_64
```

Et voilà, vous avez votre projet qui s'exécute en un binaire ! C'est pas beau ça ?

## Conclusion

Castor est un outil hyper intéressant pour les développeurs et développeuses PHP. Il ce positionne comme un outil simple, efficace et extensible (nous n'avons que survolé les possibilités de Castor dans cet article).

Il est un excellent choix pour gérer les commandes récurrentes de vos projets, et comme alternative à des outils comme Makefile ou Taskfile.

La possibilité de packager votre projet en un seul fichier `.phar` ou en un binaire est un gros plus pour la distribution de vos projets, et je pense personnellement l'utiliser dans certains de mes projets (Aussi bien en local que pour des tâches de CI/CD).
