✋ Валентин Удальцов @vudaltsov
final readonly class BasicCache
{
public function set(string $key, mixed $value): void
{
file_put_contents($this->file($key), serialize($value));
}
public function get(string $key): mixed
{
return unserialize(file_get_contents($this->file($key)));
}
}
final class City
{
public function __construct(
public string $name,
public int $code,
) {}
}
$rnd = new City('Ростов-на-Дону', 863);
$basicCache->set($rnd);
O:4:"City":2:{s:4:"name";s:26:"Ростов-на-Дону";s:4:"code";i:863;}
🤔
<?php return new City('Ростов-на-Дону', 863);
final readonly class NewCache
{
public function set(string $key, mixed $value): void
{
file_put_contents(
$this->file($key),
'<?php return '.🔮🎩🪄($value).';',
);
}
public function get(string $key): mixed
{
return include $this->file($key);
}
}
file_put_contents('serialize', serialize($rnd));
file_put_contents('igbinary', igbinary_serialize($rnd));
file_put_contents('code', "<?php return new City('Ростов...");
DragonCode\Benchmark\Benchmark::start()
->compare([
'serialized' => static function (): void {
unserialize(file_get_contents('serialized'));
},
'igbinary' => static function (): void {
igbinary_unserialize(file_get_contents('igbinary'));
},
'code' => static function (): void {
include 'code';
},
]);
------- ------------------- ------------------- -------------------
serialize igbinary code
------- ------------------- ------------------- -------------------
min 0.13 ms - 0 bytes 0.13 ms - 0 bytes 0.13 ms - 0 bytes
max 0.28 ms - 0 bytes 0.20 ms - 0 bytes 0.28 ms - 0 bytes
avg 0.22 ms - 0 bytes 0.17 ms - 0 bytes 0.17 ms - 0 bytes
total 18.09 ms 15.53 ms 16.30 ms
------- ------------------- ------------------- -------------------
Order 🥉 🥇 🥈
------- ------------------- ------------------- -------------------
; Determines if Zend OPCache is enabled for the CLI version of PHP
;opcache.enable_cli=0
; Determines if Zend OPCache is enabled for the CLI version of PHP
opcache.enable_cli=1
file_put_contents('serialize', serialize($rnd));
file_put_contents('igbinary', igbinary_serialize($rnd));
file_put_contents('code', "<?php return new City('Ростов...");
touch('code', time() - 10);
DragonCode\Benchmark\Benchmark::start()
->compare([
'serialized' => static function (): void {
unserialize(file_get_contents('serialized'));
},
'igbinary' => static function (): void {
igbinary_unserialize(file_get_contents('igbinary'));
},
'code' => static function (): void {
include 'code';
},
]);
------- ------------------- ------------------- ----------------
serialize igbinary code
------- ------------------- ------------------- ----------------
min 0.13 ms - 0 bytes 0.13 ms - 0 bytes 0 ms - 0 bytes
max 0.25 ms - 0 bytes 0.18 ms - 0 bytes 0 ms - 0 bytes
avg 0.18 ms - 0 bytes 0.16 ms - 0 bytes 0 ms - 0 bytes
total 16.55 ms 15.29 ms 0.28 ms 😳
------- ------------------- ------------------- ----------------
Order 🥉 🥈 🥇
------- ------------------- ------------------- ----------------
final readonly class NewCache
{
public function set(string $key, mixed $value): void
{
file_put_contents(
$this->file($key),
'<?php return '.🔮🎩🪄($value).';',
);
}
public function get(string $key): mixed
{
return include $this->file($key);
}
}
var_export(mixed $value, bool $return = false): ?string
var_export(null);
NULL
var_export(true);
var_export(false);
true
false
var_export(PHP_INT_MAX);
9223372036854775807
var_export(M_E);
2.718281828459045
var_export("Hello'\nworld!");
'Hello\'
world!'
var_export([
123,
['Key' => 'Value'],
]);
array (
0 => 123,
1 =>
array (
'Key' => 'Value',
),
)
enum Women
{
case OLGA;
}
var_export(Women::OLGA);
\Women::OLGA
final class City
{
public function __construct(
public string $name,
public int $code,
) {}
}
var_export(new City('Ростов-на-Дону', 863));
\City::__set_state(array(
'name' => 'Ростов-на-Дону',
'code' => 863,
));
PHP Fatal error: Uncaught Error: Call to undefined method City::__set_state()
🤯
\City::__set_state(array(
'name' => 'Ростов-на-Дону',
'code' => 863,
));
\Hydrator::hydrate(\City::class, [
'name' => 'Ростов-на-Дону',
'code' => 863,
]);
$hydrator = new \Hydrator();
$object = $hydrator->instantiate(\City::class);
$hydrator->hydrate($object, [
'name' => 'Ростов-на-Дону',
'code' => 863
]);
($h = new \Hydrator())->hydrate($h->instantiate(\City::class), [
'name' => 'Ростов-на-Дону',
'code' => 863,
]);
($hydrator = new \Hydrator())->hydrate(
object: $hydrator->instantiate($class),
data: $data,
);
🔮 Извлечение
🎩 Инстанциация
🪄 Гидрация
final class City extends Settlement
{
private string $type = 'г.';
public function __construct(
string $name,
public int $code,
) {
parent::__construct($name);
}
}
abstract class Settlement
{
private string $type = 'нп.';
public function __construct(
protected string $name,
) {}
}
var_dump(new City('Ростов-на-Дону', 863));
object(City)#2 (4) {
["type":"Settlement":private] => string(5) "нп."
["name":protected] => string(19) "Ростов-на-Дону"
["type":"City":private] => string(3) "г."
["code"] => int(863)
}
echo serialize(new City('Ростов-на-Дону', 863));
O:4:"City":4:{s:16:"\0Settlement\0type";s:5:"нп.";s:7:"\0*\0name";s:26:"Ростов-на-Дону";s:10:"\0City\0type";s:3:"г.";s:4:"code";i:863;}
var_dump((array) new City('Ростов-на-Дону', 863));
array(4) {
["\0Settlement\0type"] => string(5) "нп."
["\0*\0name"] => string(19) "Ростов-на-Дону"
["\0City\0type"] => string(3) "г."
["code"] => int(863)
}
($h = new \Hydrator())->hydrate($h->instantiate(\City::class), [
"\0Settlement\0type" => 'нп.',
"\0*\0name" => 'Ростов-на-Дону',
"\0City\0type" => 'г.',
'code' => 863,
])
final class Hydrator
{
/**
* @template T of object
* @param class-string<T> $class
* @return T
*/
public function instantiate(string $class): object
{
// TODO
}
}
final class City
{
private function __construct(string $name, int $code)
{
$this->title = $name;
$this->number = $code;
// logic
}
}
final class Hydrator
{
/**
* @template T of object
* @param class-string<T> $class
* @return T
*/
public function instantiate(string $class): object
{
$reflClass = new ReflectionClass($class);
return $reflClass->newInstanceWithoutConstructor();
}
}
final class Hydrator
{
/**
* @var array<class-string, ReflectionClass>
*/
private array $reflClasses = [];
/**
* @template T of object
* @param class-string<T> $class
* @return T
*/
public function instantiate(string $class): object
{
$this->reflClasses[$class] ??= new ReflectionClass($class);
$reflClass = $this->reflClasses[$class];
return $reflClass->newInstanceWithoutConstructor();
}
}
final class Hydrator
{
/**
* @template T of object
* @param T $object
* @param array<non-empty-string, mixed> $data
* @return T
*/
public function hydrate(object $object, array $data): object
{
// TODO
}
}
final class Hydrator
{
/**
* @template T of object
* @param T $object
* @param array<non-empty-string, mixed> $data
* @return T
*/
public function hydrate(object $object, array $data): object
{
foreach ($data as $key => $value) {
$property = $this->reflectionProperty($object::class, $key);
$property->setValue($object, $value);
}
return $object;
}
}
array(4) {
["\0Settlement\0type"] => string(5) "нп."
["\0*\0name"] => string(19) "Ростов-на-Дону"
["\0City\0type"] => string(3) "г."
["code"] => int(863)
}
private function reflectionProperty(string $class, string $key)
{
$parts = explode("\0", $key);
if (count($parts) === 1) {
return new ReflectionProperty($class, $key);
}
if ($parts[1] === '*') {
return new ReflectionProperty($class, $parts[2]);
}
return new ReflectionProperty($parts[1], $parts[2]);
}
final class Hydrator
{
/**
* @template T of object
* @param T $object
* @param array<non-empty-string, mixed> $data
* @return T
*/
public function hydrate(object $object, array $data): object
{
foreach ($data as $key => $value) {
$property = $this->reflectionProperty($object::class, $key);
$property->setValue($object, $value);
}
return $object;
}
}
final class Exporter
{
// Здесь позже добавим стейт
private function __construct() {}
public static function export(mixed $value): string
{
return (new self())->exportMixed($value);
}
private function exportMixed(mixed $value): string
{
// TODO
}
}
$valueAsCode = Exporter::export($value);
private function exportMixed(mixed $value): string
{
if ($value === null) {
return 'null';
}
if (is_scalar($value)) {
return var_export($value, true);
}
if (is_array($value)) {
return $this->exportArray($value);
}
if (is_object($value)) {
return $this->exportObject($value);
}
throw new InvalidArgumentException(sprintf(
'Export of %s is not supported.',
get_debug_type($value),
));
}
private function exportArray(array $array): string
{
$code = '[';
foreach ($array as $key => $value) {
$code .= $this->exportMixed($key).'=>';
$code .= $this->exportMixed($value).',';
}
return $code.']';
}
echo Exporter::export([123, ['Key' => 'Value']]);
[0=>123,1=>['Key'=>'Value',],]
private function exportArray(array $array): string
{
$code = '[';
$first = true;
foreach ($array as $key => $value) {
if ($first) {
$first = false;
} else {
$code .= ',';
}
$code .= $this->exportMixed($key).'=>';
$code .= $this->exportMixed($value);
}
return $code.']';
}
echo Exporter::export([123, ['Key' => 'Value']]);
[0=>123,1=>['Key'=>'Value']]
private function exportArray(array $array): string
{
$code = '[';
$first = true;
$list = array_is_list($array);
foreach ($array as $key => $value) {
if ($first) { ... }
if (!$list) {
$code .= $this->exportMixed($key).'=>';
}
$code .= $this->exportMixed($value);
}
return $code.']';
}
echo Exporter::export([123, ['Key' => 'Value']]);
[123,['Key'=>'Value']]
private function exportObject(object $object): string
{
return sprintf(
'($h = new \\%s())->hydrate($h->instantiate(\\%s::class), %s)',
Hydrator::class,
$object::class,
$this->exportArray((array) $object),
);
}
echo Exporter::export(new City('Ростов-на-Дону', 863));
($h = new \Hydrator())->hydrate($h->instantiate(\City::class), [
"\0Settlement\0type" => 'нп.',
"\0*\0name" => 'Ростов-на-Дону',
"\0City\0type" => 'г.',
'code' => 863,
])
echo Exporter::export([
new City('Ростов-на-Дону', 863),
new City('Ростов-на-Дону', 863),
]);
[
($h = new \Hydrator())->hydrate($h->instantiate(\City::class), [
"\0Settlement\0type" => 'нп.',
"\0*\0name" => 'Ростов-на-Дону',
"\0City\0type" => 'г.',
'code' => 863,
]),
($h = new \Hydrator())->hydrate($h->instantiate(\City::class), [
"\0Settlement\0type" => 'нп.',
"\0*\0name" => 'Ростов-на-Дону',
"\0City\0type" => 'г.',
'code' => 863,
]),
]
private function exportObject(object $object): string
{
return sprintf(
'%s->hydrate(%s->instantiate(\\%s::class), %s)',
$this->hydrator(),
$this->hydrator(),
$object::class,
$this->exportArray((array) $object),
);
}
private bool $hydratorInitialized = false;
private function hydrator(): string
{
if ($this->hydratorInitialized) {
return '$h';
}
$this->hydratorInitialized = true;
return sprintf('($h = new \\%s())', Hydrator::class);
}
echo Exporter::export([
new City('Ростов-на-Дону', 863),
new City('Ростов-на-Дону', 863),
]);
[
($h = new \Hydrator())->hydrate($h->instantiate(\City::class), [
"\0Settlement\0type" => 'нп.',
"\0*\0name" => 'Ростов-на-Дону',
"\0City\0type" => 'г.',
'code' => 863,
]),
$h->hydrate($h->instantiate(\City::class), [
"\0Settlement\0type" => 'нп.',
"\0*\0name" => 'Ростов-на-Дону',
"\0City\0type" => 'г.',
'code' => 863,
]),
]
$rnd = new City('Ростов-на-Дону', 863);
echo Exporter::export([$rnd, $rnd]);
[
($h = new \Hydrator())->hydrate($h->instantiate(\City::class), [
"\0Settlement\0type" => 'нп.',
"\0*\0name" => 'Ростов-на-Дону',
"\0City\0type" => 'г.',
'code' => 863,
]),
$h->hydrate($h->instantiate(\City::class), [
"\0Settlement\0type" => 'нп.',
"\0*\0name" => 'Ростов-на-Дону',
"\0City\0type" => 'г.',
'code' => 863,
]),
]
$rnd = new City('Ростов-на-Дону', 863);
echo serialize([$rnd, $rnd]);
a:2:{i:0;O:4:"City":4:{s:16:"\0Settlement\0type";s:5:"нп.";s:7:"\0*\0name";s:26:"Ростов-на-Дону";s:10:"\0City\0type";s:3:"г.";s:4:"code";i:863;}i:1;r:2;}
🤔
$rnd = new City('Ростов-на-Дону', 863);
echo Exporter::export([$rnd, $rnd]);
[
($h = new \Hydrator())->hydrate(
$o0 = $h->instantiate(\City::class),
[
"\0Settlement\0type" => 'нп.',
"\0*\0name" => 'Ростов-на-Дону',
"\0City\0type" => 'г.',
'code' => 863,
],
),
$o0,
]
/**
* @var SplObjectStorage<object, non-empty-string>
*/
private SplObjectStorage $objectVariables;
private function exportObject(object $object): string
{
if ($this->objectVariables->contains($object)) {
return $this->objectVariables[$object];
}
$objectVariable = '$o'.$this->objectVariables->count();
$this->objectVariables->attach($object, $objectVariable);
return sprintf(
'%s->hydrate(%s=%s->instantiate(\\%s::class), %s)',
$this->hydrator(),
$objectVariable,
$this->hydrator(),
$object::class,
$this->exportArray((array) $object),
);
}
$rnd = new City('Ростов-на-Дону', 863);
echo Exporter::export([$rnd, $rnd]);
[
($h = new \Hydrator())->hydrate(
$o0 = $h->instantiate(\City::class),
[
"\0Settlement\0type" => 'нп.',
"\0*\0name" => 'Ростов-на-Дону',
"\0City\0type" => 'г.',
'code' => 863,
],
),
$o0,
]
final class SelfReference
{
private self $selfReference;
public function __construct()
{
$this->selfReference = $this;
}
}
echo Exporter::export(new SelfReference());
($h = new \Hydrator())->hydrate(
$o0 = $h->instantiate(\SelfReference::class),
["\0SelfReference\0selfReference" => $o0],
)
internal PHP classes
readonly properties
__sleep()
__wakeup()
Serializable
__serialize()
__unserialize()
final readonly class NewCache
{
public function set(string $key, mixed $value): void
{
file_put_contents(
$this->file($key),
'<?php return '.Exporter::export($value).';',
);
}
public function get(string $key): mixed
{
return include $this->file($key);
}
}
🎉
private function set(string $key, mixed $value): void
{
$file = $this->file($key);
$directory = dirname($file);
$tmp = $directory.'/'.uniqid(more_entropy: true);
$handle = fopen($tmp, 'x');
fwrite($handle, '<?php return'.Exporter::export($value).';');
fclose($handle);
touch($tmp, $_SERVER['REQUEST_TIME'] - 10);
rename($tmp, $file);
if (self::opcacheEnabled()) {
opcache_invalidate($file, true);
opcache_compile_file($file);
}
}
composer install typhoon-php/exporter
composer install typhoon-php/opcache
@vudaltsov / Пых / PHP Point