81 lines
3.6 KiB
Markdown
81 lines
3.6 KiB
Markdown
|
|
# ORM (MongoDB)
|
|||
|
|
|
|||
|
|
## Атрибуты
|
|||
|
|
|
|||
|
|
| Атрибут | Применяется к | Описание |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `#[Collection(name: 'users')]` | класс entity | Имя коллекции MongoDB |
|
|||
|
|
| `#[Id]` | свойство | Кастомное поле `id` (строка), НЕ MongoDB `_id` |
|
|||
|
|
| `#[Field]` | свойство | Обычное поле; имя в MongoDB = имя свойства |
|
|||
|
|
| `#[Field(name: 'x')]` | свойство | Поле с другим именем в MongoDB |
|
|||
|
|
| `#[Embedded]` | свойство | Вложенный объект (не Entity, просто класс) |
|
|||
|
|
| `#[EmbeddedList]` | свойство | Массив вложенных объектов |
|
|||
|
|
| `#[ForEntity(Foo::class)]` | класс репозитория | Связывает репозиторий с entity |
|
|||
|
|
|
|||
|
|
## Объявление Entity
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
#[Collection(name: 'users')]
|
|||
|
|
final class User implements MongoEntity
|
|||
|
|
{
|
|||
|
|
public function __construct(
|
|||
|
|
#[Id] public readonly string $id,
|
|||
|
|
#[Field] public readonly string $email,
|
|||
|
|
#[Field] public readonly ?string $name = null,
|
|||
|
|
// массив string[] — BSONArray конвертируется автоматически
|
|||
|
|
#[Field] public readonly array $roles = [],
|
|||
|
|
) {}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- `#[Id]` хранится в документе как поле `id` (не `_id`)
|
|||
|
|
- MongoDB `_id` остаётся нативным ObjectId, пинекор его игнорирует
|
|||
|
|
- `array` поля (в т.ч. `string[]`) — `BSONArray` конвертируется в PHP array прозрачно
|
|||
|
|
- Если `$id === ''` при `persist()`, генерируется новый ID через `IdGenerator`
|
|||
|
|
|
|||
|
|
## Объявление репозитория
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
#[ForEntity(User::class)]
|
|||
|
|
final class UserRepository extends AbstractMongoRepository
|
|||
|
|
{
|
|||
|
|
// Публичный типизированный метод — не override protect persist()
|
|||
|
|
public function save(User $user): User
|
|||
|
|
{
|
|||
|
|
return $this->persist($user);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function findByEmail(string $email): ?User
|
|||
|
|
{
|
|||
|
|
return $this->findOneWhere(['email' => $email]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## AbstractMongoRepository (`src/Orm/AbstractMongoRepository.php`)
|
|||
|
|
|
|||
|
|
Инжектируется через DI: `Database`, `MongoHydrator`, `IdGenerator`.
|
|||
|
|
|
|||
|
|
| Метод | Описание |
|
|||
|
|
|---|---|
|
|||
|
|
| `persist(object $entity): object` | upsert по полю `id`; если `id === ''`, генерирует новый |
|
|||
|
|
| `findById(string $id): ?object` | поиск по полю `id` |
|
|||
|
|
| `findOneWhere(array $filter): ?object` | произвольный MongoDB filter |
|
|||
|
|
| `delete(string $id): void` | deleteOne по полю `id` |
|
|||
|
|
| `collection(string $entityClass): Collection` | MongoDB\Collection для entity |
|
|||
|
|
| `entityClass(): string` | читает `#[ForEntity]`, кешируется статически |
|
|||
|
|
|
|||
|
|
## MongoHydrator (`src/Orm/MongoHydrator.php`)
|
|||
|
|
|
|||
|
|
- `hydrate(string $class, array|BSONDocument $doc): object` — BSONDocument/array → typed entity через named constructor args
|
|||
|
|
- `dehydrate(object $entity): array` — entity → array для MongoDB
|
|||
|
|
- Рекурсивно обрабатывает `#[Embedded]` и `#[EmbeddedList]`
|
|||
|
|
- Embedded при dehydrate: сериализует все свойства ReflectionClass (без атрибутов)
|
|||
|
|
|
|||
|
|
## EntityMap (`src/Orm/EntityMap.php`)
|
|||
|
|
|
|||
|
|
Кеширует метаданные entity (reflection). `EntityMap::of(ClassName::class)` возвращает объект с:
|
|||
|
|
- `collectionName` — из `#[Collection]`
|
|||
|
|
- `fields` — список `FieldMetadata` (property, field name, флаги embedded)
|
|||
|
|
- `idField()` — `FieldMetadata` поля с `#[Id]`
|