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 トレイト
Fluent は Conditionable トレイトを使用しており、条件付き処理をサポートします。
$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 トレイト
Fluent は Macroable トレイトも使用しており、動的にメソッドを追加できます。
use Illuminate\Support\Fluent;
// プロバイダーの 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();
}
}
// コントローラーで使用
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 ?? [])
]);
}
}
// コントローラーで使用
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
| 機能 | Fluent | Array |
|---|
| プロパティアクセス | $fluent->name | $array['name'] |
| メソッドチェーン | ✓ サポート | ✗ なし |
| JSON 変換 | toJson() メソッド | json_encode() 関数 |
| ドット記法 | ✓ get('user.name') | ✗ 手動で処理 |
| 状態確認 | isEmpty() | empty() 関数 |
| 動的メソッド追加 | Macroable | ✗ 不可 |
Fluent vs Model
| 機能 | Fluent | Model |
|---|
| DB 永続化 | ✗ なし | ✓ 自動 |
| メモリ効率 | ✓ 軽量 | ✗ 重い |
| リレーション | ✗ なし | ✓ サポート |
| キャスト | ✗ なし | ✓ サポート |
| バリデーション | ✗ なし | ✓ サポート |
| 流暢な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 は以下のトレイトを使用しており、それぞれ異なる機能を提供しています:
- Conditionable —
when() / unless() で条件付き処理
- InteractsWithData —
data() などのデータ操作メソッド
- Macroable — 動的メソッド追加
次のステップ
Collection クラス
複数要素を扱う Collection クラスを深掘りします。
Conditionable トレイト
条件付き処理を流暢に記述する Conditionable トレイトを学びます。
Macroable トレイト
既存クラスに動的メソッドを追加する Macroable トレイトを学びます。