Blade 템플릿
번역일: 2026년 7월 2일
Blade 템플릿
소개
Blade는 Laravel에 내장된 간결하고 강력한 템플릿 엔진입니다. 일부 PHP 템플릿 엔진과 달리, Blade는 뷰 안에서 순수 PHP 코드를 자유롭게 사용할 수 있습니다. 모든 Blade 뷰 파일은 순수 PHP 코드로 컴파일되어 캐시되므로, 수정이 없는 한 다시 컴파일하지 않습니다. 이 덕분에 성능 부담 없이 편리한 문법을 사용할 수 있습니다.
Blade 뷰 파일은 .blade.php 확장자를 사용하며, 일반적으로 resources/views 디렉터리에 저장합니다.
컨트롤러나 라우트에서 전역 view 헬퍼 함수를 사용해 Blade 뷰를 반환할 수 있습니다.
Route::get('/', function () {
return view('greeting', ['name' => '철수']);
});Livewire로 Blade 확장하기
Blade 템플릿을 한 단계 더 발전시키고 싶다면 Laravel Livewire를 살펴보세요. Livewire를 사용하면 복잡한 JavaScript 프레임워크 없이도, Blade 컴포넌트에 동적인 인터랙티브 기능을 추가할 수 있습니다. SPA와 같은 사용자 경험을 구현하면서도 서버 사이드 렌더링의 단순함을 유지할 수 있어, 많은 팀에서 선호하는 방식입니다.
데이터 출력
Blade 뷰에 전달된 변수는 이중 중괄호({{ }})로 출력합니다. 예를 들어, 다음 라우트가 있다면:
Route::get('/', function () {
return view('welcome', ['name' => '철수']);
});뷰에서 아래와 같이 name 변수를 출력할 수 있습니다.
안녕하세요, {{ $name }}님.NOTE
Blade의 {{ }} 구문은 XSS 공격을 방지하기 위해 PHP의 htmlspecialchars 함수를 자동으로 적용합니다.
물론, 변수만 출력할 수 있는 것은 아닙니다. PHP 함수의 반환값도 얼마든지 출력할 수 있습니다.
현재 시각: {{ now()->toDateTimeString() }}HTML 엔티티 인코딩
기본적으로 Blade(그리고 Laravel의 e 헬퍼)는 HTML 엔티티를 이중으로 인코딩합니다. 이중 인코딩을 비활성화하려면 AppServiceProvider의 boot 메서드에서 Blade::withoutDoubleEncoding 메서드를 호출하세요.
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Blade::withoutDoubleEncoding();
}
}이스케이프 없이 데이터 출력하기
기본적으로 Blade의 {{ }} 구문은 HTML을 자동으로 이스케이프합니다. 이스케이프 처리 없이 원본 HTML을 그대로 출력하려면 {!! !!} 구문을 사용하세요.
안녕하세요, {!! $name !!}님.WARNING
{!! !!}로 사용자 입력 데이터를 출력할 때는 XSS 취약점에 각별히 주의하세요. 신뢰할 수 없는 데이터에는 반드시 이스케이프 처리된 {{ }} 구문을 사용하세요.
Blade와 JavaScript 프레임워크
Vue나 Alpine.js 같은 JavaScript 프레임워크도 이중 중괄호({{ }})를 사용해 변수를 출력합니다. 이런 경우 @ 기호를 앞에 붙여 Blade에게 해당 구문을 처리하지 말라고 알릴 수 있습니다. @ 기호는 최종 HTML에서 제거되며, 중괄호 구문은 그대로 남아 JavaScript 프레임워크가 처리합니다.
<h1>Laravel</h1>
안녕하세요, @{{ name }}님.위 예시에서 @ 기호는 Blade가 제거하고, {{ name }} 구문은 JavaScript 프레임워크가 처리할 수 있도록 남겨집니다.
@ 기호는 Blade 디렉티브도 이스케이프할 수 있습니다.
{{-- Blade가 이 디렉티브를 처리하지 않습니다 --}}
@@if()JSON 렌더링
배열을 JSON으로 변환해 JavaScript 변수에 할당할 때는 직접 json_encode를 사용하는 대신 Illuminate\Support\Js::from 메서드를 사용하는 것을 권장합니다. 이 메서드는 PHP의 json_encode 함수와 동일한 옵션을 사용하며, HTML 속성 안에서 안전하게 동작하도록 JSON을 적절히 이스케이프합니다. 반환값은 JSON.parse JavaScript 구문으로 래핑됩니다.
<script>
var app = {{ Illuminate\Support\Js::from($array) }};
</script>Js 파사드를 사용하면 더 간결하게 작성할 수 있습니다.
<script>
var app = {{ Js::from($array) }};
</script>WARNING
Js::from 메서드는 기존 변수를 JSON으로 렌더링할 때만 사용하세요. Blade 템플릿은 정규표현식 기반으로 동작하므로, 복잡한 표현식을 이 메서드에 전달하면 예기치 않은 결과가 발생할 수 있습니다.
`@verbatim` 디렉티브
뷰의 넓은 영역에서 JavaScript 변수를 출력해야 한다면, @verbatim 디렉티브로 해당 HTML 블록을 감쌀 수 있습니다. 이렇게 하면 모든 {{ }} 구문 앞에 일일이 @를 붙이지 않아도 됩니다.
@verbatim
<div class="container">
안녕하세요, {{ name }}님.
</div>
@endverbatimBlade 디렉티브
Blade는 이중 중괄호 구문 외에도, 조건문·반복문 같은 일반적인 PHP 제어 구조를 간결하게 표현할 수 있는 다양한 디렉티브를 제공합니다. 이 디렉티브들은 PHP 제어 구조와 완전히 동일하게 동작하면서도, 읽기 쉽고 간결한 문법을 제공합니다.
If 문
@if, @elseif, @else, @endif 디렉티브로 조건문을 작성할 수 있습니다. 이 디렉티브들은 PHP의 if, elseif, else 구문과 동일하게 동작합니다.
@if (count($records) === 1)
레코드가 하나 있습니다.
@elseif (count($records) > 1)
레코드가 여러 개 있습니다.
@else
레코드가 없습니다.
@endif@unless 디렉티브는 @if의 반대 개념으로, 조건이 false일 때 블록을 실행합니다.
@unless (Auth::check())
로그인하지 않으셨습니다.
@endunless@isset과 @empty 디렉티브는 각각 PHP의 isset()과 empty() 함수를 편리하게 사용할 수 있게 해줍니다.
@isset($records)
// $records가 정의되어 있고 null이 아닌 경우...
@endisset
@empty($records)
// $records가 "비어 있는" 경우...
@endempty인증 디렉티브
@auth와 @guest 디렉티브를 사용하면 현재 사용자의 인증 여부를 빠르게 확인할 수 있습니다.
@auth
// 인증된 사용자만 이 영역이 보입니다...
@endauth
@guest
// 비인증 사용자(게스트)만 이 영역이 보입니다...
@endguest필요하다면 특정 인증 가드를 지정할 수도 있습니다.
@auth('admin')
// admin 가드로 인증된 사용자만...
@endauth
@guest('admin')
// admin 가드로 인증되지 않은 사용자만...
@endguest환경 디렉티브
@production 디렉티브로 애플리케이션이 프로덕션 환경에서 실행 중인지 확인할 수 있습니다.
@production
// 프로덕션 환경에서만 표시되는 내용...
@endproduction또는 @env 디렉티브를 사용해 특정 환경 여부를 확인할 수 있습니다.
@env('staging')
// 스테이징 환경에서만 표시되는 내용...
@endenv
@env(['staging', 'production'])
// 스테이징 또는 프로덕션 환경에서 표시되는 내용...
@endenv섹션 디렉티브
@hasSection 디렉티브로 템플릿 상속의 특정 섹션에 내용이 있는지 확인할 수 있습니다.
@hasSection('navigation')
<div class="pull-right">
@yield('navigation')
</div>
<div class="clearfix"></div>
@endif@sectionMissing 디렉티브는 특정 섹션에 내용이 없을 때 실행됩니다.
@sectionMissing('navigation')
<div class="pull-right">
@include('default-navigation')
</div>
@endif세션 디렉티브
@session 디렉티브는 세션 값이 존재하는지 확인합니다. 세션 값이 있으면 $value 변수로 출력할 수 있습니다.
@session('status')
<div class="p-4 bg-green-100">
{{ $value }}
</div>
@endsessionSwitch 문
@switch, @case, @break, @default, @endswitch 디렉티브로 switch 문을 작성할 수 있습니다.
@switch($i)
@case(1)
첫 번째 케이스
@break
@case(2)
두 번째 케이스
@break
@default
기본 케이스
@endswitch반복문
Blade는 PHP의 반복문에 대응하는 간편한 디렉티브를 제공합니다.
@for ($i = 0; $i < 10; $i++)
현재 값: {{ $i }}
@endfor
@foreach ($users as $user)
<p>사용자: {{ $user->id }}</p>
@endforeach
@forelse ($users as $user)
<li>{{ $user->name }}</li>
@empty
<p>사용자가 없습니다.</p>
@endforelse
@while (true)
<p>무한 반복 중...</p>
@endwhileNOTE
@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)
첫 번째 항목입니다.
@endif
@if ($loop->last)
마지막 항목입니다.
@endif
<p>{{ $user->id }}</p>
@endforeach중첩 반복문에서는 $loop->parent를 통해 부모 루프의 $loop 변수에 접근할 수 있습니다.
@foreach ($posts as $post)
@foreach ($post->comments as $comment)
@if ($loop->parent->first)
이것은 첫 번째 게시글의 댓글입니다.
@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>위 코드는 다음 HTML을 렌더링합니다.
<span class="p-4 text-gray-500 bg-red"></span>추가 속성
@style 디렉티브는 동일한 방식으로 인라인 CSS 스타일을 조건에 따라 적용합니다.
@php
$isActive = true;
@endphp
<span @style([
'background-color: red',
'font-weight: bold' => $isActive,
])></span>렌더링 결과:
<span style="background-color: red; font-weight: bold;"></span>@checked 디렉티브는 체크박스가 선택되어야 하는지 편리하게 표현할 수 있습니다.
<input type="checkbox"
name="active"
value="active"
@checked(old('active', $user->active)) />@selected 디렉티브는 select 옵션이 선택된 상태인지 표현합니다.
<select name="version">
@foreach ($product->versions as $version)
<option value="{{ $version }}" @selected(old('version') == $version)>
{{ $version }}
</option>
@endforeach
</select>@disabled 디렉티브는 요소를 비활성화 상태로 만들 때 사용합니다.
<button type="submit" @disabled($errors->isNotEmpty())>제출</button>@readonly 디렉티브는 입력 요소를 읽기 전용으로 만들 때 사용합니다.
<input type="email"
name="email"
value="이메일@예시.com"
@readonly($user->isNotAdmin()) />@required 디렉티브는 입력 요소를 필수 항목으로 표시할 때 사용합니다.
<input type="text"
name="title"
value="제목"
@required($user->isAdmin()) />서브뷰 포함하기
NOTE
@include 디렉티브를 직접 사용할 수도 있지만, Blade 컴포넌트는 더 나은 데이터 바인딩과 속성 관리 등 다양한 장점을 제공합니다.
@include 디렉티브는 다른 Blade 뷰를 현재 뷰에 포함시킵니다. 포함된 뷰는 부모 뷰에서 사용 가능한 모든 변수를 자동으로 상속합니다.
<div>
@include('shared.errors')
<form>
<!-- 폼 내용 -->
</form>
</div>포함하는 뷰의 변수 외에 추가 데이터를 배열로 전달할 수도 있습니다.
@include('view.name', ['status' => '완료'])존재하지 않는 뷰를 @include하면 오류가 발생합니다. 파일이 없을 때 조용히 넘어가게 하려면 @includeIf를 사용하세요.
@includeIf('view.name', ['status' => '완료'])조건에 따라 뷰를 포함시키려면 @includeWhen 또는 @includeUnless를 사용할 수 있습니다.
@includeWhen($boolean, 'view.name', ['status' => '완료'])
@includeUnless($boolean, 'view.name', ['status' => '완료'])여러 뷰 중 처음으로 존재하는 것을 포함시키려면 @includeFirst를 사용하세요.
@includeFirst(['custom.admin', 'admin'], ['status' => '완료'])WARNING
Blade 뷰에서 __DIR__과 __FILE__ 상수를 사용하지 마세요. 이 상수들은 캐시된 컴파일 파일의 경로를 반환하므로 원하는 경로가 나오지 않습니다.
컬렉션을 위한 뷰 렌더링
반복문과 @include를 하나의 디렉티브로 합친 @each를 사용할 수 있습니다.
@each('view.name', $jobs, 'job')첫 번째 인수는 렌더링할 뷰, 두 번째는 반복할 배열 또는 컬렉션, 세 번째는 반복 항목이 뷰 안에서 사용될 변수명입니다. 예를 들어 jobs 배열을 반복할 때, 각 항목은 뷰 안에서 $job 변수로 접근할 수 있습니다. 현재 반복의 키는 뷰 안에서 key 변수로 사용할 수 있습니다.
배열이 비어 있을 때 렌더링할 뷰를 네 번째 인수로 지정할 수도 있습니다.
@each('view.name', $jobs, 'job', 'view.empty')WARNING
@each로 렌더링된 뷰는 부모 뷰의 변수를 상속하지 않습니다. 부모 변수가 필요하다면 @foreach와 @include를 조합해 사용하세요.
`@once` 디렉티브
@once 디렉티브는 해당 블록이 페이지 렌더링 사이클에서 딱 한 번만 실행되도록 보장합니다. 주로 스택을 이용해 페이지의 <head> 태그에 특정 JavaScript나 CSS를 한 번만 추가할 때 유용합니다.
예를 들어, 반복문 안에서 컴포넌트를 렌더링할 때 특정 스크립트를 최초 한 번만 head에 추가하고 싶다면:
@once
@push('scripts')
<script>
// 커스텀 JavaScript 코드...
</script>
@endpush
@endonce@once는 @push나 @prepend와 자주 함께 사용되므로 @pushOnce와 @prependOnce 단축 디렉티브도 제공합니다.
@pushOnce('scripts')
<script>
// 커스텀 JavaScript 코드...
</script>
@endPushOnce순수 PHP
상황에 따라 뷰 안에서 직접 PHP 코드를 실행해야 할 때가 있습니다. @php 디렉티브를 사용하면 됩니다.
@php
$counter = 1;
@endphp단순히 PHP 클래스를 임포트해야 한다면 @use 디렉티브를 사용할 수 있습니다.
@use('App\Models\Flight')별칭을 지정하려면 두 번째 인수를 전달하세요.
@use('App\Models\Flight', 'FlightModel')주석
Blade는 HTML로 렌더링되지 않는 주석 구문을 지원합니다. HTML 주석과 달리, Blade 주석은 애플리케이션이 반환하는 HTML에 포함되지 않습니다.
{{-- 이 주석은 렌더링된 HTML에 나타나지 않습니다 --}}컴포넌트
컴포넌트와 슬롯은 섹션, 레이아웃, @include와 유사한 기능을 제공하지만, 컴포넌트의 사고 방식이 더 직관적으로 느껴지는 경우가 많습니다. 컴포넌트는 클래스 기반과 익명(anonymous) 방식 두 가지로 작성할 수 있습니다.
클래스 기반 컴포넌트를 생성하려면 make:component Artisan 명령어를 사용합니다. 예제로 간단한 Alert 컴포넌트를 만들어 보겠습니다.
php artisan make:component Alert이 명령어는 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 />으로 렌더링할 수 있습니다.
컴포넌트 수동 등록
WARNING
아래의 수동 등록은 주로 컴포넌트를 포함하는 패키지를 개발할 때 적용됩니다. 일반 애플리케이션을 개발하는 경우에는 자동 검색으로 컴포넌트가 등록되므로 수동 등록이 필요하지 않습니다.
자신의 애플리케이션에 컴포넌트를 작성할 때는 app/View/Components 및 resources/views/components 디렉터리에 위치한 컴포넌트들이 자동으로 검색·등록됩니다.
하지만 Blade 컴포넌트를 사용하는 패키지를 개발하거나, 비표준 디렉터리에 컴포넌트를 두는 경우에는 서비스 프로바이더에서 수동으로 컴포넌트 클래스와 HTML 태그 별칭을 등록해야 합니다.
일반적으로 패키지의 서비스 프로바이더 boot 메서드에서 컴포넌트를 등록합니다.
use Illuminate\Support\Facades\Blade;
/**
* 패키지 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Blade::component('package-alert', Alert::class);
}등록 후에는 태그 별칭으로 컴포넌트를 렌더링할 수 있습니다.
<x-package-alert/>또는 componentNamespace 메서드를 사용해 네임스페이스 기반으로 컴포넌트 클래스를 자동 로드할 수도 있습니다. 예를 들어 Nightshade 패키지에 Package\Views\Components 네임스페이스 안에 Calendar와 ColorPicker 컴포넌트가 있다면:
use Illuminate\Support\Facades\Blade;
/**
* 패키지 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}이렇게 하면 패키지명::컴포넌트명 형태의 벤더 네임스페이스 구문으로 컴포넌트를 사용할 수 있습니다.
<x-nightshade::calendar />
<x-nightshade::color-picker />Blade는 컴포넌트 이름을 파스칼 케이스로 변환하여 자동으로 해당 클래스를 찾아줍니다. 서브디렉터리도 . 표기법으로 지원합니다.
컴포넌트 렌더링
컴포넌트를 뷰에서 사용하려면 Blade 컴포넌트 태그를 <x-로 시작하고 컴포넌트 클래스 이름의 케밥 케이스 형태로 작성합니다.
<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;
}인덱스 컴포넌트
컴포넌트가 여러 Blade 템플릿으로 구성될 때, 해당 컴포넌트의 뷰 파일들을 하나의 디렉터리로 묶고 싶을 수 있습니다. 예를 들어 accordion 컴포넌트의 파일 구조가 다음과 같다면:
/resources/views/components/accordion.blade.php
/resources/views/components/accordion/item.blade.php이 구조를 사용하면 accordion 컴포넌트와 그 항목을 다음과 같이 렌더링할 수 있습니다.
<x-accordion>
<x-accordion.item>
...
</x-accordion.item>
</x-accordion>그런데 accordion.blade.php를 디렉터리 안으로 옮겨 index.blade.php로 이름을 바꾸면 더 깔끔하게 정리할 수 있습니다.
/resources/views/components/accordion/index.blade.php
/resources/views/components/accordion/item.blade.php이 구조는 동일하게 동작하며, 관련 파일을 하나의 디렉터리 안에서 관리할 수 있습니다.
컴포넌트에 데이터 전달하기
HTML 속성을 통해 Blade 컴포넌트에 데이터를 전달할 수 있습니다. PHP의 기본 자료형은 문자열 속성으로, PHP 표현식이나 변수는 : 접두사를 붙인 속성으로 전달합니다.
<x-alert type="error" :message="$message"/>컴포넌트 클래스의 생성자에서 전달받을 데이터를 정의합니다. 생성자의 모든 public 프로퍼티는 컴포넌트 뷰에서 자동으로 사용할 수 있습니다.
<?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>케이스 표기법
컴포넌트 생성자 인수는 카멜 케이스(camelCase)로 작성하지만, HTML 속성에서는 케밥 케이스(kebab-case)로 참조합니다.
/**
* 컴포넌트 인스턴스를 생성합니다.
*/
public function __construct(
public string $alertType,
) {}<x-alert alert-type="danger" />단축 속성 구문
컴포넌트에 속성을 전달할 때, "단축 속성" 구문을 사용할 수도 있습니다. 속성 이름과 변수 이름이 같을 때 편리합니다.
{{-- 단축 속성 구문 --}}
<x-profile :$userId :$name />
{{-- 아래와 동일 --}}
<x-profile :user-id="$userId" :name="$name" />속성 렌더링 이스케이프
Alpine.js처럼 콜론(:)으로 시작하는 속성을 사용하는 JavaScript 프레임워크를 함께 사용할 때, 해당 속성이 PHP 표현식이 아님을 Blade에 알리려면 이중 콜론(::)을 사용하세요.
<x-button ::class="{ danger: isDeleting }">
제출
</x-button>렌더링 결과:
<button :class="{ danger: isDeleting }">
제출
</button>컴포넌트 메서드
컴포넌트 클래스의 public 메서드도 뷰에서 호출할 수 있습니다. 예를 들어 isSelected 메서드가 있다면:
/**
* 주어진 옵션이 현재 선택된 것인지 확인합니다.
*/
public function isSelected(string $option): bool
{
return $option === $this->selected;
}뷰에서 메서드 이름을 변수처럼 호출할 수 있습니다.
<option {{ $isSelected($value) ? 'selected' : '' }} value="{{ $value }}">
{{ $label }}
</option>컴포넌트 클래스 안에서 속성과 슬롯 접근하기
Blade 컴포넌트에서는 render 메서드의 클로저 안에서 컴포넌트 이름, 속성, 슬롯 등에 접근할 수 있습니다.
use Closure;
/**
* 컴포넌트를 나타내는 뷰 / 콘텐츠를 반환합니다.
*/
public function render(): Closure
{
return function () {
return '<div {{ $attributes }}>컴포넌트 내용</div>';
};
}클로저는 $data 배열을 인수로 받을 수 있으며, 컴포넌트 정보를 담은 여러 요소가 포함됩니다.
return function (array $data) {
// $data['componentName'] — 컴포넌트 이름 (예: alert)
// $data['attributes'] — 컴포넌트에 전달된 속성 컬렉션
// $data['slot'] — 슬롯 내용
return '<div {{ $attributes }}>컴포넌트 내용</div>';
};WARNING
render 메서드에서 문자열을 직접 반환하면 자동으로 Blade 컴파일이 적용되지 않습니다. 이 방식은 필요한 경우에만 사용하고, 가급적 view() 함수로 뷰 파일을 반환하는 방식을 사용하세요.
componentName은 x- 접두사 이후의 HTML 태그 이름과 같습니다. 즉, <x-alert />의 componentName은 alert입니다. attributes에는 HTML 태그에 지정된 모든 속성이 담겨 있고, slot에는 컴포넌트의 슬롯 내용이 담겨 있습니다.
클로저에서 HTML 문자열을 직접 반환할 수 있습니다.
/**
* 컴포넌트를 나타내는 뷰 / 콘텐츠를 반환합니다.
*/
public function render(): Closure
{
return function () {
return '<div>컴포넌트 내용</div>';
};
}추가 의존성 주입
컴포넌트가 Laravel 서비스 컨테이너에서 의존성을 주입받아야 한다면, 컴포넌트의 데이터 속성보다 앞에 선언하면 자동으로 주입됩니다.
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,
) {}
}컴포넌트 속성
컴포넌트에 데이터 속성을 전달하는 방법은 앞서 살펴봤습니다. 그런데 컴포넌트의 동작과 직접 관련 없는 class와 같은 추가 HTML 속성을 전달해야 할 때가 있습니다. 이런 속성들은 컴포넌트 템플릿의 루트 요소에 그대로 전달되길 원할 것입니다. 예를 들어 alert 컴포넌트를 다음과 같이 렌더링한다면:
<x-alert type="error" :message="$message" class="mt-4"/>생성자에 없는 속성들은 컴포넌트의 "속성 백(attribute bag)"에 자동으로 수집됩니다. 이 속성 백은 $attributes 변수를 통해 뷰에서 사용할 수 있습니다.
<div {{ $attributes }}>
<!-- 컴포넌트 내용 -->
</div>WARNING
컴포넌트 태그 안에서 @env와 같은 디렉티브를 속성으로 사용하는 것은 현재 지원되지 않습니다. 예를 들어 <x-button @env('production') disabled="disabled" @endenv />는 컴파일되지 않습니다.
기본값 및 속성 병합
속성에 기본값을 설정하거나 특정 속성에 값을 병합하려면 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">
<!-- $message 변수 내용 -->
</div>조건부 클래스 병합
특정 조건이 true일 때만 클래스를 병합하려면 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 디렉티브를 사용하세요.
클래스 이외 속성 병합
class 이외의 속성에 merge를 사용하면, 전달된 값이 기본값을 덮어씁니다. 예를 들어 button 컴포넌트를 다음과 같이 구성하면:
<button {{ $attributes->merge(['type' => 'button']) }}>
{{ $slot }}
</button>컴포넌트 사용 시 type을 지정하면 기본값이 덮어써집니다.
<x-button type="submit">
제출
</x-button>렌더링 결과:
<button type="submit">
제출
</button>class 이외의 속성에 기본값과 전달값을 함께 사용하려면 prepends 메서드를 활용하세요. 예를 들어 data-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 메서드를 사용하세요.
@if ($attributes->has('class'))
<div>Class 속성이 있습니다.</div>
@endif배열을 전달하면 모든 속성이 존재하는지 확인합니다.
@if ($attributes->has(['name', 'class']))
<div>name과 class 속성이 모두 있습니다.</div>
@endifhasAny 메서드는 배열 중 하나라도 존재하는지 확인합니다.
@if ($attributes->hasAny(['href', ':href', 'v-bind:href']))
<div>href 관련 속성 중 하나가 있습니다.</div>
@endif특정 속성값을 가져오려면 get 메서드를 사용하세요.
{{ $attributes->get('class') }}예약 키워드
Blade가 컴포넌트 내부적으로 사용하는 예약 키워드들이 있습니다. 다음 키워드는 컴포넌트의 public 프로퍼티나 메서드 이름으로 사용할 수 없습니다.
datarenderresolveViewshouldRenderviewwithAttributeswithName
슬롯
컴포넌트에 추가 콘텐츠를 전달해야 할 때 "슬롯"을 사용합니다. 컴포넌트의 슬롯 내용은 $slot 변수로 출력합니다. 아래의 alert 컴포넌트를 예시로 살펴보겠습니다.
<!-- /resources/views/components/alert.blade.php -->
<div class="alert alert-danger">
{{ $slot }}
</div>컴포넌트 태그 사이에 내용을 넣어 슬롯에 내용을 전달합니다.
<x-alert>
<strong>이런!</strong> 문제가 발생했습니다!
</x-alert>때로는 컴포넌트 안의 여러 위치에 서로 다른 콘텐츠를 넣어야 할 수 있습니다. 이때 이름이 있는 슬롯을 사용합니다. 예를 들어 modal 컴포넌트에 제목과 본문 슬롯을 만든다면:
<!-- /resources/views/components/modal.blade.php -->
<div class="modal-wrapper">
<div class="modal">
<div class="modal-header">
{{ $title }}
</div>
<div class="modal-content">
{{ $slot }}
</div>
</div>
</div><x-slot> 태그로 이름이 있는 슬롯에 내용을 전달합니다.
<x-modal>
<x-slot:title>
프로필 삭제
</x-slot>
정말로 프로필을 삭제하시겠습니까?
</x-modal>스코프 슬롯
Vue 같은 JavaScript 프레임워크를 사용해봤다면 "스코프 슬롯" 개념이 친숙할 것입니다. 스코프 슬롯은 컴포넌트 안의 데이터나 메서드에 슬롯 안에서 접근할 수 있게 해줍니다.
이를 구현하려면 컴포넌트의 public 메서드나 프로퍼티를 $component 변수로 노출하면 됩니다.
<x-alert>
<x-slot:title>
{{ $component->formatTitle($title) }}
</x-slot>
{{ $message }}
</x-alert>인라인 컴포넌트 뷰
매우 간단한 컴포넌트의 경우, 별도의 뷰 파일 없이 클래스의 render 메서드에서 직접 HTML을 반환하는 것이 더 편리할 수 있습니다. make:component 명령에 --inline 플래그를 사용하면 됩니다.
php artisan make:component Alert --inline생성된 컴포넌트의 render 메서드에서 바로 문자열을 반환합니다.
/**
* 컴포넌트를 나타내는 뷰 / 콘텐츠를 반환합니다.
*/
public function render(): string
{
return <<<'blade'
<div class="alert alert-danger">
{{ $slot }}
</div>
blade;
}동적 컴포넌트
런타임에 어떤 컴포넌트를 렌더링할지 결정해야 할 때 dynamic-component를 사용합니다.
// $componentName = "secondary-button"
<x-dynamic-component :component="$componentName" class="mt-4" />익명 컴포넌트
클래스 기반 컴포넌트와 달리, 익명 컴포넌트는 PHP 클래스 없이 단일 Blade 파일만으로 구성됩니다. 익명 컴포넌트를 만들려면 resources/views/components 디렉터리에 Blade 파일을 추가하면 됩니다.
예를 들
Blade 템플릿
목차
소개
Blade는 Laravel에 내장된 간결하면서도 강력한 템플릿 엔진입니다. 일부 PHP 템플릿 엔진과 달리, Blade는 템플릿 안에서 순수 PHP 코드를 사용하는 것을 제한하지 않습니다. 실제로 모든 Blade 템플릿은 순수 PHP 코드로 컴파일된 뒤 파일이 변경되기 전까지 캐시에 저장됩니다. 즉, Blade가 애플리케이션에 추가하는 성능 부담은 사실상 없다고 봐도 무방합니다.
Blade 템플릿 파일은 .blade.php 확장자를 사용하며, 일반적으로 resources/views 디렉터리에 저장합니다.
Blade 뷰는 전역 view 헬퍼를 통해 라우트나 컨트롤러에서 반환할 수 있습니다. 뷰 문서에서 설명한 것처럼, view 헬퍼의 두 번째 인수로 데이터를 뷰에 전달할 수 있습니다.
Route::get('/', function () {
return view('greeting', ['name' => '지수']);
});Livewire로 Blade 확장하기
Blade 템플릿을 한 단계 더 발전시켜 동적인 인터페이스를 손쉽게 구현하고 싶다면 Laravel Livewire를 살펴보세요. Livewire를 사용하면 React나 Vue 같은 프런트엔드 프레임워크에서나 가능했던 동적 기능을 갖춘 Blade 컴포넌트를 작성할 수 있습니다. 복잡한 JavaScript 프레임워크, 클라이언트 사이드 렌더링, 별도의 빌드 과정 없이도 현대적이고 반응형인 프런트엔드를 구축할 수 있는 훌륭한 방법입니다.
데이터 출력
데이터 출력
Blade 뷰에 전달된 데이터는 중괄호({{ }})로 감싸서 출력할 수 있습니다. 예를 들어 다음과 같은 라우트가 있다면:
Route::get('/', function () {
return view('welcome', ['name' => 'Samantha']);
});뷰에서 name 변수를 아래처럼 출력할 수 있습니다:
안녕하세요, {{ $name }}님.NOTE
Blade의 {{ }} 출력 구문은 XSS 공격을 방지하기 위해 PHP의 htmlspecialchars 함수를 자동으로 거칩니다.
{{ }} 안에는 변수뿐만 아니라 PHP 함수 호출 결과나 임의의 PHP 표현식도 사용할 수 있습니다:
현재 UNIX 타임스탬프: {{ time() }}HTML 엔티티 인코딩
Blade(와 Laravel의 e 헬퍼 함수)는 기본적으로 HTML 엔티티를 이중 인코딩합니다. 이중 인코딩을 비활성화하려면 AppServiceProvider의 boot 메서드에서 Blade::withoutDoubleEncoding을 호출하세요:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Blade::withoutDoubleEncoding();
}
}이스케이프 없이 데이터 출력하기
{{ }}는 항상 htmlspecialchars를 통해 이스케이프됩니다. HTML을 그대로 출력해야 할 경우에는 {!! !!} 구문을 사용하세요:
안녕하세요, {!! $name !!}님.WARNING
사용자가 입력한 데이터를 출력할 때 {!! !!}를 사용하면 XSS 공격에 노출될 수 있습니다. 사용자 입력 데이터는 반드시 {{ }}를 사용해 이스케이프 처리하세요.
Blade와 JavaScript 프레임워크
Vue, Alpine.js 등 많은 JavaScript 프레임워크도 {{ }}를 표현식 출력에 사용합니다. Blade 엔진이 해당 구문을 처리하지 않도록 하려면 @ 기호를 앞에 붙이세요:
<h1>Laravel</h1>
안녕하세요, @{{ name }}님.이 경우 Blade는 @만 제거하고 {{ name }}은 그대로 HTML에 남겨두어, JavaScript 프레임워크가 처리할 수 있도록 합니다.
@는 Blade 디렉티브 자체를 이스케이프할 때도 사용할 수 있습니다:
{{-- Blade 템플릿 --}}
@@if()
<!-- HTML 출력 결과 -->
@if()JSON 렌더링
JavaScript 변수를 초기화하기 위해 배열을 JSON으로 출력해야 할 때, 아래처럼 json_encode를 직접 호출하는 방식을 사용할 수도 있습니다:
<script>
var app = <?php echo json_encode($array); ?>;
</script>하지만 이보다는 Illuminate\Support\Js::from 메서드를 사용하는 것이 더 안전합니다. 이 메서드는 json_encode와 동일한 인자를 받으면서, HTML 따옴표 내에 안전하게 삽입될 수 있도록 결과를 적절히 이스케이프해줍니다. 또한 반환값은 JSON.parse(...) 형태의 JavaScript 구문이므로, 브라우저에서 곧바로 유효한 JavaScript 객체로 변환됩니다:
<script>
var app = {{ Illuminate\Support\Js::from($array) }};
</script>최신 Laravel 프로젝트 스켈레톤에는 Js 파사드가 포함되어 있어 더 간결하게 사용할 수 있습니다:
<script>
var app = {{ Js::from($array) }};
</script>WARNING
Js::from은 이미 존재하는 변수를 JSON으로 렌더링할 때만 사용해야 합니다. Blade 템플릿은 정규식 기반으로 동작하므로, 복잡한 표현식을 전달하면 예기치 않은 오류가 발생할 수 있습니다.
`@verbatim` 디렉티브
템플릿의 넓은 범위에 걸쳐 JavaScript 변수를 출력해야 한다면, 매번 @를 붙이는 대신 해당 영역 전체를 @verbatim 디렉티브로 감싸면 편리합니다:
@verbatim
<div class="container">
안녕하세요, {{ name }}님.
</div>
@endverbatim@verbatim 블록 내부에서는 Blade가 {{ }}를 전혀 처리하지 않으므로, JavaScript 프레임워크가 그대로 사용할 수 있습니다.
Blade 디렉티브
Blade는 템플릿 상속과 데이터 출력 외에도, 조건문·반복문 같은 일반적인 PHP 제어 구조를 간결하게 표현할 수 있는 디렉티브를 제공합니다. PHP 문법과 거의 동일한 형태이므로 익숙하게 사용할 수 있습니다.
조건문 (If)
@if, @elseif, @else, @endif 디렉티브로 조건문을 작성합니다. PHP의 if 문과 동일하게 동작합니다.
@if (count($records) === 1)
레코드가 1개 있습니다!
@elseif (count($records) > 1)
레코드가 여러 개 있습니다!
@else
레코드가 없습니다!
@endif@unless는 조건이 거짓일 때 실행되는 편의 디렉티브입니다.
@unless (Auth::check())
로그인하지 않은 상태입니다.
@endunlessPHP의 isset()·empty()에 대응하는 디렉티브도 있습니다.
@isset($records)
// $records가 정의되어 있고 null이 아닌 경우...
@endisset
@empty($records)
// $records가 비어 있는 경우...
@endempty인증 디렉티브
현재 사용자가 인증 상태인지 게스트인지 빠르게 확인할 수 있습니다.
@auth
// 인증된 사용자...
@endauth
@guest
// 인증되지 않은 사용자(게스트)...
@endguest특정 인증 가드를 지정할 수도 있습니다.
@auth('admin')
// admin 가드로 인증된 사용자...
@endauth
@guest('admin')
// admin 가드로 인증되지 않은 사용자...
@endguest환경 디렉티브
@production 디렉티브로 현재 애플리케이션이 프로덕션 환경에서 실행 중인지 확인할 수 있습니다.
@production
// 프로덕션 환경에서만 표시할 내용...
@endproduction특정 환경을 지정하려면 @env 디렉티브를 사용합니다. 배열로 여러 환경을 동시에 지정하는 것도 가능합니다.
@env('staging')
// staging 환경에서 실행 중...
@endenv
@env(['staging', 'production'])
// staging 또는 production 환경에서 실행 중...
@endenv섹션 디렉티브
템플릿 상속에서 특정 섹션에 콘텐츠가 있는지 확인할 수 있습니다.
@hasSection('navigation')
<div class="pull-right">
@yield('navigation')
</div>
<div class="clearfix"></div>
@endif섹션에 콘텐츠가 없을 때 처리하려면 @sectionMissing을 사용합니다.
@sectionMissing('navigation')
<div class="pull-right">
@include('default-navigation')
</div>
@endif세션 디렉티브
@session 디렉티브는 특정 세션 값이 존재하는지 확인합니다. 값이 존재하면 블록 내부가 실행되며, $value 변수로 세션 값을 출력할 수 있습니다.
@session('status')
<div class="p-4 bg-green-100">
{{ $value }}
</div>
@endsessionSwitch 문
@switch, @case, @break, @default, @endswitch 디렉티브로 switch 문을 작성합니다.
@switch($i)
@case(1)
첫 번째 케이스...
@break
@case(2)
두 번째 케이스...
@break
@default
기본 케이스...
@endswitch반복문 (Loops)
Blade는 PHP의 반복문 구조에 대응하는 디렉티브를 제공합니다.
@for ($i = 0; $i < 10; $i++)
현재 값: {{ $i }}
@endfor
@foreach ($users as $user)
<p>사용자 ID: {{ $user->id }}</p>
@endforeach
@forelse ($users as $user)
<li>{{ $user->name }}</li>
@empty
<p>사용자가 없습니다.</p>
@endforelse
@while (true)
<p>무한 반복 중...</p>
@endwhileNOTE
@foreach 반복문 안에서는 $loop 변수를 사용해 현재 순서가 첫 번째인지 마지막인지 등 유용한 정보를 확인할 수 있습니다.
@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$loop 변수
@foreach 반복문 안에서는 자동으로 $loop 변수가 제공됩니다. 현재 인덱스, 첫/마지막 여부 등 반복 관련 정보를 담고 있습니다.
@foreach ($users as $user)
@if ($loop->first)
첫 번째 반복입니다.
@endif
@if ($loop->last)
마지막 반복입니다.
@endif
<p>사용자 ID: {{ $user->id }}</p>
@endforeach중첩 반복문에서는 $loop->parent로 바깥쪽 반복문의 $loop 변수에 접근할 수 있습니다.
@foreach ($users as $user)
@foreach ($user->posts as $post)
@if ($loop->parent->first)
부모 반복문의 첫 번째 반복입니다.
@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 | 중첩 반복문에서 부모 반복문의 $loop 변수 |
조건부 클래스 & 스타일
@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 디렉티브로 인라인 CSS 스타일을 조건부로 적용할 수 있습니다.
@php
$isActive = true;
@endphp
<span @style([
'background-color: red',
'font-weight: bold' => $isActive,
])></span>
{{-- 위 코드는 아래와 같이 렌더링됩니다 --}}
<span style="background-color: red; font-weight: bold;"></span>폼 속성 디렉티브
체크박스의 checked 상태를 조건부로 출력하려면 @checked 디렉티브를 사용합니다. 조건이 true이면 checked가 출력됩니다.
<input
type="checkbox"
name="active"
value="active"
@checked(old('active', $user->active))
/><select> 옵션의 선택 상태는 @selected로 처리합니다.
<select name="version">
@foreach ($product->versions as $version)
<option value="{{ $version }}" @selected(old('version') == $version)>
{{ $version }}
</option>
@endforeach
</select>@disabled는 버튼이나 입력 요소를 비활성화합니다.
<button type="submit" @disabled($errors->isNotEmpty())>제출</button>@readonly는 입력 요소를 읽기 전용으로 만듭니다.
<input
type="email"
name="email"
value="email@laravel.com"
@readonly($user->isNotAdmin())
/>@required는 입력 요소를 필수 항목으로 지정합니다.
<input
type="text"
name="title"
value="title"
@required($user->isAdmin())
/>서브뷰 포함하기
NOTE
@include를 자유롭게 사용할 수 있지만, Blade 컴포넌트는 데이터·속성 바인딩 등 더 많은 기능을 제공합니다. 가능하면 컴포넌트 사용을 고려해 보세요.
@include 디렉티브로 다른 Blade 뷰를 현재 뷰 안에 포함할 수 있습니다. 부모 뷰에서 사용 가능한 모든 변수는 포함된 뷰에도 그대로 전달됩니다.
<div>
@include('shared.errors')
<form>
<!-- 폼 내용 -->
</form>
</div>추가 데이터를 배열로 넘길 수도 있습니다.
@include('view.name', ['status' => 'complete'])존재하지 않는 뷰를 @include하면 Laravel이 오류를 발생시킵니다. 뷰 파일이 있을 수도 없을 수도 있다면 @includeIf를 사용하세요.
@includeIf('view.name', ['status' => 'complete'])불리언 조건에 따라 포함 여부를 결정하려면 @includeWhen·@includeUnless를 사용합니다.
@includeWhen($boolean, 'view.name', ['status' => 'complete'])
@includeUnless($boolean, 'view.name', ['status' => 'complete'])주어진 배열에서 실제로 존재하는 첫 번째 뷰를 포함하려면 @includeFirst를 사용합니다.
@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])WARNING
Blade 뷰 안에서 __DIR__·__FILE__ 상수는 사용하지 마세요. 이 상수들은 컴파일된 캐시 파일의 경로를 가리키기 때문에 의도한 결과가 나오지 않습니다.
컬렉션을 뷰로 렌더링하기
@each 디렉티브를 사용하면 반복문과 뷰 포함을 한 줄로 처리할 수 있습니다.
@each('view.name', $jobs, 'job')- 첫 번째 인수: 각 항목을 렌더링할 뷰
- 두 번째 인수: 반복할 배열 또는 컬렉션
- 세 번째 인수: 뷰 안에서 현재 항목을 가리킬 변수명
현재 항목의 배열 키는 뷰 안에서 $key 변수로 접근할 수 있습니다.
배열이 비어 있을 때 렌더링할 뷰는 네 번째 인수로 지정합니다.
@each('view.name', $jobs, 'job', 'view.empty')WARNING
@each로 렌더링된 뷰는 부모 뷰의 변수를 상속받지 않습니다. 자식 뷰에서 부모 변수가 필요하다면 @foreach와 @include를 조합해서 사용하세요.
`@once` 디렉티브
@once 디렉티브는 렌더링 사이클 전체에서 해당 블록을 딱 한 번만 실행합니다. 컴포넌트를 반복문 안에서 여러 번 렌더링할 때 JavaScript를 헤더에 한 번만 추가하고 싶을 때 유용합니다.
@once
@push('scripts')
<script>
// 커스텀 JavaScript...
</script>
@endpush
@endonce@once는 @push·@prepend와 함께 자주 쓰이기 때문에, 이를 결합한 @pushOnce·@prependOnce 디렉티브도 제공합니다.
@pushOnce('scripts')
<script>
// 커스텀 JavaScript...
</script>
@endPushOnce순수 PHP 코드
뷰 안에서 PHP 코드를 직접 실행해야 할 때는 @php 디렉티브를 사용합니다.
@php
$counter = 1;
@endphp클래스를 임포트하는 것이 목적이라면 @use 디렉티브가 더 간결합니다.
@use('App\Models\Flight')두 번째 인수로 별칭(alias)을 지정할 수도 있습니다.
@use('App\Models\Flight', 'FlightModel')주석
Blade 주석은 HTML 주석(<!-- -->)과 달리 렌더링된 HTML에 포함되지 않습니다.
{{-- 이 주석은 렌더링된 HTML에 나타나지 않습니다 --}}Blade 템플릿 — 컴포넌트
목차
컴포넌트
컴포넌트와 슬롯은 섹션·레이아웃·인클루드와 비슷한 역할을 하지만, 많은 개발자가 컴포넌트 방식이 더 직관적이라고 느낍니다. 컴포넌트를 작성하는 방법은 클래스 기반 컴포넌트와 익명 컴포넌트, 두 가지입니다.
클래스 기반 컴포넌트는 make:component Artisan 명령으로 생성합니다. 예시로 간단한 Alert 컴포넌트를 만들어 보겠습니다. 생성된 클래스는 app/View/Components 디렉터리에 위치합니다:
php artisan make:component Alert이 명령은 클래스 파일과 함께 resources/views/components 디렉터리에 Blade 뷰 템플릿도 자동으로 생성합니다. app/View/Components와 resources/views/components 두 디렉터리 안의 컴포넌트는 Laravel이 자동으로 탐색하므로, 별도 등록은 필요하지 않습니다.
서브디렉터리에도 컴포넌트를 생성할 수 있습니다:
php artisan make:component Forms/Input위 명령은 app/View/Components/Forms/Input.php와 resources/views/components/forms/input.blade.php를 함께 만들어 줍니다.
클래스 없이 Blade 파일만으로 이루어진 익명 컴포넌트를 만들려면 --view 플래그를 사용하세요:
php artisan make:component forms.input --view이 명령은 resources/views/components/forms/input.blade.php를 생성하며, <x-forms.input />으로 렌더링할 수 있습니다.
패키지 컴포넌트 수동 등록
일반 애플리케이션의 컴포넌트는 자동으로 탐색됩니다. 그러나 Blade 컴포넌트를 포함하는 패키지를 개발하는 경우에는 서비스 프로바이더의 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는 컴포넌트 이름을 PascalCase로 변환해 클래스를 자동으로 찾습니다. 서브디렉터리는 점(.) 표기법으로 지원합니다.
컴포넌트 렌더링
Blade 템플릿에서 컴포넌트를 출력하려면 x- 접두사에 컴포넌트 클래스명을 kebab-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 디렉터리 안에 있더라도, 파일명과 디렉터리명이 같으면 Laravel이 자동으로 루트 컴포넌트로 인식합니다. 따라서 <x-card.card> 대신 아래처럼 사용할 수 있습니다:
<x-card>
<x-card.header>...</x-card.header>
<x-card.body>...</x-card.body>
</x-card>데이터 전달
컴포넌트에 데이터를 전달할 때는 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');
}
}뷰 템플릿에서는 퍼블릭 프로퍼티를 그대로 출력할 수 있습니다:
<div class="alert alert-{{ $type }}">
{{ $message }}
</div>네이밍 컨벤션 (camelCase vs kebab-case)
생성자 인자는 camelCase로 선언하고, HTML 어트리뷰트에서는 kebab-case로 참조합니다:
/**
* 컴포넌트 인스턴스를 생성합니다.
*/
public function __construct(
public string $alertType,
) {}<x-alert alert-type="danger" />단축 어트리뷰트 문법
변수명과 어트리뷰트명이 같을 때는 단축 문법을 사용할 수 있습니다:
{{-- 단축 문법 --}}
<x-profile :$userId :$name />
{{-- 아래와 동일합니다 --}}
<x-profile :user-id="$userId" :name="$name" />어트리뷰트 렌더링 이스케이프
Alpine.js처럼 콜론(:) 접두사를 사용하는 JavaScript 프레임워크와 충돌할 경우, 이중 콜론(::)을 사용하면 Blade가 PHP 표현식으로 해석하지 않습니다:
<x-button ::class="{ danger: isDeleting }">
제출
</x-button>Blade는 이를 아래 HTML로 렌더링합니다:
<button :class="{ danger: isDeleting }">
제출
</button>컴포넌트 메서드 호출
컴포넌트 클래스의 public 메서드도 뷰 템플릿에서 변수처럼 호출할 수 있습니다. 예를 들어 isSelected 메서드가 있다면:
/**
* 주어진 옵션이 현재 선택된 옵션인지 확인합니다.
*/
public function isSelected(string $option): bool
{
return $option === $this->selected;
}<option {{ $isSelected($value) ? 'selected' : '' }} value="{{ $value }}">
{{ $label }}
</option>클래스 내에서 어트리뷰트·슬롯 접근
render 메서드에서 클로저를 반환하면, 컴포넌트 이름·어트리뷰트·슬롯 정보에 접근할 수 있습니다:
use Closure;
/**
* 컴포넌트를 표현하는 뷰를 반환합니다.
*/
public function render(): Closure
{
return function () {
return '<div {{ $attributes }}>컴포넌트 내용</div>';
};
}클로저는 $data 배열을 인자로 받을 수도 있습니다:
return function (array $data) {
// $data['componentName'];
// $data['attributes'];
// $data['slot'];
return '<div {{ $attributes }}>컴포넌트 내용</div>';
}WARNING
$data 배열의 값을 render 메서드가 반환하는 Blade 문자열에 직접 삽입하지 마세요. 악의적인 어트리뷰트 값을 통한 원격 코드 실행(RCE)이 발생할 수 있습니다.
componentName은 HTML 태그의 x- 뒤에 오는 이름입니다(<x-alert />이면 alert). 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, id)는 자동으로 **어트리뷰트 백(attribute bag)**에 담겨 $attributes 변수로 접근할 수 있습니다:
<x-alert type="error" :message="$message" class="mt-4"/><div {{ $attributes }}>
<!-- 컴포넌트 내용 -->
</div>WARNING
컴포넌트 태그 안에서 @env와 같은 지시어를 사용하는 것은 현재 지원되지 않습니다. 예를 들어 <x-alert :live="@env('production')"/>는 올바르게 컴파일되지 않습니다.
기본값 설정 및 어트리뷰트 병합
어트리뷰트에 기본값을 주거나 값을 병합할 때는 merge 메서드를 사용합니다. CSS 클래스에 특히 유용합니다:
<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
{{ $message }}
</div>사용 측에서 아래와 같이 호출하면:
<x-alert type="error" :message="$message" class="mb-4"/>최종 렌더링 결과는 다음과 같습니다:
<div class="alert alert-error mb-4">
<!-- $message 변수의 내용 -->
</div>조건부 클래스 병합
조건에 따라 클래스를 추가하려면 class 메서드를 사용합니다. 배열의 키가 클래스명, 값이 조건(boolean)입니다. 숫자 키인 경우 항상 포함됩니다:
<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 지시어를 사용하세요.
class 이외 어트리뷰트 병합
class가 아닌 어트리뷰트는 merge에 전달한 값이 기본값이 되며, 사용 측에서 같은 어트리뷰트를 전달하면 기본값을 덮어씁니다(class처럼 합쳐지지 않습니다):
{{-- 버튼 컴포넌트 템플릿 --}}
<button {{ $attributes->merge(['type' => 'button']) }}>
{{ $slot }}
</button>{{-- type을 직접 지정하면 기본값 'button'이 'submit'으로 교체됩니다 --}}
<x-button type="submit">
제출
</x-button>렌더링 결과:
<button type="submit">
제출
</button>기본값과 주입된 값을 앞에 이어붙이고 싶다면 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 메서드를 사용합니다:
@if ($attributes->has('class'))
<div>class 어트리뷰트가 존재합니다.</div>
@endif배열을 전달하면 모든 어트리뷰트가 존재하는지 확인합니다:
@if ($attributes->has(['name', 'class']))
<div>모든 어트리뷰트가 존재합니다.</div>
@endif하나라도 존재하는지 확인하려면 hasAny를 사용합니다:
@if ($attributes->hasAny(['href', ':href', 'v-bind:href']))
<div>어트리뷰트 중 하나 이상이 존재합니다.</div>
@endif특정 어트리뷰트 값을 가져오려면 get 메서드를 사용합니다:
{{ $attributes->get('class') }}예약 키워드
다음 키워드는 Blade 컴포넌트 내부에서 사용하므로, 컴포넌트 클래스의 public 프로퍼티나 메서드 이름으로 사용할 수 없습니다:
datarenderresolveViewshouldRenderviewwithAttributeswithName
슬롯
컴포넌트 내부에 외부 콘텐츠를 삽입할 때는 슬롯을 사용합니다. 슬롯 내용은 $slot 변수로 출력합니다:
<!-- /resources/views/components/alert.blade.php -->
<div class="alert alert-danger">
{{ $slot }}
</div>컴포넌트 사용 시 태그 사이에 내용을 넣으면 그것이 슬롯으로 전달됩니다:
<x-alert>
<strong>이런!</strong> 문제가 발생했습니다.
</x-alert>이름 있는 슬롯
여러 개의 슬롯이 필요할 때는 이름 있는 슬롯을 사용합니다. x-slot 태그 밖의 내용은 기본 $slot으로 전달됩니다:
<!-- /resources/views/components/alert.blade.php -->
<span class="alert-title">{{ $title }}</span>
<div class="alert alert-danger">
{{ $slot }}
</div><x-alert>
<x-slot:title>
서버 오류
</x-slot>
<strong>이런!</strong> 문제가 발생했습니다.
</x-alert>슬롯이 비어 있는지 확인하려면 isEmpty 메서드를 사용합니다:
<span class="alert-title">{{ $title }}</span>
<div class="alert alert-danger">
@if ($slot->isEmpty())
슬롯이 비어 있을 때 표시되는 기본 내용입니다.
@else
{{ $slot }}
@endif
</div>HTML 주석을 제외한 실제 콘텐츠가 있는지 확인하려면 hasActualContent를 사용합니다:
@if ($slot->hasActualContent())
슬롯에 실제 내용이 있습니다.
@endif스코프드 슬롯
Vue의 스코프드 슬롯처럼, 슬롯 내부에서 컴포넌트의 메서드나 프로퍼티에 접근하고 싶다면 $component 변수를 사용하세요:
<x-alert>
<x-slot:title>
{{ $component->formatAlert('서버 오류') }}
</x-slot>
<strong>이런!</strong> 문제가 발생했습니다.
</x-alert>슬롯 어트리뷰트
슬롯에도 CSS 클래스 등의 어트리뷰트를 지정할 수 있습니다:
<x-card class="shadow-sm">
<x-slot:heading class="font-bold">
제목
</x-slot>
본문 내용
<x-slot:footer class="text-sm">
하단 영역
</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 메서드에서 직접 HTML을 반환할 수 있습니다:
/**
* 컴포넌트를 표현하는 뷰를 반환합니다.
*/
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
아래 내용은 주로 Blade 컴포넌트를 포함하는 패키지 개발자를 위한 것입니다. 일반 애플리케이션 개발에는 해당하지 않을 수 있습니다.
패키지를 개발하거나 컴포넌트를 비표준 디렉터리에 배치하는 경우, 서비스 프로바이더의 boot 메서드에서 수동으로 등록해야 합니다:
use Illuminate\Support\Facades\Blade;
use VendorPackage\View\Components\AlertComponent;
/**
* 패키지 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Blade::component('package-alert', AlertComponent::class);
}등록 후 태그 별칭으로 렌더링합니다:
<x-package-alert/>패키지 컴포넌트 자동 로드
componentNamespace 메서드를 사용하면 네임스페이스 기반으로 컴포넌트를 자동 로드할 수 있습니다:
use Illuminate\Support\Facades\Blade;
public function boot(): void
{
Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}<x-nightshade::calendar />
<x-nightshade::color-picker />Blade는 컴포넌트 이름을 PascalCase로 변환해 대응하는 클래스를 자동으로 찾으며, 점(.) 표기법으로 서브디렉터리도 지원합니다.
익명 컴포넌트
익명 컴포넌트는 인라인 컴포넌트와 유사하게, 단일 파일로 컴포넌트를 관리하는 방식입니다. 차이점은 별도의 클래스 파일 없이 Blade 템플릿 파일 하나만으로 컴포넌트를 정의한다는 것입니다. 익명 컴포넌트를 만들려면 resources/views/components 디렉토리 안에 Blade 템플릿 파일을 두기만 하면 됩니다.
예를 들어 resources/views/components/alert.blade.php를 생성했다면, 다음과 같이 바로 렌더링할 수 있습니다:
<x-alert/>components 디렉토리 하위에 중첩된 컴포넌트는 .(점) 표기법으로 참조합니다. 예를 들어 resources/views/components/inputs/button.blade.php라면:
<x-inputs.button/>익명 인덱스 컴포넌트
하나의 컴포넌트가 여러 Blade 템플릿으로 구성될 경우, 관련 파일들을 하나의 디렉토리로 묶어 관리하고 싶을 수 있습니다. 예를 들어 "accordion" 컴포넌트가 다음과 같은 구조를 가진다고 가정해 봅시다:
/resources/views/components/accordion.blade.php
/resources/views/components/accordion/item.blade.php이 구조에서는 accordion 컴포넌트와 하위 item 컴포넌트를 다음과 같이 렌더링할 수 있습니다:
<x-accordion>
<x-accordion.item>
...
</x-accordion.item>
</x-accordion>그런데 이 방식에서는 accordion의 루트 템플릿(accordion.blade.php)이 components 디렉토리 바로 아래에 있어야 하고, 하위 파일들만 accordion/ 디렉토리에 위치하게 됩니다. 관련 파일들을 한 곳에 모아두지 못하는 셈이죠.
다행히 Blade는 이런 상황을 위한 방법을 제공합니다. 컴포넌트 디렉토리 내부에 디렉토리 이름과 동일한 파일을 두면, Blade가 이를 해당 컴포넌트의 "루트" 템플릿으로 인식합니다. 덕분에 위와 동일한 Blade 문법을 그대로 사용하면서, 디렉토리 구조는 다음처럼 깔끔하게 정리할 수 있습니다:
/resources/views/components/accordion/accordion.blade.php
/resources/views/components/accordion/item.blade.php데이터 프로퍼티 / 어트리뷰트
익명 컴포넌트에는 연결된 클래스가 없기 때문에, 컴포넌트에 전달된 값 중 어떤 것이 변수로 사용될 데이터이고 어떤 것이 어트리뷰트 백에 포함될 HTML 어트리뷰트인지 구분하는 방법이 필요합니다.
템플릿 상단에 @props 디렉티브를 사용하면 데이터 변수로 취급할 어트리뷰트를 명시할 수 있습니다. @props에 선언되지 않은 나머지 어트리뷰트는 자동으로 어트리뷰트 백에 포함됩니다. 기본값을 지정하려면 배열 키에 변수명, 값에 기본값을 작성하면 됩니다:
<!-- /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 디렉티브를 사용하면 자식 컴포넌트에서도 부모의 prop 값을 사용할 수 있습니다:
<!-- /resources/views/components/menu/item.blade.php -->
@aware(['color' => 'gray'])
<li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}>
{{ $slot }}
</li>WARNING
@aware 디렉티브는 부모 컴포넌트에 HTML 어트리뷰트로 명시적으로 전달된 데이터에만 접근할 수 있습니다. 부모의 @props에 선언된 기본값이라도, 실제로 외부에서 전달되지 않은 값은 @aware로 접근할 수 없습니다.
익명 컴포넌트 경로 등록
기본적으로 익명 컴포넌트는 resources/views/components 디렉토리에서 자동으로 탐색됩니다. 하지만 상황에 따라 추가 경로를 등록해야 할 수도 있습니다. 예를 들어 패키지나 모듈 구조에서 별도의 컴포넌트 디렉토리를 사용하는 경우가 그렇습니다.
Blade::anonymousComponentPath() 메서드를 사용하면 추가 경로를 등록할 수 있습니다. 첫 번째 인수로 경로를, 두 번째 인수로 선택적 네임스페이스(접두사)를 지정합니다. 이 메서드는 보통 서비스 프로바이더의 boot 메서드에서 호출합니다:
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Blade::anonymousComponentPath(__DIR__.'/../components');
}접두사 없이 경로를 등록하면, 해당 경로의 컴포넌트도 접두사 없이 바로 사용할 수 있습니다. 예를 들어 위 경로에 panel.blade.php가 있다면:
<x-panel />네임스페이스를 지정할 경우에는 두 번째 인수로 전달합니다:
Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard');네임스페이스가 지정된 컴포넌트는 렌더링 시 해당 네임스페이스를 접두사로 붙여 사용합니다:
<x-dashboard::panel />Blade 템플릿
레이아웃 구성
컴포넌트를 활용한 레이아웃
대부분의 웹 애플리케이션은 여러 페이지에 걸쳐 동일한 레이아웃을 공유합니다. 모든 뷰마다 전체 HTML 구조를 반복해서 작성해야 한다면 유지보수가 매우 번거로워질 것입니다. 다행히 레이아웃을 하나의 Blade 컴포넌트로 정의하고 애플리케이션 전체에서 재사용하는 방식으로 이 문제를 깔끔하게 해결할 수 있습니다.
레이아웃 컴포넌트 정의
예를 들어, 할 일 목록(todo) 애플리케이션을 만든다고 가정해 보겠습니다. 아래와 같이 layout 컴포넌트를 정의할 수 있습니다.
<!-- resources/views/components/layout.blade.php -->
<html>
<head>
<title>{{ $title ?? 'Todo Manager' }}</title>
</head>
<body>
<h1>할 일 목록</h1>
<hr/>
{{ $slot }}
</body>
</html>레이아웃 컴포넌트 적용
layout 컴포넌트를 정의했다면, 이제 이 컴포넌트를 사용하는 Blade 뷰를 만들 수 있습니다. 아래는 할 일 목록을 표시하는 간단한 뷰 예시입니다.
<!-- resources/views/tasks.blade.php -->
<x-layout>
@foreach ($tasks as $task)
<div>{{ $task }}</div>
@endforeach
</x-layout>컴포넌트 태그 안에 작성한 내용은 layout 컴포넌트의 기본 $slot 변수로 전달됩니다. 앞서 본 것처럼 layout 컴포넌트는 $title 슬롯도 지원하며, 따로 지정하지 않으면 기본 제목이 표시됩니다. 컴포넌트 문서에서 설명하는 슬롯 문법을 사용하면 뷰에서 제목을 직접 지정할 수 있습니다.
<!-- resources/views/tasks.blade.php -->
<x-layout>
<x-slot:title>
나의 할 일 목록
</x-slot>
@foreach ($tasks as $task)
<div>{{ $task }}</div>
@endforeach
</x-layout>레이아웃과 할 일 목록 뷰를 모두 정의했다면, 라우트에서 해당 뷰를 반환하기만 하면 됩니다.
use App\Models\Task;
Route::get('/tasks', function () {
return view('tasks', ['tasks' => Task::all()]);
});템플릿 상속을 활용한 레이아웃
레이아웃 정의
레이아웃을 구성하는 또 다른 방법은 템플릿 상속입니다. 이 방식은 컴포넌트가 도입되기 이전부터 사용해온 전통적인 레이아웃 구성 방식입니다.
먼저 간단한 예시를 살펴보겠습니다. 아래는 공통 페이지 레이아웃을 정의한 Blade 뷰입니다.
<!-- resources/views/layouts/app.blade.php -->
<html>
<head>
<title>앱 이름 - @yield('title')</title>
</head>
<body>
@section('sidebar')
기본 사이드바 내용입니다.
@show
<div class="container">
@yield('content')
</div>
</body>
</html>파일을 보면 일반적인 HTML 구조 안에 @section과 @yield 디렉티브가 사용된 것을 확인할 수 있습니다. @section은 콘텐츠 영역(섹션)을 정의하고, @yield는 해당 섹션의 내용을 실제로 출력하는 역할을 합니다.
NOTE
@section과 @yield의 관계를 간단히 정리하면, @section은 "이 영역에 들어갈 내용을 정의"하고, @yield는 "해당 영역의 내용을 여기에 출력"하는 역할입니다.
레이아웃 상속
자식 뷰에서는 @extends 디렉티브로 상속할 레이아웃을 지정하고, @section 디렉티브로 각 섹션에 들어갈 내용을 채워 넣습니다. 레이아웃에서 @yield로 출력 위치를 지정한 섹션에 자식 뷰의 내용이 삽입됩니다.
<!-- resources/views/child.blade.php -->
@extends('layouts.app')
@section('title', '페이지 제목')
@section('sidebar')
@@parent
<p>기본 사이드바에 추가되는 내용입니다.</p>
@endsection
@section('content')
<p>본문 내용이 들어갑니다.</p>
@endsection이 예시에서 sidebar 섹션은 @@parent 디렉티브를 사용합니다. 이렇게 하면 레이아웃에 정의된 기본 사이드바 내용을 덮어쓰지 않고, 그 뒤에 내용을 추가합니다. @@parent는 뷰가 렌더링될 때 레이아웃의 해당 섹션 내용으로 대체됩니다.
NOTE
위 예시에서 sidebar 섹션이 @show가 아닌 @endsection으로 끝나는 점에 주목하세요. @endsection은 섹션을 정의만 하는 반면, 레이아웃에서 사용하는 @show는 섹션을 정의하는 동시에 즉시 출력합니다. 자식 뷰에서는 @endsection을 사용하는 것이 올바른 방법입니다.
@yield 디렉티브는 두 번째 인자로 기본값을 받을 수 있습니다. 해당 섹션이 정의되지 않았을 때 이 기본값이 출력됩니다.
@yield('content', '기본 콘텐츠')폼
CSRF 필드
애플리케이션에서 HTML 폼을 정의할 때는 반드시 숨겨진 CSRF 토큰 필드를 포함해야 합니다. 그래야 CSRF 보호 미들웨어가 요청을 올바르게 검증할 수 있습니다. @csrf Blade 디렉티브를 사용하면 토큰 필드를 간단히 생성할 수 있습니다:
<form method="POST" action="/profile">
@csrf
...
</form>HTTP 메서드 필드
HTML 폼은 기본적으로 PUT, PATCH, DELETE 요청을 직접 전송할 수 없습니다. 이런 HTTP 메서드를 사용하려면 숨겨진 _method 필드를 추가해 메서드를 흉내 내야 합니다. @method Blade 디렉티브를 사용하면 이 필드를 자동으로 생성할 수 있습니다:
<form action="/foo/bar" method="POST">
@method('PUT')
...
</form>유효성 검사 오류
@error 디렉티브를 사용하면 특정 필드에 유효성 검사 오류 메시지가 있는지 빠르게 확인할 수 있습니다. @error 블록 안에서는 $message 변수를 출력해 오류 메시지를 표시할 수 있습니다:
<!-- /resources/views/post/create.blade.php -->
<label for="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">이메일 주소</label>
<input
id="email"
type="email"
class="@error('email') is-invalid @else is-valid @enderror"
/>한 페이지에 여러 폼이 있는 경우, @error 디렉티브의 두 번째 인자로 특정 오류 백(error bag)의 이름을 전달해 해당 폼의 오류 메시지만 조회할 수 있습니다:
<!-- /resources/views/auth.blade.php -->
<label for="email">이메일 주소</label>
<input
id="email"
type="email"
class="@error('email', 'login') is-invalid @enderror"
/>
@error('email', 'login')
<div class="alert alert-danger">{{ $message }}</div>
@enderrorNOTE
로그인 폼과 회원가입 폼처럼 같은 페이지에 여러 폼이 존재할 때는 각 폼마다 별도의 오류 백을 지정하는 것이 좋습니다. 그렇지 않으면 서로 다른 폼의 오류 메시지가 뒤섞일 수 있습니다.
스택(Stacks)
Blade의 스택 기능을 사용하면 이름이 지정된 스택에 콘텐츠를 추가한 뒤, 다른 뷰나 레이아웃의 원하는 위치에서 한꺼번에 렌더링할 수 있습니다. 자식 뷰에서 필요한 JavaScript 파일을 레이아웃의 <head>에 모아서 출력할 때 특히 유용합니다.
@push('scripts')
<script src="/example.js"></script>
@endpush특정 조건이 참일 때만 스택에 추가하고 싶다면 @pushIf 디렉티브를 사용하세요.
@pushIf($shouldPush, 'scripts')
<script src="/example.js"></script>
@endPushIf스택에는 필요한 만큼 여러 번 내용을 추가할 수 있습니다. 스택에 쌓인 전체 내용을 출력하려면 레이아웃에서 @stack 디렉티브에 스택 이름을 전달합니다.
<head>
<!-- Head Contents -->
@stack('scripts')
</head>NOTE
@push로 추가한 내용은 기본적으로 스택 끝에 순서대로 쌓입니다. 레이아웃을 설계할 때 @stack의 위치만 잡아두면, 자식 뷰에서 자유롭게 스크립트를 추가할 수 있습니다.
스택의 맨 앞에 내용을 추가하고 싶다면 @prepend 디렉티브를 사용하세요.
@push('scripts')
{{-- 두 번째로 출력됩니다 --}}
@endpush
{{-- 이후 다른 뷰에서... --}}
@prepend('scripts')
{{-- 첫 번째로 출력됩니다 --}}
@endprepend@prepend를 사용하면 나중에 추가하더라도 스택의 가장 앞에 삽입되므로, 다른 스크립트보다 먼저 로드되어야 하는 라이브러리(예: jQuery)를 처리할 때 활용할 수 있습니다.
서비스 주입
@inject 디렉티브를 사용하면 Laravel 서비스 컨테이너에서 서비스를 직접 가져올 수 있습니다. 첫 번째 인수는 서비스를 담을 변수명이고, 두 번째 인수는 컨테이너에서 resolve할 클래스 또는 인터페이스명입니다.
@inject('metrics', 'App\Services\MetricsService')
<div>
월간 매출: {{ $metrics->monthlyRevenue() }}
</div>NOTE
@inject는 뷰 파일 안에서 서비스 로직을 직접 호출해야 할 때 유용하지만, 가능하면 컨트롤러나 뷰 컴포저에서 데이터를 준비해 뷰에 전달하는 방식을 권장합니다. 뷰는 표현에 집중하는 것이 유지보수에 유리합니다.
인라인 Blade 템플릿 렌더링
인라인 Blade 템플릿 렌더링
Blade 템플릿 문자열을 직접 HTML로 변환해야 할 때는 Blade 파사드의 render 메서드를 사용합니다. 이 메서드는 Blade 템플릿 문자열과, 선택적으로 템플릿에 전달할 데이터 배열을 인수로 받습니다.
use Illuminate\Support\Facades\Blade;
return Blade::render('안녕하세요, {{ $name }}님!', ['name' => '홍길동']);인라인 Blade 템플릿은 storage/framework/views 디렉터리에 임시 파일로 저장된 뒤 렌더링됩니다. 렌더링 후 이 임시 파일을 자동으로 삭제하려면 deleteCachedView 인수를 true로 지정하세요.
return Blade::render(
'안녕하세요, {{ $name }}님!',
['name' => '홍길동'],
deleteCachedView: true
);NOTE
임시 파일이 누적되는 것이 걱정된다면 deleteCachedView: true 옵션을 활용하세요. 다만, 동일한 템플릿을 반복 렌더링하는 경우에는 매번 캐시를 재생성하므로 성능에 영향을 줄 수 있습니다.
Blade 프래그먼트 렌더링
Turbo나 htmx 같은 프론트엔드 라이브러리를 사용할 때, HTTP 응답으로 Blade 템플릿의 일부분만 반환해야 하는 경우가 있습니다. Blade의 "프래그먼트(fragment)" 기능이 바로 이런 상황을 위한 것입니다.
NOTE
htmx를 사용한다면 특히 유용합니다. htmx는 서버에서 HTML 조각만 받아 특정 DOM 영역을 교체하는 방식으로 동작하기 때문에, 전체 페이지를 다시 렌더링할 필요가 없습니다.
사용 방법은 간단합니다. 템플릿에서 반환할 부분을 @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 메서드를 사용합니다. 조건이 false이면 뷰 전체가 반환됩니다.
return view('dashboard', ['users' => $users])
->fragmentIf($request->hasHeader('HX-Request'), 'user-list');여러 프래그먼트를 한 번에 반환하려면 fragments 또는 fragmentsIf 메서드를 사용합니다. 여러 프래그먼트는 순서대로 이어붙여져 반환됩니다.
view('dashboard', ['users' => $users])
->fragments(['user-list', 'comment-list']);
view('dashboard', ['users' => $users])
->fragmentsIf(
$request->hasHeader('HX-Request'),
['user-list', 'comment-list']
);Blade 확장
커스텀 디렉티브
directive 메서드를 사용하면 나만의 Blade 디렉티브를 정의할 수 있습니다. Blade 컴파일러가 해당 디렉티브를 만나면, 디렉티브에 전달된 표현식을 인자로 콜백을 호출합니다.
아래 예시는 DateTime 인스턴스인 $var를 받아 날짜를 포맷하는 @datetime($var) 디렉티브를 정의합니다:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* 애플리케이션 서비스를 등록합니다.
*/
public function register(): void
{
// ...
}
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Blade::directive('datetime', function (string $expression) {
return "<?php echo ($expression)->format('Y/m/d H:i'); ?>";
});
}
}디렉티브에 전달된 표현식에 format 메서드를 체이닝하는 방식입니다. 따라서 위 예시에서 최종적으로 생성되는 PHP 코드는 다음과 같습니다:
<?php echo ($var)->format('Y/m/d H:i'); ?>WARNING
Blade 디렉티브의 로직을 수정한 후에는 캐시된 Blade 뷰를 반드시 삭제해야 합니다. view:clear Artisan 명령어로 캐시된 뷰를 삭제할 수 있습니다.
커스텀 Echo 핸들러
Blade 템플릿에서 {{ $object }}처럼 객체를 출력하면 PHP의 __toString 매직 메서드가 호출됩니다. 그런데 서드파티 라이브러리의 클래스처럼 __toString을 직접 제어할 수 없는 경우도 있습니다.
이런 상황에서는 Blade의 stringable 메서드를 사용해 특정 타입의 객체에 대한 커스텀 Echo 핸들러를 등록할 수 있습니다. stringable은 클로저를 인자로 받으며, 클로저의 타입 힌트로 렌더링할 객체 타입을 지정합니다. 보통 AppServiceProvider의 boot 메서드 안에서 등록합니다:
use Illuminate\Support\Facades\Blade;
use Money\Money;
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Blade::stringable(function (Money $money) {
return $money->formatTo('ko_KR');
});
}핸들러를 등록하고 나면, Blade 템플릿에서 해당 객체를 그냥 출력하기만 하면 됩니다:
금액: {{ $money }}커스텀 조건 디렉티브
단순한 조건 분기를 위해 커스텀 디렉티브를 직접 작성하면 코드가 불필요하게 복잡해질 수 있습니다. 이를 위해 Blade는 Blade::if 메서드를 제공합니다. 클로저를 이용해 커스텀 조건 디렉티브를 간단하게 정의할 수 있습니다.
예를 들어, 애플리케이션에 설정된 기본 파일 디스크를 확인하는 조건 디렉티브를 AppServiceProvider의 boot 메서드에 정의해 봅니다:
use Illuminate\Support\Facades\Blade;
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Blade::if('disk', function (string $value) {
return config('filesystems.default') === $value;
});
}커스텀 조건 디렉티브를 정의하고 나면, 템플릿에서 다음과 같이 사용할 수 있습니다:
@disk('local')
{{-- 애플리케이션이 로컬 디스크를 사용 중입니다... --}}
@elsedisk('s3')
{{-- 애플리케이션이 S3 디스크를 사용 중입니다... --}}
@else
{{-- 애플리케이션이 다른 디스크를 사용 중입니다... --}}
@enddisk
@unlessdisk('local')
{{-- 애플리케이션이 로컬 디스크를 사용하지 않습니다... --}}
@enddisk