Internal relation
In the Modeling Principles chapters, we have already encountered the topic of internal relations. Thus, you probably already know that an entity can have (in practice, often does have) subentities, and that these subentities can have other subentities, and that there are relationships between these entities called internal relations.
In the chapter Modeling Principles, we showed an example of an invoice. And we'll follow that here, but we'll break down the same thing in detail and show the real-world definitions.
Recall the layout of the entity invoice:
And now let's see the individual definitions of the entities (subentities) that determine the internal relation.
Main entity
The invoice must have:
- Customer identifier - i.e. invoice ID.
- Document number for accounting
- Invoice issue date
- Invoice due date
- Customer ID
- Customer billing data valid at the time of invoice issue
- ... and a variety of other data (for brevity, the definition will not include everything)
namespace JetApplication;
use Jet\DataModel;
use Jet\DataModel_Definition;
use Jet\DataModel_Fetch_Instances;
use Jet\DataModel_IDController_AutoIncrement;
use Jet\Form;
use Jet\Form_Field;
use Jet\Data_DateTime;
#[DataModel_Definition(
name: 'invoice',
database_table_name: 'invoice',
id_controller_class: DataModel_IDController_AutoIncrement::class,
id_controller_options: [
'id_property_name' => 'invoice_id'
]
)]
class Invoice extends DataModel
{
#[DataModel_Definition(
type: DataModel::TYPE_ID_AUTOINCREMENT,
is_id: true
)]
protected int $invoice_id = 0;
#[DataModel_Definition(
type: DataModel::TYPE_STRING,
is_key: true,
max_len: 50
)]
protected string $invoice_number = '';
#[DataModel_Definition(
type: DataModel::TYPE_DATE
)]
protected ?Data_DateTime $date_of_invoice = null;
#[DataModel_Definition(
type: DataModel::TYPE_DATE
)]
protected ?Data_DateTime $expiry_date = null;
#[DataModel_Definition(
type: DataModel::TYPE_INT,
is_key: true
)]
protected int $customer_id = 0;
#[DataModel_Definition(
type: DataModel::TYPE_STRING,
max_len: 255
)]
protected string $customer_name = '';
#[DataModel_Definition(
type: DataModel::TYPE_DATA_MODEL,
data_model_class: Invoice_Items::class
)]
protected array $items = [];
#[DataModel_Definition(
type: DataModel::TYPE_DATA_MODEL,
data_model_class: Invoice_Payments::class
)]
protected array $payments = [];
//...........
}
It is worth noticing especially the $items property (and also $payments - the same principle). It follows the invoice_item subentity downwards in the 1:N relation.
Subentity - 1. level
In our example, the level 1 sub-entity (i.e. directly under the main entity) is the invoice item. What do we need to know about an invoice item?
- It definitely needs to have its own ID
- We need to know what invoice it belongs to. That is, know the ID of the parent entity.
- It will definitely have a few other specific parameters such as description, price per unit, number of units and so on.
namespace JetApplication;
use Jet\DataModel;
use Jet\DataModel_Definition;
use Jet\DataModel_IDController_AutoIncrement;
use Jet\DataModel_Related_1toN;
#[DataModel_Definition(
name: 'invoice_items',
database_table_name: 'invoice_items',
parent_model_class: Invoice::class,
id_controller_class: DataModel_IDController_AutoIncrement::class,
id_controller_options: [
'id_property_name' => 'item_id'
]
)]
class Invoice_Items extends DataModel_Related_1toN
{
#[DataModel_Definition(
type: DataModel::TYPE_ID_AUTOINCREMENT,
is_id: true
)]
protected int $item_id = 0;
#[DataModel_Definition(
related_to: 'main.invoice_id',
is_key: true
)]
protected int $invoice_id = 0;
#[DataModel_Definition(
type: DataModel::TYPE_INT,
is_key: true
)]
protected int $goods_id = 0;
#[DataModel_Definition(
type: DataModel::TYPE_STRING,
max_len: 255
)]
protected string $item_name = '';
#[DataModel_Definition(
type: DataModel::TYPE_FLOAT
)]
protected float $price_per_unit = 0.0;
#[DataModel_Definition(
type: DataModel::TYPE_FLOAT
)]
protected float $number_of_units = 0.0;
#[DataModel_Definition(
type: DataModel::TYPE_FLOAT
)]
protected float $vat_rate = 0.0;
public function getArrayKeyValue() : string
{
return $this->item_id;
}
//... ... ...
}
And here's where it gets a little more interesting. A subentity must define a continuity to the parent entity in two ways:
The first is the definition of the parent (parent class) in the class attributes:
#[DataModel_Definition(
.... ... ...
parent_model_class: Invoice::class
)]
And the second necessity is to define the relation of the corresponding property (or properties) to the ID property (or properties) of the parent entity as follows:
#[DataModel_Definition(
related_to: 'main.invoice_id',
is_key: true
)]
protected int $invoice_id = 0;
Subentity - 2. Level (and any other level)
And now we will show how to deal with the situation if the factor item had another subentity attached to it.
- Must know the ID of the parent entity - i.e. the ID of the invoice item.
- Must know the ID of the main entity - i.e. the ID of the invoice.
- And of course other specific properties of the subentity.
namespace JetApplication;
use Jet\DataModel;
use Jet\DataModel_Definition;
use Jet\DataModel_IDController_Passive;
use Jet\DataModel_Related_1toN;
#[DataModel_Definition(
name: 'invoice_items_subentty',
database_table_name: 'invoice_items_subentty',
parent_model_class: Invoice_Items::class,
id_controller_class: DataModel_IDController_Passive::class,
)]
class Invoice_Items_Subentty extends DataModel_Related_1toN
{
#[DataModel_Definition(
type: DataModel::TYPE_STRING,
is_id: true
)]
protected string $subitem_id = '';
#[DataModel_Definition(
related_to: 'main.invoice_id',
is_key: true
)]
protected int $invoice_id = 0;
#[DataModel_Definition(
related_to: 'parent.item_id',
is_key: true
)]
protected int $item_id = 0;
#[DataModel_Definition(
type: DataModel::TYPE_STRING,
max_len: 255
)]
protected string $something = '';
public function getArrayKeyValue() : string
{
return $this->subitem_id;
}
//... ... ...
}
The double bind is important. The 2nd and every other level entity must be bound to its immediate parent entity:
#[DataModel_Definition(
related_to: 'parent.item_id',
is_key: true
)]
protected int $item_id = 0;
Just as it must be bound to the main entity:
#[DataModel_Definition(
related_to: 'main.invoice_id',
is_key: true
)]
protected int $invoice_id = 0;
Summary
For internal bindings, just follow these principles:
- From the parent, define a binding to the child using the designated class property.
- From the child, define a binding to the parent of the class attribute.
- Bind the appropriate properties of the child to the property ID of the parent.
- In every other subentity level, always bind the appropriate properties to the property ID of the parent entity.
And look out, good news! You don't have to do any of this manually ;-) Jet Studio will do it all for you.