Some small imrpovements in the dependency injection framework:

- camelCase methods changed to snake_case for consistency with WP.
- Added a check in `ExtendedContainer::get` that throws an informative
  exception if a non-namespaced class name is passed.
- `container->reset_resolved()` is called during unit testing bootstrap.
- Added some utility methods in `WC_Unit_Test_Case`.
This commit is contained in:
Nestor Soriano 2020-06-25 09:47:25 +02:00
parent d5d02a7175
commit d55f7d10f8
8 changed files with 71 additions and 38 deletions

View File

@ -17,8 +17,8 @@ use League\Container\Definition\Definition;
* 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 `addWithAutoArguments` method that allows to register classes without having to specify the constructor arguments.
* - The `shareWithAutoArguments` method, sibling of the above.
* - The `add_with_auto_arguments` method that allows to register classes without having to specify the constructor 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()`.
*
* @package Automattic\WooCommerce\Tools\DependencyManagement
@ -38,7 +38,7 @@ abstract class AbstractServiceProvider extends \League\Container\ServiceProvider
*
* @throws \Exception Error when reflecting the class, or class constructor is not public, or an argument has no valid type hint.
*/
public function addWithAutoArguments( string $class_name, $concrete = null, bool $shared = false ) : DefinitionInterface {
public function add_with_auto_arguments( string $class_name, $concrete = null, bool $shared = false ) : DefinitionInterface {
try {
$reflector = new \ReflectionClass( $class_name );
} catch ( \ReflectionException $ex ) {
@ -90,8 +90,8 @@ abstract class AbstractServiceProvider extends \League\Container\ServiceProvider
*
* @throws \Exception Error when reflecting the class, or class constructor is not public, or an argument has no valid type hint.
*/
public function shareWithAutoArguments( string $class_name, $concrete = null ) : DefinitionInterface {
return $this->addWithAutoArguments( $class_name, $concrete, true );
public function share_with_auto_arguments( string $class_name, $concrete = null ) : DefinitionInterface {
return $this->add_with_auto_arguments( $class_name, $concrete, true );
}
/**

View File

@ -37,7 +37,7 @@ class ExtendedContainer extends \League\Container\Container {
* @throws \Exception Invalid parameters.
*/
public function add( string $id, $concrete = null, bool $shared = null ) : DefinitionInterface {
if ( ! $this->class_is_in_root_namespace( $id ) && ! in_array( $id, $this->registration_whitelist ) ) {
if ( ! $this->class_is_in_root_namespace( $id ) && ! in_array( $id, $this->registration_whitelist, true ) ) {
throw new \Exception( "Can't use the container to register '$id', only objects in the " . Container::WOOCOMMERCE_ROOT_NAMESPACE . ' namespace are allowed for registration.' );
}
@ -82,4 +82,21 @@ class ExtendedContainer extends \League\Container\Container {
$definition->setConcrete( $concrete );
}
}
/**
* Get an instance of a registered class.
*
* @param string $id The class name.
* @param bool $new True to generate a new instance even if the class was registered as shared.
*
* @return object An instance of the requested class.
* @throws \Exception Attempt to get an instance of a non-namespaced class.
*/
public function get( $id, bool $new = false ) {
if ( false === strpos( $id, '\\' ) ) {
throw new \Exception( "Attempt to get an instance of the non-namespaced class '$id' from the container, did you forget to add a namespace import?" );
}
return parent::get( $id, $new );
}
}

View File

@ -31,6 +31,6 @@ class ProxiesServiceProvider extends AbstractServiceProvider {
*/
public function register() {
$this->share( ActionsProxy::class );
$this->shareWithAutoArguments( LegacyProxy::class );
$this->share_with_auto_arguments( LegacyProxy::class );
}
}

View File

@ -25,7 +25,7 @@ class ActionsProxy {
*
* @return int The number of times action hook $tag is fired.
*/
public function didAction( $tag ) {
public function did_action( $tag ) {
return did_action( $tag );
}
@ -38,7 +38,7 @@ class ActionsProxy {
*
* @return mixed The filtered value after all hooked functions are applied to it.
*/
public function applyFilters( $tag, $value, ...$parameters ) {
public function apply_filters( $tag, $value, ...$parameters ) {
return apply_filters( $tag, $value, ...$parameters );
}

View File

@ -22,22 +22,6 @@ use \Psr\Container\ContainerInterface as Container;
*/
class LegacyProxy {
/**
* Holds the instance of the container to use.
*
* @var Container
*/
private $container;
/**
* Creates a new instance of the class.
*
* @param Container $container The container to use.
*/
public function __construct( Container $container ) {
$this->container = $container;
}
/**
* Gets an instance of a given legacy class.
* This must not be used to get instances of classes in the `src` directory.
@ -47,14 +31,14 @@ class LegacyProxy {
* @return object The instance of the class.
* @throws \Exception The requested class belongs to the `src` directory, or there was an error creating an instance of the class.
*/
public function getInstanceOf( string $class_name ) {
if ( $this->container->has( $class_name ) ) {
public function get_instance_of( string $class_name ) {
if ( false !== strpos( $class_name, '\\' ) ) {
throw new \Exception( 'The LegacyProxy class is not intended for getting instances of classes in the src directory, please use constructor injection or the instance of \\Psr\\Container\\ContainerInterface for that.' );
}
try {
// If a class has a special procedure to obtain a instance, use it.
$instance = $this->getSpecialInstanceOf( $class_name );
$instance = $this->get_special_instance_of( $class_name );
if ( ! is_null( $instance ) ) {
return $instance;
}
@ -78,7 +62,7 @@ class LegacyProxy {
*
* @return object|null The instance, or null if there's no special procedure for that class.
*/
private function getSpecialInstanceOf( string $class_name ) {
private function get_special_instance_of( string $class_name ) {
if ( \WC_Queue_Interface::class === $class_name ) {
return \WC_Queue::instance();
}
@ -95,7 +79,7 @@ class LegacyProxy {
*
* @return mixed The result from the function.
*/
public function callFunction( $function_name, ...$parameters ) {
public function call_function( $function_name, ...$parameters ) {
return call_user_func_array( $function_name, $parameters );
}
@ -109,7 +93,7 @@ class LegacyProxy {
*
* @return mixed The result from the method.
*/
public function callStatic( $class_name, $method_name, ...$parameters ) {
public function call_static( $class_name, $method_name, ...$parameters ) {
return call_user_func_array( "$class_name::$method_name", $parameters );
}
}

View File

@ -118,12 +118,12 @@ class MockableLegacyProxy extends \Automattic\WooCommerce\Proxies\LegacyProxy {
*
* @return mixed The result from the function or mock callback.
*/
public function callFunction( $function_name, ...$parameters ) {
public function call_function( $function_name, ...$parameters ) {
if ( array_key_exists( $function_name, $this->mocked_functions ) ) {
return call_user_func_array( $this->mocked_functions[ $function_name ], $parameters );
}
return parent::callFunction( $function_name, ...$parameters );
return parent::call_function( $function_name, ...$parameters );
}
/**
@ -139,7 +139,7 @@ class MockableLegacyProxy extends \Automattic\WooCommerce\Proxies\LegacyProxy {
*
* @return mixed The result from the method or mock callback.
*/
public function callStatic( $class_name, $method_name, ...$parameters ) {
public function call_static( $class_name, $method_name, ...$parameters ) {
if ( array_key_exists( $class_name, $this->mocked_statics ) ) {
$class_mocks = $this->mocked_statics[ $class_name ];
if ( array_key_exists( $method_name, $class_mocks ) ) {
@ -148,7 +148,7 @@ class MockableLegacyProxy extends \Automattic\WooCommerce\Proxies\LegacyProxy {
}
}
return parent::callStatic( $class_name, $method_name, ...$parameters );
return parent::call_static( $class_name, $method_name, ...$parameters );
}
/**
@ -163,12 +163,12 @@ class MockableLegacyProxy extends \Automattic\WooCommerce\Proxies\LegacyProxy {
* @return object The (possibly mocked) instance of the class.
* @throws \Exception The requested class belongs to the `src` directory, or there was an error creating an instance of the class.
*/
public function getInstanceOf( string $class_name ) {
public function get_instance_of( string $class_name ) {
if ( array_key_exists( $class_name, $this->mocked_classes ) ) {
$mock = $this->mocked_classes[ $class_name ];
return is_callable( $mock ) ? call_user_func( $mock, $class_name ) : $mock;
}
return parent::getInstanceOf( $class_name );
return parent::get_instance_of( $class_name );
}
}

View File

@ -11,7 +11,6 @@ use Automattic\WooCommerce\Testing\Tools\CodeHacking\CodeHacker;
use Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks\StaticMockerHack;
use Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks\FunctionsMockerHack;
use Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks\BypassFinalsHack;
use Automattic\WooCommerce\Testing\Tools\DependencyManagement\MockableLegacyProxy;
/**
@ -127,6 +126,7 @@ class WC_Unit_Tests_Bootstrap {
$inner_container = $inner_container_property->getValue( wc_get_container() );
$inner_container->replace( LegacyProxy::class, MockableLegacyProxy::class );
$inner_container->reset_resolved();
$GLOBALS['wc_container'] = $inner_container;
}

View File

@ -152,6 +152,31 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase {
return $result;
}
/**
* Get an instance of a class that has been registered in the dependency injection container.
* To get an instance of a legacy class (such as the ones in the 'íncludes' directory) use
* 'get_legacy_instance_of' instead.
*
* @param string $class_name The class name to get an instance of.
*
* @return mixed The instance.
*/
public function get_instance_of( string $class_name ) {
return wc_get_container()->get( $class_name );
}
/**
* Get an instance of legacy class (such as the ones in the 'íncludes' directory).
* To get an instance of a class registered in the dependency injection container use 'get_instance_of' instead.
*
* @param string $class_name The class name to get an instance of.
*
* @return mixed The instance.
*/
public function get_legacy_instance_of( string $class_name ) {
return wc_get_container()->get( LegacyProxy::class )->get_instance_of( $class_name );
}
/**
* Reset all the cached resolutions in the dependency injection container, so any further "get"
* for shared definitions will generate the instance again.
@ -161,6 +186,13 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase {
wc_get_container()->reset_resolved();
}
/**
* Reset the mock legacy proxy class so that all the registered mocks are unregistered.
*/
public function reset_legacy_proxy_mocks() {
wc_get_container()->get( LegacyProxy::class )->reset();
}
/**
* Register the function mocks to use in the mockable LegacyProxy.
*