diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index 39eeb1e91d1..7b3bc4bb813 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -3450,53 +3450,9 @@ - - newInstance()]]> - newInstanceArgs(array_map(function (ReflectionParameter $parameter) { - $parameterType = $parameter->getType(); - - $resolveName = $parameter->getName(); - - // try to find out if it is a class or a simple parameter - if ($parameterType !== null && ($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) { - $resolveName = $parameterType->getName(); - } - - try { - $builtIn = $parameter->hasType() && ($parameter->getType() instanceof ReflectionNamedType) - && $parameter->getType()->isBuiltin(); - return $this->query($resolveName, !$builtIn); - } catch (QueryException $e) { - // Service not found, use the default value when available - if ($parameter->isDefaultValueAvailable()) { - return $parameter->getDefaultValue(); - } - - if ($parameterType !== null && ($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) { - $resolveName = $parameter->getName(); - try { - return $this->query($resolveName); - } catch (QueryException $e2) { - // Pass null if typed and nullable - if ($parameter->allowsNull() && ($parameterType instanceof ReflectionNamedType)) { - return null; - } - - // don't lose the error we got while trying to query by type - throw new QueryException($e->getMessage(), (int)$e->getCode(), $e); - } - } - - throw $e; - } - }, $constructor->getParameters()))]]> - - - - getCode()]]> diff --git a/config/config.sample.php b/config/config.sample.php index ff494592f4d..947666e0895 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -2744,4 +2744,12 @@ $CONFIG = [ * Defaults to true. */ 'files.trash.delete' => true, + +/** + * Enable lazy objects feature from PHP 8.4 to be used in the Dependency Injection. + * Should improve performances by avoiding buiding unused objects. + * + * Defaults to true. + */ +'enable_lazy_objects' => true, ]; diff --git a/lib/base.php b/lib/base.php index 2b08137aff2..55856889489 100644 --- a/lib/base.php +++ b/lib/base.php @@ -618,6 +618,9 @@ class OC { } $loaderEnd = microtime(true); + // Enable lazy loading if activated + \OC\AppFramework\Utility\SimpleContainer::$useLazyObjects = (bool)self::$config->getValue('enable_lazy_objects', true); + // setup the basic server self::$server = new \OC\Server(\OC::$WEBROOT, self::$config); self::$server->boot(); diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php index 9af65a37ab8..481c12cc708 100644 --- a/lib/private/AppFramework/Utility/SimpleContainer.php +++ b/lib/private/AppFramework/Utility/SimpleContainer.php @@ -12,6 +12,7 @@ use Closure; use OCP\AppFramework\QueryException; use OCP\IContainer; use Pimple\Container; +use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use ReflectionClass; use ReflectionException; @@ -23,8 +24,9 @@ use function class_exists; * SimpleContainer is a simple implementation of a container on basis of Pimple */ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { - /** @var Container */ - private $container; + public static bool $useLazyObjects = false; + + private Container $container; public function __construct() { $this->container = new Container(); @@ -49,16 +51,29 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { /** * @param ReflectionClass $class the class to instantiate - * @return \stdClass the created class + * @return object the created class * @suppress PhanUndeclaredClassInstanceof */ - private function buildClass(ReflectionClass $class) { + private function buildClass(ReflectionClass $class): object { $constructor = $class->getConstructor(); if ($constructor === null) { + /* No constructor, return a instance directly */ return $class->newInstance(); } + if (PHP_VERSION_ID >= 80400 && self::$useLazyObjects) { + /* For PHP>=8.4, use a lazy ghost to delay constructor and dependency resolving */ + /** @psalm-suppress UndefinedMethod */ + return $class->newLazyGhost(function (object $object) use ($constructor): void { + /** @psalm-suppress DirectConstructorCall For lazy ghosts we have to call the constructor directly */ + $object->__construct(...$this->buildClassConstructorParameters($constructor)); + }); + } else { + return $class->newInstanceArgs($this->buildClassConstructorParameters($constructor)); + } + } - return $class->newInstanceArgs(array_map(function (ReflectionParameter $parameter) { + private function buildClassConstructorParameters(\ReflectionMethod $constructor): array { + return array_map(function (ReflectionParameter $parameter) { $parameterType = $parameter->getType(); $resolveName = $parameter->getName(); @@ -69,10 +84,10 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { } try { - $builtIn = $parameter->hasType() && ($parameter->getType() instanceof ReflectionNamedType) - && $parameter->getType()->isBuiltin(); + $builtIn = $parameterType !== null && ($parameterType instanceof ReflectionNamedType) + && $parameterType->isBuiltin(); return $this->query($resolveName, !$builtIn); - } catch (QueryException $e) { + } catch (ContainerExceptionInterface $e) { // Service not found, use the default value when available if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue(); @@ -82,7 +97,7 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { $resolveName = $parameter->getName(); try { return $this->query($resolveName); - } catch (QueryException $e2) { + } catch (ContainerExceptionInterface $e2) { // Pass null if typed and nullable if ($parameter->allowsNull() && ($parameterType instanceof ReflectionNamedType)) { return null; @@ -95,7 +110,7 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { throw $e; } - }, $constructor->getParameters())); + }, $constructor->getParameters()); } public function resolve($name) { diff --git a/lib/public/AppFramework/App.php b/lib/public/AppFramework/App.php index 6860de7c324..eec5c6e83e9 100644 --- a/lib/public/AppFramework/App.php +++ b/lib/public/AppFramework/App.php @@ -69,6 +69,12 @@ class App { $step['args'][1] === $classNameParts[1]) { $setUpViaQuery = true; break; + } elseif (isset($step['class'], $step['function'], $step['args'][0]) && + $step['class'] === \ReflectionClass::class && + $step['function'] === 'initializeLazyObject' && + $step['args'][0] === $this) { + $setUpViaQuery = true; + break; } } diff --git a/tests/lib/AppFramework/Utility/SimpleContainerTest.php b/tests/lib/AppFramework/Utility/SimpleContainerTest.php index 754da8e5fb3..a1cc1e76aeb 100644 --- a/tests/lib/AppFramework/Utility/SimpleContainerTest.php +++ b/tests/lib/AppFramework/Utility/SimpleContainerTest.php @@ -36,7 +36,9 @@ class ClassComplexConstructor { } class ClassNullableUntypedConstructorArg { + public $class; public function __construct($class) { + $this->class = $class; } } class ClassNullableTypedConstructorArg { @@ -217,6 +219,8 @@ class SimpleContainerTest extends \Test\TestCase { $object = $this->container->query( 'Test\AppFramework\Utility\ClassComplexConstructor' ); + /* Use the object to trigger DI on PHP >= 8.4 */ + get_object_vars($object); } public function testRegisterFactory(): void { @@ -243,7 +247,11 @@ class SimpleContainerTest extends \Test\TestCase { public function testQueryUntypedNullable(): void { $this->expectException(\OCP\AppFramework\QueryException::class); - $this->container->query(ClassNullableUntypedConstructorArg::class); + $object = $this->container->query( + ClassNullableUntypedConstructorArg::class + ); + /* Use the object to trigger DI on PHP >= 8.4 */ + get_object_vars($object); } public function testQueryTypedNullable(): void {