Waaseyaa의 핵심 엔티티 시스템
Source: Dev.to
죄송합니다만, 번역하려는 전체 텍스트를 제공해 주시면 한국어로 번역해 드리겠습니다. 현재는 링크만 제공되어 있어 내용을 확인할 수 없습니다. 텍스트를 복사해서 여기에 붙여 주시면 바로 번역해 드리겠습니다.
개요
시리즈 맥락: 이것은 Waaseyaa 시리즈의 3부입니다. 개요는 시리즈 소개를 읽어보시고, 멀티‑레포 워크플로우가 어떻게 관리되는지는 공동 개발 거버넌스 페이지를 참고하세요.
Drupal의 가장 큰 기여는 UI나 모듈 생태계가 아니라 엔터티/필드 모델입니다. 콘텐츠 타입이 타입이 지정된 필드들의 구성이라는 개념, 어떤 콘텐츠 타입도 어떤 필드든 가질 수 있다는 점, 그리고 필드가 자체 저장 및 검증 로직을 가진다는 것이 Drupal을 거의 모든 콘텐츠 도메인을 모델링할 수 있을 정도로 유연하게 만든다.
Waaseyaa는 이 모델을 상속받아 PHP 8.4+와 현대적인 타입 선언, Symfony의 의존성 주입으로 재작성했습니다. 이 포스트에서는 엔터티 시스템이 어떻게 작동하는지와 구조화된 AI 컨텍스트가 어떻게 여러 세션에 걸쳐 건축적 일관성을 잃지 않고 구축 가능하게 했는지 다룹니다.
EntityInterface
프레임워크의 모든 엔터티는 EntityInterface를 구현합니다. 계약은 최소한으로 정의됩니다:
interface EntityInterface
{
public function id(): int|string|null;
public function uuid(): string;
public function label(): string;
public function getEntityTypeId(): string;
public function bundle(): string;
public function isNew(): bool;
public function toArray(): array;
public function language(): string;
}이 계약에 대해 프레임워크 나머지 부분이 동작하는 데 중요한 몇 가지 사항:
id()는int|string|null을 반환합니다 — 새 엔터티는 아직 저장되지 않아 ID가 없으므로null이 가능합니다.isNew()는 이를 사용합니다: 엔터티의 ID가null일 때(또는enforceIsNew()를 통해 명시적으로 강제할 때) 엔터티는 새 것으로 간주됩니다.bundle()은 엔터티 하위 유형을 가능하게 합니다. 예를 들어node엔터티 타입은article이나page와 같이 서로 다른 필드 정의를 가진 번들을 가질 수 있습니다. 번들 키가 설정되지 않으면 번들은 엔터티 타입 ID 자체를 기본값으로 사용합니다.toArray()는 API 레이어의ResourceSerializer가 사용하는 직렬화 계약입니다. 이 메서드가 반환하는 형태가 JSON:API 응답을 구성하는 기반이 됩니다. API 레이어에서 작업하는 세션은 엔터티 시스템 스킬과 API 레이어 스킬을 모두 로드하여 이 두 계약이 어떻게 상호 작용하는지 이해합니다.
ContentEntityBase
ContentEntityBase는 대부분의 EntityInterface에 대한 기본 구현을 제공하는 추상 기본 클래스입니다. EntityBase를 상속하고 EntityInterface와 FieldableInterface를 결합한 ContentEntityInterface를 구현합니다. 사용자 정의 엔티티 타입은 이를 상속하며, 직접 EntityInterface를 상속하지 않습니다.
abstract class ContentEntityBase extends EntityBase implements ContentEntityInterface
{
protected array $fieldDefinitions = [];
public function __construct(
array $values = [],
string $entityTypeId = '',
array $entityKeys = [],
array $fieldDefinitions = [],
) {
parent::__construct($values, $entityTypeId, $entityKeys);
$this->fieldDefinitions = $fieldDefinitions;
}
public function hasField(string $name): bool
{
return array_key_exists($name, $this->values)
|| array_key_exists($name, $this->fieldDefinitions);
}
public function get(string $name): mixed
{
return $this->values[$name] ?? null;
}
public function set(string $name, mixed $value): static
{
$this->values[$name] = $value;
return $this;
}
}- 엔티티 값은
$values배열에 저장되며, 필드 접근은 전용 필드 객체가 아니라get()및set()을 통해 이루어집니다. hasField()는 값 배열과 필드 정의 모두를 확인합니다 — 필드가 아직 값이 없어도 정의될 수 있습니다. 이는 엔티티를 가볍게 유지합니다: v0.1.0에서는 필드 값이 배열의 원시 값으로 저장되며, 전체FieldItemList통합은waaseyaa/field패키지에서 계획되어 있습니다.
필드 시스템
필드 시스템은 waaseyaa/field 패키지에 존재합니다. 각 필드 타입은 FieldItemInterface를 구현합니다:
interface FieldItemInterface extends ComplexDataInterface
{
public function isEmpty(): bool;
public function getFieldDefinition(): FieldDefinitionInterface;
/** @return string[] */
public static function propertyDefinitions(): array;
public static function mainPropertyName(): string;
}- 필드 아이템은 자체 속성 구조를 알고 있는 타입화된 데이터 객체입니다.
propertyDefinitions()는 필드 타입이 보유하는 속성을 선언합니다.mainPropertyName()은 기본 속성을 식별합니다 (보통'value').
각 필드 타입은 저장 및 API 직렬화를 위해 schema()와 jsonSchema() 메서드도 제공합니다.
프레임워크는 일반적인 경우에 사용할 수 있는 필드 아이템 타입들을 제공합니다:
StringItemTextItemIntegerItemBooleanItemFloatItemEntityReferenceItem
각각은 ID, 라벨, 설명, 기본 카디널리티를 선언하는 #[FieldType] 어트리뷰트로 주석 처리되어 있습니다.
예시: Skill 엔티티 (Claudriel)
Claudriel의 Skill 엔티티는 엔티티 타입이 실제로 어떻게 작동하는지를 보여줍니다:
final class Skill extends ContentEntityBase
{
protected string $entityTypeId = 'skill';
protected array $entityKeys = [
'id' => 'sid',
'uuid' => 'uuid',
'label' => 'name',
];
public function __construct(array $values = [])
{
parent::__construct($values, 'skill', $this->entityKeys);
}
}이는 실제로 thin‑application 패턴입니다. Claudriel은 엔티티 키를 사용해 엔티티 타입을 정의합니다 — 이 매핑은 프레임워크에 어떤 value‑array 키가 엔티티의 ID, UUID, 라벨에 해당하는지를 알려줍니다. 프레임워크는 기본 클래스, UUID 처리 및 필드 접근 헬퍼를 제공하고, 애플리케이션은 엔티티를 기능하게 만들기 위해 최소한의 메타데이터만 제공하면 됩니다.
자동 생성, 직렬화 및 API 엔드포인트
애플리케이션 수준의 변경(새 엔터티 타입을 추가하거나 키를 조정하는 것)은 애플리케이션 레이어에 머무릅니다.
엔터티 팩토리
테스트를 위해 waaseyaa/testing 패키지는 EntityFactory를 제공합니다 — 엔터티 값 배열을 생성하는 테스트 데이터 생성기입니다:
$factory = new EntityFactory();
$factory->define('skill', [
'name' => 'Default skill',
'status' => 1,
]);
$values = $factory->create('skill', ['name' => 'Custom']);
// => ['name' => 'Custom', 'status' => 1]팩토리의 create(string $entityTypeId, array $overrides = []): array 메서드는 등록된 기본값과 테스트별 오버라이드를 병합합니다. 또한 다음을 지원합니다:
sequence()콜백을 사용하여 여러 엔터티에 걸쳐 고유한 값을 생성합니다.createMany()를 사용한 배치 생성.
Note: 이것은 테스트 유틸리티이며, 프로덕션 서비스가 아닙니다 — 애플리케이션 코드에서 엔터티 타입은
new Skill($values)와 같이 직접 생성됩니다.
전문 스킬의 역할
엔터티 시스템 스킬은 위의 지식(인터페이스 계약, 값 배열 패턴, 엔터티 키 매핑, 일반적인 실수)을 담고 있습니다.
스킬이 존재하기 전, 엔터티 시스템 작업 세션에서는 가끔 다음과 같은 코드를 생성하곤 했습니다:
- 잘못된 메서드 이름을 사용함.
- 존재하지 않는 클래스를 만들어 냄.
get()와set()을 통한 필드 접근 방식을 오해함.
스킬이 적용되면서 이러한 실수는 사라졌습니다. packages/entity/를 다루는 모든 세션 시작 시 스킬을 로드하고, 인터페이스 계약 및 동작 규칙을 갖추어 시스템에 맞는 코드를 생성합니다.
초기 엔터티 시스템 구축을 주도한 GitHub 이슈는 작업 범위를 정확히 정의했습니다:
EntityInterfaceEntityBaseContentEntityBaseFieldableInterface- 핵심 필드 아이템 타입 6개
- 테스트 팩토리
그 범위 밖의 작업은 없습니다. 세션이 검증 규칙이나 스토리지 어댑터 추가와 같은(후속 마일스톤에 해당하는) 작업으로 흐를 때, 이슈 범위가 이를 되돌렸습니다.
이 조합—이슈 범위와 코드화된 컨텍스트—이 복잡한 프레임워크 개발을 수십 개의 세션에 걸쳐 관리 가능하게 합니다.
Next: 시간에 대해 AI가 거짓말을 하지 않도록 하는 시간 레이어 구축
Baamaapii