All skills
asyrafhussin avatar

/laravel-best-practices

@ef59186

Laravel 13 conventions and best practices. Use when creating controllers, models, migrations, validation, services, or structuring Laravel applications. Triggers on tasks involving Laravel architecture, Eloquent, database, API development, or PHP patterns.

Use this Skill: https://skilld.dev/gh/asyrafhussin/agent-skills/laravel-best-practices

This session only. Nothing lands on disk.

rulesarch-repository-pattern.md

≈1.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Repository Pattern

Impact: MEDIUM (Abstracts data access from business logic)

Abstract database queries into repository classes to decouple business logic from data access.

Bad Example

// Eloquent queries scattered in controllers
class ProductController extends Controller
{
    public function index(Request $request)
    {
        $products = Product::query()
            ->when($request->category, fn($q, $cat) => $q->where('category_id', $cat))
            ->when($request->min_price, fn($q, $price) => $q->where('price', '>=', $price))
            ->when($request->max_price, fn($q, $price) => $q->where('price', '<=', $price))
            ->when($request->search, fn($q, $search) => $q->where('name', 'like', "%{$search}%"))
            ->with(['category', 'reviews'])
            ->where('is_active', true)
            ->orderBy('created_at', 'desc')
            ->paginate(20);

        return view('products.index', compact('products'));
    }

    public function featured()
    {
        // Same complex query duplicated
        $products = Product::query()
            ->where('is_featured', true)
            ->where('is_active', true)
            ->with(['category', 'reviews'])
            ->orderBy('featured_at', 'desc')
            ->take(10)
            ->get();

        return view('products.featured', compact('products'));
    }
}

Good Example

// Repository interface
namespace App\Repositories\Contracts;

use App\Models\Product;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;

interface ProductRepositoryInterface
{
    public function find(int $id): ?Product;
    public function findOrFail(int $id): Product;
    public function all(): Collection;
    public function paginate(int $perPage = 15): LengthAwarePaginator;
    public function search(array $filters): LengthAwarePaginator;
    public function featured(int $limit = 10): Collection;
    public function create(array $data): Product;
    public function update(Product $product, array $data): Product;
    public function delete(Product $product): bool;
}
// Repository implementation
namespace App\Repositories;

use App\Models\Product;
use App\Repositories\Contracts\ProductRepositoryInterface;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;

class ProductRepository implements ProductRepositoryInterface
{
    public function __construct(
        private Product $model
    ) {}

    public function find(int $id): ?Product
    {
        return $this->model->find($id);
    }

    public function findOrFail(int $id): Product
    {
        return $this->model->findOrFail($id);
    }

    public function all(): Collection
    {
        return $this->model->active()->get();
    }

    public function paginate(int $perPage = 15): LengthAwarePaginator
    {
        return $this->model
            ->active()
            ->with(['category', 'reviews'])
            ->latest()
            ->paginate($perPage);
    }

    public function search(array $filters): LengthAwarePaginator
    {
        return $this->model
            ->active()
            ->when(
                $filters['category'] ?? null,
                fn($q, $cat) => $q->where('category_id', $cat)
            )
            ->when(
                $filters['min_price'] ?? null,
                fn($q, $price) => $q->where('price', '>=', $price)
            )
            ->when(
                $filters['max_price'] ?? null,
                fn($q, $price) => $q->where('price', '<=', $price)
            )
            ->when(
                $filters['search'] ?? null,
                fn($q, $search) => $q->where('name', 'like', "%{$search}%")
            )
            ->with(['category', 'reviews'])
            ->latest()
            ->paginate($filters['per_page'] ?? 20);
    }

    public function featured(int $limit = 10): Collection
    {
        return $this->model
            ->active()
            ->featured()
            ->with(['category', 'reviews'])
            ->orderBy('featured_at', 'desc')
            ->take($limit)
            ->get();
    }

    public function create(array $data): Product
    {
        return $this->model->create($data);
    }

    public function update(Product $product, array $data): Product
    {
        $product->update($data);
        return $product->fresh();
    }

    public function delete(Product $product): bool
    {
        return $product->delete();
    }
}
// Bind in service provider
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(
            \App\Repositories\Contracts\ProductRepositoryInterface::class,
            \App\Repositories\ProductRepository::class
        );
    }
}
// Clean controller
class ProductController extends Controller
{
    public function __construct(
        private ProductRepositoryInterface $products
    ) {}

    public function index(Request $request)
    {
        $products = $this->products->search($request->all());

        return view('products.index', compact('products'));
    }

    public function featured()
    {
        $products = $this->products->featured();

        return view('products.featured', compact('products'));
    }
}

Why

  • Testability: Easy to mock the repository interface in tests
  • Reusability: Same queries used consistently across the application
  • Maintainability: Query logic changes in one place
  • Abstraction: Business logic doesn't depend on Eloquent specifics
  • Swappable: Can swap implementations (e.g., cache decorator, different database)
  • Clean controllers: Controllers only handle HTTP concerns

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill is a comprehensive guide to Laravel 13 best practices, providing safe and standard architectural, database, and security patterns for PHP development. No malicious behavior or security risks were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    1/37 files flagged

  • ZeroLeaks5mo

    2 findings · Score: 80/100

Signed by skilld at ef59186. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 7 months ago
Other metadata
metadata
{
  "author": "Laravel Community",
  "version": "2.1.0",
  "laravelVersion": "13.x",
  "phpVersion": "8.3+"
}

README badge

README badge for asyrafhussin/agent-skills/laravel-best-practices