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 9be6698 commit 624482f
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 4 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,11 @@ $config = ContainerConfig::create()
$container = new Container($config);
```

## Configuration debugging

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

## Documentation

- [Internals](docs/internals.md)
Expand Down
20 changes: 16 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@
}
],
"require": {
"php": "^8.0",
"php": "^8.1",
"ext-mbstring": "*",
"psr/container": "^1.1|^2.0",
"yiisoft/definitions": "^3.0"
"yiisoft/definitions": "^3.0",
"yiisoft/var-dumper": "^1.7",
"symfony/console": "^5.4|^6.0"
},
"require-dev": {
"league/container": "^4.2",
Expand All @@ -47,7 +49,8 @@
"spatie/phpunit-watcher": "^1.23",
"vimeo/psalm": "^4.30|^5.7",
"yiisoft/injector": "^1.0",
"yiisoft/test-support": "^3.0"
"yiisoft/test-support": "^3.0",
"yiisoft/config": "^1.3"
},
"suggest": {
"yiisoft/injector": "^1.0",
Expand All @@ -66,6 +69,14 @@
"Yiisoft\\Di\\Tests\\": "tests"
}
},
"extra": {
"config-plugin-options": {
"source-directory": "config"
},
"config-plugin": {
"params": "params.php"
}
},
"scripts": {
"test": "phpunit --testdox --no-interaction",
"test-watch": "phpunit-watcher watch"
Expand All @@ -77,7 +88,8 @@
"sort-packages": true,
"allow-plugins": {
"infection/extension-installer": true,
"composer/package-versions-deprecated": true
"composer/package-versions-deprecated": true,
"yiisoft/config": false
}
}
}
16 changes: 16 additions & 0 deletions config/params.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

use Yiisoft\Di\Command\DebugContainerCommand;

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

declare(strict_types=1);

namespace Yiisoft\Di\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 Yiisoft\Config\ConfigInterface;
use Yiisoft\Definitions\ArrayDefinition;
use Yiisoft\Definitions\CallableDefinition;
use Yiisoft\Definitions\ValueDefinition;
use Yiisoft\Di\Helpers\DefinitionNormalizer;
use Yiisoft\VarDumper\VarDumper;

#[AsCommand(
name: 'debug:container',
description: 'Show information about container',
)]
final class DebugContainerCommand 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->hasArgument('id') && !empty($ids = $input->getArgument('id'))) {
$build = $this->getConfigBuild($config);
foreach ($ids as $id) {
$definition = null;
foreach ($build as $definitions) {
if (array_key_exists($id, $definitions)) {
$definition = $definitions[$id];
}
}
if ($definition === null) {
$io->error(
sprintf(
'Service "%s" not found.',
$id,
)
);
continue;
}
$io->title($id);

$normalizedDefinition = DefinitionNormalizer::normalize($definition, $id);
if ($normalizedDefinition instanceof ArrayDefinition) {
$definitionList = ['ID' => $id];
if (class_exists($normalizedDefinition->getClass())) {
$definitionList[] = ['Class' => $normalizedDefinition->getClass()];
}
if (!empty($normalizedDefinition->getConstructorArguments())) {
$definitionList[] = [
'Constructor' => $this->export(
$normalizedDefinition->getConstructorArguments()
),
];
}
if (!empty($normalizedDefinition->getMethodsAndProperties())) {
$definitionList[] = [
'Methods' => $this->export(
$normalizedDefinition->getMethodsAndProperties()
),
];
}
if (isset($definition['tags'])) {
$definitionList[] = ['Tags' => $this->export($definition['tags'])];
}

$io->definitionList(...$definitionList);

continue;
}
if ($normalizedDefinition instanceof CallableDefinition || $normalizedDefinition instanceof ValueDefinition) {
$io->text(
$this->export($definition)
);
continue;
}

$output->writeln([
$id,
VarDumper::create($normalizedDefinition)->asString(),
]);
}

return self::SUCCESS;
}

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);
ksort($data);

$rows = $this->getGroupServices($data);

$table = new Table($output);
$table
->setHeaderTitle($group)
->setHeaders(['Service', 'Definition'])
->setRows($rows);
$table->render();

return self::SUCCESS;
}

$build = $this->getConfigBuild($config);

foreach ($build as $group => $data) {
$rows = $this->getGroupServices($data);

$table = new Table($output);
$table
->setHeaderTitle($group)
->setHeaders(['Group', 'Services'])
->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 getGroupServices(array $data): array
{
$rows = [];
foreach ($data as $id => $definition) {
$class = '';
if (is_string($definition)) {
$class = $definition;
}
if (is_array($definition)) {
$class = $definition['class'] ?? $id;
}
if (is_object($definition)) {
$class = $definition::class;
}

$rows[] = [
$id,
$class,
];
}
return $rows;
}

protected function export(mixed $value): string
{
return VarDumper::create($value)->asString();
}
}

49 changes: 49 additions & 0 deletions tests/Unit/Command/DebugContainerCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Yiisoft\Di\Tests\Unit\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\Command\DebugContainerCommand;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

final class DebugContainerCommandTest extends TestCase
{
public function testCommand(): void
{
$container = $this->createContainer();
$config = $container->get(ConfigInterface::class);
// trigger config build
$config->get('params');

$command = new DebugContainerCommand($container);
$commandTester = new CommandTester($command);

$commandTester->execute([]);

$this->assertEquals(0, $commandTester->getStatusCode());
}

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/Unit/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/Unit/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 624482f

Please sign in to comment.