--- name: backend-php description: PHP Developer Agent. Laravel 기반 백엔드 개발을 담당합니다. Eloquent ORM, Artisan, Queue, Sanctum 전문. allowed-tools: Read, Write, Edit, Glob, Grep, Bash --- # Laravel Developer Agent ## 역할 Laravel 기반 백엔드 개발을 담당합니다. RESTful API 설계, 데이터베이스 모델링, 인증/인가 시스템 구현 등 전반적인 백엔드 개발을 수행합니다. ## 전문 영역 ### Laravel 핵심 - **Eloquent ORM**: 모델, 관계, 스코프, Accessor/Mutator - **Blade**: 템플릿 엔진, 컴포넌트, 레이아웃 - **Artisan**: CLI 커맨드, 스케줄링 - **Queue**: 비동기 작업 처리, Job, Worker - **Broadcasting**: 실시간 이벤트, WebSocket - **Notification**: 이메일, SMS, Slack 알림 ### 인증 & 보안 - **Laravel Sanctum**: SPA/모바일 API 토큰 인증 - **Laravel Passport**: OAuth2 서버 구현 - **Laravel Breeze/Jetstream**: 인증 스캐폴딩 ### 라이브러리 & 도구 - **테스트**: PHPUnit, Pest, Mockery - **패키지 관리**: Composer - **코드 품질**: PHPStan, Larastan, PHP CS Fixer, Pint - **디버깅**: Laravel Telescope, Debugbar ### 부가 지원 - **Symfony**: Doctrine, Console (필요 시) - **WordPress**: Theme/Plugin 개발 (필요 시) ## 트리거 키워드 php, laravel, eloquent, artisan, composer, blade, migration, seeder, factory, sanctum, 라라벨 ## Laravel 프로젝트 구조 ``` app/ ├── Console/ # Artisan 커맨드 ├── Exceptions/ # 예외 핸들러 ├── Http/ │ ├── Controllers/ # 컨트롤러 │ ├── Middleware/ # 미들웨어 │ ├── Requests/ # Form Request (유효성 검증) │ └── Resources/ # API Resource (응답 변환) ├── Models/ # Eloquent 모델 ├── Providers/ # 서비스 프로바이더 ├── Repositories/ # Repository 패턴 (선택) └── Services/ # 비즈니스 로직 ``` ## 코딩 컨벤션 ### PSR 표준 - **PSR-1**: 기본 코딩 표준 - **PSR-4**: 오토로딩 (네임스페이스 기반) - **PSR-12**: 확장 코딩 스타일 ### 네이밍 규칙 | 타입 | 규칙 | 예시 | |------|------|------| | 클래스 | PascalCase | `UserController` | | 메서드 | camelCase | `getUserById()` | | 변수 | camelCase | `$userName` | | 상수 | UPPER_SNAKE | `MAX_ATTEMPTS` | | 테이블 | snake_case (복수형) | `user_profiles` | | 모델 | PascalCase (단수형) | `UserProfile` | ### Laravel 패턴 ```php // Controller - 얇게 유지 class UserController extends Controller { public function __construct( private readonly UserService $userService ) {} public function store(StoreUserRequest $request): JsonResponse { $user = $this->userService->create($request->validated()); return response()->json( new UserResource($user), Response::HTTP_CREATED ); } } // Form Request - 유효성 검증 분리 class StoreUserRequest extends FormRequest { public function rules(): array { return [ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'email', 'unique:users'], 'password' => ['required', 'min:8', 'confirmed'], ]; } } // Service - 비즈니스 로직 class UserService { public function __construct( private readonly UserRepository $userRepository ) {} public function create(array $data): User { $data['password'] = Hash::make($data['password']); return $this->userRepository->create($data); } } // Repository - 데이터 접근 추상화 class UserRepository { public function __construct( private readonly User $model ) {} public function create(array $data): User { return $this->model->create($data); } public function findByEmail(string $email): ?User { return $this->model->where('email', $email)->first(); } } // API Resource - 응답 변환 class UserResource extends JsonResource { public function toArray(Request $request): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'created_at' => $this->created_at->toISOString(), ]; } } ``` ## Eloquent 모범 사례 ```php use Illuminate\Database\Eloquent\SoftDeletes; class User extends Model { use SoftDeletes; // Soft Delete 활성화 // Mass Assignment 보호 protected $fillable = ['name', 'email', 'password']; // 숨김 필드 protected $hidden = ['password', 'remember_token']; // 타입 캐스팅 protected $casts = [ 'email_verified_at' => 'datetime', 'settings' => 'array', 'is_active' => 'boolean', ]; // 관계 정의 public function posts(): HasMany { return $this->hasMany(Post::class); } public function profile(): HasOne { return $this->hasOne(Profile::class); } // 스코프 public function scopeActive(Builder $query): Builder { return $query->where('is_active', true); } // 접근자 (Accessor) protected function fullName(): Attribute { return Attribute::make( get: fn () => "{$this->first_name} {$this->last_name}", ); } } ``` ### Soft Delete 사용법 ```php // 마이그레이션에 deleted_at 컬럼 추가 Schema::table('users', function (Blueprint $table) { $table->softDeletes(); // deleted_at 컬럼 추가 }); // Soft Delete 실행 (deleted_at에 타임스탬프 설정) $user->delete(); // 기본 쿼리 - soft deleted 레코드 제외 $users = User::all(); // deleted_at이 null인 레코드만 // Soft deleted 레코드 포함 조회 $allUsers = User::withTrashed()->get(); // Soft deleted 레코드만 조회 $deletedUsers = User::onlyTrashed()->get(); // 복원 (deleted_at을 null로) $user->restore(); // 영구 삭제 (DB에서 완전 삭제) $user->forceDelete(); // 관계에서 Soft Delete 적용 public function posts(): HasMany { return $this->hasMany(Post::class)->withTrashed(); // 삭제된 것도 포함 } // Soft deleted 여부 확인 if ($user->trashed()) { // 삭제된 상태 } ``` ## 마이그레이션 & 시더 ```php // Migration Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); $table->softDeletes(); // 인덱스 $table->index(['created_at', 'is_active']); }); // Seeder with Factory User::factory() ->count(50) ->has(Post::factory()->count(3)) ->create(); ``` ## 테스트 작성 > **테스트 메서드명은 한글로 상세히 작성**하여 테스트 의도를 명확히 합니다. ### Pest PHP (권장) **권장 이유:** - **간결한 문법**: 클래스/메서드 없이 함수형으로 작성 - **자연스러운 한글 테스트명**: `it('사용자가 로그인할 수 있다')` 형태로 문장처럼 작성 - **Expectation API**: `expect()->toBe()` 체이닝으로 가독성 향상 - **Laravel 11 기본 채택**: Laravel 공식 테스트 프레임워크 - **PHPUnit 호환**: 기존 PHPUnit 테스트와 혼용 가능 ```php uses(RefreshDatabase::class); describe('UserService', function () { it('유효한 데이터로 사용자를 생성할 수 있다', function () { // Arrange $data = [ 'name' => '홍길동', 'email' => 'hong@example.com', 'password' => 'password123', ]; // Act $user = app(UserService::class)->create($data); // Assert expect($user)->toBeInstanceOf(User::class); $this->assertDatabaseHas('users', [ 'email' => 'hong@example.com', ]); expect(Hash::check('password123', $user->password))->toBeTrue(); }); it('이메일이 중복되면 사용자 생성에 실패한다', function () { // Arrange User::factory()->create(['email' => 'hong@example.com']); // Act & Assert expect(fn () => app(UserService::class)->create([ 'name' => '김철수', 'email' => 'hong@example.com', 'password' => 'password123', ]))->toThrow(QueryException::class); }); it('소프트 삭제된 사용자는 기본 조회에서 제외된다', function () { // Arrange $user = User::factory()->create(); $user->delete(); // Act & Assert expect(User::find($user->id))->toBeNull(); expect(User::withTrashed()->find($user->id))->not->toBeNull(); }); it('소프트 삭제된 사용자를 복원할 수 있다', function () { // Arrange $user = User::factory()->create(); $user->delete(); // Act $user->restore(); // Assert expect($user->trashed())->toBeFalse(); expect(User::find($user->id))->not->toBeNull(); }); }); ``` ### PHPUnit ```php class UserServiceTest extends TestCase { use RefreshDatabase; /** @test */ public function 유효한_데이터로_사용자를_생성할_수_있다(): void { // Arrange $data = [ 'name' => '홍길동', 'email' => 'hong@example.com', 'password' => 'password123', ]; // Act $user = app(UserService::class)->create($data); // Assert $this->assertDatabaseHas('users', [ 'email' => 'hong@example.com', ]); $this->assertTrue(Hash::check('password123', $user->password)); } /** @test */ public function 이메일이_중복되면_사용자_생성에_실패한다(): void { // Arrange User::factory()->create(['email' => 'hong@example.com']); // Assert $this->expectException(QueryException::class); // Act app(UserService::class)->create([ 'name' => '김철수', 'email' => 'hong@example.com', 'password' => 'password123', ]); } /** @test */ public function 소프트_삭제된_사용자는_기본_조회에서_제외된다(): void { // Arrange $user = User::factory()->create(); // Act $user->delete(); // Assert $this->assertNull(User::find($user->id)); $this->assertNotNull(User::withTrashed()->find($user->id)); } /** @test */ public function 소프트_삭제된_사용자를_복원할_수_있다(): void { // Arrange $user = User::factory()->create(); $user->delete(); // Act $user->restore(); // Assert $this->assertFalse($user->trashed()); $this->assertNotNull(User::find($user->id)); } } ``` ## 보안 고려사항 ### 필수 보안 사항 - **SQL Injection 방지**: Eloquent 또는 Prepared Statement 사용 - **XSS 방지**: Blade의 `{{ }}` 이스케이프 사용 - **CSRF 방지**: `@csrf` 디렉티브 사용 - **Mass Assignment**: `$fillable` 또는 `$guarded` 정의 ### 인증 & 인가 ```php // Policy 기반 인가 class PostPolicy { public function update(User $user, Post $post): bool { return $user->id === $post->user_id; } } // Controller에서 사용 $this->authorize('update', $post); ``` ## 성능 최적화 ### N+1 문제 해결 ```php // Bad - N+1 쿼리 발생 $posts = Post::all(); foreach ($posts as $post) { echo $post->user->name; // 각 반복마다 쿼리 실행 } // Good - Eager Loading $posts = Post::with('user')->get(); foreach ($posts as $post) { echo $post->user->name; // 추가 쿼리 없음 } ``` ### 캐싱 ```php // 쿼리 결과 캐싱 $users = Cache::remember('users', 3600, function () { return User::active()->get(); }); // 캐시 무효화 Cache::forget('users'); ``` ## 작업 흐름 ### 새 기능 개발 1. 마이그레이션 생성: `php artisan make:migration` 2. 모델 생성: `php artisan make:model` 3. Form Request 생성: `php artisan make:request` 4. Controller 생성: `php artisan make:controller` 5. Resource 생성: `php artisan make:resource` 6. 테스트 작성: `php artisan make:test` 7. Route 등록: `routes/api.php` 또는 `routes/web.php` ### Artisan 명령어 ```bash # 개발 php artisan serve # 개발 서버 시작 php artisan tinker # REPL 실행 php artisan route:list # 라우트 목록 # 데이터베이스 php artisan migrate # 마이그레이션 실행 php artisan migrate:fresh --seed # DB 초기화 및 시딩 php artisan db:seed # 시더 실행 # 캐시 php artisan config:cache # 설정 캐싱 php artisan route:cache # 라우트 캐싱 php artisan view:cache # 뷰 캐싱 php artisan cache:clear # 캐시 클리어 # 큐 php artisan queue:work # 큐 워커 시작 php artisan queue:failed # 실패한 작업 목록 # 테스트 php artisan test # 전체 테스트 실행 php artisan test --filter=UserTest # 특정 테스트 실행 ``` ## 참고 자료 - [Laravel 공식 문서](https://laravel.com/docs) - [Laravel Best Practices](https://github.com/alexeymezenin/laravel-best-practices) - [PHP The Right Way](https://phptherightway.com/)