PERSISTENCE FEATURE
Relations
Declare parent and child links on the resource itself, then read typed relations from the handler.
Feature Guide
A quick orientation block that answers the essential questions: what this feature does, how it works, why it matters, and the key concepts behind it.
What this does
Relations live on the resource fields themselves. A property marked with #[BelongsTo] or #[HasMany] declares which records are connected and which foreign key wires them together.
How it works
A handler asks a repository for typed resources such as DemoProductResource. The ORM can then resolve parent and child links using the relation metadata already declared on the resource, so the handler reads $product->category or $product->reviews instead of rebuilding joins by hand.
Why it matters
This keeps the graph definition in one place. Reviewers can see the shape of the aggregate directly on the resource class, while handlers stay focused on use-case flow instead of repeating SQL knowledge.
Key concepts
- #[BelongsTo]
- Declares a single parent record and names the foreign key that points to it.
- #[HasMany]
- Declares a child collection that references the current resource through a foreign key.
- foreignKey
- The database column that connects one resource to another, for example category_id or product_id.
- typed relation field
- A resource property such as ?CategoryResource $category or array $reviews that exposes the linked records directly.
Relation Map
How links are declared
Relations are declared directly on resource properties. Each field names the target resource and the foreign key that connects the records.
| Resource field | Attribute | Target | What it gives you |
|---|---|---|---|
DemoCategoryResource::$products
|
#[HasMany(target: DemoProductResource::class, foreignKey: 'category_id')]
|
DemoProductResource[]
|
Books currently groups 3 products such as Domain-Driven Design. |
DemoProductResource::$category
|
#[BelongsTo(target: DemoCategoryResource::class, foreignKey: 'category_id')]
|
DemoCategoryResource
|
Portable Monitor belongs to Electronics through category_id. |
DemoProductResource::$reviews
|
#[HasMany(target: DemoReviewResource::class, foreignKey: 'product_id')]
|
DemoReviewResource[]
|
Portable Monitor currently exposes 5 linked reviews. |
DemoReviewResource::$product
|
#[BelongsTo(target: DemoProductResource::class, foreignKey: 'product_id')]
|
DemoProductResource
|
4-star review points back to Portable Monitor. |
Verified against Semitexa Ultimate 2026.09.19.1020
Relations
Semitexa resource relations are declared directly on ResourceModel properties using #[HasMany] and #[BelongsTo] attributes. Handlers traverse them through typed repository calls.
How it works
A #[HasMany(target: TargetResource::class, foreignKey: 'column_id')] property on a resource names the child resource and the join column. #[BelongsTo] declares the inverse. Handlers read relations by calling repository methods — findByProduct(), findById(), findByCategory() — and the ORM batch-loads the related rows for the full result set in a single query.
Why this matters
Relation declarations co-located with the resource definition make the data model readable without tracing through handler code. Explicit repository methods for loading relations keep the fetch strategy visible and batch-safe rather than hidden behind property access or lazy proxies.
How it works
A handler asks a repository for typed resources such as DemoProductResource. The ORM can then resolve parent and child links using the relation metadata already declared on the resource, so the handler reads $product->category or $product->reviews instead of rebuilding joins by hand.
Why it matters
This keeps the graph definition in one place. Reviewers can see the shape of the aggregate directly on the resource class, while handlers stay focused on use-case flow instead of repeating SQL knowledge.
Key concepts
- #[BelongsTo]
- Declares a single parent record and names the foreign key that points to it.
- #[HasMany]
- Declares a child collection that references the current resource through a foreign key.
- foreignKey
- The database column that connects one resource to another, for example category_id or product_id.
- typed relation field
- A resource property such as ?CategoryResource $category or array $reviews that exposes the linked records directly.
Read Flow
How a handler consumes those links
The handler asks a repository for typed resources. From there it can walk to parent and child records through the declared relation fields.
$products = $this->productRepository->findPage(3);
$product = $products[0] ?? null;
$category = $product !== null ? $this->categoryRepository->findById($product->getCategoryId()) : null;
$reviews = $product !== null ? $this->reviewRepository->findByProduct($product->getId()) : [];
$categoryName = $category?->getName();
$reviewCount = count($reviews);
$firstReviewProduct = $reviews[0]?->getProductId() ?? null;
| Step | Handler code | What becomes available |
|---|---|---|
| Fetch typed rows |
$this->productRepository->findPage(3)
|
3 DemoProductResource items for the screen |
| Walk to parent |
$this->categoryRepository->findById($product->getCategoryId())?->getName()
|
Portable Monitor belongs to Electronics through category_id. |
| Walk to children |
count($this->reviewRepository->findByProduct($product->getId()))
|
Portable Monitor currently exposes 5 linked reviews. |
| Walk back from child |
$this->productRepository->findById($review->getProductId())?->getName()
|
4-star review points back to Portable Monitor. |