mirror of
https://github.com/danog/AsyncOrm.git
synced 2024-11-30 04:39:45 +01:00
Refactor
This commit is contained in:
parent
5c3086e266
commit
a821954023
@ -16,9 +16,10 @@
|
||||
|
||||
namespace danog\AsyncOrm;
|
||||
|
||||
use Amp\ForbidCloning;
|
||||
use Amp\ForbidSerialization;
|
||||
use Amp\Sync\LocalKeyedMutex;
|
||||
use AssertionError;
|
||||
use WeakMap;
|
||||
|
||||
/**
|
||||
* Async DB mapper.
|
||||
@ -28,9 +29,12 @@ use WeakMap;
|
||||
*/
|
||||
final readonly class DbMapper
|
||||
{
|
||||
use ForbidCloning;
|
||||
use ForbidSerialization;
|
||||
|
||||
/** @var DbArray<string|int, T> */
|
||||
private readonly DbArray $arr;
|
||||
private readonly LocalKeyedMutex $mutex;
|
||||
private readonly WeakMap $inited;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@ -43,7 +47,7 @@ final readonly class DbMapper
|
||||
* @param ?self $previous Previous instance, used for migrations.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $table,
|
||||
public readonly string $table,
|
||||
private readonly string $class,
|
||||
private readonly Settings $settings,
|
||||
KeyType $keyType,
|
||||
@ -54,7 +58,6 @@ final readonly class DbMapper
|
||||
if (\is_subclass_of($class, DbArray::class)) {
|
||||
throw new AssertionError("$class must extend DbArray!");
|
||||
}
|
||||
$this->inited = new WeakMap;
|
||||
$this->mutex = new LocalKeyedMutex;
|
||||
$config = new FieldConfig(
|
||||
$table,
|
||||
@ -65,12 +68,6 @@ final readonly class DbMapper
|
||||
$optimizeIfWastedGtMb
|
||||
);
|
||||
$this->arr = $config->get($previous?->arr);
|
||||
if ($previous !== null) {
|
||||
foreach ($previous->inited as $key => $obj) {
|
||||
$obj->__initDb($this->table, $this->settings);
|
||||
$this->inited[$key] = $obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,13 +79,12 @@ final readonly class DbMapper
|
||||
{
|
||||
$lock = $this->mutex->acquire((string) $key);
|
||||
try {
|
||||
if (isset($this->inited[$key])) {
|
||||
if (isset($this->arr[$key])) {
|
||||
throw new AssertionError("An object under the key $key already exists!");
|
||||
}
|
||||
$obj = new $this->class;
|
||||
$obj->__initDb($this->table, $this->settings);
|
||||
$this->arr[$key] = $obj;
|
||||
$this->inited[$key] = $obj;
|
||||
return $obj;
|
||||
} finally {
|
||||
$lock->release();
|
||||
@ -106,13 +102,12 @@ final readonly class DbMapper
|
||||
{
|
||||
$lock = $this->mutex->acquire((string) $key);
|
||||
try {
|
||||
if (isset($this->inited[$key])) {
|
||||
return $this->inited[$key];
|
||||
}
|
||||
$obj = $this->arr[$key];
|
||||
if ($obj !== null) {
|
||||
$obj->__initDb($this->table, $this->settings);
|
||||
$this->inited[$key] = $obj;
|
||||
$obj->__initDb(
|
||||
$this->table,
|
||||
$this->settings,
|
||||
);
|
||||
}
|
||||
return $obj;
|
||||
} finally {
|
||||
|
@ -19,6 +19,7 @@
|
||||
namespace danog\AsyncOrm;
|
||||
|
||||
use danog\AsyncOrm\Annotations\OrmMappedArray;
|
||||
use danog\AsyncOrm\Internal\Driver\CachedArray;
|
||||
use danog\AsyncOrm\Settings\DriverSettings;
|
||||
use danog\AsyncOrm\Settings\Mysql;
|
||||
use ReflectionClass;
|
||||
@ -28,13 +29,20 @@ use function Amp\Future\await;
|
||||
|
||||
abstract class DbObject
|
||||
{
|
||||
/** @var list<CachedArray> */
|
||||
private array $properties;
|
||||
private DbArray $mapper;
|
||||
private string|int $key;
|
||||
|
||||
/**
|
||||
* Initialize database instance.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final public function __initDb(string $table, Settings $settings): void
|
||||
final public function initDb(DbArray $mapper, string|int $key, FieldConfig $config): void
|
||||
{
|
||||
$this->mapper = $mapper;
|
||||
$this->key = $key;
|
||||
$promises = [];
|
||||
foreach ((new ReflectionClass(static::class))->getProperties() as $property) {
|
||||
$attr = $property->getAttributes(OrmMappedArray::class);
|
||||
@ -45,31 +53,43 @@ abstract class DbObject
|
||||
|
||||
$ttl = $attr->cacheTtl;
|
||||
$optimize = $attr->optimizeIfWastedGtMb;
|
||||
if ($settings instanceof DriverSettings) {
|
||||
$ttl ??= $settings->cacheTtl;
|
||||
if ($config->settings instanceof DriverSettings) {
|
||||
$ttl ??= $config->settings->cacheTtl;
|
||||
|
||||
if ($settings instanceof Mysql) {
|
||||
$optimize ??= $settings->optimizeIfWastedGtMb;
|
||||
if ($config->settings instanceof Mysql) {
|
||||
$optimize ??= $config->settings->optimizeIfWastedGtMb;
|
||||
}
|
||||
}
|
||||
|
||||
$config = new FieldConfig(
|
||||
$table.'_'.$property->getName(),
|
||||
$settings,
|
||||
$config->table.'_'.$property->getName(),
|
||||
$config->settings,
|
||||
$attr->keyType,
|
||||
$attr->valueType,
|
||||
$ttl,
|
||||
$optimize,
|
||||
);
|
||||
|
||||
$promises[$property] = async(
|
||||
$config->get(...),
|
||||
$this->{$property} ?? null
|
||||
);
|
||||
$promises[] = async(function () use ($config, $property) {
|
||||
$v = $config->get($property->getValue());
|
||||
$property->setValue($v);
|
||||
if ($v instanceof CachedArray) {
|
||||
$this->properties []= $v;
|
||||
}
|
||||
$promises = await($promises);
|
||||
foreach ($promises as $key => $data) {
|
||||
$this->{$key} = $data;
|
||||
});
|
||||
}
|
||||
await($promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save object to database.
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
$promises = [async($this->mapper->set(...), $this->key, $this)];
|
||||
foreach ($this->properties as $v) {
|
||||
$promises []= async($v->flushCache(...));
|
||||
}
|
||||
await($promises);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
* @link https://daniil.it/AsyncOrm AsyncOrm documentation
|
||||
*/
|
||||
|
||||
namespace danog\AsyncOrm\Internal\Driver;
|
||||
namespace danog\AsyncOrm\Internal\Containers;
|
||||
|
||||
use Amp\Sync\LocalMutex;
|
||||
use danog\AsyncOrm\DbArray;
|
||||
@ -51,8 +51,7 @@ final class CacheContainer
|
||||
}
|
||||
public function __sleep()
|
||||
{
|
||||
$this->flushCache();
|
||||
return ['cache', 'ttl', 'inner'];
|
||||
return ['inner'];
|
||||
}
|
||||
public function __wakeup(): void
|
||||
{
|
177
src/Internal/Containers/ObjectContainer.php
Normal file
177
src/Internal/Containers/ObjectContainer.php
Normal file
@ -0,0 +1,177 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of AsyncOrm.
|
||||
* AsyncOrm is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General private License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* AsyncOrm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General private License for more details.
|
||||
* You should have received a copy of the GNU General private License along with AsyncOrm.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @author Alexander Pankratov <alexander@i-c-a.su>
|
||||
* @copyright 2016-2024 Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2024 Alexander Pankratov <alexander@i-c-a.su>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
* @link https://daniil.it/AsyncOrm AsyncOrm documentation
|
||||
*/
|
||||
|
||||
namespace danog\AsyncOrm\Internal\Containers;
|
||||
|
||||
use Amp\Sync\LocalMutex;
|
||||
use danog\AsyncOrm\DbArray;
|
||||
use danog\AsyncOrm\DbObject;
|
||||
use danog\AsyncOrm\FieldConfig;
|
||||
use Revolt\EventLoop;
|
||||
use Traversable;
|
||||
|
||||
/** @internal */
|
||||
final class ObjectContainer
|
||||
{
|
||||
/**
|
||||
* @var array<ObjectReference>
|
||||
*/
|
||||
private array $cache = [];
|
||||
|
||||
private int $cacheTtl;
|
||||
|
||||
/**
|
||||
* Cache cleanup watcher ID.
|
||||
*/
|
||||
private ?string $cacheCleanupId = null;
|
||||
|
||||
private LocalMutex $mutex;
|
||||
|
||||
public function __construct(
|
||||
/** @var DbArray<array-key, DbObject> */
|
||||
public DbArray $inner,
|
||||
public FieldConfig $config,
|
||||
) {
|
||||
$this->mutex = new LocalMutex;
|
||||
}
|
||||
public function __sleep()
|
||||
{
|
||||
return ['inner'];
|
||||
}
|
||||
public function __wakeup(): void
|
||||
{
|
||||
$this->mutex = new LocalMutex;
|
||||
}
|
||||
|
||||
public function startCacheCleanupLoop(int $cacheTtl): void
|
||||
{
|
||||
$this->cacheTtl = $cacheTtl;
|
||||
if ($this->cacheCleanupId) {
|
||||
EventLoop::cancel($this->cacheCleanupId);
|
||||
}
|
||||
$this->cacheCleanupId = EventLoop::repeat(
|
||||
\max(1, $this->cacheTtl / 5),
|
||||
fn () => $this->flushCache(),
|
||||
);
|
||||
}
|
||||
public function stopCacheCleanupLoop(): void
|
||||
{
|
||||
if ($this->cacheCleanupId) {
|
||||
EventLoop::cancel($this->cacheCleanupId);
|
||||
$this->cacheCleanupId = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function get(string|int $index): mixed
|
||||
{
|
||||
if (isset($this->cache[$index])) {
|
||||
$obj = $this->cache[$index];
|
||||
$ref = $obj->reference->get();
|
||||
if ($ref !== null) {
|
||||
$obj->ttl = \time() + $this->cacheTtl;
|
||||
return $obj;
|
||||
}
|
||||
unset($this->cache[$index]);
|
||||
}
|
||||
|
||||
$result = $this->inner->offsetGet($index);
|
||||
if (isset($this->cache[$index])) {
|
||||
return $this->cache[$index]->reference->get();
|
||||
}
|
||||
|
||||
\assert($result instanceof DbObject);
|
||||
$result->initDb($this->inner, $index, $this->config);
|
||||
|
||||
$this->cache[$index] = new ObjectReference($result, \time() + $this->cacheTtl);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function set(string|int $key, DbObject $value): void
|
||||
{
|
||||
if (isset($this->cache[$key]) && $this->cache[$key]->reference->get() === $value) {
|
||||
return;
|
||||
}
|
||||
$value->initDb($this->inner, $key, $this->config);
|
||||
$this->cache[$key] = new ObjectReference($value, \time() + $this->cacheTtl);
|
||||
$this->inner->set($key, $value);
|
||||
}
|
||||
|
||||
public function unset(string|int $key): void
|
||||
{
|
||||
unset($this->cache[$key]);
|
||||
$this->inner->unset($key);
|
||||
}
|
||||
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
$this->flushCache();
|
||||
foreach ($this->inner->getIterator() as $key => $value) {
|
||||
if (isset($this->cache[$key])) {
|
||||
$obj = $this->cache[$key];
|
||||
$ref = $obj->reference->get();
|
||||
if ($ref !== null) {
|
||||
$obj->ttl = \time() + $this->cacheTtl;
|
||||
yield $obj;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$value->initDb($this->inner, $key, $this->config);
|
||||
$this->cache[$key] = new ObjectReference($value, \time() + $this->cacheTtl);
|
||||
yield $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
$this->flushCache();
|
||||
return $this->inner->count();
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
$lock = $this->mutex->acquire();
|
||||
$this->cache = [];
|
||||
$lock->release();
|
||||
|
||||
$this->inner->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush all flushable keys.
|
||||
*/
|
||||
public function flushCache(): void
|
||||
{
|
||||
$lock = $this->mutex->acquire();
|
||||
try {
|
||||
$now = \time();
|
||||
$new = [];
|
||||
foreach ($this->cache as $key => $value) {
|
||||
if ($value->ttl > $now) {
|
||||
$value->obj = null;
|
||||
}
|
||||
if ($value->reference->get() !== null) {
|
||||
$new[$key] = $value;
|
||||
}
|
||||
}
|
||||
$this->cache = $new;
|
||||
} finally {
|
||||
EventLoop::queue($lock->release(...));
|
||||
}
|
||||
}
|
||||
}
|
36
src/Internal/Containers/ObjectReference.php
Normal file
36
src/Internal/Containers/ObjectReference.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of AsyncOrm.
|
||||
* AsyncOrm is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General private License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* AsyncOrm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General private License for more details.
|
||||
* You should have received a copy of the GNU General private License along with AsyncOrm.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @author Alexander Pankratov <alexander@i-c-a.su>
|
||||
* @copyright 2016-2024 Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2024 Alexander Pankratov <alexander@i-c-a.su>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
* @link https://daniil.it/AsyncOrm AsyncOrm documentation
|
||||
*/
|
||||
|
||||
namespace danog\AsyncOrm\Internal\Containers;
|
||||
|
||||
use danog\AsyncOrm\DbObject;
|
||||
use WeakReference;
|
||||
|
||||
/** @internal */
|
||||
final class ObjectReference
|
||||
{
|
||||
public readonly WeakReference $reference;
|
||||
public ?DbObject $obj;
|
||||
public function __construct(
|
||||
DbObject $object,
|
||||
public int $ttl
|
||||
) {
|
||||
$this->obj = $object;
|
||||
$this->reference = WeakReference::create($object);
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ namespace danog\AsyncOrm\Internal\Driver;
|
||||
use danog\AsyncOrm\DbArray;
|
||||
use danog\AsyncOrm\Driver\MemoryArray;
|
||||
use danog\AsyncOrm\FieldConfig;
|
||||
use danog\AsyncOrm\Internal\Containers\CacheContainer;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
@ -54,7 +55,7 @@ final class CachedArray extends DbArray
|
||||
$previous->cache->flushCache();
|
||||
return $previous->cache->inner;
|
||||
}
|
||||
$previous->cache->startCacheCleanupLoop($config->annotation->cacheTtl);
|
||||
$previous->cache->startCacheCleanupLoop($config->cacheTtl);
|
||||
return $previous;
|
||||
}
|
||||
|
||||
@ -68,6 +69,11 @@ final class CachedArray extends DbArray
|
||||
$this->cache->stopCacheCleanupLoop();
|
||||
}
|
||||
|
||||
public function flushCache(): void
|
||||
{
|
||||
$this->cache->flushCache();
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return $this->cache->count();
|
||||
|
102
src/Internal/Driver/ObjectArray.php
Normal file
102
src/Internal/Driver/ObjectArray.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of AsyncOrm.
|
||||
* AsyncOrm is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* AsyncOrm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with AsyncOrm.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @author Alexander Pankratov <alexander@i-c-a.su>
|
||||
* @copyright 2016-2024 Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2024 Alexander Pankratov <alexander@i-c-a.su>
|
||||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||||
* @link https://daniil.it/AsyncOrm AsyncOrm documentation
|
||||
*/
|
||||
|
||||
namespace danog\AsyncOrm\Internal\Driver;
|
||||
|
||||
use danog\AsyncOrm\DbArray;
|
||||
use danog\AsyncOrm\Driver\MemoryArray;
|
||||
use danog\AsyncOrm\FieldConfig;
|
||||
use danog\AsyncOrm\Internal\Containers\ObjectContainer;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Object caching proxy.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
*
|
||||
* @extends DbArray<TKey, TValue>
|
||||
*/
|
||||
final class ObjectArray extends DbArray
|
||||
{
|
||||
private readonly ObjectContainer $cache;
|
||||
|
||||
/**
|
||||
* Get instance.
|
||||
*/
|
||||
public static function getInstance(FieldConfig $config, DbArray|null $previous): DbArray
|
||||
{
|
||||
$new = $config->settings->getDriverClass();
|
||||
if ($previous === null) {
|
||||
$previous = new self($new::getInstance($config, null), $config);
|
||||
} elseif ($previous instanceof self) {
|
||||
$previous->cache->inner = $new::getInstance($config, $previous->cache->inner);
|
||||
$previous->cache->config = $config;
|
||||
} else {
|
||||
$previous = new self($new::getInstance($config, $previous), $config);
|
||||
}
|
||||
if ($previous->cache->inner instanceof MemoryArray) {
|
||||
$previous->cache->flushCache();
|
||||
return $previous->cache->inner;
|
||||
}
|
||||
$previous->cache->startCacheCleanupLoop($config->cacheTtl);
|
||||
return $previous;
|
||||
}
|
||||
|
||||
public function __construct(DbArray $inner, FieldConfig $config)
|
||||
{
|
||||
$this->cache = new ObjectContainer($inner, $config);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->cache->stopCacheCleanupLoop();
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return $this->cache->count();
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
$this->cache->clear();
|
||||
}
|
||||
|
||||
public function get(mixed $index): mixed
|
||||
{
|
||||
return $this->cache->get($index);
|
||||
}
|
||||
|
||||
public function set(string|int $key, mixed $value): void
|
||||
{
|
||||
$this->cache->set($key, $value);
|
||||
}
|
||||
|
||||
public function unset(string|int $key): void
|
||||
{
|
||||
$this->cache->unset($key);
|
||||
}
|
||||
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return $this->cache->getIterator();
|
||||
}
|
||||
}
|
@ -30,13 +30,13 @@ enum ValueType: string
|
||||
*/
|
||||
case INT = 'int';
|
||||
/**
|
||||
* Objects, serialized as specified in the settings.
|
||||
* Objects extending DbObject, serialized as specified in the settings.
|
||||
*/
|
||||
case OBJECT = 'object';
|
||||
/**
|
||||
* Values of any type, serialized as specified in the settings.
|
||||
* Values of any scalar type, serialized as specified in the settings.
|
||||
*
|
||||
* Using MIXED worsens performances, please use STRING, INT or OBJECT whenever possible.
|
||||
* Using SCALAR worsens performances, please use STRING, INT or OBJECT whenever possible.
|
||||
*/
|
||||
case MIXED = 'object';
|
||||
case SCALAR = 'scalar';
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user