| name | php-expert |
| version | 1.0.0 |
| description | Expert-level PHP development with PHP 8+, Laravel, Composer, and modern best practices |
| category | languages |
| tags | ["php","laravel","composer","symfony","phpunit","psr"] |
| allowed-tools | ["Read","Write","Edit","Bash(php:*, composer:*, artisan:*)"] |
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
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;
}
}
class User {
public function __construct(
private string $name,
private string $email,
private int $age,
) {}
public function getName(): string {
return ->name;
}
}
= (, , );
Named Arguments
<?php
function createUser(
string $name,
string $email,
int $age = 18,
bool $admin = false,
): User {
return new User($name, $email, $age, $admin);
}
$user = createUser(
name: 'Alice',
email: 'alice@example.com',
age: 30,
admin: true,
);
$user = createUser(
name: 'Bob',
email: 'bob@example.com',
);
Union Types and Mixed
<?php
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();
}
function debugValue(mixed $value): void {
var_dump($value);
}
Match Expression
<?php
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';
}
$message = match ($status) {
'pending' => 'Order is pending',
'processing' => 'Order is being processed',
'completed' => 'Order completed',
default => 'Unknown status',
};
$result = match ($value) {
0, 1, 2 => 'Small',
3, 4, 5 => 'Medium',
default => 'Large',
};
$discount = match (true) {
->() && ->() > => ,
->() => ,
->() > => ,
=> ,
};
Nullsafe Operator
<?php
$country = null;
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$country = $address->getCountry();
}
}
$country = $user?->getAddress()?->getCountry();
$country = $user?->getAddress()?->getCountry() ?? 'Unknown';
Attributes (Annotations)
<?php
#[Attribute]
class Route {
public function __construct(
public string $path,
public string $method = 'GET',
) {}
}
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')
{
::(->());
}
}
= (::);
(->() ) {
= ->(::);
( ) {
= ->();
;
}
}
Enums (PHP 8.1+)
<?php
enum Status {
case Pending;
case Processing;
case Completed;
case Cancelled;
}
$status = Status::Pending;
function updateOrder(Order $order, Status $status): void {
$order->status = $status;
$order->save();
}
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:: => ,
:: => ,
};
}
{
() {
:: => ,
:: => ,
:: => ,
:: => ,
};
}
}
= ::();
->();
->value;
Readonly Properties and Classes
<?php
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';
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',
];
public function ():
{
->(::);
}
{
->(::);
}
{
::(
: fn ( ) => (),
set: (),
);
}
{
->(, );
}
{
->(, );
}
{
->is_admin;
}
}
= ::()->();
= ::()->();
= ::(, )->();
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 {
= ::()
->()
->()
->();
::();
}
{
->([, ]);
();
}
{
= ->()->()->(
->()
);
()->(
(),
);
}
{
->(, );
->(->());
();
}
{
->(, );
->();
()->(, );
}
}
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
{
[
=> ,
=> ,
];
}
}
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(->()),
=> ::(
->()
),
=> ->tags,
=> ->created_at->(),
=> ->updated_at->(),
];
}
}
Eloquent Queries
<?php
$users = User::all();
$user = User::find(1);
$user = User::where('email', 'user@example.com')->first();
$posts = Post::where('published', true)
->where('created_at', '>', now()->subWeek())
->orderBy('created_at', 'desc')
->limit(10)
->get();
$posts = Post::with(['user', 'comments.user'])->get();
$posts = Post::all();
$posts->load('user');
$posts = Post::with([
=> function () {
->(, , );
},
=> {
->()->();
},
])->();
= ::(, )->();
= ::(, )->();
= ::();
::(, function () {
( ) {
}
});
DB::(function () ($) {
$ = ::($['']);
= ->()->([]);
->()->([]);
});
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();
->()->();
->()->();
->()->();
->();
->();
->();
->();
->([, ]);
});
}
{
::();
}
};
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( (->user));
}
{
::(, [
=> ->user->id,
=> ->(),
]);
}
}
::();
::()->(()->());
::()->();
Events and Listeners
<?php
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,
) {}
}
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 ( ) {
->( (->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');
}
{
= ::()->();
= ->(, )
->(, [
=> ,
=> ,
]);
->()
->(, );
->(, [
=> ,
=> ->id,
]);
}
{
= ->(, [
=> ,
=> ,
]);
->();
}
{
= ::()->();
= ->(, )
->(, [
=> , // Invalid
=> , // Too short
]);
->()
->([, ]);
}
{
= ::()->();
= ::()->([ => ->id]);
= ->(, )
->(, [
=> ,
=> ,
]);
->();
->(, [
=> ->id,
=> ,
]);
}
{
= ::()->();
= ::()->();
= ::()->([ => ->id]);
= ->(, )
->(, [
=> ,
]);
->();
}
}
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->()));
}
{
= ::()->([ => ]);
= ::()->([ => ]);
->(->());
->(->());
}
}
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 {
->(fn ( ) => [
=> ,
=> (),
]);
}
{
->(fn ( ) => [
=> ->id,
]);
}
}
Best Practices
Type Safety
<?php
declare(strict_types=1);
Dependency Injection
<?php
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