Last active
May 6, 2026 03:25
-
-
Save rhoerr/0aa69fd2fec4abac51b6d832ccf672c8 to your computer and use it in GitHub Desktop.
Lazy Object Loading patch for Magento 2.4.8. Requires PHP 8.4. See https://github.com/mage-os/mageos-magento2/pull/225 for details; share your results!
This file contains hidden or 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
| diff --git a/vendor/magento/framework/ObjectManager/Config/Compiled.php b/vendor/magento/framework/ObjectManager/Config/Compiled.php | |
| --- a/vendor/magento/framework/ObjectManager/Config/Compiled.php | |
| +++ b/vendor/magento/framework/ObjectManager/Config/Compiled.php | |
| @@ -7,12 +7,13 @@ | |
| use Magento\Framework\ObjectManager\ConfigInterface; | |
| use Magento\Framework\ObjectManager\ConfigCacheInterface; | |
| +use Magento\Framework\ObjectManager\LazyTypeAwareInterface; | |
| use Magento\Framework\ObjectManager\RelationsInterface; | |
| /** | |
| * Provides object manager configuration when in compiled mode | |
| */ | |
| -class Compiled implements ConfigInterface | |
| +class Compiled implements ConfigInterface, LazyTypeAwareInterface | |
| { | |
| /** | |
| * @var array | |
| @@ -30,6 +31,11 @@ | |
| private $preferences; | |
| /** | |
| + * @var array<string,bool> | |
| + */ | |
| + private array $nonLazyTypes = []; | |
| + | |
| + /** | |
| * @param array $data | |
| */ | |
| public function __construct($data) | |
| @@ -40,6 +46,25 @@ | |
| ? $data['instanceTypes'] : []; | |
| $this->preferences = isset($data['preferences']) && is_array($data['preferences']) | |
| ? $data['preferences'] : []; | |
| + $this->nonLazyTypes = isset($data['nonLazyTypes']) && is_array($data['nonLazyTypes']) | |
| + ? $data['nonLazyTypes'] : []; | |
| + } | |
| + | |
| + /** | |
| + * Whether the given concrete type was flagged at compile-time as incompatible with PHP 8.4 lazy ghosts. | |
| + * | |
| + * Fails safe: Returns true (= non-lazy) if no compile-time data is present. | |
| + * | |
| + * @param string $type | |
| + * @return bool | |
| + */ | |
| + public function isNonLazyType(string $type): bool | |
| + { | |
| + if ($this->nonLazyTypes === []) { | |
| + return true; | |
| + } | |
| + | |
| + return isset($this->nonLazyTypes[$type]); | |
| } | |
| /** | |
| @@ -147,6 +172,9 @@ | |
| $this->preferences = isset($configuration['preferences']) && is_array($configuration['preferences']) | |
| ? array_replace($this->preferences, $configuration['preferences']) | |
| : $this->preferences; | |
| + $this->nonLazyTypes = isset($configuration['nonLazyTypes']) && is_array($configuration['nonLazyTypes']) | |
| + ? array_replace($this->nonLazyTypes, $configuration['nonLazyTypes']) | |
| + : $this->nonLazyTypes; | |
| } | |
| /** | |
| diff --git a/vendor/magento/framework/ObjectManager/Factory/Compiled.php b/vendor/magento/framework/ObjectManager/Factory/Compiled.php | |
| --- a/vendor/magento/framework/ObjectManager/Factory/Compiled.php | |
| +++ b/vendor/magento/framework/ObjectManager/Factory/Compiled.php | |
| @@ -48,64 +48,98 @@ | |
| * @param array $arguments | |
| * @return object | |
| * @throws \Exception | |
| - * @SuppressWarnings(PHPMD.CyclomaticComplexity) | |
| */ | |
| public function create($requestedType, array $arguments = []) | |
| { | |
| - $args = $this->config->getArguments($requestedType); | |
| $type = $this->config->getInstanceType($requestedType); | |
| - if ($args === []) { | |
| - // Case 1: no arguments required | |
| - return new $type(); | |
| - } elseif ($args !== null) { | |
| - /** | |
| - * Case 2: arguments retrieved from pre-compiled DI cache | |
| - * | |
| - * Argument key meanings: | |
| - * | |
| - * _i_: shared instance of a class or interface | |
| - * _ins_: non-shared instance of a class or interface | |
| - * _v_: non-array literal value | |
| - * _vac_: array, may be nested and contain other types of keys listed here (objects, array, nulls, etc) | |
| - * _vn_: null value | |
| - * _a_: value to be taken from named environment variable | |
| - * _d_: default value in case environment variable specified by _a_ does not exist | |
| - */ | |
| - foreach ($args as $key => &$argument) { | |
| - if (isset($arguments[$key])) { | |
| - $argument = $arguments[$key]; | |
| - } elseif (isset($argument['_i_'])) { | |
| - $argument = $this->get($argument['_i_']); | |
| - } elseif (isset($argument['_ins_'])) { | |
| - $argument = $this->create($argument['_ins_']); | |
| - } elseif (isset($argument['_v_'])) { | |
| - $argument = $argument['_v_']; | |
| - } elseif (isset($argument['_vac_'])) { | |
| - $argument = $argument['_vac_']; | |
| - $this->parseArray($argument); | |
| - } elseif (isset($argument['_vn_'])) { | |
| - $argument = null; | |
| - } elseif (isset($argument['_a_'])) { | |
| - if (isset($this->globalArguments[$argument['_a_']])) { | |
| - $argument = $this->globalArguments[$argument['_a_']]; | |
| - } else { | |
| - $argument = $argument['_d_']; | |
| + /** | |
| + * On PHP 8.4, with no call-time overrides and a compile-time-eligible type, | |
| + * defer construction via ReflectionClass::newLazyGhost(); the constructor | |
| + * is invoked in-place when the ghost's state is first observed. | |
| + */ | |
| + if (\PHP_VERSION_ID >= 80400 | |
| + && $arguments === [] | |
| + && $this->config instanceof \Magento\Framework\ObjectManager\LazyTypeAwareInterface | |
| + && !$this->config->isNonLazyType($type) | |
| + ) { | |
| + $reflection = new \ReflectionClass($type); | |
| + if ($reflection->getConstructor() !== null) { | |
| + return $reflection->newLazyGhost( | |
| + function ($obj) use ($requestedType) { | |
| + $obj->__construct(...$this->resolveArguments($requestedType)); | |
| } | |
| - } | |
| + ); | |
| } | |
| - $args = array_values($args); | |
| - } else { | |
| - // Case 3: arguments retrieved in runtime | |
| + } | |
| + | |
| + if ($this->config->getArguments($requestedType) === []) { | |
| + return new $type(); | |
| + } | |
| + | |
| + return $this->createObject($type, $this->resolveArguments($requestedType, $arguments)); | |
| + } | |
| + | |
| + /** | |
| + * Resolve constructor arguments for a requested type. | |
| + * | |
| + * @param string $requestedType | |
| + * @param array $arguments | |
| + * @return array | |
| + * @SuppressWarnings(PHPMD.CyclomaticComplexity) | |
| + */ | |
| + private function resolveArguments($requestedType, array $arguments = []): array | |
| + { | |
| + $args = $this->config->getArguments($requestedType); | |
| + | |
| + if ($args === []) { | |
| + return []; | |
| + } | |
| + | |
| + if ($args === null) { | |
| + // Arguments resolved at runtime (no pre-compiled DI cache entry). | |
| + $type = $this->config->getInstanceType($requestedType); | |
| $parameters = $this->getDefinitions()->getParameters($type) ?: []; | |
| - $args = $this->resolveArgumentsInRuntime( | |
| - $type, | |
| - $parameters, | |
| - $arguments | |
| - ); | |
| + return $this->resolveArgumentsInRuntime($type, $parameters, $arguments); | |
| } | |
| - return $this->createObject($type, $args); | |
| + /** | |
| + * Case 2: arguments retrieved from pre-compiled DI cache | |
| + * | |
| + * Argument key meanings: | |
| + * | |
| + * _i_: shared instance of a class or interface | |
| + * _ins_: non-shared instance of a class or interface | |
| + * _v_: non-array literal value | |
| + * _vac_: array, may be nested and contain other types of keys listed here (objects, array, nulls, etc) | |
| + * _vn_: null value | |
| + * _a_: value to be taken from named environment variable | |
| + * _d_: default value in case environment variable specified by _a_ does not exist | |
| + */ | |
| + foreach ($args as $key => &$argument) { | |
| + if (isset($arguments[$key])) { | |
| + $argument = $arguments[$key]; | |
| + } elseif (isset($argument['_i_'])) { | |
| + $argument = $this->get($argument['_i_']); | |
| + } elseif (isset($argument['_ins_'])) { | |
| + $argument = $this->create($argument['_ins_']); | |
| + } elseif (isset($argument['_v_'])) { | |
| + $argument = $argument['_v_']; | |
| + } elseif (isset($argument['_vac_'])) { | |
| + $argument = $argument['_vac_']; | |
| + $this->parseArray($argument); | |
| + } elseif (isset($argument['_vn_'])) { | |
| + $argument = null; | |
| + } elseif (isset($argument['_a_'])) { | |
| + if (isset($this->globalArguments[$argument['_a_']])) { | |
| + $argument = $this->globalArguments[$argument['_a_']]; | |
| + } else { | |
| + $argument = $argument['_d_']; | |
| + } | |
| + } | |
| + } | |
| + unset($argument); | |
| + return $args; | |
| } | |
| /** | |
| diff --git a/vendor/magento/framework/ObjectManager/LazyTypeAwareInterface.php b/vendor/magento/framework/ObjectManager/LazyTypeAwareInterface.php | |
| --- /dev/null | |
| +++ b/vendor/magento/framework/ObjectManager/LazyTypeAwareInterface.php | |
| @@ -0,0 +1,24 @@ | |
| +<?php | |
| +/** | |
| + * Copyright 2026 Mage-OS | |
| + * All Rights Reserved. | |
| + */ | |
| +declare(strict_types=1); | |
| + | |
| +namespace Magento\Framework\ObjectManager; | |
| + | |
| +/** | |
| + * Implemented by ObjectManager configs that track types that must not be lazy-loaded on PHP 8.4+. | |
| + */ | |
| +interface LazyTypeAwareInterface | |
| +{ | |
| + /** | |
| + * Whether the given concrete type was flagged at compile-time as incompatible with PHP 8.4 lazy ghosts. | |
| + * | |
| + * Fails safe: Returns true (= non-lazy) if no compile-time data is present. | |
| + * | |
| + * @param string $type | |
| + * @return bool | |
| + */ | |
| + public function isNonLazyType(string $type): bool; | |
| +} | |
| diff --git a/vendor/magento/magento2-base/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/vendor/magento/magento2-base/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php | |
| --- a/vendor/magento/magento2-base/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php | |
| +++ b/vendor/magento/magento2-base/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php | |
| @@ -347,6 +347,9 @@ | |
| 'InterceptionPreferencesResolving' => [ | |
| 'instance' => \Magento\Setup\Module\Di\Compiler\Config\Chain\PreferencesResolving::class | |
| ], | |
| + 'NonLazyTypes' => [ | |
| + 'instance' => \Magento\Setup\Module\Di\Compiler\Config\Chain\NonLazyTypes::class | |
| + ], | |
| ] | |
| ] | |
| ], \Magento\Setup\Module\Di\Code\Generator\PluginList::class => [ | |
| diff --git a/vendor/magento/magento2-base/setup/src/Magento/Setup/Module/Di/Compiler/Config/Chain/NonLazyTypes.php b/vendor/magento/magento2-base/setup/src/Magento/Setup/Module/Di/Compiler/Config/Chain/NonLazyTypes.php | |
| --- /dev/null | |
| +++ b/vendor/magento/magento2-base/setup/src/Magento/Setup/Module/Di/Compiler/Config/Chain/NonLazyTypes.php | |
| @@ -0,0 +1,83 @@ | |
| +<?php | |
| +/** | |
| + * Copyright 2026 Mage-OS | |
| + * All Rights Reserved. | |
| + */ | |
| +declare(strict_types=1); | |
| + | |
| +namespace Magento\Setup\Module\Di\Compiler\Config\Chain; | |
| + | |
| +use Magento\Setup\Module\Di\Compiler\Config\ModificationInterface; | |
| + | |
| +/** | |
| + * Compile-time scanner that flags concrete types as non-lazy when they are PHP-incompatible | |
| + * with newLazyGhost: interfaces, abstracts, traits, final/enum/readonly classes, and classes | |
| + * extending internal PHP classes (e.g. ArrayObject, DateTime). Variant F: cost filter removed | |
| + * — every PHP-compatible class is lazy-eligible regardless of constructor arg count. | |
| + */ | |
| +class NonLazyTypes implements ModificationInterface | |
| +{ | |
| + | |
| + public function modify(array $config): array | |
| + { | |
| + if (\PHP_VERSION_ID < 80400) { | |
| + return $config; | |
| + } | |
| + | |
| + $candidates = []; | |
| + foreach (array_keys($config['arguments'] ?? []) as $type) { | |
| + $candidates[(string)$type] = true; | |
| + } | |
| + foreach ($config['instanceTypes'] ?? [] as $concrete) { | |
| + if (is_string($concrete) && $concrete !== '') { | |
| + $candidates[$concrete] = true; | |
| + } | |
| + } | |
| + foreach ($config['preferences'] ?? [] as $impl) { | |
| + if (is_string($impl) && $impl !== '') { | |
| + $candidates[$impl] = true; | |
| + } | |
| + } | |
| + | |
| + $nonLazy = []; | |
| + foreach (array_keys($candidates) as $class) { | |
| + if (!$this->isLazyEligible($class)) { | |
| + $nonLazy[$class] = true; | |
| + } | |
| + } | |
| + | |
| + $config['nonLazyTypes'] = $nonLazy; | |
| + return $config; | |
| + } | |
| + | |
| + private function isLazyEligible(string $class): bool | |
| + { | |
| + if (str_ends_with($class, '\\Proxy')) { | |
| + return false; | |
| + } | |
| + | |
| + try { | |
| + if (!class_exists($class)) { | |
| + return false; | |
| + } | |
| + $ref = new \ReflectionClass($class); | |
| + } catch (\Throwable) { | |
| + return false; | |
| + } | |
| + | |
| + // PHP-level disqualifiers | |
| + if ($ref->isInterface() || $ref->isAbstract() || $ref->isTrait()) { | |
| + return false; | |
| + } | |
| + if ($ref->isFinal() || $ref->isEnum() || $ref->isReadOnly()) { | |
| + return false; | |
| + } | |
| + for ($current = $ref; $current; $current = $current->getParentClass()) { | |
| + if ($current->isInternal()) { | |
| + return false; | |
| + } | |
| + } | |
| + | |
| + return true; | |
| + } | |
| +} |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Magento 2.4.8 vendor port of Mage-OS PR #225 — PHP 8.4 lazy ghost objects in DI. Non-test parts only, adapted to
vendor/magento/frameworkandvendor/magento/magento2-base.Apply via
vaimo/composer-patchesPlace the file under
m2-patches/and add tocomposer.json:Then run
composer patch:apply.