تعيين الكيانات
يفصل Weaver ORM كائنات النطاق عن بيانات وصف الاستمرار بوضع جميع معلومات التعيين في صف مُعيِّن مخصص. تغطي هذه الصفحة كل جانب من جوانب إعداد المُعيِّن.
لماذا المُعيِّنات بدلاً من الخصائص؟
يضع Doctrine ORM بيانات وصف التعيين مباشرةً على صف الكيان عبر خصائص PHP 8:
// نهج Doctrine — الكيان يعرف عن قاعدة البيانات
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;
}
يُبقيها Weaver مفصولة بصرامة:
صف الكيان → كائن PHP عادي، بدون اعتماديات ORM
صف المُعيِّن → كل معرفة الاستمرار تعيش هنا
الفوائد:
- لا انعكاس في وقت التشغيل. المُعيِّن هو PHP عادي يعيد مصفوفات وقيماً عددية.
- لا صفوف وكيل. لا حاجة لتوليد الكود على القرص.
- آمن للعمال. المُعيِّنات لا تحمل حالة لكل طلب.
- قابل للاختبار منفصلاً. أنشئ مُعيِّناً وافحصه في اختبار وحدة دون تشغيل Symfony.
- قابل للبحث بالكامل. كل اسم عمود، كل نوع، كل خيار يظهر في نص عادي ويظهر في
git diff.
المُعيِّن مقابل الكيان: المسؤوليات
| الاهتمام | يعيش في |
|---|---|
| منطق الأعمال، والثوابت | صف الكيان |
| الخصائص وأنواع PHP | صف الكيان |
| اسم الجدول والمخطط | المُعيِّن |
| أسماء الأعمدة والأنواع والخيارات | المُعيِّن |
| الفهارس والقيود | المُعيِّن |
| الترطيب (صف → كيان) | المُعيِّن |
| الاستخراج (كيان → صف) | المُعيِّن |
| العلاقات | المُعيِّن |
تعريف الكيان الأساسي
الكيان هو أي صف PHP. لا يمتد من أي شيء، ولا يُنفّذ أي شيء، ولا يستورد أي شيء من Weaver\ORM.
<?php
// src/Entity/User.php
declare(strict_types=1);
namespace App\Entity;
use DateTimeImmutable;
final class User
{
public function __construct(
public readonly ?int $id,
public readonly string $name,
public readonly string $email,
public readonly bool $isActive,
public readonly DateTimeImmutable $createdAt,
) {}
public function withEmail(string $email): self
{
return new self(
id: $this->id,
name: $this->name,
email: $email,
isActive: $this->isActive,
createdAt: $this->createdAt,
);
}
}
يمكن للكيان أن يكون:
- ثابتاً (موصى به) — طرق التحويل تعيد نسخاً جديدة
- قابلاً للتغيير — الخصائص العامة أو الضوابط مقبولة
- مجرداً — لهيكليات الوراثة
AbstractMapper
كل كيان يحتاج إلى مُعيِّن واحد بالضبط. أنشئ صفاً يمتد من Weaver\ORM\Mapping\AbstractMapper ونفّذ الطرق المطلوبة.
<?php
// src/Mapper/UserMapper.php
declare(strict_types=1);
namespace App\Mapper;
use App\Entity\User;
use DateTimeImmutable;
use Weaver\ORM\Mapping\AbstractMapper;
use Weaver\ORM\Mapping\ColumnDefinition;
use Weaver\ORM\Mapping\SchemaDefinition;
final class UserMapper extends AbstractMapper
{
public function table(): string
{
return 'users';
}
public function primaryKey(): string|array
{
return 'id';
}
public function schema(): SchemaDefinition
{
return SchemaDefinition::define(
ColumnDefinition::integer('id')->autoIncrement()->unsigned(),
ColumnDefinition::string('name', 120)->notNull(),
ColumnDefinition::string('email', 254)->unique()->notNull(),
ColumnDefinition::boolean('is_active')->notNull()->default(true),
ColumnDefinition::datetime('created_at')->notNull(),
);
}
public function hydrate(array $row): User
{
return new User(
id: (int) $row['id'],
name: $row['name'],
email: $row['email'],
isActive: (bool) $row['is_active'],
createdAt: new DateTimeImmutable($row['created_at']),
);
}
public function dehydrate(object $entity): array
{
/** @var User $entity */
$data = [
'name' => $entity->name,
'email' => $entity->email,
'is_active' => $entity->isActive,
'created_at' => $entity->createdAt->format('Y-m-d H:i:s'),
];
if ($entity->id !== null) {
$data['id'] = $entity->id;
}
return $data;
}
}
طرق المُعيِّن المطلوبة
| الطريقة | الغرض |
|---|---|
table(): string | اسم الجدول في قاعدة البيانات |
primaryKey(): string|array | اسم(أسماء) عمود المفتاح الأساسي |
schema(): SchemaDefinition | جميع تعريفات الأعمدة لـ DDL والهجرات |
hydrate(array $row): object | بناء كيان من صف قاعدة بيانات خام |
dehydrate(object $entity): array | تسلسل كيان إلى مصفوفة عمود => قيمة |
طرق المُعيِّن الاختيارية
| الطريقة | الغرض |
|---|---|
readOnly(): bool | أعِد true للكيانات المدعومة بالعرض (بدون INSERT/UPDATE/DELETE) |
discriminatorColumn(): ?string | يُستخدم لوراثة الجدول المفرد (Single Table Inheritance) |
discriminatorMap(): array | يُستخدم لوراثة الجدول المفرد |
parentMapper(): ?string | يُستخدم لوراثة جدول الصف (Class Table Inheritance) |
أنواع الأعمدة
تستخدم جميع تعريفات الأعمدة طرق المصنع الثابتة على ColumnDefinition. كل طريقة تعيد مثيل ColumnDefinition مع واجهة برمجة إعداد سلسة.
string
يُعيَّن إلى VARCHAR(n). الطول الافتراضي هو 255.
ColumnDefinition::string('username') // VARCHAR(255) NOT NULL
ColumnDefinition::string('slug', 100) // VARCHAR(100) NOT NULL
ColumnDefinition::string('nickname')->nullable() // VARCHAR(255) NULL
integer، bigint، smallint
ColumnDefinition::integer('sort_order') // INT NOT NULL
ColumnDefinition::integer('quantity')->default(0) // INT NOT NULL DEFAULT 0
ColumnDefinition::integer('stock')->unsigned() // INT UNSIGNED NOT NULL
ColumnDefinition::bigint('view_count')->default(0) // BIGINT NOT NULL DEFAULT 0
ColumnDefinition::smallint('priority')->unsigned() // SMALLINT UNSIGNED NOT NULL
float و decimal
استخدم decimal للقيم المالية؛ وfloat للإحداثيات والقياسات.
ColumnDefinition::float('latitude')
ColumnDefinition::float('longitude')
ColumnDefinition::decimal('price', 10, 2) // DECIMAL(10,2) NOT NULL
ColumnDefinition::decimal('tax_rate', 5, 4)->default('0.0000')
رطّب decimal كسلسلة للحفاظ على الدقة:
price: $row['price'], // احتفظ به كسلسلة، مرره إلى كائن قيمة Money
boolean
يُعيَّن إلى TINYINT(1) في MySQL، وBOOLEAN في PostgreSQL/SQLite.
ColumnDefinition::boolean('is_active')->default(true)
ColumnDefinition::boolean('email_verified')->default(false)
اصطبغ دائماً بشكل صريح في hydrate:
isActive: (bool) $row['is_active'],
datetime، date، time
ColumnDefinition::datetime('published_at')->nullable() // DATETIME NULL
ColumnDefinition::date('birth_date')->nullable() // DATE NULL
ColumnDefinition::time('opens_at') // TIME NOT NULL
يعيد datetime كائن \DateTime قابلاً للتغيير. فضّل datetimeImmutable للكود الجديد:
ColumnDefinition::datetimeImmutable('created_at') // DATETIME NOT NULL
ColumnDefinition::datetimeImmutable('updated_at')->nullable()
الترطيب:
createdAt: new \DateTimeImmutable($row['created_at']),
updatedAt: isset($row['updated_at']) ? new \DateTimeImmutable($row['updated_at']) : null,
الاستخراج:
'created_at' => $entity->createdAt->format('Y-m-d H:i:s'),
'updated_at' => $entity->updatedAt?->format('Y-m-d H:i:s'),
json
يُعيَّن إلى JSON (MySQL 5.7.8+، PostgreSQL، SQLite). أنت تتحكم في الترميز/فك الترميز في hydrate / dehydrate.
ColumnDefinition::json('metadata')->nullable()
ColumnDefinition::json('settings')
الترطيب:
metadata: $row['metadata'] !== null
? json_decode($row['metadata'], true, 512, JSON_THROW_ON_ERROR)
: null,
الاستخراج:
'metadata' => $entity->metadata !== null
? json_encode($entity->metadata, JSON_THROW_ON_ERROR)
: null,
text، blob
ColumnDefinition::text('body') // TEXT NOT NULL
ColumnDefinition::text('description')->nullable() // TEXT NULL
ColumnDefinition::blob('thumbnail') // BLOB NOT NULL
guid (UUID كـ CHAR(36))
ColumnDefinition::guid('external_ref')->nullable() // CHAR(36) NULL