Skip to content

Commit

Permalink
Merge pull request #4 from lmc-eu/feature/add-config-for-verbosity
Browse files Browse the repository at this point in the history
Feature/add config for verbosity
  • Loading branch information
MortalFlesh authored Apr 5, 2022
2 parents 381b279 + af4bfaa commit 19af387
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 30 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<!-- There should always be "Unreleased" section at the beginning. -->

## Unreleased
- Allow setting a profiler bag verbosity in bundle configuration
- Format generic classes in profiler

## 1.2.0 - 2021-08-10
- Allow an `$initiator` in `ResponseDecoders` `supports` method
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ composer require lmc/cqrs-bundle

```yaml
lmc_cqrs:
profiler: false # Whether to enable profiler and allow to profile queries and commands [default false]
profiler: false # Whether to enable profiler and allow profiling queries and commands [default false]
debug: false # Whether to enable debug the CQRS by a console command [default false]

cache:
Expand All @@ -41,6 +41,14 @@ lmc_cqrs:
solr: false # Whether should solr extension be active (requires a lmc/cqrs-solr dependency) [default false]
```
### Profiler extended configuration
```yaml
lmc_cqrs:
profiler:
enabled: false # Whether to enable profiler and allow profiling queries and commands [default false]
verbosity: '' # Verbosity level (verbose or debug) for a profiler bag - empty string is a default for normal
```
**TIPs**:
- it is advised to set `profiler: '%kernel.debug%'` so it profiles (and registers all services for profiling) only when it is really used
- you can define `profiler` and `debug` in your `dev/lmc_cqrs.yaml` to only allow it in `dev` Symfony environment
Expand Down
17 changes: 11 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "lmc/cqrs-bundle",
"type": "symfony-bundle",
"description": "A symfony bundle for CQRS library and its extensions for Queries and Commands",
"license": "MIT",
"type": "symfony-bundle",
"require": {
"php": "^7.4",
"ext-json": "*",
"ext-mbstring": "*",
"lmc/cqrs-handler": "^1.1",
"lmc/cqrs-types": "^2.0",
"lmc/cqrs-handler": "^1.3",
"lmc/cqrs-types": "^2.3",
"symfony/config": "^4.4 || ^5.1",
"symfony/console": "^4.4 || ^5.1",
"symfony/dependency-injection": "^4.4 || ^5.1",
Expand All @@ -34,9 +34,6 @@
"lmc/cqrs-http": "Provides http handler and base types for queries and commands.",
"lmc/cqrs-solr": "Provides solr handler and base types for queries and commands."
},
"config": {
"sort-packages": true
},
"autoload": {
"psr-4": {
"Lmc\\Cqrs\\Bundle\\": "src/"
Expand All @@ -47,6 +44,14 @@
"Lmc\\Cqrs\\Bundle\\": "tests/"
}
},
"config": {
"allow-plugins": {
"phpstan/extension-installer": true,
"ergebnis/composer-normalize": true,
"dealerdirect/phpcodesniffer-composer-installer": true
},
"sort-packages": true
},
"scripts": {
"all": [
"@lint",
Expand Down
12 changes: 11 additions & 1 deletion src/Command/DebugCqrsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Lmc\Cqrs\Bundle\Command;

use Lmc\Cqrs\Bundle\Profiler\CqrsDataCollector;
use Lmc\Cqrs\Handler\ProfilerBag;
use Lmc\Cqrs\Types\CommandSenderInterface;
use Lmc\Cqrs\Types\QueryFetcherInterface;
use Lmc\Cqrs\Types\ValueObject\PrioritizedItem;
Expand All @@ -22,6 +23,7 @@ class DebugCqrsCommand extends Command
private ?CqrsDataCollector $cqrsDataCollector;
private bool $isExtensionHttpEnabled;
private bool $isExtensionSolrEnabled;
private ?ProfilerBag $profilerBag;

/**
* @phpstan-param QueryFetcherInterface<mixed, mixed> $queryFetcher
Expand All @@ -33,14 +35,16 @@ public function __construct(
?string $cacheProvider,
?CqrsDataCollector $cqrsDataCollector,
bool $isExtensionHttpEnabled,
bool $isExtensionSolrEnabled
bool $isExtensionSolrEnabled,
?ProfilerBag $profilerBag
) {
$this->queryFetcher = $queryFetcher;
$this->commandSender = $commandSender;
$this->cacheProvider = $cacheProvider;
$this->cqrsDataCollector = $cqrsDataCollector;
$this->isExtensionHttpEnabled = $isExtensionHttpEnabled;
$this->isExtensionSolrEnabled = $isExtensionSolrEnabled;
$this->profilerBag = $profilerBag;
parent::__construct();
}

Expand Down Expand Up @@ -98,8 +102,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$this->io->title('Profiler');
if ($this->cqrsDataCollector !== null) {
$verbosity = 'normal';
if ($this->profilerBag && !empty($currentVerbosity = $this->profilerBag->getVerbosity())) {
$verbosity = $currentVerbosity;
}

$this->io->definitionList(
['Is Enabled' => 'Yes'],
['Verbosity' => $verbosity],
['Data Collector' => get_class($this->cqrsDataCollector)],
);

Expand Down
17 changes: 15 additions & 2 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Lmc\Cqrs\Bundle\DependencyInjection;

use Lmc\Cqrs\Handler\ProfilerBag;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

Expand All @@ -12,8 +13,20 @@ public function getConfigTreeBuilder()
$treeBuilder = new TreeBuilder('lmc_cqrs');
$treeBuilder->getRootNode()
->children()
->booleanNode('profiler')
->defaultFalse()
->arrayNode('profiler')
->beforeNormalization()
->ifTrue(fn ($v) => is_bool($v))
->then(fn (bool $v) => ['enabled' => $v])
->end()
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')
->defaultFalse()
->end()
->scalarNode('verbosity')
->defaultValue(ProfilerBag::VERBOSITY_NORMAL)
->end()
->end()
->end()
->booleanNode('debug')
->defaultFalse()
Expand Down
5 changes: 4 additions & 1 deletion src/DependencyInjection/LmcCqrsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class LmcCqrsExtension extends Extension

public const PARAMETER_CACHE_ENABLED = 'lmc_cqrs.cache.enabled';
public const PARAMETER_CACHE_PROVIDER = 'lmc_cqrs.cache.provider';
public const PARAMETER_PROFILER_VERBOSITY = 'lmc_cqrs.profiler.verbosity';
public const PARAMETER_EXTENSION_HTTP = 'lmc_cqrs.extension.http';
public const PARAMETER_EXTENSION_SOLR = 'lmc_cqrs.extension.solr';

Expand Down Expand Up @@ -82,7 +83,9 @@ private function setUpCache(array $config, ContainerBuilder $container): void

private function tryRegisterProfiler(array $config, ContainerBuilder $container, YamlFileLoader $loader): void
{
if ($config['profiler']) {
if ($config['profiler']['enabled']) {
$container->setParameter(self::PARAMETER_PROFILER_VERBOSITY, $config['profiler']['verbosity']);

$loader->load('services-profiler.yaml');
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/Resources/config/services-profiler.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ services:

lmc_cqrs.profiler_bag:
class: Lmc\Cqrs\Handler\ProfilerBag
calls:
- setVerbosity: ['%lmc_cqrs.profiler.verbosity%']

Lmc\Cqrs\Handler\ProfilerBag: '@lmc_cqrs.profiler_bag'

Expand All @@ -28,3 +30,6 @@ services:
Lmc\Cqrs\Bundle\Service\ErrorProfilerFormatter:
tags:
- { name: lmc_cqrs.profiler_formatter, priority: -1 }

Lmc\Cqrs\Bundle\Service\ClassExtension:
tags: ['twig.extension']
28 changes: 16 additions & 12 deletions src/Resources/views/Profiler/index.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@
.col--warning {
background-color: {{ colors.warning|raw }};
}
small.className {
color: gray;
}
</style>

<div class="metrics">
Expand Down Expand Up @@ -148,7 +152,7 @@
</thead>
<tbody>
<tr>
<td colspan="2">{{ commandItem.type }}</td>
<td colspan="2">{{ commandItem.type|genericClass }}</td>
<td>{{ commandItem.duration }}ms</td>
</tr>
<tr>
Expand All @@ -165,7 +169,7 @@
<td colspan="3">
<ol>
{% for decodedBy in commandItem.decodedBy %}
<li>{{ decodedBy }}</li>
<li>{{ decodedBy|genericClass }}</li>
{% endfor %}
</ol>
</td>
Expand Down Expand Up @@ -220,7 +224,7 @@
</thead>
<tbody>
<tr>
<td colspan="2">{{ queryItem.type }}</td>
<td colspan="2">{{ queryItem.type|genericClass }}</td>
<td>{{ queryItem.duration }}ms</td>
{% if queryItem.isLoadedFromCache %}
<td class="col--success" data-cached-status="{{ currentCacheKey }}">Yes</td>
Expand Down Expand Up @@ -260,7 +264,7 @@
<td colspan="5">
<ol>
{% for decodedBy in queryItem.decodedBy %}
<li>{{ decodedBy }}</li>
<li>{{ decodedBy|genericClass }}</li>
{% endfor %}
</ol>
</td>
Expand Down Expand Up @@ -400,7 +404,7 @@
</thead>
<tbody>
<tr>
<td colspan="2">{{ otherItem.type }}</td>
<td colspan="2">{{ otherItem.type|genericClass }}</td>
<td>{{ otherItem.duration }}ms</td>
</tr>
<tr>
Expand All @@ -417,7 +421,7 @@
<td colspan="2">
<ol>
{% for decodedBy in otherItem.decodedBy %}
<li>{{ decodedBy }}</li>
<li>{{ decodedBy|genericClass }}</li>
{% endfor %}
</ol>
</td>
Expand Down Expand Up @@ -660,27 +664,27 @@
{% macro colWrap(value, colspan = null) %}
{% if value is defined and value.formatted is defined and value.isWide %}
<td{% if colspan is not null %} colspan="{{ colspan }}"{% endif %} title="Original">
<div style="word-wrap: break-word; max-width: 800px;">{{ value.original }}</div>
<div style="word-wrap: break-word; max-width: 800px;">{{ value.original|genericClass }}</div>
</td>
</tr>
<tr>
<td{% if colspan is not null %} colspan="{{ colspan + 1 }}"{% endif %} title="Formatted">
{{ value.formatted }}
{{ value.formatted|genericClass }}
</td>
{% elseif value is defined and value.formatted is defined and value.original is null %}
<td{% if colspan is not null %} colspan="{{ colspan }}"{% endif %} title="Formatted">
<div style="word-wrap: break-word; max-width: 800px;">{{ value.formatted }}</div>
<div style="word-wrap: break-word; max-width: 800px;">{{ value.formatted|genericClass }}</div>
</td>
{% elseif value is defined and value.formatted is defined and colspan is not null %}
<td colspan="{{ (colspan / 2)|round(0, 'floor') }}" title="Original">
<div style="word-wrap: break-word; max-width: 400px;">{{ value.original }}</div>
<div style="word-wrap: break-word; max-width: 400px;">{{ value.original|genericClass }}</div>
</td>
<td colspan="{{ (colspan / 2)|round(0, 'ceil') }}" title="Formatted">
<div style="word-wrap: break-word; max-width: 400px;">{{ value.formatted }}</div>
<div style="word-wrap: break-word; max-width: 400px;">{{ value.formatted|genericClass }}</div>
</td>
{% else %}
<td{% if colspan is not null %} colspan="{{ colspan }}"{% endif %}>
<div style="word-wrap: break-word; max-width: 800px;">{{ value }}</div>
<div style="word-wrap: break-word; max-width: 800px;">{{ value|genericClass }}</div>
</td>
{% endif %}
{% endmacro %}
Expand Down
97 changes: 97 additions & 0 deletions src/Service/ClassExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php declare(strict_types=1);

namespace Lmc\Cqrs\Bundle\Service;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;

class ClassExtension extends AbstractExtension
{
public function getFilters()
{
return [
new TwigFilter(
'genericClass',
fn (string $class) => $this->formatGenericClass($class),
['is_safe' => ['html']]
),
];
}

private function formatGenericClass(string $class): string
{
if (empty($class)) {
return $class;
}

try {
if ($this->tryParseClassWithoutGenerics($class, $shortName)) {
return sprintf(
'<small class="className">%s</small><strong>%s</strong>',
$this->replaceOnceFromEnd($shortName, '', $class),
$shortName
);
} elseif ($this->tryParseClassWithGenerics($class, $shortName, $genericArguments)) {
if ($this->tryParseClassWithGenerics($genericArguments)) {
$generics = $this->formatGenericClass($genericArguments);
} else {
$generics = array_map(
fn (string $genericArgument) => $this->formatGenericClass(trim($genericArgument)),
explode(',', $genericArguments)
);

$generics = implode(', ', $generics);
}

[$classWithoutGenerics] = explode('<', $class, 2);

return sprintf(
'<small class="className">%s</small><strong>%s</strong>&lt;%s&gt;',
$this->replaceOnceFromEnd($shortName, '', $classWithoutGenerics),
$shortName,
$generics,
);
}

return $class;
} catch (\Throwable $e) {
return $class;
}
}

private function replaceOnceFromEnd(string $search, string $replace, string $value): string
{
$position = mb_strrpos($value, $search);
if ($position === false) {
return $value;
}

return substr_replace($value, $replace, $position, mb_strlen($search));
}

private function tryParseClassWithoutGenerics(string $class, ?string &$shortClassName = null): bool
{
if (preg_match('/^([A-Z][A-Za-z0-9]*\\\\)*([A-Z][A-Za-z]*?)$/', $class, $matches) === 1) {
$shortClassName = array_pop($matches);

return true;
}

return false;
}

private function tryParseClassWithGenerics(
string $class,
?string &$shortClassName = null,
?string &$genericArguments = null
): bool {
if (preg_match('/^([A-Z][A-Za-z0-9]*\\\\)*([A-Z][A-Za-z]*?)<(.*)>$/', $class, $matches) === 1) {
$genericArguments = array_pop($matches);
$shortClassName = array_pop($matches);

return true;
}

return false;
}
}
4 changes: 3 additions & 1 deletion tests/DependencyInjection/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ public function configurationDefinition(): void

$reference = <<<CONFIG
lmc_cqrs:
profiler:%w false
profiler:
enabled:%w false
verbosity:%w ''
debug:%w false
cache:
enabled:%w null
Expand Down
Loading

0 comments on commit 19af387

Please sign in to comment.