
Laravel Specialist
- 1 installs
- 404 repo stars
- Updated August 5, 2026
- aiskillstore/marketplace
laravel-specialist is a Claude Code skill for building Laravel 10+ applications using Eloquent ORM, API resources, queues, Livewire and Sanctum.
About
laravel-specialist is a Claude Code skill for building Laravel 10+ applications with PHP 8.2+. A developer uses it for Eloquent models and relationships, RESTful APIs with API resources, queue systems with Horizon, Livewire interfaces and Sanctum authentication. It enforces patterns like avoiding N+1 queries, keeping business logic out of controllers, and writing tests with Pest or PHPUnit.
- Builds Laravel 10+ apps with Eloquent ORM and API resources
- Covers queues/Horizon, Livewire and Sanctum authentication
- Enforces PHP 8.2+ patterns, eager loading and >85% test coverage
Laravel Specialist by the numbers
- 1 all-time installs (skills.sh)
- Ranked #55 of 65 PHP & Laravel skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
laravel-specialist capabilities & compatibility
- Capabilities
- eloquent orm · api resources · queue systems · livewire · sanctum auth
- Works with
- redis
- Use cases
- api development · database · testing
What laravel-specialist says it does
Senior Laravel specialist with deep expertise in Laravel 10+, Eloquent ORM, and modern PHP 8.2+ development.
Use Eloquent relationships properly (avoid N+1)
npx skills add https://github.com/aiskillstore/marketplace --skill laravel-specialistAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 404 |
| Last updated | August 5, 2026 |
| Repository | aiskillstore/marketplace ↗ |
What it does
Build a Laravel 10+ backend with Eloquent, API resources, queues and Sanctum auth.
Who is it for?
Building Laravel 10+ backends with Eloquent, APIs and queues
When should I use this skill?
Building Laravel apps, implementing Eloquent models, or setting up queues and Sanctum auth
What you get
- Eloquent models
- migrations
- API resources
By the numbers
- 5 reference guides (Eloquent, Routing, Queues, Livewire, Testing)
- 6-item output template
- targets >85% test coverage
Files
Laravel Specialist
Senior Laravel specialist with deep expertise in Laravel 10+, Eloquent ORM, and modern PHP 8.2+ development.
Role Definition
You are a senior PHP engineer with 10+ years of Laravel experience. You specialize in Laravel 10+ with PHP 8.2+, Eloquent ORM, API resources, queue systems, and modern Laravel patterns. You build elegant, scalable applications with powerful features.
When to Use This Skill
- Building Laravel 10+ applications
- Implementing Eloquent models and relationships
- Creating RESTful APIs with API resources
- Setting up queue systems and jobs
- Building reactive interfaces with Livewire
- Implementing authentication with Sanctum
- Optimizing database queries and performance
- Writing comprehensive tests with Pest/PHPUnit
Core Workflow
1. Analyze requirements - Identify models, relationships, APIs, queue needs 2. Design architecture - Plan database schema, service layers, job queues 3. Implement models - Create Eloquent models with relationships, scopes, casts 4. Build features - Develop controllers, services, API resources, jobs 5. Test thoroughly - Write feature and unit tests with >85% coverage
Reference Guide
Load detailed guidance based on context:
| Topic | Reference | Load When |
|---|---|---|
| Eloquent ORM | references/eloquent.md | Models, relationships, scopes, query optimization |
| Routing & APIs | references/routing.md | Routes, controllers, middleware, API resources |
| Queue System | references/queues.md | Jobs, workers, Horizon, failed jobs, batching |
| Livewire | references/livewire.md | Components, wire:model, actions, real-time |
| Testing | references/testing.md | Feature tests, factories, mocking, Pest PHP |
Constraints
MUST DO
- Use PHP 8.2+ features (readonly, enums, typed properties)
- Type hint all method parameters and return types
- Use Eloquent relationships properly (avoid N+1)
- Implement API resources for transforming data
- Queue long-running tasks
- Write comprehensive tests (>85% coverage)
- Use service containers and dependency injection
- Follow PSR-12 coding standards
MUST NOT DO
- Use raw queries without protection (SQL injection)
- Skip eager loading (causes N+1 problems)
- Store sensitive data unencrypted
- Mix business logic in controllers
- Hardcode configuration values
- Skip validation on user input
- Use deprecated Laravel features
- Ignore queue failures
Output Templates
When implementing Laravel features, provide: 1. Model file (Eloquent model with relationships) 2. Migration file (database schema) 3. Controller/API resource (if applicable) 4. Service class (business logic) 5. Test file (feature/unit tests) 6. Brief explanation of design decisions
Knowledge Reference
Laravel 10+, Eloquent ORM, PHP 8.2+, API resources, Sanctum/Passport, queues, Horizon, Livewire, Inertia, Octane, Pest/PHPUnit, Redis, broadcasting, events/listeners, notifications, task scheduling
Eloquent ORM
Model Patterns
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Casts\Attribute;
class Post extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'title',
'slug',
'content',
'published_at',
'user_id',
];
protected $casts = [
'published_at' => 'datetime',
'metadata' => 'array',
'is_featured' => 'boolean',
];
// Accessor using new Attribute syntax (Laravel 9+)
protected function title(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
set: fn (string $value) => strtolower($value),
);
}
// Mutator for computed property
protected function excerpt(): Attribute
{
return Attribute::make(
get: fn () => str($this->content)->limit(100),
);
}
}Relationships
// One-to-Many
class User extends Model
{
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
public function latestPost(): HasOne
{
return $this->hasOne(Post::class)->latestOfMany();
}
public function oldestPost(): HasOne
{
return $this->hasOne(Post::class)->oldestOfMany();
}
}
class Post extends Model
{
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
// Inverse relationship
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
}
// Many-to-Many with Pivot
class User extends Model
{
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class)
->withPivot('expires_at', 'assigned_by')
->withTimestamps()
->using(RoleUser::class); // Custom pivot model
}
}
// Has Many Through
class Country extends Model
{
public function posts(): HasManyThrough
{
return $this->hasManyThrough(Post::class, User::class);
}
}
// Polymorphic Relations
class Image extends Model
{
public function imageable(): MorphTo
{
return $this->morphTo();
}
}
class Post extends Model
{
public function images(): MorphMany
{
return $this->morphMany(Image::class, 'imageable');
}
}
// Many-to-Many Polymorphic
class Tag extends Model
{
public function posts(): MorphToMany
{
return $this->morphedByMany(Post::class, 'taggable');
}
public function videos(): MorphToMany
{
return $this->morphedByMany(Video::class, 'taggable');
}
}Query Scopes
class Post extends Model
{
// Local scope
public function scopePublished($query): void
{
$query->whereNotNull('published_at')
->where('published_at', '<=', now());
}
public function scopePopular($query, int $threshold = 100): void
{
$query->where('views', '>=', $threshold);
}
// Global scope
protected static function booted(): void
{
static::addGlobalScope('active', function ($query) {
$query->where('status', 'active');
});
}
}
// Usage
$posts = Post::published()->popular(500)->get();
// Custom Scope Class
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class AncientScope implements Scope
{
public function apply(Builder $builder, Model $model): void
{
$builder->where('created_at', '<', now()->subYears(10));
}
}
// Apply in model
protected static function booted(): void
{
static::addGlobalScope(new AncientScope);
}Custom Casts
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class Money implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes): float
{
return $value / 100; // Store cents, return dollars
}
public function set($model, string $key, $value, array $attributes): int
{
return (int) ($value * 100);
}
}
// In model
protected $casts = [
'price' => Money::class,
];Query Optimization
// Eager Loading (prevent N+1)
$posts = Post::with(['user', 'comments.user'])->get();
// Lazy Eager Loading
$posts = Post::all();
$posts->load('user');
// Eager Load with Constraints
$users = User::with(['posts' => function ($query) {
$query->where('published', true)->orderBy('created_at', 'desc');
}])->get();
// Count relationships efficiently
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
// Exists checks
$users = User::withExists('posts')->get();
// Chunk for large datasets
Post::chunk(100, function ($posts) {
foreach ($posts as $post) {
// Process post
}
});
// Lazy collection for memory efficiency
Post::lazy()->each(function ($post) {
// Process one at a time
});Model Events
class Post extends Model
{
protected static function booted(): void
{
static::creating(function ($post) {
$post->slug = str($post->title)->slug();
});
static::updating(function ($post) {
if ($post->isDirty('title')) {
$post->slug = str($post->title)->slug();
}
});
static::deleted(function ($post) {
$post->images()->delete();
});
}
}
// Using Observers
namespace App\Observers;
class PostObserver
{
public function creating(Post $post): void
{
$post->user_id = auth()->id();
}
public function updated(Post $post): void
{
cache()->forget("post.{$post->id}");
}
}
// Register in AppServiceProvider
use App\Models\Post;
use App\Observers\PostObserver;
public function boot(): void
{
Post::observe(PostObserver::class);
}Advanced Queries
// Subqueries
$users = User::select(['id', 'name'])
->addSelect(['latest_post_title' => Post::select('title')
->whereColumn('user_id', 'users.id')
->latest()
->limit(1)
])->get();
// When conditional queries
$posts = Post::query()
->when($search, fn ($query) => $query->where('title', 'like', "%{$search}%"))
->when($category, fn ($query) => $query->where('category_id', $category))
->get();
// Database transactions
DB::transaction(function () {
$user = User::create([...]);
$user->profile()->create([...]);
$user->assignRole('member');
});
// Pessimistic locking
$user = User::where('id', 1)->lockForUpdate()->first();
// Upserts
User::upsert(
[
['email' => 'john@example.com', 'name' => 'John'],
['email' => 'jane@example.com', 'name' => 'Jane'],
],
['email'], // Unique columns
['name'] // Columns to update
);Performance Tips
1. Always eager load relationships - Avoid N+1 queries 2. Use chunking for large datasets - Prevent memory exhaustion 3. Index foreign keys - Speed up joins 4. Use select() to limit columns - Reduce data transfer 5. Cache expensive queries - Use Redis/Memcached 6. Use database indexing - Add indexes in migrations 7. Avoid using model events for heavy operations - Use queues instead 8. Use lazy collections - For processing large datasets
Livewire Components
Component Patterns
namespace App\Http\Livewire;
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\WithFileUploads;
use App\Models\Post;
class PostList extends Component
{
use WithPagination, WithFileUploads;
public string $search = '';
public string $sortBy = 'created_at';
public string $sortDirection = 'desc';
public ?int $categoryId = null;
protected $queryString = [
'search' => ['except' => ''],
'sortBy' => ['except' => 'created_at'],
'categoryId' => ['except' => null],
];
public function updatingSearch(): void
{
$this->resetPage();
}
public function sortBy(string $field): void
{
if ($this->sortBy === $field) {
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
} else {
$this->sortBy = $field;
$this->sortDirection = 'asc';
}
}
public function render()
{
return view('livewire.post-list', [
'posts' => Post::query()
->when($this->search, fn($q) => $q->where('title', 'like', "%{$this->search}%"))
->when($this->categoryId, fn($q) => $q->where('category_id', $this->categoryId))
->orderBy($this->sortBy, $this->sortDirection)
->paginate(10),
]);
}
}Blade Template
<div>
{{-- Search --}}
<input
type="text"
wire:model.debounce.300ms="search"
placeholder="Search posts..."
class="form-input"
>
{{-- Filter by category --}}
<select wire:model="categoryId">
<option value="">All Categories</option>
@foreach($categories as $category)
<option value="{{ $category->id }}">{{ $category->name }}</option>
@endforeach
</select>
{{-- Sortable table --}}
<table>
<thead>
<tr>
<th wire:click="sortBy('title')" style="cursor: pointer">
Title
@if($sortBy === 'title')
<span>{{ $sortDirection === 'asc' ? '↑' : '↓' }}</span>
@endif
</th>
<th wire:click="sortBy('created_at')" style="cursor: pointer">
Date
@if($sortBy === 'created_at')
<span>{{ $sortDirection === 'asc' ? '↑' : '↓' }}</span>
@endif
</th>
</tr>
</thead>
<tbody>
@foreach($posts as $post)
<tr>
<td>{{ $post->title }}</td>
<td>{{ $post->created_at->diffForHumans() }}</td>
</tr>
@endforeach
</tbody>
</table>
{{-- Pagination --}}
{{ $posts->links() }}
{{-- Loading states --}}
<div wire:loading wire:target="search">
Searching...
</div>
</div>Form Component
namespace App\Http\Livewire;
use Livewire\Component;
use App\Models\Post;
class PostForm extends Component
{
public ?Post $post = null;
public string $title = '';
public string $content = '';
public array $tags = [];
public $image;
protected function rules(): array
{
return [
'title' => 'required|min:3|max:255',
'content' => 'required|min:10',
'tags' => 'array|max:5',
'tags.*' => 'exists:tags,id',
'image' => 'nullable|image|max:2048',
];
}
public function mount(?Post $post = null): void
{
if ($post) {
$this->post = $post;
$this->title = $post->title;
$this->content = $post->content;
$this->tags = $post->tags->pluck('id')->toArray();
}
}
public function updated($propertyName): void
{
$this->validateOnly($propertyName);
}
public function save(): void
{
$validated = $this->validate();
if ($this->post) {
$this->post->update($validated);
$message = 'Post updated successfully!';
} else {
$this->post = Post::create($validated);
$message = 'Post created successfully!';
}
if ($this->image) {
$this->post->update([
'image_path' => $this->image->store('posts', 'public'),
]);
}
$this->post->tags()->sync($this->tags);
session()->flash('message', $message);
$this->redirect(route('posts.show', $this->post));
}
public function render()
{
return view('livewire.post-form');
}
}Form Template
<form wire:submit.prevent="save">
{{-- Title --}}
<div>
<label for="title">Title</label>
<input
type="text"
wire:model.defer="title"
id="title"
class="@error('title') border-red-500 @enderror"
>
@error('title')
<span class="text-red-500">{{ $message }}</span>
@enderror
</div>
{{-- Content --}}
<div>
<label for="content">Content</label>
<textarea
wire:model.defer="content"
id="content"
class="@error('content') border-red-500 @enderror"
></textarea>
@error('content')
<span class="text-red-500">{{ $message }}</span>
@enderror
</div>
{{-- Tags --}}
<div>
<label>Tags</label>
@foreach($availableTags as $tag)
<label>
<input
type="checkbox"
wire:model="tags"
value="{{ $tag->id }}"
>
{{ $tag->name }}
</label>
@endforeach
@error('tags')
<span class="text-red-500">{{ $message }}</span>
@enderror
</div>
{{-- File Upload --}}
<div>
<label>Image</label>
<input type="file" wire:model="image">
@error('image')
<span class="text-red-500">{{ $message }}</span>
@enderror
{{-- Upload progress --}}
<div wire:loading wire:target="image">
Uploading...
</div>
{{-- Preview --}}
@if ($image)
<img src="{{ $image->temporaryUrl() }}" alt="Preview">
@endif
</div>
{{-- Submit --}}
<button type="submit" wire:loading.attr="disabled">
<span wire:loading.remove>Save</span>
<span wire:loading>Saving...</span>
</button>
</form>
@if (session()->has('message'))
<div class="alert alert-success">
{{ session('message') }}
</div>
@endifReal-time Validation
class PostForm extends Component
{
public string $title = '';
protected $rules = [
'title' => 'required|min:3|unique:posts,title',
];
// Real-time validation
public function updated($propertyName): void
{
$this->validateOnly($propertyName);
}
// Custom validation messages
protected $messages = [
'title.required' => 'The post title is required.',
'title.min' => 'The title must be at least 3 characters.',
'title.unique' => 'This title is already taken.',
];
// Custom attribute names
protected $validationAttributes = [
'title' => 'post title',
];
}Events
// Emit event
class PostList extends Component
{
public function deletePost($postId): void
{
Post::find($postId)->delete();
$this->emit('postDeleted', $postId);
}
}
// Listen to event
class PostStats extends Component
{
protected $listeners = ['postDeleted' => 'updateStats'];
public function updateStats($postId): void
{
// Update statistics
}
}
// Emit to specific component
$this->emitTo('post-stats', 'refresh');
// Emit to parent/children
$this->emitUp('saved');
$this->emitSelf('refresh');
// Browser events
$this->dispatchBrowserEvent('post-saved', ['id' => $post->id]);Listen to Browser Events
<div
x-data
@post-saved.window="alert('Post saved!')"
>
<!-- content -->
</div>
<script>
window.addEventListener('post-saved', event => {
console.log('Post ID:', event.detail.id);
});
</script>Polling
{{-- Poll every 2 seconds --}}
<div wire:poll.2s>
Current time: {{ now() }}
</div>
{{-- Poll specific action --}}
<div wire:poll.5s="checkStatus">
Status: {{ $status }}
</div>
{{-- Keep polling until condition --}}
<div wire:poll.keep-alive.2s>
<!-- content -->
</div>Loading States
{{-- Basic loading state --}}
<div wire:loading>
Loading...
</div>
{{-- Target specific action --}}
<div wire:loading wire:target="save">
Saving...
</div>
{{-- Hide element while loading --}}
<div wire:loading.remove>
Content (hidden during load)
</div>
{{-- Delay loading indicator --}}
<div wire:loading.delay>
This appears after 200ms
</div>
{{-- Custom delay --}}
<div wire:loading.delay.longest>
This appears after 1s
</div>
{{-- Loading classes --}}
<button
wire:click="save"
wire:loading.class="opacity-50"
wire:loading.class.remove="bg-blue-500"
>
Save
</button>
{{-- Loading attributes --}}
<button
wire:click="save"
wire:loading.attr="disabled"
>
Save
</button>Traits
// Pagination
use Livewire\WithPagination;
class PostList extends Component
{
use WithPagination;
public function render()
{
return view('livewire.post-list', [
'posts' => Post::paginate(10),
]);
}
}
// File uploads
use Livewire\WithFileUploads;
class UploadPhoto extends Component
{
use WithFileUploads;
public $photo;
public function save(): void
{
$this->validate([
'photo' => 'image|max:1024',
]);
$this->photo->store('photos');
}
}Authorization
class PostForm extends Component
{
public Post $post;
public function mount(Post $post): void
{
$this->authorize('update', $post);
$this->post = $post;
}
public function save(): void
{
$this->authorize('update', $this->post);
// Save logic
}
}Performance Tips
1. Use wire:model.defer - Batch updates on form submit 2. Lazy load components - Use wire:init for heavy operations 3. Cache computed properties - Use #[Computed] attribute 4. Disable polling when hidden - Use wire:poll.visible 5. Optimize queries - Eager load relationships 6. Use wire:key - Prevent re-rendering entire lists 7. Debounce input - Use wire:model.debounce 8. Use pagination - Don't load all records at once
use Livewire\Attributes\Computed;
class PostList extends Component
{
#[Computed]
public function posts()
{
return Post::with('user')->paginate(10);
}
public function render()
{
return view('livewire.post-list');
}
}{{-- Access computed property --}}
@foreach($this->posts as $post)
<!-- content -->
@endforeachQueue System
Job Patterns
namespace App\Jobs;
use App\Models\Post;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessPost implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $timeout = 120;
public $maxExceptions = 3;
public $backoff = [60, 120, 300]; // Exponential backoff
public function __construct(
public Post $post,
public ?User $user = null,
) {}
public function handle(): void
{
// Process the post
$this->post->update(['processed' => true]);
// Can access injected dependencies
$analytics = app(AnalyticsService::class);
$analytics->trackPostProcessed($this->post);
}
public function failed(\Throwable $exception): void
{
// Handle job failure
\Log::error('Post processing failed', [
'post_id' => $this->post->id,
'error' => $exception->getMessage(),
]);
}
}Dispatching Jobs
use App\Jobs\ProcessPost;
// Dispatch immediately
ProcessPost::dispatch($post);
// Dispatch to specific queue
ProcessPost::dispatch($post)->onQueue('processing');
// Delayed dispatch
ProcessPost::dispatch($post)->delay(now()->addMinutes(10));
// Dispatch after database commit
ProcessPost::dispatch($post)->afterCommit();
// Dispatch conditionally
ProcessPost::dispatchIf($condition, $post);
ProcessPost::dispatchUnless($condition, $post);
// Synchronous dispatch (no queue)
ProcessPost::dispatchSync($post);
// Dispatch after response
ProcessPost::dispatchAfterResponse($post);Job Chaining
use App\Jobs\{OptimizeImage, GenerateThumbnail, PublishPost};
// Chain jobs
OptimizeImage::withChain([
new GenerateThumbnail($post),
new PublishPost($post),
])->dispatch($post);
// Catch failures in chain
Bus::chain([
new ProcessPost($post),
new NotifyUser($user),
])->catch(function (\Throwable $e) {
// Handle failure
})->dispatch();Job Batching
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;
$batch = Bus::batch([
new ProcessPost($post1),
new ProcessPost($post2),
new ProcessPost($post3),
])->then(function (Batch $batch) {
// All jobs completed successfully
})->catch(function (Batch $batch, \Throwable $e) {
// First batch job failure detected
})->finally(function (Batch $batch) {
// The batch has finished executing
})->name('Process Posts')
->allowFailures()
->dispatch();
// Check batch status
$batch = Bus::findBatch($batchId);
if ($batch->finished()) {
// Batch is complete
}
if ($batch->cancelled()) {
// Batch was cancelled
}
// Add jobs to existing batch
$batch->add([
new ProcessPost($post4),
]);Rate Limiting
use Illuminate\Support\Facades\Redis;
class ProcessPost implements ShouldQueue
{
public function handle(): void
{
Redis::throttle('process-posts')
->block(0)
->allow(10)
->every(60)
->then(function () {
// Lock acquired, process job
}, function () {
// Could not acquire lock, release job back
$this->release(10);
});
}
}
// Or using middleware
use Illuminate\Queue\Middleware\RateLimited;
public function middleware(): array
{
return [new RateLimited('process-posts')];
}Job Middleware
namespace App\Jobs\Middleware;
class RateLimitedByUser
{
public function handle($job, $next): void
{
Redis::throttle("user:{$job->user->id}")
->allow(10)
->every(60)
->then(function () use ($job, $next) {
$next($job);
}, function () use ($job) {
$job->release(10);
});
}
}
// Use in job
use App\Jobs\Middleware\RateLimitedByUser;
public function middleware(): array
{
return [new RateLimitedByUser];
}
// Skip middleware
use Illuminate\Queue\Middleware\WithoutOverlapping;
public function middleware(): array
{
return [
(new WithoutOverlapping($this->user->id))->expireAfter(180),
];
}Unique Jobs
use Illuminate\Contracts\Queue\ShouldBeUnique;
class ProcessPost implements ShouldQueue, ShouldBeUnique
{
public int $uniqueFor = 3600;
public function __construct(
public Post $post,
) {}
public function uniqueId(): string
{
return $this->post->id;
}
}
// Or use unique until processing
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
class ProcessPost implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
// ...
}Failed Jobs
// Retry failed job
php artisan queue:retry <job-id>
// Retry all failed jobs
php artisan queue:retry all
// Flush failed jobs
php artisan queue:flush
// Prune failed jobs
php artisan queue:prune-failed --hours=48
// Handle in code
use Illuminate\Support\Facades\Queue;
Queue::failing(function (JobFailed $event) {
\Log::error('Job failed', [
'connection' => $event->connectionName,
'queue' => $event->job->getQueue(),
'exception' => $event->exception->getMessage(),
]);
});Queue Workers
# Start worker
php artisan queue:work
# Process specific queue
php artisan queue:work --queue=high,default
# Process one job
php artisan queue:work --once
# Stop worker gracefully
php artisan queue:restart
# Timeout settings
php artisan queue:work --timeout=60
# Memory limit
php artisan queue:work --memory=512
# Max jobs before restart
php artisan queue:work --max-jobs=1000
# Max time before restart
php artisan queue:work --max-time=3600Horizon Setup
// config/horizon.php
return [
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'maxProcesses' => 10,
'maxTime' => 0,
'maxJobs' => 0,
'memory' => 512,
'tries' => 3,
'timeout' => 60,
'nice' => 0,
],
'supervisor-2' => [
'connection' => 'redis',
'queue' => ['high', 'default'],
'balance' => 'auto',
'maxProcesses' => 5,
'tries' => 3,
],
],
],
];
// Start Horizon
php artisan horizon
// Terminate Horizon
php artisan horizon:terminate
// Pause workers
php artisan horizon:pause
// Continue workers
php artisan horizon:continue
// Check status
php artisan horizon:statusMonitoring
use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Support\Facades\Queue;
// In AppServiceProvider
public function boot(): void
{
Queue::before(function (JobProcessing $event) {
// Called before job is processed
});
Queue::after(function (JobProcessed $event) {
// Called after job is processed
\Log::info('Job processed', [
'job' => $event->job->resolveName(),
'time' => $event->job->processingTime(),
]);
});
Queue::failing(function (JobFailed $event) {
// Called when job fails
\Log::error('Job failed', [
'job' => $event->job->resolveName(),
'exception' => $event->exception,
]);
});
}Queue Configuration
// config/queue.php
return [
'default' => env('QUEUE_CONNECTION', 'sync'),
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'table' => 'jobs',
'queue' => 'default',
'retry_after' => 90,
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
'after_commit' => false,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX'),
'queue' => env('SQS_QUEUE'),
'region' => env('AWS_DEFAULT_REGION'),
],
],
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'mysql'),
'table' => 'failed_jobs',
],
];Best Practices
1. Keep jobs small and focused - Single responsibility 2. Make jobs idempotent - Safe to run multiple times 3. Use type hints - Better error detection 4. Set reasonable timeouts - Prevent hanging jobs 5. Monitor failed jobs - Set up alerts 6. Use batching for bulk operations - Better performance 7. Implement proper error handling - Use failed() method 8. Use unique jobs - Prevent duplicate processing 9. Queue long-running tasks - Don't block requests 10. Use Horizon for Redis queues - Better monitoring
Routing & API Resources
Route Patterns
// routes/web.php
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;
// Resource routes
Route::resource('posts', PostController::class);
// API resource (excludes create/edit)
Route::apiResource('posts', PostController::class);
// Partial resource
Route::resource('posts', PostController::class)->only(['index', 'show']);
Route::resource('posts', PostController::class)->except(['destroy']);
// Nested resources
Route::resource('posts.comments', CommentController::class);
// Route groups
Route::prefix('admin')->middleware('auth')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
Route::resource('users', UserController::class);
});
// Named routes
Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');
// Route model binding
Route::get('/posts/{post:slug}', [PostController::class, 'show']);
// Multiple bindings
Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
return view('posts.show', compact('user', 'post'));
});API Routes
// routes/api.php
use App\Http\Controllers\Api\V1\PostController;
Route::prefix('v1')->group(function () {
// Public routes
Route::get('/posts', [PostController::class, 'index']);
Route::get('/posts/{post}', [PostController::class, 'show']);
// Protected routes
Route::middleware('auth:sanctum')->group(function () {
Route::post('/posts', [PostController::class, 'store']);
Route::put('/posts/{post}', [PostController::class, 'update']);
Route::delete('/posts/{post}', [PostController::class, 'destroy']);
});
});
// Rate limiting
Route::middleware('throttle:60,1')->group(function () {
Route::apiResource('posts', PostController::class);
});Controllers
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\StorePostRequest;
use App\Http\Requests\UpdatePostRequest;
use App\Http\Resources\PostResource;
use App\Http\Resources\PostCollection;
use App\Models\Post;
use Illuminate\Http\Response;
class PostController extends Controller
{
public function index()
{
$posts = Post::with('user')
->published()
->paginate(15);
return new PostCollection($posts);
}
public function store(StorePostRequest $request)
{
$post = Post::create($request->validated());
return new PostResource($post);
}
public function show(Post $post)
{
$post->load(['user', 'comments.user']);
return new PostResource($post);
}
public function update(UpdatePostRequest $request, Post $post)
{
$post->update($request->validated());
return new PostResource($post);
}
public function destroy(Post $post)
{
$post->delete();
return response()->noContent();
}
}Form Requests
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return true; // Or check user permissions
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'slug' => ['required', 'string', 'unique:posts,slug'],
'content' => ['required', 'string'],
'category_id' => ['required', 'exists:categories,id'],
'tags' => ['array'],
'tags.*' => ['exists:tags,id'],
'published_at' => ['nullable', 'date', 'after:now'],
];
}
public function messages(): array
{
return [
'title.required' => 'Please provide a post title',
'slug.unique' => 'This slug is already taken',
];
}
// Prepare data before validation
protected function prepareForValidation(): void
{
$this->merge([
'slug' => str($this->title)->slug(),
]);
}
}
class UpdatePostRequest extends FormRequest
{
public function rules(): array
{
return [
'title' => ['sometimes', 'string', 'max:255'],
'slug' => [
'sometimes',
'string',
Rule::unique('posts', 'slug')->ignore($this->post)
],
'content' => ['sometimes', 'string'],
];
}
}API Resources
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class PostResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
'excerpt' => $this->excerpt,
'content' => $this->when($request->route()->named('posts.show'), $this->content),
'published_at' => $this->published_at?->toISOString(),
'created_at' => $this->created_at->toISOString(),
// Relationships
'author' => new UserResource($this->whenLoaded('user')),
'comments' => CommentResource::collection($this->whenLoaded('comments')),
'comments_count' => $this->when($this->comments_count !== null, $this->comments_count),
// Conditional fields
'is_published' => $this->when($request->user()?->isAdmin(), $this->isPublished()),
// Pivot data
'role' => $this->whenPivotLoaded('role_user', function () {
return $this->pivot->role_name;
}),
// Links
'links' => [
'self' => route('api.posts.show', $this->id),
],
];
}
public function with(Request $request): array
{
return [
'meta' => [
'version' => '1.0.0',
],
];
}
}Resource Collections
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class PostCollection extends ResourceCollection
{
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'meta' => [
'total' => $this->total(),
'current_page' => $this->currentPage(),
'last_page' => $this->lastPage(),
],
'links' => [
'self' => $request->url(),
],
];
}
}
// Or use anonymous collection
return PostResource::collection($posts);Middleware
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class EnsureUserIsAdmin
{
public function handle(Request $request, Closure $next)
{
if (!$request->user()?->isAdmin()) {
abort(403, 'Unauthorized action.');
}
return $next($request);
}
}
// Register in app/Http/Kernel.php
protected $middlewareAliases = [
'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
];
// Use in routes
Route::middleware('admin')->group(function () {
Route::resource('users', UserController::class);
});Response Helpers
// JSON responses
return response()->json(['data' => $posts], 200);
// Created response
return response()->json($post, 201);
// No content
return response()->noContent();
// Custom headers
return response()->json($data)->header('X-Custom-Header', 'Value');
// Download
return response()->download($pathToFile);
// Stream
return response()->streamDownload(function () {
echo 'CSV content...';
}, 'export.csv');Route Caching
# Generate route cache
php artisan route:cache
# Clear route cache
php artisan route:clear
# List all routes
php artisan route:list
# Filter routes
php artisan route:list --name=api
php artisan route:list --path=postsAPI Versioning
// routes/api.php
Route::prefix('v1')->name('v1.')->group(function () {
Route::apiResource('posts', \App\Http\Controllers\Api\V1\PostController::class);
});
Route::prefix('v2')->name('v2.')->group(function () {
Route::apiResource('posts', \App\Http\Controllers\Api\V2\PostController::class);
});CORS Configuration
// config/cors.php
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['http://localhost:3000'],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];Testing
Feature Tests
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\{User, Post};
use Illuminate\Foundation\Testing\RefreshDatabase;
class PostTest extends TestCase
{
use RefreshDatabase;
public function test_user_can_create_post(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)->post('/api/posts', [
'title' => 'Test Post',
'content' => 'This is a test post content.',
]);
$response->assertStatus(201)
->assertJson([
'data' => [
'title' => 'Test Post',
],
]);
$this->assertDatabaseHas('posts', [
'title' => 'Test Post',
'user_id' => $user->id,
]);
}
public function test_guest_cannot_create_post(): void
{
$response = $this->post('/api/posts', [
'title' => 'Test Post',
'content' => 'Content',
]);
$response->assertStatus(401);
}
public function test_post_requires_valid_data(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)->post('/api/posts', [
'title' => 'AB', // Too short
]);
$response->assertStatus(422)
->assertJsonValidationErrors(['title', 'content']);
}
public function test_user_can_view_their_posts(): void
{
$user = User::factory()->create();
$posts = Post::factory()->count(3)->create(['user_id' => $user->id]);
$response = $this->actingAs($user)->get('/api/posts');
$response->assertStatus(200)
->assertJsonCount(3, 'data')
->assertJsonStructure([
'data' => [
'*' => ['id', 'title', 'content', 'created_at'],
],
]);
}
public function test_user_can_update_own_post(): void
{
$user = User::factory()->create();
$post = Post::factory()->create(['user_id' => $user->id]);
$response = $this->actingAs($user)->put("/api/posts/{$post->id}", [
'title' => 'Updated Title',
'content' => $post->content,
]);
$response->assertStatus(200);
$this->assertDatabaseHas('posts', [
'id' => $post->id,
'title' => 'Updated Title',
]);
}
public function test_user_cannot_update_others_post(): void
{
$user = User::factory()->create();
$otherUser = User::factory()->create();
$post = Post::factory()->create(['user_id' => $otherUser->id]);
$response = $this->actingAs($user)->put("/api/posts/{$post->id}", [
'title' => 'Updated Title',
]);
$response->assertStatus(403);
}
}Unit Tests
namespace Tests\Unit;
use Tests\TestCase;
use App\Models\Post;
use App\Services\PostService;
use Illuminate\Foundation\Testing\RefreshDatabase;
class PostServiceTest extends TestCase
{
use RefreshDatabase;
public function test_generates_unique_slug(): void
{
$service = new PostService();
$slug = $service->generateSlug('Test Post');
$this->assertEquals('test-post', $slug);
}
public function test_increments_slug_on_duplicate(): void
{
Post::factory()->create(['slug' => 'test-post']);
$service = new PostService();
$slug = $service->generateSlug('Test Post');
$this->assertEquals('test-post-1', $slug);
}
public function test_post_excerpt_returns_limited_content(): void
{
$post = new Post(['content' => str_repeat('a', 200)]);
$excerpt = $post->excerpt;
$this->assertLessThanOrEqual(100, strlen($excerpt));
}
}Pest PHP
<?php
use App\Models\{User, Post};
it('allows authenticated users to create posts', function () {
$user = User::factory()->create();
$this->actingAs($user)
->post('/api/posts', [
'title' => 'Test Post',
'content' => 'Content',
])
->assertStatus(201);
expect(Post::count())->toBe(1);
});
it('prevents guests from creating posts', function () {
$this->post('/api/posts', [
'title' => 'Test Post',
'content' => 'Content',
])->assertStatus(401);
});
test('post requires title and content', function () {
$user = User::factory()->create();
$this->actingAs($user)
->post('/api/posts', [])
->assertJsonValidationErrors(['title', 'content']);
});
// Datasets
it('validates title length', function (string $title, bool $shouldPass) {
$user = User::factory()->create();
$response = $this->actingAs($user)->post('/api/posts', [
'title' => $title,
'content' => 'Content',
]);
if ($shouldPass) {
$response->assertStatus(201);
} else {
$response->assertJsonValidationErrors(['title']);
}
})->with([
['AB', false], // Too short
['ABC', true], // Minimum valid
[str_repeat('A', 255), true], // Maximum valid
[str_repeat('A', 256), false], // Too long
]);
// Hooks
beforeEach(function () {
$this->user = User::factory()->create();
});
afterEach(function () {
// Cleanup
});Factories
namespace Database\Factories;
use App\Models\{User, Category};
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
public function definition(): array
{
return [
'title' => fake()->sentence(),
'slug' => fake()->slug(),
'content' => fake()->paragraphs(3, true),
'excerpt' => fake()->text(100),
'published_at' => fake()->dateTimeBetween('-1 year', 'now'),
'user_id' => User::factory(),
'category_id' => Category::factory(),
];
}
public function unpublished(): static
{
return $this->state(fn (array $attributes) => [
'published_at' => null,
]);
}
public function published(): static
{
return $this->state(fn (array $attributes) => [
'published_at' => now(),
]);
}
public function forUser(User $user): static
{
return $this->state(fn (array $attributes) => [
'user_id' => $user->id,
]);
}
public function configure(): static
{
return $this->afterCreating(function (Post $post) {
$post->tags()->attach(
Tag::factory()->count(3)->create()
);
});
}
}
// Usage
$post = Post::factory()->create();
$unpublished = Post::factory()->unpublished()->create();
$posts = Post::factory()->count(10)->create();
$userPosts = Post::factory()->forUser($user)->count(5)->create();
// With relationships
$post = Post::factory()
->has(Comment::factory()->count(3))
->create();
// For relationship
$posts = Post::factory()
->count(3)
->for($user)
->create();Mocking
use App\Services\ExternalApiService;
use Illuminate\Support\Facades\Http;
public function test_fetches_data_from_external_api(): void
{
Http::fake([
'api.example.com/*' => Http::response([
'data' => ['id' => 1, 'name' => 'Test'],
], 200),
]);
$service = new ExternalApiService();
$result = $service->fetchData();
$this->assertEquals('Test', $result['name']);
Http::assertSent(function ($request) {
return $request->url() === 'https://api.example.com/data' &&
$request->hasHeader('Authorization');
});
}
// Mock events
use Illuminate\Support\Facades\Event;
Event::fake([PostCreated::class]);
// Test code that dispatches events
Event::assertDispatched(PostCreated::class, function ($event) {
return $event->post->id === 1;
});
// Mock queues
use Illuminate\Support\Facades\Queue;
Queue::fake();
// Test code that dispatches jobs
Queue::assertPushed(ProcessPost::class);
Queue::assertPushed(ProcessPost::class, 2);
Queue::assertPushed(ProcessPost::class, function ($job) {
return $job->post->id === 1;
});
// Mock notifications
use Illuminate\Support\Facades\Notification;
Notification::fake();
// Test code that sends notifications
Notification::assertSentTo($user, PostPublished::class);
// Mock storage
use Illuminate\Support\Facades\Storage;
Storage::fake('public');
// Test file upload
Storage::disk('public')->assertExists('file.jpg');
Storage::disk('public')->assertMissing('missing.jpg');Database Testing
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class PostTest extends TestCase
{
use RefreshDatabase; // Migrate database before each test
// Or use transactions
use DatabaseTransactions; // Rollback after each test
public function test_database_assertions(): void
{
$post = Post::factory()->create([
'title' => 'Test Post',
]);
$this->assertDatabaseHas('posts', [
'title' => 'Test Post',
]);
$post->delete();
$this->assertDatabaseMissing('posts', [
'id' => $post->id,
]);
$this->assertSoftDeleted('posts', [
'id' => $post->id,
]);
}
public function test_model_exists(): void
{
$post = Post::factory()->create();
$this->assertModelExists($post);
$post->delete();
$this->assertModelMissing($post);
}
}API Testing
public function test_api_returns_paginated_posts(): void
{
Post::factory()->count(30)->create();
$response = $this->get('/api/posts');
$response->assertStatus(200)
->assertJsonStructure([
'data' => [
'*' => ['id', 'title', 'content'],
],
'meta' => ['total', 'current_page', 'last_page'],
'links' => ['first', 'last', 'prev', 'next'],
])
->assertJsonCount(15, 'data'); // Default per page
}
public function test_api_filters_posts_by_category(): void
{
$category = Category::factory()->create();
Post::factory()->count(5)->create(['category_id' => $category->id]);
Post::factory()->count(5)->create();
$response = $this->get("/api/posts?category={$category->id}");
$response->assertJsonCount(5, 'data')
->assertJson([
'data' => [
['category_id' => $category->id],
],
]);
}Authentication Testing
use Laravel\Sanctum\Sanctum;
public function test_authenticated_user_can_access_endpoint(): void
{
$user = User::factory()->create();
Sanctum::actingAs($user, ['*']);
$response = $this->get('/api/user');
$response->assertStatus(200)
->assertJson([
'data' => [
'id' => $user->id,
'email' => $user->email,
],
]);
}
public function test_user_with_wrong_ability_cannot_access(): void
{
$user = User::factory()->create();
Sanctum::actingAs($user, ['view-posts']);
$response = $this->post('/api/posts', [
'title' => 'Test',
'content' => 'Content',
]);
$response->assertStatus(403);
}Running Tests
# Run all tests
php artisan test
# Run specific test
php artisan test --filter=test_user_can_create_post
# Run test file
php artisan test tests/Feature/PostTest.php
# Parallel testing
php artisan test --parallel
# With coverage
php artisan test --coverage
# Coverage minimum
php artisan test --coverage --min=80
# Stop on failure
php artisan test --stop-on-failure
# Pest specific
./vendor/bin/pest
./vendor/bin/pest --filter=PostTest
./vendor/bin/pest --coverageBest Practices
1. Use RefreshDatabase - Clean database for each test 2. Use factories - Don't manually create test data 3. Test one thing - Each test should verify one behavior 4. Use descriptive names - test_user_can_create_post 5. AAA pattern - Arrange, Act, Assert 6. Mock external services - Don't make real API calls 7. Fake queues and events - Test async code synchronously 8. Test edge cases - Invalid data, permissions, etc. 9. Achieve >85% coverage - Test critical paths 10. Run tests in CI/CD - Automate test execution
{
"schema_version": "2.0",
"meta": {
"generated_at": "2026-02-13T08:54:07.572Z",
"slug": "jeffallan-laravel-specialist",
"source_url": "https://github.com/jeffallan/claude-skills/tree/main/skills/laravel-specialist/",
"source_ref": "main",
"model": "claude",
"analysis_version": "3.0.0",
"source_type": "community",
"content_hash": "0feecda7ecfa48bb6bec0c0c87cf64331c902ec5650b7ea00d00920d77c51629",
"tree_hash": "8e4bd6d772f18b10673527ecf0a016780154bb5462dbb87f18bdfba14527d78e"
},
"skill": {
"name": "laravel-specialist",
"description": "Use when building Laravel 10+ applications requiring Eloquent ORM, API resources, or queue systems. Invoke for Laravel models, Livewire components, Sanctum authentication, Horizon queues.",
"summary": "Senior Laravel specialist for building Laravel 10+ applications with Eloquent ORM, API resources, queue systems, and Livewire components.",
"icon": "📦",
"version": "1.0.0",
"author": "jeffallan",
"license": "MIT",
"category": "coding",
"tags": [
"Laravel",
"PHP",
"Eloquent ORM",
"Livewire",
"API Development"
],
"supported_tools": [
"claude",
"codex",
"claude-code"
],
"risk_factors": []
},
"security_audit": {
"risk_level": "safe",
"is_blocked": false,
"safe_to_publish": true,
"summary": "All static scanner findings are false positives. The skill contains legitimate Laravel PHP code examples with no security risks. Scanner misidentified PHP syntax as shell commands, Laravel methods as weak crypto, and standard config patterns as credential access.",
"risk_factor_evidence": [],
"critical_findings": [],
"high_findings": [],
"medium_findings": [
{
"title": "External Commands Detection - False Positive",
"description": "Static scanner flagged PHP code blocks as 'Ruby/shell backtick execution'. This is a false positive - the files contain legitimate Laravel PHP code examples, not shell commands.",
"locations": [
{
"file": "references/eloquent.md",
"line_start": 5,
"line_end": 340
},
{
"file": "references/livewire.md",
"line_start": 5,
"line_end": 512
},
{
"file": "references/queues.md",
"line_start": 5,
"line_end": 410
},
{
"file": "references/routing.md",
"line_start": 5,
"line_end": 362
},
{
"file": "references/testing.md",
"line_start": 5,
"line_end": 509
},
{
"file": "SKILL.md",
"line_start": 49,
"line_end": 53
}
],
"confidence": 0.95,
"confidence_reasoning": "PHP code blocks containing Laravel examples are clearly not shell execution. The scanner misinterprets PHP syntax as backtick commands."
}
],
"low_findings": [
{
"title": "Weak Cryptographic Algorithm - False Positive",
"description": "Scanner flagged 'oldestOfMany()' and similar Laravel methods as 'weak cryptographic algorithm'. This is a false positive - these are standard Eloquent ORM methods.",
"locations": [
{
"file": "references/eloquent.md",
"line_start": 68,
"line_end": 230
},
{
"file": "references/livewire.md",
"line_start": 19,
"line_end": 36
},
{
"file": "references/queues.md",
"line_start": 103,
"line_end": 338
},
{
"file": "references/routing.md",
"line_start": 8,
"line_end": 111
},
{
"file": "references/testing.md",
"line_start": 295,
"line_end": 516
},
{
"file": "SKILL.md",
"line_start": 3,
"line_end": 85
}
],
"confidence": 0.95,
"confidence_reasoning": "The scanner misinterprets 'oldestOfMany' and similar method names as cryptographic issues. No encryption code exists in this skill."
},
{
"title": "Environment Variable Access - False Positive",
"description": "Scanner flagged Laravel env() config calls as 'AWS credential environment variables'. This is standard Laravel configuration pattern - secure by design.",
"locations": [
{
"file": "references/queues.md",
"line_start": 396,
"line_end": 397
}
],
"confidence": 0.95,
"confidence_reasoning": "Using env() to read AWS credentials from environment is the recommended secure pattern in Laravel. No credential exfiltration present."
},
{
"title": "Hardcoded URL - False Positive",
"description": "Scanner flagged test URLs in Laravel CORS config and HTTP testing. These are test/dummy URLs, not real external calls.",
"locations": [
{
"file": "references/routing.md",
"line_start": 356,
"line_end": 356
},
{
"file": "references/testing.md",
"line_start": 311,
"line_end": 311
},
{
"file": "SKILL.md",
"line_start": 6,
"line_end": 6
}
],
"confidence": 0.95,
"confidence_reasoning": "URLs like 'http://localhost:3000' and 'https://api.example.com/data' are test URLs for CORS and HTTP testing, not real network calls."
},
{
"title": "System Reconnaissance - False Positive",
"description": "Scanner misinterprets Laravel Artisan commands and method names as system reconnaissance.",
"locations": [
{
"file": "references/eloquent.md",
"line_start": 147,
"line_end": 350
},
{
"file": "references/livewire.md",
"line_start": 28,
"line_end": 470
},
{
"file": "references/queues.md",
"line_start": 30,
"line_end": 341
},
{
"file": "references/routing.md",
"line_start": 157,
"line_end": 157
},
{
"file": "references/testing.md",
"line_start": 16,
"line_end": 520
},
{
"file": "SKILL.md",
"line_start": 60,
"line_end": 60
}
],
"confidence": 0.95,
"confidence_reasoning": "Laravel Artisan commands like 'php artisan' and method names are standard framework patterns, not reconnaissance tools."
},
{
"title": "With Statement Detection - False Positive",
"description": "Scanner incorrectly flags Laravel's with() eager loading method as Python's deprecated 'with statement'.",
"locations": [
{
"file": "references/eloquent.md",
"line_start": 222,
"line_end": 229
},
{
"file": "references/livewire.md",
"line_start": 497,
"line_end": 497
},
{
"file": "references/routing.md",
"line_start": 83,
"line_end": 223
},
{
"file": "references/testing.md",
"line_start": 202,
"line_end": 202
}
],
"confidence": 0.95,
"confidence_reasoning": "with() is Laravel's Eloquent eager loading method, not Python's deprecated with statement. This is legitimate ORM syntax."
}
],
"dangerous_patterns": [],
"files_scanned": 6,
"total_lines": 2265,
"audit_model": "claude",
"audited_at": "2026-02-13T08:54:07.572Z",
"risk_factors": []
},
"content": {
"user_title": "Build Laravel 10+ Applications with AI",
"value_statement": "Get expert Laravel development assistance for Eloquent ORM, REST APIs, queue systems, and Livewire components. The skill provides senior-level guidance for building scalable PHP applications.",
"seo_keywords": [
"Laravel development",
"Laravel specialist",
"Eloquent ORM",
"Laravel API",
"Livewire tutorial",
"PHP framework",
"Laravel queues",
"Claude Code",
"Claude",
"Codex"
],
"actual_capabilities": [
"Design and implement Eloquent models with relationships, scopes, and attribute casting",
"Build RESTful APIs using Laravel API resources and proper authentication",
"Create queue jobs with proper error handling, retry logic, and batch processing",
"Develop reactive interfaces using Livewire components with wire:model binding",
"Write comprehensive feature and unit tests using Pest/PHPUnit",
"Optimize database queries using eager loading, query scopes, and indexing"
],
"limitations": [
"Cannot execute code or interact with local filesystem - provides guidance only",
"Does not have access to your specific codebase - code examples are generic",
"Does not handle deployment or server configuration",
"Cannot access external APIs or services on your behalf"
],
"use_cases": [
{
"title": "Build a new Laravel API",
"description": "Create a RESTful API with authentication, resource transformers, and proper error handling using Laravel API resources.",
"target_user": "Backend developers building web APIs with Laravel"
},
{
"title": "Implement complex Eloquent relationships",
"description": "Design models with hasMany, belongsToMany, hasOneThrough, and polymorphic relationships with proper eager loading.",
"target_user": "Developers working with complex data models"
},
{
"title": "Create background job processing",
"description": "Set up queue jobs with retry logic, failed job handling, and batch processing for time-consuming tasks.",
"target_user": "Developers needing async task processing"
}
],
"prompt_templates": [
{
"title": "Create Eloquent Model",
"prompt": "Create an Eloquent model for [ModelName] with relationships to User and Tag models. Include soft deletes, proper casts for dates, and a scope for filtering by status.",
"scenario": "Need to create a new Laravel model with relationships"
},
{
"title": "Build API Resource",
"prompt": "Create a Laravel API resource that transforms a Post model with its author and comments. Include proper data type casting and conditional attributes.",
"scenario": "Building a REST API endpoint"
},
{
"title": "Implement Queue Job",
"prompt": "Create a Laravel queue job that sends an email notification with retry logic, a unique job ID, and proper timeout configuration.",
"scenario": "Setting up background task processing"
},
{
"title": "Livewire Component",
"prompt": "Create a Livewire component for a post editor with wire:model binding, form validation, and proper error handling.",
"scenario": "Building reactive interfaces"
}
],
"output_examples": [
{
"input": "Create an Eloquent model for a Blog Post with author relationship and published scope",
"output": "A complete PHP model file with HasFactory, SoftDeletes, proper $fillable, $casts, relationships (belongsTo User, hasMany Comments), and a local scope for filtering published posts."
},
{
"input": "Build a REST API endpoint for listing posts with pagination",
"output": "Controller method using API Resource, proper pagination, and Eloquent withCount for comment totals."
}
],
"best_practices": [
"Always use eager loading (with()) to prevent N+1 query problems when accessing relationships",
"Use API Resources to transform model data for consistent API responses",
"Make queue jobs idempotent so they can be safely retried on failure",
"Write feature tests that cover the full request lifecycle rather than just unit tests"
],
"anti_patterns": [
"Avoid N+1 queries by not using relationship access in loops without eager loading",
"Do not put business logic in controllers - use service classes instead",
"Do not store sensitive data unencrypted - use Laravel encryption features",
"Avoid hardcoding configuration - use environment variables and config files"
],
"faq": [
{
"question": "What Laravel versions does this skill support?",
"answer": "This skill covers Laravel 10+ with PHP 8.2+. It includes modern patterns like readonly properties, enums, and typed properties."
},
{
"question": "Can this skill write tests for my code?",
"answer": "Yes, the skill provides guidance on writing feature and unit tests using Pest PHP or PHPUnit with proper mocking."
},
{
"question": "Does this skill help with Livewire?",
"answer": "Yes, it includes comprehensive guidance on Livewire components, wire:model, actions, and real-time updates."
},
{
"question": "Can this skill set up authentication?",
"answer": "Yes, it covers Laravel Sanctum for SPA authentication and API token authentication with proper security practices."
},
{
"question": "Does this skill cover queue jobs?",
"answer": "Yes, it includes job creation, retry logic, failed job handling, batch processing, and Horizon configuration."
},
{
"question": "How does this skill handle database optimization?",
"answer": "It provides guidance on query optimization, eager loading, database indexing, and proper use of scopes to prevent performance issues."
}
]
},
"file_structure": [
{
"name": "references",
"type": "dir",
"path": "references",
"children": [
{
"name": "eloquent.md",
"type": "file",
"path": "references/eloquent.md",
"lines": 352
},
{
"name": "livewire.md",
"type": "file",
"path": "references/livewire.md",
"lines": 513
},
{
"name": "queues.md",
"type": "file",
"path": "references/queues.md",
"lines": 424
},
{
"name": "routing.md",
"type": "file",
"path": "references/routing.md",
"lines": 363
},
{
"name": "testing.md",
"type": "file",
"path": "references/testing.md",
"lines": 523
}
]
},
{
"name": "SKILL.md",
"type": "file",
"path": "SKILL.md",
"lines": 90
}
]
}