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

Add when() helper to make conditional effects smaller #26

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ composer.lock
.php_cs.cache
.phpunit.result.cache
.php-cs-fixer.cache
.vscode
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ This library adheres to [Semantic Versioning](https://semver.org/) and [Keep a C

## Unreleased

Nothing yet.
### Added

- Added `when()` method to `Group` class to conditionally include features without using `Effect`.

## 3.0.0

Expand Down
17 changes: 17 additions & 0 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,23 @@ $plugin->include(
),
);

// This can also be written using the when() helper.
$plugin->when(
when: fn () => get_current_blog_id() !== 1,
Comment on lines +96 to +97
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be interesting to note that in the project where Effect was developed, it was originally called When. @efuller and I had a long conversation about it (https://l.alley.dev/48a45b18ff) that eventually resulted in renaming it Effect (https://l.alley.dev/88aae5417b).

IMO, the double "when" shown here is a bit of a red flag that precision is being lost, which can sort of be illustrated by writing the logic compared to the longer way:

"In the plugin, include an effect that when...then..."

"In the plugin, when when...then..."

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hear what you're saying, @dlh01, but I also see the value in having a shorthand for conditionally including features. Having the shorthand function be named "when" makes it clear what it's doing. The "double when" problem is, imo, due to the fact that the example uses named parameters. With positional args, it's $plugin->when( $if_condition, $then_condition ) which reads fine to me.

then: fn () => new Ordered(
first: new Library\Plugin_Loader(
plugins: [
'block-visibility/block-visibility.php',
],
),
then: new Group(
new Features\Block_Visibility_Settings(),
new Features\Block_Visibility_Custom_Conditions(),
),
),
),
);

// Load the Google Tag Manager script on templates.
$plugin->include(
new Template_Feature(
Expand Down
12 changes: 7 additions & 5 deletions src/features/class-effect.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ final class Effect implements Feature {
/**
* The condition to check.
*
* @var callable
* @var bool|callable
*/
private $when;

/**
* Constructor.
*
* @param callable $when The condition to check.
* @param Feature $then The feature to boot if the condition is met.
* @param bool|callable $when The condition to check.
* @param Feature $then The feature to boot if the condition is met.
*/
public function __construct(
callable $when,
bool|callable $when,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had left this as only callable intentionally for a couple reasons:

  • So that the boolean would be calculated only at the moment that it's needed.
  • So that a developer couldn't accidentally cause the boolean to be calculated before they meant to.

For example, this object:

new Template_Feature(
	new Effect(
		when: some_condition(),
		then: new Feature( ... ),
	),
);

The boolean is being calculated immediately, but it doesn't need to be until we know we're serving a template, so it's somewhat inefficient.

In this example, it would be easy for a reader to assume that some_condition() is running only when the template is being served, when it's actually running immediately, which both could produce the wrong result and be non-obvious to debug (or at least it was for me).

So, the closure is longer to write, but I think it makes it harder to make a mistake because it won't ever run until the feature is actually booted.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can largely be solved with documentation - we can call out a preference for using the callback for the reasons you've identified, but still have boolean values in case there's a simple comparison that we're doing that can be known ahead of time (e.g., comparing against a constant, like VIP_GO_APP_ENVIRONMENT or similar). @srtfisher can you add this detail to the features documentation, please?

private readonly Feature $then,
) {
$this->when = $when;
Expand All @@ -37,7 +37,9 @@ public function __construct(
* Boot the feature.
*/
public function boot(): void {
if ( ( $this->when )() === true ) {
if ( is_callable( $this->when ) && ( $this->when )() === true ) {
$this->then->boot();
} elseif ( is_bool( $this->when ) && true === $this->when ) {
$this->then->boot();
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/features/class-group.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,15 @@ public function boot(): void {
public function include( Feature ...$features ): void {
array_push( $this->features, ...$features );
}

/**
* Include a conditional feature.
*
* @param bool|callable $when The condition to check.
* @param Feature $then The feature to boot if the condition is met.
* @return void
*/
public function when( bool|callable $when, Feature $then ): void {
$this->include( new Effect( $when, $then ) );
}
}
Empty file removed tests/Feature/.gitkeep
Empty file.
56 changes: 56 additions & 0 deletions tests/Feature/Features/GroupTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php
/**
* GroupTest class file.
*
* @package wp-type-extensions
*
* phpcs:disable Squiz.PHP.DisallowMultipleAssignments.Found, WordPress.Security.ValidatedSanitizedInput
*/

namespace Alley\WP\Tests\Feature\Features;

use Mantle\Testkit\Test_Case;
use Alley\WP\Features\Group;
use Alley\WP\Features\Quick_Feature;
use Alley\WP\Types\Feature;

/**
* Tests for Group class.
*/
class GroupTest extends Test_Case {
/**
* Test that group can boot a set of features.
*/
public function test_it_can_boot_a_group_of_features(): void {
$_SERVER['booted_quick_feature'] = false;
$_SERVER['booted_feature'] = false;
$_SERVER['booted_group_of_groups'] = false;
$_SERVER['included_feature'] = false;

$group = new Group(
new Quick_Feature( fn () => $_SERVER['booted_quick_feature'] = true ),
new class() implements Feature {
/**
* Boot the feature.
*/
public function boot(): void {
$_SERVER['booted_feature'] = true;
}
},
new Group(
new Quick_Feature( fn () => $_SERVER['booted_group_of_groups'] = true ),
),
);

$group->include(
new Quick_Feature( fn () => $_SERVER['included_feature'] = true ),
);

$group->boot();

$this->assertTrue( $_SERVER['booted_feature'] );
$this->assertTrue( $_SERVER['booted_quick_feature'] );
$this->assertTrue( $_SERVER['booted_group_of_groups'] );
$this->assertTrue( $_SERVER['included_feature'] );
}
}
Empty file removed tests/Unit/.gitkeep
Empty file.
Loading