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

Introduce startup policy #303

Merged
merged 26 commits into from
Jan 18, 2025
Merged
Show file tree
Hide file tree
Changes from 9 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
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\CommandCondition;
use Yiisoft\Yii\Debug\StartupPolicy\Condition\EnvironmentVariableCondition;
use Yiisoft\Yii\Debug\StartupPolicy\StartupPolicy;

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'],
'startupPolicy' => DynamicReference::to(
static fn () => new StartupPolicy(
new EnvironmentVariableCondition('YII_DEBUG_IGNORE'),
new CommandCondition($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\RequestCondition;
use Yiisoft\Yii\Debug\StartupPolicy\StartupPolicy;

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'],
'startupPolicy' => DynamicReference::to(
static fn () => new StartupPolicy(
new EnvironmentVariableCondition('YII_DEBUG_IGNORE'),
new HeaderCondition('X-Debug-Ignore'),
new RequestCondition($params['yiisoft/yii-debug']['ignoredRequests'])
),
),
'excludedClasses' => $params['yiisoft/yii-debug']['excludedClasses'],
],
],
Expand Down
52 changes: 8 additions & 44 deletions src/Debugger.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
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\CollectorPolicyInterface;
use Yiisoft\Yii\Debug\StartupPolicy\StartupPolicy;
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 +29,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 ?StartupPolicy $startupPolicy = null,
vjik marked this conversation as resolved.
Show resolved Hide resolved
private readonly ?CollectorPolicyInterface $collectorPolicy = null,
vjik marked this conversation as resolved.
Show resolved Hide resolved
array $excludedClasses = [],
) {
$preparedCollectors = [];
Expand All @@ -64,18 +60,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->startupPolicy?->shouldPrevent($event) === true) {
return;
}

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

foreach ($this->collectors as $collector) {
$collector->startup();
if ($this->collectorPolicy?->shouldStartup($collector, $event) !== false) {
$collector->startup();
}
}
}

Expand Down Expand Up @@ -118,36 +112,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
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 CollectorPolicyInterface
{
public function __construct(
/**
* @var ConditionInterface[]
* @psalm-var array<string, ConditionInterface>
*/
private readonly array $conditions,
) {
}

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

return !$condition->match($event);
}
}
12 changes: 12 additions & 0 deletions src/StartupPolicy/Collector/CollectorPolicyInterface.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 CollectorPolicyInterface
xepozz marked this conversation as resolved.
Show resolved Hide resolved
{
public function shouldStartup(CollectorInterface $collector, object $event): bool;
xepozz marked this conversation as resolved.
Show resolved Hide resolved
}
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 CollectorPolicyInterface
{
public function __construct(
/**
* @var ConditionInterface[]
* @psalm-var array<string, ConditionInterface>
*/
private readonly array $conditions,
) {
}

public function shouldStartup(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/CommandCondition.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 CommandCondition implements ConditionInterface
xepozz marked this conversation as resolved.
Show resolved Hide resolved
{
public function __construct(
/**
* @var string[]
* @psalm-var list<string>
*/
private readonly array $commands,
) {
}

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

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

foreach ($this->commands as $pattern) {
if ((new WildcardPattern($pattern))->match($command)) {
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;
}
18 changes: 18 additions & 0 deletions src/StartupPolicy/Condition/EnvironmentVariableCondition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\StartupPolicy\Condition;

final class EnvironmentVariableCondition implements ConditionInterface
{
public function __construct(
private readonly string $variableName,
) {
}

public function match(object $event): bool
{
return (bool) getenv($this->variableName);
}
}
26 changes: 26 additions & 0 deletions src/StartupPolicy/Condition/HeaderCondition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\StartupPolicy\Condition;

use Yiisoft\Yii\Http\Event\BeforeRequest;

final class HeaderCondition implements ConditionInterface
{
public function __construct(
private readonly string $headerName,
) {
}

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

$request = $event->getRequest();

return $request->hasHeader($this->headerName) && $request->getHeaderLine($this->headerName);
}
}
18 changes: 18 additions & 0 deletions src/StartupPolicy/Condition/PredefinedCondition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Debug\StartupPolicy\Condition;

final class PredefinedCondition implements ConditionInterface
{
public function __construct(
private readonly bool $match,
) {
}

public function match(object $event): bool
{
return $this->match;
}
}
37 changes: 37 additions & 0 deletions src/StartupPolicy/Condition/RequestCondition.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\Http\Event\BeforeRequest;

final class RequestCondition implements ConditionInterface
xepozz marked this conversation as resolved.
Show resolved Hide resolved
{
public function __construct(
/**
* @var string[]
* @psalm-var list<string>
*/
private readonly array $uriPaths,
) {
}

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

$path = $event->getRequest()->getUri()->getPath();

foreach ($this->uriPaths as $pattern) {
if ((new WildcardPattern($pattern))->match($path)) {
return true;
}
}

return false;
}
}
Loading
Loading