ServerContainer.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  6. * SPDX-License-Identifier: AGPL-3.0-only
  7. */
  8. namespace OC;
  9. use OC\AppFramework\App;
  10. use OC\AppFramework\DependencyInjection\DIContainer;
  11. use OC\AppFramework\Utility\SimpleContainer;
  12. use OCP\AppFramework\QueryException;
  13. use function explode;
  14. use function strtolower;
  15. /**
  16. * Class ServerContainer
  17. *
  18. * @package OC
  19. */
  20. class ServerContainer extends SimpleContainer {
  21. /** @var DIContainer[] */
  22. protected $appContainers;
  23. /** @var string[] */
  24. protected $hasNoAppContainer;
  25. /** @var string[] */
  26. protected $namespaces;
  27. /**
  28. * ServerContainer constructor.
  29. */
  30. public function __construct() {
  31. parent::__construct();
  32. $this->appContainers = [];
  33. $this->namespaces = [];
  34. $this->hasNoAppContainer = [];
  35. }
  36. /**
  37. * @param string $appName
  38. * @param string $appNamespace
  39. */
  40. public function registerNamespace(string $appName, string $appNamespace): void {
  41. // Cut of OCA\ and lowercase
  42. $appNamespace = strtolower(substr($appNamespace, strrpos($appNamespace, '\\') + 1));
  43. $this->namespaces[$appNamespace] = $appName;
  44. }
  45. /**
  46. * @param string $appName
  47. * @param DIContainer $container
  48. */
  49. public function registerAppContainer(string $appName, DIContainer $container): void {
  50. $this->appContainers[strtolower(App::buildAppNamespace($appName, ''))] = $container;
  51. }
  52. /**
  53. * @param string $appName
  54. * @return DIContainer
  55. * @throws QueryException
  56. */
  57. public function getRegisteredAppContainer(string $appName): DIContainer {
  58. if (isset($this->appContainers[strtolower(App::buildAppNamespace($appName, ''))])) {
  59. return $this->appContainers[strtolower(App::buildAppNamespace($appName, ''))];
  60. }
  61. throw new QueryException();
  62. }
  63. /**
  64. * @param string $namespace
  65. * @param string $sensitiveNamespace
  66. * @return DIContainer
  67. * @throws QueryException
  68. */
  69. protected function getAppContainer(string $namespace, string $sensitiveNamespace): DIContainer {
  70. if (isset($this->appContainers[$namespace])) {
  71. return $this->appContainers[$namespace];
  72. }
  73. if (isset($this->namespaces[$namespace])) {
  74. if (!isset($this->hasNoAppContainer[$namespace])) {
  75. $applicationClassName = 'OCA\\' . $sensitiveNamespace . '\\AppInfo\\Application';
  76. if (class_exists($applicationClassName)) {
  77. $app = new $applicationClassName();
  78. if (isset($this->appContainers[$namespace])) {
  79. $this->appContainers[$namespace]->offsetSet($applicationClassName, $app);
  80. return $this->appContainers[$namespace];
  81. }
  82. }
  83. $this->hasNoAppContainer[$namespace] = true;
  84. }
  85. return new DIContainer($this->namespaces[$namespace]);
  86. }
  87. throw new QueryException();
  88. }
  89. public function has($id, bool $noRecursion = false): bool {
  90. if (!$noRecursion && ($appContainer = $this->getAppContainerForService($id)) !== null) {
  91. return $appContainer->has($id);
  92. }
  93. return parent::has($id);
  94. }
  95. /**
  96. * @template T
  97. * @param class-string<T>|string $name
  98. * @return T|mixed
  99. * @psalm-template S as class-string<T>|string
  100. * @psalm-param S $name
  101. * @psalm-return (S is class-string<T> ? T : mixed)
  102. * @throws QueryException
  103. * @deprecated 20.0.0 use \Psr\Container\ContainerInterface::get
  104. */
  105. public function query(string $name, bool $autoload = true) {
  106. $name = $this->sanitizeName($name);
  107. if (str_starts_with($name, 'OCA\\')) {
  108. // Skip server container query for app namespace classes
  109. try {
  110. return parent::query($name, false);
  111. } catch (QueryException $e) {
  112. // Continue with general autoloading then
  113. }
  114. }
  115. // In case the service starts with OCA\ we try to find the service in
  116. // the apps container first.
  117. if (($appContainer = $this->getAppContainerForService($name)) !== null) {
  118. try {
  119. return $appContainer->queryNoFallback($name);
  120. } catch (QueryException $e) {
  121. // Didn't find the service or the respective app container
  122. // In this case the service won't be part of the core container,
  123. // so we can throw directly
  124. throw $e;
  125. }
  126. } elseif (str_starts_with($name, 'OC\\Settings\\') && substr_count($name, '\\') >= 3) {
  127. $segments = explode('\\', $name);
  128. try {
  129. $appContainer = $this->getAppContainer(strtolower($segments[1]), $segments[1]);
  130. return $appContainer->queryNoFallback($name);
  131. } catch (QueryException $e) {
  132. // Didn't find the service or the respective app container,
  133. // ignore it and fall back to the core container.
  134. }
  135. }
  136. return parent::query($name, $autoload);
  137. }
  138. /**
  139. * @internal
  140. * @param string $id
  141. * @return DIContainer|null
  142. */
  143. public function getAppContainerForService(string $id): ?DIContainer {
  144. if (!str_starts_with($id, 'OCA\\') || substr_count($id, '\\') < 2) {
  145. return null;
  146. }
  147. try {
  148. [,$namespace,] = explode('\\', $id);
  149. return $this->getAppContainer(strtolower($namespace), $namespace);
  150. } catch (QueryException $e) {
  151. return null;
  152. }
  153. }
  154. }