Skip to content

Commit

Permalink
Add debug command
Browse files Browse the repository at this point in the history
  • Loading branch information
samdark committed Sep 13, 2024
1 parent 9b8f112 commit 436cf81
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 8 deletions.
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ All you need is to use any [PSR-11](https://www.php-fig.org/psr/psr-11/) compati

## Requirements

- PHP 8.0 or higher.
- PHP 8.1 or higher.

## Installation

Expand All @@ -35,7 +35,7 @@ composer require yiisoft/yii-event

### DI configuration

You can find default configuration in the [config directory](config):
You can find the default configuration in the [config directory](config):

- [di.php](config/di.php) contains the configuration for the [PSR-14](https://www.php-fig.org/psr/psr-14/) interfaces.
- [di-web.php](config/di-web.php) and [di-console.php](config/di-consle.php) contains the configuration for the `Yiisoft\EventDispatcher\Provider\ListenerCollection`.
Expand Down Expand Up @@ -73,13 +73,13 @@ return [
// they will be resolved the same way as in the previous example.
[SomeClass::class, 'staticMethodName'],

// Non-static methods are allowed too. In this case `SomeClass` will be instantiated by your DI container.
// Non-static methods are allowed too. In this case, `SomeClass` will be instantiated by your DI container.
[SomeClass::class, 'methodName'],

// An object of a class with the `__invoke` method implemented
new InvokableClass(),

// In this case the `InvokableClass` with the `__invoke` method will be instantiated by your DI container
// In this case, the `InvokableClass` with the `__invoke` method will be instantiated by your DI container
InvokableClass::class,

// Any definition of an invokable class may be here while your `$container->has('the definition)`
Expand All @@ -89,11 +89,11 @@ return [
```

The dependency resolving is done in a lazy way: dependencies will not be resolved before the corresponding event
will happen.
happens.

### Configuration checker

To help you with event listener configuration validation there is the `ListenerConfigurationChecker`. It is converting
To help you with event listener configuration validation, there is the `ListenerConfigurationChecker`. It is converting
your whole listener config to actual callables at once to validate it. It is intended to be used in development environment
or in tests since it is a resource greedy operation in large projects. An `InvalidEventConfigurationFormatException`
will be thrown if your configuration contains an invalid listener.
Expand All @@ -104,6 +104,11 @@ Usage example:
$checker->check($configuration->get('events-web'));
```

## Event configuration debugging

If you use the package with Yii3, `./yii debug:events` command is available.
It shows information about events and listeners.

## Documentation

- [Internals](docs/internals.md)
Expand Down
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
}
],
"require": {
"php": "^8.0",
"php": "^8.1",
"psr/container": "^1.0|^2.0",
"yiisoft/event-dispatcher": "^1.0",
"yiisoft/friendly-exception": "^1.0",
"yiisoft/injector": "^1.0"
"yiisoft/injector": "^1.0",
"symfony/console": "^5.4|^6.0",
"symfony/var-dumper": "^5.4|^6.4"
},
"require-dev": {
"maglnet/composer-require-checker": "^4.4",
Expand Down
10 changes: 10 additions & 0 deletions config/params-console.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@

declare(strict_types=1);

use Yiisoft\Yii\Event\Command\DebugEventsCommand;

return [
'yiisoft/yii-event' => [
'eventsConfigGroup' => 'events-console',
],
'yiisoft/yii-debug' => [
'ignoredCommands' => [
'debug:events',
],
],
'yiisoft/yii-console' => [
'debug:events' => DebugEventsCommand::class,
]
];
124 changes: 124 additions & 0 deletions src/Command/DebugEventsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Event\Command;

use Psr\Container\ContainerInterface;
use ReflectionClass;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\VarDumper\VarDumper as SymfonyVarDumper;
use Yiisoft\Config\ConfigInterface;
use Yiisoft\VarDumper\VarDumper;

#[AsCommand(
name: 'debug:events',
description: 'Show information about events and listeners',
)]
final class DebugEventsCommand extends Command
{
public function __construct(
private readonly ContainerInterface $container,
) {
parent::__construct();
}

protected function configure(): void
{
$this
->addArgument('id', InputArgument::IS_ARRAY, 'Service ID')
->addOption('groups', null, InputOption::VALUE_NONE, 'Show groups')
->addOption('group', 'g', InputOption::VALUE_REQUIRED, 'Show group');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$config = $this->container->get(ConfigInterface::class);

$io = new SymfonyStyle($input, $output);

if ($input->hasOption('groups') && $input->getOption('groups')) {
$build = $this->getConfigBuild($config);
$groups = array_keys($build);
sort($groups);

$io->table(['Groups'], array_map(static fn ($group) => [$group], $groups));

return self::SUCCESS;
}
if ($input->hasOption('group') && !empty($group = $input->getOption('group'))) {
$data = $config->get($group);

Check failure on line 57 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

MixedMethodCall

src/Command/DebugEventsCommand.php:57:30: MixedMethodCall: Cannot determine the type of $config when calling method get (see https://psalm.dev/015)

Check failure on line 57 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MixedMethodCall

src/Command/DebugEventsCommand.php:57:30: MixedMethodCall: Cannot determine the type of $config when calling method get (see https://psalm.dev/015)
ksort($data);

Check failure on line 58 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

MixedArgument

src/Command/DebugEventsCommand.php:58:19: MixedArgument: Argument 1 of ksort cannot be mixed, expecting array<array-key, mixed> (see https://psalm.dev/030)

Check failure on line 58 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MixedArgument

src/Command/DebugEventsCommand.php:58:19: MixedArgument: Argument 1 of ksort cannot be mixed, expecting array<array-key, mixed> (see https://psalm.dev/030)
$table = new Table($output);

foreach ($data as $event => $listeners) {
$io->title($event);

Check failure on line 62 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

MixedArgument

src/Command/DebugEventsCommand.php:62:28: MixedArgument: Argument 1 of Symfony\Component\Console\Style\SymfonyStyle::title cannot be mixed, expecting string (see https://psalm.dev/030)

Check failure on line 62 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MixedArgument

src/Command/DebugEventsCommand.php:62:28: MixedArgument: Argument 1 of Symfony\Component\Console\Style\SymfonyStyle::title cannot be mixed, expecting string (see https://psalm.dev/030)
foreach ($listeners as $listener) {
if (is_callable($listener) && !is_array($listener)) {
SymfonyVarDumper::dump($this->export($listener));
} else {
SymfonyVarDumper::dump($listener);
}
}
$table->render();
$io->newLine();
}
return self::SUCCESS;
}

$data = [];
if ($config->has('events')) {

Check failure on line 77 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

MixedMethodCall

src/Command/DebugEventsCommand.php:77:22: MixedMethodCall: Cannot determine the type of $config when calling method has (see https://psalm.dev/015)

Check failure on line 77 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MixedMethodCall

src/Command/DebugEventsCommand.php:77:22: MixedMethodCall: Cannot determine the type of $config when calling method has (see https://psalm.dev/015)
$data = array_merge($data, $config->get('events'));

Check failure on line 78 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

MixedArgument

src/Command/DebugEventsCommand.php:78:40: MixedArgument: Argument 2 of array_merge cannot be mixed, expecting array<array-key, mixed> (see https://psalm.dev/030)

Check failure on line 78 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

MixedMethodCall

src/Command/DebugEventsCommand.php:78:49: MixedMethodCall: Cannot determine the type of $config when calling method get (see https://psalm.dev/015)

Check failure on line 78 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MixedArgument

src/Command/DebugEventsCommand.php:78:40: MixedArgument: Argument 2 of array_merge cannot be mixed, expecting array<array-key, mixed> (see https://psalm.dev/030)

Check failure on line 78 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MixedMethodCall

src/Command/DebugEventsCommand.php:78:49: MixedMethodCall: Cannot determine the type of $config when calling method get (see https://psalm.dev/015)
}
if ($config->has('events-console')) {

Check failure on line 80 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

MixedMethodCall

src/Command/DebugEventsCommand.php:80:22: MixedMethodCall: Cannot determine the type of $config when calling method has (see https://psalm.dev/015)

Check failure on line 80 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MixedMethodCall

src/Command/DebugEventsCommand.php:80:22: MixedMethodCall: Cannot determine the type of $config when calling method has (see https://psalm.dev/015)
$data = array_merge($data, $config->get('events-console'));

Check failure on line 81 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

MixedArgument

src/Command/DebugEventsCommand.php:81:40: MixedArgument: Argument 2 of array_merge cannot be mixed, expecting array<array-key, mixed> (see https://psalm.dev/030)

Check failure on line 81 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

MixedMethodCall

src/Command/DebugEventsCommand.php:81:49: MixedMethodCall: Cannot determine the type of $config when calling method get (see https://psalm.dev/015)

Check failure on line 81 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MixedArgument

src/Command/DebugEventsCommand.php:81:40: MixedArgument: Argument 2 of array_merge cannot be mixed, expecting array<array-key, mixed> (see https://psalm.dev/030)

Check failure on line 81 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MixedMethodCall

src/Command/DebugEventsCommand.php:81:49: MixedMethodCall: Cannot determine the type of $config when calling method get (see https://psalm.dev/015)
}
$rows = [];
foreach ($data as $event => $listeners) {
$rows[] = [
$event,
is_countable($listeners) ? count($listeners) : 0,
implode(
"\n",
array_map(function (mixed $listener) {
if (is_array($listener)) {
return sprintf(
'%s::%s',
$listener[0],

Check failure on line 94 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

MixedArgument

src/Command/DebugEventsCommand.php:94:33: MixedArgument: Argument 2 of sprintf cannot be mixed, expecting float|int|object{__tostring()}|string (see https://psalm.dev/030)

Check failure on line 94 in src/Command/DebugEventsCommand.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MixedArgument

src/Command/DebugEventsCommand.php:94:33: MixedArgument: Argument 2 of sprintf cannot be mixed, expecting float|int|object{__tostring()}|string (see https://psalm.dev/030)
$listener[1]
);
}
return $this->export($listener);
}, $listeners)
),
];
}
$table = new Table($output);
$table
->setHeaders(['Event', 'Count', 'Listeners'])
->setRows($rows);
$table->render();

return self::SUCCESS;
}

private function getConfigBuild(mixed $config): array
{
$reflection = new ReflectionClass($config);
$buildReflection = $reflection->getProperty('build');
$buildReflection->setAccessible(true);
return $buildReflection->getValue($config);
}

protected function export(mixed $value): string
{
return VarDumper::create($value)->asString();
}
}
45 changes: 45 additions & 0 deletions tests/Command/DebugEventsCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Yiisoft\Yii\Event\Tests\Command;

use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Console\Tester\CommandTester;
use Yiisoft\Config\Config;
use Yiisoft\Config\ConfigInterface;
use Yiisoft\Config\ConfigPaths;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
use Yiisoft\Yii\Event\Command\DebugEventsCommand;

final class DebugEventsCommandTest extends TestCase
{
public function testCommand(): void
{
$container = $this->createContainer();
$command = new DebugEventsCommand($container);
$commandTester = new CommandTester($command);
$commandTester->execute([]);

$commandTester->assertCommandIsSuccessful();
$output = $commandTester->getDisplay();
$this->assertStringContainsString('Listeners', $output);
}

private function createContainer(): ContainerInterface
{
$config = ContainerConfig::create()
->withDefinitions([
LoggerInterface::class => NullLogger::class,
ConfigInterface::class => [
'class' => Config::class,
'__construct()' => [
new ConfigPaths(__DIR__ . '/config'),
],
],
]);
return new Container($config);
}
}
11 changes: 11 additions & 0 deletions tests/Command/config/.merge-plan.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

return [
'/'=>[
'params' => [

]
],
];
13 changes: 13 additions & 0 deletions tests/Command/config/param1.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

return [
'/' => [
'params' => [
'yiitest/yii-debug' => [
'param1.php',
],
],
],
];

0 comments on commit 436cf81

Please sign in to comment.