update documentation

This commit is contained in:
Dennis Eichhorn 2021-12-19 20:16:46 +01:00
parent 13082fca57
commit 0bb0807f28
8 changed files with 261 additions and 81 deletions

View File

@ -5,55 +5,55 @@
Models can be constructed in what ever way you like, all of the mapping logic is defined in the data mapper itself. However, it is recommended to provide the following member variables if applicable (names can be different):
```php
private $id = 0;
private $createdAt = null;
private $createdBy = null;
private int $id = 0;
private \DateTime $createdAt;
private Account $createdBy;
```
The `$id` can be used as primary key. For this member variable no setter method should be present. For the `$createdAt` as well as the `$createdBy` member variables both getter and setter methods are possible. It's also possible to make these variables immutable since both should be known during the initialization point of a new model.
## DataMapper
The data mapper itself is where all the magic happens, from inserts, updates, to selects etc.
The data mapper itself is where all the magic happens, from inserts, updates, to selects etc. In reality the data mappers are factories which create/populate the data mappers but since this is only internal behavior we will continue to call them mappers.
### Primary key
The primary key can be indicated with the variable `$primaryField`. This variable should contain the string representation of the database field name. This variable is compulsory.
The primary key can be indicated with the constant `public const PRIMARYFIELD`. This constant should contain the string representation of the database field name. This constant is mandatory.
### Created at
While it is possible to log user, module and database activities thorugh the logging module it is often necessary to know when a certain entry got created. For that purpose the `$createdAt` can be used to define the string representation of the database field name which contains the date of the insert.
While it is possible to log user, module and database activities through the logging module it is often necessary to know when a certain entry got created. For that purpose the `public const CREATED_AT` can be used to define the string representation of the database field name which contains the date of the insert.
### Created by
### Autoincrement
In a similar fashion as the `$createdAt` variable often it is also necessary to have a field containing the id of the account creating the entry. The varibale `$createdBy` has to contain the string representation of that database field name.
In some cases it is necessary to define a primary key as not autoincrement. This messes with some parts of the write mapper. To let the mapper know that the model can have a user defined primary key you must set `public const AUTOINCREMENT = false`. By default it is `true`.
### Table
One model can only be stored in one table. With the `$table` variable it's possible to specify the table name. This variable is compulsory. It's important to note that by extending a model you also need to implement a data mapper that can access multiple tables. In that case it's also necessary to extend the data mapper of the extended module.
One model can only be stored in one table. With the `public const TABLE` constant it's possible to specify the table name. This constant is mandatory. It's important to note that by extending a model you also need to implement a data mapper that can access multiple tables. In that case it's also necessary to extend the data mapper of the extended module.
### Model
The `$model` variable can be optionally used in order to specify the model this mapper is supposed to populate. By default the mapper will try to find a model in the same directory as the mapper without the `Mapper` suffix in its name.
The `public const MODEL` constant can be optionally used in order to specify the model this mapper is supposed to populate. By default the mapper will try to find a model in the same directory as the mapper without the `Mapper` suffix in its name.
Default behavior example:
* Mapper name: `\test\path\TestMapper`
* Default maodel: `\test\path\Test`
* Default model: `\test\path\Test`
If the model is defined somewhere else or has a different name, the `$model` variable name should be used to define the correct model. E.g.
If the model is defined somewhere else or has a different name, the `public const MODEL` constant name should be used to define the correct model. E.g.
```php
protected static string $model = OtherModel::class;
public const MODEL = OtherModel::class;
```
### Columns
In the `$columns` array all columns, respective model variables and data types need to be specified.
In the `public const COLUMNS` array all columns, respective model constant and data types need to be specified.
```php
protected static array $columns = [
'db_field_name_1' => ['name' => 'db_field_name_1', 'type' => 'int', 'internal' => 'model_var_name_1'],
public const COLUMNS = [
'db_field_name_1' => ['name' => 'db_field_name_1', 'type' => 'int', 'internal' => 'model_var_name_1'],
'db_field_name_2' => ['name' => 'db_field_name_2', 'type' => 'string', 'internal' => 'model_var_name_2'],
];
```
@ -65,8 +65,8 @@ The `name` contains the field name in the database, the `type` represents the da
In order to make columns searchable you have to add `'autocomplete' => true` as column information to the respective column.
```php
protected static array $columns = [
'db_field_name_1' => ['name' => 'db_field_name_1', 'type' => 'int', 'internal' => 'model_var_name_1', 'autocomplete' => true],
public COLUMNS = [
'db_field_name_1' => ['name' => 'db_field_name_1', 'type' => 'int', 'internal' => 'model_var_name_1', 'autocomplete' => true],
'db_field_name_2' => ['name' => 'db_field_name_2', 'type' => 'string', 'internal' => 'model_var_name_2'],
];
```
@ -80,20 +80,21 @@ Possible types are:
* bool
* float
* DateTime
* DateTimeImmutable
* Serializable (will call `serialize()`)
* Json (will call `jsonSerialize()`)
### Has many
With the `$hasMany` variable it's possible to specify other models that belong to this model.
With the `public const HAS_MANY` constant it's possible to specify other models that belong to this model.
```php
protected static array $hasMany = [
public const HAS_MANY = [
'model_var_name_3' => [
'mapper' => HasManyMapper::class,
'table' => 'relation_table_name',
'dst' => 'relation_destination_name',
'src' => 'relation_source_name',
'mapper' => HasManyMapper::class,
'table' => 'relation_table_name',
'dst' => 'relation_destination_name',
'src' => 'relation_source_name',
],
];
```
@ -105,12 +106,12 @@ The `mapper` contains the class name of the mapper responsible for the many mode
A many to one or one to one relation would look like the following:
```php
protected static array $hasMany = [
public const HAS_MANY = [
'model_var_name_3' => [
'mapper' => HasManyMapper::class,
'table' => null,
'dst' => 'relation_destination_name',
'src' => null,
'mapper' => HasManyMapper::class,
'table' => null,
'dst' => 'relation_destination_name',
'src' => null,
],
];
```
@ -120,12 +121,12 @@ protected static array $hasMany = [
A many to many relation which can only be defined in a relation table looks like the following:
```php
protected static array $hasMany = [
public const HAS_MANY = [
'model_var_name_3' => [
'mapper' => HasManyMapper::class,
'table' => 'relation_table_name',
'dst' => 'relation_destination_name',
'src' => 'relation_source_name',
'mapper' => HasManyMapper::class,
'table' => 'relation_table_name',
'dst' => 'relation_destination_name',
'src' => 'relation_source_name',
],
];
```
@ -135,14 +136,14 @@ protected static array $hasMany = [
By defining a `column` it's also possible to only populate the model with a single column/field value from another table or model.
```php
protected static array $hasMany = [
public const HAS_MANY = [
'my_title' => [
'mapper' => L11nTagMapper::class,
'table' => 'tag_l11n',
'external' => 'tag_l11n_tag',
'column' => 'title',
'conditional' => true,
'self' => null,
'mapper' => L11nTagMapper::class,
'table' => 'tag_l11n',
'external' => 'tag_l11n_tag',
'column' => 'title',
'conditional' => true,
'self' => null,
],
];
```
@ -151,10 +152,10 @@ In the example above the model member variable `my_title` will be populated with
### Owns one
It's possible to also define a relation in the source module itself. This can be accomplished by using the `$ownsOne` variable. In this case the model itself has to specify a field where the primary key of the source model is defined.
It's possible to also define a relation in the source module itself. This can be accomplished by using the `public const OWNS_ONE` constant. In this case the model itself has to specify a field where the primary key of the source model is defined.
```php
protected static array $ownsOne = [
public const OWNS_ONE = [
'model_var_name_4' => [
'mapper' => OwnsOneMapper::class,
'src' => 'relation_dest_name',
@ -166,10 +167,10 @@ The `mapper` field contains the class name of the mapper of the source model. Th
### Belongs to
The reverse of a has one is a belongs to. This allows to also load models that a specific model belongs to. This can be accomplished by using the `$belongsTo` variable. In this case the model itself has to specify a field where the primary key of the source model is defined.
The reverse of a has one is a belongs to. This allows to also load models that a specific model belongs to. This can be accomplished by using the `public const BELONGS_TO` constant. In this case the model itself has to specify a field where the primary key of the source model is defined.
```php
protected static array $belongsTo = [
public const BELONGS_TO = [
'model_var_name_6' => [
'mapper' => BelongsToMapper::class,
'dest' => 'relation_destination_name',
@ -179,18 +180,196 @@ protected static array $belongsTo = [
The `mapper` field contains the class name of the mapper of the destination model. The `dest` field contains the database field name where the primary key is stored that this model belongs to.
## Conditionals
## Mapper interaction
Conditionals provide a general way to filter the desired result of models. You can think about conditionals as SQL `WHERE` clauses. A conditional uses the model member variable name instead of the database table name.
The mapper interaction is similar to the query builder interaction.
### Reader
The read mapper allows you to read/select models from the database. The two most common use cases are `get()` and `getAll()`. The `get()` interaction returns a single model or an array of models if multiple models are found who fit the search criteria. The `getAll()` interaction always returns an array of models.
Example use case:
```php
WithConditionalMapper::withConditional('language', 'en')::getAll();
$models = BaseModelMapper::get()->with(...)->with(...)->where(...)->sort(...)->limit(...)->execute();
```
This query returns all models of the `WithConditionalMapper` which have a member variable with the value `en`. In the background the mapper does a reverse lookup, checks the column name which is associated with the `language` member variable and filters the database result accordingly.
##### Basic get
By default conditionals are recursive and get applied to all models which are somehow referenced in the request (e.g. has many models which have also a `language` member variable). In some cases this is undesired and the user wants to specify the models where the conditionals should be applied to. This can be achieved in the following way:
All interactions with the mappers are based on the property names of the models instead of the field names. The most simple read request would be to retrieve a model based on a property name (e.g. '$id'):
```php
WithConditionalMapper::withConditional('language', 'en', [FirstModelToApplyTo::class, SecondModelToApplyTo::class, ...])::getAll();
```
$model = BaseModelMapper::get()->where('id', 12)->execute();
```
> Remark: By default no relations (e.g. has many, belongs to, owns one) are loaded. Make sure to use the `with()` function!
###### Writeonly
Write only properties are not loaded / remain empty. In order to force a load you may use the `with()` function.
##### Multiple filters
Of course if no primary key is known you may also select by other properties or by multiple properties:
```php
$model = BaseModelMapper::get()->where('property1', 'firstValue')->where('property2', 'altValue', '=', 'or')->execute();
```
##### Limit result set
In order to limit the result set you may use the limit function:
```php
$models = BaseModelMapper::getAll()->where('property1', 'someValue')->limit(5)->execute();
```
##### Sort result set (and limit)
If the result order is important you can also define a order (e.g. get the newest 3 elements):
```php
$models = BaseModelMapper::getAll()->where('property1', 'someValue')->sort('id', OrderType::DESC)->limit(3)->execute();
```
##### Load relations (has many, belongs to, owns one)
By default no relations are loaded. This results in faster loading times and much more control over what data should be loaded. At the same time this requires more attention when using the data mappers. If relations aren't explicitly loaded they are initialized as null models in case of belongs to and owns one relationships. Has many relations result in empty arrays.
In order to load relations you must use the `with()` function:
```php
$model = BaseModelMapper::get()->with('ownsOnePropertyName')->where('id', 12)->execute();
```
It is normal for models to have sub-relations. Such models can be loaded by defining a path e.g.:
```php
$model = BaseModelMapper::get()
->with('ownsOnePropertyName')
->with('ownsOnePropertyName/childHasManyPropertyName')
->where('id', 12)
->execute();
```
Of course it's also possible to filter and limit these relations e.g.:
```php
$model = BaseModelMapper::get()
->with('ownsOnePropertyName')
->with('ownsOnePropertyName/childHasManyPropertyName')
->where('id', 12)
->where('ownsOnePropertyName/childHasManyPropertyName/someIntegerPropertyOfChild', 99, '<=')
->limit(4, 'ownsOnePropertyName/childHasManyPropertyName')
->execute();
```
#### Merge queries
In some cases you maybe want to provide a base query which the mapper should build upon. This base query is merged/extended in the mapper e.g.:
```php
$baseQuery = new Builder($con);
$baseQuery->innerJoin(...)->on(...)->where(...);
$model = BaseModelMapper::get()->query($baseQuery)->where('property1', 'test')->execute();
// since the $baseQuery is merged with the internal query generation you can use ->where(), ->limit(), etc. and they will be applied to the $baseQuery
```
This allows you to create better filtering than what is possible with the rudimentary where implementation of the mappers.
#### Overwrite query
Sometimes it may be necessary to ignore the internal query building process and create a model based on a custom query. Note that this function call DOESN'T merge the query which means you need to be very careful how you construct it so that it still correctly works with the mapper. One solution could be to first get the query built by the mapper and than modify that query e.g.:
```php
$query = BaseModelMapper::getQuery();
$query->innerJoin(...)->on(...)->where(...);
$model = BaseModelMapper::get()->execute($query);
// If you would specify a ->limit() here this would be ignored because it only uses the $query
```
#### Custom columns to load
In few cases you may only want to load specific columns only. This can be achieved with `columns(...)`. If the columns are defined only these columns are loaded e.g.:
```php
$model = BaseModelMapper::get()->columns(['table_id' => 'table_id_d1'])->where('id', 12)->execute();
```
> Remark: `columns()` actually requires column information, NOT the property name!
### Writer
The writer allows you to create a model in the database.
```php
$model = new BaseModel();
BaseModelMapper::create()->execute($model);
```
The writer goes through all relations and checks if they are already created in the database. If relations (e.g. has many, ...) are not created, the writer automatically creates them as well.
### Updater
The updater allows you to update a model in the database.
##### Basic update
The default update function works similar to the create function with the exception that relations (e.g. has many, belongs to, ...) are NOT automatically updated.
```php
$model = BaseModelMapper::get()->where('id', 12)->execute();
...
BaseModelMapper::update()->execute($model);
```
##### Update readonly/writeonly
Properties marked as either `readonly` or `writeonly` are not updated by default, use `with()` to force a different behavior.
###### Readonly
Readonly properties are supposed to not change. However, in order to force an overwrite you may use the `with(...)` function to do so.
###### Writeonly
Writeonly properties are not loaded during the `get()` call. Therefore they should also not get updated/overwritten with an empty value. However, in order to force an overwrite you may use the `with(...)` function to do so.
##### Update relations
In order to update relations you must specify that with `with()` e.g.:
```php
$model = BaseModelMapper::get()->with('ownsOnePropertyName')->where('id', 12)->execute();
...
BaseModelMapper::update()->with('ownsOnePropertyName')->execute($model);
```
This lets you specify very detailed what should get updated and what shouldn't get updated. Of course it also means you need to be careful when calling the updater.
### Remover
The updater allows you to delete a model from the database.
##### Basic delete
The default delete function deletes the model itself.
```php
$model = BaseModelMapper::get()->where('id', 12)->execute();
...
BaseModelMapper::delete()->execute($model);
```
The delete function doesn't delete owns one and belongs to relations. However, has many relations are handled a little bit more complicated.
###### Has many relations with relation table
If a has many relationship is defined in a relation table, then the relation is deleted from the relation table. However, the related module is not deleted as it might also be related to other models.
###### Has many relations with relations defined in the related model (not in a relation table)
If a relation is defined in the related model, than this model is also delete. The reason for this is that it cannot remain while the "parent" model is deleted (the foreign key would fail).

View File

View File

@ -10,7 +10,7 @@ Make sure your dev-environment or server fulfills the following requirements:
* PHP extensions: mbstring, gd, zip, dom, mysql/pgsql/sqlsrv, sqlite, bcmath, imap\*, redis\*, memcached\*, ftp\*, socket\*, curl\*, xml\*
* databases: mysql, postgresql, sqlsrv
* webserver: apache2
* mod_headers
* mod_headers (apache2)
The application and frameworks can use different databases. For the normal development process you only need one (whichever you prefer). However, in order to test against all supported databases and all code paths you would have to install all above mentioned databases.
@ -20,7 +20,7 @@ Steps which are not explained in this documentation are how to install and setup
### Installation Options
1. Option 1: Full installation, code checks/tests, generating documentation. **Not recomended for quick setup**
1. Option 1: Full installation, code checks/tests, generating documentation. **Not recomended as quick setup**
2. Option 2: Only installs the application with some tests. Requires you to install the dev tools manually. **Recommended**
3. Option 3: Only installs the application, due to the large amount of data takes some time to execute. **Recommended**

View File

@ -111,4 +111,4 @@ For developers it is recommended to copy the contents of the `default.sh` file i
The same should be done with every module. Simply go to `.git/modules/**/hooks` and also add the content of the `default.sh` file to all `pre-commit` files.
By doing this every commit will be inspected and either pass without warnings, pass with warnings or stop with errors. This will allow you to fix code before committing it. Be aware only changed files will be inspected. Also make sure all `pre-commit` have `+x` permissions.
By doing this every commit will be inspected and either pass without warnings, pass with warnings or stop with errors. This will allow you to fix code before committing it. Be aware only changed files will be inspected. Also make sure all `pre-commit` files have `+x` permissions.

View File

View File

@ -102,12 +102,12 @@ In some cases superglobals will even be overwritten by values from these classes
## Input validation
Input validation be implemented on multiple levels.
Input validation can be implemented on multiple levels.
1. Regex validation in html/javascript by using the `pattern=""` attribute
2. Type hints for method parameters wherever possible.
3. Making use of the `Validation` classes as much as possible
4. **Don't** sanitize! Accept or dismiss.
4. **Don't** sanitize! Accept or dismiss!
## Inclusion and file paths

View File

@ -172,3 +172,25 @@ In some cases it may be required to type hint a variable in this case the follow
```php
/** @var variable_type varName {optional_description}
```
## Todos
Todos should be documented in the [PROJECT.md](https://github.com/Orange-Management/Docs/blob/master/Project/PROJECT.md).
In code todos can be created like this
```php
// @todo: Single line todo
```
```php
/**
* @todo: Multi line todo
* This way developers can see todos directly in the code without going to an external source.
* Todos must not have empty lines in their descriptions.
* If the external ressources have empty lines they must be removed in the todo comment.
* 1. list item 1
* 2. list item 2
*/
```

View File

@ -150,10 +150,10 @@ Switch statements must have a `default` case.
### Constants
Constants must be written with capital letters and snake case.
Constants must have a visibility defined if possible (private, public, protected) and must be written with capital letters and snake case.
```js
CONSTANT_TEST = true;
```php
public CONSTANT_TEST = true;
```
### Function
@ -196,24 +196,3 @@ All string representations should use single quotes `''` unless `""` provides si
'This is a string'
```
## Todos
Todos should be documented in the [PROJECT.md](https://github.com/Orange-Management/Docs/blob/master/Project/PROJECT.md).
In code todos can be created like this
```php
// @todo: Single line todo
```
```php
/**
* @todo: Multi line todo
* This way developers can see todos directly in the code without going to an external source.
* Todos must not have empty lines in their descriptions.
* If the external ressources have empty lines they must be removed in the todo comment.
* 1. list item 1
* 2. list item 2
*/
```