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.
- 1,585