Skip to content

Commit

Permalink
Introduce startup policy (#303)
Browse files Browse the repository at this point in the history
  • Loading branch information
vjik authored Jan 18, 2025
1 parent ace7cea commit b326385
Show file tree
Hide file tree
Showing 35 changed files with 909 additions and 151 deletions.
11 changes: 10 additions & 1 deletion config/di-console.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

declare(strict_types=1);

use Yiisoft\Definitions\DynamicReference;
use Yiisoft\Definitions\ReferencesArray;
use Yiisoft\Yii\Debug\Debugger;
use Yiisoft\Yii\Debug\StartupPolicy\Condition\CommandNameCondition;
use Yiisoft\Yii\Debug\StartupPolicy\Condition\EnvironmentVariableCondition;
use Yiisoft\Yii\Debug\StartupPolicy\Debugger\DenyDebuggerPolicy;

if (!(bool)($params['yiisoft/yii-debug']['enabled'] ?? false)) {
return [];
Expand All @@ -18,7 +22,12 @@
$params['yiisoft/yii-debug']['collectors.console'] ?? []
)
),
'ignoredCommands' => $params['yiisoft/yii-debug']['ignoredCommands'],
'debuggerStartupPolicy' => DynamicReference::to(
static fn () => new DenyDebuggerPolicy(
new EnvironmentVariableCondition('YII_DEBUG_IGNORE'),
new CommandNameCondition($params['yiisoft/yii-debug']['ignoredCommands'])
),
),
'excludedClasses' => $params['yiisoft/yii-debug']['excludedClasses'],
],
],
Expand Down
13 changes: 12 additions & 1 deletion config/di-web.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

declare(strict_types=1);

use Yiisoft\Definitions\DynamicReference;
use Yiisoft\Definitions\ReferencesArray;
use Yiisoft\Yii\Debug\Debugger;
use Yiisoft\Yii\Debug\StartupPolicy\Condition\EnvironmentVariableCondition;
use Yiisoft\Yii\Debug\StartupPolicy\Condition\HeaderCondition;
use Yiisoft\Yii\Debug\StartupPolicy\Condition\UriPathCondition;
use Yiisoft\Yii\Debug\StartupPolicy\Debugger\DenyDebuggerPolicy;

if (!(bool)($params['yiisoft/yii-debug']['enabled'] ?? false)) {
return [];
Expand All @@ -18,7 +23,13 @@
$params['yiisoft/yii-debug']['collectors.web'] ?? [],
)
),
'ignoredRequests' => $params['yiisoft/yii-debug']['ignoredRequests'],
'debuggerStartupPolicy' => DynamicReference::to(
static fn () => new DenyDebuggerPolicy(
new EnvironmentVariableCondition('YII_DEBUG_IGNORE'),
new HeaderCondition('X-Debug-Ignore'),
new UriPathCondition($params['yiisoft/yii-debug']['ignoredRequests'])
),
),
'excludedClasses' => $params['yiisoft/yii-debug']['excludedClasses'],
],
],
Expand Down
54 changes: 10 additions & 44 deletions src/Debugger.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
namespace Yiisoft\Yii\Debug;

use LogicException;
use Psr\Http\Message\ServerRequestInterface;
use Yiisoft\Strings\WildcardPattern;
use Yiisoft\Yii\Console\Event\ApplicationStartup;
use Yiisoft\Yii\Debug\Collector\CollectorInterface;
use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface;
use Yiisoft\Yii\Debug\StartupPolicy\Collector\AllowAllCollectorPolicy;
use Yiisoft\Yii\Debug\StartupPolicy\Collector\CollectorStartupPolicyInterface;
use Yiisoft\Yii\Debug\StartupPolicy\Debugger\AlwaysOnDebuggerPolicy;
use Yiisoft\Yii\Debug\StartupPolicy\Debugger\DebuggerStartupPolicyInterface;
use Yiisoft\Yii\Debug\Storage\StorageInterface;
use Yiisoft\Yii\Http\Event\BeforeRequest;

/**
* @psalm-type BacktraceType = list<array{file?:string,line?:int,function?:string,class?:class-string,object?:object,type?:string,args?:array}>
Expand All @@ -31,14 +31,12 @@ final class Debugger

/**
* @param CollectorInterface[] $collectors
* @param string[] $ignoredRequests
* @param string[] $ignoredCommands
*/
public function __construct(
private readonly StorageInterface $storage,
array $collectors,
private readonly array $ignoredRequests = [],
private readonly array $ignoredCommands = [],
private readonly DebuggerStartupPolicyInterface $debuggerStartupPolicy = new AlwaysOnDebuggerPolicy(),
private readonly CollectorStartupPolicyInterface $collectorStartupPolicy = new AllowAllCollectorPolicy(),
array $excludedClasses = [],
) {
$preparedCollectors = [];
Expand All @@ -64,18 +62,16 @@ public function getId(): string

public function startup(object $event): void
{
if ($event instanceof BeforeRequest && $this->isRequestIgnored($event->getRequest())) {
return;
}

if ($event instanceof ApplicationStartup && $this->isCommandIgnored($event->commandName)) {
if (!$this->debuggerStartupPolicy->satisfies($event)) {
return;
}

$this->id = str_replace('.', '', uniqid('', true));

foreach ($this->collectors as $collector) {
$collector->startup();
if ($this->collectorStartupPolicy->satisfies($collector, $event)) {
$collector->startup();
}
}
}

Expand Down Expand Up @@ -118,36 +114,6 @@ public function stop(): void
$this->id = null;
}

private function isRequestIgnored(ServerRequestInterface $request): bool
{
if ($request->hasHeader('X-Debug-Ignore') && $request->getHeaderLine('X-Debug-Ignore') === 'true') {
return true;
}
$path = $request->getUri()->getPath();
foreach ($this->ignoredRequests as $pattern) {
if ((new WildcardPattern($pattern))->match($path)) {
return true;
}
}
return false;
}

private function isCommandIgnored(?string $command): bool
{
if ($command === null || $command === '') {
return true;
}
if (getenv('YII_DEBUG_IGNORE') === 'true') {
return true;
}
foreach ($this->ignoredCommands as $pattern) {
if ((new WildcardPattern($pattern))->match($command)) {
return true;
}
}
return false;
}

/**
* Collects summary data of current request.
*/
Expand Down
15 changes: 15 additions & 0 deletions src/StartupPolicy/Collector/AllowAllCollectorPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\StartupPolicy\Collector;

use Yiisoft\Yii\Debug\Collector\CollectorInterface;

final class AllowAllCollectorPolicy implements CollectorStartupPolicyInterface
{
public function satisfies(CollectorInterface $collector, object $event): bool
{
return true;
}
}
30 changes: 30 additions & 0 deletions src/StartupPolicy/Collector/BlackListCollectorPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\StartupPolicy\Collector;

use Yiisoft\Yii\Debug\Collector\CollectorInterface;
use Yiisoft\Yii\Debug\StartupPolicy\Condition\ConditionInterface;

final class BlackListCollectorPolicy implements CollectorStartupPolicyInterface
{
public function __construct(
/**
* @var ConditionInterface[]
* @psalm-var array<string, ConditionInterface>
*/
private readonly array $conditions,
) {
}

public function satisfies(CollectorInterface $collector, object $event): bool
{
$condition = $this->conditions[$collector->getName()] ?? null;
if ($condition === null) {
return true;
}

return !$condition->match($event);
}
}
33 changes: 33 additions & 0 deletions src/StartupPolicy/Collector/CallableCollectorPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\StartupPolicy\Collector;

use Yiisoft\Yii\Debug\Collector\CollectorInterface;

/**
* @psalm-type TCallable = callable(CollectorInterface, object): bool
*/
final class CallableCollectorPolicy implements CollectorStartupPolicyInterface
{
/**
* @var callable
* @psalm-var TCallable
*/
private $callable;

/**
* @psalm-param TCallable $callable
*/
public function __construct(
callable $callable
) {
$this->callable = $callable;
}

public function satisfies(CollectorInterface $collector, object $event): bool
{
return ($this->callable)($collector, $event);
}
}
12 changes: 12 additions & 0 deletions src/StartupPolicy/Collector/CollectorStartupPolicyInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\StartupPolicy\Collector;

use Yiisoft\Yii\Debug\Collector\CollectorInterface;

interface CollectorStartupPolicyInterface
{
public function satisfies(CollectorInterface $collector, object $event): bool;
}
30 changes: 30 additions & 0 deletions src/StartupPolicy/Collector/WhiteListCollectorPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\StartupPolicy\Collector;

use Yiisoft\Yii\Debug\Collector\CollectorInterface;
use Yiisoft\Yii\Debug\StartupPolicy\Condition\ConditionInterface;

final class WhiteListCollectorPolicy implements CollectorStartupPolicyInterface
{
public function __construct(
/**
* @var ConditionInterface[]
* @psalm-var array<string, ConditionInterface>
*/
private readonly array $conditions,
) {
}

public function satisfies(CollectorInterface $collector, object $event): bool
{
$condition = $this->conditions[$collector->getName()] ?? null;
if ($condition === null) {
return false;
}

return $condition->match($event);
}
}
37 changes: 37 additions & 0 deletions src/StartupPolicy/Condition/CommandNameCondition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\StartupPolicy\Condition;

use Yiisoft\Strings\WildcardPattern;
use Yiisoft\Yii\Console\Event\ApplicationStartup;

final class CommandNameCondition implements ConditionInterface
{
public function __construct(
/**
* @var string[]
* @psalm-var list<non-empty-string>
*/
private readonly array $names,
) {
}

public function match(object $event): bool
{
if (!$event instanceof ApplicationStartup) {
return false;
}

$name = (string) $event->commandName;

foreach ($this->names as $pattern) {
if ((new WildcardPattern($pattern, [':']))->match($name)) {
return true;
}
}

return false;
}
}
10 changes: 10 additions & 0 deletions src/StartupPolicy/Condition/ConditionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\StartupPolicy\Condition;

interface ConditionInterface
{
public function match(object $event): bool;
}
23 changes: 23 additions & 0 deletions src/StartupPolicy/Condition/EnvironmentVariableCondition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\StartupPolicy\Condition;

use function in_array;

final class EnvironmentVariableCondition implements ConditionInterface
{
private const TRUE_VALUES = ['1', 'true', 'on'];

public function __construct(
private readonly string $variableName,
) {
}

public function match(object $event): bool
{
$value = getenv($this->variableName);
return $value !== false && in_array(strtolower($value), self::TRUE_VALUES, true);
}
}
35 changes: 35 additions & 0 deletions src/StartupPolicy/Condition/HeaderCondition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\StartupPolicy\Condition;

use Yiisoft\Yii\Http\Event\BeforeRequest;

use function in_array;
use function strtolower;

final class HeaderCondition implements ConditionInterface
{
private const TRUE_VALUES = ['1', 'true', 'on'];

public function __construct(
/**
* @psalm-var non-empty-string
*/
private readonly string $headerName,
) {
}

public function match(object $event): bool
{
if (!$event instanceof BeforeRequest) {
return false;
}

$request = $event->getRequest();

return $request->hasHeader($this->headerName)
&& in_array(strtolower($request->getHeaderLine($this->headerName)), self::TRUE_VALUES, true);
}
}
Loading

0 comments on commit b326385

Please sign in to comment.