استكشف الآلية الداخلية للسمات وreadonly والقابلات للاستدعاء من الدرجة الأولى والأنواع الصارمة في PHP 8.
افتح هذا الدرس في Kodokonتستبدل السمات (attributes) تعليقات docblock بأصناف حقيقية يتحقق منها المحرّك. فالسمة ليست سوى بيانات وصفية خامدة: إذ لا تُنشئ كتابة #[Route('/users')] أيّ كائن. ولا تدبّ فيها الحياة إلا حين يستعلمها كود ما عبر الانعكاس (reflection) ويستدعي 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 أن تكون جميع خصائصه ذات أنواع محدّدة وغير ساكنة.
<?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;تُنشئ صياغة القابل للاستدعاء من الدرجة الأولى strtoupper(...) (PHP 8.1) كائن Closure من أيّ دالة أو دالة صنف. وخلافًا للسلاسل مثل 'strtoupper' أو المصفوفات مثل [$obj, 'method']، يُتحقَّق من الرمز عند الإنشاء، وتتابع أدوات التحليل الساكن الأنواعَ من البداية إلى النهاية. وبالنسبة لدالة نسخة، يلتقط الإغلاق (closure) الكائنَ؛ أما بالنسبة لدالة ساكنة، فيلتقط الصنف فقط.
<?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());