Кэш через OPcache

✋ Валентин Удальцов @vudaltsov

Пых / PHP Point

                
                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   🥉                  🥇                  🥈
                    ------- ------------------- ------------------- -------------------
                
            

php.ini

                    
                        ; 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()

                
                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;
                        }
                    }
                
            

🔮🎩🪄 = Exporter

                
                    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()

Cache via OPcache 😍

                
                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

Спасибо за внимание!

QR code

@vudaltsov / Пых / PHP Point