internal/skills/content/laravel/SKILL.md
Laravel 11+ framework guardrails, patterns, and best practices for AI-assisted development. Use when working with Laravel projects, or when the user mentions Laravel. Provides Eloquent ORM, Blade templates, routing, middleware, and queue guidelines.
npx skillsauth add ar4mirez/samuel laravelInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Applies to: Laravel 11+, PHP 8.2+, Full-Stack Web Applications, APIs, SaaS
composer install --no-dev for productioncomposer.lockdeclare(strict_types=1); at the top of every PHP filereadonly properties where values should not change after constructionDB::raw() without parameter binding.env files; use .env.example as template$fillable (whitelist) on models, never $guarded = []{{ }} (not {!! !!} unless explicitly safe)->with('relation') or ->load('relation')->paginate(15) or ->cursorPaginate(15)Cache::remember()WHERE, ORDER BY, JOINmyapp/
├── app/
│ ├── Console/Commands/ # Artisan commands
│ ├── Enums/ # PHP enums (roles, statuses)
│ ├── Events/ # Domain events
│ ├── Http/
│ │ ├── Controllers/ # Thin controllers (delegate to services)
│ │ ├── Middleware/ # HTTP middleware
│ │ └── Requests/ # Form Request validation
│ ├── Jobs/ # Queued jobs
│ ├── Listeners/ # Event listeners
│ ├── Models/ # Eloquent models
│ ├── Policies/ # Authorization policies
│ ├── Providers/ # Service providers
│ └── Services/ # Business logic layer
├── config/ # Configuration files
├── database/
│ ├── factories/ # Model factories for testing
│ ├── migrations/ # Always include down() for rollback
│ └── seeders/ # Database seeders
├── resources/views/ # Blade templates
├── routes/
│ ├── api.php # API routes (versioned: /api/v1/)
│ └── web.php # Web routes
├── tests/
│ ├── Feature/ # Integration/HTTP tests
│ └── Unit/ # Isolated unit tests
├── .env.example
├── composer.json
└── phpunit.xml
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'title',
'slug',
'body',
'status',
'user_id',
];
protected $casts = [
'published_at' => 'datetime',
'is_featured' => 'boolean',
];
// -- Relationships --
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
// -- Scopes --
public function scopePublished($query)
{
return $query->where('status', 'published')
->whereNotNull('published_at');
}
// -- Accessors (Laravel 11 attribute casting) --
public function getExcerptAttribute(): string
{
return str()->limit($this->body, 150);
}
}
$fillable (whitelist) for mass assignment protection$casts for type casting (datetime, boolean, enum, array, json)HasMany, BelongsTo, etc.)scopeActive, scopePublished, scopeByRole<?php
// routes/web.php
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;
Route::get('/', fn () => view('welcome'));
Route::middleware('auth')->group(function () {
Route::resource('posts', PostController::class);
});
<?php
// routes/api.php
use App\Http\Controllers\Api\PostController;
use Illuminate\Support\Facades\Route;
Route::prefix('v1')->group(function () {
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('posts', PostController::class);
});
});
Route::middleware()->group()apiResource for API controllers (excludes create/edit views)/api/v1/...->name('posts.publish')<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\StorePostRequest;
use App\Http\Resources\PostResource;
use App\Models\Post;
use App\Services\PostService;
use Illuminate\Http\JsonResponse;
class PostController extends Controller
{
public function __construct(
private readonly PostService $postService,
) {}
public function index(): PostResource
{
return PostResource::collection(
Post::with('author')->published()->paginate(15)
);
}
public function store(StorePostRequest $request): JsonResponse
{
$post = $this->postService->create($request->validated());
return PostResource::make($post)->response()->setStatusCode(201);
}
public function show(Post $post): PostResource
{
return PostResource::make($post->load('author', 'comments'));
}
public function destroy(Post $post): JsonResponse
{
$this->authorize('delete', $post);
$this->postService->delete($post);
return response()->json(null, 204);
}
}
readonly for dependencies$this->authorize() or Policy middleware for authorization{{-- resources/views/components/alert.blade.php --}}
@props(['type' => 'info', 'dismissible' => false])
<div {{ $attributes->merge(['class' => "alert alert-{$type}"]) }}>
{{ $slot }}
@if($dismissible)
<button type="button" class="close">×</button>
@endif
</div>
{{ }} for escaped output (default); {!! !!} only for trusted HTML@props in components for typed attributes with defaults@stack / @push for page-specific CSS/JS@include for partials, <x-component> for reusable components@vite() for asset bundling (replaces Laravel Mix)<?php
declare(strict_types=1);
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('create', Post::class);
}
/** @return array<string, mixed> */
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'slug' => ['required', 'string', 'max:255', Rule::unique('posts')],
'body' => ['required', 'string', 'min:10'],
'status' => ['required', Rule::in(['draft', 'published'])],
];
}
}
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserHasRole
{
public function handle(Request $request, Closure $next, string $role): Response
{
if ($request->user()?->role !== $role) {
abort(403, "Role '{$role}' is required.");
}
return $next($request);
}
}
Register in bootstrap/app.php (Laravel 11+):
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'role' => \App\Http\Middleware\EnsureUserHasRole::class,
]);
})
# Scaffolding
php artisan make:model Post -mfcs # Model + migration + factory + controller + seeder
php artisan make:controller Api/PostController --api
php artisan make:request StorePostRequest
php artisan make:resource PostResource
php artisan make:event PostPublished
php artisan make:job ProcessReport
php artisan make:policy PostPolicy --model=Post
# Database
php artisan migrate # Run pending migrations
php artisan migrate:fresh --seed # Reset DB and seed (dev only)
# Cache & Optimization
php artisan optimize # Cache config, routes, views
php artisan optimize:clear # Clear all caches (dev)
# Queue & Testing
php artisan queue:work # Start queue worker
php artisan test # Run PHPUnit
php artisan test --filter=PostTest # Run specific test
php artisan test --coverage # With coverage report
<?php
declare(strict_types=1);
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PostControllerTest extends TestCase
{
use RefreshDatabase;
public function test_authenticated_user_can_create_post(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->postJson('/api/v1/posts', [
'title' => 'My Post',
'slug' => 'my-post',
'body' => 'Post content here.',
'status' => 'draft',
]);
$response->assertCreated()
->assertJsonPath('data.title', 'My Post');
$this->assertDatabaseHas('posts', ['slug' => 'my-post']);
}
public function test_guest_cannot_create_post(): void
{
$this->postJson('/api/v1/posts', ['title' => 'My Post'])
->assertUnauthorized();
}
}
RefreshDatabase trait for database teststest_guest_cannot_delete_postactingAs() for authenticated requests<?php
declare(strict_types=1);
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->string('status')->default('draft');
$table->timestamp('published_at')->nullable();
$table->timestamps();
$table->softDeletes();
$table->index(['status', 'published_at']);
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
};
down() for rollbackforeignId()->constrained() for foreign keys with cascadingWHERE, ORDER BYFor detailed patterns and examples, see:
development
Zig language guardrails, patterns, and best practices for AI-assisted development. Use when working with Zig files (.zig), build.zig, or when the user mentions Zig. Provides comptime patterns, allocator conventions, C interop guidelines, and testing standards specific to this project's coding standards.
tools
WordPress framework guardrails, patterns, and best practices for AI-assisted development. Use when working with WordPress projects, or when the user mentions WordPress. Provides theme development, plugin architecture, REST API, blocks, and security guidelines.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. Use when testing web apps, automating browser interactions, or debugging frontend issues.
tools
Suite of tools for creating elaborate, multi-component web applications using modern frontend technologies (React, Tailwind CSS, shadcn/ui). Use for complex projects requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX pages.