annotate core/lib/Drupal/Component/ProxyBuilder/ProxyBuilder.php @ 19:fa3358dc1485 tip

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