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

Make Check macroable and allow chaining callables in if() and unless() #184

Merged
merged 8 commits into from
Jul 21, 2023
76 changes: 76 additions & 0 deletions docs/basic-usage/conditionally-running-or-modifying-checks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
title: Conditionally running or modifying checks
weight: 5
---

This package provides methods to run certain checks only when specified conditions are met.

If you would like to conditionally run a check, you can use the `if` and `unless` methods.
For more control, you can also use callables. They are evaluated every time a health check is run.

```php
use Spatie\Health\Facades\Health;
use Spatie\Health\Checks\Checks\DebugModeCheck;
use Spatie\Health\Checks\Checks\RedisCheck;

Health::checks([
DebugModeCheck::new()->unless(app()->environment('local')),
RedisCheck::new()->if(fn () => app(SomeHeavyService::class)->shouldCheckHealth()),
]);
```

## Custom condition methods

You may find yourself repeating conditions for multiple checks. To avoid that,
you can register a Laravel macro on a check with a custom condition method.

```php
use Spatie\Health\Facades\Health;
use Spatie\Health\Checks\Check;
use Spatie\Health\Checks\Checks\DebugModeCheck;
use Spatie\Health\Checks\Checks\RedisCheck;

Health::macro('ifEnvironment', fn (string|array $envs) => app()->environment($envs));

Health::checks([
DebugModeCheck::new()->ifEnvironment('production')
]);
```

## Chaining conditions

Sometimes you need more than one condition on a check, so you may chain two or more of them
simply by calling `if` or `unless` multiple times. They are evaluated in the order that
you define them.

```php
use Spatie\Health\Facades\Health;
use Spatie\Health\Checks\Checks\DebugModeCheck;
use Spatie\Health\Checks\Checks\RedisCheck;

Health::checks([
DebugModeCheck::new()
->unless(app()->environment('local'))
->if(fn () => app(SomeHeavyService::class)->shouldCheckHealth()),
]);
```

## Modifying checks on a condition

You may want to slightly change check's configuration under a specific condition. You can do
so using `when` and `doUnless` methods. In this example, a smaller memory limit is enforced
on a local environment.

```php
use Spatie\Health\Facades\Health;
use Spatie\Health\Checks\Checks\RedisMemoryUsageCheck;

Health::checks([
RedisMemoryUsageCheck::new()
->failWhenAboveMb(1000)
->when(
app()->environment('local'),
fn (RedisMemoryUsageCheck $check) => $check->failWhenAboveMb(200)
),
]);
```
15 changes: 0 additions & 15 deletions docs/viewing-results/general.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,3 @@ Health::checks([
UsedDiskSpaceCheck::new()->label('Disk space on main disk'),
]);
```

## Running checks conditionally

If you would like to conditionally run a check, you can use the `if` and `unless` methods.

```php
use Spatie\Health\Facades\Health;
use Spatie\Health\Checks\Checks\DebugModeCheck;
use Spatie\Health\Checks\Checks\RedisCheck;

Health::checks([
DebugModeCheck::new()->if(app()->isProduction()),
RedisCheck::new()->unless(app()->environment('development')),
]);
```
33 changes: 24 additions & 9 deletions src/Checks/Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,28 @@
use Illuminate\Console\Scheduling\ManagesFrequencies;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Conditionable;
use Illuminate\Support\Traits\Macroable;
use Spatie\Health\Enums\Status;

abstract class Check
{
use ManagesFrequencies;
use Macroable;
use Conditionable {
unless as doUnless;
}

protected string $expression = '* * * * *';

protected ?string $name = null;

protected ?string $label = null;

protected bool $shouldRun = true;
/**
* @var array<bool|callable(): bool>
*/
protected array $shouldRun = [];

public function __construct()
{
Expand All @@ -33,14 +42,14 @@ public static function new(): static
return $instance;
}

public function name(string $name): self
public function name(string $name): static
{
$this->name = $name;

return $this;
}

public function label(string $label): self
public function label(string $label): static
{
$this->label = $label;

Expand Down Expand Up @@ -71,25 +80,31 @@ public function getName(): string

public function shouldRun(): bool
{
if (! $this->shouldRun) {
return false;
foreach ($this->shouldRun as $shouldRun) {
$shouldRun = is_callable($shouldRun) ? $shouldRun() : $shouldRun;

if (!$shouldRun) {
return false;
}
}

$date = Date::now();

return (new CronExpression($this->expression))->isDue($date->toDateTimeString());
}

public function if(bool $condition)
public function if(bool|callable $condition)
{
$this->shouldRun = $condition;
$this->shouldRun[] = $condition;

return $this;
}

public function unless(bool $condition)
public function unless(bool|callable $condition)
{
$this->shouldRun = ! $condition;
$this->shouldRun[] = is_callable($condition) ?
fn () => !$condition() :
! $condition;

return $this;
}
Expand Down
49 changes: 41 additions & 8 deletions tests/HealthTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use Spatie\Health\Checks\Check;
use Spatie\Health\Checks\Checks\DatabaseCheck;
use Spatie\Health\Checks\Checks\DebugModeCheck;
use Spatie\Health\Checks\Checks\EnvironmentCheck;
use Spatie\Health\Checks\Checks\PingCheck;
use Spatie\Health\Checks\Checks\UsedDiskSpaceCheck;
use Spatie\Health\Checks\Result;
Expand All @@ -27,7 +28,9 @@
it('can run checks conditionally using if method', function () {
Health::checks([
UsedDiskSpaceCheck::new(),
DebugModeCheck::new()->if(false),
DebugModeCheck::new()->name('Debug 1')->if(false),
DebugModeCheck::new()->name('Debug 2')->if(true)->if(false),
EnvironmentCheck::new()->if(fn () => false),
]);

$checks = Health::registeredChecks()->filter(function (Check $check) {
Expand All @@ -43,23 +46,31 @@

Health::checks([
UsedDiskSpaceCheck::new(),
DebugModeCheck::new()->if(true),
DebugModeCheck::new()->name('Debug 1')->if(true),
DebugModeCheck::new()->name('Debug 2')->if(true)->if(true),
EnvironmentCheck::new()->if(fn () => true),
]);

$checks = Health::registeredChecks()->filter(function (Check $check) {
return $check->shouldRun();
});

expect($checks)
->toHaveCount(2)
->toHaveCount(4)
->and($checks[1])
->toBeInstanceOf(DebugModeCheck::class);
->toBeInstanceOf(DebugModeCheck::class)
->and($checks[2])
->toBeInstanceOf(DebugModeCheck::class)
->and($checks[3])
->toBeInstanceOf(EnvironmentCheck::class);
});

it('can run checks conditionally using unless method', function () {
Health::checks([
UsedDiskSpaceCheck::new(),
DebugModeCheck::new()->unless(true),
DebugModeCheck::new()->name('Debug 1')->unless(true),
DebugModeCheck::new()->name('Debug 2')->unless(false)->unless(true),
EnvironmentCheck::new()->unless(fn () => true),
]);

$checks = Health::registeredChecks()->filter(function (Check $check) {
Expand All @@ -75,17 +86,39 @@

Health::checks([
UsedDiskSpaceCheck::new(),
DebugModeCheck::new()->unless(false),
DebugModeCheck::new()->name('Debug 1')->unless(false),
DebugModeCheck::new()->name('Debug 2')->unless(false)->unless(false),
EnvironmentCheck::new()->unless(fn () => false),
]);

$checks = Health::registeredChecks()->filter(function (Check $check) {
return $check->shouldRun();
});

expect($checks)
->toHaveCount(2)
->toHaveCount(4)
->and($checks[1])
->toBeInstanceOf(DebugModeCheck::class);
->toBeInstanceOf(DebugModeCheck::class)
->and($checks[2])
->toBeInstanceOf(DebugModeCheck::class)
->and($checks[3])
->toBeInstanceOf(EnvironmentCheck::class);
});

it('can conditionally modify a check using when method', function () {
$check = DebugModeCheck::new()
->when(true, fn (DebugModeCheck $check) => $check->name('Debug 1'))
->when(false, fn (DebugModeCheck $check) => $check->name('Debug 2'));

expect($check->getName())->toBe('Debug 1');
});

it('can conditionally modify a check using doUnless method', function () {
$check = DebugModeCheck::new()
->doUnless(false, fn (DebugModeCheck $check) => $check->name('Debug 1'))
->doUnless(true, fn (DebugModeCheck $check) => $check->name('Debug 2'));

expect($check->getName())->toBe('Debug 1');
});

it('will throw an exception when duplicate checks are registered', function () {
Expand Down