Merge branch 'master' into add/woorelease-support

This commit is contained in:
Claudio Sanches 2020-08-19 12:10:15 -03:00
commit 98d5f52de2
29 changed files with 36 additions and 1862 deletions

View File

@ -1,5 +1,11 @@
== Changelog == == Changelog ==
= 4.4.1 - 2020-08-19 =
**WooCommerce**
* Fix - Add protection to run adjust methods only if product query. #27396
* Dev - Stripped the internals of the DI Container to address plugin dependency conflicts it caused. #27395
= 4.4.0 - 2020-08-18 = = 4.4.0 - 2020-08-18 =
**WooCommerce** **WooCommerce**

View File

@ -11,9 +11,9 @@
"automattic/jetpack-autoloader": "2.2.0", "automattic/jetpack-autoloader": "2.2.0",
"automattic/jetpack-constants": "1.4.0", "automattic/jetpack-constants": "1.4.0",
"composer/installers": "1.7.0", "composer/installers": "1.7.0",
"league/container": "3.3.1",
"maxmind-db/reader": "1.6.0", "maxmind-db/reader": "1.6.0",
"pelago/emogrifier": "3.1.0", "pelago/emogrifier": "3.1.0",
"psr/container": "^1.0",
"woocommerce/action-scheduler": "3.1.6", "woocommerce/action-scheduler": "3.1.6",
"woocommerce/woocommerce-admin": "1.5.0-rc.1", "woocommerce/woocommerce-admin": "1.5.0-rc.1",
"woocommerce/woocommerce-blocks": "3.1.0" "woocommerce/woocommerce-blocks": "3.1.0"

72
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "42e4de440843075e3074f98fa1ef5b61", "content-hash": "7f82f8a4f08f7f4f966dc2ef68081293",
"packages": [ "packages": [
{ {
"name": "automattic/jetpack-autoloader", "name": "automattic/jetpack-autoloader",
@ -195,72 +195,6 @@
], ],
"time": "2019-08-12T15:00:31+00:00" "time": "2019-08-12T15:00:31+00:00"
}, },
{
"name": "league/container",
"version": "3.3.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/container.git",
"reference": "93238f74ff5964aee27a78508cdfbdba1cd338f6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/container/zipball/93238f74ff5964aee27a78508cdfbdba1cd338f6",
"reference": "93238f74ff5964aee27a78508cdfbdba1cd338f6",
"shasum": ""
},
"require": {
"php": "^7.0",
"psr/container": "^1.0"
},
"provide": {
"psr/container-implementation": "^1.0"
},
"replace": {
"orno/di": "~2.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0",
"squizlabs/php_codesniffer": "^3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-3.x": "3.x-dev",
"dev-2.x": "2.x-dev",
"dev-1.x": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"League\\Container\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Phil Bennett",
"email": "philipobenito@gmail.com",
"homepage": "http://www.philipobenito.com",
"role": "Developer"
}
],
"description": "A fast and intuitive dependency injection container.",
"homepage": "https://github.com/thephpleague/container",
"keywords": [
"container",
"dependency",
"di",
"injection",
"league",
"provider",
"service"
],
"time": "2020-05-18T08:20:23+00:00"
},
{ {
"name": "maxmind-db/reader", "name": "maxmind-db/reader",
"version": "v1.6.0", "version": "v1.6.0",
@ -1735,6 +1669,7 @@
"keywords": [ "keywords": [
"tokenizer" "tokenizer"
], ],
"abandoned": true,
"time": "2019-09-17T06:23:10+00:00" "time": "2019-09-17T06:23:10+00:00"
}, },
{ {
@ -3002,5 +2937,6 @@
"platform-dev": [], "platform-dev": [],
"platform-overrides": { "platform-overrides": {
"php": "7.1" "php": "7.1"
} },
"plugin-api-version": "1.1.0"
} }

View File

@ -44,8 +44,6 @@ class WC_Query {
add_filter( 'query_vars', array( $this, 'add_query_vars' ), 0 ); add_filter( 'query_vars', array( $this, 'add_query_vars' ), 0 );
add_action( 'parse_request', array( $this, 'parse_request' ), 0 ); add_action( 'parse_request', array( $this, 'parse_request' ), 0 );
add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) ); add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
add_filter( 'the_posts', array( $this, 'handle_get_posts' ) );
add_filter( 'found_posts', array( $this, 'adjust_posts_count' ), 10, 2 );
add_filter( 'get_pagenum_link', array( $this, 'remove_add_to_cart_pagination' ), 10, 1 ); add_filter( 'get_pagenum_link', array( $this, 'remove_add_to_cart_pagination' ), 10, 1 );
} }
$this->init_query_vars(); $this->init_query_vars();
@ -356,10 +354,14 @@ class WC_Query {
* Handler for the 'the_posts' WP filter. * Handler for the 'the_posts' WP filter.
* *
* @param array $posts Posts from WP Query. * @param array $posts Posts from WP Query.
* @param WP_Query $query Current query.
* *
* @return array * @return array
*/ */
public function handle_get_posts( $posts ) { public function handle_get_posts( $posts, $query ) {
if ( 'product_query' !== $query->get( 'wc_query' ) ) {
return $posts;
}
$this->adjust_total_pages(); $this->adjust_total_pages();
$this->remove_product_query_filters( $posts ); $this->remove_product_query_filters( $posts );
return $posts; return $posts;
@ -511,7 +513,8 @@ class WC_Query {
// Additonal hooks to change WP Query. // Additonal hooks to change WP Query.
add_filter( 'posts_clauses', array( $this, 'price_filter_post_clauses' ), 10, 2 ); add_filter( 'posts_clauses', array( $this, 'price_filter_post_clauses' ), 10, 2 );
add_filter( 'the_posts', array( $this, 'handle_get_posts' ), 10, 2 );
add_filter( 'found_posts', array( $this, 'adjust_posts_count' ), 10, 2 );
do_action( 'woocommerce_product_query', $q, $this ); do_action( 'woocommerce_product_query', $q, $this );
} }

View File

@ -912,60 +912,4 @@ final class WooCommerce {
public function is_wc_admin_active() { public function is_wc_admin_active() {
return function_exists( 'wc_admin_url' ); return function_exists( 'wc_admin_url' );
} }
/**
* Call a user function. This should be used to execute any non-idempotent function, especially
* those in the `includes` directory or provided by WordPress.
*
* This method can be useful for unit tests, since functions called using this method
* can be easily mocked by using WC_Unit_Test_Case::register_legacy_proxy_function_mocks.
*
* @param string $function_name The function to execute.
* @param mixed ...$parameters The parameters to pass to the function.
*
* @return mixed The result from the function.
*
* @since 4.4
*/
public function call_function( $function_name, ...$parameters ) {
return wc_get_container()->get( LegacyProxy::class )->call_function( $function_name, ...$parameters );
}
/**
* Call a static method in a class. This should be used to execute any non-idempotent method in classes
* from the `includes` directory.
*
* This method can be useful for unit tests, since methods called using this method
* can be easily mocked by using WC_Unit_Test_Case::register_legacy_proxy_static_mocks.
*
* @param string $class_name The name of the class containing the method.
* @param string $method_name The name of the method.
* @param mixed ...$parameters The parameters to pass to the method.
*
* @return mixed The result from the method.
*
* @since 4.4
*/
public function call_static( $class_name, $method_name, ...$parameters ) {
return wc_get_container()->get( LegacyProxy::class )->call_static( $class_name, $method_name, ...$parameters );
}
/**
* Gets an instance of a given legacy class.
* This must not be used to get instances of classes in the `src` directory.
*
* This method can be useful for unit tests, since objects obtained using this method
* can be easily mocked by using WC_Unit_Test_Case::register_legacy_proxy_class_mocks.
*
* @param string $class_name The name of the class to get an instance for.
* @param mixed ...$args Parameters to be passed to the class constructor or to the appropriate internal 'get_instance_of_' method.
*
* @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.
*
* @since 4.4
*/
public function get_instance_of( string $class_name, ...$args ) {
return wc_get_container()->get( LegacyProxy::class )->get_instance_of( $class_name, ...$args );
}
} }

View File

@ -4,7 +4,7 @@ Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, d
Requires at least: 5.2 Requires at least: 5.2
Tested up to: 5.5 Tested up to: 5.5
Requires PHP: 7.0 Requires PHP: 7.0
Stable tag: 4.4.0 Stable tag: 4.4.1
License: GPLv3 License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html License URI: https://www.gnu.org/licenses/gpl-3.0.html
@ -179,147 +179,11 @@ INTERESTED IN DEVELOPMENT?
== Changelog == == Changelog ==
= 4.4.0 - 2020-08-18 = = 4.4.1 - 2020-08-19 =
**WooCommerce** **WooCommerce**
* Accessibility: Adds alt attribute to photoswipe gallery images. #26945 * Fix - Add protection to run adjust methods only if product query. #27396
* Enhancement - Remove the privacy page dropdown from the Accounts & Privacy page. #26809 * Dev - Stripped the internals of the DI Container to address plugin dependency conflicts it caused. #27395
* Enhancement - Added automatic language pack updates for WooCommerce.com extensions. #26750
* Enhancement - Improvements for the Hungarian address format. #26697
* Enhancement - Dropdown arrow width was made smaller. #26202
* Enhancement - Add a "No change" option to the "Stock status" selector in quick edit, preselect it when the product being edited is a variable product. #26174
* Enhancement - Don't request language packs for empty locales list. #27148
* Localization - Added 14 Namibia regions. #26894
* Localization - Change default Greek states names to English. #26719
* Localization - Improved Puerto Rico addresses and improve address formatting. #26698
* Localization - Wrapped price and currency inside a BDI tag, in order to prevent the bidirectional algorithm to produce confusing results. #26462
* Localization - Added Algerian provinces. #25687
* Tweak - Added "order_total" to the wcadmin_orders_edit_status_change tracker event. #26935
* Tweak - Fixed WooCommerce menu for users that can only manage orders on WooCommerce. #26877
* Tweak - Limit nocache headers to googleweblight by default. #26858
* Tweak - Preserve quantity input value when changing variations. #26805
* Tweak - Confirm before running any tool from the WooCommerce Status settings. #26660
* Tweak - Limit stock changes for order items to status methods for consistency. #26642
* Tweak - Custom vendor taxonomy update messages. #26634
* Tweak - Remove HTML tags from plain text email template for Customer new account. #26613
* Tweak - Conditionally change the text in My account to reflect if shipping is disabled. #26325
* Tweak - Show CSV file name in result message when product import is complete. #25240
* Tweak - Improve order details UI to highlight "Paid" and "Net Payment" sections. #27142
* Fix - Remove the dot after the generated password in new account emails. #27073
* Fix - Delayed the execution of all webhooks until after the request has completed. #27067
* Fix - [Importer/Exporter] Fixed the value display of "Published" for children of draft variable products. #27046
* Fix - Removed the extra id parameter added to CLI commands that shouldn't have one. #27017
* Fix - Added the missing instance_id to the REST CLI command so that shipping zone method commands will work. #27017
* Fix - Add rating_count to order by rating clause. #26964
* Fix - Don't show premium support forum link if the store is not connected to WooCommerce.com. #26932
* Fix - Incorrect capability used on add order note while creating an user note. #26920
* Fix - Preserve HTML entities from product names in the cart page. #26885
* Fix - Display warning hen leaving settings page without saving first. #26880
* Fix - Remove wc_round_tax_total from shipping tax because shipping prices never include tax so rounding down is not needed. #26850
* Fix - Make the "Please log in" message displayed to users with an existing account a hyperlink. #26837
* Fix - Typo in composer.json for makepot. #26829
* Fix - Layout issue on the checkout page when switching countries. #26697
* Fix - Missing closing select tag to the product exporter category select. #26680
* Fix - Possible PHP undefined index notice before WooCommerce has been configured. #26658
* Fix - A deferred product sync is now scheduled when a product having a parent (e.g. a variation product) is deleted, not only when it's saved. #26629
* Fix - Stock status of variable products that handle stock at the main product level is now appropriately updated when the product is saved. #26611
* Fix - Discounted prices are no longer underlined in Twenty Twenty. #26609
* Fix - Email link color clash. #26591
* Fix - Remove HTML from error message. #26589
* Fix - Fixed Tooltip flashing. #26558
* Fix - Correctly displays the instructional option as default in the select box for picking a Country / Region on the checkout page. #26554
* Fix - Default option "Select a country..." will now display accurately on Country select box in Cart shipping calculator. #26541
* Fix - Fixed user capability required to view the order count indicator. #26338
* Fix - The filtering widget now works as expected with variable products, displaying those products for which visible variations are available. #26260
* Fix - Added a z-index to the remove button (x) to set the z-order of the element. #26202
* Fix - Don't change the stock status of variations when bulk editing a variable product and leaving the "Stock status" selector as "No change". #26174
* Fix - Remove new WP 5.5 meta box arrows from "Order data" and "Order items" meta boxes. #27173
* Fix - After clicking to update WooCommerce, the user will stay in the same page instead of being redirected to the "Settings" page. #27172
* Fix - "Product type" dropdown missing from Product's data meta box on WP 5.5. #27170
* Fix - Removed the JETPACK_AUTOLOAD_DEV define. #27185
* Fix - Fixed "virtual" and "downlodable" pointers on product walkthrough. #27145
* Fix - Updated tested up to for WordPress 5.5. #27334
* Dev - Update WooCommerce Admin version to v1.4.0. #27378
* Dev - Upgraded to v2.2 of Jetpack Autoloader. #27358
* Dev - Update jest-preset-default version to ^6.2.0. #27090
* Dev - Added a second $existing_meta_keys parameter to the woocommerce_duplicate_product_exclude_meta filter. #27038
* Dev - Remove leftover note for translators in customer-completed-order.php. #26989
* Dev - Allow extend BACS accounts filter with order ID. #26961
* Dev - Add npm run build:packages to npm run build. #26906
* Dev - Add woocommerce_order_note_added action. #26846
* Dev - Add tests for template cache. #26840
* Dev - Add filter to allow disabling nocache headers. #26802
* Dev - Introduce a dependency injection framework for the code in the src directory. #26731
* Dev - Normalized parameters of woocommerce_product_importer_parsed_data filter. #26669
* Dev - Introduced new WC_Product_CSV_Importer::get_formatting_callback() fixing a typo in the method name. #26668
* Dev - Allow set "date_created" while creating orders via CRUD. #26567
* Dev - Allow set a custom as order key using wc_generate_order_key(). #26566
* Dev - Allow set order_key while creating an order via CRUD. #26565
* Dev - Introduced woocommerce_product_cross_sells_products_heading filter. #26545
* Dev - Added the removed_coupon_in_checkout event that is triggered on the Checkout page after a coupon is removed using .woocommerce-remove-coupon button. #26536
* Dev - Remove no longer used styles from TwentyTwenty. #26516
* Dev - Fix error message in wc_get_template() function. #26515
* Dev - Add npm publish script for @woocommerce/e2e-environment. #26432
* Dev - Make WC_Cart::display_prices_including_tax aware of tax display changes. #26400
* Dev - Deprecated WC_Legacy_Cart::tax_display_cart in favor of WC_Cart:: get_tax_price_display_mode(). #26400
* Dev - Add an optional $render_variations argument to in WC_Product_Variable::get_available_variation() in order to allow plugins to avoid performance bottlenecks. #26303
* Dev - Ensure wc_load_cart loads its own dependencies. #26219
* Dev - Clean up deprecated documentation. #27054
* Dev - Update WooCommerce Blocks version to 3.1.0. #27177
* Dev - Added woocommerce_order_item_quantity filter to ReserveStock::reserve_stock_for_order(). #27251
* Dev - Updated docs to make the type in docblock more specific. #27285
**REST API 1.0.15**
* Enhancement - Introduced X-WP-Total header for product attributes GET endpoint listing the number of entries in the response. woocommerce/woocommerce-rest-api#171
* Enhancement - Introduced X-WP-TotalPages header for product attributes GET endpoint listing the number of pages that can be fetched. woocommerce/woocommerce-rest-api#171
* Enhancement - Introduced the modified option for orderby fetch requests in post based resources. woocommerce/woocommerce-rest-api#226
* Enhancement - Compatibility fixes for WordPress 5.5. woocommerce/woocommerce-rest-api#232
* Fix - Ensured Action Scheduler transients are cleared by "Clear Transients" tool. woocommerce/woocommerce-rest-api#152
* Fix - Corrected the schema datatype for coupon expiry_date, date_expires, and date_expires_gmt fields. woocommerce/woocommerce-rest-api#176
* Fix - Query parameters are now passed correctly when using the batch product variation endpoints. woocommerce/woocommerce-rest-api#191
* Fix - Fix regression and restore backward compatibility for date-time and mixed data types. woocommerce/woocommerce-rest-api#238
**WooCommerce Admin 1.4.0**
* Enhancement - Move the WooCommerce > Coupons dashboard menu item to Marketing > Coupons. #4786
* Fix - Installation of child theme zip files from the store setup wizard. #4852
* Fix - Center the skip link on the theme selection step. #4847
* Fix - Removed item "profiler" from the menu. #4851
* Fix - PHP notices when hosts block certain WP scripts. #4856
* Fix - Remove new WP 5.5 meta box arrows in the shipping banner. #4914
* Fix - Allow revisiting of the payments task. #4918
* Fix - Use of Jetpack autoloader. #4920
* Fix - Only show WCPay task in US based stores. #4899
* Fix - Polyfill core-data saveUser() on WP 5.3.x. #4869
* Fix - Product types step bugs in onboarding wizard. #4900
* Fix - Center all descriptive text on onboarding wizard steps. #4902
* Fix - Match the requires version to the exact WordPress version number in readme.txt. #4956
* Fix - Change account required text on biz step in onboarding wizard. #4909
* Fix - Fix industry args type in REST API. #4974
* Fix - Update style on shipping banner. #4948
* Fix - CSS Fixes for Business Features Popover ( parts 1&2 ). #4994
* Dev - Add the experimental resolver to WCA data package. #4862
* Dev - Fix linter errors. #4904
* Dev - Fix usage of "package" tag in file headers. #4940
* Dev - Update Jetpack Autoloader to match Woo Core. #4993
**WooCommerce Blocks 3.0.0**
* Build - Updated the automattic/jetpack-autoloader package to the 2.0 branch. #2847
* Enhancement - Add support for the Bank Transfer (BACS) payment method in the Checkout block. #2821
* Enhancement - Several improvements to make Credit Card input fields display more consistent across different themes and viewport sizes. #2869
* Enhancement - Cart and Checkout blocks show a notification for products on backorder. #2833
* Enhancement - Chip styles of the Filter Products by Attribute and Active Filters have been updated to give a more consistent experience. #2765
* Enhancement - Add protection for rogue filters on order queries when executing cleanup draft orders logic. #2874
* Enhancement - Extend payment gateway extension API so gateways (payment methods) can dynamically disable (hide), based on checkout or order data (such as cart items or shipping method). For example, Cash on Delivery can limit availability to specific shipping methods only. #2840 [DN]
* Enhancement - Support Cash on Delivery core payment gateway in the Checkout block. #2831
* Performance - Don't load shortcode Cart and Checkout scripts when using the blocks. #2842
* Performance - Scripts only relevant to the frontend side of blocks are no longer loaded in the editor. #2788
* Performance - Lazy Loading Atomic Components. #2777
* Pefactor - Remove dashicon classes. #2848
**WooCommerce Blocks 3.1.0**
* Fix - Missing permissions_callback arg in StoreApi route definitions. #2926
* Fix - 'Product Summary' in All Products block is not pulling in the short description of the product. #2913
* Dev - Add query filter when searching for a table. #2886
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/master/changelog.txt). [See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/master/changelog.txt).

View File

@ -5,8 +5,9 @@
namespace Automattic\WooCommerce; namespace Automattic\WooCommerce;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider; use Psr\Container\ContainerExceptionInterface;
use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer; use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
/** /**
* PSR11 compliant dependency injection container for WooCommerce. * PSR11 compliant dependency injection container for WooCommerce.
@ -25,52 +26,19 @@ use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer;
* and those should go in the `src\Internal\DependencyManagement\ServiceProviders` folder unless there's a good reason * and those should go in the `src\Internal\DependencyManagement\ServiceProviders` folder unless there's a good reason
* to put them elsewhere. All the service provider class names must be in the `SERVICE_PROVIDERS` constant. * to put them elsewhere. All the service provider class names must be in the `SERVICE_PROVIDERS` constant.
*/ */
final class Container implements \Psr\Container\ContainerInterface { final class Container implements ContainerInterface {
/**
* The list of service provider classes to register.
*
* @var string[]
*/
private $service_providers = array(
ProxiesServiceProvider::class,
);
/**
* The underlying container.
*
* @var \League\Container\Container
*/
private $container;
/**
* Class constructor.
*/
public function __construct() {
$this->container = new ExtendedContainer();
// Add ourselves as the shared instance of ContainerInterface,
// register everything else using service providers.
$this->container->share( \Psr\Container\ContainerInterface::class, $this );
foreach ( $this->service_providers as $service_provider_class ) {
$this->container->addServiceProvider( $service_provider_class );
}
}
/** /**
* Finds an entry of the container by its identifier and returns it. * Finds an entry of the container by its identifier and returns it.
* *
* @param string $id Identifier of the entry to look for. * @param string $id Identifier of the entry to look for.
* *
* @throws NotFoundExceptionInterface No entry was found for **this** identifier. * @throws NotFoundExceptionInterface No entry was found for **this** identifier.
* @throws Psr\Container\ContainerExceptionInterface Error while retrieving the entry. * @throws ContainerExceptionInterface Error while retrieving the entry.
* *
* @return mixed Entry. * @return mixed Entry.
*/ */
public function get( $id ) { public function get( $id ) {
return $this->container->get( $id ); return null;
} }
/** /**
@ -85,6 +53,6 @@ final class Container implements \Psr\Container\ContainerInterface {
* @return bool * @return bool
*/ */
public function has( $id ) { public function has( $id ) {
return $this->container->has( $id ); return false;
} }
} }

View File

@ -1,150 +0,0 @@
<?php
/**
* AbstractServiceProvider class file.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement;
use League\Container\Argument\RawArgument;
use League\Container\Definition\DefinitionInterface;
/**
* 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 \League\Container\ServiceProvider\AbstractServiceProvider {
/**
* 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 = $argument->getClass();
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;
}
/**
* 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 );
}
}

View File

@ -1,23 +0,0 @@
<?php
/**
* ExtendedContainer class file.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement;
/**
* Class ContainerException.
* Used to signal error conditions related to the dependency injection container.
*/
class ContainerException extends \Exception {
/**
* Create a new instance of the class.
*
* @param null $message The exception message to throw.
* @param int $code The error code.
* @param Exception|null $previous The previous throwable used for exception chaining.
*/
public function __construct( $message = null, $code = 0, Exception $previous = null ) {
parent::__construct( $message, $code, $previous );
}
}

View File

@ -1,39 +0,0 @@
<?php
/**
* An extension to the Definition class to prevent constructor injection from being possible.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement;
use \League\Container\Definition\Definition as BaseDefinition;
/**
* An extension of the definition class that replaces constructor injection with method injection.
*/
class Definition extends BaseDefinition {
/**
* The standard method that we use for dependency injection.
*/
const INJECTION_METHOD = 'init';
/**
* Resolve a class using method injection instead of constructor injection.
*
* @param string $concrete The concrete to instantiate.
*
* @return object
*/
protected function resolveClass( string $concrete ) {
$resolved = $this->resolveArguments( $this->arguments );
$concrete = new $concrete();
// Constructor injection causes backwards compatibility problems
// so we will rely on method injection via an internal method.
if ( method_exists( $concrete, static::INJECTION_METHOD ) ) {
call_user_func_array( array( $concrete, static::INJECTION_METHOD ), $resolved );
}
return $concrete;
}
}

View File

@ -1,152 +0,0 @@
<?php
/**
* ExtendedContainer class file.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement;
use Automattic\WooCommerce\Utilities\StringUtil;
use League\Container\Container as BaseContainer;
use League\Container\Definition\DefinitionInterface;
/**
* This class extends the original League's Container object by adding some functionality
* that we need for WooCommerce.
*/
class ExtendedContainer extends BaseContainer {
/**
* The root namespace of all WooCommerce classes in the `src` directory.
*
* @var string
*/
private $woocommerce_namespace = 'Automattic\\WooCommerce\\';
/**
* Whitelist of classes that we can register using the container
* despite not belonging to the WooCommerce root namespace.
*
* In general we allow only the registration of classes in the
* WooCommerce root namespace to prevent registering 3rd party code
* (which doesn't really belong to this container) or old classes
* (which may be eventually deprecated, also the LegacyProxy
* should be used for those).
*
* @var string[]
*/
private $registration_whitelist = array(
\Psr\Container\ContainerInterface::class,
);
/**
* Register a class in the container.
*
* @param string $class_name Class name.
* @param mixed $concrete How to resolve the class with `get`: a factory callback, a concrete instance, another class name, or null to just create an instance of the class.
* @param bool|null $shared Whether the resolution should be performed only once and cached.
*
* @return DefinitionInterface The generated definition for the container.
* @throws ContainerException Invalid parameters.
*/
public function add( string $class_name, $concrete = null, bool $shared = null ) : DefinitionInterface {
if ( ! $this->is_class_allowed( $class_name ) ) {
throw new ContainerException( "You cannot add '$class_name', only classes in the {$this->woocommerce_namespace} namespace are allowed." );
}
$concrete_class = $this->get_class_from_concrete( $concrete );
if ( isset( $concrete_class ) && ! $this->is_class_allowed( $concrete_class ) ) {
throw new ContainerException( "You cannot add concrete '$concrete_class', only classes in the {$this->woocommerce_namespace} namespace are allowed." );
}
// We want to use a definition class that does not support constructor injection to avoid accidental usage.
if ( ! $concrete instanceof DefinitionInterface ) {
$concrete = new Definition( $class_name, $concrete );
}
return parent::add( $class_name, $concrete, $shared );
}
/**
* Replace an existing registration with a different concrete.
*
* @param string $class_name The class name whose definition will be replaced.
* @param mixed $concrete The new concrete (same as "add").
*
* @return DefinitionInterface The modified definition.
* @throws ContainerException Invalid parameters.
*/
public function replace( string $class_name, $concrete ) : DefinitionInterface {
if ( ! $this->has( $class_name ) ) {
throw new ContainerException( "The container doesn't have '$class_name' registered, please use 'add' instead of 'replace'." );
}
$concrete_class = $this->get_class_from_concrete( $concrete );
if ( isset( $concrete_class ) && ! $this->is_class_allowed( $concrete_class ) ) {
throw new ContainerException( "You cannot use concrete '$concrete_class', only classes in the {$this->woocommerce_namespace} namespace are allowed." );
}
return $this->extend( $class_name )->setConcrete( $concrete );
}
/**
* Reset all the cached resolutions, so any further "get" for shared definitions will generate the instance again.
*/
public function reset_all_resolved() {
foreach ( $this->definitions->getIterator() as $definition ) {
// setConcrete causes the cached resolved value to be forgotten.
$concrete = $definition->getConcrete();
$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 ContainerException Attempt to get an instance of a non-namespaced class.
*/
public function get( $id, bool $new = false ) {
if ( false === strpos( $id, '\\' ) ) {
throw new ContainerException( "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 );
}
/**
* Gets the class from the concrete regardless of type.
*
* @param mixed $concrete The concrete that we want the class from..
*
* @return string|null The class from the concrete if one is available, null otherwise.
*/
protected function get_class_from_concrete( $concrete ) {
if ( is_object( $concrete ) && ! is_callable( $concrete ) ) {
if ( $concrete instanceof DefinitionInterface ) {
return $this->get_class_from_concrete( $concrete->getConcrete() );
}
return get_class( $concrete );
}
if ( is_string( $concrete ) && class_exists( $concrete ) ) {
return $concrete;
}
return null;
}
/**
* Checks to see whether or not a class is allowed to be registered.
*
* @param string $class_name The class to check.
*
* @return bool True if the class is allowed to be registered, false otherwise.
*/
protected function is_class_allowed( string $class_name ): bool {
return StringUtil::starts_with( $class_name, $this->woocommerce_namespace, false ) || in_array( $class_name, $this->registration_whitelist, true );
}
}

View File

@ -1,34 +0,0 @@
<?php
/**
* Proxies class file.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Proxies\ActionsProxy;
/**
* Service provider for the classes in the Automattic\WooCommerce\Proxies namespace.
*/
class ProxiesServiceProvider extends AbstractServiceProvider {
/**
* The classes/interfaces that are serviced by this service provider.
*
* @var array
*/
protected $provides = array(
LegacyProxy::class,
ActionsProxy::class,
);
/**
* Register the classes.
*/
public function register() {
$this->share( ActionsProxy::class );
$this->share_with_auto_arguments( LegacyProxy::class );
}
}

View File

@ -1,5 +1,9 @@
# WooCommerce `src` files # WooCommerce `src` files
## Important note
The dependency injection container is disabled for now due to conflicts with plugins that use the same container package. Therefore all the content about registering and resolving classes, and interacting with legacy code, doesn't apply yet. It will be enabled at a later time.
## Table of contents ## Table of contents
* [Installing Composer](#installing-composer) * [Installing Composer](#installing-composer)

View File

@ -25,9 +25,6 @@ final class DependencyManagementTestHook implements BeforeTestHook {
* @param string $test "TestClass::TestMethod". * @param string $test "TestClass::TestMethod".
*/ */
public function executeBeforeTest( string $test ): void { public function executeBeforeTest( string $test ): void {
// Reset the instance of MockableLegacyProxy that was registered during bootstrap,
// in order to start the test in a clean state (without anything mocked).
wc_get_container()->get( LegacyProxy::class )->reset();
} }
} }

View File

@ -69,9 +69,6 @@ class WC_Unit_Tests_Bootstrap {
// load WC testing framework. // load WC testing framework.
$this->includes(); $this->includes();
// re-initialize dependency injection, this needs to be the last operation after everything else is in place.
$this->initialize_dependency_injection();
} }
/** /**
@ -121,35 +118,6 @@ class WC_Unit_Tests_Bootstrap {
CodeHacker::enable(); CodeHacker::enable();
} }
/**
* Re-initialize the dependency injection engine.
*
* The dependency injection engine has been already initialized as part of the Woo initialization, but we need
* to replace the registered read-only container with a fully configurable one for testing.
* To this end we hack a bit and use reflection to grab the underlying container that the read-only one stores
* in a private property.
*
* Additionally, we replace the legacy/function proxies with mockable versions to easily replace anything
* in tests as appropriate.
*
* @throws \Exception The Container class doesn't have a 'container' property.
*/
private function initialize_dependency_injection() {
try {
$inner_container_property = new \ReflectionProperty( \Automattic\WooCommerce\Container::class, 'container' );
} catch ( ReflectionException $ex ) {
throw new \Exception( "Error when trying to get the private 'container' property from the " . \Automattic\WooCommerce\Container::class . ' class using reflection during unit testing bootstrap, has the property been removed or renamed?' );
}
$inner_container_property->setAccessible( true );
$inner_container = $inner_container_property->getValue( wc_get_container() );
$inner_container->replace( LegacyProxy::class, MockableLegacyProxy::class );
$inner_container->reset_all_resolved();
$GLOBALS['wc_container'] = $inner_container;
}
/** /**
* Load WooCommerce. * Load WooCommerce.
* *

View File

@ -183,7 +183,7 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase {
* @return mixed The instance. * @return mixed The instance.
*/ */
public function get_instance_of( string $class_name ) { public function get_instance_of( string $class_name ) {
return wc_get_container()->get( $class_name ); return null;
} }
/** /**
@ -195,7 +195,7 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase {
* @return mixed The instance. * @return mixed The instance.
*/ */
public function get_legacy_instance_of( string $class_name ) { public function get_legacy_instance_of( string $class_name ) {
return wc_get_container()->get( LegacyProxy::class )->get_instance_of( $class_name ); return null;
} }
/** /**
@ -204,14 +204,12 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase {
* This may be needed when registering mocks for already resolved shared classes. * This may be needed when registering mocks for already resolved shared classes.
*/ */
public function reset_container_resolutions() { public function reset_container_resolutions() {
wc_get_container()->reset_all_resolved();
} }
/** /**
* Reset the mock legacy proxy class so that all the registered mocks are unregistered. * Reset the mock legacy proxy class so that all the registered mocks are unregistered.
*/ */
public function reset_legacy_proxy_mocks() { public function reset_legacy_proxy_mocks() {
wc_get_container()->get( LegacyProxy::class )->reset();
} }
/** /**
@ -222,7 +220,6 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase {
* @throws \Exception Invalid parameter. * @throws \Exception Invalid parameter.
*/ */
public function register_legacy_proxy_function_mocks( array $mocks ) { public function register_legacy_proxy_function_mocks( array $mocks ) {
wc_get_container()->get( LegacyProxy::class )->register_function_mocks( $mocks );
} }
/** /**
@ -233,7 +230,6 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase {
* @throws \Exception Invalid parameter. * @throws \Exception Invalid parameter.
*/ */
public function register_legacy_proxy_static_mocks( array $mocks ) { public function register_legacy_proxy_static_mocks( array $mocks ) {
wc_get_container()->get( LegacyProxy::class )->register_static_mocks( $mocks );
} }
/** /**
@ -244,6 +240,5 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase {
* @throws \Exception Invalid parameter. * @throws \Exception Invalid parameter.
*/ */
public function register_legacy_proxy_class_mocks( array $mocks ) { public function register_legacy_proxy_class_mocks( array $mocks ) {
wc_get_container()->get( LegacyProxy::class )->register_class_mocks( $mocks );
} }
} }

View File

@ -1,233 +0,0 @@
<?php
/**
* AbstractServiceProviderTests class file.
*/
namespace Automattic\WooCommerce\Tests\Internal\DependencyManagement;
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
use Automattic\WooCommerce\Internal\DependencyManagement\ContainerException;
use Automattic\WooCommerce\Internal\DependencyManagement\Definition;
use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer;
use Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses\ClassWithInjectionMethodArgumentWithoutTypeHint;
use Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses\ClassWithDependencies;
use Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses\ClassWithNonFinalInjectionMethod;
use Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses\ClassWithPrivateInjectionMethod;
use Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses\ClassWithScalarInjectionMethodArgument;
use Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses\DependencyClass;
use League\Container\Definition\DefinitionInterface;
/**
* Tests for AbstractServiceProvider.
*/
class AbstractServiceProviderTest extends \WC_Unit_Test_Case {
/**
* The system under test.
*
* @var AbstractServiceProvider
*/
private $sut;
/**
* The container used for tests.
*
* @var ExtendedContainer
*/
private $container;
/**
* Runs before each test.
*/
public function setUp() {
$this->container = new ExtendedContainer();
$this->sut = new class() extends AbstractServiceProvider {
// phpcs:disable
/**
* Public version of add_with_auto_arguments, which is usually protected.
*/
public function add_with_auto_arguments( string $class_name, $concrete = null, bool $shared = false ) : DefinitionInterface {
return parent::add_with_auto_arguments( $class_name, $concrete, $shared );
}
/**
* The mandatory 'register' method (defined in the base class as abstract).
* Not implemented because this class is tested on its own, not as a service provider actually registered on a container.
*/
public function register() {}
// phpcs:enable
};
$this->sut->setContainer( $this->container );
}
/**
* Runs before all the tests of the class.
*/
public static function setUpBeforeClass() {
/**
* Return a new instance of ClassWithDependencies.
*
* @param DependencyClass $dependency The dependency to inject.
* @return ClassWithDependencies The new instance.
*/
function get_new_dependency_class( DependencyClass $dependency ) {
return new ClassWithDependencies( $dependency );
};
}
/**
* @testdox 'add_with_auto_arguments' should throw an exception if an invalid class name is passed as class name.
*/
public function test_add_with_auto_arguments_throws_on_non_class_passed_as_class_name() {
$this->expectException( ContainerException::class );
$this->expectExceptionMessage( "You cannot add 'foobar', only classes in the Automattic\WooCommerce\ namespace are allowed." );
$this->sut->add_with_auto_arguments( 'foobar' );
}
/**
* @testdox 'add_with_auto_arguments' should throw an exception if the passed class has a private injection method.
*/
public function test_add_with_auto_arguments_throws_on_class_private_method_injection() {
$this->expectException( ContainerException::class );
$this->expectExceptionMessage( "Method '" . Definition::INJECTION_METHOD . "' of class '" . ClassWithPrivateInjectionMethod::class . "' isn't 'public', instances can't be created." );
$this->sut->add_with_auto_arguments( ClassWithPrivateInjectionMethod::class );
}
/**
* @testdox 'add_with_auto_arguments' should throw an exception if the passed class has a non-final injection method.
*/
public function test_add_with_auto_arguments_throws_on_class_non_final_method_injection() {
$this->expectException( ContainerException::class );
$this->expectExceptionMessage( "Method '" . Definition::INJECTION_METHOD . "' of class '" . ClassWithNonFinalInjectionMethod::class . "' isn't 'final', instances can't be created." );
$this->sut->add_with_auto_arguments( ClassWithNonFinalInjectionMethod::class );
}
/**
* @testdox 'add_with_auto_arguments' should throw an exception if the passed concrete is a class with a private injection method.
*/
public function test_add_with_auto_arguments_throws_on_concrete_private_method_injection() {
$this->expectException( ContainerException::class );
$this->expectExceptionMessage( "Method '" . Definition::INJECTION_METHOD . "' of class '" . ClassWithPrivateInjectionMethod::class . "' isn't 'public', instances can't be created." );
$this->sut->add_with_auto_arguments( ClassWithDependencies::class, ClassWithPrivateInjectionMethod::class );
}
/**
* @testdox 'add_with_auto_arguments' should throw an exception if the passed concrete is a class with a non-final injection method.
*/
public function test_add_with_auto_arguments_throws_on_concrete_non_final_method_injection() {
$this->expectException( ContainerException::class );
$this->expectExceptionMessage( "Method '" . Definition::INJECTION_METHOD . "' of class '" . ClassWithNonFinalInjectionMethod::class . "' isn't 'final', instances can't be created." );
$this->sut->add_with_auto_arguments( ClassWithDependencies::class, ClassWithNonFinalInjectionMethod::class );
}
/**
* @testdox 'add_with_auto_arguments' should throw an exception if the passed class has a method argument without type hint.
*/
public function test_add_with_auto_arguments_throws_on_method_argument_without_type_hint() {
$this->expectException( ContainerException::class );
$this->expectExceptionMessage( "Argument 'argument_without_type_hint' of class '" . ClassWithInjectionMethodArgumentWithoutTypeHint::class . "' doesn't have a type hint or has one that doesn't specify a class." );
$this->sut->add_with_auto_arguments( ClassWithInjectionMethodArgumentWithoutTypeHint::class );
}
/**
* @testdox 'add_with_auto_arguments' should throw an exception if the passed class has a method argument with a scalar type hint.
*/
public function test_add_with_auto_arguments_throws_on_method_argument_with_scalar_type_hint() {
$this->expectException( ContainerException::class );
$this->expectExceptionMessage( "Argument 'scalar_argument_without_default_value' of class '" . ClassWithScalarInjectionMethodArgument::class . "' doesn't have a type hint or has one that doesn't specify a class." );
$this->sut->add_with_auto_arguments( ClassWithScalarInjectionMethodArgument::class );
}
/**
* @testdox 'add_with_auto_arguments' should properly register the supplied class when no concrete is passed.
*
* @testWith [true, 1]
* [false, 2]
*
* @param bool $shared Whether to register the test class as shared or not.
* @param int $expected_constructions_count Expected number of times that the test class will have been instantiated.
*/
public function test_add_with_auto_arguments_works_as_expected_with_no_concrete( bool $shared, int $expected_constructions_count ) {
ClassWithDependencies::$instances_count = 0;
$this->container->share( DependencyClass::class );
$this->sut->add_with_auto_arguments( ClassWithDependencies::class, null, $shared );
$this->container->get( ClassWithDependencies::class );
$resolved = $this->container->get( ClassWithDependencies::class );
// A new instance is created for each resolution or not, depending on $shared.
$this->assertEquals( $expected_constructions_count, ClassWithDependencies::$instances_count );
// Arguments with default values are honored.
$this->assertEquals( ClassWithDependencies::SOME_NUMBER, $resolved->some_number );
// Method arguments are filled as expected.
$this->assertSame( $this->container->get( DependencyClass::class ), $resolved->dependency_class );
}
/**
* @testdox 'add_with_auto_arguments' should properly register the supplied class when a concrete representing a class name is passed.
*/
public function test_add_with_auto_arguments_works_as_expected_when_concrete_is_class_name() {
$this->sut->add_with_auto_arguments( ClassWithDependencies::class, DependencyClass::class );
$resolved = $this->container->get( ClassWithDependencies::class );
$this->assertInstanceOf( DependencyClass::class, $resolved );
}
/**
* @testdox 'add_with_auto_arguments' should properly register the supplied class when a concrete that is an object is passed.
*/
public function test_add_with_auto_arguments_works_as_expected_when_concrete_is_object() {
$object = new DependencyClass();
$this->sut->add_with_auto_arguments( ClassWithDependencies::class, $object );
$resolved = $this->container->get( ClassWithDependencies::class );
$this->assertSame( $object, $resolved );
}
/**
* @testdox 'add_with_auto_arguments' should properly register the supplied class when a concrete that is a closure is passed.
*/
public function test_add_with_auto_arguments_works_as_expected_when_concrete_is_a_closure() {
$this->container->share( DependencyClass::class );
$callable = function( DependencyClass $dependency ) {
return new ClassWithDependencies( $dependency );
};
$this->sut->add_with_auto_arguments( ClassWithDependencies::class, $callable );
$resolved = $this->container->get( ClassWithDependencies::class );
$this->assertInstanceOf( ClassWithDependencies::class, $resolved );
}
/**
* @testdox 'add_with_auto_arguments' should properly register the supplied class when a concrete that is a function name is passed.
*/
public function test_add_with_auto_arguments_works_as_expected_when_concrete_is_a_function_name() {
$this->container->share( DependencyClass::class );
$this->sut->add_with_auto_arguments( ClassWithDependencies::class, __NAMESPACE__ . '\get_new_dependency_class' );
$resolved = $this->container->get( ClassWithDependencies::class );
$this->assertInstanceOf( ClassWithDependencies::class, $resolved );
}
}

View File

@ -1,52 +0,0 @@
<?php
/**
* ClassWithDependencies class file.
*/
namespace Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses;
/**
* An example of a class with dependencies that are supplied via constructor arguments.
*/
class ClassWithDependencies {
/**
* Default value for $some_number argument.
*/
const SOME_NUMBER = 34;
/**
* Count of instances of the class created so far.
*
* @var int
*/
public static $instances_count = 0;
/**
* Value supplied to constructor in $some_number argument.
*
* @var int
*/
public $some_number = 0;
/**
* Value supplied to constructor in $dependency_class argument.
*
* @var DependencyClass
*/
public $dependency_class = null;
/**
* Initialize the class instance.
*
* @internal
*
* @param DependencyClass $dependency_class A class we depend on.
* @param int $some_number Some number we need for some reason.
*/
final public function init( DependencyClass $dependency_class, int $some_number = self::SOME_NUMBER ) {
self::$instances_count++;
$this->dependency_class = $dependency_class;
$this->some_number = self::SOME_NUMBER;
}
}

View File

@ -1,24 +0,0 @@
<?php
/**
* ClassWithInjectionMethodArgumentWithoutTypeHint class file.
*
* @package Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses
*/
namespace Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses;
/**
* An example class that has a injector method argument without type hint.
*/
class ClassWithInjectionMethodArgumentWithoutTypeHint {
/**
* Initialize the class instance.
*
* @internal
*
* @param mixed $argument_without_type_hint Anything, really.
*/
final public function init( $argument_without_type_hint ) {
}
}

View File

@ -1,24 +0,0 @@
<?php
/**
* ClassWithNonFinalInjectionMethod class file.
*
* @package Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses
*/
namespace Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses;
/**
* An example of a class with a private injection method.
*/
class ClassWithNonFinalInjectionMethod {
// phpcs:disable WooCommerce.Functions.InternalInjectionMethod.MissingFinal
/**
* Initialize the class instance.
*
* @internal
*/
public function init() {
}
}

View File

@ -1,24 +0,0 @@
<?php
/**
* ClassWithPrivateInjectionMethod class file.
*
* @package Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses
*/
namespace Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses;
/**
* An example of a class with a private injection method.
*/
class ClassWithPrivateInjectionMethod {
// phpcs:disable WooCommerce.Functions.InternalInjectionMethod.MissingPublic
/**
* Initialize the class instance.
*
* @internal
*/
final private function init() {
}
}

View File

@ -1,26 +0,0 @@
<?php
/**
* ClassWithScalarInjectionMethodArgument class file.
*
* @package Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses
*/
namespace Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses;
/**
* An example class that has an injector method argument with a scalar type but without a default value.
*/
class ClassWithScalarInjectionMethodArgument {
// phpcs:disable Squiz.Commenting.FunctionComment.InvalidTypeHint
/**
* Initialize the class instance.
*
* @internal
*
* @param mixed $scalar_argument_without_default_value Anything, really.
*/
final public function init( int $scalar_argument_without_default_value ) {
}
}

View File

@ -1,37 +0,0 @@
<?php
/**
* ClassWithSingleton class file.
*/
// This class is in the root namespace on purpose, since it simulates being a legacy class in the 'includes' directory.
/**
* An example of a class that holds a singleton instance.
*/
class ClassWithSingleton {
/**
* @var ClassWithSingleton The singleton instance of the class.
*/
public static $instance;
/**
* @var array The arguments supplied to 'instance'.
*/
public static $instance_args;
/**
* Gets the singleton instance of the class.
*
* @param mixed ...$args Any arguments required by the method.
*
* @return ClassWithSingleton The singleton instance of the class.
*/
public static function instance( ...$args ) {
if ( is_null( self::$instance ) ) {
self::$instance = new ClassWithSingleton();
self::$instance_args = $args;
}
return self::$instance;
}
}

View File

@ -1,23 +0,0 @@
<?php
/**
* DependencyClass class file.
*/
namespace Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses;
/**
* An example of a class other classes depend on.
*/
class DependencyClass {
/**
* Concatenates the supplied string parts just for fun.
*
* @param mixed ...$parts The parts.
*
* @return string The resulting concatenated string.
*/
public static function concat( ...$parts ) {
return 'Parts: ' . join( ', ', $parts );
}
}

View File

@ -1,126 +0,0 @@
<?php
/**
* ExtendedContainerTests class file.
*/
namespace Automattic\WooCommerce\Tests\Internal\DependencyManagement;
use Automattic\WooCommerce\Internal\DependencyManagement\ContainerException;
use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer;
use Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses\ClassWithDependencies;
use Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses\DependencyClass;
/**
* Tests for ExtendedContainer.
*/
class ExtendedContainerTest extends \WC_Unit_Test_Case {
/**
* The system under test.
*
* @var ExtendedContainer
*/
private $sut;
/**
* Runs before each test.
*/
public function setUp() {
$this->sut = new ExtendedContainer();
}
/**
* @testdox 'add' should throw an exception when trying to register a class not in the WooCommerce root namespace.
*/
public function test_add_throws_when_trying_to_register_class_in_forbidden_namespace() {
$external_class = \League\Container\Container::class;
$this->expectException( ContainerException::class );
$this->expectExceptionMessage( "You cannot add '$external_class', only classes in the Automattic\WooCommerce\ namespace are allowed." );
$this->sut->add( $external_class );
}
/**
* @testdox 'add' should throw an exception when trying to register a concrete class not in the WooCommerce root namespace.
*/
public function test_add_throws_when_trying_to_register_concrete_class_in_forbidden_namespace() {
$external_class = \League\Container\Container::class;
$this->expectException( ContainerException::class );
$this->expectExceptionMessage( "You cannot add concrete '$external_class', only classes in the Automattic\WooCommerce\ namespace are allowed." );
$this->sut->add( DependencyClass::class, $external_class );
}
/**
* @testdox 'add' should allow registering classes in the WooCommerce root namespace.
*/
public function test_add_allows_registering_classes_in_woocommerce_root_namespace() {
$instance = new DependencyClass();
$this->sut->add( DependencyClass::class, $instance, true );
$resolved = $this->sut->get( DependencyClass::class );
$this->assertSame( $instance, $resolved );
}
/**
* @testdox 'replace' should throw an exception when trying to replace a class that has not been previously registered.
*/
public function test_replace_throws_if_class_has_not_been_registered() {
$this->expectException( ContainerException::class );
$this->expectExceptionMessage( "The container doesn't have '" . DependencyClass::class . "' registered, please use 'add' instead of 'replace'." );
$this->sut->replace( DependencyClass::class, null );
}
/**
* @testdox 'replace'
*/
public function test_replace_throws_if_concrete_not_in_woocommerce_root_namespace() {
$instance = new DependencyClass();
$this->sut->add( DependencyClass::class, $instance, true );
$external_class = \League\Container\Container::class;
$this->expectException( ContainerException::class );
$this->expectExceptionMessage( "You cannot use concrete '$external_class', only classes in the Automattic\WooCommerce\ namespace are allowed." );
$this->sut->replace( DependencyClass::class, $external_class );
}
/**
* @testdox 'replace' should allow to replace existing registrations.
*/
public function test_replace_allows_replacing_existing_registrations() {
$instance_1 = new DependencyClass();
$instance_2 = new DependencyClass();
$this->sut->add( DependencyClass::class, $instance_1, true );
$this->assertSame( $instance_1, $this->sut->get( DependencyClass::class ) );
$this->sut->replace( DependencyClass::class, $instance_2, true );
$this->assertSame( $instance_2, $this->sut->get( DependencyClass::class ) );
}
/**
* @testdox 'reset_all_resolved' should discard cached resolutions for classes registered as 'shared'.
*/
public function test_reset_all_resolved_discards_cached_shared_resolutions() {
$this->sut->add( DependencyClass::class );
$this->sut->add( ClassWithDependencies::class, null, true )->addArgument( DependencyClass::class );
ClassWithDependencies::$instances_count = 0;
$this->sut->get( ClassWithDependencies::class );
$this->assertEquals( 1, ClassWithDependencies::$instances_count );
$this->sut->get( ClassWithDependencies::class );
$this->assertEquals( 1, ClassWithDependencies::$instances_count );
$this->sut->reset_all_resolved();
$this->sut->get( ClassWithDependencies::class );
$this->assertEquals( 2, ClassWithDependencies::$instances_count );
$this->sut->get( ClassWithDependencies::class );
$this->assertEquals( 2, ClassWithDependencies::$instances_count );
}
}

View File

@ -1,126 +0,0 @@
<?php
/**
* ClassThatDependsOnLegacyCodeTest class file
*/
namespace Automattic\WooCommerce\Tests\Proxies;
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Tests\Proxies\ExampleClasses\ClassThatDependsOnLegacyCode;
use Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses\DependencyClass;
/**
* Tests for a class that depends on legacy code
*/
class ClassThatDependsOnLegacyCodeTest extends \WC_Unit_Test_Case {
/**
* The system under test.
*
* @var LegacyProxy
*/
private $sut;
/**
* Runs before each test.
*/
public function setUp() {
$container = wc_get_container();
$container->add( ClassThatDependsOnLegacyCode::class )->addArgument( LegacyProxy::class );
$this->sut = $container->get( ClassThatDependsOnLegacyCode::class );
}
/**
* Legacy proxy's 'call_function' can be used from both an injected LegacyProxy and from 'WC()->call_function'
*
* @param string $method_to_use Method in the tested class to use.
*
* @testWith ["call_legacy_function_using_injected_proxy"]
* ["call_legacy_function_using_woocommerce_class"]
*/
public function test_call_function_can_be_invoked_via_injected_legacy_proxy_and_woocommerce_object( $method_to_use ) {
$this->assertEquals( 255, $this->sut->$method_to_use( 'hexdec', 'FF' ) );
}
/**
* Function mocks can be used from both an injected LegacyProxy and from 'WC()->call_function'
*
* @param string $method_to_use Method in the tested class to use.
*
* @testWith ["call_legacy_function_using_injected_proxy"]
* ["call_legacy_function_using_woocommerce_class"]
*/
public function test_function_mocks_can_be_used_via_injected_legacy_proxy_and_woocommerce_object( $method_to_use ) {
$this->register_legacy_proxy_function_mocks(
array(
'hexdec' => function( $hex_string ) {
return "Mocked hexdec for $hex_string";
},
)
);
$this->assertEquals( 'Mocked hexdec for FF', $this->sut->$method_to_use( 'hexdec', 'FF' ) );
}
/**
* Legacy proxy's 'call_static' can be used from both an injected LegacyProxy and from 'WC()->call_function'
*
* @param string $method_to_use Method in the tested class to use.
*
* @testWith ["call_static_method_using_injected_proxy"]
* ["call_static_method_using_woocommerce_class"]
*/
public function test_call_static_can_be_invoked_via_injected_legacy_proxy_and_woocommerce_object( $method_to_use ) {
$result = $this->sut->$method_to_use( DependencyClass::class, 'concat', 'foo', 'bar', 'fizz' );
$this->assertEquals( 'Parts: foo, bar, fizz', $result );
}
/**
* Static method mocks can be used from both an injected LegacyProxy and from 'WC()->call_function'
*
* @param string $method_to_use Method in the tested class to use.
*
* @testWith ["call_static_method_using_injected_proxy"]
* ["call_static_method_using_woocommerce_class"]
*/
public function test_static_mocks_can_be_used_via_injected_legacy_proxy_and_woocommerce_object( $method_to_use ) {
$this->register_legacy_proxy_static_mocks(
array(
DependencyClass::class => array(
'concat' => function( ...$parts ) {
return "I'm returning concat of these parts: " . join( ' ', $parts );
},
),
)
);
$expected = "I'm returning concat of these parts: foo bar fizz";
$result = $this->sut->$method_to_use( DependencyClass::class, 'concat', 'foo', 'bar', 'fizz' );
$this->assertEquals( $expected, $result );
}
/**
* Legacy proxy's 'get_instance_of' can be used from both an injected LegacyProxy and from 'WC()->call_function'
*
* @param string $method_to_use Method in the tested class to use.
*
* @testWith ["get_instance_of_using_injected_proxy"]
* ["get_instance_of_using_woocommerce_class"]
*/
public function test_get_instance_of_can_be_used_via_injected_legacy_proxy_and_woocommerce_object( $method_to_use ) {
$instance = $this->sut->$method_to_use( \WC_Queue_Interface::class, 34 );
$this->assertInstanceOf( \WC_Action_Queue::class, $instance );
}
/**
* Legacy object mocks can be used from both an injected LegacyProxy and from 'WC()->call_function'
*
* @param string $method_to_use Method in the tested class to use.
*
* @testWith ["get_instance_of_using_injected_proxy"]
* ["get_instance_of_using_woocommerce_class"]
*/
public function test_class_mocks_can_be_used_via_injected_legacy_proxy_and_woocommerce_object( $method_to_use ) {
$mock = new \stdClass();
$this->register_legacy_proxy_class_mocks( array( \WC_Query::class => $mock ) );
$this->assertSame( $mock, $this->sut->$method_to_use( \WC_Query::class ) );
}
}

View File

@ -1,106 +0,0 @@
<?php
/**
* ClassThatDependsOnLegacyCode class file
*/
namespace Automattic\WooCommerce\Tests\Proxies\ExampleClasses;
use Automattic\WooCommerce\Proxies\LegacyProxy;
/**
* An example class that uses the legacy proxy both from a dependency injected proxy and from the helper methods in the WooCommerce class.
*/
class ClassThatDependsOnLegacyCode {
/**
* The injected LegacyProxy.
*
* @var LegacyProxy
*/
private $legacy_proxy;
/**
* Initialize the class instance.
*
* @internal
*
* @param LegacyProxy $legacy_proxy The instance of LegacyProxy to use.
*/
final public function init( LegacyProxy $legacy_proxy ) {
$this->legacy_proxy = $legacy_proxy;
}
/**
* Use proxy's 'call_function' from the injected proxy.
*
* @param string $function Function to call.
* @param mixed ...$parameters Parameters to pass to the function.
*
* @return mixed The result from the function.
*/
public function call_legacy_function_using_injected_proxy( $function, ...$parameters ) {
return $this->legacy_proxy->call_function( $function, ...$parameters );
}
/**
* Use proxy's 'call_function' using 'WC()->call_function'.
*
* @param string $function Function to call.
* @param mixed ...$parameters Parameters to pass to the function.
*
* @return mixed The result from the function.
*/
public function call_legacy_function_using_woocommerce_class( $function, ...$parameters ) {
return WC()->call_function( $function, ...$parameters );
}
/**
* Use proxy's 'call_static' from the injected proxy.
*
* @param string $class_name Class containing the static method to call.
* @param string $method_name Static method to call.
* @param mixed ...$parameters Parameters to pass to the method.
*
* @return mixed The result from the method.
*/
public function call_static_method_using_injected_proxy( $class_name, $method_name, ...$parameters ) {
return $this->legacy_proxy->call_static( $class_name, $method_name, ...$parameters );
}
/**
* Use proxy's 'call_static' using 'WC()->call_function'.
*
* @param string $class_name Class containing the static method to call.
* @param string $method_name Static method to call.
* @param mixed ...$parameters Parameters to pass to the method.
*
* @return mixed The result from the method.
*/
public function call_static_method_using_woocommerce_class( $class_name, $method_name, ...$parameters ) {
return WC()->call_static( $class_name, $method_name, ...$parameters );
}
/**
* Use proxy's 'get_instance_of' from the injected proxy.
*
* @param string $class_name The name of the class to get an instance of.
* @param mixed ...$args Extra arguments for 'get_instance_of'.
*
* @return object The instance obtained.
*/
public function get_instance_of_using_injected_proxy( string $class_name, ...$args ) {
return $this->legacy_proxy->get_instance_of( $class_name, ...$args );
}
/**
* Use proxy's 'get_instance_of' using 'WC()->call_function'.
*
* @param string $class_name The name of the class to get an instance of.
* @param mixed ...$args Extra arguments for 'get_instance_of'.
*
* @return object The instance obtained.
*/
public function get_instance_of_using_woocommerce_class( string $class_name, ...$args ) {
return WC()->get_instance_of( $class_name, ...$args );
}
}

View File

@ -1,86 +0,0 @@
<?php
/**
* LegacyProxyTests class file
*/
namespace Automattic\WooCommerce\Tests\Proxies;
use Automattic\WooCommerce\Internal\DependencyManagement\Definition;
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses\DependencyClass;
/**
* Tests for LegacyProxy
*/
class LegacyProxyTest extends \WC_Unit_Test_Case {
/**
* The system under test.
*
* @var LegacyProxy
*/
private $sut;
/**
* Runs before each test.
*/
public function setUp() {
$this->sut = new LegacyProxy();
}
/**
* @testdox 'get_instance_of' throws an exception when trying to use it to get an instance of a namespaced class.
*/
public function test_get_instance_of_throws_when_trying_to_get_a_namespaced_class() {
$this->expectException( \Exception::class );
$this->expectExceptionMessage( 'The LegacyProxy class is not intended for getting instances of classes in the src directory, please use ' . Definition::INJECTION_METHOD . ' method injection or the instance of Psr\Container\ContainerInterface for that.' );
$this->sut->get_instance_of( DependencyClass::class );
}
/**
* @testdox 'get_instance_of' can be used to get an instance of a class by using its constructor and passing constructor arguments.
*/
public function test_get_instance_of_can_be_used_to_get_a_non_namespaced_class_with_constructor_parameters() {
$instance = $this->sut->get_instance_of( \WC_Data_Exception::class, 1234, 'Error!', 432 );
$this->assertInstanceOf( \WC_Data_Exception::class, $instance );
$this->assertEquals( 1234, $instance->getErrorCode() );
$this->assertEquals( 'Error!', $instance->getMessage() );
$this->assertEquals( 432, $instance->getCode() );
}
/**
* @testdox 'get_instance_of' uses the 'instance' static method in classes that implement it, passing the supplied arguments.
*/
public function test_get_instance_of_class_with_instance_method_gets_an_instance_of_the_appropriate_class() {
// ClassWithSingleton is in the root namespace and thus can't be autoloaded.
require_once dirname( __DIR__ ) . '/Internal/DependencyManagement/ExampleClasses/ClassWithSingleton.php';
$instance = $this->sut->get_instance_of( \ClassWithSingleton::class, 'foo', 'bar' );
$this->assertSame( \ClassWithSingleton::$instance, $instance );
$this->assertEquals( array( 'foo', 'bar' ), \ClassWithSingleton::$instance_args );
}
/**
* @testdox 'get_instance_of' can be used to get an instance of a class implementing WC_Queue_Interface.
*/
public function test_get_instance_of_wc_queue_interface_gets_an_instance_of_the_appropriate_class() {
$instance = $this->sut->get_instance_of( \WC_Queue_Interface::class, 34 );
$this->assertInstanceOf( \WC_Action_Queue::class, $instance );
}
/**
* @testdox 'call_function' can be used to invoke any standalone function.
*/
public function test_call_function_can_be_used_to_invoke_functions() {
$result = $this->sut->call_function( 'substr', 'foo bar fizz', 4, 3 );
$this->assertEquals( 'bar', $result );
}
/**
* @testdox 'call_static' can be used to invoke any public static class method.
*/
public function test_call_static_can_be_used_to_invoke_public_static_methods() {
$result = $this->sut->call_static( DependencyClass::class, 'concat', 'foo', 'bar', 'fizz' );
$this->assertEquals( 'Parts: foo, bar, fizz', $result );
}
}

View File

@ -1,226 +0,0 @@
<?php
/**
* MockableLegacyProxyTests class file
*/
namespace Automattic\WooCommerce\Tests\Proxies;
use Automattic\WooCommerce\Testing\Tools\DependencyManagement\MockableLegacyProxy;
use Automattic\WooCommerce\Tests\Internal\DependencyManagement\ExampleClasses\DependencyClass;
/**
* Tests for MockableLegacyProxy
*/
class MockableLegacyProxyTest extends \WC_Unit_Test_Case {
/**
* The system under test.
*
* @var MockableLegacyProxy
*/
private $sut;
/**
* Runs before each test.
*/
public function setUp() {
$this->sut = new MockableLegacyProxy();
}
/**
* @testdox 'get_instance_of' works as in LegacyProxy if no class mocks are registered.
*/
public function test_get_instance_of_works_as_regular_legacy_proxy_if_no_mock_registered() {
$instance = $this->sut->get_instance_of( \WC_Data_Exception::class, 1234, 'Error!', 432 );
$this->assertInstanceOf( \WC_Data_Exception::class, $instance );
$this->assertEquals( 1234, $instance->getErrorCode() );
$this->assertEquals( 'Error!', $instance->getMessage() );
$this->assertEquals( 432, $instance->getCode() );
}
/**
* The data provider for test_register_class_mocks_throws_if_invalid_parameters_supplied.
*
* @return array[]
*/
public function data_provider_for_test_register_class_mocks_throws_if_invalid_parameters_supplied() {
return array(
array( 1234, new \stdClass() ),
array( 'SomeClassName', 1234 ),
);
}
/**
* @testdox 'register_class_mocks' throws an exception if an invalid parameter is supplied (not an array of class name => object or factory callback).
*
* @dataProvider data_provider_for_test_register_class_mocks_throws_if_invalid_parameters_supplied
*
* @param string $class_name The name of the class to mock.
* @param object $mock The mock.
*/
public function test_register_class_mocks_throws_if_invalid_parameters_supplied( $class_name, $mock ) {
$this->expectException( \Exception::class );
$this->expectExceptionMessage( 'MockableLegacyProxy::register_class_mocks: $mocks must be an associative array of class_name => object or factory callback.' );
$this->sut->register_class_mocks( array( $class_name => $mock ) );
}
/**
* @testdox 'register_class_mocks' can be used to return class mocks by passing fixed mock instances.
*/
public function test_register_class_mocks_can_be_used_so_that_get_instance_of_returns_a_fixed_instance_mock() {
$mock = new \stdClass();
$this->sut->register_class_mocks( array( \WC_Query::class => $mock ) );
$this->assertSame( $mock, $this->sut->get_instance_of( \WC_Query::class ) );
}
/**
* @testdox 'register_class_mocks' can be used to return class mocks by passing mock factory callbacks.
*/
public function test_register_class_mocks_can_be_used_so_that_get_instance_of_uses_a_factory_function_to_return_the_instance() {
$mock_factory = function( $code, $message, $http_status_code = 400, $data = array() ) {
return "$code, $message, $http_status_code";
};
$this->sut->register_class_mocks( array( \WC_Data_Exception::class => $mock_factory ) );
$this->assertEquals( '1234, Error!, 432', $this->sut->get_instance_of( \WC_Data_Exception::class, 1234, 'Error!', 432 ) );
}
/**
* @testdox 'call_function' works as in LegacyProxy if no function mocks are registered.
*/
public function test_call_function_works_as_regular_legacy_proxy_if_no_mocks_registered() {
$result = $this->sut->call_function( 'substr', 'foo bar fizz', 4, 3 );
$this->assertEquals( 'bar', $result );
}
/**
* The data provider for test_register_function_mocks_throws_if_invalid_parameters_supplied.
*
* @return array[]
*/
public function data_provider_for_test_register_function_mocks_throws_if_invalid_parameters_supplied() {
return array(
array( 1234, function() {} ),
array( 'SomeClassName', 1234 ),
);
}
/**
* @testdox 'register_function_mocks' throws an exception if an invalid parameter is supplied (not an array of function name => mock function).
*
* @dataProvider data_provider_for_test_register_function_mocks_throws_if_invalid_parameters_supplied
*
* @param string $function_name The name of the function to mock.
* @param callable $mock The mock.
*/
public function test_register_function_mocks_throws_if_invalid_parameters_supplied( $function_name, $mock ) {
$this->expectException( \Exception::class );
$this->expectExceptionMessage( 'MockableLegacyProxy::register_function_mocks: The supplied mocks array must have function names as keys and function replacement callbacks as values.' );
$this->sut->register_function_mocks( array( $function_name => $mock ) );
}
/**
* @testdox 'register_function_mocks' can be used to register mocks for any function.
*/
public function test_register_function_mocks_can_be_used_so_that_call_function_calls_mock_functions() {
$this->sut->register_function_mocks(
array(
'substr' => function( $string, $start, $length ) {
return "I'm returning substr of '$string' from $start with length $length";
},
)
);
$expected = "I'm returning substr of 'foo bar fizz' from 4 with length 3";
$result = $this->sut->call_function( 'substr', 'foo bar fizz', 4, 3 );
$this->assertEquals( $expected, $result );
}
/**
* @testdox 'call_static' works as in LegacyProxy if no static method mocks are registered.
*/
public function test_call_static_works_as_regular_legacy_proxy_if_no_mocks_registered() {
$result = $this->sut->call_static( DependencyClass::class, 'concat', 'foo', 'bar', 'fizz' );
$this->assertEquals( 'Parts: foo, bar, fizz', $result );
}
/**
* The data provider for test_register_static_mocks_throws_if_invalid_parameters_supplied.
*
* @return array[]
*/
public function data_provider_for_test_register_static_mocks_throws_if_invalid_parameters_supplied() {
return array(
array( 1234, array( 'some_method' => function(){} ) ),
array( 'SomeClassName', 1234 ),
array( 'SomeClassName', array( 1234 => function(){} ) ),
array( 'SomeClassName', array( 'the_method' => 1234 ) ),
);
}
/**
* @testdox
*
* @dataProvider data_provider_for_test_register_function_mocks_throws_if_invalid_parameters_supplied
*
* @param string $class_name The name of the class whose static methods we want to mock.
* @param array $mocks The mocks.
*/
public function test_register_static_mocks_throws_if_invalid_parameters_supplied( $class_name, $mocks ) {
$this->expectException( \Exception::class );
$this->expectExceptionMessage( 'MockableLegacyProxy::register_static_mocks: $mocks must be an associative array of class name => associative array of method name => callable.' );
$this->sut->register_static_mocks( array( $class_name => $mocks ) );
}
/**
* @testdox 'register_static_mocks' can be used to register mocks for any static method.
*/
public function test_register_static_mocks_can_be_used_so_that_call_function_calls_mock_functions() {
$this->sut->register_static_mocks(
array(
DependencyClass::class => array(
'concat' => function( ...$parts ) {
return "I'm returning concat of these parts: " . join( ' ', $parts );
},
),
)
);
$expected = "I'm returning concat of these parts: foo bar fizz";
$result = $this->sut->call_static( DependencyClass::class, 'concat', 'foo', 'bar', 'fizz' );
$this->assertEquals( $expected, $result );
}
/**
* @testdox 'reset' can be used to revert the instance to its original state, in which nothing is mocked.
*/
public function test_reset_can_be_used_to_unregister_all_mocks() {
$this->sut->register_class_mocks( array( \WC_Query::class => new \stdClass() ) );
$this->sut->register_function_mocks(
array(
'substr' => function( $string, $start, $length ) {
return null;
},
)
);
$this->sut->register_static_mocks(
array(
DependencyClass::class => array(
'concat' => function( ...$parts ) {
return null;
},
),
)
);
$this->sut->reset();
$this->test_call_function_works_as_regular_legacy_proxy_if_no_mocks_registered();
$this->test_call_static_works_as_regular_legacy_proxy_if_no_mocks_registered();
$this->test_call_static_works_as_regular_legacy_proxy_if_no_mocks_registered();
}
}