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 debug command #73

Open
wants to merge 7 commits into
base: master
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 2.1.1 under development

- Enh #65: Minor refactoring of `CallableFactory` and `ListenerCollectionFactory` (@vjik)
- New #73: Add `debug:events` console command (@samdark, @xepozz)
- Enh #76: Raise minimum PHP version to `^8.1` and refactor code (@vjik)

## 2.1.0 November 04, 2023
Expand Down
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,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 @@ -72,13 +72,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 @@ -88,11 +88,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 @@ -103,6 +103,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
8 changes: 7 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@
"vimeo/psalm": "^5.26",
"yiisoft/config": "^1.3",
"yiisoft/di": "^1.2",
"yiisoft/test-support": "^3.0"
"yiisoft/test-support": "^3.0",
"symfony/console": "^5.4|^6.0",
"symfony/var-dumper": "^5.4|^6.4"
},
"suggest": {
"symfony/console": "For debug:events command",
"symfony/var-dumper": "For debug:events command"
},
"autoload": {
"psr-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);

Check warning on line 50 in src/Command/DebugEventsCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L48-L50

Added lines #L48 - L50 were not covered by tests

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

Check warning on line 52 in src/Command/DebugEventsCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L52

Added line #L52 was not covered by tests

return self::SUCCESS;

Check warning on line 54 in src/Command/DebugEventsCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L54

Added line #L54 was not covered by tests
}
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.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.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);

Check warning on line 59 in src/Command/DebugEventsCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L57-L59

Added lines #L57 - L59 were not covered by tests

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.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));

Check warning on line 65 in src/Command/DebugEventsCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L61-L65

Added lines #L61 - L65 were not covered by tests
} else {
SymfonyVarDumper::dump($listener);

Check warning on line 67 in src/Command/DebugEventsCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L67

Added line #L67 was not covered by tests
}
}
$table->render();
$io->newLine();

Check warning on line 71 in src/Command/DebugEventsCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L70-L71

Added lines #L70 - L71 were not covered by tests
}
return self::SUCCESS;

Check warning on line 73 in src/Command/DebugEventsCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L73

Added line #L73 was not covered by tests
}

$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.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.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)

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

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L78

Added line #L78 was not covered by tests
}
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.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.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)

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

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L81

Added line #L81 was not covered by tests
}
$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.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]

Check warning on line 95 in src/Command/DebugEventsCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L85-L95

Added lines #L85 - L95 were not covered by tests
);
}
return $this->export($listener);
}, $listeners)
),
];

Check warning on line 101 in src/Command/DebugEventsCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L98-L101

Added lines #L98 - L101 were not covered by tests
}
$table = new Table($output);
$table
->setHeaders(['Event', 'Count', 'Listeners'])
->setRows($rows);
$table->render();

return self::SUCCESS;
}

private function getConfigBuild(mixed $config): array

Check warning on line 112 in src/Command/DebugEventsCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L112

Added line #L112 was not covered by tests
{
$reflection = new ReflectionClass($config);
$buildReflection = $reflection->getProperty('build');
$buildReflection->setAccessible(true);
return $buildReflection->getValue($config);

Check warning on line 117 in src/Command/DebugEventsCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L114-L117

Added lines #L114 - L117 were not covered by tests
}

protected function export(mixed $value): string

Check warning on line 120 in src/Command/DebugEventsCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L120

Added line #L120 was not covered by tests
{
return VarDumper::create($value)->asString();

Check warning on line 122 in src/Command/DebugEventsCommand.php

View check run for this annotation

Codecov / codecov/patch

src/Command/DebugEventsCommand.php#L122

Added line #L122 was not covered by tests
}
}
47 changes: 47 additions & 0 deletions tests/Command/DebugEventsCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

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',
],
],
],
];
Loading