When creating Internet-based projects using the Bitrix Framework platform, vast functional capabilities are available out of the box for use through API calls for relevant modules. In this case, each module in the framework concept is a separate working unit which ensures a solution for a certain range of tasks.
As a rule, the API of each module is designed based on specific tasks, and quite often call formats are different for each module. The basic functionality existing in almost every module is standardized in order to minimize these differences. These are CRUD operations: Create, Read, Update, and Delete (creating, reading, updating, and deleting data).
Concept of Entities
An entity is an aggregate of a set of objects with their intrinsic basic (low level) business logic. An entity has a set of properties which values are subject to specific processing rules.
For example, the entity of the User means numerous users with a set of fields:
- ID
- First name
- Last name
- Password
- Login
- etc.
At the same time, the database issues an ID automatically, First Name and Last Name are limited to 50 characters, Login must contain only Latin letters, numbers, and underscore sign, etc.
Instead of programming each entity, we would rather describe it in a specific format. Such a description would be processed by the system core and would represent some sort of configuration for it:
Book
ID int [autoincrement, primary]
ISBN str [match: /[0-9-]+/]
TITLE str [max_length: 50]
PUBLISH_DATE date
For example, a similar description can be used for a book catalog where the system itself will monitor the correctness and integrity of the data checking of the format and the correct range of acceptable values.
Field Typing
Tag tools (xml, yml, and similar) are not used for the configuration of entities, php is used instead. This option provides for better development opportunities and flexibility.
The determination of data types from the example above is as follows:
namespace SomePartner\MyBooksCatalog;
use Bitrix\Main\Entity;
class BookTable extends Entity\DataManager
{
public static function getTableName()
{
return 'my_book';
}
public static function getMap()
{
return array(
new Entity\IntegerField('ID'),
new Entity\StringField('ISBN'),
new Entity\StringField('TITLE'),
new Entity\DateField('PUBLISH_DATE')
);
}
}
Attention! Notwithstanding the fact that the example means a Book as an entity, the class name is followed by a postfix: BookTable. It is intended, because the name of a descriptive class of an entity must always be completed with the word Table. The main name Book in the same namespace is considered reserved, and in the future the main name is expected to be used (in this case, the Book class) to represent entity elements as objects (at present, these entities are represented by arrays as in the old getList methods).
The method getMap() is responsible for the description of the entity structure. It returns an array to field instances.
Each type of the field is represented as an inheritor class Entity\ScalarField. These fields operate simple scalar values which are saved in the database 'as is'. By default, 8 of such types are available:
- Integral number
- Number
- Line
- Text
- Date
- Date/Time
- Yes/No
- Value from the list
For the sake of consistency with code writing standards, field names should be uppercase. The names must be unique within the same entity.
As a rule, the first parameter in the field designer is the field name, and the second parameter contains additional settings. General settings will be dealt with later on in the same chapter, but there is a specific setting for BooleanField and EnumField:
new Entity\BooleanField('NAME', array(
'values' => array('N', 'Y')
))
new Entity\EnumField('NAME', array(
'values' => array('VALUE1', 'VALUE2', 'VALUE3')
))
Since true and false cannot be stored as such in the database, a mapping of values is created in the form of an array for BooleanField, where the first element replaces false and the second replaces true at the time of saving.
Note: when describing the entity the table name can be set in the method getTableName. In our example, it is my_book. If this method is not defined, the table name will be generated automatically from the namespace and class name, and for this entity it will result in b_somepartner_mybookscatalog_book.
Primary & autoincrement & required
In the majority of cases, the entity has a primary key for one field. The same key is, as a rule, autoincremental. To inform an entity about it, we have to use parameters in the field designer:
new Entity\IntegerField('ID', array(
'primary' => true
))
Note: a composite primary key is also possible. For example, in the relations of two entities, the IDs of both entities will form a composite key. To learn more about this and see an example, please refer to the section
N:M relations.
This is the way to announce the ownership of a field to the primary key. Thanks to this option, the entity will control data entering and will not permit adding an entry without specifying the value for the primary key. During the update and deletion of entries it will be possible to identify them using the primary key only.
The ID value is often not indicated but rather obtained from the database after successfully adding an entry. In this case, the entity must be informed about that:
new Entity\IntegerField('ID', array(
'primary' => true,
'autocomplete' => true
))
The flag autocomplete for an entity means that when adding a new entry the developer is not required to set values for this field. By default, this requirement is applicable only to the primary key fields, but it is possible to ask the system to also insist on setting any other field:
new Entity\StringField('ISBN', array(
'required' => true
))
Now, it will not be possible to add a new book without specifying its ISBN code.
Column Name Mapping
When describing entities for an already existing table you may wish to rename the column. For example, initially the ISBN field in the table my_book is named as ISBNCODE, and the old code uses this column name in SQL-query. If you wish to optimize the name, making it a more readable ISBN in the new API, you may use the parameter column_name:
new Entity\StringField('ISBN', array(
'required' => true,
'column_name' => 'ISBNCODE'
))
There are other instances when the same physical column of the table stores values that differ in meaning. In this case, we can create several entity fields with the same column_name.
ExpressionField Expressions
The system provides for the storage of data as is as well as its transformation during sampling. Let us assume that we need to obtain the age of a book in days at the same time when we obtain the issue date. This number is difficult to store in the database since, in this case, we would have to recalculate and update data daily. The age can be calculated on the side of the database:
SELECT DATEDIFF(NOW(), PUBLISH_DATE) AS AGE_DAYS FROM my_book
For this, it is necessary to describe a virtual field in the entity whose value is based on a SQL expression with other field(s):
new Entity\ExpressionField('AGE_DAYS',
'DATEDIFF(NOW(), %s)', array('PUBLISH_DATE')
)
As with the rest of fields, the first parameter to set is the name. The second parameter is the text of an SQL expression, but in this case other entity fields must be replaced with placeholders according to the format sprintf. The third parameter should transmit the array with entity field names in a certain order set in the expression.
Note: %s or %1$s, %2$s, etc. are recommended as placeholders. For example, when several fields (FIELD_X + FIELD_Y) * FIELD_X
participate in the expression EXPR, the expression can be described as follows:
'(%s + %s) * %s', [FIELD_X, FIELD_Y, FIELD_X];
or:
'(%1$s + %2$s) * %1$s', [FIELD_X, FIELD_Y]
.
Very often, expressions can be used to aggregate data (for example, COUNT(*)
or SUM(FIELD)
). These examples will be reviewed in the chapter Data Retrieval.
Note: expression fields can be used only in data sampling to select, filter, group, and sort according to them. Since such columns do not exist physically in the database table, there is no place to write the field value, and the system will generate an exception.
User-Defined (Custom) Fields
In addition to the fields ScalarField and ExpressionField, the entity may contain User-Defined Fields. They are configured through the Administrative interface and require no additional description on the entity side. Only a selected Object of the user-defined field must be indicated in the entity:/p>
class BookTable extends Entity\DataManager
{
...
public static function getUfId()
{
return 'MY_BOOK';
}
...
}
Note: Support for SqlExpression values for user fields in ORM is added starting from the Main module version 20.5.200 (main).
Later on, it is this identifier that must be indicated when attaching user-defined fields to an entity:
Thus, it is possible to select and update values of user-defined fields on the same basis as values of standard fields of an entity.
Caching
Starting from Main module version 24.100.0, now you have an option to restrict caching in ORM table.
To restrict caching, add the following description:
public static function isCacheable(): bool
{
return false;
}
Example
The following entity was obtained following this chapter:
namespace SomePartner\MyBooksCatalog;
use Bitrix\Main\Entity;
class BookTable extends Entity\DataManager
{
public static function getTableName()
{
return 'my_book';
}
public static function getUfId()
{
return 'MY_BOOK';
}
public static function getMap()
{
return array(
new Entity\IntegerField('ID', array(
'primary' => true,
'autocomplete' => true
)),
new Entity\StringField('ISBN', array(
'required' => true,
'column_name' => 'ISBNCODE'
)),
new Entity\StringField('TITLE'),
new Entity\DateField('PUBLISH_DATE')
);
}
}
// code to create a table in MySQL
// (obtained by calling BookTable::getEntity()->compileDbTableStructureDump())
CREATE TABLE `my_book` (
`ID` int NOT NULL AUTO_INCREMENT,
`ISBN` varchar(255) NOT NULL,
`TITLE` varchar(255) NOT NULL,
`PUBLISH_DATE` date NOT NULL,
PRIMARY KEY(`ID`)
);
Therefore, it is possible to describe regular scalar fields, single out a primary key from among them, and indicate the autoincrementive fields and the required fields. If there is a discrepancy between the column name in the table and a desired name in the entity, it can be fixed.
Attention! The getMap method is used only as a means to obtain the primary configuration of an entity. If you want to obtain the actual list of entity fields, please use the method BookTable::getEntity()->getFields().
The only thing left is to record the entity code in the project. According to the general file naming rules in D7, the entity code must be stored in the file: local/modules/somepartner.mybookscatalog/book.php
After that, the system will automatically connect the file upon finding calls of the BookTable class.
Note: the example above uses the recommended data entry. The legacy entry form as an array:
'ID' => array(
'data_type' => 'integer',
'primary' => true,
'autocomplete' => true,
),
has been kept for compatibility. Upon initialization, objects of classes
\Bitrix\Main\Entity\*
are still created. You can use both variants, but recommended and correct variant is via objects.