Merge pull request #52538 from nextcloud/feat/use-php84-lazy-objects

Use PHP 8.4 lazy ghosts for Dependency injection
pull/53419/head
Côme Chilliet 4 months ago committed by GitHub
commit 231916d403
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 44
      build/psalm-baseline.xml
  2. 8
      config/config.sample.php
  3. 3
      lib/base.php
  4. 35
      lib/private/AppFramework/Utility/SimpleContainer.php
  5. 6
      lib/public/AppFramework/App.php
  6. 10
      tests/lib/AppFramework/Utility/SimpleContainerTest.php

@ -3450,53 +3450,9 @@
</MoreSpecificImplementedParamType>
</file>
<file src="lib/private/AppFramework/Utility/SimpleContainer.php">
<LessSpecificReturnStatement>
<code><![CDATA[$class->newInstance()]]></code>
<code><![CDATA[$class->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()))]]></code>
</LessSpecificReturnStatement>
<MissingTemplateParam>
<code><![CDATA[ArrayAccess]]></code>
</MissingTemplateParam>
<MoreSpecificReturnType>
<code><![CDATA[\stdClass]]></code>
</MoreSpecificReturnType>
<RedundantCast>
<code><![CDATA[(int)$e->getCode()]]></code>
</RedundantCast>

@ -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,
];

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

@ -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) {

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

@ -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 {

Loading…
Cancel
Save