본문으로 건너뛰기
버전: 11.x

블레이드 템플릿 (Blade Templates)

소개

블레이드는 라라벨에 기본 포함되어 있는 간단하면서도 강력한 템플릿 엔진입니다. 일부 PHP 템플릿 엔진과 달리, 블레이드는 템플릿 파일 내에서 일반 PHP 코드를 자유롭게 사용할 수 있도록 제한하지 않습니다. 실제로, 모든 블레이드 템플릿은 일반 PHP 코드로 컴파일되어 변경 전까지 캐시되므로, 블레이드는 애플리케이션에 거의 성능 저하 없이 동작합니다. 블레이드 템플릿 파일은 .blade.php 확장자를 사용하며, 일반적으로 resources/views 디렉터리에 저장됩니다.

블레이드 뷰는 라우트나 컨트롤러에서 글로벌 view 헬퍼를 사용해 반환할 수 있습니다. 물론, 문서에서 다뤄진 것처럼 view 헬퍼의 두 번째 인수를 통해 데이터를 블레이드 뷰로 전달할 수도 있습니다.

Route::get('/', function () {
return view('greeting', ['name' => 'Finn']);
});

Livewire로 블레이드 확장하기

블레이드 템플릿을 한 단계 더 발전시키고 동적인 인터페이스를 손쉽게 만들어보고 싶으신가요? Laravel Livewire를 참고해보세요. Livewire를 사용하면 일반적으로 프론트엔드 프레임워크(React, Vue 등)에서만 가능했던 동적 기능이 값에 의해 보강된 Blade 컴포넌트를 작성할 수 있습니다. 이를 통해 별도의 복잡한 자바스크립트 프레임워크의 클라이언트 렌더링이나 빌드 과정 없이도 현대적인 반응형 프론트엔드를 훨씬 수월하게 구현할 수 있습니다.

데이터 표시

블레이드 뷰로 전달된 데이터를 중괄호로 감싸서 표시할 수 있습니다. 예를 들어 아래와 같은 라우트가 있다고 가정해보겠습니다.

Route::get('/', function () {
return view('welcome', ['name' => 'Samantha']);
});

name 변수의 내용을 뷰에서 이렇게 출력할 수 있습니다.

Hello, {{ $name }}.

[!NOTE]
블레이드의 {{ }} 에코 구문은 XSS 공격을 방지하기 위해 PHP의 htmlspecialchars 함수로 자동 변환 처리됩니다.

뷰에 전달된 변수 내용만 표시하는 데에 국한되지 않습니다. 어떤 PHP 함수의 결과도 에코할 수 있으며, 실제로 블레이드 에코 구문 내에 원하는 PHP 코드를 자유롭게 넣을 수 있습니다.

The current UNIX timestamp is {{ time() }}.

HTML 엔터티 인코딩

기본적으로, 블레이드(그리고 라라벨의 e 함수)는 HTML 엔터티를 이중으로 인코딩합니다. 이중 인코딩을 비활성화하고 싶다면, AppServiceProviderboot 메서드에서 Blade::withoutDoubleEncoding 메서드를 호출하면 됩니다.

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Blade::withoutDoubleEncoding();
}
}

이스케이프 되지 않은 데이터 표시

기본적으로 블레이드의 {{ }} 문은 XSS 공격을 막기 위해 PHP의 htmlspecialchars 함수로 자동 이스케이프됩니다. 만약 데이터를 이스케이프 없이 그대로 출력하고 싶다면, 다음과 같은 구문을 사용할 수 있습니다.

Hello, {!! $name !!}.

[!WARNING]
사용자로부터 입력받은 데이터를 에코할 때는 특히 주의해야 합니다. 사용자 제공 데이터를 표시할 때는 반드시 XSS 공격을 막기 위해 이스케이프된 중괄호({{ }}) 구문을 사용하는 것이 안전합니다.

블레이드와 자바스크립트 프레임워크

많은 자바스크립트 프레임워크 역시 "중괄호"를 이용해 브라우저에 표현식을 표시하도록 합니다. 이럴 때, 블레이드 렌더링 엔진에 해당 표현식을 건드리지 말라고 알리려면 @ 심볼을 사용할 수 있습니다. 예를 들면 다음과 같습니다.

<h1>Laravel</h1>

Hello, @{{ name }}.

위 예제에서는 블레이드가 @ 심볼을 제거하고, {{ name }} 표현식은 그대로 남아 자바스크립트 프레임워크에서 렌더링될 수 있습니다.

@ 심볼은 블레이드 디렉티브를 이스케이프할 때에도 사용할 수 있습니다.

{{-- Blade template --}}
@@if()

<!-- HTML output -->
@if()

JSON 렌더링

때때로 뷰에 배열을 전달해 자바스크립트 변수를 초기화할 목적으로 JSON으로 출력하고 싶을 때가 있습니다. 예를 들면 아래와 같습니다.

<script>
var app = <?php echo json_encode($array); ?>;
</script>

하지만 직접 json_encode를 호출하는 대신, Illuminate\Support\Js::from 메서드 디렉티브를 사용할 수 있습니다. from 메서드는 PHP의 json_encode 함수와 동일한 인수를 받지만, HTML 안에서 안전하게 사용할 수 있도록 JSON이 올바르게 이스케이프되도록 보장합니다. 이 메서드는 주어진 객체나 배열을 유효한 JavaScript 객체로 변환하는 JSON.parse 자바스크립트 구문을 반환합니다.

<script>
var app = {{ Illuminate\Support\Js::from($array) }};
</script>

최신 버전의 라라벨 애플리케이션 스켈레톤에서는 Js 파사드를 포함하고 있어, 블레이드 템플릿에서 좀 더 편리하게 이 기능을 사용할 수 있습니다.

<script>
var app = {{ Js::from($array) }};
</script>

[!WARNING]
Js::from 메서드는 이미 생성된 변수를 JSON으로 변환할 때만 사용해야 합니다. 블레이드 템플릿은 정규 표현식을 기반으로 동작하므로, 복잡한 표현식을 전달하면 예기치 않은 오류가 발생할 수 있습니다.

@verbatim 디렉티브

블레이드 템플릿의 상당 부분에서 자바스크립트 변수를 표시해야 하는 경우, 각각의 블레이드 에코 구문 앞에 @ 심볼을 일일이 붙이지 않고도 사용할 수 있도록, 해당 HTML을 @verbatim 디렉티브로 감쌀 수 있습니다.

@verbatim
<div class="container">
Hello, {{ name }}.
</div>
@endverbatim

블레이드 디렉티브

블레이드는 템플릿 상속 및 데이터 표시 외에도 조건문, 반복문 등 자주 사용하는 PHP 제어문에 대해 간결한 문법의 디렉티브를 제공합니다. 이러한 단축 구문을 사용하면 PHP 문법과 거의 동일한 친숙함은 유지하면서, 매우 깔끔하고 코드량이 적은 방식으로 제어문을 다룰 수 있습니다.

If 문

@if, @elseif, @else, @endif 디렉티브를 이용해 조건문을 생성할 수 있습니다. 이 디렉티브들은 PHP의 if문과 작동 방식이 완전히 동일합니다.

@if (count($records) === 1)
I have one record!
@elseif (count($records) > 1)
I have multiple records!
@else
I don't have any records!
@endif

추가로 편리하게 사용할 수 있는 @unless 디렉티브도 지원합니다.

@unless (Auth::check())
You are not signed in.
@endunless

앞서 설명한 조건문 디렉티브 외에도, 각각의 PHP 함수의 단축 구문으로 @isset@empty 디렉티브를 사용할 수 있습니다.

@isset($records)
// $records가 정의되어 있고 null이 아님...
@endisset

@empty($records)
// $records가 "비어 있음"...
@endempty

인증 관련 디렉티브

@auth@guest 디렉티브를 사용하여 현재 사용자가 인증된 상태인지 혹은 게스트인지를 빠르게 확인할 수 있습니다.

@auth
// 사용자가 인증됨...
@endauth

@guest
// 사용자가 인증되지 않음...
@endguest

필요하다면, @auth@guest 디렉티브에 인증 가드를 지정하여 검사할 수도 있습니다.

@auth('admin')
// 사용자가 인증됨...
@endauth

@guest('admin')
// 사용자가 인증되지 않음...
@endguest

환경(Environment) 관련 디렉티브

애플리케이션이 실제 운영(Production) 환경에서 실행 중인지 확인하려면, @production 디렉티브를 사용할 수 있습니다.

@production
// 운영 환경에만 표시할 내용...
@endproduction

또는, 애플리케이션이 특정 환경에서 실행 중인지 확인하려면 @env 디렉티브를 사용할 수 있습니다.

@env('staging')
// 현재 애플리케이션이 "staging" 환경에서 동작 중...
@endenv

@env(['staging', 'production'])
// "staging" 또는 "production" 환경에서 동작 중...
@endenv

Section 디렉티브

템플릿 상속에서 특정 section에 컨텐츠가 정의되어 있는지, @hasSection 디렉티브로 확인할 수 있습니다.

@hasSection('navigation')
<div class="pull-right">
@yield('navigation')
</div>

<div class="clearfix"></div>
@endif

반대로 section에 컨텐츠가 없을 때를 확인하고 싶다면 sectionMissing 디렉티브를 사용할 수 있습니다.

@sectionMissing('navigation')
<div class="pull-right">
@include('default-navigation')
</div>
@endif

세션(Session) 디렉티브

@session 디렉티브를 사용하면 세션 값이 존재하는지 확인할 수 있습니다. 세션 값이 존재하면, @session@endsession 사이의 내용을 렌더링합니다. 이 블록 내부에서는 세션 값을 $value 변수로 참조할 수 있습니다.

@session('status')
<div class="p-4 bg-green-100">
{{ $value }}
</div>
@endsession

Switch 문

@switch, @case, @break, @default, @endswitch 디렉티브를 이용해 switch 문을 작성할 수 있습니다.

@switch($i)
@case(1)
First case...
@break

@case(2)
Second case...
@break

@default
Default case...
@endswitch

반복문

블레이드는 조건문 외에도 PHP의 다양한 반복문 구조를 편리하게 사용할 수 있는 디렉티브를 제공합니다. 각 디렉티브는 PHP의 for, foreach, while 반복문과 완전히 동일하게 작동합니다.

@for ($i = 0; $i < 10; $i++)
The current value is {{ $i }}
@endfor

@foreach ($users as $user)
<p>This is user {{ $user->id }}</p>
@endforeach

@forelse ($users as $user)
<li>{{ $user->name }}</li>
@empty
<p>No users</p>
@endforelse

@while (true)
<p>I'm looping forever.</p>
@endwhile

[!NOTE]
foreach 반복문 내부에서는 루프 변수를 사용해 현재 루프의 첫 번째 또는 마지막 순회인지 등 다양한 정보를 얻을 수 있습니다.

반복문을 사용할 때, 특정 반복을 건너뛰거나 반복문을 끝내고 싶다면 @continue@break 디렉티브를 사용할 수 있습니다.

@foreach ($users as $user)
@if ($user->type == 1)
@continue
@endif

<li>{{ $user->name }}</li>

@if ($user->number == 5)
@break
@endif
@endforeach

또는, 조건식을 디렉티브 선언부에 직접 포함시킬 수도 있습니다.

@foreach ($users as $user)
@continue($user->type == 1)

<li>{{ $user->name }}</li>

@break($user->number == 5)
@endforeach

루프 변수

foreach 반복문을 순회할 때, 루프 내부에서 $loop 변수를 사용할 수 있습니다. 이 변수는 현재 루프의 인덱스, 첫 번째/마지막 순회 여부 등 유용한 정보들을 제공합니다.

@foreach ($users as $user)
@if ($loop->first)
This is the first iteration.
@endif

@if ($loop->last)
This is the last iteration.
@endif

<p>This is user {{ $user->id }}</p>
@endforeach

중첩된 반복문 안에 있다면, 부모 반복문의 $loop 변수는 parent 속성을 통해 접근할 수 있습니다.

@foreach ($users as $user)
@foreach ($user->posts as $post)
@if ($loop->parent->first)
This is the first iteration of the parent loop.
@endif
@endforeach
@endforeach

$loop 변수에는 아래와 같은 다양한 속성이 포함되어 있습니다.

속성설명
$loop->index현재 반복문의 인덱스(0에서 시작)
$loop->iteration현재 반복문의 순번(1에서 시작)
$loop->remaining반복문이 남은 횟수
$loop->count반복 대상 배열의 총 아이템 개수
$loop->first이번 순회가 첫 번째인지
$loop->last이번 순회가 마지막인지
$loop->even이번 순회가 짝수 순번인지
$loop->odd이번 순회가 홀수 순번인지
$loop->depth현재 루프의 중첩 깊이
$loop->parent중첩 루프일 때 부모의 루프 변수

조건부 클래스 & 스타일

@class 디렉티브는 조건에 따라 CSS 클래스 문자열을 동적으로 합성해줍니다. 이 디렉티브는 클래스명을 배열로 받아, 키가 클래스명 또는 클래스들의 문자열이고 값이 불리언 조건식으로 되어 있습니다. 만약 배열의 키가 숫자일 경우, 해당 클래스는 조건과 상관없이 항상 포함됩니다.

@php
$isActive = false;
$hasError = true;
@endphp

<span @class([
'p-4',
'font-bold' => $isActive,
'text-gray-500' => ! $isActive,
'bg-red' => $hasError,
])></span>

<span class="p-4 text-gray-500 bg-red"></span>

마찬가지로, @style 디렉티브를 사용하면 HTML 요소에 조건부로 인라인 CSS 스타일을 추가할 수 있습니다.

@php
$isActive = true;
@endphp

<span @style([
'background-color: red',
'font-weight: bold' => $isActive,
])></span>

<span style="background-color: red; font-weight: bold;"></span>

추가 속성

HTML 체크박스 input이 "checked" 상태인지 쉽게 표시하려면 @checked 디렉티브를 사용할 수 있습니다. 제공한 조건이 true로 평가되면 해당 input 요소에 checked 속성을 출력합니다.

<input
type="checkbox"
name="active"
value="active"
@checked(old('active', $user->active))
/>

마찬가지로, 해당 select option이 "selected" 상태여야 할 경우 @selected 디렉티브를 사용할 수 있습니다.

<select name="version">
@foreach ($product->versions as $version)
<option value="{{ $version }}" @selected(old('version') == $version)>
{{ $version }}
</option>
@endforeach
</select>

또한, 특정 요소가 "disabled" 되어야 하면 @disabled 디렉티브를 사용할 수 있습니다.

<button type="submit" @disabled($errors->isNotEmpty())>Submit</button>

또한 "readonly" 속성을 동적으로 설정하려면 @readonly 디렉티브를 사용할 수 있습니다.

<input
type="email"
name="email"
value="[email protected]"
@readonly($user->isNotAdmin())
/>

추가로, 해당 요소가 "required"해야 한다면 @required 디렉티브를 사용할 수 있습니다.

<input
type="text"
name="title"
value="title"
@required($user->isAdmin())
/>

서브뷰 포함하기

[!NOTE]
@include 디렉티브를 자유롭게 사용할 수 있지만, 블레이드의 컴포넌트@include와 유사한 기능을 제공하면서 데이터 및 Attribute 바인딩과 같은 여러 이점을 더 가지고 있습니다.

블레이드의 @include 디렉티브를 사용하면 한 뷰 파일 안에서 다른 블레이드 뷰를 쉽게 불러올 수 있습니다. 부모 뷰에서 사용할 수 있는 모든 변수는 포함된 뷰에서도 동일하게 사용할 수 있습니다.

<div>
@include('shared.errors')

<form>
<!-- Form Contents -->
</form>
</div>

포함된 뷰가 부모의 모든 데이터를 상속받긴 하지만, 추가로 제공할 데이터를 배열 형태로 전달할 수도 있습니다.

@include('view.name', ['status' => 'complete'])

포함하려는 뷰 파일이 존재하지 않을 경우, 라라벨은 에러를 발생시킵니다. 하지만 해당 뷰가 있을 때만 포함하고 싶을 때는 @includeIf 디렉티브를 사용하면 됩니다.

@includeIf('view.name', ['status' => 'complete'])

지정한 불리언 조건이 true일 때만 뷰를 포함하고 싶다면 @includeWhen, 반대로 false일 때만 포함하려면 @includeUnless 디렉티브를 사용할 수 있습니다.

@includeWhen($boolean, 'view.name', ['status' => 'complete'])

@includeUnless($boolean, 'view.name', ['status' => 'complete'])

뷰들의 배열에서 첫 번째로 존재하는 파일을 포함하고 싶다면, includeFirst 디렉티브를 사용하면 됩니다.

@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])

[!WARNING]
블레이드 뷰에서는 __DIR__, __FILE__ 상수 사용을 피해야 합니다. 이 상수들은 컴파일되어 캐시된 뷰의 경로를 가리키게 됩니다.

컬렉션에 대한 뷰 렌더링

Blade의 @each 디렉티브를 사용하면 반복문과 include를 한 줄로 결합할 수 있습니다.

@each('view.name', $jobs, 'job')

@each 디렉티브의 첫 번째 인수는 배열 또는 컬렉션의 각 요소를 렌더링할 때 사용할 뷰입니다. 두 번째 인수는 반복하고자 하는 배열이나 컬렉션이고, 세 번째 인수는 현재 반복 요소가 뷰에서 지정될 변수명입니다. 예를 들어 jobs 배열을 순회한다면, 각 뷰 내부에서 해당 잡(job)을 job 변수로 접근할 수 있습니다. 그리고 현재 반복의 배열 키는 뷰 내에서 key 변수로 사용할 수 있습니다.

또한, 네 번째 인수를 @each 디렉티브에 전달할 수 있습니다. 이 인수는 만약 지정한 배열이 비어 있을 때 렌더링할 뷰를 정합니다.

@each('view.name', $jobs, 'job', 'view.empty')

[!WARNING]
@each로 렌더링된 뷰는 부모 뷰의 변수들을 상속받지 않습니다. 자식 뷰에서 부모 뷰의 변수를 필요로 한다면 @foreach@include 디렉티브를 대신 사용해야 합니다.

@once 디렉티브

@once 디렉티브를 사용하면 렌더링 사이클당 한 번만 평가되는 템플릿 일부를 정의할 수 있습니다. 예를 들어, 스택을 사용해서 일정한 자바스크립트를 페이지 헤더에 한 번만 넣고 싶을 때 유용합니다. 예를 들어, 컴포넌트를 반복문으로 여러 번 렌더링할 때, 처음 렌더링 시에만 자바스크립트를 헤더에 push하고 싶다면 다음과 같이 사용합니다.

@once
@push('scripts')
<script>
// Your custom JavaScript...
</script>
@endpush
@endonce

@once 디렉티브는 주로 @push, @prepend와 함께 많이 사용되며, 편의를 위해 @pushOnce@prependOnce 디렉티브도 제공됩니다.

@pushOnce('scripts')
<script>
// Your custom JavaScript...
</script>
@endPushOnce

PHP 코드 직접 사용

어떤 상황에서는 뷰에 PHP 코드를 직접 사용할 필요가 있습니다. Blade의 @php 디렉티브를 사용하면 템플릿 내에서 일반 PHP 코드를 실행할 수 있습니다.

@php
$counter = 1;
@endphp

또한, PHP 클래스를 import하는 용도로만 PHP 코드를 쓰고 싶다면 @use 디렉티브를 사용할 수 있습니다.

@use('App\Models\Flight')

@use 디렉티브에는 두 번째 인수로 import한 클래스의 별칭을 지정할 수도 있습니다.

@use('App\Models\Flight', 'FlightModel')

주석

Blade는 뷰 내에서 주석을 정의하는 기능도 제공합니다. 이 주석은 HTML 주석과 달리, 애플리케이션이 최종적으로 반환하는 HTML에 절대 포함되지 않습니다.

{{-- 이 주석은 렌더링된 HTML에 포함되지 않습니다 --}}

컴포넌트(Components)

컴포넌트와 슬롯(slots)은 section, layout, include가 제공하는 이점과 유사하지만, 컴포넌트와 슬롯의 개념이 더 이해하기 쉬울 수 있습니다. 컴포넌트를 작성하는 방법은 크게 클래스 기반 컴포넌트와 익명 컴포넌트 두 가지가 있습니다.

클래스 기반 컴포넌트를 만들려면 make:component Artisan 명령어를 사용할 수 있습니다. 사용 예시로, 간단한 Alert 컴포넌트를 만들어 보겠습니다. make:component 명령어는 컴포넌트를 app/View/Components 디렉터리에 생성합니다.

php artisan make:component Alert

make:component 명령어는 컴포넌트용 뷰 템플릿도 함께 생성합니다. 이 뷰는 resources/views/components 디렉터리에 위치합니다. 애플리케이션 용도로 컴포넌트를 만들 때는, 이 두 디렉터리(app/View/Components, resources/views/components) 내의 컴포넌트들은 자동으로 감지 및 등록되므로 별도의 등록 과정이 필요하지 않습니다.

서브디렉터리 안에 컴포넌트를 생성하는 것도 가능합니다.

php artisan make:component Forms/Input

위 명령어를 실행하면, app/View/Components/Forms 디렉터리에 Input 컴포넌트가 생성되고 뷰 템플릿은 resources/views/components/forms 디렉터리에 생성됩니다.

만약 클래스 없이 Blade 템플릿 파일만 가지는 익명 컴포넌트를 생성하고 싶다면, 명령 실행 시 --view 플래그를 사용하면 됩니다.

php artisan make:component forms.input --view

이 명령어는 resources/views/components/forms/input.blade.php 파일을 생성하며, <x-forms.input /> 형태로 컴포넌트처럼 렌더링할 수 있습니다.

패키지 컴포넌트 수동 등록

애플리케이션용 컴포넌트는 위에서 설명한 대로 지정된 디렉터리 내에서 자동으로 감지 및 등록됩니다.

하지만 패키지를 개발하며 Blade 컴포넌트를 활용하는 경우, 컴포넌트 클래스와 HTML 태그 별칭을 직접 등록해야 합니다. 보통 패키지의 서비스 프로바이더의 boot 메서드에서 컴포넌트를 등록합니다.

use Illuminate\Support\Facades\Blade;

/**
* 패키지 서비스 부트스트랩.
*/
public function boot(): void
{
Blade::component('package-alert', Alert::class);
}

컴포넌트가 등록되면 태그 별칭으로 렌더링할 수 있습니다.

<x-package-alert/>

또는, componentNamespace 메서드를 이용해 네임스페이스에 따라 컴포넌트 클래스를 오토로드할 수도 있습니다. 예를 들어, Nightshade 패키지에 Calendar, ColorPicker 컴포넌트가 있고, Package\Views\Components 네임스페이스에 위치한다면 다음과 같이 등록할 수 있습니다.

use Illuminate\Support\Facades\Blade;

/**
* 패키지 서비스 부트스트랩.
*/
public function boot(): void
{
Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}

이렇게 하면 package-name:: 문법을 사용하여 벤더 네임스페이스로 패키지 컴포넌트를 사용할 수 있습니다.

<x-nightshade::calendar />
<x-nightshade::color-picker />

Blade는 컴포넌트 이름을 파스칼케이스로 변환하여 자동으로 연결된 클래스를 감지합니다. 서브디렉터리는 "도트(dot) 표기"를 사용해 지원됩니다.

컴포넌트 렌더링

컴포넌트를 표시하려면 Blade 템플릿에서 Blade 컴포넌트 태그를 사용할 수 있습니다. Blade 컴포넌트 태그는 x-로 시작하며, 그 뒤에 컴포넌트 클래스명을 케밥(case) 형태로 작성합니다.

<x-alert/>

<x-user-profile/>

만약 컴포넌트 클래스가 app/View/Components 디렉토리 내에서 더 깊은 경로에 있다면, 디렉토리 구조를 . 문자로 표현할 수 있습니다. 예를 들어, app/View/Components/Inputs/Button.php에 컴포넌트가 있다면 다음과 같이 렌더링할 수 있습니다.

<x-inputs.button/>

컴포넌트를 조건부로 렌더링하고 싶다면, 컴포넌트 클래스에서 shouldRender 메서드를 정의할 수 있습니다. 이 메서드가 false를 반환하면 해당 컴포넌트는 렌더링되지 않습니다.

use Illuminate\Support\Str;

/**
* 컴포넌트 렌더링 여부 반환
*/
public function shouldRender(): bool
{
return Str::length($this->message) > 0;
}

인덱스 컴포넌트

때때로 컴포넌트가 컴포넌트 그룹의 일부로 사용되어 같은 디렉터리에 관련 컴포넌트들을 모으고 싶을 때가 있습니다. 예를 들어 다음과 같이 "카드(card)" 컴포넌트를 구성했다고 가정합니다.

App\Views\Components\Card\Card
App\Views\Components\Card\Header
App\Views\Components\Card\Body

루트 Card 컴포넌트가 Card 디렉터리 내에 있으므로, <x-card.card>와 같이 렌더링해야 할 것처럼 보일 수 있습니다. 하지만, 컴포넌트 파일명이 디렉터리 이름과 동일한 경우 라라벨은 이를 "루트" 컴포넌트로 간주하여 디렉터리 이름을 반복하지 않고 다음과 같이 렌더링할 수 있게 해줍니다.

<x-card>
<x-card.header>...</x-card.header>
<x-card.body>...</x-card.body>
</x-card>

컴포넌트에 데이터 전달하기

Blade 컴포넌트에 데이터를 전달할 때는 HTML 속성을 활용할 수 있습니다. 하드코딩된 기본형 값은 HTML 속성 문자열로, PHP 표현식이나 변수를 넘길 때는 속성 이름 앞에 :를 붙여서 사용할 수 있습니다.

<x-alert type="error" :message="$message"/>

컴포넌트 클래스의 생성자에서 모든 데이터 속성을 정의해야 합니다. 컴포넌트의 모든 public 속성은 자동으로 컴포넌트 뷰에서 사용할 수 있습니다. 컴포넌트의 render 메서드에서 별도로 데이터를 뷰로 전달할 필요는 없습니다.

<?php

namespace App\View\Components;

use Illuminate\View\Component;
use Illuminate\View\View;

class Alert extends Component
{
/**
* 컴포넌트 인스턴스 생성자
*/
public function __construct(
public string $type,
public string $message,
) {}

/**
* 컴포넌트를 표현하는 뷰/내용 반환
*/
public function render(): View
{
return view('components.alert');
}
}

컴포넌트가 렌더링될 때, 컴포넌트의 public 변수는 해당 이름으로 뷰에서 바로 출력할 수 있습니다.

<div class="alert alert-{{ $type }}">
{{ $message }}
</div>

대소문자 규칙(Casing)

컴포넌트 생성자 인수는 camelCase로 작성해야 하며, HTML 속성에서 인수명을 사용할 때는 kebab-case로 사용해야 합니다. 예를 들어 다음과 같은 컴포넌트 생성자가 있을 때,

/**
* 컴포넌트 인스턴스 생성자
*/
public function __construct(
public string $alertType,
) {}

$alertType 인수는 아래와 같이 컴포넌트에 전달할 수 있습니다.

<x-alert alert-type="danger" />

속성 축약 문법(Short Attribute Syntax)

컴포넌트에 속성을 전달할 때, "속성 축약 문법"을 사용할 수 있습니다. 속성명과 변수명이 일치하는 경우 자주 유용합니다.

{{-- 속성 축약 문법 --}}
<x-profile :$userId :$name />

{{-- 아래와 동일함 --}}
<x-profile :user-id="$userId" :name="$name" />

속성 렌더링 이스케이프(Escaping Attribute Rendering)

Alpine.js 같은 자바스크립트 프레임워크에서 속성 앞에 콜론(:)이 사용되는 경우, Blade에 해당 속성이 PHP 표현식이 아니라는 것을 알리기 위해 더블 콜론(::)을 사용할 수 있습니다. 예를 들어 아래와 같은 컴포넌트가 있다면,

<x-button ::class="{ danger: isDeleting }">
Submit
</x-button>

아래와 같은 HTML이 Blade에 의해 렌더링됩니다.

<button :class="{ danger: isDeleting }">
Submit
</button>

컴포넌트 메서드

컴포넌트 템플릿에서는 public 변수 외에도 컴포넌트의 public 메서드를 불러올 수 있습니다. 예를 들어, isSelected 메서드가 있는 컴포넌트라면 다음과 같이 사용할 수 있습니다.

/**
* 주어진 옵션이 현재 선택된 옵션인지 확인
*/
public function isSelected(string $option): bool
{
return $option === $this->selected;
}

컴포넌트 템플릿에서 해당 메서드명을 변수처럼 직접 호출할 수 있습니다.

<option {{ $isSelected($value) ? 'selected' : '' }} value="{{ $value }}">
{{ $label }}
</option>

컴포넌트 클래스 내에서 속성 및 슬롯 접근

Blade 컴포넌트에서는 컴포넌트 이름, 속성(attributes), 슬롯(slot)에 접근할 수 있습니다. 이 데이터를 사용하려면 컴포넌트의 render 메서드에서 클로저(Closure)를 반환해야 합니다.

use Closure;

/**
* 컴포넌트를 표현하는 뷰/내용 반환
*/
public function render(): Closure
{
return function () {
return '<div {{ $attributes }}>Components content</div>';
};
}

이 클로저는 $data 배열을 유일한 인자로 받을 수 있습니다. 이 배열에는 컴포넌트 정보를 담은 여러 요소가 포함됩니다.

return function (array $data) {
// $data['componentName'];
// $data['attributes'];
// $data['slot'];

return '<div {{ $attributes }}>Components content</div>';
}

[!WARNING]
$data 배열의 요소를 Blade 문자열에 직접 포함시키면, 악의적인 속성 값을 통한 원격 코드 실행이 발생할 수 있으므로 절대 사용하지 않아야 합니다.

componentName은 HTML 태그의 x- 접두어 뒤에 온 이름에 해당합니다. 즉 <x-alert />componentNamealert이 됩니다. attributes 요소는 HTML 태그에 지정된 모든 속성을 가지며, slot 요소는 컴포넌트 슬롯의 내용을 포함한 Illuminate\Support\HtmlString 인스턴스입니다.

클로저는 문자열을 반환해야 하며, 이 문자열이 기존 뷰와 일치할 경우 해당 뷰가 렌더링되고, 아니면 인라인 Blade 뷰로 처리됩니다.

추가 의존성 주입

컴포넌트에서 라라벨의 서비스 컨테이너에서 의존성이 필요한 경우, 컴포넌트의 데이터 속성 앞에 의존성을 나열하면 컨테이너가 자동으로 주입해줍니다.

use App\Services\AlertCreator;

/**
* 컴포넌트 인스턴스 생성자
*/
public function __construct(
public AlertCreator $creator,
public string $type,
public string $message,
) {}

속성/메서드 감추기

컴포넌트 템플릿에 public 메서드나 속성이 변수로 노출되는 것을 막으려면, $except 배열 속성에 제외할 항목을 지정할 수 있습니다.

<?php

namespace App\View\Components;

use Illuminate\View\Component;

class Alert extends Component
{
/**
* 컴포넌트 템플릿에서 노출하지 않을 속성/메서드
*
* @var array
*/
protected $except = ['type'];

/**
* 컴포넌트 인스턴스 생성자
*/
public function __construct(
public string $type,
) {}
}

컴포넌트 속성

앞서 살펴본 것처럼, 데이터 속성을 컴포넌트에 전달할 수 있습니다. 하지만 때로는 컴포넌트 기능과는 무관한 추가 HTML 속성(예시: class 같은)을 지정해야 할 수도 있습니다. 보통 이러한 추가 속성들은 컴포넌트 템플릿의 루트 요소로 전달하는 것이 바람직합니다. 예를 들어 아래처럼 alert 컴포넌트를 렌더링한다고 가정해보겠습니다.

<x-alert type="error" :message="$message" class="mt-4"/>

컴포넌트 생성자에 없는 모든 속성은 자동으로 컴포넌트의 "속성 백(attribute bag)"에 추가됩니다. 이 속성 백은 컴포넌트 내에서 $attributes 변수로 사용할 수 있고, 모든 속성을 렌더링하려면 이 변수를 echo하면 됩니다.

<div {{ $attributes }}>
<!-- Component content -->
</div>

[!WARNING]
컴포넌트 태그에서 @env 같은 디렉티브 사용은 현재 지원되지 않습니다. 예를 들어 <x-alert :live="@env('production')"/>와 같은 코드는 컴파일되지 않습니다.

기본값/병합된 속성(Default / Merged Attributes)

때로는 속성에 기본값을 지정하거나, 일부 속성에 값을 추가로 합쳐야 할 때가 있습니다. 이럴 때는 속성 백의 merge 메서드를 사용할 수 있습니다. 이 메서드는 항상 적용할 CSS 클래스의 기본값을 지정할 때 특히 유용합니다.

<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
{{ $message }}
</div>

예를 들어, 이 컴포넌트가 아래와 같이 사용된다면:

<x-alert type="error" :message="$message" class="mb-4"/>

최종적으로 렌더링되는 HTML은 아래처럼 나타납니다.

<div class="alert alert-error mb-4">
<!-- Contents of the $message variable -->
</div>

조건부 클래스 병합

특정 조건이 참일 때만 클래스를 병합하고 싶은 경우에는, class 메서드를 사용할 수 있습니다. 이 메서드는 클래스 혹은 여러 클래스를 키로, 불리언 값을 값으로 갖는 배열을 받습니다. 배열의 키가 숫자면, 조건과 상관없이 항상 렌더링된 클래스 리스트에 포함됩니다.

<div {{ $attributes->class(['p-4', 'bg-red' => $hasError]) }}>
{{ $message }}
</div>

다른 속성도 병합하고 싶으면, class 메서드 뒤에 merge 메서드를 체이닝할 수 있습니다.

<button {{ $attributes->class(['p-4'])->merge(['type' => 'button']) }}>
{{ $slot }}
</button>

[!NOTE]
병합 속성이 필요 없는 다른 HTML 요소에서 조건부 클래스를 처리하고 싶다면 @class 디렉티브를 사용할 수 있습니다.

클래스 외 속성 병합(Non-Class Attribute Merging)

클래스가 아닌 다른 속성의 병합 시에는 merge 메서드에 지정된 값이 해당 속성의 "기본값"으로 간주됩니다. 하지만, class 속성과 달리 이 속성들은 전달된 값과 병합되지 않고, 기본값이 오버라이드됩니다. 예를 들어 button 컴포넌트 구현은 아래와 같습니다.

<button {{ $attributes->merge(['type' => 'button']) }}>
{{ $slot }}
</button>

이 컴포넌트를 사용자 정의 type 속성으로 렌더링하려면 아래와 같이 사용할 수 있습니다. 지정하지 않으면 기본형인 button 타입이 적용됩니다.

<x-button type="submit">
Submit
</x-button>

이 예시에서 렌더링되는 HTML은 다음과 같습니다.

<button type="submit">
Submit
</button>

만약 class가 아닌 다른 속성에서 기본값과 전달값을 합쳐서 사용하고 싶다면 prepends 메서드를 사용하면 됩니다. 예를 들어, data-controller 속성은 항상 profile-controller로 시작하고, 전달된 값은 그 뒤에 추가되게 할 수 있습니다.

<div {{ $attributes->merge(['data-controller' => $attributes->prepends('profile-controller')]) }}>
{{ $slot }}
</div>

속성 필터링 및 조회

filter 메서드를 사용하면 속성을 필터링할 수 있습니다. 이 메서드는 true를 반환하는 경우에 한해 해당 속성을 속성 백에 남깁니다.

{{ $attributes->filter(fn (string $value, string $key) => $key == 'foo') }}

편의상, whereStartsWith 메서드를 사용하면 키가 특정 문자열로 시작하는 모든 속성을 한 번에 가져올 수 있습니다.

{{ $attributes->whereStartsWith('wire:model') }}

반대로, whereDoesntStartWith 메서드는 키가 특정 문자열로 시작하지 않는 속성만 남깁니다.

{{ $attributes->whereDoesntStartWith('wire:model') }}

first 메서드를 사용하면 주어진 속성 백에서 첫 번째 속성만 렌더링할 수 있습니다.

{{ $attributes->whereStartsWith('wire:model')->first() }}

컴포넌트에 특정 속성이 존재하는지 확인하려면 has 메서드를 사용할 수 있습니다. 이 메서드는 속성명을 유일한 인수로 받아, 해당 속성이 존재하면 true를 반환합니다.

@if ($attributes->has('class'))
<div>Class attribute is present</div>
@endif

배열을 has 메서드에 전달하면, 지정한 모든 속성이 있는지 검사합니다.

@if ($attributes->has(['name', 'class']))
<div>All of the attributes are present</div>
@endif

hasAny 메서드는 지정한 속성 중 하나라도 존재하는지 검사합니다.

@if ($attributes->hasAny(['href', ':href', 'v-bind:href']))
<div>One of the attributes is present</div>
@endif

특정 속성의 값을 가져오려면 get 메서드를 사용하면 됩니다.

{{ $attributes->get('class') }}

예약된 키워드

기본적으로, Blade에서 컴포넌트를 렌더링할 때 내부적으로 사용하는 일부 키워드는 예약어로 지정되어 있습니다. 아래 나열된 키워드는 컴포넌트 내에서 public 속성이나 메서드 이름으로 정의할 수 없습니다.

  • data
  • render
  • resolveView
  • shouldRender
  • view
  • withAttributes
  • withName

슬롯(Slot)

컴포넌트에 추가적인 콘텐츠를 전달해야 할 때, 흔히 "슬롯(slot)"을 사용합니다. 컴포넌트 슬롯은 $slot 변수를 출력함으로써 렌더링할 수 있습니다. 이 개념을 이해하기 위해, alert 컴포넌트가 다음과 같은 마크업을 가진다고 가정해보겠습니다.

<!-- /resources/views/components/alert.blade.php -->

<div class="alert alert-danger">
{{ $slot }}
</div>

컴포넌트에 콘텐츠를 주입하여 slot으로 전달할 수 있습니다.

<x-alert>
<strong>Whoops!</strong> Something went wrong!
</x-alert>

때로는 하나의 컴포넌트 내부에서 여러 위치에 서로 다른 슬롯을 렌더링해야 할 수도 있습니다. 알림 컴포넌트에 "title" 슬롯을 주입할 수 있도록 수정해보겠습니다.

<!-- /resources/views/components/alert.blade.php -->

<span class="alert-title">{{ $title }}</span>

<div class="alert alert-danger">
{{ $slot }}
</div>

명명된 슬롯의 내용을 정의할 때는 x-slot 태그를 사용합니다. 명시적으로 x-slot 태그 안에 포함되지 않은 모든 내용은 $slot 변수로 컴포넌트에 전달됩니다.

<x-alert>
<x-slot:title>
Server Error
</x-slot>

<strong>Whoops!</strong> Something went wrong!
</x-alert>

슬롯에 콘텐츠가 있는지 확인하기 위해, isEmpty 메서드를 사용할 수 있습니다.

<span class="alert-title">{{ $title }}</span>

<div class="alert alert-danger">
@if ($slot->isEmpty())
This is default content if the slot is empty.
@else
{{ $slot }}
@endif
</div>

또한, hasActualContent 메서드를 사용하면 해당 슬롯에 HTML 주석이 아닌 진짜 콘텐츠가 있는지 확인할 수 있습니다.

@if ($slot->hasActualContent())
The scope has non-comment content.
@endif

스코프 슬롯(Scoped Slot)

Vue 같은 자바스크립트 프레임워크를 사용해본 적이 있다면, 컴포넌트의 데이터나 메서드에 슬롯 내부에서 접근할 수 있게 해주는 "스코프 슬롯" 개념이 익숙할 수 있습니다. 라라벨에서도 컴포넌트에 public 메서드나 속성을 정의하고, 슬롯 내부에서 $component 변수를 통해 이들을 사용할 수 있습니다. 예를 들어, x-alert 컴포넌트 클래스에 public formatAlert 메서드가 있다고 가정하면 아래와 같이 사용할 수 있습니다.

<x-alert>
<x-slot:title>
{{ $component->formatAlert('Server Error') }}
</x-slot>

<strong>Whoops!</strong> Something went wrong!
</x-alert>

슬롯 속성

Blade 컴포넌트처럼, 슬롯에도 CSS 클래스명 등 속성을 추가로 할당할 수 있습니다.

<x-card class="shadow-sm">
<x-slot:heading class="font-bold">
Heading
</x-slot>

Content

<x-slot:footer class="text-sm">
Footer
</x-slot>
</x-card>

슬롯의 속성과 상호작용하려면, 해당 슬롯 변수의 attributes 속성에 접근하면 됩니다. 속성 처리에 대한 자세한 내용은 컴포넌트 속성 문서를 참고하세요.

@props([
'heading',
'footer',
])

<div {{ $attributes->class(['border']) }}>
<h1 {{ $heading->attributes->class(['text-lg']) }}>
{{ $heading }}
</h1>

{{ $slot }}

<footer {{ $footer->attributes->class(['text-gray-700']) }}>
{{ $footer }}
</footer>
</div>

인라인 컴포넌트 뷰

매우 작은 컴포넌트의 경우, 컴포넌트 클래스와 뷰 템플릿을 따로 관리하는 것이 번거롭게 느껴질 수 있습니다. 이런 상황에서는 render 메서드에서 컴포넌트의 마크업을 직접 반환할 수 있습니다.

/**
* Get the view / contents that represent the component.
*/
public function render(): string
{
return <<<'blade'
<div class="alert alert-danger">
{{ $slot }}
</div>
blade;
}

인라인 뷰 컴포넌트 생성하기

인라인 뷰를 렌더링하는 컴포넌트를 생성하려면, make:component 명령어 실행 시 --inline 옵션을 사용하면 됩니다.

php artisan make:component Alert --inline

동적 컴포넌트

때로는 어떤 컴포넌트를 렌더링해야 할지 실행 시점까지 알 수 없는 경우가 있습니다. 이런 경우, 라라벨 내장 dynamic-component 컴포넌트를 사용해 런타임 값이나 변수를 기반으로 컴포넌트를 렌더링할 수 있습니다.

// $componentName = "secondary-button";

<x-dynamic-component :component="$componentName" class="mt-4" />

컴포넌트 수동 등록

[!WARNING]
다음 문서는 주로 뷰 컴포넌트가 포함된 라라벨 패키지를 작성하는 경우에만 해당됩니다. 패키지 개발이 아니라면 이 부분은 대부분 해당되지 않습니다.

애플리케이션에서 컴포넌트를 직접 작성하는 경우, app/View/Components 디렉터리와 resources/views/components 디렉터리에 있는 컴포넌트들은 자동으로 인식됩니다.

하지만, Blade 컴포넌트를 사용하는 패키지를 만들거나 컴포넌트를 별도의 디렉터리에 둘 경우, 직접 컴포넌트 클래스와 해당 HTML 태그 별칭을 등록해야 라라벨이 해당 컴포넌트의 위치를 알 수 있습니다. 보통 패키지의 서비스 프로바이더의 boot 메서드에서 컴포넌트를 등록합니다.

use Illuminate\Support\Facades\Blade;
use VendorPackage\View\Components\AlertComponent;

/**
* Bootstrap your package's services.
*/
public function boot(): void
{
Blade::component('package-alert', AlertComponent::class);
}

컴포넌트가 등록되면 태그 별칭을 이용해 다음과 같이 렌더링할 수 있습니다.

<x-package-alert/>

패키지 컴포넌트 자동 로드(Autoloading)

또는, componentNamespace 메서드를 사용해 규칙에 따라 컴포넌트 클래스를 자동 등록할 수도 있습니다. 예를 들어, Nightshade 패키지의 CalendarColorPicker 컴포넌트가 Package\Views\Components 네임스페이스에 있다면 아래와 같이 할 수 있습니다.

use Illuminate\Support\Facades\Blade;

/**
* Bootstrap your package's services.
*/
public function boot(): void
{
Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}

이렇게 하면 벤더 네임스페이스와 함께 package-name:: 문법을 사용해서 패키지 컴포넌트를 사용할 수 있습니다.

<x-nightshade::calendar />
<x-nightshade::color-picker />

Blade는 컴포넌트 이름을 파스칼 케이스로 변환해 해당 클래스와 자동으로 연결합니다. 서브 디렉터리는 "도트" 표기법도 지원합니다.

익명 컴포넌트(Anonymous Components)

인라인 컴포넌트와 마찬가지로, 익명 컴포넌트는 하나의 파일만으로 컴포넌트를 관리할 수 있는 방법을 제공합니다. 하지만 익명 컴포넌트는 하나의 뷰 파일만 사용하고 별도의 클래스가 필요하지 않습니다. 익명 컴포넌트는 resources/views/components 디렉터리에 Blade 템플릿을 두기만 하면 됩니다. 예를 들어, resources/views/components/alert.blade.php에 컴포넌트를 정의하면, 다음과 같이 렌더링할 수 있습니다.

<x-alert/>

components 디렉터리 내에서 더 깊이 중첩된 컴포넌트라면 . 문자를 사용해 표현할 수 있습니다. 예를 들어, resources/views/components/inputs/button.blade.php에 정의된 경우 다음과 같이 사용할 수 있습니다.

<x-inputs.button/>

익명 인덱스 컴포넌트

여러 Blade 템플릿으로 이루어진 컴포넌트를 만들 때, 각 컴포넌트 템플릿을 하나의 디렉터리로 그룹화하고 싶을 때가 있습니다. 예를 들어, "아코디언" 컴포넌트를 다음과 같이 구성할 수 있습니다.

/resources/views/components/accordion.blade.php
/resources/views/components/accordion/item.blade.php

이 디렉터리 구조에서는 아코디언 컴포넌트 및 그 항목을 아래와 같이 렌더링할 수 있습니다.

<x-accordion>
<x-accordion.item>
...
</x-accordion.item>
</x-accordion>

하지만 위 예시에서처럼, x-accordion 컴포넌트를 렌더링하려면 "index" 역할을 하는 템플릿 파일을 항상 resources/views/components 루트에 두어야 했습니다.

다행히, Blade에서는 컴포넌트의 디렉터리 내에 디렉터리명과 동일한 파일명을 가진 템플릿을 둘 수 있습니다. 이 템플릿이 존재하면, 디렉터리 내부에 중첩되어 있더라도 해당 컴포넌트의 "루트" 요소로 렌더링할 수 있습니다. 위 예시와 동일하게 Blade 문법을 계속 사용할 수 있으며, 디렉터리 구조만 다음과 같이 변경하면 됩니다.

/resources/views/components/accordion/accordion.blade.php
/resources/views/components/accordion/item.blade.php

데이터 속성 / 속성(attribute)

익명 컴포넌트에는 별도의 클래스가 없기 때문에, 어떤 데이터가 변수로 전달되어야 하는지, 어떤 속성이 속성 백에 속해야 하는지 구분이 필요합니다.

Blade 템플릿 상단에서 @props 지시어를 사용해 어떤 속성이 데이터 변수로 취급되어야 하는지 지정할 수 있습니다. 컴포넌트의 다른 모든 속성은 속성 백(attribute bag)에서 사용할 수 있습니다. 데이터 변수에 기본값을 지정하려면 배열의 키에 변수명을, 값에 기본값을 써주면 됩니다.

<!-- /resources/views/components/alert.blade.php -->

@props(['type' => 'info', 'message'])

<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
{{ $message }}
</div>

위와 같이 컴포넌트를 정의했다면 다음과 같이 렌더링할 수 있습니다.

<x-alert type="error" :message="$message" class="mb-4"/>

상위 데이터 접근하기

때때로, 자식 컴포넌트 내부에서 상위 컴포넌트에 전달된 데이터를 참조하고 싶을 수 있습니다. 이럴 때는 @aware 지시어를 활용할 수 있습니다. 예를 들어 복잡한 메뉴 컴포넌트(<x-menu><x-menu.item>로 구성)를 만든다고 가정합시다.

<x-menu color="purple">
<x-menu.item>...</x-menu.item>
<x-menu.item>...</x-menu.item>
</x-menu>

<x-menu> 컴포넌트는 다음과 같이 구현될 수 있습니다.

<!-- /resources/views/components/menu/index.blade.php -->

@props(['color' => 'gray'])

<ul {{ $attributes->merge(['class' => 'bg-'.$color.'-200']) }}>
{{ $slot }}
</ul>

이때 color prop이 상위(<x-menu>) 컴포넌트에만 전달되었기 때문에, 그냥 두면 <x-menu.item> 안에서는 사용할 수 없습니다. 하지만 @aware 지시어를 활용하면 자식 컴포넌트에서도 이 값을 사용할 수 있습니다.

<!-- /resources/views/components/menu/item.blade.php -->

@aware(['color' => 'gray'])

<li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}>
{{ $slot }}
</li>

[!WARNING]
@aware 지시어는 반드시 상위 컴포넌트에 HTML 속성(attribute)으로 명시적으로 전달된 값만 접근할 수 있습니다. 상위 컴포넌트의 @props 기본값(명시적으로 속성으로 전달되지 않은 값)은 @aware로 접근할 수 없습니다.

익명 컴포넌트 경로

앞서 설명했듯, 익명 컴포넌트는 일반적으로 resources/views/components 디렉터리에 Blade 템플릿 파일을 두어 정의합니다. 하지만 이 기본 경로 외에 다른 익명 컴포넌트 경로도 라라벨에 등록할 수 있습니다.

anonymousComponentPath 메서드는 첫 번째 인수에 익명 컴포넌트의 위치(경로), 두 번째 인수에는 선택적으로 컴포넌트에 붙일 "네임스페이스"를 받습니다. 이 메서드는 보통 애플리케이션의 서비스 프로바이더boot 메서드에서 호출합니다.

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Blade::anonymousComponentPath(__DIR__.'/../components');
}

위와 같이 프리픽스 없이 컴포넌트 경로를 등록하면, 해당 경로에 컴포넌트가 존재할 경우 Blade 컴포넌트에서 프리픽스 없이 바로 렌더링할 수 있습니다. 예를 들어 등록된 경로에 panel.blade.php 컴포넌트가 있다면,

<x-panel />

두 번째 인수로 프리픽스 "네임스페이스"를 지정할 수도 있습니다.

Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard');

프리픽스를 지정했다면, 해당 네임스페이스를 컴포넌트 이름 앞에 붙여서 렌더링하면 됩니다.

<x-dashboard::panel />

레이아웃 구성하기

컴포넌트를 활용한 레이아웃

대부분의 웹 애플리케이션은 여러 페이지에서 공통된 레이아웃을 유지합니다. 만약 우리가 모든 뷰마다 동일한 레이아웃의 HTML을 반복해서 작성한다면, 이는 아주 번거롭고 유지 보수도 어렵게 될 것입니다. 다행히 Blade 컴포넌트로 레이아웃을 정의하고, 애플리케이션 전반에 걸쳐 재사용하는 것이 편리합니다.

레이아웃 컴포넌트 정의하기

예를 들어, "todo" 리스트 애플리케이션을 만든다고 가정해봅시다. 이때, 아래와 같은 layout 컴포넌트를 정의할 수 있습니다.

<!-- resources/views/components/layout.blade.php -->

<html>
<head>
<title>{{ $title ?? 'Todo Manager' }}</title>
</head>
<body>
<h1>Todos</h1>
<hr/>
{{ $slot }}
</body>
</html>

레이아웃 컴포넌트 적용하기

layout 컴포넌트를 정의한 후, 해당 컴포넌트를 사용하는 Blade 뷰를 만들 수 있습니다. 예를 들어, 우리의 작업(Task) 리스트를 출력하는 뷰는 다음과 같습니다.

<!-- resources/views/tasks.blade.php -->

<x-layout>
@foreach ($tasks as $task)
<div>{{ $task }}</div>
@endforeach
</x-layout>

컴포넌트에 주입된 콘텐츠는 내부적으로 컴포넌트의 $slot 변수에 전달된다는 점을 기억하세요. 또한, layout 컴포넌트는 $title 슬롯이 전달된 경우 이를 활용하고, 없을 경우에는 기본 제목을 출력합니다. 아래와 같이 일반적인 슬롯 사용법을 통해 커스텀 제목을 별도로 지정할 수 있습니다.

<!-- resources/views/tasks.blade.php -->

<x-layout>
<x-slot:title>
Custom Title
</x-slot>

@foreach ($tasks as $task)
<div>{{ $task }}</div>
@endforeach
</x-layout>

이제 레이아웃과 작업 목록 뷰를 정의했다면, 라우트에서 이 tasks 뷰를 반환하면 됩니다.

use App\Models\Task;

Route::get('/tasks', function () {
return view('tasks', ['tasks' => Task::all()]);
});

템플릿 상속을 활용한 레이아웃

레이아웃 정의하기

레이아웃은 "템플릿 상속" 방식으로도 생성할 수 있습니다. 이는 컴포넌트가 도입되기 전 애플리케이션 구조의 기본 방식이었습니다.

간단한 예제로 시작해보겠습니다. 우선, 페이지 레이아웃을 정의해보죠. 대부분의 웹 애플리케이션은 여러 페이지에서 동일한 레이아웃을 유지하므로, 레이아웃을 하나의 Blade 뷰로 정의하는 것이 편리합니다.

<!-- resources/views/layouts/app.blade.php -->

<html>
<head>
<title>App Name - @yield('title')</title>
</head>
<body>
@section('sidebar')
This is the master sidebar.
@show

<div class="container">
@yield('content')
</div>
</body>
</html>

보시다시피, 일반적인 HTML 마크업으로 되어 있습니다. 여기서 @section@yield 지시어에 주목하세요. @section은 콘텐츠 영역을 정의하며, @yield는 해당 영역의 내용을 출력할 때 사용됩니다.

이제 애플리케이션의 레이아웃을 정의했으니, 이를 상속하는 하위 페이지를 만들어봅시다.

레이아웃 확장하기

하위 뷰를 작성할 때는 @extends Blade 지시어로 상속할 레이아웃을 지정합니다. 레이아웃을 상속하는 하위 뷰는 @section 지시어를 통해 원하는 섹션에 콘텐츠를 주입할 수 있습니다. 위 예시에서 본 것처럼, 각 섹션의 내용은 레이아웃에서 @yield로 출력됩니다.

<!-- resources/views/child.blade.php -->

@extends('layouts.app')

@section('title', 'Page Title')

@section('sidebar')
@@parent

<p>This is appended to the master sidebar.</p>
@endsection

@section('content')
<p>This is my body content.</p>
@endsection

이 예시에서, sidebar 섹션에서는 @@parent 지시어를 사용해 기존 레이아웃의 사이드바 내용 뒤에 추가 내용을 붙이고 있습니다. @@parent 지시어는 뷰가 렌더링될 때 레이아웃의 해당 부분으로 대체됩니다.

[!NOTE]
앞선 예제와는 달리, 이 sidebar 섹션은 마지막에 @endsection으로 끝납니다(@show가 아님). @endsection은 해당 영역만 정의하며, @show는 영역 정의와 동시에 즉시 출력합니다.

@yield 지시어는 두 번째 인수로 기본값도 받을 수 있습니다. 지정한 섹션이 정의되지 않았을 때 이 값이 렌더링됩니다.

@yield('content', 'Default content')

폼(Forms)

CSRF 필드

애플리케이션에서 HTML 폼을 정의할 때는 CSRF 보호 미들웨어가 요청을 검증할 수 있도록 반드시 숨겨진 CSRF 토큰 필드를 포함해야 합니다. @csrf Blade 지시어를 사용하면 간편하게 토큰 필드를 생성할 수 있습니다.

<form method="POST" action="/profile">
@csrf

...
</form>

메서드 필드

HTML 폼은 PUT, PATCH, DELETE와 같은 요청을 직접 보낼 수 없습니다. 따라서 이런 HTTP 메서드를 모방하려면 숨은 _method 필드를 추가해야 합니다. @method Blade 지시어로 이 필드를 쉽게 생성할 수 있습니다.

<form action="/foo/bar" method="POST">
@method('PUT')

...
</form>

유효성 검증 에러

@error 지시어를 사용하면 유효성 검증 에러 메시지가 해당 속성에 대해 존재하는지 빠르게 확인할 수 있습니다. @error 블록 내에서는 $message 변수를 echo 하여 에러 메시지를 출력할 수 있습니다.

<!-- /resources/views/post/create.blade.php -->

<label for="title">Post Title</label>

<input
id="title"
type="text"
class="@error('title') is-invalid @enderror"
/>

@error('title')
<div class="alert alert-danger">{{ $message }}</div>
@enderror

@error 지시어는 내부적으로 "if" 문으로 변환되므로, 에러가 없을 때 다른 내용을 출력하고 싶다면 @else 지시어를 함께 사용할 수 있습니다.

<!-- /resources/views/auth.blade.php -->

<label for="email">Email address</label>

<input
id="email"
type="email"
class="@error('email') is-invalid @else is-valid @enderror"
/>

여러 폼이 있는 페이지에서 특정 에러 백의 이름을 두 번째 매개변수로 전달하면 명명된 에러 백에 대한 검증 메시지도 얻을 수 있습니다.

<!-- /resources/views/auth.blade.php -->

<label for="email">Email address</label>

<input
id="email"
type="email"
class="@error('email', 'login') is-invalid @enderror"
/>

@error('email', 'login')
<div class="alert alert-danger">{{ $message }}</div>
@enderror

스택

Blade에서는 이름이 지정된 스택(named stack)에 내용을 추가할 수 있으며, 이 스택은 다른 뷰나 레이아웃의 원하는 위치에서 렌더링할 수 있습니다. 이 기능은 특히 자식 뷰에서 필요한 JavaScript 라이브러리를 지정할 때 유용하게 사용할 수 있습니다.

@push('scripts')
<script src="/example.js"></script>
@endpush

만약 어떤 불리언(boolean) 식이 true일 때만 @push로 내용을 추가하려면, @pushIf 디렉티브를 사용할 수 있습니다.

@pushIf($shouldPush, 'scripts')
<script src="/example.js"></script>
@endPushIf

하나의 스택에는 몇 번이든 자유롭게 내용을 추가(push)할 수 있습니다. 이렇게 추가한 스택의 전체 내용을 렌더링하려면, @stack 디렉티브에 스택의 이름을 넣어 사용하면 됩니다.

<head>
<!-- Head Contents -->

@stack('scripts')
</head>

스택의 앞부분에 내용을 추가하고 싶다면, @prepend 디렉티브를 사용해야 합니다.

@push('scripts')
This will be second...
@endpush

// 이후에...

@prepend('scripts')
This will be first...
@endprepend

서비스 주입

@inject 디렉티브를 사용해 서비스 컨테이너에서 서비스를 추출해 뷰에서 사용할 수 있습니다. @inject의 첫 번째 인자는 서비스가 저장될 변수명이고, 두 번째 인자는 주입받고자 하는 서비스의 클래스명 또는 인터페이스명입니다.

@inject('metrics', 'App\Services\MetricsService')

<div>
Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
</div>

인라인 Blade 템플릿 렌더링

간혹 원시 Blade 템플릿 문자열을 실제 HTML로 변환해야 할 때가 있습니다. 이럴 때는 Blade 파사드의 render 메서드를 사용하면 됩니다. render 메서드는 Blade 템플릿 문자열과, 선택적으로 뷰에 전달할 데이터를 배열로 받을 수 있습니다.

use Illuminate\Support\Facades\Blade;

return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);

라라벨은 인라인 Blade 템플릿을 렌더링할 때 임시로 해당 템플릿을 storage/framework/views 디렉토리에 기록합니다. 만약 Blade 템플릿 렌더링 후 이러한 임시 파일을 자동으로 삭제하고 싶다면, deleteCachedView 인자를 메서드에 전달하면 됩니다.

return Blade::render(
'Hello, {{ $name }}',
['name' => 'Julian Bashir'],
deleteCachedView: true
);

Blade 프래그먼트(Fragment) 렌더링

Tubrohtmx 같은 프론트엔드 프레임워크를 사용할 때, HTTP 응답으로 Blade 템플릿의 특정 부분만 반환하고 싶을 수 있습니다. Blade "프래그먼트" 기능을 사용하면 이러한 작업이 가능합니다. 먼저, Blade 템플릿에서 반환하고자 하는 영역을 @fragment@endfragment 디렉티브로 감쌉니다.

@fragment('user-list')
<ul>
@foreach ($users as $user)
<li>{{ $user->name }}</li>
@endforeach
</ul>
@endfragment

이제 이 템플릿을 사용하는 뷰를 렌더링할 때, fragment 메서드를 호출해 특정 프래그먼트만 HTTP 응답에 포함할 수 있습니다.

return view('dashboard', ['users' => $users])->fragment('user-list');

fragmentIf 메서드를 사용하면 주어진 조건에 따라 뷰의 프래그먼트만 반환하거나, 조건에 맞지 않으면 전체 뷰를 반환할 수 있습니다.

return view('dashboard', ['users' => $users])
->fragmentIf($request->hasHeader('HX-Request'), 'user-list');

fragmentsfragmentsIf 메서드를 사용하면 응답으로 복수의 뷰 프래그먼트를 반환할 수 있습니다. 반환되는 프래그먼트들은 하나로 합쳐집니다.

view('dashboard', ['users' => $users])
->fragments(['user-list', 'comment-list']);

view('dashboard', ['users' => $users])
->fragmentsIf(
$request->hasHeader('HX-Request'),
['user-list', 'comment-list']
);

Blade 확장하기

Blade에서는 directive 메서드를 이용해 커스텀 디렉티브를 정의할 수 있습니다. Blade 컴파일러가 커스텀 디렉티브를 만나면, 해당 디렉티브에 포함된 식(expression)을 콜백 함수에 인수로 전달합니다.

아래 예시는 주어진 $var(반드시 DateTime 인스턴스여야 함)를 포맷해주는 @datetime($var) 디렉티브를 만드는 방법을 보여줍니다.

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// ...
}

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Blade::directive('datetime', function (string $expression) {
return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
});
}
}

보시는 것처럼, 전달받은 식에 format 메서드를 체이닝해서 사용합니다. 따라서 위 예시에서 최종적으로 해당 디렉티브가 생성하는 PHP 코드는 다음과 같습니다.

<?php echo ($var)->format('m/d/Y H:i'); ?>

[!WARNING]
Blade 디렉티브의 동작 로직을 수정했다면, 반드시 캐시된 Blade 뷰 파일을 모두 삭제해야 합니다. 캐시된 Blade 뷰는 view:clear Artisan 명령어로 삭제할 수 있습니다.

커스텀 Echo 핸들러

Blade에서 객체를 "echo"로 출력할 경우, 해당 객체의 __toString 메서드가 자동으로 호출됩니다. __toString 메서드는 PHP의 내장 "매직 메서드" 중 하나입니다. 하지만, 사용하려는 클래스가 외부 라이브러리 소속이어서 그 클래스의 __toString 메서드를 직접 제어할 수 없는 경우도 있습니다.

이러한 경우 Blade에서는 특정 객체 타입에 대해 커스텀 echo 핸들러를 등록할 수 있습니다. 이를 위해서는 Blade의 stringable 메서드를 사용합니다. stringable 메서드는 클로저(익명 함수)를 인수로 받으며, 반드시 해당 객체 타입이 명시된 타입힌트가 포함되어야 합니다. 일반적으로, 이 메서드는 애플리케이션의 AppServiceProvider 클래스의 boot 메서드에서 호출합니다.

use Illuminate\Support\Facades\Blade;
use Money\Money;

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Blade::stringable(function (Money $money) {
return $money->formatTo('en_GB');
});
}

커스텀 echo 핸들러가 정의되면, Blade 템플릿에서 객체를 바로 출력할 수 있습니다.

Cost: {{ $money }}

커스텀 If 문

커스텀 디렉티브를 직접 만드는 것은 단순한 조건문만 구현하려는 경우에는 오히려 복잡할 수 있습니다. Blade는 간단한 조건문을 쉽게 커스텀할 수 있도록, 클로저를 활용하는 Blade::if 메서드를 제공합니다. 예를 들어, 애플리케이션의 기본 "디스크(disk)" 설정 값을 확인하는 커스텀 조건문을 아래와 같이 정의할 수 있습니다. 이 작업은 주로 AppServiceProviderboot 메서드 내에서 이루어집니다.

use Illuminate\Support\Facades\Blade;

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Blade::if('disk', function (string $value) {
return config('filesystems.default') === $value;
});
}

이렇게 커스텀 조건문을 정의했으면, Blade 템플릿에서 바로 사용할 수 있습니다.

@disk('local')
<!-- The application is using the local disk... -->
@elsedisk('s3')
<!-- The application is using the s3 disk... -->
@else
<!-- The application is using some other disk... -->
@enddisk

@unlessdisk('local')
<!-- The application is not using the local disk... -->
@enddisk