diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..39b22e7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.php] +indent_style = space +indent_size = 4 + +[*.{yaml,yml,neon}] +indent_style = space +indent_size = 4 + +[*.{json,js}] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 365c6af..d25d9db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,10 +4,10 @@ on: [push] jobs: tests: name: Tests - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: matrix: - version: ['8.1'] + version: ['8.2', '8.3', '8.4'] flags: ['', '--prefer-lowest'] fail-fast: false steps: @@ -15,6 +15,6 @@ jobs: - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.version }} - coverage: xdebug2 + coverage: xdebug - run: composer update --prefer-dist --no-interaction ${{ matrix.flags }} - run: vendor/bin/atoum diff --git a/.gitignore b/.gitignore index 7579f74..408b291 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ vendor composer.lock +/.php-cs-fixer.cache diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..46dbe3f --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,36 @@ +in(__DIR__ . '/src') + ->exclude('vendor') + ->name('*.php'); + +return (new Config()) + ->setRiskyAllowed(true) + ->setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + '@PHP82Migration' => true, + '@PHP80Migration:risky' => true, + 'declare_strict_types' => false, + 'ordered_imports' => [ + 'sort_algorithm' => 'alpha' + ], + 'no_unused_imports' => true, + 'array_syntax' => ['syntax' => 'short'], + 'concat_space' => ['spacing' => 'one'], + 'phpdoc_order' => true, + 'phpdoc_separation' => true, + 'global_namespace_import' => ['import_classes' => false], + 'native_function_invocation' => [ + 'include' => [ + '@compiler_optimized' + ], 'scope' => 'namespaced' + ], + ]) + ->setFinder($finder); diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4ba2ad6 --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +.PHONY: help rector rector-dry rector-fix cs cs-fix phpstan + +help: + @echo "Available commands:" + @echo " make rector-fix - Run Rector and apply changes" + @echo " make rector-dry - Run Rector in dry-run mode (no modifications)" + @echo " make cs - Run PHP CS Fixer in check mode (no modifications)" + @echo " make cs-fix - Run PHP CS Fixer and apply changes" + @echo " make phpstan - Run PHPStan static analysis" + + +rector: rector-dry + +rector-dry: + @echo "Running Rector in dry-run mode..." + @./vendor/bin/rector process --dry-run + +rector-fix: + @echo "Running Rector and applying changes..." + @./vendor/bin/rector process + +cs: + @echo "Running PHP CS Fixer in check mode..." + @./vendor/bin/php-cs-fixer check --diff + +cs-fix: + @echo "Running PHP CS Fixer and applying changes..." + @./vendor/bin/php-cs-fixer fix + +phpstan: + @echo "Running PHPStan..." + @./vendor/bin/phpstan analyse -c phpstan.dist.neon diff --git a/composer.json b/composer.json index aa8baa5..d70f70d 100644 --- a/composer.json +++ b/composer.json @@ -13,17 +13,27 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/framework-bundle": "~5.0 || ~6.0 || ~7.0", "symfony/monolog-bundle": "~3.0", "symfony/expression-language": "~5.0 || ~6.0 || ~7.0", - "monolog/monolog": "^3.0" + "monolog/monolog": "^3.10" }, "require-dev": { - "atoum/atoum": "~3.4 || ~4.0", - "symfony/yaml": "~5.0 || ~6.0 || ~7.0" + "atoum/atoum": "~4.0", + "symfony/yaml": "~5.0 || ~6.0 || ~7.0", + "rector/rector": "^2.4", + "friendsofphp/php-cs-fixer": "^3.95", + "phpstan/phpstan": "^2.1", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan-symfony": "^2.0" }, "autoload": { "psr-0": { "": "src/" } + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } } } diff --git a/phpstan.dist.neon b/phpstan.dist.neon new file mode 100644 index 0000000..b581d55 --- /dev/null +++ b/phpstan.dist.neon @@ -0,0 +1,10 @@ +parameters: + level: 10 + paths: + - src + excludePaths: + - src/M6Web/Bundle/MonologExtraBundle/Tests + symfony: + containerXmlPath: null + ignoreErrors: [] + reportUnmatchedIgnoredErrors: true diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..ddd5fe5 --- /dev/null +++ b/rector.php @@ -0,0 +1,23 @@ +withPaths([ + __DIR__ . '/src', + ]) + ->withSkip([ + __DIR__ . '/vendor', + ]) + ->withPhpVersion(PhpVersion::PHP_82) + ->withComposerBased( + phpunit: true, + symfony: true, + ) + ->withSets([ + SetList::PHP_82, + ]); diff --git a/src/M6Web/Bundle/MonologExtraBundle/DependencyInjection/Configuration.php b/src/M6Web/Bundle/MonologExtraBundle/DependencyInjection/Configuration.php index 3c45a1d..da77c3b 100644 --- a/src/M6Web/Bundle/MonologExtraBundle/DependencyInjection/Configuration.php +++ b/src/M6Web/Bundle/MonologExtraBundle/DependencyInjection/Configuration.php @@ -6,13 +6,10 @@ use Symfony\Component\Config\Definition\ConfigurationInterface; /** - * This is the class that validates and merges configuration from your app/config files + * This is the class that validates and merges configuration from your app/config files. */ class Configuration implements ConfigurationInterface { - /** - * {@inheritDoc} - */ public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('m6_web_monolog_extra'); @@ -29,9 +26,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->variableNode('config')->end() ->end() ->validate() - ->ifTrue(function ($v) { - return isset($v['handler']) && isset($v['channel']); - }) + ->ifTrue(static fn ($v) => is_array($v) && isset($v['handler'], $v['channel'])) ->thenInvalid('You can define a channel or a handler but not both.') ->end() ->end() diff --git a/src/M6Web/Bundle/MonologExtraBundle/DependencyInjection/M6WebMonologExtraExtension.php b/src/M6Web/Bundle/MonologExtraBundle/DependencyInjection/M6WebMonologExtraExtension.php index 2069dc7..06d969f 100644 --- a/src/M6Web/Bundle/MonologExtraBundle/DependencyInjection/M6WebMonologExtraExtension.php +++ b/src/M6Web/Bundle/MonologExtraBundle/DependencyInjection/M6WebMonologExtraExtension.php @@ -2,54 +2,50 @@ namespace M6Web\Bundle\MonologExtraBundle\DependencyInjection; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; -use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; /** - * This is the class that loads and manages your bundle configuration + * This is the class that loads and manages your bundle configuration. */ class M6WebMonologExtraExtension extends Extension { - /** - * {@inheritDoc} - */ - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { - $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); $loader->load('processors.yml'); $configuration = new Configuration(); + /** @var array{processors: array} $config */ $config = $this->processConfiguration($configuration, $configs); if (!empty($config['processors'])) { $alias = $this->getAlias(); foreach ($config['processors'] as $name => $processor) { - $serviceId = sprintf('%s.processor.%s', $alias, is_int($name) ? uniqid() : $name); + $serviceId = \sprintf('%s.processor.%s', $alias, \is_int($name) ? uniqid() : $name); $tagOptions = []; - if (array_key_exists('channel', $processor)) { + if (\array_key_exists('channel', $processor)) { $tagOptions['channel'] = $processor['channel']; } - if (array_key_exists('handler', $processor)) { + if (\array_key_exists('handler', $processor)) { $tagOptions['handler'] = $processor['handler']; } - $definition = clone $container->getDefinition(sprintf('%s.processor.%s', $alias, lcfirst($processor['type']))); + $definition = clone $container->getDefinition(\sprintf('%s.processor.%s', $alias, lcfirst($processor['type']))); $definition->setAbstract(false); $definition->addtag('monolog.processor', $tagOptions); - if (array_key_exists('config', $processor)) { + if (\array_key_exists('config', $processor)) { if ($definition->hasMethodCall('setConfiguration')) { $definition->removeMethodCall('setConfiguration'); $definition->addMethodCall('setConfiguration', [$processor['config']]); } else { - throw new InvalidConfigurationException( - sprintf('"%s" processor is not configurable.', $processor['type']) - ); + throw new InvalidConfigurationException(\sprintf('"%s" processor is not configurable.', $processor['type'])); } } diff --git a/src/M6Web/Bundle/MonologExtraBundle/M6WebMonologExtraBundle.php b/src/M6Web/Bundle/MonologExtraBundle/M6WebMonologExtraBundle.php index 1966c19..ca25687 100644 --- a/src/M6Web/Bundle/MonologExtraBundle/M6WebMonologExtraBundle.php +++ b/src/M6Web/Bundle/MonologExtraBundle/M6WebMonologExtraBundle.php @@ -5,7 +5,7 @@ use Symfony\Component\HttpKernel\Bundle\Bundle; /** - * Bundle class + * Bundle class. */ class M6WebMonologExtraBundle extends Bundle { diff --git a/src/M6Web/Bundle/MonologExtraBundle/Processor/ContextInformationProcessor.php b/src/M6Web/Bundle/MonologExtraBundle/Processor/ContextInformationProcessor.php index d76e9f1..463b0c3 100644 --- a/src/M6Web/Bundle/MonologExtraBundle/Processor/ContextInformationProcessor.php +++ b/src/M6Web/Bundle/MonologExtraBundle/Processor/ContextInformationProcessor.php @@ -1,4 +1,5 @@ expressionLanguage = $expressionLanguage; } - /** - * Processor configuration - * - * @var array - */ - protected $configuration; + /** @var array */ + protected array $configuration; public function __invoke(LogRecord $record): LogRecord { return $record->with( - context: array_merge($this->evaluateConfiguration(), $record['context']) + context: array_merge($this->evaluateConfiguration(), $record->context) ); } - /** - * Define processor configuration - * - * @param array $config - */ - public function setConfiguration(array $config) + /** @param array $config */ + public function setConfiguration(array $config): void { $this->configuration = $config; } - /** - * Evaluate configuration array - * - * @return array - */ - protected function evaluateConfiguration() + /** @return array */ + protected function evaluateConfiguration(): array { - $context = []; - foreach ($this->configuration as $key => $value) { - $context[$key] = $this->evaluateValue($value); - } - - return $context; + return array_map(fn ($value) => $this->evaluateValue($value), $this->configuration); } /** - * Evaluate configuration value - * - * @param string $value - * - * @return string + * Evaluate configuration value. */ - protected function evaluateValue($value) + protected function evaluateValue(string $value): string { if (preg_match('/^expr\((.*)\)$/', $value, $matches)) { - return $this->expressionLanguage->evaluate($matches[1], ['container' => $this->container]); + $result = $this->expressionLanguage->evaluate($matches[1], ['container' => $this->container]); + if (!is_string($result)) { + throw new \UnexpectedValueException(\sprintf('Expression "%s" must evaluate to a string, got %s.', $matches[1], get_debug_type($result))); + } + + return $result; } return $value; diff --git a/src/M6Web/Bundle/MonologExtraBundle/Tests/Units/DependencyInjection/M6WebMonologExtraExtension.php b/src/M6Web/Bundle/MonologExtraBundle/Tests/Units/DependencyInjection/M6WebMonologExtraExtension.php index 3f5c9db..5f47a21 100644 --- a/src/M6Web/Bundle/MonologExtraBundle/Tests/Units/DependencyInjection/M6WebMonologExtraExtension.php +++ b/src/M6Web/Bundle/MonologExtraBundle/Tests/Units/DependencyInjection/M6WebMonologExtraExtension.php @@ -3,32 +3,30 @@ namespace M6Web\Bundle\MonologExtraBundle\Tests\Units\DependencyInjection; use M6Web\Bundle\MonologExtraBundle\DependencyInjection\M6WebMonologExtraExtension as TestedClass; - -use atoum; use Symfony\Component\DependencyInjection\ContainerBuilder; /** - * Class M6WebMonologExtraExtension + * Class M6WebMonologExtraExtension. */ -class M6WebMonologExtraExtension extends atoum +class M6WebMonologExtraExtension extends \atoum { - public function testLoad() + public function testLoad(): void { $extension = new TestedClass(); $container = new ContainerBuilder(); - $config = array( - 'processors' => array( - 'myProcessor' => array( + $config = [ + 'processors' => [ + 'myProcessor' => [ 'type' => 'ContextInformation', 'handler' => 'gelf', - 'config' => array( + 'config' => [ 'foo' => 'bar', 'bar' => 'foo', 'env' => "expr(container.getParameter('kernel.environment'))", - ), - ), - ), - ); + ], + ], + ], + ]; $extension->load([$config], $container); @@ -38,24 +36,24 @@ public function testLoad() ->boolean($definition->isAbstract()) ->isEqualTo(false) ->array(array_values($definition->getMethodCalls())) - ->isEqualTo(array( - array( + ->isEqualTo([ + [ 'setConfiguration', - array( - array( + [ + [ 'foo' => 'bar', 'bar' => 'foo', 'env' => "expr(container.getParameter('kernel.environment'))", - ) - ) - ) - )) + ], + ], + ], + ]) ->array($definition->getTags()) - ->isEqualTo(array( - 'monolog.processor' => array( - array('handler' => 'gelf'), - ) - )) + ->isEqualTo([ + 'monolog.processor' => [ + ['handler' => 'gelf'], + ], + ]) ; } } diff --git a/src/M6Web/Bundle/MonologExtraBundle/Tests/Units/Processor/ContextInformationProcessor.php b/src/M6Web/Bundle/MonologExtraBundle/Tests/Units/Processor/ContextInformationProcessor.php index e2761f8..0a220b8 100644 --- a/src/M6Web/Bundle/MonologExtraBundle/Tests/Units/Processor/ContextInformationProcessor.php +++ b/src/M6Web/Bundle/MonologExtraBundle/Tests/Units/Processor/ContextInformationProcessor.php @@ -1,18 +1,18 @@ uniqid(), 'bar' => uniqid(), 'expr' => 'expr(container.getParameter("test"))' ]; + $data = ['foo' => uniqid(), 'bar' => uniqid(), 'expr' => 'expr(container.getParameter("test"))']; $container = new ContainerBuilder(); $container->setParameter('test', 'yes'); @@ -20,7 +20,7 @@ public function testInvoke(): void $processor = new Base($container, new ExpressionLanguage()); $processor->setConfiguration($data); - $record = call_user_func($processor, new LogRecord( + $record = $processor(new LogRecord( datetime: new \DateTimeImmutable(), channel: uniqid('channel_', true), level: Level::Info,