Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement dependency resolution for services #8513

Merged
merged 62 commits into from
Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
7b4db96
Implement dependency resolution for services
swissspidy Jul 29, 2021
bc5472b
Fix incorrect test
swissspidy Jul 29, 2021
b6421cb
LInt fix
swissspidy Jul 30, 2021
45cacb2
Make experiment toggling more robust
swissspidy Jul 30, 2021
70d5119
Merge branch 'main' into add/services-dependencies
swissspidy Jul 30, 2021
f8a3a1b
Fix `Settings::register()`
swissspidy Aug 2, 2021
977631a
Merge branch 'main' into add/services-dependencies
swissspidy Aug 4, 2021
2127db6
Add some types
swissspidy Aug 4, 2021
1b9590c
Pull upstream changes
swissspidy Aug 4, 2021
f99c380
Merge branch 'main' into add/services-dependencies
swissspidy Aug 11, 2021
a7a4233
Merge branch 'main' into add/services-dependencies
swissspidy Aug 17, 2021
f4bd461
Merge branch 'main' into add/services-dependencies
swissspidy Aug 17, 2021
7c6527d
Merge branch 'main' into add/services-dependencies
swissspidy Aug 20, 2021
c50f20c
Update docs
swissspidy Aug 20, 2021
9686c90
Ensure services that are delayed and have requirements are scheduled …
swissspidy Aug 20, 2021
e8db36d
Fix PHPDoc
swissspidy Aug 20, 2021
fbbd35f
Fix typo
swissspidy Aug 20, 2021
617873c
Add some more robust assertions
swissspidy Aug 20, 2021
9955cbe
Move `rest_api_init` to `wpSetUpBeforeClass`
swissspidy Aug 20, 2021
0689fc3
Merge branch 'main' into add/services-dependencies
swissspidy Sep 8, 2021
62bd00f
Add return type
swissspidy Sep 8, 2021
1fbd86f
Merge branch 'main' into add/services-dependencies
swissspidy Sep 22, 2021
da6f69f
Code & test fixes
swissspidy Sep 22, 2021
2e06989
Implement `HasRequirements`
swissspidy Sep 22, 2021
700d36d
Add requirement for KSES class
swissspidy Sep 23, 2021
e841195
Merge branch 'main' into add/services-dependencies
swissspidy Sep 23, 2021
860813b
Merge branch 'main' into add/services-dependencies
swissspidy Sep 30, 2021
1aede89
Add missing call
swissspidy Sep 30, 2021
d5f2476
Merge branch 'main' into add/services-dependencies
swissspidy Sep 30, 2021
3a6c1c3
More dependency injection
swissspidy Sep 30, 2021
6e8263f
Some cleanup
swissspidy Sep 30, 2021
371dff4
Delete user meta in tear down to prevent leaking
swissspidy Sep 30, 2021
431bc40
More dependency injection
swissspidy Sep 30, 2021
3d2e79f
Merge branch 'main' into add/services-dependencies
swissspidy Sep 30, 2021
11782d4
Further improve tests
swissspidy Oct 1, 2021
9ed3eb8
Merge branch 'main' into add/services-dependencies
swissspidy Oct 1, 2021
26daf06
Remove unneeded code
swissspidy Oct 1, 2021
0490ff2
Remove count
swissspidy Oct 1, 2021
6d51579
Remove unneeded function call
swissspidy Oct 1, 2021
ffca829
Fix fixture setup when running tests in random order
swissspidy Oct 1, 2021
7e6c49b
Merge branch 'main' into add/services-dependencies
swissspidy Oct 1, 2021
03dc2fd
Remove debug cruft
swissspidy Oct 1, 2021
2d001bd
Update variables
swissspidy Oct 1, 2021
435cc1c
Add missing `parent::set_up()` call
swissspidy Oct 1, 2021
3ad286f
Use `DependencyInjectedTestCase`
swissspidy Oct 1, 2021
7256371
More test improvements
swissspidy Oct 1, 2021
e8131a3
Merge branch 'main' into add/services-dependencies
swissspidy Oct 1, 2021
f40b47a
Merge branch 'main' into add/services-dependencies
swissspidy Oct 1, 2021
73267e9
Get settings name via instance
swissspidy Oct 4, 2021
042279f
More test updates
swissspidy Oct 4, 2021
cd9a197
Merge branch 'main' into add/services-dependencies
swissspidy Oct 4, 2021
8d84f58
Lint fixes
swissspidy Oct 4, 2021
0bd0263
Fix kses
swissspidy Oct 4, 2021
4571e94
Merge branch 'main' into add/services-dependencies
swissspidy Oct 4, 2021
f013709
Merge branch 'main' into add/services-dependencies
swissspidy Oct 5, 2021
3ff2c56
Update rest tests
swissspidy Oct 5, 2021
66dbc99
Update list of shared instances
swissspidy Oct 5, 2021
b792d07
Merge branch 'main' into add/services-dependencies
swissspidy Oct 5, 2021
d4ebbe7
Multi-line function call
swissspidy Oct 5, 2021
345ed48
Extract variable
swissspidy Oct 5, 2021
4b7eb43
Merge branch 'main' into add/services-dependencies
swissspidy Oct 5, 2021
da24557
Remove alias
swissspidy Oct 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion includes/Experiments.php
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ public function is_experiment_enabled( string $name ): bool {
*
* @return array List of all enabled experiments.
*/
public function get_enabled_experiments() {
public function get_enabled_experiments(): array {
$experiments = array_filter(
wp_list_pluck( $this->get_experiments(), 'name' ),
[ $this, 'is_experiment_enabled' ]
Expand Down
43 changes: 43 additions & 0 deletions includes/Infrastructure/HasRequirements.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php
/**
* Interface HasRequirements.
*
* @package Google\Web_Stories
*/

/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Web_Stories\Infrastructure;

/**
* Something that requires other services to be registered before it can be registered.
*
* A class marked as having requirements can return the list of services it requires
* to be available before it can be registered.
*
* @since 1.10.0
* @internal
*/
interface HasRequirements {

/**
* Get the list of service IDs required for this service to be registered.
*
* @return string[] List of required services.
*/
public static function get_requirements(): array;
}
93 changes: 73 additions & 20 deletions includes/Infrastructure/ServiceBasedPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

use Google\Web_Stories\Exception\InvalidService;
use Google\Web_Stories\Infrastructure\ServiceContainer\LazilyInstantiatedService;
use function add_action;
use function apply_filters;
use function Google\Web_Stories\rewrite_flush;


Expand Down Expand Up @@ -161,7 +163,7 @@ public function deactivate( $network_wide ) {
*/
public function register() {
if ( false !== static::REGISTRATION_ACTION ) {
\add_action(
add_action(
static::REGISTRATION_ACTION,
[ $this, 'register_services' ]
);
Expand Down Expand Up @@ -205,38 +207,89 @@ public function register_services() {
* classes need to implement the
* Service interface.
*/
$filtered_services = \apply_filters(
$filtered_services = apply_filters(
static::HOOK_PREFIX . static::SERVICES_FILTER,
$services
);

$services = $this->validate_services( $filtered_services, $services );
}
foreach ( $services as $id => $class ) {
$id = $this->maybe_resolve( $id );
$class = $this->maybe_resolve( $class );

while ( null !== key( $services ) ) {
$id = $this->maybe_resolve( key( $services ) );
$class = $this->maybe_resolve( current( $services ) );

// Delay registering the service until all requirements are met.
if (
is_a( $class, HasRequirements::class, true )
&&
! $this->requirements_are_met( $class, array_keys( $services ) )
) {
/*
* Move the service to the end of the array.
*
* Note: Unsetting the key advances the internal array pointer to the next array item, so
* the current array item will have to be temporarily stored so that it can be re-added later.
*/
$delayed_service = [ key( $services ) => current( $services ) ];
unset( $services[ key( $services ) ] );
$services[ key( $delayed_service ) ] = current( $delayed_service );

continue;
}

// Allow the services to delay their registration.
if ( is_a( $class, Delayed::class, true ) ) {
$registration_action = $class::get_registration_action();

if ( did_action( $registration_action ) ) {
$this->register_service( $id, $class );

continue;
} else {
add_action(
$class::get_registration_action(),
function () use ( $id, $class ) {
$this->register_service( $id, $class );
}
);
}

\add_action(
$class::get_registration_action(),
function () use ( $id, $class ) {
$this->register_service( $id, $class );
},
$class::get_registration_action_priority()
);

next( $services );
continue;
}

$this->register_service( $id, $class );

next( $services );
}
}

/**
* Determine if the requirements for a service to be registered are met.
*
* @since 1.10.0
*
* @param class-string|HasRequirements $class Service with requirements.
* @param string[] $service_ids List of service IDs to be registered.
*
* @throws InvalidService If the required service is not recognized.
*
* @return bool Whether the requirements for the service has been met.
*/
protected function requirements_are_met( $class, $service_ids ): bool {
$requirements = $class::get_requirements();

foreach ( $requirements as $requirement ) {
// Bail if it requires a service that is not recognized.
if ( ! in_array( $requirement, $service_ids, true ) ) {
throw InvalidService::from_service_id( $requirement );
}

if ( ! $this->get_container()->has( $requirement ) ) {
return false;
}
}

return true;
}

/**
Expand Down Expand Up @@ -396,7 +449,7 @@ function () use ( $class ) {
* @param Injector $injector Injector instance to configure.
* @return Injector Configured injector instance.
*/
protected function configure_injector( Injector $injector ) {
protected function configure_injector( Injector $injector ): Injector {
swissspidy marked this conversation as resolved.
Show resolved Hide resolved
$bindings = $this->get_bindings();
$shared_instances = $this->get_shared_instances();
$arguments = $this->get_arguments();
Expand All @@ -412,7 +465,7 @@ protected function configure_injector( Injector $injector ) {
* implementation bindings. Both
* should be FQCNs.
*/
$bindings = (array) \apply_filters(
$bindings = (array) apply_filters(
static::HOOK_PREFIX . static::BINDINGS_FILTER,
$bindings
);
Expand All @@ -428,7 +481,7 @@ protected function configure_injector( Injector $injector ) {
* array maps argument names to
* values.
*/
$arguments = (array) \apply_filters(
$arguments = (array) apply_filters(
static::HOOK_PREFIX . static::ARGUMENTS_FILTER,
$arguments
);
Expand All @@ -442,7 +495,7 @@ protected function configure_injector( Injector $injector ) {
* @param array<string> $shared_instances Array of FQCNs to turn
* into shared objects.
*/
$shared_instances = (array) \apply_filters(
$shared_instances = (array) apply_filters(
static::HOOK_PREFIX . static::SHARED_INSTANCES_FILTER,
$shared_instances
);
Expand All @@ -456,7 +509,7 @@ protected function configure_injector( Injector $injector ) {
* @param array<string> $delegations Associative array of class =>
* callable mappings.
*/
$delegations = (array) \apply_filters(
$delegations = (array) apply_filters(
static::HOOK_PREFIX . static::DELEGATIONS_FILTER,
$delegations
);
Expand Down
33 changes: 33 additions & 0 deletions tests/phpunit/includes/Fixture/DummyServiceWithRequirements.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php
/**
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Web_Stories\Tests\Fixture;

use Google\Web_Stories\Infrastructure\HasRequirements;
use Google\Web_Stories\Infrastructure\Service;

class DummyServiceWithRequirements implements Service, HasRequirements {

/**
* Get the list of service IDs required for this service to be registered.
*
* @return string[] List of required services.
*/
public static function get_requirements(): array {
return [ 'service_a' ];
}
}
7 changes: 5 additions & 2 deletions tests/phpunit/tests/Experiments.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,14 @@ public function test_add_menu_page() {
* @covers ::initialize_settings
*/
public function test_initialize_settings() {
global $wp_settings_fields, $wp_settings_sections;

$experiments = new \Google\Web_Stories\Experiments();
$experiments->initialize_settings();

$options = get_registered_settings();
$this->assertArrayHasKey( \Google\Web_Stories\Settings::SETTING_NAME_EXPERIMENTS, $options );
$this->assertArrayHasKey( \Google\Web_Stories\Experiments::PAGE_NAME, $wp_settings_fields );
$this->assertArrayHasKey( \Google\Web_Stories\Experiments::PAGE_NAME, $wp_settings_sections );
$this->assertArrayHasKey( 'web_stories_experiments_section', $wp_settings_sections[ \Google\Web_Stories\Experiments::PAGE_NAME ] );
}

/**
Expand Down
Loading