Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ActiveRecord::reset() method #23

Merged
merged 15 commits into from
Dec 17, 2024
33 changes: 33 additions & 0 deletions docs/active-record.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ class MyClass
MyClass::initialize($dbDriver);
```

After the class is initialized you can use the Active Record to save, update, delete and retrieve the data.
If you call the `initialize` method more than once, it won't have any effect, unless you call the method `reset`.

It is possible define a Default DBDriver for all classes using the Active Record.

```php
<?php
// Set a default DBDriver
ORM::defaultDriver($dbDriver);

// Initialize the Active Record
MyClass::initialize()
```

## Using the Active Record

Once is properly configured you can use the Active Record to save, update, delete and retrieve the data.
Expand Down Expand Up @@ -78,6 +92,25 @@ $myClass = MyClass::get(1);
$myClass->delete();
```

### Refresh a record

```php
<?php
// Retrieve a record
$myClass = MyClass::get(1);

// do some changes in the database
// **OR**
// expect that the record in the database was changed by another process

// Get the updated data from the database
$myClass->refresh();
```

### Update a model from another model or array

```php

### Using the `Query` class

```php
Expand Down
23 changes: 22 additions & 1 deletion src/ORM.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

namespace ByJG\MicroOrm;

use InvalidArgumentException;
use ByJG\AnyDataset\Db\DbDriverInterface;
use ByJG\MicroOrm\Exception\InvalidArgumentException;

class ORM
{
private static ?DbDriverInterface $dbDriver = null;
private static array $relationships = [];

/**
Expand Down Expand Up @@ -176,6 +178,25 @@ public static function clearRelationships(): void
{
static::$relationships = [];
static::$incompleteRelationships = [];
foreach (static::$mapper as $mapper) {
// Reset the ActiveRecord DbDriver
if (method_exists($mapper->getEntity(), 'reset')) {
call_user_func([$mapper->getEntity(), 'reset']);
}
}
static::$mapper = [];
}

public static function defaultDbDriver(?DbDriverInterface $dbDriver = null): DbDriverInterface
{
if (is_null($dbDriver)) {
if (is_null(static::$dbDriver)) {
throw new InvalidArgumentException("You must initialize the ORM with a DbDriverInterface");
}
return static::$dbDriver;
}

static::$dbDriver = $dbDriver;
return $dbDriver;
}
}
13 changes: 9 additions & 4 deletions src/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -211,21 +211,26 @@ public function deleteByQuery(DeleteQuery $updatable): bool
* @param bool $forUpdate
* @return array
*/
public function getByFilter(string|IteratorFilter $filter, array $params = [], bool $forUpdate = false): array
public function getByFilter(string|IteratorFilter $filter = "", array $params = [], bool $forUpdate = false, int $page = 0, ?int $limit = null): array
{
if ($filter instanceof IteratorFilter) {
$formatter = new IteratorFilterSqlFormatter();
$filter = $formatter->getFilter($filter->getRawFilters(), $params);
}


$query = $this->getMapper()->getQuery()
->where($filter, $params);
$query = $this->getMapper()->getQuery();
if (!empty($filter)) {
$query->where($filter, $params);
}

if ($forUpdate) {
$query->forUpdate();
}

if (!is_null($limit)) {
$query->limit($page, ($page + 1) * $limit);
}

return $this->getByQuery($query);
}

Expand Down
88 changes: 80 additions & 8 deletions src/Trait/ActiveRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,77 +4,149 @@

use ByJG\AnyDataset\Core\IteratorFilter;
use ByJG\AnyDataset\Db\DbDriverInterface;
use ByJG\MicroOrm\Exception\OrmInvalidFieldsException;
use ByJG\MicroOrm\Mapper;
use ByJG\MicroOrm\ORM;
use ByJG\MicroOrm\Query;
use ByJG\MicroOrm\Repository;
use ByJG\Serializer\ObjectCopy;
use ByJG\Serializer\Serialize;

trait ActiveRecord
{
protected static ?DbDriverInterface $dbDriver = null;

protected static ?Repository $repository = null;

public static function initialize(DbDriverInterface $dbDriver)
public static function initialize(?DbDriverInterface $dbDriver = null)
{
if (!is_null(self::$dbDriver)) {
return;
}

if (is_null($dbDriver)) {
$dbDriver = ORM::defaultDbDriver();
}

self::$dbDriver = $dbDriver;
self::$repository = new Repository($dbDriver, self::discoverClass());
}

self::$repository = new Repository($dbDriver, static::class);
public static function reset(?DbDriverInterface $dbDriver = null)
{
self::$dbDriver = null;
self::$repository = null;
if (!is_null($dbDriver)) {
self::initialize($dbDriver);
}
}

public static function tableName(): string
{
self::initialize();
return self::$repository->getMapper()->getTable();
}

public function save()
{
self::initialize();
self::$repository->save($this);
}

public function delete()
protected function pkList(): array
{
self::initialize();
$pk = self::$repository->getMapper()->getPrimaryKeyModel();

$filter = [];
foreach ($pk as $field) {
$pkValue = $this->{$field};
if (empty($pkValue)) {
throw new OrmInvalidFieldsException("Primary key '$field' is null");
}
$filter[] = $this->{$field};
}

self::$repository->delete($filter);
return $filter;
}

public static function new(array $data): static
public function delete()
{
self::$repository->delete($this->pkList());
}

public static function new(mixed $data = null): static
{
return self::$repository->entity($data);
self::initialize();
$data = $data ?? [];
return self::$repository->entity(Serialize::from($data)->toArray());
}

public static function get(mixed ...$pk)
{
self::initialize();
return self::$repository->get(...$pk);
}

public function fill(mixed $data)
{
$newData = self::new($data)->toArray();
ObjectCopy::copy($newData, $this);
}

public function refresh()
{
$this->fill(self::$repository->get(...$this->pkList()));
}

/**
* @param IteratorFilter $filter
* @param int $page
* @param int $limit
* @return static[]
*/
public static function filter(IteratorFilter $filter): array
public static function filter(IteratorFilter $filter, int $page = 0, int $limit = 50): array
{
return self::$repository->getByFilter($filter);
self::initialize();
return self::$repository->getByFilter($filter, page: $page, limit: $limit);
}

public static function all(int $page = 0, int $limit = 50): array
{
self::initialize();
return self::$repository->getByFilter(page: $page, limit: $limit);
}

public static function joinWith(string ...$tables): Query
{
self::initialize();
$tables[] = self::$repository->getMapper()->getTable();
return ORM::getQueryInstance(...$tables);
}

public function toArray(bool $includeNullValue = false): array
{
if ($includeNullValue) {
return Serialize::from($this)->toArray();
}

return Serialize::from($this)->withDoNotParseNullValues()->toArray();
}

/**
* @param Query $query
* @return static[]
*/
public static function query(Query $query): array
{
self::initialize();
return self::$repository->getByQuery($query);
}

// Override this method to create a custom mapper instead of discovering by attributes in the class
protected static function discoverClass(): string|Mapper
{
return static::class;
}

}
47 changes: 41 additions & 6 deletions src/UpdateQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class UpdateQuery extends Updatable
{
protected array $set = [];

protected array $joinTables = [];

/**
* @throws InvalidArgumentException
*/
Expand Down Expand Up @@ -49,6 +51,24 @@ public function set(string $field, int|float|bool|string|LiteralInterface|null $
return $this;
}

protected function getJoinTables(DbFunctionsInterface $dbHelper = null): array
{
if (is_null($dbHelper)) {
if (!empty($this->joinTables)) {
throw new InvalidArgumentException('You must specify a DbFunctionsInterface to use join tables');
}
return ['sql' => '', 'position' => 'before_set'];
}

return $dbHelper->getJoinTablesUpdate($this->joinTables);
}

public function join(string $table, string $joinCondition): UpdateQuery
{
$this->joinTables[] = ["table" => $table, "condition" => $joinCondition];
return $this;
}

/**
* @param DbFunctionsInterface|null $dbHelper
* @return SqlObject
Expand All @@ -63,12 +83,17 @@ public function build(DbFunctionsInterface $dbHelper = null): SqlObject
$fieldsStr = [];
$params = [];
foreach ($this->set as $field => $value) {
$fieldName = $field;
$fieldName = explode('.', $field);
$paramName = preg_replace('/[^A-Za-z0-9_]/', '', $fieldName[count($fieldName) - 1]);
if (!is_null($dbHelper)) {
$fieldName = $dbHelper->delimiterField($fieldName);
foreach ($fieldName as $key => $item) {
$fieldName[$key] = $dbHelper->delimiterField($item);
}
}
$fieldsStr[] = "$fieldName = :$field ";
$params[$field] = $value;
/** @psalm-suppress InvalidArgument $fieldName */
$fieldName = implode('.', $fieldName);
$fieldsStr[] = "$fieldName = :{$paramName} ";
$params[$paramName] = $value;
}

$whereStr = $this->getWhere();
Expand All @@ -81,8 +106,14 @@ public function build(DbFunctionsInterface $dbHelper = null): SqlObject
$tableName = $dbHelper->delimiterTable($tableName);
}

$sql = 'UPDATE ' . $tableName . ' SET '
. implode(', ', $fieldsStr)
$joinTables = $this->getJoinTables($dbHelper);
$joinBeforeSet = $joinTables['position'] === 'before_set' ? $joinTables['sql'] : '';
$joinAfterSet = $joinTables['position'] === 'after_set' ? $joinTables['sql'] : '';

$sql = 'UPDATE ' . $tableName
. $joinBeforeSet
. ' SET ' . implode(', ', $fieldsStr)
. $joinAfterSet
. ' WHERE ' . $whereStr[0];

$params = array_merge($params, $whereStr[1]);
Expand All @@ -100,6 +131,10 @@ public function convert(?DbFunctionsInterface $dbDriver = null): QueryBuilderInt
$query->where($item['filter'], $item['params']);
}

foreach ($this->joinTables as $joinTable) {
$query->join($joinTable['table'], $joinTable['condition']);
}

return $query;
}
}
11 changes: 8 additions & 3 deletions src/WhereTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,14 @@ protected function getWhere(): ?array
/** @psalm-suppress RedundantCondition This is a Trait, and $this->join is defined elsewhere */
if (isset($this->join)) {
foreach ($this->join as $item) {
if (!($item['table'] instanceof QueryBasic) && !in_array($item['table'], $tableList) && ORM::getMapper($item['table'])?->isSoftDeleteEnabled() === true) {
$tableList[] = $item['table'];
$where[] = ["filter" => "{$item['table']}.deleted_at is null", "params" => []];
if ($item['table'] instanceof QueryBasic) {
continue;
}

$tableName = $item["alias"] ?? $item['table'];
if (!in_array($tableName, $tableList) && ORM::getMapper($item['table'])?->isSoftDeleteEnabled() === true) {
$tableList[] = $tableName;
$where[] = ["filter" => "{$tableName}.deleted_at is null", "params" => []];
}
}
}
Expand Down
Loading
Loading