ProxyBuilder.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. <?php
  2. namespace Drupal\Component\ProxyBuilder;
  3. /**
  4. * Generates the string representation of the proxy service.
  5. */
  6. class ProxyBuilder {
  7. /**
  8. * Generates the used proxy class name from a given class name.
  9. *
  10. * @param string $class_name
  11. * The class name of the actual service.
  12. *
  13. * @return string
  14. * The class name of the proxy.
  15. */
  16. public static function buildProxyClassName($class_name) {
  17. $match = [];
  18. preg_match('/([a-zA-Z0-9_]+\\\\[a-zA-Z0-9_]+)\\\\(.+)/', $class_name, $match);
  19. $root_namespace = $match[1];
  20. $rest_fqcn = $match[2];
  21. $proxy_class_name = $root_namespace . '\\ProxyClass\\' . $rest_fqcn;
  22. return $proxy_class_name;
  23. }
  24. /**
  25. * Generates the used proxy namespace from a given class name.
  26. *
  27. * @param string $class_name
  28. * The class name of the actual service.
  29. *
  30. * @return string
  31. * The namespace name of the proxy.
  32. */
  33. public static function buildProxyNamespace($class_name) {
  34. $proxy_classname = static::buildProxyClassName($class_name);
  35. preg_match('/(.+)\\\\[a-zA-Z0-9]+/', $proxy_classname, $match);
  36. $proxy_namespace = $match[1];
  37. return $proxy_namespace;
  38. }
  39. /**
  40. * Builds a proxy class string.
  41. *
  42. * @param string $class_name
  43. * The class name of the actual service.
  44. * @param string $proxy_class_name
  45. * (optional) The class name of the proxy service.
  46. *
  47. * @return string
  48. * The full string with namespace class and methods.
  49. */
  50. public function build($class_name, $proxy_class_name = '') {
  51. $reflection = new \ReflectionClass($class_name);
  52. if ($proxy_class_name) {
  53. $proxy_class_reflection = new \ReflectionClass($proxy_class_name);
  54. $proxy_namespace = $proxy_class_reflection->getNamespaceName();
  55. }
  56. else {
  57. $proxy_class_name = $this->buildProxyClassName($class_name);
  58. $proxy_namespace = $this->buildProxyNamespace($class_name);
  59. $proxy_class_shortname = str_replace($proxy_namespace . '\\', '', $proxy_class_name);
  60. }
  61. $output = '';
  62. $class_documentation = <<<'EOS'
  63. namespace {{ namespace }}{
  64. /**
  65. * Provides a proxy class for \{{ class_name }}.
  66. *
  67. * @see \Drupal\Component\ProxyBuilder
  68. */
  69. EOS;
  70. $class_start = ' class {{ proxy_class_shortname }}';
  71. // For cases in which the implemented interface is a child of another
  72. // interface, getInterfaceNames() also returns the parent. This causes a
  73. // PHP error.
  74. // In order to avoid that, check for each interface, whether one of its
  75. // parents is also in the list and exclude it.
  76. if ($interfaces = $reflection->getInterfaces()) {
  77. foreach ($interfaces as $interface_name => $interface) {
  78. // Exclude all parents from the list of implemented interfaces of the
  79. // class.
  80. if ($parent_interfaces = $interface->getInterfaceNames()) {
  81. foreach ($parent_interfaces as $parent_interface) {
  82. unset($interfaces[$parent_interface]);
  83. }
  84. }
  85. }
  86. $interface_names = [];
  87. foreach ($interfaces as $interface) {
  88. $interface_names[] = '\\' . $interface->getName();
  89. }
  90. $class_start .= ' implements ' . implode(', ', $interface_names);
  91. }
  92. $output .= $this->buildUseStatements();
  93. // The actual class;
  94. $properties = <<<'EOS'
  95. /**
  96. * The id of the original proxied service.
  97. *
  98. * @var string
  99. */
  100. protected $drupalProxyOriginalServiceId;
  101. /**
  102. * The real proxied service, after it was lazy loaded.
  103. *
  104. * @var \{{ class_name }}
  105. */
  106. protected $service;
  107. /**
  108. * The service container.
  109. *
  110. * @var \Symfony\Component\DependencyInjection\ContainerInterface
  111. */
  112. protected $container;
  113. EOS;
  114. $output .= $properties;
  115. // Add all the methods.
  116. $methods = [];
  117. $methods[] = $this->buildConstructorMethod();
  118. $methods[] = $this->buildLazyLoadItselfMethod();
  119. // Add all the methods of the proxied service.
  120. $reflection_methods = $reflection->getMethods();
  121. foreach ($reflection_methods as $method) {
  122. if ($method->getName() === '__construct') {
  123. continue;
  124. }
  125. if ($method->isPublic()) {
  126. $methods[] = $this->buildMethod($method) . "\n";
  127. }
  128. }
  129. $output .= implode("\n", $methods);
  130. // Indent the output.
  131. $output = implode("\n", array_map(function ($value) {
  132. if ($value === '') {
  133. return $value;
  134. }
  135. return " $value";
  136. }, explode("\n", $output)));
  137. $final_output = $class_documentation . $class_start . "\n {\n\n" . $output . "\n }\n\n}\n";
  138. $final_output = str_replace('{{ class_name }}', $class_name, $final_output);
  139. $final_output = str_replace('{{ namespace }}', $proxy_namespace ? $proxy_namespace . ' ' : '', $final_output);
  140. $final_output = str_replace('{{ proxy_class_shortname }}', $proxy_class_shortname, $final_output);
  141. return $final_output;
  142. }
  143. /**
  144. * Generates the string for the method which loads the actual service.
  145. *
  146. * @return string
  147. */
  148. protected function buildLazyLoadItselfMethod() {
  149. $output = <<<'EOS'
  150. /**
  151. * Lazy loads the real service from the container.
  152. *
  153. * @return object
  154. * Returns the constructed real service.
  155. */
  156. protected function lazyLoadItself()
  157. {
  158. if (!isset($this->service)) {
  159. $this->service = $this->container->get($this->drupalProxyOriginalServiceId);
  160. }
  161. return $this->service;
  162. }
  163. EOS;
  164. return $output;
  165. }
  166. /**
  167. * Generates the string representation of a single method: signature, body.
  168. *
  169. * @param \ReflectionMethod $reflection_method
  170. * A reflection method for the method.
  171. *
  172. * @return string
  173. */
  174. protected function buildMethod(\ReflectionMethod $reflection_method) {
  175. $parameters = [];
  176. foreach ($reflection_method->getParameters() as $parameter) {
  177. $parameters[] = $this->buildParameter($parameter);
  178. }
  179. $function_name = $reflection_method->getName();
  180. $reference = '';
  181. if ($reflection_method->returnsReference()) {
  182. $reference = '&';
  183. }
  184. $signature_line = <<<'EOS'
  185. /**
  186. * {@inheritdoc}
  187. */
  188. EOS;
  189. if ($reflection_method->isStatic()) {
  190. $signature_line .= 'public static function ' . $reference . $function_name . '(';
  191. }
  192. else {
  193. $signature_line .= 'public function ' . $reference . $function_name . '(';
  194. }
  195. $signature_line .= implode(', ', $parameters);
  196. $signature_line .= ')';
  197. $output = $signature_line . "\n{\n";
  198. $output .= $this->buildMethodBody($reflection_method);
  199. $output .= "\n" . '}';
  200. return $output;
  201. }
  202. /**
  203. * Builds a string for a single parameter of a method.
  204. *
  205. * @param \ReflectionParameter $parameter
  206. * A reflection object of the parameter.
  207. *
  208. * @return string
  209. */
  210. protected function buildParameter(\ReflectionParameter $parameter) {
  211. $parameter_string = '';
  212. if ($parameter->isArray()) {
  213. $parameter_string .= 'array ';
  214. }
  215. elseif ($parameter->isCallable()) {
  216. $parameter_string .= 'callable ';
  217. }
  218. elseif ($class = $parameter->getClass()) {
  219. $parameter_string .= '\\' . $class->getName() . ' ';
  220. }
  221. if ($parameter->isPassedByReference()) {
  222. $parameter_string .= '&';
  223. }
  224. $parameter_string .= '$' . $parameter->getName();
  225. if ($parameter->isDefaultValueAvailable()) {
  226. $parameter_string .= ' = ';
  227. $parameter_string .= var_export($parameter->getDefaultValue(), TRUE);
  228. }
  229. return $parameter_string;
  230. }
  231. /**
  232. * Builds the body of a wrapped method.
  233. *
  234. * @param \ReflectionMethod $reflection_method
  235. * A reflection method for the method.
  236. *
  237. * @return string
  238. */
  239. protected function buildMethodBody(\ReflectionMethod $reflection_method) {
  240. $output = '';
  241. $function_name = $reflection_method->getName();
  242. if (!$reflection_method->isStatic()) {
  243. $output .= ' return $this->lazyLoadItself()->' . $function_name . '(';
  244. }
  245. else {
  246. $class_name = $reflection_method->getDeclaringClass()->getName();
  247. $output .= " \\$class_name::$function_name(";
  248. }
  249. // Add parameters;
  250. $parameters = [];
  251. foreach ($reflection_method->getParameters() as $parameter) {
  252. $parameters[] = '$' . $parameter->getName();
  253. }
  254. $output .= implode(', ', $parameters) . ');';
  255. return $output;
  256. }
  257. /**
  258. * Builds the constructor used to inject the actual service ID.
  259. *
  260. * @return string
  261. */
  262. protected function buildConstructorMethod() {
  263. $output = <<<'EOS'
  264. /**
  265. * Constructs a ProxyClass Drupal proxy object.
  266. *
  267. * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
  268. * The container.
  269. * @param string $drupal_proxy_original_service_id
  270. * The service ID of the original service.
  271. */
  272. public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
  273. {
  274. $this->container = $container;
  275. $this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
  276. }
  277. EOS;
  278. return $output;
  279. }
  280. /**
  281. * Build the required use statements of the proxy class.
  282. *
  283. * @return string
  284. */
  285. protected function buildUseStatements() {
  286. $output = '';
  287. return $output;
  288. }
  289. }