2013-08-20 11:21:14 -04:00
< ? php
2024-05-23 03:26:56 -04:00
2014-12-13 13:28:20 -05:00
/**
2024-05-23 03:26:56 -04:00
* SPDX - FileCopyrightText : 2016 - 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX - FileCopyrightText : 2016 ownCloud , Inc .
* SPDX - License - Identifier : AGPL - 3.0 - only
2014-12-13 13:28:20 -05:00
*/
2013-08-20 11:21:14 -04:00
namespace OC\AppFramework\Utility ;
2020-07-13 05:18:14 -04:00
use ArrayAccess ;
2015-07-17 11:18:37 -04:00
use Closure ;
use OCP\AppFramework\QueryException ;
use OCP\IContainer ;
2019-11-22 14:52:10 -05:00
use Pimple\Container ;
2025-04-29 10:47:32 -04:00
use Psr\Container\ContainerExceptionInterface ;
2020-07-13 05:18:14 -04:00
use Psr\Container\ContainerInterface ;
2025-06-16 12:00:28 -04:00
use Psr\Log\LoggerInterface ;
2019-11-22 14:52:10 -05:00
use ReflectionClass ;
use ReflectionException ;
2022-01-19 17:30:34 -05:00
use ReflectionNamedType ;
2023-11-23 04:22:34 -05:00
use ReflectionParameter ;
2020-07-13 05:18:14 -04:00
use function class_exists ;
2014-12-13 13:28:20 -05:00
2013-08-20 11:21:14 -04:00
/**
2020-07-13 05:18:14 -04:00
* SimpleContainer is a simple implementation of a container on basis of Pimple
2013-08-20 11:21:14 -04:00
*/
2020-07-13 05:18:14 -04:00
class SimpleContainer implements ArrayAccess , ContainerInterface , IContainer {
2025-05-14 12:42:42 -04:00
public static bool $useLazyObjects = false ;
2025-04-29 09:54:20 -04:00
private Container $container ;
2020-07-13 05:18:14 -04:00
public function __construct () {
$this -> container = new Container ();
}
2022-07-28 01:50:17 -04:00
/**
* @ template T
* @ param class - string < T >| string $id
* @ return T | mixed
* @ psalm - template S as class - string < T >| string
* @ psalm - param S $id
* @ psalm - return ( S is class - string < T > ? T : mixed )
*/
2024-04-02 11:13:35 -04:00
public function get ( string $id ) : mixed {
2020-07-13 05:18:14 -04:00
return $this -> query ( $id );
}
2013-08-20 11:21:14 -04:00
2021-03-08 14:06:10 -05:00
public function has ( string $id ) : bool {
2020-07-13 05:18:14 -04:00
// If a service is no registered but is an existing class, we can probably load it
return isset ( $this -> container [ $id ]) || class_exists ( $id );
}
2014-12-13 13:28:20 -05:00
/**
2015-07-17 11:18:37 -04:00
* @ param ReflectionClass $class the class to instantiate
2025-04-29 09:54:20 -04:00
* @ return object the created class
2018-03-06 08:02:50 -05:00
* @ suppress PhanUndeclaredClassInstanceof
2014-12-13 13:28:20 -05:00
*/
2025-04-29 09:54:20 -04:00
private function buildClass ( ReflectionClass $class ) : object {
2014-12-13 13:28:20 -05:00
$constructor = $class -> getConstructor ();
if ( $constructor === null ) {
2025-04-29 09:54:20 -04:00
/* No constructor, return a instance directly */
2014-12-13 13:28:20 -05:00
return $class -> newInstance ();
2020-07-13 05:18:14 -04:00
}
2025-09-28 08:35:49 -04:00
if ( PHP_VERSION_ID >= 80400 && self :: $useLazyObjects && ! $class -> isInternal ()) {
2025-04-29 10:47:32 -04:00
/* 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 ));
}
}
2014-12-13 13:28:20 -05:00
2025-04-29 10:47:32 -04:00
private function buildClassConstructorParameters ( \ReflectionMethod $constructor ) : array {
return array_map ( function ( ReflectionParameter $parameter ) {
$parameterType = $parameter -> getType ();
2020-11-10 15:05:32 -05:00
2025-04-29 10:47:32 -04:00
$resolveName = $parameter -> getName ();
2020-07-13 05:18:14 -04:00
2025-04-29 10:47:32 -04:00
// try to find out if it is a class or a simple parameter
if ( $parameterType !== null && ( $parameterType instanceof ReflectionNamedType ) && ! $parameterType -> isBuiltin ()) {
$resolveName = $parameterType -> getName ();
}
2020-07-13 05:18:14 -04:00
2025-04-29 10:47:32 -04:00
try {
$builtIn = $parameterType !== null && ( $parameterType instanceof ReflectionNamedType )
&& $parameterType -> isBuiltin ();
return $this -> query ( $resolveName , ! $builtIn );
} catch ( ContainerExceptionInterface $e ) {
// Service not found, use the default value when available
if ( $parameter -> isDefaultValueAvailable ()) {
return $parameter -> getDefaultValue ();
}
2014-12-13 13:28:20 -05:00
2025-04-29 10:47:32 -04:00
if ( $parameterType !== null && ( $parameterType instanceof ReflectionNamedType ) && ! $parameterType -> isBuiltin ()) {
$resolveName = $parameter -> getName ();
try {
return $this -> query ( $resolveName );
} catch ( ContainerExceptionInterface $e2 ) {
// Pass null if typed and nullable
if ( $parameter -> allowsNull () && ( $parameterType instanceof ReflectionNamedType )) {
return null ;
2023-11-03 06:53:43 -04:00
}
2025-04-29 11:32:15 -04:00
// don't lose the error we got while trying to query by type
throw new QueryException ( $e -> getMessage (), ( int ) $e -> getCode (), $e );
2021-02-08 13:13:25 -05:00
}
2025-04-29 09:54:20 -04:00
}
2025-04-29 10:47:32 -04:00
throw $e ;
}
}, $constructor -> getParameters ());
2014-12-13 13:28:20 -05:00
}
2015-09-13 05:35:21 -04:00
public function resolve ( $name ) {
2014-12-13 13:28:20 -05:00
$baseMsg = 'Could not resolve ' . $name . '!' ;
try {
2015-07-17 11:18:37 -04:00
$class = new ReflectionClass ( $name );
2014-12-13 13:28:20 -05:00
if ( $class -> isInstantiable ()) {
return $this -> buildClass ( $class );
} else {
2025-06-30 09:04:05 -04:00
throw new QueryException ( $baseMsg
. ' Class can not be instantiated' );
2014-12-13 13:28:20 -05:00
}
2020-04-10 08:19:56 -04:00
} catch ( ReflectionException $e ) {
2023-01-27 21:41:24 -05:00
// Class does not exist
throw new QueryNotFoundException ( $baseMsg . ' ' . $e -> getMessage ());
2014-12-13 13:28:20 -05:00
}
}
2019-05-30 08:20:15 -04:00
public function query ( string $name , bool $autoload = true ) {
2015-07-30 06:29:06 -04:00
$name = $this -> sanitizeName ( $name );
2020-07-13 05:18:14 -04:00
if ( isset ( $this -> container [ $name ])) {
return $this -> container [ $name ];
}
if ( $autoload ) {
2014-12-13 13:28:20 -05:00
$object = $this -> resolve ( $name );
$this -> registerService ( $name , function () use ( $object ) {
return $object ;
});
return $object ;
}
2020-07-13 05:18:14 -04:00
2023-01-27 21:41:24 -05:00
throw new QueryNotFoundException ( 'Could not resolve ' . $name . '!' );
2013-08-20 11:21:14 -04:00
}
2014-10-22 09:07:16 -04:00
/**
* @ param string $name
* @ param mixed $value
*/
2015-04-20 06:52:40 -04:00
public function registerParameter ( $name , $value ) {
2013-08-20 11:21:14 -04:00
$this [ $name ] = $value ;
}
/**
* The given closure is call the first time the given service is queried .
* The closure has to return the instance for the given service .
* Created instance will be cached in case $shared is true .
*
* @ param string $name name of the service to register another backend for
2015-07-17 11:18:37 -04:00
* @ param Closure $closure the closure to be called on service creation
2014-10-22 09:07:16 -04:00
* @ param bool $shared
2013-08-20 11:21:14 -04:00
*/
2015-07-17 11:18:37 -04:00
public function registerService ( $name , Closure $closure , $shared = true ) {
2020-07-13 05:18:14 -04:00
$wrapped = function () use ( $closure ) {
return $closure ( $this );
};
2015-07-30 06:29:06 -04:00
$name = $this -> sanitizeName ( $name );
2025-05-06 11:18:41 -04:00
if ( isset ( $this -> container [ $name ])) {
unset ( $this -> container [ $name ]);
2014-11-03 07:10:03 -05:00
}
2013-08-20 11:21:14 -04:00
if ( $shared ) {
2025-05-06 11:18:41 -04:00
$this -> container [ $name ] = $wrapped ;
2014-11-03 07:10:03 -05:00
} else {
2025-05-06 11:18:41 -04:00
$this -> container [ $name ] = $this -> container -> factory ( $wrapped );
2013-08-20 11:21:14 -04:00
}
}
2014-12-13 13:28:20 -05:00
2015-07-17 11:18:37 -04:00
/**
* Shortcut for returning a service from a service under a different key ,
* e . g . to tell the container to return a class when queried for an
* interface
* @ param string $alias the alias that should be registered
* @ param string $target the target that should be resolved instead
*/
2025-06-16 12:00:28 -04:00
public function registerAlias ( $alias , $target ) : void {
$this -> registerService ( $alias , function ( ContainerInterface $container ) use ( $target ) : mixed {
return $container -> get ( $target );
}, false );
}
protected function registerDeprecatedAlias ( string $alias , string $target ) : void {
$this -> registerService ( $alias , function ( ContainerInterface $container ) use ( $target , $alias ) : mixed {
try {
$logger = $container -> get ( LoggerInterface :: class );
2025-08-06 10:11:25 -04:00
$logger -> debug ( 'The requested alias "' . $alias . '" is deprecated. Please request "' . $target . '" directly. This alias will be removed in a future Nextcloud version.' , [
'app' => $this -> appName ? ? 'serverDI' ,
]);
2025-06-16 12:00:28 -04:00
} catch ( ContainerExceptionInterface $e ) {
// Could not get logger. Continue
}
2020-07-13 05:18:14 -04:00
return $container -> get ( $target );
2015-07-24 07:43:50 -04:00
}, false );
2015-07-17 11:18:37 -04:00
}
2014-12-13 13:28:20 -05:00
2025-02-17 09:01:33 -05:00
/**
2015-07-30 06:29:06 -04:00
* @ param string $name
* @ return string
*/
protected function sanitizeName ( $name ) {
2016-09-16 03:52:52 -04:00
if ( isset ( $name [ 0 ]) && $name [ 0 ] === '\\' ) {
return ltrim ( $name , '\\' );
}
return $name ;
2015-07-30 06:29:06 -04:00
}
2020-07-13 05:18:14 -04:00
/**
* @ deprecated 20.0 . 0 use \Psr\Container\ContainerInterface :: has
*/
2021-10-19 11:11:53 -04:00
public function offsetExists ( $id ) : bool {
2020-07-13 05:18:14 -04:00
return $this -> container -> offsetExists ( $id );
}
/**
* @ deprecated 20.0 . 0 use \Psr\Container\ContainerInterface :: get
2021-10-19 11:11:53 -04:00
* @ return mixed
2020-07-13 05:18:14 -04:00
*/
2021-10-19 11:11:53 -04:00
#[\ReturnTypeWillChange]
2020-07-13 05:18:14 -04:00
public function offsetGet ( $id ) {
return $this -> container -> offsetGet ( $id );
}
/**
* @ deprecated 20.0 . 0 use \OCP\IContainer :: registerService
*/
2024-04-22 12:11:32 -04:00
public function offsetSet ( $offset , $value ) : void {
$this -> container -> offsetSet ( $offset , $value );
2020-07-13 05:18:14 -04:00
}
/**
* @ deprecated 20.0 . 0
*/
2021-10-19 11:11:53 -04:00
public function offsetUnset ( $offset ) : void {
2020-07-13 05:18:14 -04:00
$this -> container -> offsetUnset ( $offset );
}
2013-08-20 11:21:14 -04:00
}