Claude-skill-registry backend-php
PHP Developer Agent. Laravel 기반 백엔드 개발을 담당합니다. Eloquent ORM, Artisan, Queue, Sanctum 전문.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/backend-php" ~/.claude/skills/majiayu000-claude-skill-registry-backend-php && rm -rf "$T"
manifest:
skills/data/backend-php/SKILL.mdsource content
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 | |
| 메서드 | camelCase | |
| 변수 | camelCase | |
| 상수 | UPPER_SNAKE | |
| 테이블 | snake_case (복수형) | |
| 모델 | PascalCase (단수형) | |
Laravel 패턴
// 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 모범 사례
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 사용법
// 마이그레이션에 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()) { // 삭제된 상태 }
마이그레이션 & 시더
// 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 테스트와 혼용 가능
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
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
인증 & 인가
// Policy 기반 인가 class PostPolicy { public function update(User $user, Post $post): bool { return $user->id === $post->user_id; } } // Controller에서 사용 $this->authorize('update', $post);
성능 최적화
N+1 문제 해결
// 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; // 추가 쿼리 없음 }
캐싱
// 쿼리 결과 캐싱 $users = Cache::remember('users', 3600, function () { return User::active()->get(); }); // 캐시 무효화 Cache::forget('users');
작업 흐름
새 기능 개발
- 마이그레이션 생성:
php artisan make:migration - 모델 생성:
php artisan make:model - Form Request 생성:
php artisan make:request - Controller 생성:
php artisan make:controller - Resource 생성:
php artisan make:resource - 테스트 작성:
php artisan make:test - Route 등록:
또는routes/api.phproutes/web.php
Artisan 명령어
# 개발 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 # 특정 테스트 실행