173 lines
7.1 KiB
PHP
173 lines
7.1 KiB
PHP
|
<?php
|
||
|
/**
|
||
|
* AbstractServiceProvider class file.
|
||
|
*/
|
||
|
|
||
|
namespace Automattic\WooCommerce\Internal\DependencyManagement;
|
||
|
|
||
|
use Automattic\WooCommerce\Vendor\League\Container\Argument\RawArgument;
|
||
|
use Automattic\WooCommerce\Vendor\League\Container\Definition\DefinitionInterface;
|
||
|
use Automattic\WooCommerce\Vendor\League\Container\ServiceProvider\AbstractServiceProvider as BaseServiceProvider;
|
||
|
|
||
|
/**
|
||
|
* Base class for the service providers used to register classes in the container.
|
||
|
*
|
||
|
* See the documentation of the original class this one is based on (https://container.thephpleague.com/3.x/service-providers)
|
||
|
* for basic usage details. What this class adds is:
|
||
|
*
|
||
|
* - The `add_with_auto_arguments` method that allows to register classes without having to specify the injection method arguments.
|
||
|
* - The `share_with_auto_arguments` method, sibling of the above.
|
||
|
* - Convenience `add` and `share` methods that are just proxies for the same methods in `$this->getContainer()`.
|
||
|
*/
|
||
|
abstract class AbstractServiceProvider extends BaseServiceProvider {
|
||
|
|
||
|
/**
|
||
|
* Register a class in the container and use reflection to guess the injection method arguments.
|
||
|
*
|
||
|
* WARNING: this method uses reflection, so please have performance in mind when using it.
|
||
|
*
|
||
|
* @param string $class_name Class name to register.
|
||
|
* @param mixed $concrete The concrete to register. Can be a shared instance, a factory callback, or a class name.
|
||
|
* @param bool $shared Whether to register the class as shared (`get` always returns the same instance) or not.
|
||
|
*
|
||
|
* @return DefinitionInterface The generated container definition.
|
||
|
*
|
||
|
* @throws ContainerException Error when reflecting the class, or class injection method is not public, or an argument has no valid type hint.
|
||
|
*/
|
||
|
protected function add_with_auto_arguments( string $class_name, $concrete = null, bool $shared = false ) : DefinitionInterface {
|
||
|
$definition = new Definition( $class_name, $concrete );
|
||
|
|
||
|
$function = $this->reflect_class_or_callable( $class_name, $concrete );
|
||
|
|
||
|
if ( ! is_null( $function ) ) {
|
||
|
$arguments = $function->getParameters();
|
||
|
foreach ( $arguments as $argument ) {
|
||
|
if ( $argument->isDefaultValueAvailable() ) {
|
||
|
$default_value = $argument->getDefaultValue();
|
||
|
$definition->addArgument( new RawArgument( $default_value ) );
|
||
|
} else {
|
||
|
$argument_class = $this->get_class( $argument );
|
||
|
if ( is_null( $argument_class ) ) {
|
||
|
throw new ContainerException( "Argument '{$argument->getName()}' of class '$class_name' doesn't have a type hint or has one that doesn't specify a class." );
|
||
|
}
|
||
|
|
||
|
$definition->addArgument( $argument_class->name );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Register the definition only after being sure that no exception will be thrown.
|
||
|
$this->getContainer()->add( $definition->getAlias(), $definition, $shared );
|
||
|
|
||
|
return $definition;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the class of a parameter.
|
||
|
*
|
||
|
* This method is a replacement for ReflectionParameter::getClass,
|
||
|
* which is deprecated as of PHP 8.
|
||
|
*
|
||
|
* @param \ReflectionParameter $parameter The parameter to get the class for.
|
||
|
*
|
||
|
* @return \ReflectionClass|null The class of the parameter, or null if it hasn't any.
|
||
|
*/
|
||
|
private function get_class( \ReflectionParameter $parameter ) {
|
||
|
// TODO: Remove this 'if' block once minimum PHP version for WooCommerce is bumped to at least 7.1.
|
||
|
if ( version_compare( PHP_VERSION, '7.1', '<' ) ) {
|
||
|
return $parameter->getClass();
|
||
|
}
|
||
|
|
||
|
return $parameter->getType() && ! $parameter->getType()->isBuiltin()
|
||
|
? new \ReflectionClass( $parameter->getType()->getName() )
|
||
|
: null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if a combination of class name and concrete is valid for registration.
|
||
|
* Also return the class injection method if the concrete is either a class name or null (then use the supplied class name).
|
||
|
*
|
||
|
* @param string $class_name The class name to check.
|
||
|
* @param mixed $concrete The concrete to check.
|
||
|
*
|
||
|
* @return \ReflectionFunctionAbstract|null A reflection instance for the $class_name injection method or $concrete injection method or callable; null otherwise.
|
||
|
* @throws ContainerException Class has a private injection method, can't reflect class, or the concrete is invalid.
|
||
|
*/
|
||
|
private function reflect_class_or_callable( string $class_name, $concrete ) {
|
||
|
if ( ! isset( $concrete ) || is_string( $concrete ) && class_exists( $concrete ) ) {
|
||
|
try {
|
||
|
$class = $concrete ?? $class_name;
|
||
|
$method = new \ReflectionMethod( $class, Definition::INJECTION_METHOD );
|
||
|
if ( ! isset( $method ) ) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
$missing_modifiers = array();
|
||
|
if ( ! $method->isFinal() ) {
|
||
|
$missing_modifiers[] = 'final';
|
||
|
}
|
||
|
if ( ! $method->isPublic() ) {
|
||
|
$missing_modifiers[] = 'public';
|
||
|
}
|
||
|
if ( ! empty( $missing_modifiers ) ) {
|
||
|
throw new ContainerException( "Method '" . Definition::INJECTION_METHOD . "' of class '$class' isn't '" . implode( ' ', $missing_modifiers ) . "', instances can't be created." );
|
||
|
}
|
||
|
|
||
|
return $method;
|
||
|
} catch ( \ReflectionException $ex ) {
|
||
|
return null;
|
||
|
}
|
||
|
} elseif ( is_callable( $concrete ) ) {
|
||
|
try {
|
||
|
return new \ReflectionFunction( $concrete );
|
||
|
} catch ( \ReflectionException $ex ) {
|
||
|
throw new ContainerException( "Error when reflecting callable: {$ex->getMessage()}" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register a class in the container and use reflection to guess the injection method arguments.
|
||
|
* The class is registered as shared, so `get` on the container always returns the same instance.
|
||
|
*
|
||
|
* WARNING: this method uses reflection, so please have performance in mind when using it.
|
||
|
*
|
||
|
* @param string $class_name Class name to register.
|
||
|
* @param mixed $concrete The concrete to register. Can be a shared instance, a factory callback, or a class name.
|
||
|
*
|
||
|
* @return DefinitionInterface The generated container definition.
|
||
|
*
|
||
|
* @throws ContainerException Error when reflecting the class, or class injection method is not public, or an argument has no valid type hint.
|
||
|
*/
|
||
|
protected function share_with_auto_arguments( string $class_name, $concrete = null ) : DefinitionInterface {
|
||
|
return $this->add_with_auto_arguments( $class_name, $concrete, true );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register an entry in the container.
|
||
|
*
|
||
|
* @param string $id Entry id (typically a class or interface name).
|
||
|
* @param mixed|null $concrete Concrete entity to register under that id, null for automatic creation.
|
||
|
* @param bool|null $shared Whether to register the class as shared (`get` always returns the same instance) or not.
|
||
|
*
|
||
|
* @return DefinitionInterface The generated container definition.
|
||
|
*/
|
||
|
protected function add( string $id, $concrete = null, bool $shared = null ) : DefinitionInterface {
|
||
|
return $this->getContainer()->add( $id, $concrete, $shared );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register a shared entry in the container (`get` always returns the same instance).
|
||
|
*
|
||
|
* @param string $id Entry id (typically a class or interface name).
|
||
|
* @param mixed|null $concrete Concrete entity to register under that id, null for automatic creation.
|
||
|
*
|
||
|
* @return DefinitionInterface The generated container definition.
|
||
|
*/
|
||
|
protected function share( string $id, $concrete = null ) : DefinitionInterface {
|
||
|
return $this->add( $id, $concrete, true );
|
||
|
}
|
||
|
}
|