Saltar al contenido principal

Qué es la clase Fluent

La clase Fluent es una utilidad genérica que permite tratar un array como si fuera un objeto. Está implementada en Illuminate\Support\Fluent y existe desde las primeras versiones de Laravel, aunque la documentación oficial apenas la menciona. Internamente gestiona un array como una propiedad y, mediante métodos mágicos (__get / __set / __call), permite leer y escribir como si fueran propiedades.
En Laravel 11 se añadió el helper fluent() y se reforzaron las funcionalidades de la clase Fluent, así que ahora es el momento perfecto para aprovechar esta gran clase.

Crear instancias

Constructor

use Illuminate\Support\Fluent;

// Inicialización con un array
$user = new Fluent(['name' => 'Laravel', 'type' => 'Framework']);

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

Método factory make()

use Illuminate\Support\Fluent;

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

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

Helper fluent()

En Laravel 11 se añadió el helper fluent(). Es equivalente a Fluent::make().
$request = fluent([
    'method' => 'POST',
    'path' => '/api/users',
    'status' => 201
]);

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

Acceso a las propiedades

Lectura y escritura de propiedades dinámicas

$fluent = new Fluent();

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

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

Encadenamiento de métodos

Gracias al método mágico __call, si llamas a un método inexistente se establece una propiedad. Como el método devuelve $this, puedes encadenarlos.
$config = new Fluent();

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

echo $config->host;     // 'localhost'
echo $config->password; // 'secret'
De esta manera puedes definir valores mediante una API fluida (Fluent Interface) en lugar de con un array.

Métodos principales

get() — acceso con notación de puntos

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

// Obtener valores anidados con notación de puntos
$email = $user->get('profile.email'); // '[email protected]'
$phone = $user->get('profile.phone'); // '090-xxxx-xxxx'

// Se puede indicar un valor por defecto
$fax = $user->get('profile.fax', 'N/A'); // 'N/A'

set() — asignación con notación de puntos

$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() — establecer varias propiedades a la vez

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

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

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

all() — obtener todas las propiedades como array

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

// Todas las propiedades
$all = $fluent->all();
// ['name' => 'Laravel', 'version' => 13, 'license' => 'MIT']

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

scope() — convertir un valor anidado en un nuevo Fluent

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

$dbConfig = $config->scope('database');
// $dbConfig es una nueva instancia de Fluent

echo $dbConfig->host; // 'localhost'
echo $dbConfig->port; // 3306
Así puedes tratar arrays anidados como otros objetos Fluent independientes.

value() — valor por defecto dinámico con callback

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

// Si la clave existe, devuelve el valor
$role = $user->value('role'); // 'admin'

// Si la clave no existe, devuelve el valor por defecto
$status = $user->value('status', 'active');
// 'active'

// Puedes indicar el valor por defecto con un callback
$timestamp = $user->value('updated_at', function () {
    return now()->toIso8601String();
});

Operaciones sobre el array

toArray() — convertir a array

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

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

print_r($array);

getAttributes() — acceder directamente a los atributos internos

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

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

Interfaz ArrayAccess

Como Fluent implementa ArrayAccess, puedes usarlo como si fuera un array.
$config = new Fluent();

// Asignar como si fuera un array
$config['host'] = 'localhost';
$config['port'] = 3306;

// Leer como si fuera un array
echo $config['host']; // 'localhost'

// Comprobar existencia
if (isset($config['port'])) {
    echo $config['port'];
}

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

Interfaz IteratorAggregate

Fluent se puede recorrer con foreach.
$settings = fluent([
    'debug' => true,
    'cache' => 'redis',
    'queue' => 'database'
]);

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

Manejo de JSON

toJson() — convertir a cadena JSON

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

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

// Se puede usar en respuestas de API
return $json;

toPrettyJson() — convertir a JSON con formato

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

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

Interfaz JsonSerializable

Como Fluent implementa JsonSerializable, se puede convertir directamente con json_encode().
$fluent = fluent(['status' => 'ok', 'code' => 200]);

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

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

Comprobación de estado

isEmpty() / isNotEmpty()

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

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

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

Trait Conditionable

Fluent usa el trait Conditionable, lo que le permite ejecutar procesamiento condicional.
$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;
Con when() / unless() puedes escribir configuraciones condicionales de forma fluida.

Trait Macroable

Fluent también usa el trait Macroable, así que puedes añadirle métodos de forma dinámica.
use Illuminate\Support\Fluent;

// Definidos en boot() de un provider
Fluent::macro('isProduction', function () {
    /** @var Fluent $this */
    return $this->env === 'production';
});

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

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

if ($config->isProduction()) {
    // Lógica de producción
}

Casos de uso prácticos

Constructor de respuestas de 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();
    }
}

// Uso en un controlador
public function store(Request $request)
{
    $user = User::create($request->validated());

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

Constructor de configuración

$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'));
    });

// Verifica y aplica la configuración
config(['database.connections.mysql' => $dbConfig->toArray()]);

Validación y transformación de parámetros de petición

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'];
            });

        // Cálculo de la paginación
        $filter->offset = ($filter->page - 1) * $filter->perPage;

        return $filter;
    }
}

// Uso
$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();

Combinar un modelo con 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);
        }
    }
}

// Uso
$post = new Post();

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

$post->save();

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

Normalización de datos de formulario

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',
        ];
    }

    // Devuelve los datos validados como 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 ?? [])
        ]);
    }
}

// Uso en el controlador
public function store(CreateUserRequest $request)
{
    $data = $request->toFluent();

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

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

Comparativa con otras clases

Fluent vs Array

FunciónFluentArray
Acceso a propiedades$fluent->name$array['name']
Method chaining✓ Soportado✗ No
Conversión a JSONMétodo toJson()Función json_encode()
Notación de puntosget('user.name')✗ Manual
Comprobación de estadoisEmpty()Función empty()
Añadir métodos dinámicosMacroable✗ No

Fluent vs Modelo

FunciónFluentModelo
Persistencia en BD✗ No✓ Automática
Eficiencia de memoria✓ Ligero✗ Pesado
Relaciones✗ No✓ Soportadas
Casts✗ No✓ Soportados
Validación✗ No✓ Soportada
API fluida✓ Sí△ Limitada

Detalles de la implementación interna

class Fluent
{
    protected $attributes = [];

    // Método mágico: acceso a propiedades inexistentes
    public function __get($key)
    {
        return $this->value($key);
    }

    // Método mágico: asignación de propiedades inexistentes
    public function __set($key, $value)
    {
        $this->offsetSet($key, $value);
    }

    // Método mágico: llamada a métodos inexistentes
    // El nombre del método se convierte en la clave de la propiedad
    public function __call($method, $parameters)
    {
        $this->attributes[$method] = count($parameters) > 0
            ? $parameters[0]
            : true;

        return $this;
    }
}
Si no hay una macro registrada, __call toma el nombre del método como clave y almacena el valor en los atributos, devolviendo $this. Esto es lo que hace posible el encadenamiento.
Fluent utiliza los siguientes traits, cada uno de los cuales aporta funcionalidades diferentes:
  • Conditionable — Procesamiento condicional con when() / unless().
  • InteractsWithData — Métodos para manipular datos como data().
  • Macroable — Añadir métodos de forma dinámica.

Próximos pasos

Clase Collection

Profundiza en la clase Collection para manejar múltiples elementos.

Trait Conditionable

Aprende el trait Conditionable para escribir procesamiento condicional de forma fluida.

Trait Macroable

Aprende el trait Macroable para añadir métodos dinámicamente a clases existentes.
Última modificación el 13 de julio de 2026