-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add (weakly referenced) local object DBAL type (#1083)
- Loading branch information
Showing
10 changed files
with
416 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Atk4\Data\Type; | ||
|
||
class LocalObjectHandle | ||
{ | ||
private int $localUid; | ||
|
||
/** @var \WeakReference<object> */ | ||
private \WeakReference $weakValue; | ||
|
||
private \Closure $destructFx; | ||
|
||
public function __construct(int $localUid, object $value, \Closure $destructFx) | ||
{ | ||
$this->localUid = $localUid; | ||
$this->weakValue = \WeakReference::create($value); | ||
$this->destructFx = $destructFx; | ||
} | ||
|
||
public function __destruct() | ||
{ | ||
($this->destructFx)($this); | ||
} | ||
|
||
public function getLocalUid(): int | ||
{ | ||
return $this->localUid; | ||
} | ||
|
||
public function getValue(): ?object | ||
{ | ||
return $this->weakValue->get(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
<?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; | ||
|
||
protected function __clone() | ||
{ | ||
// prevent clonning | ||
} | ||
|
||
protected function init(): void | ||
{ | ||
$this->instanceUid = hash('sha256', microtime(true) . random_bytes(64)); | ||
$this->localUidCounter = 0; | ||
$this->handles = new \WeakMap(); | ||
$this->handlesIndex = []; | ||
} | ||
|
||
public function getName(): string | ||
{ | ||
return Types::LOCAL_OBJECT; | ||
} | ||
|
||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string | ||
{ | ||
return DbalTypes\Type::getType(DbalTypes\Types::STRING)->getSQLDeclaration($fieldDeclaration, $platform); | ||
} | ||
|
||
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(); | ||
} | ||
|
||
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; | ||
} | ||
|
||
public function requiresSQLCommentHint(AbstractPlatform $platform): bool | ||
{ | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Atk4\Data\Type; | ||
|
||
use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
use Doctrine\DBAL\Types as DbalTypes; | ||
|
||
class MoneyType extends DbalTypes\Type | ||
{ | ||
public function getName(): string | ||
{ | ||
return Types::MONEY; | ||
} | ||
|
||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string | ||
{ | ||
return DbalTypes\Type::getType(DbalTypes\Types::FLOAT)->getSQLDeclaration($fieldDeclaration, $platform); | ||
} | ||
|
||
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string | ||
{ | ||
if ($value === null || trim((string) $value) === '') { | ||
return null; | ||
} | ||
|
||
return (string) round((float) $value, 4); | ||
} | ||
|
||
public function convertToPHPValue($value, AbstractPlatform $platform): ?float | ||
{ | ||
$v = $this->convertToDatabaseValue($value, $platform); | ||
|
||
return $v === null ? null : (float) $v; | ||
} | ||
|
||
public function requiresSQLCommentHint(AbstractPlatform $platform): bool | ||
{ | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Atk4\Data\Type; | ||
|
||
final class Types | ||
{ | ||
public const LOCAL_OBJECT = 'atk4_local_object'; | ||
public const MONEY = 'atk4_money'; | ||
} |
Oops, something went wrong.