Unconventional Autoloaders

What are Autoloaders?

<?php

$user = new User();
// Uncaught Error: Class 'User' not found
<?php

spl_autoload_register(function ($className) {
    require_once __DIR__ . $className . '.php';
});

$user = new User();
// require_once __DIR__ . 'User.php';

PSR-0

PSR-4

<?php

spl_autoload_register(function ($class) {
    $prefix = 'App\\';

    $base_dir = __DIR__ . '/app/';

    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }

    $relative_class = substr($class, $len);

    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';

    if (file_exists($file)) {
        require $file;
    }
});

https://www.php-fig.org/psr/psr-4/examples/

Constructors

Static

1

<?php

class Countries
{
    public static function getValues()
    {
        return json_decode(
            file_get_contents(__DIR__ . '/countries.json'),
            true
        );
    }
}

Countries::getValues();
<?php

class Countries
{
    protected static array $values;

    public static function getValues()
    {
        if (!isset($static::$values)) {
             static::$values = json_decode(
                 file_get_contents(__DIR__ . '/countries.json'),
                 true
             );
        }

        return static::$values;
    }
}

Countries::getValues();

A static constructor...

takes no parameters

should only be called once

is executed BEFORE any instances of the class are created

new Example();

is executed BEFORE any static properties are referenced

Example::$value;

is executed BEFORE any other static methods are called

Example::test();

<?php

class Countries
{
    public static array $values = [];

    public static function __constructStatic()
    {
        static::$values = json_decode(
            file_get_contents(__DIR__ . '/countries.json'),
            true
        );
    }
}
<?php

class Countries
{
    public static array $values = [];

    public static function __constructStatic()
    {
        static::$values = json_decode(file_get_contents(__DIR__ . '/countries.json'));
    }
}

Countries::__constructStatic();

Countries.php

<?php

namespace App\Http\Controllers;

use App\Models\User;

class UserIndexController
{
    public function __invoke()
    {
        return view('users.index', [
            'users' => User::all(),
        ]);
    }
}

UserIndexController.php

<?php

Route::get('/', HomepageController::class);

Route::get('/users', UserIndexController::class);

Route::get('/login', Auth\LoginController::class);

Route::post('/logout', Auth\LogoutController::class);

routes/web.php

<?php

use App\Models\User;

class UserIndexController
{
    public static function __constructStatic()
    {
        Route::get('/users', UserIndexController::class);
    }

    public function __invoke()
    {
        return view('users.index', [
            'users' => User::all(),
        ]);
    }
}

UserIndexController::__constructStatic();

UserIndexController.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class CustomServiceProvider extends ServiceProvider
{
    public function register()
    {
        $directory = new \RecursiveDirectoryIterator(
            base_path('app/Http/Controllers')
        );

        $iterator = new \RecursiveIteratorIterator($directory);

        $files = new \RegexIterator(
            $iterator,
            '/^.+\.php$/',
            \RecursiveRegexIterator::GET_MATCH
        );

        foreach ($files as $file) {
            require_once $file;
        }
    }
}
<?php

use App\Models\User;

class UserIndexController
{
    public static function __constructStatic()
    {
        Route::get('/users', UserIndexController::class);
    }

    public function __invoke()
    {
        return view('users.index', [
            'users' => User::all(),
        ]);
    }
}

UserIndexController::__constructStatic();

UserIndexController.php

<?php

require_once __DIR__ . '/vendor/autoload.php';

spl_autoload_register(function ($className) {
    if (class_exists($className, autoload: false)) {
        if (method_exists($className, '__constructStatic')) {
            $className::__constructStatic();
        }
    }
}, prepend: false);
<?php

/** @var Composer\Autoload\ClassLoader $autoloader */
$autoloader = require_once __DIR__ . '/vendor/autoload.php';

$autoloader->unregister();

spl_autoload_register(function ($className) use ($autoloader) {
    $result = $autoloader->loadClass($className);

    if ($result === true) {
        // ...

        return true;
    }

    return null;
}, throw: true, prepend: true);
<?php

/** @var Composer\Autoload\ClassLoader $autoloader */
$autoloader = require_once __DIR__ . '/vendor/autoload.php';

$autoloader->unregister();

spl_autoload_register(function ($className) use ($autoloader) {
    $result = $autoloader->loadClass($className);

    if ($result === true) {
        if (class_exists($className, autoload: false)) {
            if (method_exists($className, '__constructStatic')) {
                $className::__constructStatic();
            }
        }

        return true;
    }

    return null;
}, throw: true, prepend: true);
<?php

use App\Models\User;

class UserIndexController
{
    public static function __constructStatic()
    {
        Route::get('/users', UserIndexController::class);
    }

    public function __invoke()
    {
        return view('users.index', [
            'users' => User::all(),
        ]);
    }
}

- UserIndexController::__constructStatic();

UserIndexController.php

Class Attributes

2

<?php

class UserIndexController
{
    public function __invoke()
    {
        // ...
    }
}
<?php

#[Route('get', '/users')]
class UserIndexController
{
    public function __invoke()
    {
        // ...
    }
}
<?php

#[Attribute(Attribute::TARGET_CLASS)]
class Route
{
    protected string $method;
    protected string $route;

    public function __construct(string $method, string $route) {
        $this->method = $method;
        $this->route = $route;
    }
}
<?php

/** @var Composer\Autoload\ClassLoader $autoloader */
$autoloader = require_once __DIR__ . '/vendor/autoload.php';

$autoloader->unregister();

spl_autoload_register(function ($className) use ($autoloader) {
    $result = $autoloader->loadClass($className);

    if ($result === true) {
        $reflectionClass = new ReflectionClass($className);
        $attributes = $r->getAttributes();

        foreach ($attributes as $attribute) {
            if ($attribute->getName() === Route::class) {
                $route = $attribute->newInstance();
            }
        }

        return true;
    }

    return null;
}, throw: true, prepend: true);
<?php

#[Attribute(Attribute::TARGET_CLASS)]
class Route
{
    protected string $method;
    protected string $route;

    public function __construct(string $method, string $route) {
        $this->method = $method;
        $this->route = $route;
    }
    
    public function register(string $controller)
    {
        \Illuminate\Support\Facades\Route::{$this->method}($this->route, $controller);
    }
}
<?php

/** @var Composer\Autoload\ClassLoader $autoloader */
$autoloader = require_once __DIR__ . '/vendor/autoload.php';

$autoloader->unregister();

spl_autoload_register(function ($className) use ($autoloader) {
    $result = $autoloader->loadClass($className);

    if ($result === true) {
        $reflectionClass = new ReflectionClass($className);
        $attributes = $r->getAttributes();

        foreach ($attributes as $attribute) {
            if ($attribute->getName() === Route::class) {
                $route = $attribute->newInstance();

                $route->register($className);
            }
        }

        return true;
    }

    return null;
}, throw: true, prepend: true);
<?php

#[Route('get', '/users')]
class UserIndexController
{
    public function __invoke()
    {
        // ...
    }
}

Realtime Classes

3

<?php

class Email
{
    protected string $to;
    protected string $subject = '(No subject)';
    protected string $body = '';

    public function to(string $to)
    {
        $this->to = $to;
        return $this;
    }

    public function subject(string $subject)
    {
        $this->subject = $subject;
        return $this;
    }

    public function body(string $body)
    {
        $this->body = $body;
        return $this;
    }

    public function send()
    {
        mail($this->to, $this->subject, $this->body);
    }
}
<?php

$email = new Email();

$email->to('freek@spatie.be')
    ->body('Hey Freek!')
    ->subject('Thanks for having me at the Laravel Worldwide Meetup!')
    ->send();
<?php

class EmailFacade
{
    public static function __callStatic($name, $arguments)
    {
        $email = new Email();
        
        return $email->{$name}(...$arguments);
    }
}








EmailFacade::body('Hey Freek!')
    ->to('freek@spatie.be')
    ->subject('Thanks for having me at the Laravel Worldwide Meetup!')
    ->send();
<?php

spl_autoload_register(
    autoload_function: function($className) {
        // ...
    },
    throw: true,
    prepend: true
);
<?php

spl_autoload_register(
    autoload_function: function($className) {
        if (str_starts_with($className, 'Facades\\')) {
            $path = __DIR__ . '/Facades/' . sha1($className) . '.php';
            
            // ...
        }
    },
    throw: true,
    prepend: true
);
<?php

spl_autoload_register(
    autoload_function: function($className) {
        if (str_starts_with($className, 'Facades\\')) {
            $path = __DIR__ . '/Facades/' . sha1($className) . '.php';
            
            if (file_exists($path)) {
                require_once $path;
                return true;
            }
            
            // ...
        }
    },
    throw: true,
    prepend: true
);
<?php

spl_autoload_register(
    autoload_function: function($className) {
        if (str_starts_with($className, 'Facades\\')) {
            $path = __DIR__ . '/Facades/' . sha1($className) . '.php';
            
            if (file_exists($path)) {
                require_once $path;
                return true;
            }
            
            if (!is_dir(__DIR__ . '/Facades')) {
                mkdir(__DIR__ . '/Facades');
            }

            touch($path);
            
            // ...
        }
    },
    throw: true,
    prepend: true
);
<?php

spl_autoload_register(
    autoload_function: function($className) {
        if (str_starts_with($className, 'Facades\\')) {
            $path = __DIR__ . '/Facades/' . sha1($className) . '.php';
            
            if (file_exists($path)) {
                require_once $path;
                return true;
            }
            
            if (!is_dir(__DIR__ . '/Facades')) {
                mkdir(__DIR__ . '/Facades');
            }

            touch($path);
            
            $classBasename = class_basename($className);
            $originalNamespace = Str::between($className, 'Facades\\', '\\' . $classBasename);
            
            // ...
        }
    },
    throw: true,
    prepend: true
);
<?php

spl_autoload_register(
    autoload_function: function($className) {
        if (str_starts_with($className, 'Facades\\')) {
            $path = __DIR__ . '/Facades/' . sha1($className) . '.php';
            
            if (file_exists($path)) {
                require_once $path;
                return true;
            }
            
            if (!is_dir(__DIR__ . '/Facades')) {
                mkdir(__DIR__ . '/Facades');
            }

            touch($path);
            
            $classBasename = class_basename($className);
            $originalNamespace = Str::between($className, 'Facades\\', '\\' . $classBasename);
            
            file_put_contents($path, <<<EOL
            <?php

            namespace Facades\\{$originalNamespace};

            class {$realClassName}
            {
                public static function __callStatic(\$name, \$arguments)
                {
                    \$class = new \\{$originalNamespace}\\{$classBasename};

                    return \$class->{\$name}(...\$arguments);
                }
            }
            EOL);

            require_once $path;
            return true;
        }
    },
    throw: true,
    prepend: true
);
<?php

spl_autoload_register(
    autoload_function: function($className) {
        if (str_starts_with($className, 'Facades\\')) {
            $path = __DIR__ . '/Facades/' . sha1($className) . '.php';
            
            if (file_exists($path)) {
                require_once $path;
                return true;
            }
            
            if (!is_dir(__DIR__ . '/Facades')) {
                mkdir(__DIR__ . '/Facades');
            }

            touch($path);
            
            $classBasename = class_basename($className);
            $originalNamespace = Str::between($className, 'Facades\\', '\\' . $classBasename);
            
            file_put_contents($path, <<<EOL
            <?php

            namespace Facades\\{$originalNamespace};

            class {$realClassName}
            {
                public static function __callStatic(\$name, \$arguments)
                {
                    \$class = new \\{$originalNamespace}\\{$classBasename};

                    return \$class->{\$name}(...\$arguments);
                }
            }
            EOL);

            require_once $path;
            return true;
        }
    },
    throw: true,
    prepend: true
);
<?php

require_once __DIR__ . '/realtime_facade_autoload.php';

Facades\Email::to('freek@spatie.be')
    ->body('Hey Freek!')
    ->subject('Thanks for having me at the Laravel Worldwide Meetup!')
    ->send();
src/
├── Email.php
├── index.php
├── realtime_facade_autoload.php
├── Facades/
│ ├── 84add5b2952787581cb9a8851eef63d1ec75d22b.php

Illuminate\Foundation\AliasLoader

Pre-processing

4

class Fixture
{
    private $name = ucwords("acme fixture");
    private $thing = new \stdClass();
}
function example($one, $two = 2.2, $three = round(3.3), $four = new stdClass) { ... }
async function get_file(string $path)
{
    await \Amp\File\get($path);
}
$users = { $user1, $user2, $user3 };

$admins = $users->filter(fn($user) => $user->isAdmin());
class Collection<T> {
    protected $items = [];

    public function __construct($items = [])
    {
        $this->items = $items;
    }
    
    public function add(T $item)
    {
        $this->items[] = $item;
    }
}

$users = new Collection<User>;
$users->add($user1); // ✅
$users->add('Foo'); // ⛔︎
$file = fopen('file.txt');
defer fclose($file);

// Do stuff with $file ...
// The file will automatically be closed at the end
function MyForm($props) {
  return (
    <form>
      {$props->showLabel ? <label htmlFor={"email"}>Email</label> : null}
      <input type={"text"} name={"email"} id={"email"} />
    </form>
  );
}

@LiamHammett

liamhammett.com

slides.com/liamhammett/unconventional-autoloaders

Unconventional Autoloaders

By Liam Hammett

Unconventional Autoloaders

We use autoloaders in PHP all the time, but if you change your mindset a little, you’ll find they can do a lot more than you might’ve thought. We’ll be taking a look at using some unconventional techniques, we can use autoloaders to take PHP a step further, introducing interesting and exciting new functionality PHP natively doesn’t support.

  • 254
Loading comments...

More from Liam Hammett