Stratified relation graphs¶
A scope can contain a root manager and several dependency managers.
$configs = [
new StratifiedEntityConfig(CatalogManager::class, 'catalog', 'shopId', scopeRoot: true),
new StratifiedEntityConfig(CatalogSettingsManager::class, 'catalog', 'shopId'),
new StratifiedEntityConfig(ProductManager::class, 'catalog', 'shopId'),
new StratifiedEntityConfig(TagManager::class, 'catalog', 'shopId'),
new StratifiedEntityConfig(ProductTagManager::class, 'catalog', 'shopId'),
];
Include the strate in every relation key¶
#[ToOne(
CatalogSettingsManager::class,
[
'id' => 'catalogId',
'buildStrate' => 'buildStrate',
],
)]
private ?CatalogSettingsEntity $settings = null;
#[ToMany(
ProductManager::class,
[
'id' => 'catalogId',
'buildStrate' => 'buildStrate',
],
)]
private ?EntityCollection $products = null;
Without buildStrate in the mapping, a join can combine a current row with a relation from an older snapshot. The build identifier is therefore part of the relational boundary, not only version metadata.
Many-to-many¶
Represent many-to-many relations with an explicit stratified join entity. The join entity owns two ToOne relations; each side exposes a ToMany relation to join rows. Include buildStrate in both mappings and enforce uniqueness with database constraints.
Database constraints and indexes¶
The database schema must enforce the same snapshot boundary as the ORM metadata. For each stratified table:
- index
(scope_id, build_strate)for released queries and garbage collection; - make relation targets unique on
(id, build_strate)when a composite foreign key references both columns; - include
build_stratein child foreign keys; - include
build_stratein join-entity uniqueness constraints.
Example:
CREATE INDEX idx_product_scope
ON product (shop_id, build_strate);
ALTER TABLE product
ADD CONSTRAINT uniq_product_id_build UNIQUE (id, build_strate);
ALTER TABLE product_tag
ADD CONSTRAINT fk_product_tag_product
FOREIGN KEY (product_id, build_strate)
REFERENCES product (id, build_strate)
ON DELETE CASCADE;
ALTER TABLE product_tag
ADD CONSTRAINT uniq_product_tag_link
UNIQUE (product_id, tag_id, build_strate);
The table primary-key strategy must permit rows from several snapshots to coexist. Generate a fresh row id for every snapshot, as in the supplied fixtures, or use a schema whose composite primary key includes build_strate.
Persistence order is a dependency graph¶
Foreign-key constraints still define ordering requirements. A child cannot be inserted before the parent row it references exists in the same build.
The important distinction is that only dependent work must be ordered. Independent branches can run at the same time.
For example:
Catalog
|
+-- Settings
|
+-- Products --------+
| |
+-- Tags -------------+-- Products and Tags can be persisted concurrently
|
+-- ProductTag rows after both sides exist
A safe build strategy is therefore:
- persist common parents required by foreign keys;
- group the remaining work by dependency level;
- run independent groups concurrently when the runtime and connection pool allow it;
- wait for every required branch;
- persist join/dependent rows whose parents are now present;
- call
release()only after the complete graph is valid.
The package does not traverse the graph or schedule coroutines automatically. persist() and persistMany() are explicit persistence APIs; the application owns concurrency and failure coordination.
Sequential example¶
$strate = $stratifiedPersist->createNewVersion(
[
CatalogManager::class,
CatalogSettingsManager::class,
ProductManager::class,
TagManager::class,
ProductTagManager::class,
],
'catalog',
$shopId,
);
$stratifiedPersist->persist($catalog, $strate);
$stratifiedPersist->persist($settings, $strate);
$stratifiedPersist->persistMany($products, $strate);
$stratifiedPersist->persistMany($tags, $strate);
$stratifiedPersist->persistMany($productTags, $strate);
$stratifiedPersist->release('catalog', $shopId, $strate);
This sequential form is valid. A concurrent implementation may execute the independent settings, products and tags branches in parallel after their required parent rows exist, then persist productTags and release the build.