Skip to content

Instantly share code, notes, and snippets.

@rhoerr
Last active May 6, 2026 03:25
Show Gist options
  • Select an option

  • Save rhoerr/0aa69fd2fec4abac51b6d832ccf672c8 to your computer and use it in GitHub Desktop.

Select an option

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!
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;
+ }
+}
@rhoerr
Copy link
Copy Markdown
Author

rhoerr commented May 5, 2026

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/framework and vendor/magento/magento2-base.

Apply via vaimo/composer-patches

Place the file under m2-patches/ and add to composer.json:

"extra": {
    "patches": {
        "magento/project-community-edition": {
            "Lazy object loading (Mage-OS #225)": "m2-patches/m2-lazy-ghost-objects.patch"
        }
    }
}

Then run composer patch:apply.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment