메인 콘텐츠로 건너뛰기

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

Config 빌더

$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

기능FluentArray
프로퍼티 접근$fluent->name$array['name']
메서드 체인✓ 지원✗ 없음
JSON 변환toJson() 메서드json_encode() 함수
도트 표기법get('user.name')✗ 수동 처리
상태 확인isEmpty()empty() 함수
동적 메서드 추가Macroable✗ 불가

Fluent vs Model

기능FluentModel
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는 다음의 트레이트를 사용하고 있으며, 각각 다른 기능을 제공합니다:
  • Conditionablewhen() / unless()로 조건부 처리
  • InteractsWithDatadata() 등의 데이터 조작 메서드
  • Macroable — 동적 메서드 추가

다음 단계

Collection 클래스

여러 요소를 다루는 Collection 클래스를 심층 분석합니다.

Conditionable 트레이트

조건부 처리를 유창하게 기술하는 Conditionable 트레이트를 배웁니다.

Macroable 트레이트

기존 클래스에 동적 메서드를 추가하는 Macroable 트레이트를 배웁니다.
마지막 수정일 2026년 7월 13일