src/Type/LocalObjectType.php
<?php
declare(strict_types=1);
namespace Atk4\Data\Type;
use Atk4\Data\Exception;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types as DbalTypes;
/**
* Type that allows to weakly reference a local PHP object using a scalar string
* and get the original object instance back using the string.
*
* The local object is never serialized.
*
* An exception is thrown when getting an object from a string back and the original
* object instance has been destroyed/released.
*/
class LocalObjectType extends DbalTypes\Type
{
private ?string $instanceUid = null;
private int $localUidCounter;
/** @var \WeakMap<object, LocalObjectHandle> */
private \WeakMap $handles;
/** @var array<int, \WeakReference<LocalObjectHandle>> */
private array $handlesIndex;
private function __clone()
{
// prevent cloning
}
protected function init(): void
{
$this->instanceUid = hash('sha256', microtime(true) . random_bytes(64));
$this->localUidCounter = 0;
$this->handles = new \WeakMap();
$this->handlesIndex = [];
}
#[\Override]
public function getName(): string
{
return Types::LOCAL_OBJECT;
}
#[\Override]
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
{
return DbalTypes\Type::getType(DbalTypes\Types::STRING)->getSQLDeclaration($fieldDeclaration, $platform);
}
#[\Override]
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
if ($value === null) {
return null;
}
if ($this->instanceUid === null) {
$this->init();
}
$handle = $this->handles->offsetExists($value)
? $this->handles->offsetGet($value)
: null;
if ($handle === null) {
$handle = new LocalObjectHandle(++$this->localUidCounter, $value, function (LocalObjectHandle $handle): void {
unset($this->handlesIndex[$handle->getLocalUid()]);
});
$this->handles->offsetSet($value, $handle);
$this->handlesIndex[$handle->getLocalUid()] = \WeakReference::create($handle);
}
$className = get_debug_type($value);
if (strlen($className) > 160) { // keep result below 255 bytes
$className = mb_strcut($className, 0, 80)
. '...'
. mb_strcut(substr($className, strlen(mb_strcut($className, 0, 80))), -80);
}
return $className . '-' . $this->instanceUid . '-' . $handle->getLocalUid();
}
#[\Override]
public function convertToPHPValue($value, AbstractPlatform $platform): ?object
{
if ($value === null || trim($value) === '') {
return null;
}
$valueExploded = explode('-', $value, 3);
if (count($valueExploded) !== 3
|| $valueExploded[1] !== $this->instanceUid
|| $valueExploded[2] !== (string) (int) $valueExploded[2]
) {
throw new Exception('Local object does not match the DBAL type instance');
}
$handle = $this->handlesIndex[(int) $valueExploded[2]] ?? null;
if ($handle !== null) {
$handle = $handle->get();
}
$res = $handle !== null ? $handle->getValue() : null;
if ($res === null) {
throw new Exception('Local object does no longer exist');
}
return $res;
}
#[\Override]
public function requiresSQLCommentHint(AbstractPlatform $platform): bool
{
return true;
}
}