AgentSkillsCN

laravel-fp

引导智能体以函数式编程原则开发 Laravel 应用程序。涵盖模型、控制器、校验、任务、CLI 命令、服务及辅助工具等领域的 FP 设计模式,同时保持 Laravel 的目录结构,绝不删除现有类。

SKILL.md
--- frontmatter
name: laravel-fp
description: Guides the agent to write Laravel applications using functional programming principles. Covers FP patterns for models, controllers, validation, jobs, CLI commands, services, and helpers while maintaining Laravel's folder structure and never removing existing classes.
license: MIT
metadata:
  author: "Imam Susanto"
  version: "1.0.0"
dependencies:
  - spatie/laravel-data      # Data transfer objects
  - fp4php/functional        # Monads (Option, Either, Result)

Laravel Functional Programming Guide

This skill guides agents to write Laravel applications using functional programming (FP) principles while respecting Laravel's conventions.

Core Principles

  1. Immutability: Never mutate data; create new instances instead
  2. Pure Functions: Functions should have no side effects and return consistent outputs for same inputs
  3. Higher-Order Functions: Use map, filter, reduce, and other collection operations
  4. Function Composition: Combine small functions to build complex behavior
  5. Avoid Global State: Minimize use of facades and static helpers in core logic
  6. Type Safety: Use PHP type declarations and value objects

Best Practices

Declare Strict Types

Always enable strict typing in PHP files:

php
<?php

declare(strict_types=1);

namespace App\Data;
// ...

Why:

  • Type coercion is disabled (1 !== '1')
  • Better type safety
  • Clearer error messages
  • Prevents silent bugs

Single Responsibility

Each function/class should do one thing:

php
// ❌ Bad - does too much
function createUser(array $data): array
{
    $validated = validate($data);
    $user = User::create($validated);
    sendWelcomeEmail($user);
    return $user->toArray();
}

// ✅ Good - single responsibility
function validateUserData(array $data): Result { ... }
function persistUser(array $data): User { ... }
function notifyUser(User $user): void { ... }

Prefer Composition Over Inheritance

php
// ❌ Inheritance - tightly coupled
class AdminUser extends User
{
    public function deleteEverything() { ... }
}

// ✅ Composition - flexible
class UserService
{
    public function __construct(
        private AuthorizationService $auth,
        private NotificationService $notify,
    ) {}

    public function deleteUser(User $user): Result { ... }
}

Avoid Side Effects in Pure Functions

php
// ❌ Side effect - modifies global state
function registerUser(array $data): User
{
    $user = User::create($data);
    session()->flash('success', 'User created'); // Side effect!
    return $user;
}

// ✅ Pure - no side effects
function createUser(array $data): Result
{
    return Result::success(User::create($data));
}

// Controller handles side effects
$result = $this->userService->createUser($data);
$result->then(fn($user) => session()->flash('success', 'User created'));

Recommended Packages

bash
composer require spatie/laravel-data     # Data transfer objects (RECOMMENDED)
composer require fp4php/functional       # Monads (Option, Either, Result) - NOT for Collection operations

Use Laravel built-ins for:

  • Collection - map, filter, reduce, etc.
  • Number facade - formatting
  • str() helper - UUID, slugs, strings
  • illuminate/validation - validation
  • Bus/Queue - commands/jobs

Folder Structure (Preserve Laravel Boilerplate)

code
app/
├── Console/          # Keep existing - use FP in commands
├── Data/             # Spatie Laravel Data objects (NEW)
├── Exceptions/       # Keep existing - functional exception handling
├── Http/
│   ├── Controllers/  # Functional controllers
│   ├── Middleware/   # Keep existing - compose middleware functionally
│   └── Requests/     # Functional form requests
├── Jobs/             # Pure job classes
├── Models/           # Keep existing - enhance with FP patterns
├── Providers/        # Keep existing
├── Repositories/     # Functional repositories
├── Services/         # Functional services
├── Support/          # Helpers, Result, Maybe monads
└── Traits/           # FP traits

Quick Reference Patterns

Result Monad Pattern (using fp4php/functional)

php
<?php declare(strict_types=1);

namespace App\Support;

use Fp4php\Functional\Result\Result;
use Fp4php\Functional\Result\Success;
use Fp4php\Functional\Result\Failure;

// Usage in controller:
return $this->validateRequest($request)
    ->andThen(fn($data) => User::create($data))
    ->map(fn($user) => response()->json($user, 201))
    ->getOrElse(fn($error) => response()->json(['error' => $error], 500));

Why fp4php/functional?

  • Laravel doesn't have a Result monad
  • Provides map, filter, flatMap, getOrElse, etc.
  • Composable error handling without exceptions

Collection Pipeline

php
// ✅ Functional - prefer
User::where('is_active', true)
    ->get()
    ->filter(fn($u) => $u->role === 'admin')
    ->map(fn($u) => ['id' => $u->id, 'name' => $u->name])
    ->toArray();

Data Objects

php
// app/Data/UserData.php
class UserData extends Data
{
    public function __construct(
        public string $email,
        public string $name,
        public ?int $id = null,
    ) {}
}

// In controller:
return UserData::from(User::create($data->toArray()));

Progressive Disclosure

Load detailed references when needed:

TopicFile
Models, Controllers, ServicesREFERENCE.md
Validation patternsVALIDATION.md
Spatie Data objectsDATAOBJECTS.md
Value objectsVALUEOBJECTS.md
Helper functionsHELPERS.md
CLI commandsCLI.md

Key Patterns

Functional Controller

See REFERENCE.md for full controller patterns with Result monad, validation, and error handling.

Functional Jobs

See REFERENCE.md for pure job pattern with batch processing and Laravel native commands.

Functional Services

See REFERENCE.md for service layer with dependency injection and pure functions.

Things to Avoid

  1. Mutating collections in place - Use map, filter, reject which return new collections
  2. Direct facade calls in business logic - Inject services instead
  3. Global state - Pass dependencies explicitly
  4. Procedural code in controllers - Extract to services
  5. Side effects in models - Keep models anemic (data only)
  6. Static helpers - Use dependency injection

Never Remove Existing Classes

When refactoring existing Laravel code:

  • Add new classes alongside existing ones
  • Extend rather than replace
  • Deprecate with warning comments
  • Use traits to inject FP patterns into existing classes
  • Never delete models, controllers, or other core classes

References