
Laravel Architecture
- 121 installs
- 22 repo stars
- Updated August 3, 2026
- fusengine/agents
For development and infrastructure management.
About
laravel-architecture is an AI coding tool that enhances development workflows. Builders use it for infrastructure, integration, and platform development within the catalog ecosystem.
- laravel-architecture
- Development
Laravel Architecture by the numbers
- 121 all-time installs (skills.sh)
- Ranked #2,818 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/fusengine/agents --skill laravel-architectureAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 121 |
|---|---|
| repo stars | ★ 22 |
| Last updated | August 3, 2026 |
| Repository | fusengine/agents ↗ |
What it does
For development and infrastructure management.
Files
Laravel Architecture Patterns
Agent Workflow (MANDATORY)
Before ANY implementation, use TeamCreate to spawn 3 agents:
1. fuse-ai-pilot:explore-codebase - Analyze existing architecture 2. fuse-ai-pilot:research-expert - Verify Laravel patterns via Context7 3. mcp__context7__query-docs - Check service container and DI patterns
After implementation, run fuse-ai-pilot:sniper for validation.
---
Overview
Laravel architecture focuses on clean separation of concerns, dependency injection, and maintainable code organization. This skill covers everything from project structure to production deployment.
When to Use
- Structuring new Laravel projects
- Implementing services, repositories, actions
- Setting up dependency injection
- Configuring development environments
- Deploying to production
---
Critical Rules
1. Thin controllers - Delegate business logic to services 2. Interfaces in app/Contracts/ - Never alongside implementations 3. DI over facades - Constructor injection for testability 4. Files < 100 lines - Split larger files per SOLID 5. Environment separation - .env never committed
---
Architecture
app/
├── Actions/ # Single-purpose action classes
├── Contracts/ # Interfaces (DI)
├── DTOs/ # Data transfer objects
├── Enums/ # PHP 8.1+ enums
├── Events/ # Domain events
├── Http/
│ ├── Controllers/ # Thin controllers
│ ├── Middleware/ # Request filters
│ ├── Requests/ # Form validation
│ └── Resources/ # API transformations
├── Jobs/ # Queued jobs
├── Listeners/ # Event handlers
├── Models/ # Eloquent models only
├── Policies/ # Authorization
├── Providers/ # Service registration
├── Repositories/ # Data access layer
└── Services/ # Business logic---
Reference Guide
Core Architecture
| Reference | When to Use |
|---|---|
| container.md | Dependency injection, binding, resolution |
| providers.md | Service registration, bootstrapping |
| facades.md | Static proxies, real-time facades |
| contracts.md | Interfaces, loose coupling |
| structure.md | Directory organization |
| lifecycle.md | Request handling flow |
Configuration & Setup
| Reference | When to Use |
|---|---|
| configuration.md | Environment, config files |
| installation.md | New project setup |
| upgrade.md | Version upgrades, breaking changes |
| releases.md | Release notes, versioning |
Development Environments
| Reference | When to Use |
|---|---|
| sail.md | Docker development |
| valet.md | macOS native development |
| homestead.md | Vagrant (legacy) |
| octane.md | High-performance servers |
Utilities & Tools
| Reference | When to Use |
|---|---|
| artisan.md | CLI commands, custom commands |
| helpers.md | Global helper functions |
| filesystem.md | File storage, S3, local |
| processes.md | Shell command execution |
| context.md | Request-scoped data sharing |
Advanced Features
| Reference | When to Use |
|---|---|
| pennant.md | Feature flags |
| mcp.md | Model Context Protocol |
| concurrency.md | Parallel execution |
Operations
| Reference | When to Use |
|---|---|
| deployment.md | Production deployment |
| envoy.md | SSH task automation |
| logging.md | Log channels, formatting |
| errors.md | Exception handling |
| packages.md | Creating packages |
---
Templates
| Template | Purpose |
|---|---|
| UserService.php.md | Service + repository pattern |
| AppServiceProvider.php.md | DI bindings, bootstrapping |
| ArtisanCommand.php.md | CLI commands, signatures, I/O |
| McpServer.php.md | MCP servers, tools, resources, prompts |
| PennantFeature.php.md | Feature flags, A/B testing |
| Envoy.blade.php.md | SSH deployment automation |
| sail-config.md | Docker Sail configuration |
| octane-config.md | FrankenPHP, Swoole, RoadRunner |
---
Feature Matrix
| Feature | Reference | Priority |
|---|---|---|
| Service Container | container.md | High |
| Service Providers | providers.md | High |
| Directory Structure | structure.md | High |
| Configuration | configuration.md | High |
| Installation | installation.md | High |
| Octane (Performance) | octane.md | High |
| Sail (Docker) | sail.md | High |
| Artisan CLI | artisan.md | Medium |
| Deployment | deployment.md | Medium |
| Envoy (SSH) | envoy.md | Medium |
| Facades | facades.md | Medium |
| Contracts | contracts.md | Medium |
| Valet (macOS) | valet.md | Medium |
| Upgrade Guide | upgrade.md | Medium |
| Logging | logging.md | Medium |
| Errors | errors.md | Medium |
| Lifecycle | lifecycle.md | Medium |
| Filesystem | filesystem.md | Medium |
| Helpers | helpers.md | Low |
| Pennant (Flags) | pennant.md | Low |
| Context | context.md | Low |
| Processes | processes.md | Low |
| Concurrency | concurrency.md | Low |
| MCP | mcp.md | Low |
| Packages | packages.md | Low |
| Releases | releases.md | Low |
| Homestead | homestead.md | Low |
---
Quick Reference
Service Injection
public function __construct(
private readonly UserServiceInterface $userService,
) {}Service Provider Binding
public function register(): void
{
$this->app->bind(UserServiceInterface::class, UserService::class);
$this->app->singleton(CacheService::class);
}Artisan Command
php artisan make:provider CustomServiceProvider
php artisan make:command ProcessOrdersEnvironment Access
$debug = env('APP_DEBUG', false);
$config = config('app.name');---
Laravel 13 Notes
Stack mis à jour
- Symfony 7.4 et 8.0 supportés en parallèle (HttpFoundation, Console, Mailer)
- PHP 8.3 minimum (8.2 retiré)
- pda/pheanstalk 8.0+ requis si driver Beanstalk
Cache::touch() API
Nouvelle méthode pour rafraîchir le TTL sans recalculer la valeur.
Cache::touch('user:123', now()->addHour());
Cache::touch(['user:123', 'user:456'], 3600);Queue::route() pour routing dynamique
Voir [[laravel-queues]] pour le routing déclaratif par job (connexion/queue cible via configuration plutôt que sur chaque job).
new Model() dans boot() → LogicException
Laravel 13 jette une LogicException si vous instanciez un modèle Eloquent dans register() d'un ServiceProvider (container pas prêt). Utiliser boot() ou un listener.
Migration Laravel 13 → 13
| Sujet | Avant (12) | Après (13) |
|---|---|---|
| PHP minimum | 8.2 | 8.3 |
| PHPUnit | 11 | 12 |
| Pest | 3 | 4 |
| CSRF | VerifyCsrfToken | `PreventRequestForgery` (origin-aware) |
| Cache prefix | underscore | hyphens par défaut (configurer CACHE_PREFIX, REDIS_PREFIX, SESSION_COOKIE pour rétro-compat) |
| Beanstalk | pheanstalk 7.x | pheanstalk 8.0+ |
| Symfony | 7.x | 7.4 / 8.0 |
| Model boot | toléré | `new Model()` → LogicException |
| Config | — | nouveau serializable_classes (allowlist hardening) |
# Rétro-compat cache prefixes pour upgrade depuis L12
CACHE_PREFIX=laravel_cache_
REDIS_PREFIX=laravel_database_
SESSION_COOKIE=laravel_session// config/app.php — durcissement deserialize
'serializable_classes' => [
App\DTO\PaymentDto::class,
App\DTO\OrderDto::class,
],Best Practices
DO
- Utiliser
final readonly classpour DTOs et Value Objects (PHP 8.3+) - Injecter via constructor promotion +
interface(DI inversion) - Logger via
Context::add()pour propager metadata entre jobs/requêtes - Configurer
serializable_classesen production - Préférer
app(Contract::class)surApp::make()(typage strict)
DON'T
- Instancier des modèles dans
register()(→ LogicException L13) - Hardcoder des chemins absolus (utiliser
base_path(),storage_path()) - Mélanger Repository et Service (un par responsabilité)
- Bypasser le container avec
new ConcreteClass() - Ignorer le bump du préfixe cache lors d'un upgrade depuis L12
Artisan Console
Overview
Artisan is Laravel's CLI interface with built-in commands and support for custom commands.
Common Commands
Development
| Command | Purpose |
|---|---|
php artisan serve | Dev server |
php artisan tinker | REPL |
php artisan route:list | List routes |
Code Generation
| Command | Creates |
|---|---|
make:model Post -mfc | Model + migration + factory + controller |
make:controller PostController --api | API controller |
make:request StorePostRequest | Form Request |
make:resource PostResource | API Resource |
make:migration create_posts_table | Migration |
make:command SendEmails | Custom command |
Database & Cache
| Command | Purpose |
|---|---|
migrate | Run migrations |
migrate:fresh --seed | Drop all, migrate, seed |
optimize | Cache config, routes, views |
optimize:clear | Clear all caches |
Creating Commands
php artisan make:command SendEmailsCommand Structure
class SendEmails extends Command
{
protected $signature = 'emails:send {user} {--queue}';
protected $description = 'Send emails to user';
public function handle(): int
{
$userId = $this->argument('user');
$this->info('Sending emails...');
return Command::SUCCESS;
}
}Signature Syntax
| Pattern | Meaning |
|---|---|
{user} | Required |
{user?} | Optional |
{user=default} | With default |
{--queue} | Boolean option |
| `{--Q\ | queue}` |
Input/Output
| Method | Purpose |
|---|---|
$this->argument('name') | Get argument |
$this->option('name') | Get option |
$this->info('msg') | Green text |
$this->error('msg') | Red text |
$this->ask('Question?') | Prompt |
$this->confirm('Sure?') | Yes/no |
Calling Commands
Artisan::call('emails:send', ['user' => 1]);
Artisan::queue('emails:send', ['user' => 1]);Scheduling
// routes/console.php
Schedule::command('emails:send')->daily();Best Practices
1. Return codes - SUCCESS, FAILURE, INVALID 2. Validate input - Check arguments 3. Inject dependencies - Constructor injection
Related References
- providers.md - Registering commands
Concurrency
Overview
The Concurrency facade executes multiple slow, independent tasks in parallel for performance gains.
---
When to Use
| Scenario | Use Concurrency | Alternative |
|---|---|---|
| Multiple independent DB queries | ✅ Yes | - |
| External API calls in parallel | ✅ Yes | - |
| Tasks dependent on each other | ❌ No | Sequential |
| Long-running background work | ❌ No | Queues |
| Fire-and-forget tasks | ✅ defer() | Events |
---
Drivers
| Driver | Context | Performance | Setup |
|---|---|---|---|
| process | Web + CLI | Good | Default |
| fork | CLI only | Better | spatie/fork |
| sync | Testing | None (sequential) | Built-in |
Fork Driver Setup
composer require spatie/forkNote: Fork only works in CLI (Artisan commands, queues). Not in web requests.
---
Key Methods
| Method | Purpose | Returns |
|---|---|---|
run([closures]) | Execute in parallel | Array of results |
defer([closures]) | Execute after response | void |
driver('fork') | Use specific driver | Concurrency instance |
---
Decision Guide
Tasks independent?
├── No → Run sequentially
└── Yes → Need results?
├── Yes → Concurrency::run()
└── No → Concurrency::defer()---
How It Works
1. Closures are serialized 2. Dispatched to hidden Artisan command 3. Each runs in separate PHP process 4. Results serialized back to parent
Important: Closures must be serializable (no anonymous classes, no $this from non-serializable objects).
---
Performance Patterns
| Pattern | Without Concurrency | With Concurrency |
|---|---|---|
| 3 queries × 100ms each | 300ms | ~100ms |
| 5 API calls × 500ms each | 2500ms | ~500ms |
---
Use Cases
Aggregating Counts
Multiple independent database counts executed in parallel.
Multiple API Calls
Fetching data from multiple external services simultaneously.
Post-Response Tasks
Using defer() for analytics, logging, or notifications after sending response.
---
Constraints
| Constraint | Reason |
|---|---|
| Closures must be serializable | Sent to child process |
| No shared state | Each process isolated |
| Fork only CLI | PHP limitation |
| Results must be serializable | Returned to parent |
---
Best Practices
DO
- Use for truly independent tasks
- Use
defer()for non-critical post-response work - Consider
forkdriver in CLI for performance - Keep closures simple and serializable
DON'T
- Don't use for dependent tasks (use sequential)
- Don't expect shared memory
- Don't use fork in web requests
- Don't defer critical operations
---
Testing
Use sync driver in tests:
// config/concurrency.php or test setup
'default' => 'sync'This runs closures sequentially for predictable test results.
Configuration
Overview
Laravel configuration is stored in config/ directory. Environment-specific values use .env file. Always access config via config() helper, never env() in application code.
Environment File
APP_NAME="My App"
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=myapp
CACHE_DRIVER=redis
QUEUE_CONNECTION=redisAccessing Config
// Get value
$name = config('app.name');
$debug = config('app.debug');
// With default
$timeout = config('services.api.timeout', 30);
// Set at runtime
config(['app.timezone' => 'America/Chicago']);Config Files
| File | Purpose |
|---|---|
app.php | Application settings |
auth.php | Authentication guards |
cache.php | Cache drivers |
database.php | Database connections |
filesystems.php | Storage disks |
logging.php | Log channels |
mail.php | Email drivers |
queue.php | Queue connections |
services.php | Third-party services |
session.php | Session drivers |
Environment Detection
if (App::environment('production')) {
// Production-only code
}
if (App::environment(['local', 'staging'])) {
// Local or staging
}Config Caching
In production, cache configuration:
php artisan config:cacheImportant: After caching, env() returns null. Always use config().
Clear cache:
php artisan config:clearCustom Config Files
Create config/payment.php:
return [
'gateway' => env('PAYMENT_GATEWAY', 'stripe'),
'key' => env('PAYMENT_KEY'),
'secret' => env('PAYMENT_SECRET'),
];Access via config('payment.gateway').
Encrypted Environment
Store sensitive env vars encrypted:
php artisan env:encrypt
php artisan env:decryptBest Practices
1. Never use env() in code - Only in config files 2. Cache in production - config:cache 3. Don't commit .env - Use .env.example 4. Use secrets manager - For production secrets 5. Type cast - (bool), (int) in config files
Related References
- deployment.md - Production config
- providers.md - Config in providers
Service Container
Overview
The Service Container is Laravel's powerful tool for managing class dependencies and performing dependency injection. It's the foundation of the framework, automatically resolving dependencies declared in constructors.
Why Use the Container
| Benefit | Description |
|---|---|
| Automatic DI | Constructor dependencies resolved automatically |
| Loose Coupling | Bind interfaces to implementations |
| Testability | Swap implementations for testing |
| Singletons | Share instances across application |
Automatic Resolution
Laravel automatically resolves type-hinted dependencies:
class PostController extends Controller
{
public function __construct(
private readonly PostService $postService, // Auto-resolved
) {}
}Binding
Register bindings in Service Providers:
Simple Binding
$this->app->bind(PostService::class, function ($app) {
return new PostService($app->make(PostRepository::class));
});Interface to Implementation
$this->app->bind(
PostRepositoryInterface::class,
EloquentPostRepository::class
);Singleton
$this->app->singleton(PaymentGateway::class, function ($app) {
return new StripeGateway(config('services.stripe.key'));
});Instance
$this->app->instance(PaymentGateway::class, $gateway);Contextual Binding
Different implementations for different classes:
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('photos');
});Resolving
// From container
$service = app(PostService::class);
$service = resolve(PostService::class);
// With parameters
$service = app()->make(PostService::class, ['param' => $value]);Method Injection
Laravel also injects into controller methods:
public function store(Request $request, PostService $service)
{
// $service is auto-injected
}Tagging
Group related bindings:
$this->app->tag([EmailReport::class, PdfReport::class], 'reports');
// Resolve all tagged
$reports = app()->tagged('reports');Extending Bindings
Modify resolved instances:
$this->app->extend(PostService::class, function ($service, $app) {
return new CachedPostService($service);
});Best Practices
1. Bind interfaces - Not concrete classes 2. Use singletons - For expensive-to-create services 3. Contextual binding - When same interface needs different implementations 4. Constructor injection - Prefer over method injection
Related References
- providers.md - Where to register bindings
- facades.md - Static access to container services
- contracts.md - Laravel's interface contracts
Context
Overview
Laravel Context allows sharing data throughout the request lifecycle - across services, jobs, logs, and more. It's useful for request IDs, user info, and debugging data.
Basic Usage
use Illuminate\Support\Facades\Context;
// Add data
Context::add('request_id', Str::uuid());
Context::add('user_id', auth()->id());
// Get data
$requestId = Context::get('request_id');
// Check existence
if (Context::has('user_id')) {
// ...
}
// Get all
$all = Context::all();Adding Context
// Single value
Context::add('key', 'value');
// Multiple values
Context::add([
'request_id' => $requestId,
'user_id' => $userId,
]);
// Only if missing
Context::addIf('key', 'value');Retrieving Context
// Get single
$value = Context::get('key');
// With default
$value = Context::get('key', 'default');
// Get multiple
$data = Context::only(['request_id', 'user_id']);
// Pull (get and remove)
$value = Context::pull('key');Removing Context
// Remove single
Context::forget('key');
// Remove all
Context::flush();Hidden Context
Store sensitive data that won't appear in logs:
Context::addHidden('api_token', $token);
$token = Context::getHidden('api_token');Context in Logs
Context is automatically included in logs:
Context::add('request_id', Str::uuid());
Log::info('Processing order'); // Includes request_idContext in Jobs
Push context to queued jobs:
Context::add('user_id', auth()->id());
// Context available in job
dispatch(new ProcessOrder($order));Middleware Example
class AddRequestContext
{
public function handle(Request $request, Closure $next)
{
Context::add([
'request_id' => Str::uuid(),
'ip' => $request->ip(),
'url' => $request->fullUrl(),
]);
return $next($request);
}
}Dehydrating/Hydrating
For custom serialization:
Context::dehydrating(function (Repository $context) {
return $context->only(['request_id']);
});
Context::hydrating(function (Repository $context, array $data) {
$context->add($data);
});Best Practices
1. Request ID - Add unique ID early in request 2. User context - Add after authentication 3. Hide sensitive - Use addHidden() for tokens 4. Clean up - Context is cleared after request
Related References
- logging.md - Contextual logging
- errors.md - Context in error reports
Contracts
Overview
Contracts are Laravel's set of interfaces defining core framework services. They provide explicit, documented APIs for framework features, enabling loose coupling and easier testing.
Why Use Contracts
| Benefit | Description |
|---|---|
| Loose coupling | Depend on interface, not implementation |
| Documentation | Clear API definition |
| Swappable | Easy to change implementations |
| Testable | Mock interfaces in tests |
Contracts vs Facades
| Aspect | Contracts | Facades |
|---|---|---|
| Type | Interface | Static proxy |
| DI | Explicit injection | Implicit resolution |
| IDE support | Native | Needs helper |
| Testing | Mock interface | Fake facade |
Common Contracts
| Contract | Facade | Purpose |
|---|---|---|
Illuminate\Contracts\Auth\Guard | Auth | Authentication |
Illuminate\Contracts\Cache\Repository | Cache | Caching |
Illuminate\Contracts\Config\Repository | Config | Configuration |
Illuminate\Contracts\Events\Dispatcher | Event | Events |
Illuminate\Contracts\Filesystem\Filesystem | Storage | File storage |
Illuminate\Contracts\Mail\Mailer | Mail | |
Illuminate\Contracts\Queue\Queue | Queue | Job queuing |
Illuminate\Contracts\Validation\Factory | Validator | Validation |
Using Contracts
Type-hint the contract in your constructor:
use Illuminate\Contracts\Cache\Repository as Cache;
class UserRepository
{
public function __construct(
private readonly Cache $cache,
) {}
public function find(int $id): ?User
{
return $this->cache->remember(
"user.{$id}",
3600,
fn () => User::find($id)
);
}
}Contract Location
All contracts are in Illuminate\Contracts namespace:
Illuminate\Contracts\
├── Auth\
├── Broadcasting\
├── Bus\
├── Cache\
├── Config\
├── Console\
├── Container\
├── Cookie\
├── Database\
├── Encryption\
├── Events\
├── Filesystem\
├── Foundation\
├── Hashing\
├── Http\
├── Mail\
├── Notifications\
├── Pagination\
├── Pipeline\
├── Queue\
├── Redis\
├── Routing\
├── Session\
├── Support\
├── Translation\
├── Validation\
└── View\Creating Your Own Contracts
// Define interface
namespace App\Contracts;
interface PaymentGateway
{
public function charge(int $amount, string $token): PaymentResult;
public function refund(string $chargeId): RefundResult;
}
// Bind in provider
$this->app->bind(PaymentGateway::class, StripeGateway::class);
// Use via DI
public function __construct(private readonly PaymentGateway $gateway) {}Best Practices
1. Use for core services - Cache, Queue, etc. 2. Create for domain - Your own service interfaces 3. Bind in providers - Register implementations 4. Test with mocks - Mock interface, not implementation
Related References
- container.md - Binding contracts
- facades.md - Alternative access pattern
Deployment
Overview
Deploying Laravel requires proper server configuration, environment setup, and optimization. Follow this checklist for production deployments.
Server Requirements
- PHP >= 8.2
- Required extensions: Ctype, cURL, DOM, Fileinfo, Filter, Hash, Mbstring, OpenSSL, PCRE, PDO, Session, Tokenizer, XML
- Composer
- Web server (Nginx/Apache)
Deployment Steps
1. Clone/Upload Code
git clone repo /var/www/app
cd /var/www/app
composer install --optimize-autoloader --no-dev2. Environment Setup
cp .env.example .env
php artisan key:generate
# Edit .env with production values3. Directory Permissions
chmod -R 775 storage bootstrap/cache
chown -R www-data:www-data storage bootstrap/cache4. Database Migration
php artisan migrate --force5. Optimize
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
# Or all at once:
php artisan optimizeNginx Config
server {
listen 80;
server_name example.com;
root /var/www/app/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}Environment Variables
| Variable | Production Value |
|---|---|
APP_ENV | production |
APP_DEBUG | false |
APP_URL | Your domain |
LOG_LEVEL | error |
Queue Workers
# Supervisor config for queue workers
php artisan queue:work --sleep=3 --tries=3Scheduler
Add cron entry:
* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1Zero-Downtime Deploy
Use tools like Envoyer, Deployer, or GitHub Actions.
Best Practices
1. Never debug in production - APP_DEBUG=false 2. Cache everything - Config, routes, views 3. Use queue workers - For background jobs 4. Monitor logs - Set up log rotation 5. SSL - Always use HTTPS
Related References
- configuration.md - Environment config
- octane.md - High-performance deployment
Laravel Envoy
Overview
Envoy executes common operations on remote servers via SSH using Blade-style syntax. Define tasks for deployment, artisan commands, and maintenance in Envoy.blade.php.
Installation
composer require laravel/envoy --devBasic Structure
@servers(['web' => ['user@192.168.1.1']])
@task('deploy', ['on' => 'web'])
cd /var/www/app
git pull origin main
php artisan migrate --force
@endtaskRunning Tasks
php vendor/bin/envoy run deploy
php vendor/bin/envoy run deploy --branch=mainVariables
Pass via CLI and use in tasks with Blade syntax:
@task('deploy', ['on' => 'web'])
@if ($branch)
git pull origin {{ $branch }}
@endif
@endtaskMultiple Servers
@servers(['web-1' => '192.168.1.1', 'web-2' => '192.168.1.2'])
@task('deploy', ['on' => ['web-1', 'web-2'], 'parallel' => true])
php artisan down && git pull && php artisan up
@endtaskStories (Task Groups)
@story('deploy')
pull-code
install-deps
migrate
@endstoryHooks
| Hook | When | Receives |
|---|---|---|
@before | Before each task | $task |
@after | After each task | $task |
@error | On failure | $task |
@success | All succeed | - |
@finished | Always | $exitCode |
Notifications
@finished
@slack('webhook-url', '#channel', 'Done!')
@discord('webhook-url')
@telegram('bot-id', 'chat-id')
@endfinishedBest Practices
1. Use stories - Group related tasks 2. Add confirmations - For destructive operations ('confirm' => true) 3. Send notifications - Keep team informed 4. Parallel execution - For independent server tasks
Related References
- deployment.md - Production deployment
Error Handling
Overview
Laravel's exception handler manages how exceptions are reported (logged) and rendered (shown to users).
Configuration
In bootstrap/app.php:
->withExceptions(function (Exceptions $exceptions) {
$exceptions->report(function (Throwable $e) {
// Custom reporting
});
$exceptions->render(function (NotFoundHttpException $e) {
return response()->json(['message' => 'Not found'], 404);
});
})Reporting
$exceptions->report(function (PaymentException $e) {
Sentry::captureException($e);
})->stop(); // Stop propagation
$exceptions->dontReport([InvalidOrderException::class]);Rendering
$exceptions->render(function (InvalidOrderException $e, Request $request) {
return response()->view('errors.invalid-order', [], 500);
});
// API responses
$exceptions->render(function (Throwable $e, Request $request) {
if ($request->expectsJson()) {
return response()->json(['message' => $e->getMessage()], 500);
}
});HTTP Exceptions
abort(404);
abort(403, 'Unauthorized.');
abort_if(!$user->isAdmin(), 403);
abort_unless($user->isAdmin(), 403);Custom Exceptions
class InsufficientFundsException extends Exception
{
public function report(): void
{
Log::warning('Insufficient funds');
}
public function render(Request $request): Response
{
return response()->json(['error' => 'Insufficient funds'], 402);
}
}Error Pages
Create in resources/views/errors/:
404.blade.php,403.blade.php,500.blade.php,503.blade.php
Best Practices
1. Custom exceptions - For domain errors 2. Consistent API format - Same error structure 3. Hide internals - No stack traces in production 4. Report to services - Sentry, Bugsnag
Related References
- logging.md - Logging exceptions
Facades
Overview
Facades provide a static-like interface to classes available in the service container. They act as proxies to underlying service instances, offering convenient syntax while maintaining testability.
How Facades Work
// Facade call
Cache::get('key');
// Actually resolves to
app('cache')->get('key');Facades extend Illuminate\Support\Facades\Facade and define a getFacadeAccessor() method.
Common Facades
| Facade | Service | Purpose |
|---|---|---|
Auth | Authentication | User auth |
Cache | Cache manager | Caching |
Config | Config repository | Configuration |
DB | Database manager | Database queries |
Event | Event dispatcher | Events |
File | Filesystem | File operations |
Hash | Hasher | Password hashing |
Log | Logger | Logging |
Mail | Mailer | Sending emails |
Queue | Queue manager | Job queuing |
Route | Router | Routing |
Session | Session manager | Sessions |
Storage | Filesystem | Cloud storage |
Validator | Validator factory | Validation |
Facades vs Dependency Injection
| Aspect | Facades | DI |
|---|---|---|
| Syntax | Cache::get() | $this->cache->get() |
| Testing | Fake with Cache::fake() | Mock in constructor |
| Discoverability | IDE needs helper | Clear dependencies |
| Coupling | Implicit | Explicit |
Real-Time Facades
Use any class as a facade:
use Facades\App\Services\PaymentService;
PaymentService::charge($amount);Prefix namespace with Facades\ to access any class statically.
Creating Custom Facades
// 1. Create facade class
class Payment extends Facade
{
protected static function getFacadeAccessor(): string
{
return PaymentService::class;
}
}
// 2. Register alias in config/app.php
'aliases' => [
'Payment' => App\Facades\Payment::class,
],Testing with Facades
// Fake the facade
Cache::fake();
// Assert interactions
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');Facade vs Contract
| Use | When |
|---|---|
| Facade | Quick prototyping, simple apps |
| Contract (Interface) | Large apps, explicit dependencies |
Best Practices
1. Prefer DI - In controllers and services 2. Use in config - Facades ok in config, routes 3. Real-time facades - For temporary facade access 4. Don't overuse - Hides dependencies
Related References
- container.md - Services behind facades
- contracts.md - Interface alternative
- providers.md - Registering facades
Filesystem / Storage
Overview
Laravel's filesystem provides a unified API for local and cloud storage (S3, GCS). The Storage facade works identically regardless of where files are stored.
Disks
Configured in config/filesystems.php:
| Disk | Purpose |
|---|---|
local | Private files (storage/app) |
public | Public files (storage/app/public) |
s3 | Amazon S3 bucket |
Basic Operations
Writing
| Method | Purpose |
|---|---|
Storage::put('file.txt', $contents) | Write |
Storage::putFile('avatars', $file) | Store upload |
Storage::putFileAs('avatars', $file, 'name.jpg') | Store with name |
Reading
| Method | Returns |
|---|---|
Storage::get('file.txt') | Contents |
Storage::exists('file.txt') | Boolean |
Storage::size('file.txt') | Bytes |
Deleting
Storage::delete('file.txt');
Storage::delete(['file1.txt', 'file2.txt']);Directories
Storage::files('directory');
Storage::allFiles('directory'); // Recursive
Storage::makeDirectory('path');
Storage::deleteDirectory('path');Disk Selection
Storage::disk('s3')->put('file.txt', $contents);
Storage::disk('public')->put('avatar.jpg', $file);File Uploads
$path = $request->file('avatar')->store('avatars');
$path = $request->file('avatar')->storeAs('avatars', 'avatar.jpg');
$path = $request->file('avatar')->store('avatars', 's3');Public Files
1. Store in public disk 2. Run php artisan storage:link 3. Get URL: Storage::disk('public')->url('file.jpg')
Temporary URLs (S3)
$url = Storage::temporaryUrl('file.pdf', now()->addMinutes(5));Streaming
return Storage::download('file.pdf', 'report.pdf');
return Storage::response('video.mp4');S3 Configuration
composer require league/flysystem-aws-s3-v3# .env
AWS_ACCESS_KEY_ID=key
AWS_SECRET_ACCESS_KEY=secret
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=bucketBest Practices
1. Use public disk - For user uploads 2. Validate uploads - Check types and sizes 3. Unique names - Use store() for auto names 4. Temporary URLs - For private S3 files
Related References
- configuration.md - Environment config
Laravel Helpers
Overview
Laravel provides global helper functions for common operations. These functions are used throughout the framework and available in your applications. Main categories include arrays, strings, paths, URLs, and utilities.
Helper Categories
| Category | Class/Prefix | Purpose |
|---|---|---|
| Arrays | Arr:: | Array manipulation, access, transformation |
| Strings | Str:: | String manipulation, formatting |
| Paths | *_path() | Application directory paths |
| URLs | url(), route() | URL generation |
| Misc | app(), config(), etc. | Framework utilities |
Array Helpers (Arr::)
The Arr class provides methods for working with arrays. Common use cases:
| Method | Purpose |
|---|---|
Arr::get($array, 'key.nested') | Get value using dot notation |
Arr::set($array, 'key', $value) | Set value using dot notation |
Arr::has($array, 'key') | Check if key exists |
Arr::only($array, ['key1', 'key2']) | Get only specified keys |
Arr::except($array, ['key']) | Get all except specified keys |
Arr::pluck($array, 'name') | Extract column values |
Arr::first($array, $callback) | Get first matching element |
Arr::flatten($array) | Flatten multi-dimensional array |
Arr::dot($array) | Flatten with dot notation keys |
String Helpers (Str::)
The Str class handles string operations. See strings.md for detailed reference.
| Method | Purpose |
|---|---|
Str::uuid() | Generate UUID |
Str::slug($text) | Create URL-friendly slug |
Str::random(32) | Generate random string |
Str::limit($text, 100) | Truncate with ellipsis |
Str::camel($text) | Convert to camelCase |
Str::snake($text) | Convert to snake_case |
Path Helpers
Functions returning absolute paths to application directories:
| Function | Returns |
|---|---|
app_path('Models') | app/Models |
base_path('config') | Project root + path |
config_path('app.php') | config/app.php |
database_path('migrations') | database/migrations |
public_path('images') | public/images |
resource_path('views') | resources/views |
storage_path('logs') | storage/logs |
URL Helpers
| Function | Purpose |
|---|---|
url('/path') | Generate full URL |
route('posts.show', $post) | Generate named route URL |
asset('css/app.css') | Generate asset URL |
secure_url('/path') | Generate HTTPS URL |
Utility Helpers
| Function | Purpose |
|---|---|
app() | Service container instance |
config('app.name') | Get config value |
env('APP_DEBUG') | Get environment variable |
logger('message') | Log message |
now() | Current Carbon instance |
today() | Current date Carbon |
collect($array) | Create Collection |
data_get($data, 'key') | Get from object/array |
filled($value) | Check if not blank |
blank($value) | Check if blank |
throw_if($condition) | Conditional throw |
throw_unless($condition) | Throw unless true |
retry(3, fn() => ...) | Retry operation |
Benchmarking
Measure execution time:
$time = Benchmark::measure(fn () => expensiveOperation());Deferred Functions
Run code after response is sent:
defer(fn () => cleanup());Pipeline
Process data through stages:
Pipeline::send($data)
->through([Stage1::class, Stage2::class])
->thenReturn();Best Practices
1. Use class methods - Prefer Arr::get() over data_get() for clarity 2. Avoid env() in code - Use config values instead 3. Type safety - Use Arr::integer(), Arr::boolean() for casting 4. Null safety - Helpers handle null gracefully
Related References
- configuration.md - Config and environment
- Laravel Docs - Full helper list
Laravel Homestead (Legacy)
Overview
Warning: Homestead is legacy and no longer actively maintained. Use Laravel Sail instead.
Homestead is a Vagrant box with PHP, Nginx, MySQL, and other tools. Runs on VirtualBox or Parallels.
Installation
git clone https://github.com/laravel/homestead.git ~/Homestead
cd ~/Homestead && git checkout release
bash init.sh # Creates Homestead.yamlConfiguration
# Homestead.yaml
provider: virtualbox # or parallels (Apple Silicon)
folders:
- map: ~/code/project1
to: /home/vagrant/project1
sites:
- map: project1.test
to: /home/vagrant/project1/public
php: "8.2"Commands
vagrant up # Start VM
vagrant ssh # SSH into VM
vagrant reload --provision # Reload config
vagrant halt # Stop VM
vagrant destroy # Remove VMDatabase
From host: localhost:33060 (MySQL), localhost:54320 (PostgreSQL) Credentials: homestead / secret
Multiple PHP Versions
sites:
- map: legacy.test
to: /home/vagrant/legacy/public
php: "7.4"Migrate to Sail
composer require laravel/sail --dev
php artisan sail:install
# Update .env: DB_HOST=mysql
sail upBest Practices
1. Use Sail instead - Modern, maintained 2. Map individual projects - Not large directories 3. Enable backup - backup: true in config
Related References
- sail.md - Recommended alternative
Installation
Overview
Laravel provides multiple ways to create new applications, from the Laravel installer to Composer. Understanding the installation process helps set up projects correctly from the start.
Requirements
Laravel 13 requires PHP 8.2+ with these extensions: Ctype, cURL, DOM, Fileinfo, Filter, Hash, Mbstring, OpenSSL, PCRE, PDO, Session, Tokenizer, XML. You'll also need Composer for dependency management.
Installation Methods
Laravel Installer (Recommended)
The Laravel installer provides an interactive setup experience with starter kit selection, testing framework choice, and database configuration.
# Install installer globally
composer global require laravel/installer
# Create new application
laravel new my-appThe installer prompts you for preferences and configures everything automatically, including running initial migrations with SQLite.
Via Composer
For environments without the installer, use Composer directly:
composer create-project laravel/laravel my-appphp.new (Quick Start)
Laravel provides one-liner installations that set up PHP, Composer, and the installer together for each operating system (macOS, Windows, Linux).
Development Environments
Laravel Herd (Recommended for macOS/Windows)
Herd is a native development environment that includes PHP, Nginx, and the Laravel installer. Sites in ~/Herd are automatically served at *.test domains.
Laravel Sail (Docker)
Sail provides a Docker-based environment for consistent development across all platforms. Ideal for teams needing identical environments.
Valet (macOS)
Lightweight environment using Nginx and DnsMasq. Uses ~7MB RAM and serves applications at *.test domains.
Initial Configuration
Environment Setup
After installation, Laravel creates a .env file from .env.example. Key settings include:
| Variable | Purpose |
|---|---|
APP_ENV | Environment (local, production) |
APP_DEBUG | Debug mode |
APP_KEY | Encryption key (auto-generated) |
APP_URL | Application URL |
DB_* | Database connection |
Database Configuration
Laravel defaults to SQLite with a pre-created database/database.sqlite file. For MySQL/PostgreSQL, update the DB_* variables and run migrations:
php artisan migrateStarting Development
cd my-app
npm install && npm run build
composer run devThis starts the development server, queue worker, and Vite dev server simultaneously.
Laravel Boost (AI Integration)
Laravel Boost bridges AI agents with Laravel applications, providing context, tools, and documentation access for AI-assisted development.
composer require laravel/boost --dev
php artisan boost:installIDE Support
- VS Code/Cursor: Use the official Laravel extension for syntax highlighting, snippets, and autocompletion
- PhpStorm: Use Laravel Idea plugin for comprehensive framework support
- Firebase Studio: Cloud-based development with zero setup
Best Practices
1. Use the installer - Provides the most complete setup experience 2. Configure .env first - Set environment before running migrations 3. Never commit .env - Contains sensitive credentials 4. Use starter kits - For projects needing authentication scaffolding 5. Match environments - Use Sail or Herd for team consistency
Related References
- configuration.md - Environment configuration
- structure.md - Directory organization
Request Lifecycle
Overview
Understanding the request lifecycle helps debug issues and know where to place code.
Lifecycle Phases
| Phase | Component | Purpose |
|---|---|---|
| 1. Entry | public/index.php | Loads autoloader, creates app |
| 2. Bootstrap | bootstrap/app.php | Creates service container |
| 3. Kernel | HTTP/Console Kernel | Configures bootstrappers, middleware |
| 4. Providers | Service Providers | Register and boot services |
| 5. Routing | Router | Match request to route/controller |
| 6. Middleware | Middleware Stack | Filter request/response |
| 7. Controller | Controller/Action | Handle business logic |
| 8. Response | Response Object | Send to browser |
---
Key Concepts
Entry Point
All web requests go through public/index.php:
| Action | Purpose |
|---|---|
| Load autoloader | Composer's vendor/autoload.php |
| Create application | From bootstrap/app.php |
| Handle request | Via HTTP Kernel |
HTTP Kernel
The kernel (Illuminate\Foundation\Http\Kernel) does:
| Step | Purpose |
|---|---|
| Run bootstrappers | Error handling, logging, environment |
| Load middleware | Session, CSRF, auth |
| Dispatch to router | Match route, run controller |
Service Providers
Providers bootstrap the entire framework:
| Method | When Called | Purpose |
|---|---|---|
register() | First | Bind services to container |
boot() | After all register | Use other services |
Important: boot() runs after ALL providers are registered.
---
Request Flow Diagram
Request → index.php → app.php → Kernel
↓
Bootstrappers
↓
Service Providers
(register → boot)
↓
Middleware
(before → after)
↓
Router → Controller
↓
Response---
When to Use What
| Need | Where to Put Code |
|---|---|
| Bind service | ServiceProvider register() |
| Use other service | ServiceProvider boot() |
| Filter all requests | Global middleware |
| Filter some requests | Route middleware |
| Handle specific route | Controller |
---
Debugging Tips
| Issue | Check |
|---|---|
| Service not found | Provider registered in bootstrap/providers.php? |
| Middleware not running | Applied to route/group? |
| Route not found | php artisan route:list |
| Provider error | Check boot() dependencies exist |
---
Best Practices
DO
- Keep providers focused (single responsibility)
- Use
boot()for logic needing other services - Register middleware in
bootstrap/app.php
DON'T
- Don't put business logic in providers
- Don't access services in
register()(not ready yet) - Don't forget middleware order matters
Logging
Overview
Laravel logging is built on Monolog, providing channels for sending logs to files, Slack, databases, and more. Configure channels in config/logging.php.
Log Levels
| Level | Method | Use Case |
|---|---|---|
emergency | Log::emergency() | System unusable |
alert | Log::alert() | Immediate action needed |
critical | Log::critical() | Critical conditions |
error | Log::error() | Runtime errors |
warning | Log::warning() | Exceptional but not error |
notice | Log::notice() | Normal but significant |
info | Log::info() | Informational |
debug | Log::debug() | Debug information |
Basic Usage
use Illuminate\Support\Facades\Log;
Log::info('User logged in', ['user_id' => $user->id]);
Log::error('Payment failed', ['order_id' => $order->id, 'error' => $e->getMessage()]);Channels
| Channel | Driver | Destination |
|---|---|---|
stack | stack | Multiple channels |
single | single | Single file |
daily | daily | Daily rotated files |
slack | slack | Slack webhook |
stderr | monolog | PHP stderr |
syslog | syslog | System log |
Using Specific Channel
Log::channel('slack')->critical('Server down!');Stack Channel
Log to multiple channels simultaneously:
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'slack'],
],Configuration
// config/logging.php
'channels' => [
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 14,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'level' => 'critical',
],
],Contextual Logging
Add context to all logs:
Log::withContext(['request_id' => $requestId]);Custom Channels
Create custom Monolog channels in config/logging.php:
'custom' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'with' => [
'stream' => 'php://stderr',
],
],Helper Function
logger('Debug message');
logger()->info('Info message');Best Practices
1. Use appropriate levels - Don't log everything as error 2. Add context - Include relevant data 3. Rotate logs - Use daily driver 4. Alert on critical - Send critical to Slack/email 5. Don't log sensitive data - Mask passwords, tokens
Related References
- errors.md - Exception handling
- configuration.md - Log configuration
Laravel MCP
Overview
Laravel MCP integrates with the Model Context Protocol, allowing AI clients (Claude, ChatGPT) to interact with your application via servers, tools, resources, and prompts.
Core Components
| Component | Purpose | Example |
|---|---|---|
| Server | Communication point | WeatherServer |
| Tool | Callable function | GetWeather |
| Resource | Data access | Database records |
| Prompt | Template | SummarizeArticle |
Installation
composer require laravel/mcp
php artisan vendor:publish --tag=ai-routesCreating Components
php artisan make:mcp-server WeatherServer
php artisan make:mcp-tool GetWeather
php artisan make:mcp-prompt SummarizeArticle
php artisan make:mcp-resource UserProfileServer Types
| Type | Use Case | Transport |
|---|---|---|
| Web | Remote AI | HTTP/SSE |
| Local | CLI tools | Stdio |
Tool Annotations
| Annotation | Meaning |
|---|---|
readOnlyHint | Only reads data |
destructiveHint | Modifies/deletes |
idempotentHint | Safe to retry |
openWorldHint | External services |
Resources
| Property | Purpose |
|---|---|
| URI | Unique identifier |
| MIME Type | Content type |
| Name | Human-readable |
| Description | What it contains |
Authentication
- OAuth 2.1 for web servers
- Sanctum for simpler setups
Use Laravel Gate/policies for tool/resource authorization.
Testing
npx @anthropic/mcp-inspectorBest Practices
1. Annotate tools - Help AI understand behavior 2. Validate input - Always validate arguments 3. Authorize access - Use policies 4. Type resources - Correct MIME types
Related References
- artisan.md - Creating MCP commands
Laravel Octane
Overview
Octane supercharges Laravel by keeping applications in memory between requests. Instead of bootstrapping for each request, Octane boots once and serves subsequent requests at supersonic speeds (10-100x improvement).
When to Use
Use Octane for high throughput, low latency, real-time features, and concurrent tasks. Avoid if your app relies on mutable global state.
Available Servers
| Server | Best For |
|---|---|
| FrankenPHP | Modern PHP, HTTP/2, HTTP/3 |
| Swoole | Concurrent tasks, ticks, tables |
| RoadRunner | Cross-platform, simple setup |
Installation
composer require laravel/octane
php artisan octane:installBasic Commands
php artisan octane:start # Start
php artisan octane:start --watch # Dev with reload
php artisan octane:start --workers=4
php artisan octane:reload # Reload workers
php artisan octane:stop # StopCritical: Persistence Problem
Since app stays in memory, be careful about: 1. Singletons - Persist across requests 2. Static arrays - Accumulate data (memory leak) 3. Request injection - Never in singleton constructors
Safe Patterns
// Bad - stale container
$this->app->singleton(Service::class, fn ($app) => new Service($app));
// Good - always fresh
$this->app->singleton(Service::class, fn () =>
new Service(fn () => Container::getInstance())
);Safe globals: app(), request(), config() - always fresh.
Production
Use Supervisor to keep Octane running:
[program:octane]
command=php /var/www/app/artisan octane:start --server=frankenphp --port=8000
autostart=true
autorestart=truePlace Nginx in front for SSL and static assets.
Swoole Features
// Concurrent tasks
[$users, $posts] = Octane::concurrently([
fn () => User::all(),
fn () => Post::all(),
]);
// Ticks
Octane::tick('heartbeat', fn () => Log::info('alive'))->seconds(10);
// Octane cache (2M ops/sec)
Cache::store('octane')->put('key', 'value', 30);Best Practices
1. Avoid memory leaks - Don't append to static arrays 2. Test thoroughly - Behavior differs from traditional PHP 3. Monitor memory - Watch for gradual increases 4. Proxy with Nginx - For SSL and static files
Related References
- deployment.md - Production deployment
Package Development
Overview
Laravel packages are reusable code bundles that can be shared across projects via Composer. They typically include service providers, facades, config, views, and migrations.
Package Structure
vendor/your-package/
├── config/
│ └── package.php
├── database/
│ └── migrations/
├── resources/
│ └── views/
├── src/
│ ├── PackageServiceProvider.php
│ └── YourClass.php
├── composer.json
└── README.mdService Provider
class PackageServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(__DIR__.'/../config/package.php', 'package');
}
public function boot(): void
{
$this->publishes([
__DIR__.'/../config/package.php' => config_path('package.php'),
], 'config');
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
$this->loadViewsFrom(__DIR__.'/../resources/views', 'package');
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
}
}composer.json
{
"name": "vendor/package-name",
"autoload": {
"psr-4": {
"Vendor\\Package\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"Vendor\\Package\\PackageServiceProvider"
]
}
}
}Publishing Assets
// Config
$this->publishes([
__DIR__.'/../config/package.php' => config_path('package.php'),
], 'config');
// Views
$this->publishes([
__DIR__.'/../resources/views' => resource_path('views/vendor/package'),
], 'views');
// Migrations
$this->publishesMigrations([
__DIR__.'/../database/migrations' => database_path('migrations'),
]);Commands
if ($this->app->runningInConsole()) {
$this->commands([
YourCommand::class,
]);
}Testing Package
Use Orchestra Testbench for testing packages in isolation.
Best Practices
1. Auto-discovery - Register in composer.json extra 2. Publish selectively - Tag publishable assets 3. Prefix config - Avoid conflicts 4. Document - Clear installation instructions
Related References
- providers.md - Service Provider details
Laravel Pennant
Overview
Pennant is Laravel's feature flag package for incremental rollouts, A/B testing, and trunk-based development. It supports multiple storage drivers (database, array) and rich scoping.
Installation
composer require laravel/pennant
php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
php artisan migrateDefining Features
// app/Providers/AppServiceProvider.php
use Laravel\Pennant\Feature;
public function boot(): void
{
Feature::define('new-dashboard', fn (User $user) =>
$user->is_beta_tester
);
}Class-Based Features
class NewApi
{
public function resolve(User $user): bool
{
return $user->created_at->isAfter('2024-01-01');
}
}Checking Features
// Boolean check
if (Feature::active('new-dashboard')) { }
// For specific scope
Feature::for($team)->active('new-dashboard');
// Conditional execution
Feature::when('new-dashboard',
fn () => /* active */,
fn () => /* inactive */
);Blade Directives
@feature('new-dashboard')
<x-new-dashboard />
@else
<x-old-dashboard />
@endfeatureMiddleware
Route::get('/dashboard', DashboardController::class)
->middleware('feature:new-dashboard');Rich Values
Return values beyond boolean:
Feature::define('theme', fn () =>
match (true) {
$user->is_admin => 'admin',
default => 'default',
}
);
$theme = Feature::value('theme');Storage & Performance
// Eager load
Feature::load(['new-dashboard', 'new-api']);
// Purge stored values
Feature::purge('new-dashboard');
// Store drivers: database (default), arrayEvents
FeatureRetrieved- Feature checkedFeatureResolved- Feature resolved (first time)AllFeaturesPurged- All features purged
Best Practices
1. Class-based for complex - Better organization 2. Eager load - Reduce queries 3. Purge on deploy - Clear stale values 4. Scope appropriately - User, team, or global
Related References
- configuration.md - Environment config
Processes
Overview
The Process facade provides an expressive API for running external shell commands. It wraps Symfony Process component with Laravel-friendly syntax.
Basic Usage
use Illuminate\Support\Facades\Process;
$result = Process::run('ls -la');
echo $result->output();Result Methods
| Method | Returns |
|---|---|
output() | Command stdout |
errorOutput() | Command stderr |
successful() | True if exit code 0 |
failed() | True if exit code != 0 |
exitCode() | Exit code |
Throwing on Failure
$result = Process::run('invalid-command')->throw();
// Throws ProcessFailedException if failedCommand Options
Process::timeout(60)
->path('/home/user')
->env(['APP_ENV' => 'testing'])
->run('php artisan migrate');Input
Process::input('yes')
->run('php artisan migrate --force');Asynchronous Processes
$process = Process::start('long-running-command');
// Do other work...
$result = $process->wait();Process Pools
Run multiple processes concurrently:
$pool = Process::pool(function (Pool $pool) {
$pool->command('npm run build');
$pool->command('php artisan optimize');
});
$results = $pool->start()->wait();Fake for Testing
Process::fake([
'ls *' => Process::result(output: 'file1.txt file2.txt'),
'*' => Process::result(exitCode: 1),
]);
Process::assertRan('ls *');Best Practices
1. Set timeouts - Prevent hanging processes 2. Handle failures - Check failed() or use throw() 3. Use pools - For concurrent operations 4. Fake in tests - Don't run real processes
Related References
- artisan.md - Laravel CLI commands
Service Providers
Overview
Service Providers are the central place for configuring and bootstrapping your Laravel application. All core Laravel services and your application services are bootstrapped via providers.
Provider Structure
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
// Bind services to container
}
public function boot(): void
{
// Bootstrap after all providers registered
}
}Register vs Boot
| Method | When | Purpose |
|---|---|---|
register() | First | Bind services to container |
boot() | After all register() | Use services, configure app |
Important: Never use services in register() - other providers may not be registered yet.
Creating Providers
php artisan make:provider PaymentServiceProviderRegister in bootstrap/providers.php:
return [
App\Providers\AppServiceProvider::class,
App\Providers\PaymentServiceProvider::class,
];Binding Services
public function register(): void
{
// Simple binding
$this->app->bind(PaymentGateway::class, StripeGateway::class);
// Singleton
$this->app->singleton(Analytics::class, function ($app) {
return new Analytics(config('services.analytics.key'));
});
// Interface to implementation
$this->app->bind(
PaymentGatewayInterface::class,
StripeGateway::class
);
}Bootstrapping
public function boot(): void
{
// Define routes
Route::middleware('api')->group(base_path('routes/api.php'));
// Register Blade components
Blade::component('alert', AlertComponent::class);
// Configure services
Validator::extend('phone', fn ($attr, $value) => preg_match('/^\+/', $value));
// Publish config
$this->publishes([
__DIR__.'/../config/payment.php' => config_path('payment.php'),
]);
}Deferred Providers
Load providers only when needed:
class HeavyServiceProvider extends ServiceProvider implements DeferrableProvider
{
public function provides(): array
{
return [HeavyService::class];
}
}Common Use Cases
| Use Case | Method |
|---|---|
| Bind services | register() |
| Register commands | boot() |
| Configure routes | boot() |
| Register Blade directives | boot() |
| Publish assets | boot() |
| Listen to events | boot() |
Best Practices
1. One responsibility - Split large providers 2. Defer heavy services - Use DeferrableProvider 3. No logic in register - Only bindings 4. Use boot for config - Routes, Blade, etc.
Related References
- container.md - Service Container bindings
- facades.md - Facade registration
Laravel Releases
Overview
Laravel follows semantic versioning with annual major releases. Choose versions based on support timeline and PHP requirements.
---
Versioning Scheme
| Type | Frequency | Breaking Changes |
|---|---|---|
| Major (12.0) | Yearly (~Q1) | Yes |
| Minor (12.1) | Weekly | No |
| Patch (12.0.1) | As needed | No |
Constraint: Always use ^12.0 in composer.json (not 12.*).
---
Support Policy
| Duration | Type |
|---|---|
| 18 months | Bug fixes |
| 2 years | Security fixes |
---
Version Matrix (2025-2028)
| Version | PHP | Release | Bug Fixes | Security |
|---|---|---|---|---|
| 12 | 8.2 - 8.4 | Feb 2025 | Aug 2026 | Feb 2027 |
| 13 | 8.3 - 8.4 | Q1 2026 | Q3 2027 | Q1 2028 |
Recommendation: Use Laravel 13 for new projects (current stable).
---
Laravel 13 Highlights
| Feature | Description |
|---|---|
| Minimal breaking changes | Upgrade in < 1 day |
| New starter kits | React, Vue, Livewire with shadcn/ui |
| WorkOS AuthKit | Social auth, passkeys, SSO |
| Inertia 2 | TypeScript support |
Deprecated
| Package | Status | Alternative |
|---|---|---|
| Laravel Breeze | No more updates | New starter kits |
| Laravel Jetstream | No more updates | New starter kits |
---
Upgrade Decision Guide
Current version supported?
├── Yes → Need new features?
│ ├── Yes → Plan upgrade during maintenance window
│ └── No → Stay on current version
└── No (EOL) → Upgrade immediately (security risk)---
PHP Compatibility
| Laravel | Minimum PHP | Maximum PHP |
|---|---|---|
| 10 | 8.1 | 8.3 |
| 11 | 8.2 | 8.4 |
| 12 | 8.2 | 8.4 |
| 13 | 8.3 | 8.4+ |
Tip: Target PHP 8.4 for best performance and features.
---
Upgrade Checklist
- [ ] Check PHP version compatibility
- [ ] Review breaking changes in upgrade guide
- [ ] Update
composer.jsonconstraint - [ ] Run
composer update - [ ] Run test suite
- [ ] Check deprecated features
- [ ] Update deprecated code
---
Best Practices
DO
- Upgrade within 6 months of new major release
- Test thoroughly in staging first
- Read the official upgrade guide
- Keep PHP version current
DON'T
- Don't skip major versions (10 → 12)
- Don't upgrade without test coverage
- Don't ignore deprecation warnings
- Don't stay on EOL versions
Laravel Sail
Overview
Sail is Laravel's Docker development environment with a lightweight CLI. It provides consistent environments across teams without Docker expertise. Only Docker is required locally.
Installation
composer require laravel/sail --dev
php artisan sail:installBasic Commands
sail up # Start containers
sail up -d # Start in background
sail stop # Stop containers
sail down # Destroy containersShell alias (add to ~/.zshrc):
alias sail='sh $([ -f sail ] && echo sail || echo vendor/bin/sail)'Executing Commands
| Command | Description |
|---|---|
sail artisan | Run Artisan |
sail composer | Run Composer |
sail npm | Run NPM |
sail test | Run tests |
sail tinker | Start REPL |
sail shell | Container bash |
Database Services
- MySQL:
mysqlhost inside,localhost:3306from host - Redis:
redishost inside,localhost:6379from host - PostgreSQL:
pgsqlhost inside,localhost:5432from host
Add services: php artisan sail:add
PHP Versions
Edit compose.yaml:
build:
context: ./vendor/laravel/sail/runtimes/8.3Then: sail build --no-cache && sail up
Debugging (Xdebug)
# .env
SAIL_XDEBUG_MODE=develop,debug,coverageRebuild after changes. Use sail debug artisan migrate for CLI.
Email Preview
Mailpit at http://localhost:8025:
MAIL_HOST=mailpit
MAIL_PORT=1025File Storage (MinIO)
S3-compatible storage at http://localhost:8900:
AWS_ENDPOINT=http://minio:9000
AWS_ACCESS_KEY_ID=sail
AWS_SECRET_ACCESS_KEY=passwordSharing Sites
sail shareConfigure trusted proxies in bootstrap/app.php.
Best Practices
1. Use alias - Saves typing 2. Run detached - Use -d flag 3. Rebuild after PHP changes - sail build --no-cache
Related References
- installation.md - Laravel installation
Directory Structure
Overview
Laravel follows a conventional directory structure. Understanding it helps navigate and organize code effectively.
Root Directories
| Directory | Purpose |
|---|---|
app/ | Application code |
bootstrap/ | Framework bootstrap |
config/ | Configuration files |
database/ | Migrations, seeders, factories |
public/ | Web server document root |
resources/ | Views, assets, lang |
routes/ | Route definitions |
storage/ | Logs, cache, sessions |
tests/ | Test files |
vendor/ | Composer dependencies |
App Directory
| Directory | Purpose |
|---|---|
Console/ | Artisan commands |
Exceptions/ | Exception handlers |
Http/ | Controllers, middleware, requests |
Models/ | Eloquent models |
Providers/ | Service providers |
Http Subdirectories
| Directory | Purpose |
|---|---|
Controllers/ | HTTP controllers |
Middleware/ | Request middleware |
Requests/ | Form requests |
Resources/ | API resources |
Common Custom Directories
| Directory | Purpose |
|---|---|
app/Services/ | Business logic |
app/Repositories/ | Data access |
app/Actions/ | Single-action classes |
app/DTOs/ | Data transfer objects |
app/Enums/ | PHP enums |
app/Events/ | Event classes |
app/Jobs/ | Queued jobs |
app/Listeners/ | Event listeners |
app/Policies/ | Authorization policies |
app/Rules/ | Custom validation rules |
Routes Directory
| File | Purpose |
|---|---|
web.php | Web routes (sessions) |
api.php | API routes (stateless) |
console.php | Artisan commands |
channels.php | Broadcast channels |
Storage Directory
| Directory | Purpose |
|---|---|
app/ | Application files |
app/public/ | Public uploads |
framework/ | Cache, sessions |
logs/ | Log files |
Best Practices
1. Follow conventions - Use standard directories 2. Services for logic - Keep controllers thin 3. Group by feature - For large apps 4. API versioning - Controllers/Api/V1/
Related References
- providers.md - Bootstrapping
- configuration.md - Config files
App Service Provider
File: app/Providers/AppServiceProvider.php
<?php
declare(strict_types=1);
namespace App\Providers;
use App\Contracts\PostRepositoryInterface;
use App\Contracts\UserRepositoryInterface;
use App\Repositories\EloquentPostRepository;
use App\Repositories\EloquentUserRepository;
use Illuminate\Support\ServiceProvider;
/**
* Application service provider.
*/
final class AppServiceProvider extends ServiceProvider
{
/**
* Register application services.
*/
public function register(): void
{
$this->registerRepositories();
}
/**
* Bootstrap application services.
*/
public function boot(): void
{
//
}
/**
* Register repository bindings.
*/
private function registerRepositories(): void
{
$this->app->bind(
UserRepositoryInterface::class,
EloquentUserRepository::class
);
$this->app->bind(
PostRepositoryInterface::class,
EloquentPostRepository::class
);
}
}Directory Structure
app/
├── Contracts/ # Interfaces
│ ├── UserRepositoryInterface.php
│ └── PostRepositoryInterface.php
├── DTOs/ # Data Transfer Objects
│ ├── CreateUserDTO.php
│ └── UpdateUserDTO.php
├── Repositories/ # Repository implementations
│ ├── EloquentUserRepository.php
│ └── EloquentPostRepository.php
├── Services/ # Business logic
│ ├── UserService.php
│ └── PostService.php
└── Providers/
└── AppServiceProvider.phpArtisan Command Templates
Basic Command Class
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class SendEmails extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mail:send {user}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send a marketing email to a user';
/**
* Execute the console command.
*/
public function handle(): int
{
// Command logic here...
return Command::SUCCESS;
}
}Signature with Arguments and Options
// Required argument
protected $signature = 'mail:send {user}';
// Optional argument
protected $signature = 'mail:send {user?}';
// Argument with default
protected $signature = 'mail:send {user=foo}';
// Boolean option (switch)
protected $signature = 'mail:send {user} {--queue}';
// Option with value
protected $signature = 'mail:send {user} {--queue=}';
// Option with default value
protected $signature = 'mail:send {user} {--queue=default}';
// Option shortcut
protected $signature = 'mail:send {user} {--Q|queue}';
// Array argument
protected $signature = 'mail:send {user*}';
// Array option
protected $signature = 'mail:send {user} {--id=*}';
// With descriptions
protected $signature = 'mail:send
{user : The ID of the user}
{--queue : Whether the job should be queued}';Input/Output Methods
public function handle(): int
{
// Get argument
$userId = $this->argument('user');
$allArguments = $this->arguments();
// Get option
$queueName = $this->option('queue');
$allOptions = $this->options();
// Output
$this->info('This is informational text');
$this->error('This is an error');
$this->warn('This is a warning');
$this->line('Plain text output');
$this->newLine(3);
// Prompts
$name = $this->ask('What is your name?');
$password = $this->secret('What is the password?');
if ($this->confirm('Do you wish to continue?')) {
// ...
}
$name = $this->anticipate('What is your name?', ['Taylor', 'Dayle']);
$name = $this->choice('What is your name?', ['Taylor', 'Dayle'], 0);
return Command::SUCCESS;
}Progress Bar
use App\Models\User;
public function handle(): void
{
$users = User::all();
$bar = $this->output->createProgressBar(count($users));
$bar->start();
foreach ($users as $user) {
$this->performTask($user);
$bar->advance();
}
$bar->finish();
$this->newLine();
}Table Output
$this->table(
['Name', 'Email'],
User::all(['name', 'email'])->toArray()
);Closure Command
// routes/console.php
use Illuminate\Support\Facades\Artisan;
Artisan::command('mail:send {user}', function (string $user) {
$this->info("Sending email to: {$user}!");
})->purpose('Send a marketing email to a user');Calling Commands Programmatically
use Illuminate\Support\Facades\Artisan;
// Call command
Artisan::call('mail:send', [
'user' => 1, '--queue' => 'default'
]);
// Get output
$output = Artisan::output();
// Queue command
Artisan::queue('mail:send', ['user' => 1]);
// From another command
$this->call('mail:send', ['user' => 1]);
$this->callSilently('mail:send', ['user' => 1]);Scheduling
// routes/console.php
use App\Console\Commands\SendEmailsCommand;
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send Taylor --force')->daily();
Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();Return Codes
public function handle(): int
{
// Success
return Command::SUCCESS; // 0
// Failure
return Command::FAILURE; // 1
// Invalid
return Command::INVALID; // 2
}Envoy Deployment Template
Basic Task Definition
@servers(['web' => ['user@192.168.1.1'], 'workers' => ['user@192.168.1.2']])
@task('restart-queues', ['on' => 'workers'])
cd /home/user/example.com
php artisan queue:restart
@endtaskStories (Task Groups)
@servers(['web' => ['user@192.168.1.1']])
@story('deploy')
update-code
install-dependencies
@endstory
@task('update-code')
cd /home/user/example.com
git pull origin master
@endtask
@task('install-dependencies')
cd /home/user/example.com
composer install
@endtaskMultiple Servers with Parallel Execution
@servers(['web-1' => '192.168.1.1', 'web-2' => '192.168.1.2'])
@task('deploy', ['on' => ['web-1', 'web-2'], 'parallel' => true])
cd /home/user/example.com
git pull origin {{ $branch }}
php artisan migrate --force
@endtaskVariables and Conditionals
@servers(['web' => ['user@192.168.1.1']])
@task('deploy', ['on' => 'web'])
cd /home/user/example.com
@if ($branch)
git pull origin {{ $branch }}
@endif
php artisan migrate --force
@endtaskHooks
@before
if ($task === 'deploy') {
// Before deployment...
}
@endbefore
@after
if ($task === 'deploy') {
// After deployment...
}
@endafter
@error
if ($task === 'deploy') {
// Handle deployment error...
}
@enderror
@success
// All tasks completed successfully...
@endsuccess
@finished
if ($exitCode > 0) {
// There were errors in one of the tasks...
}
@endfinishedNotifications
@finished
@slack('webhook-url', '#bots')
@endfinished
@finished
@slack('webhook-url', '#bots', 'Hello, Slack.')
@endfinished
@finished
@discord('discord-webhook-url')
@endfinished
@finished
@telegram('bot-id', 'chat-id')
@endfinished
@finished
@microsoftTeams('webhook-url')
@endfinishedConfirmation Prompt
@task('deploy', ['on' => 'web', 'confirm' => true])
cd /home/user/example.com
git pull origin {{ $branch }}
php artisan migrate
@endtaskSetup Block
@setup
$now = new DateTime;
@endsetupImport External Tasks
@import('vendor/package/Envoy.blade.php')Usage
php vendor/bin/envoy run deploy
php vendor/bin/envoy run deploy --branch=masterLaravel MCP Templates
Installation
composer require laravel/mcp
php artisan vendor:publish --tag=ai-routesCreate Components
php artisan make:mcp-server WeatherServer
php artisan make:mcp-tool CurrentWeatherTool
php artisan make:mcp-resource WeatherGuidelinesResource
php artisan make:mcp-prompt DescribeWeatherPromptMCP Server Class
<?php
namespace App\Mcp\Servers;
use Laravel\Mcp\Server;
class WeatherServer extends Server
{
/**
* The MCP server's name.
*/
protected string $name = 'Weather Server';
/**
* The MCP server's version.
*/
protected string $version = '1.0.0';
/**
* The MCP server's instructions for the LLM.
*/
protected string $instructions = 'This server provides weather information and forecasts.';
/**
* The tools registered with this MCP server.
*
* @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
*/
protected array $tools = [
CurrentWeatherTool::class,
];
/**
* The resources registered with this MCP server.
*
* @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
*/
protected array $resources = [
WeatherGuidelinesResource::class,
];
/**
* The prompts registered with this MCP server.
*
* @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
*/
protected array $prompts = [
DescribeWeatherPrompt::class,
];
}Register Server (routes/ai.php)
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;
// Web server (HTTP/SSE)
Mcp::web('/mcp/weather', WeatherServer::class);
// With Sanctum authentication
Mcp::web('/mcp/weather', WeatherServer::class)
->middleware('auth:sanctum');
// Local server (Stdio)
Mcp::local('weather', WeatherServer::class);MCP Tool Class
<?php
namespace App\Mcp\Tools;
use Laravel\Mcp\Server\Tool;
use Laravel\Mcp\Types\ToolAnnotations;
class CurrentWeatherTool extends Tool
{
/**
* The tool's name.
*/
protected string $name = 'get_current_weather';
/**
* The tool's description.
*/
protected string $description = 'Get current weather for a location';
/**
* The tool's input schema.
*/
protected function inputSchema(): array
{
return [
'type' => 'object',
'properties' => [
'location' => [
'type' => 'string',
'description' => 'City name or coordinates',
],
],
'required' => ['location'],
];
}
/**
* The tool's annotations.
*/
protected function annotations(): ToolAnnotations
{
return new ToolAnnotations(
readOnlyHint: true,
openWorldHint: true,
);
}
/**
* Execute the tool.
*/
public function __invoke(string $location): string
{
// Fetch weather data...
return "Current weather in {$location}: 72°F, Sunny";
}
}MCP Resource Class
<?php
namespace App\Mcp\Resources;
use Laravel\Mcp\Server\Resource;
class WeatherGuidelinesResource extends Resource
{
/**
* The resource's URI.
*/
protected string $uri = 'weather://guidelines';
/**
* The resource's name.
*/
protected string $name = 'Weather Guidelines';
/**
* The resource's description.
*/
protected string $description = 'Guidelines for interpreting weather data';
/**
* The resource's MIME type.
*/
protected string $mimeType = 'text/plain';
/**
* Get the resource contents.
*/
public function __invoke(): string
{
return 'Weather interpretation guidelines...';
}
}MCP Prompt Class
<?php
namespace App\Mcp\Prompts;
use Laravel\Mcp\Server\Prompt;
use Laravel\Mcp\Types\PromptMessage;
class DescribeWeatherPrompt extends Prompt
{
/**
* The prompt's name.
*/
protected string $name = 'describe_weather';
/**
* The prompt's description.
*/
protected string $description = 'Generate a weather description';
/**
* The prompt's arguments.
*/
protected function arguments(): array
{
return [
'location' => [
'description' => 'The location to describe weather for',
'required' => true,
],
];
}
/**
* Get the prompt messages.
*/
public function __invoke(string $location): array
{
return [
new PromptMessage(
role: 'user',
content: "Describe the typical weather in {$location}.",
),
];
}
}Testing with Inspector
# Web server
php artisan mcp:inspector mcp/weather
# Local server
php artisan mcp:inspector weatherTool Annotations
new ToolAnnotations(
readOnlyHint: true, // Only reads data
destructiveHint: false, // Modifies/deletes data
idempotentHint: true, // Safe to retry
openWorldHint: true, // Interacts with external services
);Laravel Octane Configuration
Installation
composer require laravel/octane
php artisan octane:installFrankenPHP via Sail
./vendor/bin/sail up
./vendor/bin/sail composer require laravel/octane
./vendor/bin/sail artisan octane:install --server=frankenphp# docker-compose.yml
services:
laravel.test:
environment:
SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=frankenphp --host=0.0.0.0 --admin-port=2019 --port='${APP_PORT:-80}'"
XDG_CONFIG_HOME: /var/www/html/config
XDG_DATA_HOME: /var/www/html/dataRoadRunner via Sail
./vendor/bin/sail up
./vendor/bin/sail composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http
./vendor/bin/sail shell
./vendor/bin/rr get-binaryservices:
laravel.test:
environment:
SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=roadrunner --host=0.0.0.0 --rpc-port=6001 --port='${APP_PORT:-80}'"Swoole Installation
pecl install swoole
# or openswoole
pecl install openswooleBasic Commands
php artisan octane:start
php artisan octane:start --watch
php artisan octane:start --workers=4
php artisan octane:start --workers=4 --task-workers=6
php artisan octane:start --max-requests=250
php artisan octane:reload
php artisan octane:stop
php artisan octane:statusConcurrent Tasks (Swoole)
use App\Models\User;
use App\Models\Server;
use Laravel\Octane\Facades\Octane;
[$users, $servers] = Octane::concurrently([
fn () => User::all(),
fn () => Server::all(),
]);Ticks/Intervals (Swoole)
Octane::tick('simple-ticker', fn () => ray('Ticking...'))
->seconds(10);
Octane::tick('simple-ticker', fn () => ray('Ticking...'))
->seconds(10)
->immediate();Octane Cache (Swoole)
Cache::store('octane')->put('framework', 'Laravel', 30);Swoole Tables
// config/octane.php
'tables' => [
'example:1000' => [
'name' => 'string:1000',
'votes' => 'int',
],
],use Laravel\Octane\Facades\Octane;
Octane::table('example')->set('uuid', [
'name' => 'Nuno Maduro',
'votes' => 1000,
]);
return Octane::table('example')->get('uuid');Safe Singleton Pattern
use App\Service;
use Illuminate\Container\Container;
// Bad - stale container
$this->app->singleton(Service::class, function ($app) {
return new Service($app);
});
// Good - always fresh
$this->app->singleton(Service::class, function () {
return new Service(fn () => Container::getInstance());
});Safe Request Pattern
// Bad - stale request
$this->app->singleton(Service::class, function ($app) {
return new Service($app['request']);
});
// Good - closure resolver
$this->app->singleton(Service::class, function ($app) {
return new Service(fn () => $app['request']);
});
// Best - pass at runtime
$service->method($request->input('name'));Supervisor Configuration
[program:octane]
process_name=%(program_name)s_%(process_num)02d
command=php /home/forge/example.com/artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000
autostart=true
autorestart=true
user=forge
redirect_stderr=true
stdout_logfile=/home/forge/example.com/storage/logs/octane.log
stopwaitsecs=3600HTTPS Configuration
// config/octane.php
'https' => env('OCTANE_HTTPS', false),Laravel Pennant Templates
Installation
composer require laravel/pennant
php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
php artisan migrateDefining Features (Class-Based)
<?php
namespace App\Features;
use App\Models\User;
use Illuminate\Support\Lottery;
class NewApi
{
/**
* Resolve the feature's initial value.
*/
public function resolve(User $user): mixed
{
return match (true) {
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
};
}
}Rich Value Features
<?php
namespace App\Features;
use Illuminate\Support\Arr;
class PurchaseButton
{
/**
* Resolve the feature's initial value.
*/
public function resolve(): string
{
return Arr::random([
'blue-hierarchyaw',
'green-hierarchyaw',
'red-hierarchyaw',
]);
}
}Checking Features
use Laravel\Pennant\Feature;
use App\Features\NewApi;
// Boolean check
if (Feature::active('new-api')) {
// ...
}
// Class-based check
if (Feature::active(NewApi::class)) {
// ...
}
// Rich value retrieval
$color = Feature::value(PurchaseButton::class);
// With custom scope
Feature::for($user)->active('new-api');
// Multiple features
Feature::someAreActive(['feature-a', 'feature-b']);
Feature::allAreActive(['feature-a', 'feature-b']);
Feature::someAreInactive(['feature-a', 'feature-b']);
Feature::allAreInactive(['feature-a', 'feature-b']);Blade Directives
@feature('new-api')
<!-- Feature is active -->
@else
<!-- Feature is inactive -->
@endfeature
@feature(App\Features\NewApi::class)
<!-- Class-based feature -->
@endfeatureMiddleware
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;
Route::get('/api/v2/endpoint', function () {
// ...
})->middleware(EnsureFeaturesAreActive::using('new-api'));
// Multiple features
->middleware(EnsureFeaturesAreActive::using('billing-v2', 'new-api'));
// Custom response on inactive
->middleware(EnsureFeaturesAreActive::using('new-api')->whenInactive(
fn () => redirect('/dashboard')
));Activating/Deactivating Features
use Laravel\Pennant\Feature;
// Activate for everyone
Feature::activate('new-api');
// Activate with value
Feature::activate('purchase-button', 'seafoam-green');
// Activate for specific scope
Feature::for($user)->activate('new-api');
// Deactivate
Feature::deactivate('new-api');
Feature::for($user)->deactivate('new-api');
// Forget (re-resolve on next check)
Feature::forget('new-api');Bulk Operations
// Activate for all users
Feature::activateForEveryone('new-api');
Feature::activateForEveryone('purchase-button', 'seafoam-green');
// Deactivate for everyone
Feature::deactivateForEveryone('new-api');
// Purge stored values
Feature::purge('new-api');
Feature::purge(['new-api', 'purchase-button']);Eager Loading
// Load multiple features for scope
Feature::for($user)->load(['new-api', 'purchase-button']);
// Load for multiple scopes
Feature::for([$user1, $user2])->load(['new-api']);Testing
use Laravel\Pennant\Feature;
public function test_new_api_feature()
{
Feature::define('new-api', true);
// Test with feature active...
}
public function test_without_new_api()
{
Feature::define('new-api', false);
// Test with feature inactive...
}Artisan Commands
# List all features
php artisan pennant:feature
# Purge resolved features
php artisan pennant:purge new-api
php artisan pennant:purge --except=new-apiLaravel Sail Configuration
Installation
composer require laravel/sail --dev
php artisan sail:installShell Alias
alias sail='sh $([ -f sail ] && echo sail || echo vendor/bin/sail)'Basic Commands
# Start containers
sail up
# Start in background
sail up -d
# Stop containers
sail stop
# Rebuild containers
sail build --no-cache
sail upExecuting Commands
# PHP commands
sail php --version
sail php script.php
# Composer commands
sail composer require laravel/sanctum
# Artisan commands
sail artisan queue:work
# Node/NPM commands
sail node --version
sail npm run dev
# Yarn
sail yarnTesting
sail test
sail test --group orders
sail artisan testXdebug Configuration
# .env
SAIL_XDEBUG_MODE=develop,debug,coverage# php.ini (after sail:publish)
[xdebug]
xdebug.mode=${XDEBUG_MODE}# Rebuild after changes
sail build --no-cache
# Debug Artisan commands
sail debug migrateDatabase Configuration
# MySQL
DB_HOST=mysql
# PostgreSQL
DB_HOST=pgsql
# Redis
REDIS_HOST=redis
# MongoDB
MONGODB_URI=mongodb://mongodb:27017Sharing Sites
sail share
sail share --subdomain=my-sail-site// bootstrap/app.php - Trust proxies
->withMiddleware(function (Middleware $middleware): void {
$middleware->trustProxies(at: '*');
})PHP Versions
# compose.yaml
build:
context: ./vendor/laravel/sail/runtimes/8.4
# or 8.3, 8.2, 8.1
image: sail-8.4/appMinIO (S3-compatible storage)
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=sail
AWS_SECRET_ACCESS_KEY=password
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=local
AWS_ENDPOINT=http://minio:9000
AWS_USE_PATH_STYLE_ENDPOINT=true
AWS_URL=http://localhost:9000/localMailpit
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_ENCRYPTION=nullCustomization
sail artisan sail:publishContainer Shell
sail shell
sail root-shell
sail tinkerDusk Testing
# compose.yaml - uncomment selenium
selenium:
image: 'selenium/standalone-chrome'
extra_hosts:
- 'host.docker.internal:host-gateway'
volumes:
- '/dev/shm:/dev/shm'
networks:
- sailsail duskUser Service
File: app/Services/UserService.php
<?php
declare(strict_types=1);
namespace App\Services;
use App\Contracts\UserRepositoryInterface;
use App\DTOs\CreateUserDTO;
use App\DTOs\UpdateUserDTO;
use App\Models\User;
use Illuminate\Pagination\LengthAwarePaginator;
/**
* User business logic service.
*/
final readonly class UserService
{
public function __construct(
private UserRepositoryInterface $repository,
) {}
/**
* Get paginated users.
*/
public function paginate(int $perPage = 15): LengthAwarePaginator
{
return $this->repository->paginate($perPage);
}
/**
* Find user by ID.
*/
public function findById(int $id): ?User
{
return $this->repository->findById($id);
}
/**
* Create a new user.
*/
public function create(CreateUserDTO $dto): User
{
return $this->repository->create($dto);
}
/**
* Update existing user.
*/
public function update(User $user, UpdateUserDTO $dto): User
{
return $this->repository->update($user, $dto);
}
/**
* Delete user.
*/
public function delete(User $user): bool
{
return $this->repository->delete($user);
}
}File: app/Contracts/UserRepositoryInterface.php
<?php
declare(strict_types=1);
namespace App\Contracts;
use App\DTOs\CreateUserDTO;
use App\DTOs\UpdateUserDTO;
use App\Models\User;
use Illuminate\Pagination\LengthAwarePaginator;
/**
* User repository contract.
*/
interface UserRepositoryInterface
{
public function paginate(int $perPage): LengthAwarePaginator;
public function findById(int $id): ?User;
public function create(CreateUserDTO $dto): User;
public function update(User $user, UpdateUserDTO $dto): User;
public function delete(User $user): bool;
}File: app/Repositories/EloquentUserRepository.php
<?php
declare(strict_types=1);
namespace App\Repositories;
use App\Contracts\UserRepositoryInterface;
use App\DTOs\CreateUserDTO;
use App\DTOs\UpdateUserDTO;
use App\Models\User;
use Illuminate\Pagination\LengthAwarePaginator;
/**
* Eloquent user repository implementation.
*/
final class EloquentUserRepository implements UserRepositoryInterface
{
public function paginate(int $perPage): LengthAwarePaginator
{
return User::latest()->paginate($perPage);
}
public function findById(int $id): ?User
{
return User::find($id);
}
public function create(CreateUserDTO $dto): User
{
return User::create([
'name' => $dto->name,
'email' => $dto->email,
'password' => $dto->password,
]);
}
public function update(User $user, UpdateUserDTO $dto): User
{
$user->update(array_filter([
'name' => $dto->name,
'email' => $dto->email,
]));
return $user->fresh();
}
public function delete(User $user): bool
{
return $user->delete();
}
}Upgrade Guide
Overview
Laravel follows semantic versioning with annual major releases. Upgrading requires updating dependencies and addressing breaking changes. The upgrade from 11.x to 12.0 is typically straightforward.
Upgrade Process
1. Update Dependencies
Update your composer.json with new version constraints:
{
"require": {
"laravel/framework": "^12.0"
},
"require-dev": {
"phpunit/phpunit": "^11.0",
"pestphp/pest": "^3.0"
}
}Then run:
composer update2. Update Laravel Installer
If using the Laravel installer CLI, update it for compatibility:
composer global update laravel/installerOr re-run the php.new installation commands for your OS.
Breaking Changes by Impact
High Impact
Changes that affect most applications:
| Change | Action Required |
|---|---|
| Framework version | Update to ^12.0 |
| PHPUnit | Update to ^11.0 |
| Pest | Update to ^3.0 |
Medium Impact
Changes affecting common use cases:
UUIDv7 Migration: The HasUuids trait now generates UUIDv7 (ordered) instead of UUIDv4. To keep UUIDv4 behavior:
// Before
use Illuminate\Database\Eloquent\Concerns\HasUuids;
// After (to keep UUIDv4)
use Illuminate\Database\Eloquent\Concerns\HasVersion4Uuids as HasUuids;Low Impact
Changes affecting edge cases:
| Change | Details |
|---|---|
| Carbon 3 | Carbon 2.x support removed |
| Concurrency results | Associative arrays now return keyed results |
| Container resolution | Respects default values for class properties |
| Image validation | SVGs excluded by default |
| Local disk root | Defaults to storage/app/private |
| Schema inspection | Multi-schema results by default |
Specific Changes
Container Dependency Resolution
Default parameter values are now respected:
class Example
{
public function __construct(public ?Carbon $date = null) {}
}
$example = resolve(Example::class);
// Laravel 11.x: $date is Carbon instance
// Laravel 13.x: $date is null (respects default)Image Validation with SVG
To allow SVGs in image validation:
'photo' => 'required|image:allow_svg'
// Or using File rule
'photo' => ['required', File::image(allowSvg: true)]Request Merging
mergeIfMissing() now supports dot notation for nested arrays:
$request->mergeIfMissing([
'user.last_name' => 'Otwell', // Creates nested array
]);Automated Upgrades
Laravel Shift provides automated upgrade services that handle most changes automatically, saving significant time on larger applications.
Best Practices
1. Read the full guide - Review all changes before upgrading 2. Test thoroughly - Run your test suite after upgrading 3. Upgrade incrementally - Don't skip major versions 4. Check packages - Ensure third-party packages support the new version 5. Use Shift - For large applications, automated upgrades save time
Related References
- installation.md - Fresh installation
- configuration.md - Configuration changes
Laravel Valet
Overview
Valet is a blazing-fast macOS development environment using Nginx and DnsMasq. Sites are served at *.test domains with ~7MB RAM. Consider Laravel Herd for an easier GUI experience.
Installation
brew update && brew install php
composer global require laravel/valet
valet installServing Sites
Park (Multiple Sites)
cd ~/Sites
valet park
# ~/Sites/laravel → http://laravel.testLink (Single Site)
cd ~/Sites/my-project
valet link
# → http://my-project.testPHP Versions
valet use php@8.2 # Global
valet isolate php@8.1 # Per-site
valet unisolate # RevertOr create .valetrc in project: php=php@8.1
HTTPS
valet secure laravel # Enable
valet unsecure laravel # DisableSharing Sites
valet share-tool ngrok # Configure tool
valet share # Share current siteProxying Services
valet proxy elasticsearch http://127.0.0.1:9200
valet unproxy elasticsearchCommon Commands
| Command | Description |
|---|---|
valet park | Serve directory |
valet link | Serve current |
valet links | List links |
valet secure | Enable HTTPS |
valet restart | Restart services |
valet diagnose | Debug issues |
Configuration
| Location | Purpose |
|---|---|
~/.config/valet/config.json | Main config |
~/.config/valet/Drivers/ | Custom drivers |
Best Practices
1. Use Herd - Easier GUI management 2. Park directories - Simpler than linking each 3. Isolate PHP - For legacy projects 4. Use DBngin - For database management
Related References
- sail.md - Docker alternative