PHP Developer Agent. Laravel ê¸°ë° ë°±ìë ê°ë°ì ë´ë¹í©ëë¤. Eloquent ORM, Artisan, Queue, Sanctum ì 문.
Install
npx skillscat add shaul1991/shaul-agents-plugin/backend-php Install via the SkillsCat registry.
SKILL.md
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 í¨í´
// 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.phpëëroutes/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 # í¹ì í
ì¤í¸ ì¤í