Into the Laradrome Metaprogramming in Laravel

Wendell Adriel

About Me

Wendell Adriel

PHP and JavaScript since 2009
Laravel since 2015
Laravel Open Source Team

Laravel's DX is not magic.

It is PHP metaprogramming with taste.

Two-Part Journey

Part 1

Metaprogramming 101

Part 2

Metaprogramming in Laravel

Definition

Code that reasons about code.

It reads shape, names, metadata, or calls, then turns them into behavior.

PHP Gives Us

Magic Methods
Reflection
Attributes
Closure Binding

Magic Methods

Special hooks PHP calls when normal object access needs custom behavior.

__get()
__set()
__call()
__callStatic()
__invoke()
__isset()

Magic Methods

final class Profile
{
    private array $data = [];

    public function __get(string $key): mixed {}

    public function __set(string $key, mixed $value): void {}

    public function __call(string $method, array $arguments): bool {}
}

$profile = new Profile;
$profile->admin = true; // __set

$profile->admin; // __get
$profile->isAdmin(); // __call

Reflection

Runtime APIs for inspecting classes, methods, properties, parameters, and metadata.

ReflectionClass
ReflectionMethod
ReflectionParameter
ReflectionProperty
newInstanceArgs

Reflection

final class WelcomeEmail
{
    public function __construct(
        public string $subject,
        private bool $urgent = false,
    ) {}

    public function send(string $to): void {}
}

$class = new ReflectionClass(WelcomeEmail::class);
$constructor = $class->getConstructor();
$send = $class->getMethod('send');

$constructor->getParameters(); // subject, urgent
$class->newInstanceArgs(['Hello']); // WelcomeEmail

Attributes

Structured metadata attached to code; behavior appears when runtime code reads it.

#[Attribute(Attribute::TARGET_CLASS)]
final class Audit
{
    public function __construct(public string $stream) {}
}

#[Audit('orders')]
final class OrderPlaced {}

$attribute = new ReflectionClass(OrderPlaced::class)
    ->getAttributes(Audit::class)[0]
    ->newInstance();

Closure Binding

Rebind a closure so $this and visibility scope point at another object.

final class Formatter
{
    private string $style = 'loud';
}

$format = function (string $text): string {
    return $this->style === 'loud'
        ? strtoupper($text)
        : $text;
};

$format = $format->bindTo(new Formatter, Formatter::class);

$format('laradrome'); // LARADROME

From PHP primitives to Laravel DX.

Magic methods
Reflection
Attributes
Closure binding
Eloquent
Facades
Macros
and others...
How Laravel turns metaprogramming techniques into clean, expressive APIs and a great developer experience.

Eloquent

A model that feels like a domain object.

$post->title;
$post->author;

Post::published()->latest()->get();

Eloquent Property Read

1. Magic Method

$post->title

2. Dynamic Dispatch

getAttribute()

3. Metadata Maps

$attributes / casts()

4. Runtime Pipeline

transformModelValue()

5. Domain Feel

int | string | Carbon | object

Eloquent Query Execution

Post::published()->latest()->get();

1. Static Magic

Model::__callStatic

2. Attribute Scope

#[Scope] published()

3. Builder Call

static::query()

4. Builder Magic

Builder::__call

5. Chain + Execute

latest()->get()

6. Hydrate

hydrate() → Collection

Facades

Static syntax. Container-backed object.

Cache::remember('stats', 60, fn () => $analytics->summary());

Cache::expects('remember')->once();
It reads like a static call, but Laravel resolves and calls a real object.

Facade Call Flow

1. Static Call

Cache::remember()

2. Magic Method

Facade::__callStatic

3. Accessor

getFacadeAccessor()

4. Container Key

'cache'

5. Root Object

$app['cache']

6. Forward Call

$instance->remember()

Facade Source Shape

class Cache extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'cache';
    }
}
public static function __callStatic($method, $args)
{
    $instance = static::getFacadeRoot();

    return $instance->$method(...$args);
}

Macros

Laravel's runtime plugin slot.

use Illuminate\Support\Collection;
use Illuminate\Support\Str;

Collection::macro('toUpper', function () {
    return $this->map(fn (string $value) => Str::upper($value));
});

collect(['laravel', 'php'])->toUpper();

Macro Dispatch Flow

1. Register

macro($name)

2. Registry

static::$macros

3. Dynamic Call

$collection->toUpper()

4. Magic Method

__call()

5. Closure Binding

bindTo($this)

6. Execute

$macro(...$args)

Laravel 13

Attributes FTW

Attribute Lifecycle

Declare metadata
Reflect class
Instantiate attribute
Mutate behavior

Laravel 13 Attribute Surface

70+

framework attributes

Eloquent models and resources
Container injection and bindings
Queues, jobs, and listeners
Controllers and form requests
Console commands
Testing helpers

Eloquent And Resources

Available here

Table, Scope, ScopedBy, ObservedBy, UsePolicy, UseFactory, UseResource, UseResourceCollection, CollectedBy, Fillable, Hidden, Visible...

use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Attributes\Table;

#[Table('blog_posts', key: 'uuid', keyType: 'string', incrementing: false)]
final class Post extends Model
{
    #[Scope]
    protected function published(Builder $query): void
    {
        $query->whereNotNull('published_at');
    }
}

Container And Injection

Available here

Singleton, Scoped, Bind, Give, Config, Cache, Storage, Database, DB, Auth, CurrentUser, RouteParameter, Tag, Context...

use Illuminate\Container\Attributes\Config;
use Illuminate\Container\Attributes\Storage;

final class ReportExporter
{
    public function __construct(
        #[Storage('s3')] Filesystem $files,
        #[Config('app.timezone')] string $timezone,
    ) {}
}

Metaprogramming Thorns

Invisible behavior
Stringly-typed APIs
Tooling confusion
Hot-path reflection

Good Metaprogramming like in Laravel

Hides ceremony
Preserves intent
Has escape hatches
Caches metadata
Stable runtime
Expressive API

Laravel keeps intent and removes complexity

The magic is a contract

Learn the contract, and the framework becomes simpler

Thank You 🇬🇧

X: @wendell_adriel

Website: wendelladriel.com