एंटिटी मैपिंग
Weaver ORM डोमेन ऑब्जेक्ट्स को persistence मेटाडेटा से अलग करता है, सभी मैपिंग जानकारी को एक समर्पित मैपर क्लास में रखकर। यह पृष्ठ मैपर कॉन्फ़िगरेशन के हर पहलू को कवर करता है।
एट्रिब्यूट्स के बजाय मैपर्स क्यों?
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 निर्भरताएं शून्य
मैपर क्लास → सभी persistence ज्ञान यहाँ रहता है
लाभ:
- शून्य रनटा इम रिफ्लेक्शन। मैपर सामान्य PHP है जो एरे और स्केलर लौटाता है।
- कोई प्रॉक्सी क्लास नहीं। कोई ऑन-डिस्क कोड जनरेशन आवश्यक नहीं।
- वर्कर-सुरक्षित। मैपर्स प्रति-अनुरोध कोई स्थिति नहीं रखते।
- अलगाव में टेस्टयोग्य। Symfony बूट किए बिना यूनिट टेस्ट में मैपर को इंस्टेंशिएट और इंस्पेक्ट करें।
- पूरी तरह से grep-योग्य। हर कॉलम नाम, हर टाइप, हर विकल्प सामान्य टेक्स्ट में दिखाई देता है और
git diffमें दिखता है।
मैपर बनाम एंटिटी: जिम्मेदारियाँ
| चिंता | में रहता है |
|---|---|
| बिज़नेस लॉजिक, invariants | एंटिटी क्लास |
| प्रॉपर्टीज़ और PHP टाइप्स | एंटिटी क्लास |
| टेबल नाम और स्कीमा | मैपर |
| कॉलम नाम, टाइप्स, विकल्प | मैपर |
| इंडेक्स और constraints | मैपर |
| हाइड्रेशन (row → entity) | मैपर |
| निष्कर्षण (entity → row) | मैपर |
| रिलेशन्स | मैपर |
बुनियादी एंटिटी परिभाषा
एंटिटी कोई भी 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,
);
}
}
एंटिटी हो सकती है:
- Immutable (अनुशंसित) — म्यूटेटिंग मेथड नए इंस्टेंस लौटाते हैं
- Mutable — public प्रॉपर्टीज़ या setters ठीक हैं
- Abstract — इनहेरिटेंस hierarchies के लिए
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 | raw डेटाबेस row से एंटिटी बनाएं |
dehydrate(object $entity): array | एंटिटी को column => value एरे में serialize करें |
वैकल्पिक मैपर मेथड
| मेथड | उद्देश्य |
|---|---|
readOnly(): bool | view-backed एंटिटीज़ क े लिए true लौटाएं (कोई INSERT/UPDATE/DELETE नहीं) |
discriminatorColumn(): ?string | Single Table Inheritance के लिए उपयोग |
discriminatorMap(): array | Single Table Inheritance के लिए उपयोग |
parentMapper(): ?string | Class Table Inheritance के लिए उपयोग |
कॉलम टाइप्स
सभी कॉलम परिभाषाएं ColumnDefinition पर static factory मेथड का उपयोग करती हैं। प्रत्येक मेथड एक fluent configuration API के साथ 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 को string के रूप में hydrate करें:
price: $row['price'], // string के रूप में रखें, Money value object को पास करें
boolean
MySQL पर TINYINT(1), PostgreSQL/SQLite पर BOOLEAN पर मैप होता है।
ColumnDefinition::boolean('is_active')->default(true)
ColumnDefinition::boolean('email_verified')->default(false)
hydrate में हमेशा स्पष्ट रूप से cast करें:
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 एक mutable \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 में encoding/decoding नियंत्रित करते हैं।
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 as CHAR(36))
ColumnDefinition::guid('external_ref')->nullable() // CHAR(36) NULL
प्राइमरी की टाइप्स
Auto-increment integer
ColumnDefinition::integer('id')->autoIncrement()->unsigned()
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id)
Weaver INSERT से id को छोड़ देता है जब मूल्य null होता है और जनरेट मूल्य को स्वचालित रूप से पढ़ता है।
UUID v4 (यादृच्छिक)
ColumnDefinition::guid('id')->primaryKey()
persist करने से पहले एंटिटी factory मेथड में UUID जनरेट करें:
use Symfony\Component\Uid\Uuid;
public static function create(string $name): self
{
return new self(id: (string) Uuid::v4(), name: $name);
}