Entities; The Beating Heart
Effects on the projects
Xua will generate a PHP class extending a Xua abstract class calledEntity
for each Entity Block the programmer creates. These Entity classes are in a one-to-one correspondence with database tables, and each row of the table can be corresponded by an instance of the table's corresponding class.Also, the same happens in the Marshal Library with respect to the language. However, the front-end clone of the entities only contains method signatures, and the bodies are just network calls.
Server Project Side
A Literal Name is a literal string containing only alphanumeric characters, starting with a lowercase character. The set of all Literal Names is shown by $\mathbb{L}$.The set of all values possible to store in a variable is shown by $\mathbb{X}$.
A Dictionary is a partial function $D: \mathbb{L} \to \mathbb{X}$ and is usually expressed by all of its records. The set of all dictionaries is shown by $\mathbb{D}$.
A Field $(T, D)$ is a tuple, where $T \in \mathbb{T}$ is a type (described in Supers chapter) called Field Type and $D \in \mathbb{X}$ is a value called Field Default Value (providing $D$ is optional). Note that if $D$ is provided, it is mandatory that $D: T$.
An Entity Signature is a dictionary $F$ such that $F(s) = (T_s,
D_s)$
or $F(s)$ is undefined, where $s$ is a Field Name. Domain of the $F$ is called the Set of Field Names of the Entity Signature.
The set $\{ \mathsf{ASC}, \mathsf{DESC} \}$ is called the set of all Order Indicators and is shown by $\mathbb{O}$.
An Index is a $k$-tuple of tuples $(f, o)$ with an extra boolean determining if the index is unique, where $f$ is a Field Name, and $o \in \mathbb{O}$ is an Order Indicator. Indexes are used in the MySQL engine for faster
select
queries. We try to have a simple explanation here. Let $i_0 =
\Big( \big( (f_0,
o_0), \dots,
(f_n,
o_n) \big), u \Big)$ be an Index.
Intuitively speaking, The MySQL engine will create a list of pointers to data rows, sorted by the mentioned field, which makes it faster to search on those fields. Also if the Index is marked as unique, i.e., $u = \mathsf{1}$, the combination of fields must be unique in data, i.e., we cannot have two rows with same value of $f_0$,
same value of $f_1$,
..., and
same value of $f_n$ at the same time.An Entity Indexes is a set of indexes $I$.
An Entity Validation is a function with no output that inputs a data row and checks if the data is valid. In case of invalid data, an exception is thrown. For example, assume we store data about some events, each event has two fields,
start_time
and
end_time
, and the start must be sooner than the end. The Entity Validation checks if this condition holds, and if the end is sooner than the start, it will throw an EntityFieldException
.An Entity Class is a triplet $(F, I, V)$ of fields, indexes, and the validation function.
An Entity Instance is an instance of an entity class, which contains actual values for entity fields.
MySQL Side
A database is a set of tables; each table has a structure consisting of columns, and a set of rows as data.insert
is the action of adding new rows to a table.select
is the action of retrieving table rows on some specific conditions.update
is the action of modifying some table rows on some specific conditions.delete
is the action of removing some table rows on some specific conditions.Correspondence
Each table is in one-to-one correspondence with an Entity Class. Methods of these classes can communicate with the database server to select, insert, update, and delete data. Fields of the entity class represent the columns of the table, and instances of the class represent the rows of the table.Structure
An entity block has the following structure.# Path\To\Entity\EntityName
# Description of what this Entity is all about, probably in markdown formatting.
Entity [extends Path\To\Another\Entity\EntityName] {
fields {
field0 : type0 [= DEFAULT_0]; # Description of field0
... ; # ...
fieldN : typeN [= DEFAULT_0]; # Description of fieldN
}
[ indexes : {
([-]filedName00, ..., [-]fieldName0K)[*]; # Description of index number 0
... ; # ...
([-]filedNameM0, ..., [-]fieldNameML)[*]; # Description of index number M
} ]
[ validation : { VALIDATION_BODY } ]
[ override<METHOD_NAME_0> { METHOD_0_BODY } ]
...
[ override<METHOD_NAME_P> { METHOD_P_BODY } ]
}
Note that there is no name for the Entity. An Entity name is its file path. Each file can contain at most one block, and the block inherits its name from the file. It is a good practice to have a comment in the first line of the file describing where the file is located.
Note. All entities have a read-only field called
id
, of its own type Identifier()
, defined implicitly. The field id
is used in some underlying Xua codes and cannot be removed.Fields
Each Entity represents a class and is responsible for storing properties of its instances as data in the database. The object (instance) can have different properties, each with its type and default value. These properties are called fields and should be defined with their type and default value in thefields
part.Indexes
An index is a list of fields along with a boolean determining if the index is unique. Indexes are used in the database engine for fasterselect
queries. All fields are assumed to be ascending by default unless the programmer specifies one as descending by a -
sign before it, which tells the database engine to sort that field in descending order. A *
sign at the end of an index definition makes it a unique index. If the index is marked as unique, the combination of fields must be unique in data. Note that the implicit field id
is a unique field by default.Validation
The body of thevalidation
block is written in pure PHP.The
validation
block is responsible for checking if an instance of the Entity is valid, and in case of invalid data, an EntityFieldException
must be thrown. For example, assume we have a table of restaurants in an entity called Restaurant
. This Entity has two fields, title
and active
. The title
field is unique, but it is impossible to mark it as a unique index in indexes
because we may have many inactive records sharing the same title, and the title is unique among the active restaurants. (There is a workaround here to solve this problem only using indexes
, but for the purpose of this documentation, we assume there is not.) We can check this in the validation
block and throw an EntityFieldException
if the title is duplicate.Overriding Methods
Xua generates a PHP class from each entity, extendingEntity
. This class have many methods which are possible to override. Here is a list of these methods, with the PHP method that is actually being overridden.Override<initialize> => protected static _initialize();
Override<getOne> => protected static _getOne(Condition $condition, Order $order, string $caller);
Override<store> => protected _store(string $caller);
Override<storeQueries> => protected _storeQueries(string $caller);
Override<delete> => protected _delete(string $caller);
Override<getMany> => protected static _getMany(Condition $condition, Order $order, Pager $pager, string $caller);
Override<countMany> => protected static _countMany(Condition $condition, Order $order, Pager $pager, string $caller);
Override<deleteMany> => protected static _deleteMany(Condition $condition, Order $order, Pager $pager, string $caller);
Override<setMany> => protected static _setMany(array $changes, Condition $condition, Order $order, Pager $pager, string $caller);
Xua provides final methods that include the actual logic, which one can use when overriding a method. Here is a list of these method names.
Name in .xua file | Original Method |
---|---|
<initialize> | _x_initialize |
<getOne> | _x_getOne |
<store> | _x_store |
<storeQueries> | _x_storeQueries |
<delete> | _x_delete |
<getMany> | _x_getMany |
<countMany> | _x_countMany |
<deleteMany> | _x_deleteMany |
<setMany> | _x_setMany |
So, as an example, one can override the
storeQueries
procedure like this.Override<storeQueries> {
if (isset(static::fieldSignatures()['updatedAt'])) {
$this->updatedAt = DateTimeService::now();
}
try {
$return = static::_x_storeQueries(); // original Xua's store Queries logic
LogService::logDatabaseChange($this);
} catch (Exception as $e) {
LogService::logDatabaseException($e);
}
return $return;
}
Usually, the original methods suffice, and there is no need to override them, but in case of necessity, be careful not to corrupt the functionality.
Hierarchy
Entities can come in a hierarchy just like PHP classes, and thevalidation
block is a class method.protected function _validation();
Also, the fields and indexes of a child entity override the ones in the parent. This override includes the type and default value. Also, it is possible to add new fields or indexes to the type, but it is not possible to remove existing fields. Read Examples for more details.
Visibility
The visibility of entities is controlled by overriding methods. There is an additional variable accessible in all methods, including validation, called$caller
. This variable contains a string telling what party called this method. The values are accessible as class constants in the class \Xua\Tools\Visibility
. These values are Visibility::CALLER_PHP
,
Visibility::CALLER_DART
, etc.So it is possible to block foreign callers like the following.
if ($caller != Visibility::CALLER_PHP) {
throw AccessForbiddenException();
}
But there is more than this. It is possible to customize procedures according to the caller. For example
if ($caller != Visibility::CALLER_PHP) {
if ($this->id) {
if (!UserService::hasAccess(AccessService::MODIFY_SOME_ENTITY)) {
throw AccessForbiddenException();
}
} else
if (!UserService::hasAccess(AccessService::CREATE_SOME_ENTITY)) {
throw AccessForbiddenException();
}
}
}
$this->updatedByCaller = $caller;
Note. Accessing The entities through
URPI
is disabled by default, and therefore the $caller
is always PHP. One can enable this feature, but they must be super careful since it may result in severe vulnerabilities.Special Field Types
In addition to defined supers that can be called to generate type for field types, Xua offers two categories of unusual types that make the development of a project significantly easier and faster and the resulting project more efficient and more secure. Here we try to cover these two outstanding features of Xua.Virtual Field Supers
In some cases, one needs some fields for an entity that does not contain new data, so if defined as regular fields, this will result in duplicate/not-synced data. These supers will help mix up other fields and generate a new field that is calculated each time called but not stored. There are two types of virtual supers. One is calculated by the PHP engine and the other by the database engine. The Database Virtual Field is used when the programmer wants to use the result in a query, e.g., using in condition or order, while the PHP Virtual Field is used for more complicated mixtures of fields.PHP Virtual Field
The PHP Virtual Field has the following signature.PHPVirtualField{
arguments {
getter: Callback(
nullable = false,
parameters = [
{
name: null,
type: @php(Entity::class),
allowSubtype: true,
required: true,
checkDefault: false,
default: null,
passByReference: false,
},
]
);
setter: Callback(
nullable = true,
parameters = [
{
name: null,
type: @php(Entity::class),
allowSubtype: true,
required: true,
checkDefault: false,
default: null,
passByReference: true,
},
{
name: null,
type: null,
allowSubtype: true,
required: true,
checkDefault: false,
default: null,
passByReference: false,
},
]
) = null;
}
...
}
PHP Virtual Field Example
Let us say we have fieldsgender
, firstName
, and lastName
in entity User
, and want to create a field called title
based on these fields. We can define it like this.title : PHPVirtualField (
getter = (User $user) => {
if ($user->gender == User::GENDER_MALE) {
$honorific = "Mr ";
} elseif ($user->gender == self::GENDER_FEMALE) {
$honorific = "Miss ";
} else {
$honorific = "";
}
return $honorific . $user->firstName . " " . $user->lastName;
}
);
Database Virtual Field
The Database Virtual Field has the following signature.DatabaseVirtualField{
arguments {
getter: Callback(
nullable = false,
parameters = [
{
name: null,
type: @php(Entity::class),
allowSubtype: true,
required: true,
checkDefault: false,
default: null,
passByReference: false,
},
{
name: 'params',
type: 'array',
allowSubtype: true,
required: true,
checkDefault: false,
default: null,
passByReference: false,
},
]
);
}
...
}
Note that one cannot set a database virtual field, and therefore there is no setter method available on this field.
Note that another method argument is available called
params
, which is used to pass some extra parameters into the getter method. We discuss it in more detail in the Example section.Database Virtual Field Example
Let us say that we have an entity calledCity
, and we need a field that tells if the city is a town or a big city. At the moment, we consider cities that have a population of less than a million to be town, but this might change; either the population may change, or we may think of the area as an item, or we can have a more complicated way that involves both population and area of a city. So we cannot calculate the field isTown
each time we need it somewhere (this may result in duplicate code). Instead, we need a field that does this so we can change it later and the change affect all usages. We can do this by defining the field isTown
this way.isTown : DatabaseVirtualField (
getter (City $city, array $params) => {
return "{Entity::F(self::_POPULATION)->name} < 1000000";
}
)
For a more complex example, consider this scenario. Let us say that we have an entity called
Restaurant
, and we want to sort the restaurants by distance in ascending order, so we need a field called distance. A PHP Virtual Field can do this, but in that case, we need to fetch all restaurants from the database server and then sort them, which takes a significant amount of time and space. Instead, we can define it as a Database Virtual Field that allows us to use it while creating an order expression and tell the database server to sort the restaurants by itself and give us the first page. We define this field using the following code.distance : DatabaseVirtualField (
getter (City $city, array $params) => {
here = $params['here']; # Here, one can understand the application of the params argument.
$lat0 = "(PI() * {$here->lat} / 180)";
$long0 = "(PI() * {$here->long} / 180)";
$lat1 = "(PI() * {Entity::F(self::_GEO_LAT)->name} / 180)";
$long1 = "(PI() * {Entity::F(self::_GEO_LONG)->name} / 180)";
$a = "(
POWER(SIN(($lat0 - $lat1) / 2), 2) +
COS($lat0) *
COS($lat1) *
POWER(SIN(($long0 - $long1) / 2), 2)
)";
$c = "(2 * ATAN2(POWER($a, .5), POWER(1 - $a, .5)))";
$d = "6371000 * $c";
return $d;
}
)
Entity Relation
In almost any back-end project, some Entities are in relation with each other. For example, in a simple food delivery app, restaurants are handling orders, orders have items, items are being liked/ commented by users, users are ordering orders, restaurants are being liked/ commented by users, etc.There is a unique and special Super called
EntityRelation
responsible for handling such relations.But before we discuss this Super, we need to discuss different relations classes based on how we implement them.
A Little Formalism on Relation Classes
Assume $\mathcal{A}$ and $\mathcal{B}$ are two Entities, and $A$ and $B$ are sets of their instances, respectively. $R \subseteq A \times B$ is called a relation between $\mathcal{A}$ and $\mathcal{B}$. We define nine different classes of relations based on how we implement them. Any possible relation fits in one of these classes; actually, all of them fit in $\mathsf{NN}$. However, choosing the best class when defining the database structure is a matter of restriction and efficiency.1. $\mathsf{O11O}$ (Optional one-to-one Optional) is the class of all relations with the following conditions.
\begin{eqnarray*}
& i. & \forall a \in A, |\{ b \in B : aRb \}| \leq 1 \\
& ii. & \forall b \in B, |\{ a \in A : aRb \}| \leq 1
\end{eqnarray*}
2. $\mathsf{O11R}$ (Optional one-to-one Required) is the class of all relations with the following conditions.
\begin{eqnarray*}
& i. & \forall a \in A, |\{ b \in B : aRb \}| \leq 1 \\
& ii. & \forall b \in B, |\{ a \in A : aRb \}| = 1
\end{eqnarray*}
3. $\mathsf{R11O}$ (Required one-to-one Optional) is the class of all relations with the following conditions.
\begin{eqnarray*}
& i. & \forall a \in A, |\{ b \in B : aRb \}| = 1 \\
& ii. & \forall b \in B, |\{ a \in A : aRb \}| \leq 1
\end{eqnarray*}
4. $\mathsf{R11R}$ (Required one-to-one Required) is the class of all relations with the following conditions.
\begin{eqnarray*}
& i. & \forall a \in A, |\{ b \in B : aRb \}| = 1 \\
& ii. & \forall b \in B, |\{ a \in A : aRb \}| = 1
\end{eqnarray*}
5. $\mathsf{ON1}$ (Optional many-to-one) is the class of all relations with the following condition.
\begin{eqnarray*}
\forall a \in A, |\{ b \in B : aRb \}| \leq 1
\end{eqnarray*}
6. $\mathsf{RN1}$ (Required many-to-one) is the class of all relations with the following condition.
\begin{eqnarray*}
\forall a \in A, |\{ b \in B : aRb \}| = 1
\end{eqnarray*}
7. $\mathsf{1NO}$ (one-to-many Optional) is the class of all relations with the following condition.
\begin{eqnarray*}
\forall b \in B, |\{ a \in A : aRb \}| \leq 1
\end{eqnarray*}
8. $\mathsf{1NR}$ (one-to-many Required) is the class of all relations with the following condition.
\begin{eqnarray*}
\forall b \in B, |\{ a \in A : aRb \}| = 1
\end{eqnarray*}
9. $\mathsf{NN}$ (many-to-many) is the class of all relations.
The Signature
TheEntityRelation
has the following signature.EntityRelation{
arguments {
# Standard Arguments
relatedEntity : Universal( ) ;
relation : Enum (values = self::REL_ ) ;
invName : Symbol (nullable = true ) = null ;
# Constant Arguments
const fromMany : Boolean ( ) = false;
const fromOne : Boolean ( ) = false;
const toMany : Boolean ( ) = false;
const toOne : Boolean ( ) = false;
const is11 : Boolean ( ) = false;
const isN1 : Boolean ( ) = false;
const is1N : Boolean ( ) = false;
const isNN : Boolean ( ) = false;
const optional : Boolean ( ) = false;
const nullable : Boolean ( ) = false;
const required : Boolean ( ) = false;
const invOptional : Boolean ( ) = false;
const invRequired : Boolean ( ) = false;
const hasJunction : Boolean ( ) = false;
# Definition Side Arguments
definedOn : Enum (values = self::DEFINED_ON_) ;
const definedHere : Boolean ( ) = false;
const definedThere : Boolean ( ) = false;
const columnHere : Boolean ( ) = false;
const columnThere : Boolean ( ) = false;
}
...
}
Related Entity
To create a relation $R$ between two Entities $\mathcal{L}$ and $\mathcal{R}$, one has to define a field that represents $R$ with a type generated from theEntityRelation
Super. The field must be defined on $\mathcal{L}$ (called the Left Entity), and the relatedEntity
must be set to $\mathcal{R}$ (called the Right Entity).Relation Class
Therelation
argument determines the class of the relation and has one of the following values.EntityRelation::REL_O11O
EntityRelation::REL_O11R
EntityRelation::REL_R11O
EntityRelation::REL_R11R
EntityRelation::REL_ON1
EntityRelation::REL_RN1
EntityRelation::REL_1NO
EntityRelation::REL_1NR
EntityRelation::REL_NN
Name & Inverse Name
One can use this field to get all the related rows of a row in the database. TheinvName
argument is used to do the inverse job. We try to make it clear by an example.Assume one defines a field called
rel
on the LeftEntity
like this.LeftEntity {
fields {
...
rel : EntityRelation(
relatedEntity = @php(RightEntity::class),
relation = EntityRelation::REL_NN,
invName = 'invRel',
)
...
}
...
}
The Xua engine automatically generates an implicit field like this.
RightEntity {
fields {
...
invRel : EntityRelation(
relatedEntity = @php(LeftEntity::class),
relation = EntityRelation::REL_NN,
invName = 'rel',
)
...
}
...
}
Now, one can access the related instances using these fields.
$l = new LeftEntity();
// $l->rel is the set of all instances of RightEntity $r s.t. $l is in relation with $r.
$r = new RightEntity();
// $r->invRel is the set of all instances of LeftEntity $l s.t. $l is in relation with $r.
Note. In the X-to-one cases, the result of retrieving a field is not a set but instead a value that can be an empty Entity in optional cases.
Note. The
invName
argument is optional, and if it is not provided, the Xua engine does not generate the implicit inverse field.Constant Arguments
EntityRelation
offers a set of constant arguments calculated based on the relation class, that help with recognizing a relation better. The names are pretty explanatory by themselves, but here we provide the way we calculate each.$this->fromMany = in_array($this->relation, [self::REL_NN, self::REL_ON1, self::REL_RN1]);
$this->fromOne = !$this->fromMany;
$this->toMany = in_array($this->relation, [self::REL_NN, self::REL_1NO, self::REL_1NR]);
$this->toOne = !$this->toMany;
$this->is11 = ($this->fromOne and $this->toOne);
$this->isN1 = ($this->fromMany and $this->toOne);
$this->is1N = ($this->fromOne and $this->toMany);
$this->isNN = ($this->fromMany and $this->toMany);
$this->optional = in_array($this->relation, [self::REL_O11O, self::REL_O11R, self::REL_ON1]);
$this->nullable = $this->optional;
$this->required = !$this->optional;
$this->invOptional = in_array($this->relation, [self::REL_O11O, self::REL_R11O, self::REL_1NO]);
$this->invRequired = !$this->invOptional;
$this->hasJunction = $this->isNN;
Definition Side Arguments
There is a particular argument calleddefinedOn
which can either be here
or there
. This argument is not to be filled by the Xua programmer. If a relational field is defined explicitly on an Entity, the Xua engine sets this argument to here
, and for the implicit inverse field, the value of this argument is there
. There are also some constant fields in this regard; all of these arguments are used by the Xua core to decide how to store and process data.$this->definedHere = ($this->definedOn == self::DEFINED_ON_HERE);
$this->definedThere = ($this->definedOn == self::DEFINED_ON_THERE);
$this->columnHere = (($this->is11 and $this->definedHere) or $this->isN1);
$this->columnThere = (($this->is11 and $this->definedThere) or $this->is1N);
Entity Relation Example
For example, assume we have two Entities calledUser
and City
. Further, assume we want a field in the User
entity called currentCity
, and we want this field to refer to a row of the City
table. We have to add the following field to the User
entity.User {
fields {
...
currentCity : EntityRelation(
relatedEntity = @php(City::class),
relation = EntityRelation::REL_RNI,
invName = 'citizens',
)
...
}
...
}
Relation. The
EntityRelation::REL_RNI
stands for a many-to-one relation required on the left side, which means that no user can be in more than one city simultaneously but needs to be in a city, although one city can have many citizens at once.Inverse Name. Here, we created a field called
currentCity
that shows us a relation between users and cities, and we can get related cities of a user by $user->currentCity
, but how can we get related users of a city? The Xua engine generates an implicit field based on the invName
. So $city->citizens
gives us the list of all users that their currentCity
is $city
.Field Class
Each field defined under a name is accessible using the static methodEntity::F({fieldName})
of the entity class. This property is an instance of a class called EntityFieldSignature
. This class has the following structure.public string $entity,
public string $name,
public Super $type,
public mixed $default = null,
public function p(?array $param = null): array|EntityFieldSignature;
Field Class Example
Assume the Database Virtual Field Example we have provided above. Here we work a little around the field$restaurant->distance
.Notations
Restaurant::fieldSignatures()[Restaurant::DISTANCE]
and Entity::F(Restaurant::_DISTANCE)
both refer to the same value, an instance of class EntityFieldSignature
describing this field. So we know the following expressions are true.Entity::F(Restaurant::_DISTANCE)->entity == Restaurant::class;
Entity::F(Restaurant::_DISTANCE)->name == 'distance';
Entity::F(Restaurant::_DISTANCE)->type == DatabaseVirtualField (...);
Entity::F(Restaurant::_DISTANCE)->default == null;
// The parameters of a field is null by default.
Entity::F(Restaurant::_DISTANCE)->p() == null;
// We can modify the parameters of a field by calling the method `p` on its signature.
Entity::F(Restaurant::_DISTANCE)->p(['here' => (object)['lat' => 42, 'long' => 59]]); // https://en.wikipedia.org/wiki/Khwarazm
// Now it is set.
Entity::F(Restaurant::_DISTANCE)->p() == ['here' => (object)['lat' => 42, 'long' => 59]];
Conditional Field Class
TheConditionField
class is similar to the regular field class but has some features used in defining conditions and orders, which we discuss later. The Condition Field of each field is accessible as the static method Entity::C({fieldName})
of the entity class. This class has the following structure.public function __construct(public EntityFieldSignature $signature);
public function rel(ConditionField $conditionField): static;
public function name() : string;
public function joins(): array;
So the
Entity::C({fieldName})
is actually new ConditionField(Entity::F({fieldName}))
, an instance of ConditionField
based on the field signature.The critical feature of this class is the
rel
method, which makes it possible to access fields on the related tables. We cover this in the Condition and Order sections.The methods
name
and joins
are not usually helpful for the Xua programmer and are used in the Xua core. So we do not cover them here.Condition
Condition is a Xua built-in class that we use to createWHERE
expressions with.Theoretically speaking, a condition node is a deciding machine that inputs a row of a specific table and returns a boolean that indicates whether the given row is accepted in the condition or not.
Each instance of Condition is a node in a semi-binary tree. There are two ways two create an instance of this class. One is to create a new node (a leaf), and the other is to operate on existing nodes.
Leaf Condition
There are three common types of leaf conditions: relational leaf, true leaf, and false leaf. However, one can create a custom leaf as a raw leaf.Relational Leaf
The method for creating a relational leaf is the following.Condition::leaf(ConditionField $field, string $relation, mixed $value = null);
This method will create a condition asserting that the field
$filedName
must be in $relation
relation with the value $value
.Field
As described above, for each entity, conditional fields are available as instances ofConditionField
using the static methods of the form Entity::C({fieldName})
.Relation
XUA also provides relation constants as class constants of theCondition
class. These constants are listed below.Name | SQL equivalent | Description |
---|---|---|
Condition::GRATER | > | Greater than |
Condition::NGRATER | <= | Negation of GRATER |
Condition::GRATEREQ | >= | Greater than or equal |
Condition::NGRATEREQ | < | Negation of GRATEREQ |
Condition::LESS | < | Less than |
Condition::NLESS | >= | Negation of LESS |
Condition::LESSEQ | <= | Less than or equal |
Condition::NLESSEQ | > | Negation of LESSEQ |
Condition::EQ | = | Equal |
Condition::NEQ | != | Negation of EQ |
Condition::NULLSAFEEQ | <=> | NULL-safe equal |
Condition::NNULLSAFEEQ | !(... <=> ...) | Negation of NNULLSAFEEQ |
Condition::BETWEEN | BETWEEN ... AND ... | Whether a value is within a range of values (This relation requires the argument $value to be an array consisting of two values) |
Condition::NBETWEEN | NOT BETWEEN ... AND ... | Negation of BETWEEN |
Condition::IN | IN | Whether a value is within a set of values (This relation requires the argument $value to be an array of values) |
Condition::NIN | NOT IN | Negation of IN |
Condition::IS | IS | Test a value against a boolean |
Condition::NIS | IS NOT | Negation of IS |
Condition::ISNULL | IS NULL | NULL value test (This relation does not depend on value of argument $value ) |
Condition::NISNULL | IS NOT NULL | Negation of ISNULL (This relation does not depend on value of argument $value ) |
Condition::LIKE | LIKE | Simple pattern matching |
Condition::NOT_LIKE | NOT LIKE | Negation of LIKE |
Condition::REGEXP | REGEXP | Simple regular expression pattern matching |
Condition::NOT_REGEXP | NOT REGEXP | Negation of REGEXP |
Value
The value argument must usually fit in the field type. However, in some cases (some relations), one value does not suffice (e. g.Condition::BETWEEN
), so we need to provide two values of the field type as an array.Condition on Related Entities
To create a condition node that presents a condition on one of the related entities, one can use therel
method on condition field instances.Example
For example, a condition node that asserts that a person is born in the '90s would look like the following.Condition::leaf(
Entity::C(User::_BIRTH_DATE),
Condition::BETWEEN,
[
DateTimeInstance::fromGregorianYmd('1990-1-1'),
DateTimeInstance::fromGregorianYmd('2000-1-1')
]
)
As an other example, assume we want to have a condition that refers to all posts written by people living in Palo Alto. We may use the following node.
Condition::leaf(
Entity::C(Post::AUTHOR)
->rel(Entity::C(User::_LIVING_IN))
->rel(Entity::C(AdministrativeDivision::_TITLE)),
Condition::EQ,
'Palo Alto'
)
True Leaf & False Leaf
The special leaf methodsCondition::trueLeaf()
and Condition::falseLeaf()
are always true and false respectively.Raw Leaf
The raw leaf method is described as follows.Condition::rawLeaf(string $template, array $parameters = [], array $joins = [])
This method is used to inject pure and raw SQL script to the WHERE expression in case other leaf methods do not satisfy the programmer's needs. The use of this method is highly discouraged as the other methods are powerful enough to satisfy almost all of the programmer's needs. Still, a somehow funny usage of this method would be the following.
Condition::rawLeaf("RAND() > ?", [0.5])
We will discuss parameter binding used in the above code in more detail later.
Operations on Conditions
In addition to the above methods for creating new leaf nodes, Xua provides some methods to create new nodes using existing nodes.Logical Operators
four logical operators are used to create a new node using existing nodes. These operators are binary-operators AND, OR, XOR, and unary-operator NOT.Classic Operators
The primitive way to create a new node is to provide operands to an operator and receive a new node as the return value. The four methods for this are the followings.public static function _and_(Condition $leftCondition, Condition $rightCondition): Condition;
public static function _or_(Condition $leftCondition, Condition $rightCondition): Condition;
public static function _xor_(Condition $leftCondition, Condition $rightCondition): Condition;
public static function _not_(Condition $condition): Condition;
High-Level Operators
Although the described methods can theoretically create any condition, Xua provides some methods to improve code readability.C Operators
The first group is called the C (Condition) group. These methods are used on a node to connect it to another node.public function andC(Condition $condition): Condition;
public function orC(Condition $condition): Condition
public function xorC(Condition $condition): Condition;
public function not(): Condition;
For example, take a look at the following code.
$l = Condition::trueLeaf();
// $l is now rendered as 'TRUE'
$l->andC(Condition::falseLeaf());
// $l is now rendered as '(TRUE) AND (FALSE)'
$l->not();
// $l is now rendered as 'NOT ((TRUE) AND (FALSE))'
Common Operators
These methods are used on a node to connect it to a relational node, but the creation of the second node is embedded.public function and(ConditionField $field, string $relation, mixed $value = null): Condition
{
return $this->andC(Condition::leaf($field, $relation, $value));
}
public function or(ConditionField $field, string $relation, mixed $value = null): Condition
{
return $this->orC(Condition::leaf($field, $relation, $value));
}
public function xor(ConditionField $field, string $relation, mixed $value = null): Condition
{
return $this->xorC(Condition::leaf($field, $relation, $value));
}
R Operators
This group is called the R (Raw) group. These methods are used on a node to connect it to a raw node, but the creation of the second node is embedded.public function andR(string $template, array $parameters = [], array $joins = []): Condition
{
return $this->andC(Condition::rawLeaf($template, $parameters, $joins));
}
public function orR(string $template, array $parameters = [], array $joins = []): Condition
{
return $this->orC(Condition::rawLeaf($template, $parameters, $joins));
}
public function xorR(string $template, array $parameters = [], array $joins = []): Condition
{
return $this->xorC(Condition::rawLeaf($template, $parameters, $joins));
}
Order
Order is a Xua built-in class that is usually used to createORDER BY
expressions.To create an instance of this class, one needs to start with an empty instance and append order expressions. Each new order appended has a lesser priority than the last one. The bootstrap instance is created using the following method.
Order::noOrder(): Order;
To append a raw order expression in SQL syntax, one may use the following method, but it is highly discouraged.
public function addRaw(string $order): Order;
To append an order based on an entity field, one may use the following method, which is preferred.
public function add(ConditionField $field, string $direction): Order
{
return $this->addRaw($field->name() . ' ' . $direction);
}
Note that the direction can be one of the values
Order::ASC
and Order::DESC
.There are some other come in handy methods to use in order to avoid using the
addRaw
method, such as the following.public function addRandom(): Order
{
return $this->addRaw('RAND()');
}
Pager
Pager is a Xua built-in class that is usually used to generateLIMIT ... OFFSET ...
expressions.Each class instance contains these arguments (
limit
and offset
) as properties, but it is a good practice not to modify them manually. The class constructor takes two arguments, $pageSize
and $pageIndex
, and calculates the limit and offset values according to them. It is possible to modify these arguments as well. There is also one specific instance accessible usingPager::unlimited()
, which is used to fetch all rows. The following properties and methods are present in the pager class.Properties | Description |
---|---|
private int $pageSize; | Page Size |
private int $pageNumber; | Page Number |
Methods | Description |
---|---|
<em></em>construct(int $pageSize, int $pageNumber); | Creates a new instance with the given data. |
next(); | Goes to the next page. |
previous(); | Goes to the previous page. |
getPageSize(); | Returns page size. |
setPageSize(int $pageSize); | Sets page size. |
getPageNumber(); | Returns page number. |
setPageNumber(int $pageNumber); | Sets page number. |
static pages(Condition $condition, int $pageSize); | Returns the number of pages if we store rows of $entityName under $condition in pages of size $pageSize . |
Note. The pages start from one.
Deploy & Alters
When a new Entity is created, or modified, these changes should be synced with the MySQL server. There is a particular service calledEntityAlterService
, which is responsible for doing this. The static method alters(): string
on this class adds newly created entities as tables to the database by itself and returns ALTER
queries for modified entities. This method does not run the alters by itself, instead, it prints out the alters; which are possible to run by the programmer or another person in charge.The convention is to use this method in the deploy procedure, check for the alters, and generate a warning if the database structure is not synced with the program. It is also a convention to give developers the ability to complete the deploy process with the undone alters. This process is called force deploy, and the alters which are possible to ignore while deploying are called force-friendly alters.
Usage
It is possible to use a defined entity in both server project (PHP language) and front-end native codes via the Marshal Library, but not in the Xua source codes. The usage in the Marshal Library is controlled via the visibility of entity methods and the modification of URPI, and it is not that different from the PHP usage.In this section, we try to cover all methods, properties, and constants available on an Entity class.
Properties & Constants
Xua generates a class property and a class constant for each field defined in an entity. The property is defined on each instance and refers to the corresponding value in the database, while the constant (named<em>{FIELD</em>NAME<em>IN</em>SCREAMING<em>SNAKE</em>CASE}
) contains the field name. For example for field birthDate
on User
, Xua generate $user->birthDate
and User::<em>BIRTH</em>DATE = 'User.birthDate'
. This constant can be used to get Field and Conditional Field instances using Entity::F(User::<em>BIRTH</em>DATE)
and Entity::C(User::<em>BIRTH</em>DATE)
respectively.Helpers
@TODOExecute
@TODOField Signatures
@TODOIndexes
@TODOFields
@TODOTable
@TODOVARQUE Actions
In order to discuss other entity methods, we need to discuss the VARQUE actions first.There are different actions that one can take on a database. We divide these functionalities into three categories: Select Data Actions, Create/Modify Data Actions, and Delete Data Actions. We may also divide the functions based on whether they act on a single row or a group of rows, which takes us to two categories Singular Actions and Conditional Actions. This gives us six types of actions, which we name each one as follows.
Retrieve | Create/Modify | Delete | |
---|---|---|---|
Singular | View | Adjust | Remove |
Conditional | Query | Update | Eliminate |
These actions are called the VARQUE (View, Adjust, Remove, Query, Update, Eliminate) actions.
View
Two methods are used to retrieve a single row of a table. The first one has the following signature.static function getOne(
?Condition $condition = null,
?Order $order = null,
string $caller = Visibility::CALLER_PHP
): static;
This function takes an instance of the Condition class and an instance of the Order class (both are described before) and returns the first row (ordered by
$order
) that matches the $condition
criteria. If no such row is found, it returns an empty instance of the entity. To check if an instance of an entity class is empty, one can check if the id
property is null or not ($entity->id === null
or not).The second one is the class constructor.
function __construct(?int $id = null);
This function is just an alias for the following call, designed for simplicity.
EntityName::getOne(
Condition::leaf(
Entity::C(EntityName::_ID),
Condition::EQ,
$id
)
);
Adjust
@TODOstore
Remove
@TODOdelete
Eliminate
@TODOdeleteMany
Introduction
Xua can be configured to communicate with a database server (usually MySQL), but the programmer must specify the database structure. To do so, Xua offers Entity Blocks. Read Entities section under Units chapter for theoretical explanations. In this chapter, we focus on practical details of defining and using entities.