Skip to content

Commit

Permalink
Merge pull request #23 from byjg/5.0
Browse files Browse the repository at this point in the history
Add ActiveRecord::reset() method
  • Loading branch information
byjg authored Dec 17, 2024
2 parents c3ab583 + a3e62a7 commit d14b4f0
Show file tree
Hide file tree
Showing 8 changed files with 367 additions and 23 deletions.
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

0 comments on commit d14b4f0

Please sign in to comment.