<!-- skill: Laravel PHP | version: 1.0.0 | description: Laravel routing, Blade, Eloquent, auth, queues and VPS deploys | category: Web -->

## Instructions
- Require PHP 8.2+ + Laravel 11/12; keep `composer.json` php constraint honest, run `composer validate` after edits, commit `composer.lock`.
- Routes in `routes/web.php` (pages) + `routes/api.php` (stateless, `/api` prefix); name every route, group with middleware (`auth`, `verified`, `can:`).
- Controllers thin, Form Requests fat: validate in `app/Http/Requests/*` with `authorize()` + `rules()` + messages; never validate inline in controllers.
- Blade: layout (`layouts/app.blade.php`) with `@section/@yield`, components (`<x-button>`), `@csrf` on all POST forms, escape with `{{ }}` (raw `{!! !!}` only for trusted HTML).
- Eloquent: migrations for every schema change (never edit shipped migrations — add new ones), relationships + eager loading (`with()`) to kill N+1, use factories/seeders.
- Auth: start with Breeze (simple Blade/Livewire) or Fortify (headless); hash via `Hash::make`, enforce `email_verified_at`, throttle login attempts.
- Config: env-driven (`config/*.php` reads `env()`), cache in prod (`config:cache route:cache view:cache`), never call `env()` outside config files.
- Queues for slow work (mail, images, webhooks): `ShouldQueue` jobs, `failed_jobs` table, supervisor running `queue:work --tries=3`, `php artisan queue:failed` triage.
- Storage/mail: `storage:link` for public disk, queued `Mailable`s, log mail locally via `MAIL_MAILER=log` in dev; test with Mailtrap/Mailpit.
- Secure defaults: `APP_DEBUG=false` in prod, mass-assignment `$fillable`, policy checks (`@can`/`authorize`), rate-limit API (`throttle:60,1`).
- Deploy shared vs VPS: shared → upload `public/` to `public_html`, app above root, cron for scheduler; VPS → Nginx+PHP-FPM, deploy script with `migrate --force`.
- Observe: `php artisan about`, Telescope (dev) / Pulse (prod), structured logs to `storage/logs/laravel.log`, queue + scheduler monitored by supervisor/cron.

## Examples
- Blog CRUD: resource route + controller + Form Request validation + Blade index/show/create/edit + Eloquent policy (`PostPolicy`) for author-only edits.
- Auth + roles: Breeze install, `is_admin` gate, admin-only `Route::middleware(['auth','can:admin'])` dashboard with user table.
- Queued welcome flow: `Registered` event → `SendWelcomeMail` job (ShouldQueue) → Mailable with Markdown template → failed-job retry.

## Workflows
1. Scaffold: `composer create-project laravel/laravel app`, set `.env` (DB/mail), `php artisan key:generate migrate`.
2. Model slice: `php artisan make:model Post -mcr` (migration+controller+request), define schema, run `migrate`, seed with factory.
3. Build UI/API: routes → Form Request → controller → Blade view/API resource; test happy + 422 + 403 paths.
4. Add auth/queues: `composer require laravel/breeze --dev` + install, queue table + supervisor, scheduler cron `* * * * * php artisan schedule:run`.
5. Deploy: `composer install --no-dev -o`, `npm run build`, `php artisan migrate --force + config:cache + route:cache`, verify + rollback plan (`migrate:rollback --step=1`).

## Templates
```php
// routes/web.php
use App\Http\Controllers\PostController;
Route::get('/', fn () => view('welcome'))->name('home');
Route::resource('posts', PostController::class);
Route::middleware(['auth'])->group(function () {
    Route::get('/dashboard', fn () => view('dashboard'))->name('dashboard');
});
```
```php
// database/migrations/2026_01_01_000001_create_posts_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
    public function up(): void {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->string('title');
            $table->string('slug')->unique();
            $table->text('body');
            $table->timestamp('published_at')->nullable();
            $table->timestamps();
        });
    }
    public function down(): void { Schema::dropIfExists('posts'); }
};
```
```php
// app/Http/Requests/StorePostRequest.php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest {
    public function authorize(): bool { return $this->user() !== null; }
    public function rules(): array {
        return ['title' => 'required|string|max:255', 'body' => 'required|string|min:10'];
    }
}
```

## Tools
- SARAH `shell_run`: `php artisan migrate/fresh --seed`, `php artisan route:list`, `php artisan tinker` for Eloquent checks, `composer test` (Pest/PHPUnit).
- SARAH `file_write`: controllers in `app/Http/Controllers/`, views in `resources/views/`, never edit `vendor/` or cached `bootstrap/cache/*`.
- SARAH preview: `php artisan serve` (port 8000) for Blade; test APIs with curl + `Accept: application/json` to see validation JSON.

## Advanced
- N+1 killer: always `Post::with('user','tags')->paginate(15)`; confirm with Telescope queries tab or `DB::enableQueryLog()`.
- Troubleshooting: blank page → `storage/logs/laravel.log` + perms on `storage/bootstrap/cache` (775, www-data); `419 expired` → missing `@csrf`; `SQLSTATE` on migrate → wrong `.env` DB creds; queues stuck → supervisor down or missing `--tries`/failed table.
- Scheduler/queues on VPS: supervisor conf for `queue:work`, cron `* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1`.
- Pro tips: API resources (`PostResource`) for stable JSON, `php artisan model:show Post` to audit schema, `Str::slug()` for slugs, `composer audit` for CVE check.

## Documentation
- Laravel docs: https://laravel.com/docs
- Blade templates: https://laravel.com/docs/blade
- Eloquent ORM: https://laravel.com/docs/eloquent
