Skip to content

Commit

Permalink
Merge pull request #22 from byjg/softdelete
Browse files Browse the repository at this point in the history
Add Soft Delete and Others
  • Loading branch information
byjg authored Nov 11, 2024
2 parents 19db0f9 + dbfbe23 commit 9d7ec9c
Show file tree
Hide file tree
Showing 32 changed files with 1,398 additions and 124 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,9 @@ $result = $repository->getByQuery($query);

## Advanced Topics

* [Active Record](docs/active-record.md)
* [The Literal Object](docs/the-literal-object.md)
* [Soft Delete](docs/softdelete.md)
* [Caching the Results](docs/cache.md)
* [Observing the Database](docs/observers.md)
* [Controlling the data queried/updated](docs/controlling-the-data.md)
Expand Down
90 changes: 90 additions & 0 deletions docs/active-record.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Active Record

Active Record is the M in MVC - the model - which is the layer of the system responsible
for representing business data and logic. Active Record facilitates the creation and use of
business objects whose data requires persistent storage to a database.
It is an implementation of the Active Record pattern which itself is a description of an
Object Relational Mapping system.

## How to use Active Record

- Create a model and add the annotations to the class and properties. (see [Model](model.md))
- Add the `ActiveRecord` trait to your model.
- Initialize the Active Record with the `initialize` static method (need to do only once)

e.g.:

```php
<?php
[TableAttribute('my_table')]
class MyClass
{
// Add the ActiveRecord trait to enable the Active Record
use ActiveRecord;

#[FieldAttribute(primaryKey: true)]
public ?int $id;

#[FieldAttribute(fieldName: "some_property")]
public ?int $someProperty;
}

// Initialize the Active Record
MyClass::initialize($dbDriver);
```

## Using the Active Record

Once is properly configured you can use the Active Record to save, update, delete and retrieve the data.

### Insert a new record

```php
<?php
// Create a new instance
$myClass = MyClass::new();
$myClass->someProperty = 123;
$myClass->save();

// Another example Create a new instance
$myClass = MyClass::new(['someProperty' => 123]);
$myClass->save();
```

### Retrieve a record

```php
<?php
$myClass = MyClass::get(1);
$myClass->someProperty = 456;
$myClass->save();
```

### Complex Filter

```php
<?php
$myClassList = MyClass::filter((new IteratorFilter())->and('someProperty', Relation::EQUAL, 123));
foreach ($myClassList as $myClass) {
echo $myClass->someProperty;
}
```

### Delete a record

```php
<?php
$myClass = MyClass::get(1);
$myClass->delete();
```

### Using the `Query` class

```php
<?php
$query = MyClass::joinWith('other_table');
// do some query here
// ...
// Execute the query
$myClassList = MyClass::query($query);
```
58 changes: 58 additions & 0 deletions docs/auto-discovering-relationship.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Auto Discovering Relationship

The `FieldAttribute` has a parameter `parentTable` that is used to define the parent table of the field. This is used in
the case of a foreign key.

Once this attribute is set, the `Repository` can auto-discover the relationship between the tables
and generate the proper SQL with the relationship to retrieve the data.

## How to use

You can use the `parentTable` parameter in the `FieldAttribute` to define the parent table of the field.

```php
<?php
#[TableAttribute('table1')]
class Class1
{
#[FieldAttribute(primaryKey: true)]
public ?int $id;
}

#[TableAttribute('table2')]
class Class2
{
#[FieldAttribute(primaryKey: true)]
public ?int $id;

#[FieldAttribute(fieldName: "id_table1", parentTable: "table1")]
public ?int $idTable1;
}

$repository1 = new Repository($dbDriver, Class1::class);
$repository2 = new Repository($dbDriver, Class2::class);
```

This will automatically create the relationship between `table1` and `table2`.

To generate the SQL with the relationship you can use the `ORM` static class:

```php
<?php
$query = ORM::getQueryInstance("table1", "table2");
```

The command above will return a query object similar to:

```php
<?php
$query = Query::getInstance()
->table('table1')
->join('table2', 'table2.id_table1 = table1.id');
```

## Limitations

- This feature does not support multiple relationships between the same tables
- Primary keys with two or more fields are not supported.

11 changes: 10 additions & 1 deletion docs/cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,24 @@ $query = Query::getInstance()
$cacheEngine = /* any SimpleCache implementation */;

// Get the result and save to cache
$result = $repository->getByQuery($query, cache: new CacheQueryResult($cacheEngine, 120));
$result = $repository->getByQuery($query, cache: new CacheQueryResult($cacheEngine, 'key', 120));
```

In this example, the result of the query will be saved in the cache for 120 seconds.

The `CacheQueryResult` object has the following parameters:

- `cacheEngine`: The cache engine to use. It must implement the `Psr\SimpleCache\CacheInterface` interface.
- `key`: The key to use to save the cache. It must be unique for each query. See the notes below.
- `ttl`: The time-to-live of the cache in seconds. If the cache is older than this value, it will be considered expired.

**NOTES**
The key must be unique for each query. If you use the same key for different queries, it will not differentiate one from
another,
and you can get unexpected results.

If the query has parameters, the key will be concatenated with the hash of the parameters.




45 changes: 38 additions & 7 deletions docs/controlling-the-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ You can control the data queried or updated by the micro-orm using the Mapper ob
Let's say you want to store the phone number only with numbers in the database,
but in the entity class, you want to store with the mask.

You can add the `withUpdateFunction` and `withSelectFunction` to the FieldMap object
You can add the `withUpdateFunction`, `withInsertFunction` and `withSelectFunction` to the FieldMap object
as you can see below:


Expand All @@ -28,19 +28,47 @@ $fieldMap = FieldMap::create('propertyname') // The property name of the entity
$mapper->addFieldMapping($fieldMap);
```

You can also pass any callable function to the `withUpdateFunction`, `withInsertFunction` and `withSelectFunction`.

## Pre-defined closures for field map
e.g.:

### Mapper::defaultClosure($value, $instance)
- `withUpdateFunction('myFunction')`
- `withUpdateFunction([$myObject, 'myMethod'])`
- `withUpdateFunction('MyClass::myStaticMethod')`
- `withUpdateFunction(function ($field, $instance) { return $field; })`
- `withUpdateFunction(fn($field, $instance) => $field)`
- `withUpdateFunction(MapperFunctions::NOW_UTC)` (pre-defined functions, see below)

Defines the basic behavior for select and update fields; You don't need to set it. Just know it exists.
## Arguments passed to the functions

### Mapper::doNotUpdateClosure($value, $instance)
The functions will receive three arguments:

Defines a read-only field. It can be retrieved from the database but will not be updated.
- `$field`: The value of the field in the entity class;
- `$instance`: The instance of the entity class;
- `$dbHelper`: The instance of the DbHelper class. You can use it to convert the value to the database format.

## Pre-defined functions

## Before insert and update functions
| Function | Description |
|---------------------------------------|------------------------------------------------------------------------------------------------------------|
| `MapperFunctions::STANDARD` | Defines the basic behavior for select and update fields; You don't need to set it. Just know it exists. |
| `MapperFunctions::READ_ONLY` | Defines a read-only field. It can be retrieved from the database but will not be updated. |
| `MapperFunctions::NOW_UTC` | Returns the current date/time in UTC. It is used to set the current date/time in the database. |
| `MapperFunctions::UPDATE_BINARY_UUID` | Converts a UUID string to a binary representation. It is used to store UUID in a binary field. |
| `MapperFunctions::SELECT_BINARY_UUID` | Converts a binary representation of a UUID to a string. It is used to retrieve a UUID from a binary field. |

You can use them in the FieldAttribute as well:

```php
<?php
#[FieldAttribute(
fieldName: 'uuid',
updateFunction: MapperFunctions::UPDATE_BINARY_UUID,
selectFunction: MapperFunctions::SELECT_BINARY_UUID)
]
```

## Generic function to be processed before any insert or update

You can also set closure to be applied before insert or update at the record level and not only in the field level.
In this case will set in the Repository:
Expand All @@ -55,3 +83,6 @@ Repository::setBeforeUpdate(function ($instance) {
return $instance;
});
```

The return of the function will be the instance that will be used to insert or update the record.

56 changes: 17 additions & 39 deletions docs/model.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,49 +78,27 @@ The `FieldAttribute` can be used in the following properties:

The `TableAttribute` has the following parameters:

* `tableName`: The name of the table in the database.
* `primaryKeySeedFunction` (optional): A function that returns the seed for the primary key. The function must return a value.
| Field | Description | Required |
|----------------------------|-----------------------------------------------------------------------------------------|:--------:|
| **tableName** | The name of the table in the database. | Yes |
| **primaryKeySeedFunction** | A function that returns the seed for the primary key. The function must return a value. | No |

## Field Attributes parameters

The `FieldAttribute` has the following parameters:

* primaryKey (optional): If the field is a primary key.
* fieldName (optional): The name of the field in the database. If not set, the field name is the same as the property name.
* fieldAlias (optional): The alias of the field in the database. If not set, the field alias is the same as the property name.
* syncWithDb (optional): If the field should be synchronized with the database. Default is true.
* updateFunction (optional): A function that is called when the field is updated. The function must return a value.
* selectFunction (optional): A function that is called when the field is selected. The function must return a value.

```tip
To use a function as a parameter, you must inherit from the `FieldAttribute` and
in the constructor call the parent with the function.
```

## Special Field Attributes: FieldReadOnlyAttribute

* It is used to define a field that is read-only.
* It sets the MapperClosure::readonly() method to the updateFunction.

## Closure Function Signature

### primaryKeySeedFunction:

```php
function (object $instance) {
// $instance is the instance of the model with the properties set
return mixed;
}
```

### selectFunction and updateFunction:

```php
function (mixed $fieldValue, mixed $data) {
// $value is the value to be set in the property
// $data is an array with the value properties set
return $fieldValue;
}
```
| Field | Description | Required |
|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|:--------:|
| **primaryKey** | If the field is a primary key. It is required at least one field to be a PK. It is used for insert/updates/deletes. | No |
| **fieldName** | The name of the field in the database. If not set, the field name is the same as the property name. | No |
| **fieldAlias** | The alias of the field in the database. If not set, the field alias is the same as the field name. | No |
| **syncWithDb** | If the field should be synchronized with the database. Default is true. | No |
| **updateFunction** | A function that is called when the field is updated. The function must return a value. | No |
| **selectFunction** | A function that is called when the field is selected. The function must return a value. | No |
| **insertFunction** | A function that is called when the field is inserted. The function must return a value. | No |
| **parentTable** | The parent table of the field. This is used in the case of a foreign key. See [Auto Discovering Relationship](auto-discovering-relationship.md). | No |

See also [Controlling the Data](controlling-the-data.md) for more information about the `updateFunction`,
`selectFunction`, and `insertFunction`.


Loading

0 comments on commit 9d7ec9c

Please sign in to comment.