AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Laravel

skill-omar-obando-qwen-orchestrator-laravel · by Omar-Obando

>

No reviews yet
0 installs
41 views
0.0% view→install

Install

$ agentstack add skill-omar-obando-qwen-orchestrator-laravel

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-omar-obando-qwen-orchestrator-laravel)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Laravel? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Laravel Development Skill — PHP Framework Mastery

This skill provides comprehensive Laravel development patterns following convention-over-configuration principles, Eloquent-first data access, and enterprise-grade architecture.

Key Principles

  • Convention over Configuration: Follow Laravel naming conventions for zero-config discovery
  • Eloquent-First: Use ORM for all data access; raw SQL only for complex queries
  • Validation at the Edge: Form Requests validate before reaching controllers
  • Thin Controllers, Fat Models: Business logic in models and services, not controllers

Project Scaffolding

Model Creation (All-in-One)

php artisan make:model Product -crR
# Creates: Model + Controller (resource) + Form Requests + API Resource

| Flag | Creates | | ---- | ------------------------------------------- | | -c | Controller | | -r | Resource controller (CRUD methods) | | -R | Form Requests (StoreRequest, UpdateRequest) | | -m | Migration | | -f | Factory | | -s | Seeder |

Eloquent ORM

Relationships

class User extends Model
{
    // One-to-One
    public function profile(): HasOne
    {
        return $this->hasOne(Profile::class);
    }

    // One-to-Many
    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }

    // Inverse: belongsTo (on the child model)
    public function author(): BelongsTo
    {
        return $this->belongsTo(User::class, 'author_id');
    }

    // Many-to-Many (pivot table)
    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class)
            ->withPivot('assigned_at')
            ->withTimestamps();
    }

    // Polymorphic
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }

    // Has-One-Through
    public function license(): HasOneThrough
    {
        return $this->hasOneThrough(License::class, Subscription::class);
    }
}

Eager Loading (Prevent N+1)

// BAD: N+1 query — one query per post to get author
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->author->name; // N queries
}

// GOOD: Eager load with 2 queries total
$posts = Post::with('author', 'comments.user')->get();
foreach ($posts as $post) {
    echo $post->author->name; // No additional queries
}

// Conditional eager loading
$posts = Post::with(['comments' => function ($query) {
    $query->where('is_approved', true)->latest();
}])->get();

// Lazy eager loading (load only when needed)
$posts = Post::all();
if ($needComments) {
    $posts->load('comments');
}

Factories and Seeders

// database/factories/ProductFactory.php
class ProductFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->words(3, true),
            'sku' => fake()->unique()->ean8(),
            'price' => fake()->randomFloat(2, 5, 500),
            'description' => fake()->paragraphs(3, true),
            'is_active' => true,
            'category_id' => Category::factory(),
        ];
    }

    public function inactive(): static
    {
        return $this->state(fn (array $attributes) => [
            'is_active' => false,
        ]);
    }
}

// database/seeders/DatabaseSeeder.php
class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        Category::factory(10)->create();
        Product::factory(100)
            ->has(Review::factory()->count(3))
            ->create();
    }
}

Scopes, Accessors, and Mutators

class Product extends Model
{
    // Query Scope
    public function scopeActive(Builder $query): Builder
    {
        return $query->where('is_active', true);
    }

    public function scopeInStock(Builder $query): Builder
    {
        return $query->where('stock_count', '>', 0);
    }

    // Accessor (computed attribute)
    protected function formattedPrice(): Attribute
    {
        return Attribute::make(
            get: fn (mixed $value, array $attributes) =>
                '$' . number_format($attributes['price'], 2),
        );
    }

    // Mutator (transform on set)
    protected function name(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
            set: fn (string $value) => strtolower(trim($value)),
        );
    }

    // Casts
    protected function casts(): array
    {
        return [
            'price' => 'decimal:2',
            'is_active' => 'boolean',
            'metadata' => 'array',
            'published_at' => 'datetime',
            'tags' => 'collection',
        ];
    }
}

Form Requests (Validation at the Edge)

// php artisan make:request StoreProductRequest
class StoreProductRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', Product::class);
    }

    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'sku' => ['required', 'string', 'unique:products,sku'],
            'price' => ['required', 'numeric', 'min:0', 'max:999999.99'],
            'description' => ['nullable', 'string', 'max:5000'],
            'category_id' => ['required', 'exists:categories,id'],
            'is_active' => ['boolean'],
            'tags' => ['array'],
            'tags.*' => ['string', 'max:50'],
        ];
    }

    public function messages(): array
    {
        return [
            'sku.unique' => 'This SKU is already in use.',
            'price.max' => 'Price cannot exceed 999,999.99.',
        ];
    }

    // Prepare data before validation
    protected function prepareForValidation(): void
    {
        $this->merge([
            'sku' => strtoupper(trim($this->sku)),
        ]);
    }
}

Service Container and Dependency Injection

Automatic Injection

class ProductController extends Controller
{
    // Automatic resolution via constructor
    public function __construct(
        private readonly ProductRepository $products,
    ) {}

    // Automatic resolution via method
    public function show(Product $product): ProductResource
    {
        return new ProductResource($product);
    }
}

Binding

// AppServiceProvider.php
public function register(): void
{
    // Interface → Implementation
    $this->app->bind(
        PaymentGatewayInterface::class,
        StripeGateway::class
    );

    // Singleton (shared instance)
    $this->app->singleton(
        CacheService::class,
        fn () => new CacheService(config('cache.ttl'))
    );

    // Scoped (one per request cycle)
    $this->app->scoped(TenantManager::class);

    // Factory binding (new instance each time)
    $this->app->bind(ReportGenerator::class, function ($app) {
        return new ReportGenerator(
            $app->make(DataSource::class),
            $app->make(Formatter::class),
        );
    });
}

API Development

API Resources

class ProductResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'sku' => $this->sku,
            'price' => [
                'amount' => (float) $this->price,
                'currency' => 'USD',
                'formatted' => $this->formatted_price,
            ],
            'category' => new CategoryResource($this->whenLoaded('category')),
            'reviews_count' => $this->whenCounted('reviews'),
            'tags' => $this->whenLoaded('tags'),
            'created_at' => $this->created_at->toISOString(),
            'links' => [
                'self' => route('api.products.show', $this->id),
            ],
        ];
    }
}

// Collection with metadata
class ProductCollection extends ResourceCollection
{
    public function toArray(Request $request): array
    {
        return [
            'data' => $this->collection,
            'meta' => [
                'total' => $this->total(),
                'count' => $this->count(),
                'per_page' => $this->perPage(),
                'current_page' => $this->currentPage(),
            ],
        ];
    }
}

API Routes

// routes/api.php
Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
    Route::apiResource('products', ProductController::class);

    Route::get('products/search', [ProductController::class, 'search']);

    Route::prefix('admin')->middleware('role:admin')->group(function () {
        Route::apiResource('users', Admin\UserController::class);
    });
});

Queues and Jobs

Job Classes

class ProcessProductImport implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public int $backoff = 30;
    public bool $deleteWhenMissingModels = true;

    public function __construct(
        public readonly string $filePath,
        public readonly int $userId,
    ) {}

    public function handle(
        ProductImportService $importService,
        NotificationService $notifications,
    ): void {
        $result = $importService->import($this->filePath);

        $notifications->notifyUser($this->userId, new ImportComplete($result));
    }

    public function failed(\Throwable $exception): void
    {
        Log::error('Product import failed', [
            'file' => $this->filePath,
            'error' => $exception->getMessage(),
        ]);
    }
}

// Dispatch
ProcessProductImport::dispatch($filePath, auth()->id())
    ->onQueue('imports')
    ->delay(now()->addMinutes(5));

// Job chains
Bus::chain([
    new ValidateImportFile($path),
    new ProcessProductImport($path),
    new CleanupImportFile($path),
])->catch(function (\Throwable $e) {
    Log::error('Import chain failed: ' . $e->getMessage());
})->dispatch();

Middleware

// Custom middleware
class EnsureTenantScope
{
    public function handle(Request $request, Closure $next): Response
    {
        if ($tenant = $request->route('tenant')) {
            app(TenantManager::class)->setCurrent($tenant);
        }

        return $next($request);
    }
}

// Registration in bootstrap/app.php or Kernel.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->appendToGroup('api', [
        \App\Http\Middleware\EnsureJsonResponse::class,
    ]);

    $middleware->alias([
        'tenant' => \App\Http\Middleware\EnsureTenantScope::class,
        'role' => \App\Http\Middleware\EnsureRole::class,
    ]);
});

Policy-Based Authorization

// php artisan make:policy ProductPolicy --model=Product
class ProductPolicy
{
    public function viewAny(User $user): bool
    {
        return $user->can('products.view');
    }

    public function view(User $user, Product $product): bool
    {
        return $user->belongsToTenant($product->tenant);
    }

    public function create(User $user): bool
    {
        return $user->can('products.create');
    }

    public function update(User $user, Product $product): bool
    {
        return $user->can('products.update')
            && $user->belongsToTenant($product->tenant);
    }

    // Super-admin bypass
    public function before(User $user, string $ability): bool|null
    {
        return $user->isSuperAdmin() ? true : null;
    }
}

// Usage in controller
class ProductController extends Controller
{
    public function update(UpdateProductRequest $request, Product $product)
    {
        $this->authorize('update', $product);
        // ...
    }
}

// Usage in Blade
@can('update', $product)
    Edit
@endcan

Migrations

// php artisan make:migration create_products_table
return new class extends Migration
{
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
            $table->foreignId('category_id')->nullable()->constrained()->nullOnDelete();
            $table->string('name');
            $table->string('sku')->unique();
            $table->decimal('price', 10, 2)->default(0);
            $table->text('description')->nullable();
            $table->json('metadata')->nullable();
            $table->boolean('is_active')->default(true);
            $table->unsignedInteger('stock_count')->default(0);
            $table->timestamp('published_at')->nullable();
            $table->softDeletes();
            $table->timestamps();

            // Indexes for common queries
            $table->index(['tenant_id', 'is_active']);
            $table->index('published_at');
            $table->fullText(['name', 'description']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};

Testing

Feature Tests with Pest

test('can list active products', function () {
    Product::factory(3)->create(['is_active' => true]);
    Product::factory(2)->create(['is_active' => false]);

    $response = $this->getJson('/api/products');

    $response->assertOk()
        ->assertJsonCount(3, 'data')
        ->assertJsonStructure([
            'data' => [
                '*' => ['id', 'name', 'price', 'category'],
            ],
        ]);
});

test('cannot create product without authentication', function () {
    $this->postJson('/api/products', [
        'name' => 'Test Product',
    ])->assertUnauthorized();
});

test('product belongs to user tenant', function () {
    $user = User::factory()->create();
    $product = Product::factory()->create(['tenant_id' => $user->tenant_id]);

    $response = $this->actingAs($user)
        ->getJson("/api/products/{$product->id}");

    $response->assertOk();
});

HTTP Test Assertions

$response->assertStatus(200);
$response->assertJson(['status' => 'success']);
$response->assertJsonPath('data.name', 'Test Product');
$response->assertJsonCount(10, 'data');
$response->assertJsonStructure(['data' => ['id', 'name']]);
$response->assertJsonValidationErrors(['email']);
$response->assertCreated();
$response->assertNotFound();
$response->assertForbidden();

Anti-Patterns

| Anti-Pattern | Problem | Fix | | ---------------------------------- | ---------------------------- | -------------------------------------------- | | Fat controllers | Hard to test, mixed concerns | Extract to services/actions | | Queries in Blade templates | N+1, slow rendering | Eager load in controller | | No eager loading | N+1 queries | Use ::with() or ::load() | | Raw SQL without bindings | SQL injection | Use Eloquent or parameterized queries | | Ignoring mass assignment | Security vulnerability | Use $fillable or $guarded on all models | | Validation in controllers | Repeated, hard to test | Use Form Request classes | | Business logic in routes | No reusability | Move to controller or service | | Not using database transactions | Data inconsistency | Wrap related operations in DB::transaction | | Returning models directly from API | Exposes internal structure | Use API Resources for transformation | | Catching all exceptions silently | Hidden bugs | Log and handle specifically |

When NOT to Use

Do NOT use this skill when:

  • Writing application business logic (use domain-

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.