Passer au contenu principal

Qu’est-ce que la classe Fluent

La classe Fluent est une classe utilitaire générique qui permet de traiter un tableau comme un objet. Implémentée dans Illuminate\Support\Fluent, elle existe depuis les premières versions de Laravel mais n’est presque pas mentionnée dans la documentation officielle. En interne, elle gère un tableau via des propriétés et permet l’accès en lecture/écriture façon propriétés via les méthodes magiques (__get / __set / __call).
Laravel 11 a ajouté le helper fluent() et la classe Fluent a été enrichie. C’est le bon moment pour tirer parti de cette excellente classe.

Création d’instance

Constructeur

use Illuminate\Support\Fluent;

// Initialisation à partir d'un tableau
$user = new Fluent(['name' => 'Laravel', 'type' => 'Framework']);

echo $user->name; // 'Laravel'
echo $user->type; // 'Framework'

Méthode fabrique make()

use Illuminate\Support\Fluent;

$config = Fluent::make([
    'host' => 'localhost',
    'port' => 3306,
    'database' => 'laravel'
]);

echo $config->host; // 'localhost'

Fonction helper fluent()

Laravel 11 a ajouté le helper fluent(). Il est équivalent à Fluent::make().
$request = fluent([
    'method' => 'POST',
    'path' => '/api/users',
    'status' => 201
]);

echo $request->method; // 'POST'

Accès aux propriétés

Lecture/écriture dynamique des propriétés

$fluent = new Fluent();

// Écriture
$fluent->name = 'Laravel';
$fluent->version = 13;

// Lecture
echo $fluent->name;    // 'Laravel'
echo $fluent->version; // 13

Chaînage de méthodes

Grâce à la méthode magique __call, l’appel d’une méthode inexistante définit une propriété. La méthode retourne $this, ce qui permet le chaînage.
$config = new Fluent();

$config
    ->host('localhost')
    ->port(3306)
    ->database('laravel')
    ->username('root')
    ->password('secret');

echo $config->host;     // 'localhost'
echo $config->password; // 'secret'
Ce mécanisme permet de définir des valeurs via une API fluide (Fluent Interface) au lieu d’un tableau.

Principales méthodes

get() — accès avec la notation par point

$user = new Fluent([
    'profile' => [
        'email' => '[email protected]',
        'phone' => '090-xxxx-xxxx'
    ]
]);

// Récupérer une valeur imbriquée avec la notation par point
$email = $user->get('profile.email'); // '[email protected]'
$phone = $user->get('profile.phone'); // '090-xxxx-xxxx'

// On peut préciser une valeur par défaut
$fax = $user->get('profile.fax', 'N/A'); // 'N/A'

set() — écriture avec la notation par point

$fluent = new Fluent();

$fluent->set('user.name', 'Laravel');
$fluent->set('user.email', '[email protected]');

print_r($fluent->toArray());
// Array (
//     [user] => Array (
//         [name] => Laravel
//         [email] => [email protected]
//     )
// )

fill() — définir plusieurs propriétés à la fois

$fluent = new Fluent(['initial' => 'value']);

$fluent->fill([
    'name' => 'Laravel',
    'version' => 13,
    'license' => 'MIT'
]);

echo $fluent->name;    // 'Laravel'
echo $fluent->version; // 13

all() — récupérer toutes les propriétés sous forme de tableau

$fluent = fluent([
    'name' => 'Laravel',
    'version' => 13,
    'license' => 'MIT'
]);

// Toutes les propriétés
$all = $fluent->all();
// ['name' => 'Laravel', 'version' => 13, 'license' => 'MIT']

// Seulement certaines
$subset = $fluent->all(['name', 'license']);
// ['name' => 'Laravel', 'license' => 'MIT']

scope() — convertir une valeur imbriquée en un nouveau Fluent

$config = fluent([
    'database' => [
        'host' => 'localhost',
        'port' => 3306,
        'name' => 'laravel'
    ]
]);

$dbConfig = $config->scope('database');
// $dbConfig est une nouvelle instance Fluent

echo $dbConfig->host; // 'localhost'
echo $dbConfig->port; // 3306
Cela permet de manipuler un tableau imbriqué comme un autre objet Fluent.

value() — valeur par défaut dynamique via un callback

$user = fluent(['role' => 'admin']);

// Si la clé existe, la valeur est retournée
$role = $user->value('role'); // 'admin'

// Si la clé n'existe pas, la valeur par défaut est retournée
$status = $user->value('status', 'active');
// 'active'

// Valeur par défaut spécifiée via callback
$timestamp = $user->value('updated_at', function () {
    return now()->toIso8601String();
});

Manipulations de tableau

toArray() — conversion en tableau

$fluent = fluent(['name' => 'Laravel', 'version' => 13]);

$array = $fluent->toArray();
// ['name' => 'Laravel', 'version' => 13]

print_r($array);

getAttributes() — accès direct aux attributs internes

$fluent = fluent(['a' => 1, 'b' => 2]);

$attributes = $fluent->getAttributes();
// ['a' => 1, 'b' => 2]

Interface ArrayAccess

Fluent implémente ArrayAccess, donc il se manipule comme un tableau.
$config = new Fluent();

// Affectation type tableau
$config['host'] = 'localhost';
$config['port'] = 3306;

// Lecture type tableau
echo $config['host']; // 'localhost'

// Vérification d'existence
if (isset($config['port'])) {
    echo $config['port'];
}

// Suppression
unset($config['port']);

Interface IteratorAggregate

Fluent peut être itéré via foreach.
$settings = fluent([
    'debug' => true,
    'cache' => 'redis',
    'queue' => 'database'
]);

foreach ($settings as $key => $value) {
    echo "$key: $value\n";
    // debug: 1
    // cache: redis
    // queue: database
}

Traitement JSON

toJson() — conversion en chaîne JSON

$response = fluent([
    'success' => true,
    'data' => ['id' => 1, 'name' => 'User']
]);

$json = $response->toJson();
// {"success":true,"data":{"id":1,"name":"User"}}

// Utilisable en réponse API
return $json;

toPrettyJson() — JSON formaté

$data = fluent([
    'users' => [
        ['id' => 1, 'name' => 'Alice'],
        ['id' => 2, 'name' => 'Bob']
    ]
]);

echo $data->toPrettyJson();
// {
//     "users": [
//         {
//             "id": 1,
//             "name": "Alice"
//         },
//         {
//             "id": 2,
//             "name": "Bob"
//         }
//     ]
// }

Interface JsonSerializable

Fluent implémente JsonSerializable : json_encode() peut donc le convertir directement.
$fluent = fluent(['status' => 'ok', 'code' => 200]);

$json = json_encode($fluent);
// {"status":"ok","code":200}

$data = json_decode($json, true);
// ['status' => 'ok', 'code' => 200]

Vérification d’état

isEmpty() / isNotEmpty()

$empty = new Fluent();
$filled = fluent(['value' => 1]);

$empty->isEmpty();      // true
$empty->isNotEmpty();   // false

$filled->isEmpty();     // false
$filled->isNotEmpty();  // true

Trait Conditionable

Fluent utilise le trait Conditionable et prend en charge le traitement conditionnel.
$config = fluent(['env' => 'production']);

$config
    ->when($config->env === 'production', function ($fluent) {
        $fluent->debug = false;
        $fluent->cache = 'redis';
    })
    ->when($config->env === 'local', function ($fluent) {
        $fluent->debug = true;
        $fluent->cache = 'array';
    });

echo $config->debug;
echo $config->cache;
Les méthodes when() / unless() permettent d’écrire de manière fluide une configuration conditionnelle.

Trait Macroable

Fluent utilise également le trait Macroable, ce qui permet d’ajouter dynamiquement des méthodes.
use Illuminate\Support\Fluent;

// À définir dans la méthode boot() d'un provider
Fluent::macro('isProduction', function () {
    /** @var Fluent $this */
    return $this->env === 'production';
});

Fluent::macro('isDevelopment', function () {
    /** @var Fluent $this */
    return $this->env === 'development';
});

// Utilisation
$config = fluent(['env' => 'production']);

if ($config->isProduction()) {
    // Traitement production
}

Cas d’usage pratiques

Constructeur de réponse API

namespace App\Support;

use Illuminate\Support\Fluent;

class ApiResponse
{
    public static function success($data = null, string $message = 'Success'): string
    {
        return fluent([
            'success' => true,
            'message' => $message,
            'data' => $data,
            'timestamp' => now()->toIso8601String()
        ])->toJson();
    }

    public static function error(string $message, int $code = 400): string
    {
        return fluent([
            'success' => false,
            'message' => $message,
            'code' => $code,
            'timestamp' => now()->toIso8601String()
        ])->toJson();
    }
}

// Utilisation dans un contrôleur
public function store(Request $request)
{
    $user = User::create($request->validated());

    return response()->json(
        json_decode(ApiResponse::success(['id' => $user->id]))
    );
}

Constructeur de configuration

$dbConfig = fluent()
    ->host(env('DB_HOST', 'localhost'))
    ->port(env('DB_PORT', 3306))
    ->database(env('DB_DATABASE', 'laravel'))
    ->username(env('DB_USERNAME', 'root'))
    ->password(env('DB_PASSWORD', ''))
    ->charset('utf8mb4')
    ->collation('utf8mb4_unicode_ci')
    ->when(env('APP_ENV') === 'production', function ($config) {
        $config->sslmode('require');
        $config->sslcert(env('DB_SSL_CERT'));
    });

// Vérifier et utiliser la config
config(['database.connections.mysql' => $dbConfig->toArray()]);

Validation et transformation des paramètres de requête

namespace App\Services;

use Illuminate\Support\Fluent;

class SearchFilter
{
    public function apply(array $params): Fluent
    {
        $filter = fluent()
            ->page($params['page'] ?? 1)
            ->perPage($params['per_page'] ?? 15)
            ->sort($params['sort'] ?? 'created_at')
            ->order($params['order'] ?? 'desc')
            ->when(isset($params['search']), function ($f) use ($params) {
                $f->search = $params['search'];
            })
            ->when(isset($params['status']), function ($f) use ($params) {
                $f->status = $params['status'];
            });

        // Calcul de la pagination
        $filter->offset = ($filter->page - 1) * $filter->perPage;

        return $filter;
    }
}

// Utilisation
$filter = app(SearchFilter::class)->apply(request()->all());

$users = User::query()
    ->when($filter->has('search'), fn ($q) => $q->search($filter->search))
    ->when($filter->has('status'), fn ($q) => $q->where('status', $filter->status))
    ->orderBy($filter->sort, $filter->order)
    ->offset($filter->offset)
    ->limit($filter->perPage)
    ->get();

Combinaison Model et Fluent

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Fluent;

class Post extends Model
{
    protected $casts = [
        'metadata' => 'json'
    ];

    public function getMetadataAttribute($value): Fluent
    {
        return new Fluent($value ?? []);
    }

    public function setMetadataAttribute($value): void
    {
        if ($value instanceof Fluent) {
            $this->attributes['metadata'] = $value->toJson();
        } else {
            $this->attributes['metadata'] = json_encode($value);
        }
    }
}

// Utilisation
$post = new Post();

$post->metadata = fluent()
    ->title('SEO Title')
    ->description('Meta Description')
    ->keywords(['laravel', 'fluent', 'tutorial'])
    ->author('Laravel Community');

$post->save();

// Lecture
$post = Post::first();
echo $post->metadata->title; // 'SEO Title'
echo $post->metadata->author; // 'Laravel Community'

Normalisation des données de formulaire

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Fluent;

class CreateUserRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'name' => 'required|string',
            'email' => 'required|email|unique:users',
            'password' => 'required|min:8|confirmed',
            'role' => 'in:user,admin',
            'preferences' => 'json',
        ];
    }

    // Retourne les données validées sous forme de Fluent
    public function toFluent(): Fluent
    {
        $preferences = is_string($this->preferences)
            ? json_decode($this->preferences, true)
            : $this->preferences;

        return fluent([
            'name' => $this->name,
            'email' => $this->email,
            'password' => bcrypt($this->password),
            'role' => $this->role ?? 'user',
            'preferences' => new Fluent($preferences ?? [])
        ]);
    }
}

// Utilisation dans un contrôleur
public function store(CreateUserRequest $request)
{
    $data = $request->toFluent();

    $user = User::create($data->all());

    return response()->json(['success' => true, 'user_id' => $user->id]);
}

Comparaison avec d’autres classes

Fluent vs Array

FonctionnalitéFluentArray
Accès aux propriétés$fluent->name$array['name']
Chaînage de méthodes✓ Pris en charge✗ Non
Conversion JSONMéthode toJson()Fonction json_encode()
Notation par pointget('user.name')✗ À gérer manuellement
Vérification d’étatisEmpty()Fonction empty()
Ajout dynamique de méthodesMacroable✗ Impossible

Fluent vs Model

FonctionnalitéFluentModel
Persistance BDD✗ Non✓ Automatique
Efficacité mémoire✓ Léger✗ Lourd
Relations✗ Non✓ Prises en charge
Casts✗ Non✓ Pris en charge
Validation✗ Non✓ Prise en charge
API fluide✓ Oui△ Limitée

Détails d’implémentation interne

class Fluent
{
    protected $attributes = [];

    // Méthode magique : accès à une propriété inexistante
    public function __get($key)
    {
        return $this->value($key);
    }

    // Méthode magique : affectation d'une propriété inexistante
    public function __set($key, $value)
    {
        $this->offsetSet($key, $value);
    }

    // Méthode magique : appel d'une méthode inexistante
    // Le nom de la méthode devient la clé de la propriété
    public function __call($method, $parameters)
    {
        $this->attributes[$method] = count($parameters) > 0
            ? $parameters[0]
            : true;

        return $this;
    }
}
Si aucune macro n’est enregistrée, la méthode __call utilise le nom de la méthode comme clé et définit la valeur, puis retourne $this. C’est ce qui rend possible le chaînage de méthodes.
Fluent utilise les traits suivants, chacun fournissant des fonctionnalités différentes :
  • Conditionable — traitement conditionnel avec when() / unless()
  • InteractsWithData — méthodes de manipulation de données comme data()
  • Macroable — ajout dynamique de méthodes

Étapes suivantes

Classe Collection

Approfondissez la classe Collection qui manipule plusieurs éléments.

Trait Conditionable

Apprenez à écrire des traitements conditionnels fluides avec le trait Conditionable.

Trait Macroable

Apprenez à ajouter dynamiquement des méthodes à une classe existante avec le trait Macroable.
Dernière modification le 13 juillet 2026