アトリビュート、readonly、第一級callable、そしてPHP 8の厳密な型付けの内部の仕組みを探求しましょう。
このレッスンを Kodokon で開くアトリビュートは、docblockのアノテーションを、エンジンによって検証される本物のクラスに置き換えます。アトリビュートは休眠中のメタデータにすぎません。#[Route('/users')]と書いても、オブジェクトは作られません。それが命を得るのは、あるコードがリフレクションを通してそれに問い合わせ、newInstance()を呼んだときだけです。これはまさに、フレームワークが起動時にルーティングテーブルを構築するために行うことです。許可されるターゲット(TARGET_METHOD、TARGET_CLASSなど)自体も、アトリビュートクラスに付けられたアトリビュートによって宣言されます。
<?php
declare(strict_types=1);
#[Attribute(Attribute::TARGET_METHOD)]
final class Route
{
public function __construct(
public readonly string $path,
public readonly string $method = 'GET'
) {}
}
final class UserController
{
#[Route('/users', method: 'GET')]
public function index(): array
{
return ['users' => []];
}
}
$ref = new ReflectionMethod(
UserController::class,
'index'
);
foreach ($ref->getAttributes(Route::class) as $attr) {
$route = $attr->newInstance();
echo $route->method, ' ', $route->path, PHP_EOL;
}readonlyは、8.1ではプロパティに、8.2ではクラス全体に適用でき、プロパティがクラスのスコープの内側から一度だけ書き込まれることを保証します。これは値オブジェクトのための道具です。あらゆる「変更」は新しいオブジェクトの構築になり、共有状態のバグの一族をまるごと排除します。readonlyクラスは、そのすべてのプロパティが型付けされ、非staticであることを要求します。
<?php
declare(strict_types=1);
final readonly class Money
{
public function __construct(
public int $amount,
public string $currency
) {}
public function add(Money $other): self
{
if ($other->currency !== $this->currency) {
throw new InvalidArgumentException(
'Currency mismatch'
);
}
return new self(
$this->amount + $other->amount,
$this->currency
);
}
}
$total = new Money(500, 'EUR');
$total = $total->add(new Money(250, 'EUR'));
echo $total->amount;第一級callable構文strtoupper(...)(PHP 8.1)は、任意の関数やメソッドからClosureを作ります。'strtoupper'のような文字列や[$obj, 'method']のような配列とは違い、シンボルは作成時に検証され、静的解析ツールは型を端から端まで追いかけます。インスタンスメソッドの場合、クロージャはオブジェクトを捕捉し、staticメソッドの場合はクラスだけを捕捉します。
<?php
declare(strict_types=1);
$words = ['php', 'rust', 'go'];
$upper = strtoupper(...);
var_dump($upper('php'));
print_r(array_map(strtoupper(...), $words));
final class Mailer
{
public function send(string $to): bool
{
return $to !== '';
}
public static function ping(): string
{
return 'pong';
}
}
$mailer = new Mailer();
$send = $mailer->send(...);
$ping = Mailer::ping(...);
var_dump($send('dev@example.org'), $ping());