Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    personamanagmentlayer

    php-expert

    personamanagmentlayer/php-expert
    Coding
    1
    2 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Expert-level PHP development with PHP 8+, Laravel, Composer, and modern best practices

    SKILL.md

    PHP Expert

    Expert guidance for modern PHP development including PHP 8+ features, Laravel framework, Composer dependency management, and PHP best practices.

    Core Concepts

    PHP 8+ Features

    • Union types and mixed type
    • Named arguments
    • Attributes (annotations)
    • Constructor property promotion
    • Match expressions
    • Nullsafe operator
    • JIT compiler
    • Fibers (PHP 8.1+)
    • Readonly properties and classes

    Object-Oriented PHP

    • Classes and objects
    • Interfaces and abstract classes
    • Traits
    • Namespaces
    • Autoloading (PSR-4)
    • Type declarations
    • Visibility modifiers

    Modern PHP

    • Strict types
    • Return type declarations
    • Property type declarations
    • Enums (PHP 8.1+)
    • First-class callable syntax

    Modern PHP 8+ Syntax

    Constructor Property Promotion

    <?php
    
    // Before PHP 8
    class User {
        private string $name;
        private string $email;
        private int $age;
    
        public function __construct(string $name, string $email, int $age) {
            $this->name = $name;
            $this->email = $email;
            $this->age = $age;
        }
    }
    
    // PHP 8+ (constructor property promotion)
    class User {
        public function __construct(
            private string $name,
            private string $email,
            private int $age,
        ) {}
    
        public function getName(): string {
            return $this->name;
        }
    }
    
    $user = new User('Alice', 'alice@example.com', 30);
    

    Named Arguments

    <?php
    
    function createUser(
        string $name,
        string $email,
        int $age = 18,
        bool $admin = false,
    ): User {
        return new User($name, $email, $age, $admin);
    }
    
    // Named arguments (PHP 8+)
    $user = createUser(
        name: 'Alice',
        email: 'alice@example.com',
        age: 30,
        admin: true,
    );
    
    // Skip optional parameters
    $user = createUser(
        name: 'Bob',
        email: 'bob@example.com',
    );
    

    Union Types and Mixed

    <?php
    
    // Union types (PHP 8+)
    function processValue(int|float $number): int|float {
        return $number * 2;
    }
    
    function findUser(int|string $identifier): ?User {
        if (is_int($identifier)) {
            return User::find($identifier);
        }
        return User::where('email', $identifier)->first();
    }
    
    // Mixed type (accepts any type)
    function debugValue(mixed $value): void {
        var_dump($value);
    }
    

    Match Expression

    <?php
    
    // Old switch
    switch ($status) {
        case 'pending':
            $message = 'Order is pending';
            break;
        case 'processing':
            $message = 'Order is being processed';
            break;
        case 'completed':
            $message = 'Order completed';
            break;
        default:
            $message = 'Unknown status';
    }
    
    // Match expression (PHP 8+)
    $message = match ($status) {
        'pending' => 'Order is pending',
        'processing' => 'Order is being processed',
        'completed' => 'Order completed',
        default => 'Unknown status',
    };
    
    // Multiple conditions
    $result = match ($value) {
        0, 1, 2 => 'Small',
        3, 4, 5 => 'Medium',
        default => 'Large',
    };
    
    // With expressions
    $discount = match (true) {
        $customer->isPremium() && $order->total() > 1000 => 0.20,
        $customer->isPremium() => 0.15,
        $order->total() > 500 => 0.10,
        default => 0,
    };
    

    Nullsafe Operator

    <?php
    
    // Before PHP 8
    $country = null;
    if ($user !== null) {
        $address = $user->getAddress();
        if ($address !== null) {
            $country = $address->getCountry();
        }
    }
    
    // PHP 8+ nullsafe operator
    $country = $user?->getAddress()?->getCountry();
    
    // With default
    $country = $user?->getAddress()?->getCountry() ?? 'Unknown';
    

    Attributes (Annotations)

    <?php
    
    // Define attribute
    #[Attribute]
    class Route {
        public function __construct(
            public string $path,
            public string $method = 'GET',
        ) {}
    }
    
    // Use attribute
    class UserController {
        #[Route('/users', method: 'GET')]
        public function index(): array {
            return User::all();
        }
    
        #[Route('/users/{id}', method: 'GET')]
        public function show(int $id): User {
            return User::findOrFail($id);
        }
    
        #[Route('/users', method: 'POST')]
        public function store(Request $request): User {
            return User::create($request->all());
        }
    }
    
    // Read attributes
    $reflection = new ReflectionClass(UserController::class);
    foreach ($reflection->getMethods() as $method) {
        $attributes = $method->getAttributes(Route::class);
        foreach ($attributes as $attribute) {
            $route = $attribute->newInstance();
            echo "{$route->method} {$route->path}\n";
        }
    }
    

    Enums (PHP 8.1+)

    <?php
    
    // Basic enum
    enum Status {
        case Pending;
        case Processing;
        case Completed;
        case Cancelled;
    }
    
    // Usage
    $status = Status::Pending;
    
    function updateOrder(Order $order, Status $status): void {
        $order->status = $status;
        $order->save();
    }
    
    // Backed enums
    enum OrderStatus: string {
        case Pending = 'pending';
        case Processing = 'processing';
        case Completed = 'completed';
        case Cancelled = 'cancelled';
    
        public function label(): string {
            return match($this) {
                self::Pending => 'Pending',
                self::Processing => 'Being Processed',
                self::Completed => 'Completed',
                self::Cancelled => 'Cancelled',
            };
        }
    
        public function color(): string {
            return match($this) {
                self::Pending => 'yellow',
                self::Processing => 'blue',
                self::Completed => 'green',
                self::Cancelled => 'red',
            };
        }
    }
    
    $status = OrderStatus::from('pending');
    echo $status->label(); // 'Pending'
    echo $status->value;   // 'pending'
    

    Readonly Properties and Classes

    <?php
    
    // Readonly property (PHP 8.1+)
    class User {
        public function __construct(
            public readonly string $id,
            public readonly string $email,
            public string $name,
        ) {}
    }
    
    $user = new User('123', 'user@example.com', 'Alice');
    $user->name = 'Bob'; // OK
    // $user->email = 'new@example.com'; // Error: readonly property
    
    // Readonly class (PHP 8.2+)
    readonly class Point {
        public function __construct(
            public int $x,
            public int $y,
        ) {}
    }
    

    Laravel Framework

    Models

    <?php
    
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Model;
    use Illuminate\Database\Eloquent\Relations\HasMany;
    use Illuminate\Database\Eloquent\Relations\BelongsToMany;
    use Illuminate\Database\Eloquent\SoftDeletes;
    
    class User extends Model
    {
        use SoftDeletes;
    
        protected $fillable = [
            'name',
            'email',
            'password',
        ];
    
        protected $hidden = [
            'password',
            'remember_token',
        ];
    
        protected $casts = [
            'email_verified_at' => 'datetime',
            'is_admin' => 'boolean',
            'settings' => 'array',
        ];
    
        // Relationships
        public function posts(): HasMany
        {
            return $this->hasMany(Post::class);
        }
    
        public function roles(): BelongsToMany
        {
            return $this->belongsToMany(Role::class);
        }
    
        // Accessors (Laravel 9+)
        protected function name(): Attribute
        {
            return Attribute::make(
                get: fn (string $value) => ucfirst($value),
                set: fn (string $value) => strtolower($value),
            );
        }
    
        // Scopes
        public function scopeActive($query)
        {
            return $query->where('active', true);
        }
    
        public function scopeAdmins($query)
        {
            return $query->where('is_admin', true);
        }
    
        // Methods
        public function isAdmin(): bool
        {
            return $this->is_admin;
        }
    }
    
    // Usage
    $users = User::active()->get();
    $admins = User::admins()->get();
    $user = User::with('posts', 'roles')->find(1);
    

    Controllers

    <?php
    
    namespace App\Http\Controllers\Api;
    
    use App\Http\Controllers\Controller;
    use App\Http\Requests\StorePostRequest;
    use App\Http\Requests\UpdatePostRequest;
    use App\Http\Resources\PostResource;
    use App\Models\Post;
    use Illuminate\Http\JsonResponse;
    use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
    
    class PostController extends Controller
    {
        public function __construct()
        {
            $this->middleware('auth:api')->except(['index', 'show']);
        }
    
        public function index(): AnonymousResourceCollection
        {
            $posts = Post::with('user')
                ->published()
                ->latest()
                ->paginate(20);
    
            return PostResource::collection($posts);
        }
    
        public function show(Post $post): PostResource
        {
            $post->load(['user', 'comments.user']);
            return new PostResource($post);
        }
    
        public function store(StorePostRequest $request): JsonResponse
        {
            $post = $request->user()->posts()->create(
                $request->validated()
            );
    
            return response()->json(
                new PostResource($post),
                201
            );
        }
    
        public function update(UpdatePostRequest $request, Post $post): PostResource
        {
            $this->authorize('update', $post);
    
            $post->update($request->validated());
    
            return new PostResource($post);
        }
    
        public function destroy(Post $post): JsonResponse
        {
            $this->authorize('delete', $post);
    
            $post->delete();
    
            return response()->json(null, 204);
        }
    }
    

    Form Requests

    <?php
    
    namespace App\Http\Requests;
    
    use Illuminate\Foundation\Http\FormRequest;
    
    class StorePostRequest extends FormRequest
    {
        public function authorize(): bool
        {
            return $this->user() !== null;
        }
    
        public function rules(): array
        {
            return [
                'title' => ['required', 'string', 'max:200'],
                'content' => ['required', 'string', 'min:100'],
                'published' => ['sometimes', 'boolean'],
                'tags' => ['sometimes', 'array'],
                'tags.*' => ['string', 'max:50'],
            ];
        }
    
        public function messages(): array
        {
            return [
                'title.required' => 'Please provide a title for your post',
                'content.min' => 'Post content must be at least 100 characters',
            ];
        }
    }
    

    API Resources

    <?php
    
    namespace App\Http\Resources;
    
    use Illuminate\Http\Request;
    use Illuminate\Http\Resources\Json\JsonResource;
    
    class PostResource extends JsonResource
    {
        public function toArray(Request $request): array
        {
            return [
                'id' => $this->id,
                'title' => $this->title,
                'slug' => $this->slug,
                'content' => $this->when(
                    $request->routeIs('posts.show'),
                    $this->content
                ),
                'excerpt' => $this->excerpt,
                'published' => $this->published,
                'published_at' => $this->published_at?->toIso8601String(),
                'author' => new UserResource($this->whenLoaded('user')),
                'comments' => CommentResource::collection(
                    $this->whenLoaded('comments')
                ),
                'tags' => $this->tags,
                'created_at' => $this->created_at->toIso8601String(),
                'updated_at' => $this->updated_at->toIso8601String(),
            ];
        }
    }
    

    Eloquent Queries

    <?php
    
    // Basic queries
    $users = User::all();
    $user = User::find(1);
    $user = User::where('email', 'user@example.com')->first();
    
    // Complex queries
    $posts = Post::where('published', true)
        ->where('created_at', '>', now()->subWeek())
        ->orderBy('created_at', 'desc')
        ->limit(10)
        ->get();
    
    // Eager loading (avoid N+1)
    $posts = Post::with(['user', 'comments.user'])->get();
    
    // Lazy eager loading
    $posts = Post::all();
    $posts->load('user');
    
    // Conditional loading
    $posts = Post::with([
        'user' => function ($query) {
            $query->select('id', 'name', 'email');
        },
        'comments' => function ($query) {
            $query->latest()->limit(5);
        },
    ])->get();
    
    // Aggregates
    $count = Post::where('published', true)->count();
    $sum = Order::where('status', 'completed')->sum('total');
    $avg = Product::avg('price');
    
    // Chunk processing
    Post::chunk(100, function ($posts) {
        foreach ($posts as $post) {
            // Process post
        }
    });
    
    // Transactions
    DB::transaction(function () use ($data) {
        $user = User::create($data['user']);
        $profile = $user->profile()->create($data['profile']);
        $user->roles()->attach($data['roles']);
    });
    

    Migrations

    <?php
    
    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Support\Facades\Schema;
    
    return new class extends Migration
    {
        public function up(): void
        {
            Schema::create('posts', function (Blueprint $table) {
                $table->id();
                $table->foreignId('user_id')->constrained()->cascadeOnDelete();
                $table->string('title');
                $table->string('slug')->unique();
                $table->text('content');
                $table->text('excerpt')->nullable();
                $table->boolean('published')->default(false);
                $table->timestamp('published_at')->nullable();
                $table->json('tags')->nullable();
                $table->timestamps();
                $table->softDeletes();
    
                $table->index('published');
                $table->index('published_at');
                $table->index(['user_id', 'published']);
            });
        }
    
        public function down(): void
        {
            Schema::dropIfExists('posts');
        }
    };
    

    Jobs (Queues)

    <?php
    
    namespace App\Jobs;
    
    use App\Models\User;
    use Illuminate\Bus\Queueable;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Illuminate\Foundation\Bus\Dispatchable;
    use Illuminate\Queue\InteractsWithQueue;
    use Illuminate\Queue\SerializesModels;
    
    class SendWelcomeEmail implements ShouldQueue
    {
        use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    
        public function __construct(
            public User $user,
        ) {}
    
        public function handle(): void
        {
            Mail::to($this->user->email)
                ->send(new WelcomeEmail($this->user));
        }
    
        public function failed(\Throwable $exception): void
        {
            // Handle job failure
            Log::error('Failed to send welcome email', [
                'user_id' => $this->user->id,
                'error' => $exception->getMessage(),
            ]);
        }
    }
    
    // Dispatch job
    SendWelcomeEmail::dispatch($user);
    SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(10));
    SendWelcomeEmail::dispatch($user)->onQueue('emails');
    

    Events and Listeners

    <?php
    
    // Event
    namespace App\Events;
    
    use App\Models\Post;
    use Illuminate\Foundation\Events\Dispatchable;
    use Illuminate\Queue\SerializesModels;
    
    class PostPublished
    {
        use Dispatchable, SerializesModels;
    
        public function __construct(
            public Post $post,
        ) {}
    }
    
    // Listener
    namespace App\Listeners;
    
    use App\Events\PostPublished;
    use App\Notifications\NewPostNotification;
    
    class NotifyFollowers
    {
        public function handle(PostPublished $event): void
        {
            $followers = $event->post->user->followers;
    
            foreach ($followers as $follower) {
                $follower->notify(new NewPostNotification($event->post));
            }
        }
    }
    
    // Register in EventServiceProvider
    protected $listen = [
        PostPublished::class => [
            NotifyFollowers::class,
        ],
    ];
    
    // Dispatch event
    PostPublished::dispatch($post);
    

    Testing with PHPUnit

    Feature Tests

    <?php
    
    namespace Tests\Feature;
    
    use App\Models\User;
    use App\Models\Post;
    use Illuminate\Foundation\Testing\RefreshDatabase;
    use Tests\TestCase;
    
    class PostControllerTest extends TestCase
    {
        use RefreshDatabase;
    
        public function test_can_list_posts(): void
        {
            Post::factory()->count(3)->create(['published' => true]);
            Post::factory()->create(['published' => false]);
    
            $response = $this->getJson('/api/posts');
    
            $response->assertOk()
                ->assertJsonCount(3, 'data');
        }
    
        public function test_can_create_post_when_authenticated(): void
        {
            $user = User::factory()->create();
    
            $response = $this->actingAs($user, 'api')
                ->postJson('/api/posts', [
                    'title' => 'Test Post',
                    'content' => 'Test content with enough characters to pass validation.',
                ]);
    
            $response->assertCreated()
                ->assertJsonPath('data.title', 'Test Post');
    
            $this->assertDatabaseHas('posts', [
                'title' => 'Test Post',
                'user_id' => $user->id,
            ]);
        }
    
        public function test_cannot_create_post_when_not_authenticated(): void
        {
            $response = $this->postJson('/api/posts', [
                'title' => 'Test Post',
                'content' => 'Test content',
            ]);
    
            $response->assertUnauthorized();
        }
    
        public function test_validates_post_creation(): void
        {
            $user = User::factory()->create();
    
            $response = $this->actingAs($user, 'api')
                ->postJson('/api/posts', [
                    'title' => '', // Invalid
                    'content' => 'Short', // Too short
                ]);
    
            $response->assertUnprocessable()
                ->assertJsonValidationErrors(['title', 'content']);
        }
    
        public function test_can_update_own_post(): void
        {
            $user = User::factory()->create();
            $post = Post::factory()->create(['user_id' => $user->id]);
    
            $response = $this->actingAs($user, 'api')
                ->putJson("/api/posts/{$post->id}", [
                    'title' => 'Updated Title',
                    'content' => 'Updated content with enough characters.',
                ]);
    
            $response->assertOk();
            $this->assertDatabaseHas('posts', [
                'id' => $post->id,
                'title' => 'Updated Title',
            ]);
        }
    
        public function test_cannot_update_other_user_post(): void
        {
            $user = User::factory()->create();
            $otherUser = User::factory()->create();
            $post = Post::factory()->create(['user_id' => $otherUser->id]);
    
            $response = $this->actingAs($user, 'api')
                ->putJson("/api/posts/{$post->id}", [
                    'title' => 'Updated Title',
                ]);
    
            $response->assertForbidden();
        }
    }
    

    Unit Tests

    <?php
    
    namespace Tests\Unit;
    
    use App\Models\User;
    use App\Models\Post;
    use Illuminate\Foundation\Testing\RefreshDatabase;
    use Tests\TestCase;
    
    class UserTest extends TestCase
    {
        use RefreshDatabase;
    
        public function test_user_has_posts(): void
        {
            $user = User::factory()->create();
            $posts = Post::factory()->count(3)->create(['user_id' => $user->id]);
    
            $this->assertCount(3, $user->posts);
            $this->assertTrue($user->posts->contains($posts->first()));
        }
    
        public function test_is_admin_returns_true_for_admin_users(): void
        {
            $admin = User::factory()->create(['is_admin' => true]);
            $user = User::factory()->create(['is_admin' => false]);
    
            $this->assertTrue($admin->isAdmin());
            $this->assertFalse($user->isAdmin());
        }
    }
    

    Factories

    <?php
    
    namespace Database\Factories;
    
    use App\Models\User;
    use Illuminate\Database\Eloquent\Factories\Factory;
    
    class PostFactory extends Factory
    {
        public function definition(): array
        {
            return [
                'user_id' => User::factory(),
                'title' => fake()->sentence(),
                'slug' => fake()->slug(),
                'content' => fake()->paragraphs(5, true),
                'excerpt' => fake()->paragraph(),
                'published' => false,
                'published_at' => null,
                'tags' => fake()->words(3),
            ];
        }
    
        public function published(): static
        {
            return $this->state(fn (array $attributes) => [
                'published' => true,
                'published_at' => now(),
            ]);
        }
    
        public function withUser(User $user): static
        {
            return $this->state(fn (array $attributes) => [
                'user_id' => $user->id,
            ]);
        }
    }
    

    Best Practices

    Type Safety

    <?php
    declare(strict_types=1);
    
    // Always use strict types
    // Use type declarations for parameters and return types
    // Use property types where possible
    

    Dependency Injection

    <?php
    
    // Use constructor injection
    class UserService
    {
        public function __construct(
            private UserRepository $repository,
            private EventDispatcher $dispatcher,
        ) {}
    
        public function createUser(array $data): User
        {
            $user = $this->repository->create($data);
            $this->dispatcher->dispatch(new UserCreated($user));
            return $user;
        }
    }
    

    PSR Standards

    • PSR-1: Basic Coding Standard
    • PSR-4: Autoloading Standard
    • PSR-12: Extended Coding Style
    • PSR-7: HTTP Message Interface

    Anti-Patterns to Avoid

    ❌ Not using strict types: Always declare(strict_types=1) ❌ Fat controllers: Extract logic to services ❌ N+1 queries: Use eager loading ❌ No type declarations: Use types everywhere ❌ Ignoring PSR standards: Follow PSR-4, PSR-12 ❌ Direct DB queries in controllers: Use repositories ❌ Missing validation: Always validate input ❌ No tests: Write tests for critical code

    Resources

    • PHP Documentation: https://www.php.net/docs.php
    • Laravel Documentation: https://laravel.com/docs
    • Composer: https://getcomposer.org/
    • PSR Standards: https://www.php-fig.org/psr/
    • PHPUnit: https://phpunit.de/
    Recommended Servers
    Vercel Grep
    Vercel Grep
    Svelte
    Svelte
    InfraNodus Knowledge Graphs & Text Analysis
    InfraNodus Knowledge Graphs & Text Analysis
    Repository
    personamanagmentlayer/pcl
    Files