Vnitřní relace
V kapitolách Principy modelování a Definice jsme na téma vnitřních relací již narazili. Tedy již patrně víte, že entita může mít (v praxi často má) své subentity a ty mohou mít další subentity a že mezi těmito entitami jsou vztahy, kterým se říká vnitřní relace.
V kapitole Principy modelování jsme si ukazovali reálný příklad z chystané e-commerce platformy Jet Shop a to příklad číselníku. A toho se budeme držet i zde, jen si totéž rozebereme do detailu a ukážeme si reálnou podobu definic.
Připomeňme si uspořádání entity číselník:

A teď si již ukažme jednotlivé definice entity (subentit), které určují vnitřní relaci.
Hlavní entita
Číselník e-shopu musí mít:
- Vlastní identifikátor - tedy ID číselníku.
- Interní název číselníku (např.: barvy, velikosti, ....)
- A samotný seznam možností. Tedy seznam subenetit.
namespace JetShop;
use Jet\DataModel;
use Jet\DataModel_Definition;
use Jet\DataModel_IDController_AutoIncrement;
#[DataModel_Definition(
name: 'option_group',
database_table_name: 'option_groups',
id_controller_class: DataModel_IDController_AutoIncrement::class,
id_controller_options: ['id_property_name'=>'id']
)]
class OptionGroup extends DataModel {
#[DataModel_Definition(
type: DataModel::TYPE_ID_AUTOINCREMENT,
is_id: true,
)]
protected int $id = 0;
#[DataModel_Definition(
type: DataModel::TYPE_STRING,
max_len: 100,
)]
#[Form_Definition(
type: Form_Field::TYPE_INPUT,
label: 'Name:'
)]
protected string $name = '';
/**
* @var OptionGroup_Option[]
*/
#[DataModel_Definition(
type: DataModel::TYPE_DATA_MODEL,
data_model_class: OptionGroup_Option::class,
)]
protected array $options = [];
// ... ... ...
}
Na definici hlavní entity není nic zvláštního, kromě vlastnosti $options. Ta navazuje subentitu option_group_option směrem dolů - tedy od nadřazené entity po podřízenou entity a v rámci této vlastnosti (jedná se o relaci 1:N - tedy jde o pole) budou instance subentit - tedy samotné možnosti (barvy, velikosti a tak dále).
Subentita 1. úrovně
V našem příkladu je subentita 1. úrovně (tedy přímo náležící pod hlavní entitu) definice možnosti v rámci daného číselníku. Co o takové možnosti potřebujeme vědět?
- Určitě musí mít vlastní ID (aby bylo jasné např. o jakou barvu jde)
- Je nutné vědět, do jakého číselníku patří. Tedy znát ID nadřazené entity.
- Určitě bude mít několik dalších specifických parametrů jako například příznak zda je aktivní, jakou má prioritu a tak dále.
- A protože jsme v Evropě (ne-li ve světě), tak určitě potřebujeme nějak lokalizovat popisky a další údaje pro různé jazyky. Ovšem to již budou další subentity.
namespace JetShop;
use Jet\DataModel_Definition;
use Jet\DataModel;
use Jet\DataModel_IDController_AutoIncrement;
use Jet\DataModel_Related_1toN;
#[DataModel_Definition(
name: 'option_group_option',
database_table_name: 'option_groups_options',
id_controller_class: DataModel_IDController_AutoIncrement::class,
id_controller_options: ['id_property_name'=>'id'],
default_order_by: ['priority'],
parent_model_class: OptionGroup::class
)]
class OptionGroup_Option extends DataModel_Related_1toN {
#[DataModel_Definition(
type: DataModel::TYPE_ID_AUTOINCREMENT,
is_id: true,
)]
protected int $id = 0;
#[DataModel_Definition(
type: DataModel::TYPE_INT,
is_key: true,
related_to: 'main.id'
)]
protected int $option_group_id = 0;
#[DataModel_Definition(
type: DataModel::TYPE_BOOL,
)]
#[Form_Definition(
type: Form_Field::TYPE_INPUT,
label: 'Is active'
)]
protected bool $is_active = true;
#[DataModel_Definition(
type: DataModel::TYPE_INT,
is_key: true,
)]
protected int $priority = 0;
/**
* @var OptionGroup_Option_Localized[]
*/
#[DataModel_Definition(
type: DataModel::TYPE_DATA_MODEL,
data_model_class: OptionGroup_Option_Localized::class
)]
#[Form_Definition(is_sub_forms: true)]
protected array $localized = [];
//... ... ...
}
A tady už je to trochu zajímavější. Subentita musí definovat návaznost na rodičovskou entitu a to dvěma způsoby:
První je definice rodiče (rodičovské třídy) v atributech třídy:
#[DataModel_Definition(
.... ... ...
parent_model_class: OptionGroup::class
)]
A druhou nutností je definovat návaznost příslušné vlastnosti (nebo vlastností) na ID vlastnost (nebo vlastnosti) nadřazené entity a to takto:
#[DataModel_Definition(
... ... ...
related_to: 'main.id'
)]
protected int $option_group_id = 0;
Jak jsme si již řekli, tak tato subentita má svou subenetitu a platí zde to samé co pro hlavní entitu. Tedy musí definovat návaznost rodič - potomek a to vlastností $localized. Princip je zcela totožný jako u hlavní entity:
/**
* @var OptionGroup_Option_Localized[]
*/
#[DataModel_Definition(
type: DataModel::TYPE_DATA_MODEL,
data_model_class: OptionGroup_Option_Localized::class
)]
#[Form_Definition(is_sub_forms: true)]
protected array $localized = [];
Subentita 2. úrovně (a libovolné další úrovně)
A konečně jsme u subentity subentity. Tedy v našem příkladu u lokalizovaných dat možností z číselníku. Tak co o této subentitě potřebujeme vědět?
- Nemusí mít své vlastní ID (ve smyslu například autoincrement id). Záznam bude identifikován na základě ostatních údajů. (ale není to dogma, jakákoliv subentita může mít identifikaci záznamu jakoukoliv - jde pouze o tento konkrétní příklad).
- Musí znát ID nadřazené entity - tedy ID možnosti z číselníku.
- Musí znát ID hlavní entity - tedy ID číselníku (viz princip modelování).
- V našem konkrétním případě musí znát kód lokalizace ke kterému popisky (a další údaje) náleží.
- A samozřejmě ty vlastnosti, které chceme lokalizovat (popisky, nápovědy a tak dále).
namespace JetShop;
use Jet\DataModel;
use Jet\DataModel_Definition;
use Jet\DataModel_Related_1toN;
use Jet\DataModel_IDController_Passive;
use Jet\Locale;
#[DataModel_Definition(
name: 'option_group_option_localized',
database_table_name: 'option_groups_options_localized',
id_controller_class: DataModel_IDController_Passive::class,
parent_model_class: OptionGroup_Option::class
)]
class OptionGroup_Option_Localized extends DataModel_Related_1toN {
#[DataModel_Definition(
type: DataModel::TYPE_INT,
is_id: true,
related_to: 'main.id',
)]
protected int $option_group_id = 0;
#[DataModel_Definition(
type: DataModel::TYPE_INT,
is_id: true,
related_to: 'parent.id',
)]
protected int $option_id = 0;
#[DataModel_Definition(
type: DataModel::TYPE_LOCALE,
is_id: true
)]
protected ?Locale $locale = null;
#[DataModel_Definition(
type: DataModel::TYPE_STRING,
max_len: 255
)]
protected string $filter_label = '';
//... ... ...
}
V atributech třídy jste si patrně všimli pasivního ID kontroleru - to je však specifikum daného použití a daného příkladu. Může být použit jakýkoliv ID kontroler a systém identifikace záznamu.
Jinak návaznost na rodiče je stejná jako u subentity předchozí úrovně. Princip platí dále až do úrovně X. Je stále stejný.
Důležitá je dvojí vazba. Subentita 2. a každé další úrovně musí být vázána na svou přímo nadřazenou (rodičovskou) entitu:
#[DataModel_Definition(
type: DataModel::TYPE_INT,
is_id: true,
related_to: 'parent.id', //** !!!!!!! **
)]
protected int $option_id = 0;
Stejně tak jako musí být vázána na entitu hlavní:
#[DataModel_Definition(
type: DataModel::TYPE_INT,
is_id: true,
related_to: 'main.id', //** !!!!!!! **
)]
protected int $option_group_id = 0;
Shrnutí
Pro vnitřní vazby stačí dodržovat tyto principy:
- Z rodiče definovat vazbu na potomka pomocí k tomu určené vlastnosti třídy.
- Z potomka definovat vazbu na rodiče atributu třídy.
- Provázat příslušné vlastnosti potomka na ID vlastnosti rodiče.
- V každé další úrovni subentit vždy provázat příslušné vlastnosti na ID vlastnosti hlavní entity.