跳转到主要内容

什么是 Fluent 类

Fluent 类是可以像对象一样处理数组的通用工具类。它实现在 Illuminate\Support\Fluent 中,自 Laravel 早期版本就存在,但在官方文档中几乎没有记载。 在内部它使用属性管理数组,并通过魔术方法(__get / __set / __call)实现类似属性的读写。
Laravel 11 新增了 fluent() 辅助函数,同时 Fluent 类也被强化,是一个正当其时值得使用的优秀类。

创建实例

构造函数

use Illuminate\Support\Fluent;

// 用数组初始化
$user = new Fluent(['name' => 'Laravel', 'type' => 'Framework']);

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

make() 工厂方法

use Illuminate\Support\Fluent;

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

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

fluent() 辅助函数

Laravel 11 中新增了 fluent() 辅助函数,与 Fluent::make() 等价。
$request = fluent([
    'method' => 'POST',
    'path' => '/api/users',
    'status' => 201
]);

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

访问属性

动态属性的读写

$fluent = new Fluent();

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

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

链式调用

通过 __call 魔术方法,调用不存在的方法时会将参数设置为对应属性。方法返回 $this,因此可以链式调用。
$config = new Fluent();

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

echo $config->host;     // 'localhost'
echo $config->password; // 'secret'
通过这种机制,你可以以流畅 API(Fluent Interface)的方式代替数组来设置值。

主要方法

get() — 用点号语法访问

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

// 用点号语法获取嵌套值
$email = $user->get('profile.email'); // '[email protected]'
$phone = $user->get('profile.phone'); // '090-xxxx-xxxx'

// 可以指定默认值
$fax = $user->get('profile.fax', 'N/A'); // 'N/A'

set() — 用点号语法设置

$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() — 一次性设置多个属性

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

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

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

all() — 以数组形式获取全部属性

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

// 全部属性
$all = $fluent->all();
// ['name' => 'Laravel', 'version' => 13, 'license' => 'MIT']

// 仅获取特定属性
$subset = $fluent->all(['name', 'license']);
// ['name' => 'Laravel', 'license' => 'MIT']

scope() — 将嵌套值转换为新的 Fluent

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

$dbConfig = $config->scope('database');
// $dbConfig 是一个新的 Fluent 实例

echo $dbConfig->host; // 'localhost'
echo $dbConfig->port; // 3306
由此可以将嵌套的数组当作另一个 Fluent 对象处理。

value() — 用回调动态指定默认值

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

// 若键存在,则返回值
$role = $user->value('role'); // 'admin'

// 若键不存在,则返回默认值
$status = $user->value('status', 'active');
// 'active'

// 用回调指定默认值
$timestamp = $user->value('updated_at', function () {
    return now()->toIso8601String();
});

数组操作

toArray() — 转换为数组

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

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

print_r($array);

getAttributes() — 直接访问内部属性

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

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

ArrayAccess 接口

Fluent 实现了 ArrayAccess,可以像数组一样操作。
$config = new Fluent();

// 像数组一样赋值
$config['host'] = 'localhost';
$config['port'] = 3306;

// 像数组一样读取
echo $config['host']; // 'localhost'

// 存在性检查
if (isset($config['port'])) {
    echo $config['port'];
}

// 删除
unset($config['port']);

IteratorAggregate 接口

Fluent 可以用 foreach 循环。
$settings = fluent([
    'debug' => true,
    'cache' => 'redis',
    'queue' => 'database'
]);

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

JSON 处理

toJson() — 转换为 JSON 字符串

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

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

// 可用于 API 响应
return $json;

toPrettyJson() — 转换为格式化的 JSON

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

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

JsonSerializable 接口

Fluent 实现了 JsonSerializable,因此可以直接通过 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]

状态检查

isEmpty() / isNotEmpty()

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

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

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

Conditionable trait

Fluent 使用了 Conditionable trait,支持条件处理。
$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;
使用 when() / unless() 方法可以流畅地基于条件描述配置。

Macroable trait

Fluent 也使用了 Macroable trait,可以动态添加方法。
use Illuminate\Support\Fluent;

// 在 Provider 的 boot() 方法中定义
Fluent::macro('isProduction', function () {
    /** @var Fluent $this */
    return $this->env === 'production';
});

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

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

if ($config->isProduction()) {
    // 生产环境处理
}

实战用例

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

// 在 Controller 中使用
public function store(Request $request)
{
    $user = User::create($request->validated());

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

配置构造器

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

// 校验后作为配置使用
config(['database.connections.mysql' => $dbConfig->toArray()]);

请求参数的校验与转换

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

        // 计算分页
        $filter->offset = ($filter->page - 1) * $filter->perPage;

        return $filter;
    }
}

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

模型与 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);
        }
    }
}

// 使用
$post = new Post();

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

$post->save();

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

表单数据的规范化

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

    // 将校验后的数据作为 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 ?? [])
        ]);
    }
}

// 在 Controller 中使用
public function store(CreateUserRequest $request)
{
    $data = $request->toFluent();

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

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

与其他类的对比

Fluent vs Array

功能FluentArray
属性访问$fluent->name$array['name']
链式调用✓ 支持✗ 无
JSON 转换toJson() 方法json_encode() 函数
点号语法get('user.name')✗ 手动处理
状态检查isEmpty()empty() 函数
动态添加方法Macroable✗ 不可

Fluent vs Model

功能FluentModel
数据库持久化✗ 无✓ 自动
内存占用✓ 轻量✗ 较重
关联关系✗ 无✓ 支持
Cast✗ 无✓ 支持
校验✗ 无✓ 支持
流畅 API✓ 有△ 有限

内部实现细节

class Fluent
{
    protected $attributes = [];

    // 魔术方法:访问不存在的属性
    public function __get($key)
    {
        return $this->value($key);
    }

    // 魔术方法:设置不存在的属性
    public function __set($key, $value)
    {
        $this->offsetSet($key, $value);
    }

    // 魔术方法:调用不存在的方法
    // 方法名直接作为属性键
    public function __call($method, $parameters)
    {
        $this->attributes[$method] = count($parameters) > 0
            ? $parameters[0]
            : true;

        return $this;
    }
}
当没有注册宏时,__call 方法会将方法名作为属性键设置值,并返回 $this。这就是链式调用能够成立的原因。
Fluent 使用了以下 trait,各自提供不同功能:
  • Conditionable — 用 when() / unless() 进行条件处理
  • InteractsWithDatadata() 等数据操作方法
  • Macroable — 动态方法添加

下一步

Collection 类

深入了解处理多元素的 Collection 类。

Conditionable trait

学习流畅描述条件处理的 Conditionable trait。

Macroable trait

学习向既有类添加动态方法的 Macroable trait。
最后修改于 2026年7月13日