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)
The basic definition of this entity can take the following form:

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_classDataModel_IDController_AutoIncrement::class,
    
id_controller_options: [
        
'id_property_name' => 'invoice_id'
    
]
)]
class 
Invoice extends DataModel
{
    #[
DataModel_Definition(
        
typeDataModel::TYPE_ID_AUTOINCREMENT,
        
is_idtrue
    
)]
    protected 
int $invoice_id 0;

    #[
DataModel_Definition(
        
typeDataModel::TYPE_STRING,
        
is_keytrue,
        
max_len50
    
)]
    protected 
string $invoice_number '';

    #[
DataModel_Definition(
        
typeDataModel::TYPE_DATE
    
)]
    protected ?
Data_DateTime $date_of_invoice null;

    #[
DataModel_Definition(
        
typeDataModel::TYPE_DATE
    
)]
    protected ?
Data_DateTime $expiry_date null;

    #[
DataModel_Definition(
        
typeDataModel::TYPE_INT,
        
is_keytrue
    
)]
    protected 
int $customer_id 0;

    #[
DataModel_Definition(
        
typeDataModel::TYPE_STRING,
        
max_len255
    
)]
    protected 
string $customer_name '';

    #[
DataModel_Definition(
        
typeDataModel::TYPE_DATA_MODEL,
        
data_model_classInvoice_Items::class
    )]
    protected array 
$items = [];

    #[
DataModel_Definition(
        
typeDataModel::TYPE_DATA_MODEL,
        
data_model_classInvoice_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.
Let's illustrate:

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_classInvoice::class,
    
id_controller_classDataModel_IDController_AutoIncrement::class,
    
id_controller_options: [
        
'id_property_name' => 'item_id'
    
]
)]
class 
Invoice_Items extends DataModel_Related_1toN
{

    #[
DataModel_Definition(
        
typeDataModel::TYPE_ID_AUTOINCREMENT,
        
is_idtrue
    
)]
    protected 
int $item_id 0;
 
    #[
DataModel_Definition(
        
related_to'main.invoice_id',
        
is_keytrue
    
)]
    protected 
int $invoice_id 0;

    #[
DataModel_Definition(
        
typeDataModel::TYPE_INT,
        
is_keytrue
    
)]
    protected 
int $goods_id 0;

    #[
DataModel_Definition(
        
typeDataModel::TYPE_STRING,
        
max_len255
    
)]
    protected 
string $item_name '';

    #[
DataModel_Definition(
        
typeDataModel::TYPE_FLOAT
    
)]
    protected 
float $price_per_unit 0.0;

    #[
DataModel_Definition(
        
typeDataModel::TYPE_FLOAT
    
)]
    protected 
float $number_of_units 0.0;

    #[
DataModel_Definition(
        
typeDataModel::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_classInvoice::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_keytrue
)]
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.
Again, let's illustrate:

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_classInvoice_Items::class,
    
id_controller_classDataModel_IDController_Passive::class,
)]
class 
Invoice_Items_Subentty extends DataModel_Related_1toN
{

    #[
DataModel_Definition(
        
typeDataModel::TYPE_STRING,
        
is_idtrue
    
)]
    protected 
string $subitem_id '';

    #[
DataModel_Definition(
        
related_to'main.invoice_id',
        
is_keytrue
    
)]
    protected 
int $invoice_id 0;

    #[
DataModel_Definition(
        
related_to'parent.item_id',
        
is_keytrue
    
)]
    protected 
int $item_id 0;

    #[
DataModel_Definition(
        
typeDataModel::TYPE_STRING,
        
max_len255
    
)]
    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_keytrue
)]
protected 
int $item_id 0;

Just as it must be bound to the main entity:

#[DataModel_Definition(
    
related_to'main.invoice_id',
    
is_keytrue
)]
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.
Besides, there is no need to define extra internal relations.

And look out, good news! You don't have to do any of this manually ;-) Jet Studio will do it all for you.

Previous chapter
ID controllers
Next chapter
External relation