Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
daf8d6f
EE-282 Support Symfony 7.4
vinayak-iyer-paysera Sep 23, 2026
04ee5b0
EE-282 Stop the deprecation notices that name the bundle's own classes
vinayak-iyer-paysera Sep 23, 2026
dba19b9
EE-282 Run the workflow on Symfony 7 and let the Symfony 3 and 4 jobs…
vinayak-iyer-paysera Sep 23, 2026
59fe72a
EE-282 Add the 1.4.0 changelog entry
vinayak-iyer-paysera Sep 23, 2026
9f5e898
EE-282 Say which change removes which deprecation notice
vinayak-iyer-paysera Sep 23, 2026
630871f
EE-282 Test that Symfony's debug class loader reports nothing against…
vinayak-iyer-paysera Sep 23, 2026
cbbdd2c
EE-282 Record what the change breaks and leaves, and why the workflow…
vinayak-iyer-paysera Sep 23, 2026
52c2b15
EE-282 Keep the deprecation test green on PHP 8.4 with Symfony 4.4
vinayak-iyer-paysera Sep 23, 2026
084fff9
EE-282 Answer the second review: the test checks its own precondition…
vinayak-iyer-paysera Sep 24, 2026
f055c78
EE-282 Keep the advisory step's reasoning in the PR description
vinayak-iyer-paysera Sep 25, 2026
ec2dce6
EE-282 Merge master with the CI workflow
vinayak-iyer-paysera Sep 25, 2026
80efbdf
EE-282 Keep the deprecation test's reasoning in the PR description
vinayak-iyer-paysera Sep 25, 2026
dabe1ef
EE-282 Run the workflow jobs with lowest dependencies too
vinayak-iyer-paysera Sep 28, 2026
b6f8902
EE-282 Define the services in the extension instead of XML
vinayak-iyer-paysera Sep 29, 2026
aabd352
EE-282 Raise the floors the lowest jobs need instead of excluding them
vinayak-iyer-paysera Sep 29, 2026
1df96f0
EE-282 Let the deprecation test fail on warnings and notices
vinayak-iyer-paysera Sep 30, 2026
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
14 changes: 12 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,26 @@ jobs:
- '4.*'
- '5.*'
- '6.*'
- '7.*'
dependency:
- 'lowest'
- 'highest'
include:
- { php: '7.0', symfony: '3.*', dependency: 'lowest' }
exclude:
- { php: '7.0', symfony: '4.*' }
- { php: '7.0', symfony: '5.*' }
- { php: '7.1', symfony: '5.*' }
- { php: '7.0', symfony: '6.*' }
- { php: '7.1', symfony: '6.*' }
- { php: '7.2', symfony: '6.*' }
- { php: '7.3', symfony: '6.*' }
- { php: '7.4', symfony: '6.*' }
- { php: '7.0', symfony: '7.*' }
- { php: '7.1', symfony: '7.*' }
- { php: '7.2', symfony: '7.*' }
- { php: '7.3', symfony: '7.*' }
- { php: '7.4', symfony: '7.*' }
- { php: '8.0', symfony: '7.*' }
- { php: '8.1', symfony: '7.*' }

steps:
- name: Checkout
Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 1.4.0
### Added
- Support for Symfony 7.4.

### Changed
- `Configuration::getConfigTreeBuilder()` declares the `TreeBuilder` return type, which Symfony 7 requires.
- `PayseraNormalizationExtension` extends `Symfony\Component\DependencyInjection\Extension\Extension` instead of
`Symfony\Component\HttpKernel\DependencyInjection\Extension` (internal since Symfony 7.1, deprecated in 8.1), and
`load()` documents its `void` return. Together with the return type above, this removes the deprecation notices that
Symfony 5.4 and later report for the bundle's own classes when their debug class loader is on.
- Breaking for subclasses of these two classes: an override of `getConfigTreeBuilder()` must declare `: TreeBuilder`, and a
subclass of the extension no longer inherits the class-cache methods of Symfony's HttpKernel `Extension`
(`addAnnotatedClassesToCompile()` and its getter, deprecated since Symfony 7.1, and on Symfony 3.4 also
`addClassesToCompile()` and its getter): a call to one of them fails and an override of the getter is ignored. The
classes are not part of the bundle's public API (see "Semantic versioning" in the README), so this is a minor release.
- The bundle's services are defined in `PayseraNormalizationExtension` instead of `Resources/config/services.xml` and
`Resources/config/services/date_time_normalizer.xml`, which are removed, so Symfony 7.4 reports no "XML configuration
format is deprecated" notice. The service ids, classes, arguments, tags and aliases are unchanged.
- `symfony/config`, `symfony/dependency-injection` and `symfony/http-kernel`, which the bundle's code uses, are required
explicitly. `symfony/framework-bundle`, which only the bundle's tests use, moves to `require-dev`.
- `paysera/lib-normalization` starts at 1.3.1: in earlier releases `DateTimeNormalizer` fails on PHP 8.2 and later,
where `date_get_last_errors()` returns `false`.

## 1.3.1
### Added
- `void` phpdoc typehint to `PayseraNormalizationBundle::build` method to fix the deprecation message on Symfony 6
Expand Down
11 changes: 7 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@
},
"require": {
"php": "^7.0 || ^8.0",
"paysera/lib-normalization": "^1.2",
"symfony/framework-bundle": "^3.4.26|^4.2.7|^5.4|^6.0",
"paysera/lib-normalization": "^1.3.1",
"symfony/config": "^3.4.26|^4.2.7|^5.4|^6.0|^7.4",
"symfony/dependency-injection": "^3.4.26|^4.2.7|^5.4|^6.0|^7.4",
"symfony/http-kernel": "^3.4.26|^4.2.7|^5.4|^6.0|^7.4",
"paysera/lib-dependency-injection": "^1.3.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0 || ^9.0",
"symfony/yaml": "^2.7|^3.0|^4.0|^5.0",
"phpunit/phpunit": "^6.0 || ^9.6.33",
"symfony/framework-bundle": "^3.4.26|^4.2.7|^5.4|^6.0|^7.4",
"symfony/yaml": "^2.7|^3.0|^4.0|^5.0|^6.0|^7.4",
"mockery/mockery": "^1.2",
"yoast/phpunit-polyfills": "^1.0"
},
Expand Down
2 changes: 1 addition & 1 deletion src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('paysera_normalization');
$rootNode = method_exists($treeBuilder, 'getRootNode')
Expand Down
71 changes: 65 additions & 6 deletions src/DependencyInjection/PayseraNormalizationExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@

namespace Paysera\Bundle\NormalizationBundle\DependencyInjection;

use Paysera\Component\Normalization\CoreDenormalizer;
use Paysera\Component\Normalization\CoreNormalizer;
use Paysera\Component\Normalization\DataFilter;
use Paysera\Component\Normalization\Normalizer\DateTimeImmutableNormalizer;
use Paysera\Component\Normalization\Normalizer\DateTimeNormalizer;
use Paysera\Component\Normalization\NormalizerRegistryInterface;
use Paysera\Component\Normalization\Registry\GroupedNormalizerRegistryProvider;
use Paysera\Component\Normalization\TypeAwareInterface;
use Paysera\Component\Normalization\TypeGuesser;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Reference;

class PayseraNormalizationExtension extends Extension
{
Expand All @@ -16,13 +25,16 @@ public static function supportsAutoconfiguration()
return method_exists(ContainerBuilder::class, 'registerForAutoconfiguration');
}

/**
* @param array<array<mixed>|null> $configs
* @return void
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);

$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.xml');
$this->registerServices($container);

if (self::supportsAutoconfiguration()) {
$container
Expand All @@ -34,7 +46,54 @@ public function load(array $configs, ContainerBuilder $container)
if (isset($config['register_normalizers']['date_time'])) {
$dateTimeFormat = $config['register_normalizers']['date_time']['format'];
$container->setParameter('paysera_normalization.date_time_normalizer.format', $dateTimeFormat);
$loader->load('services/date_time_normalizer.xml');
$this->registerDateTimeNormalizers($container);
}
}

private function registerServices(ContainerBuilder $container)
{
$container->setDefinition(
'paysera_normalization.normalizer_registry_provider',
(new Definition(GroupedNormalizerRegistryProvider::class))->setLazy(true)
);
$container->setDefinition(
'paysera_normalization.normalizer_registry',
(new Definition(NormalizerRegistryInterface::class))
->setLazy(true)
->setFactory([
new Reference('paysera_normalization.normalizer_registry_provider'),
'getDefaultNormalizerRegistry',
])
);
$container->setDefinition('paysera_normalization.type_guesser', new Definition(TypeGuesser::class));
$container->setDefinition('paysera_normalization.data_filter', new Definition(DataFilter::class));

$container->setDefinition('paysera_normalization.core_normalizer', new Definition(CoreNormalizer::class, [
new Reference('paysera_normalization.normalizer_registry_provider'),
new Reference('paysera_normalization.type_guesser'),
new Reference('paysera_normalization.data_filter'),
]));
$container->setAlias(CoreNormalizer::class, new Alias('paysera_normalization.core_normalizer'));

$container->setDefinition('paysera_normalization.core_denormalizer', new Definition(CoreDenormalizer::class, [
new Reference('paysera_normalization.normalizer_registry_provider'),
]));
$container->setAlias(CoreDenormalizer::class, new Alias('paysera_normalization.core_denormalizer'));
}

private function registerDateTimeNormalizers(ContainerBuilder $container)
{
$format = '%paysera_normalization.date_time_normalizer.format%';
$container->setDefinition(
'paysera_normalization.date_time_normalizer',
(new Definition(DateTimeNormalizer::class, [$format]))
->addTag('paysera_normalization.autoconfigured_normalizer')
);
$container->setDefinition(
'paysera_normalization.date_time_immutable_normalizer',
(new Definition(DateTimeImmutableNormalizer::class, [$format]))
->addTag('paysera_normalization.autoconfigured_normalizer')
->addTag('paysera_normalization.mixed_type_denormalizer', ['type' => 'DateTimeInterface'])
);
}
}
37 changes: 0 additions & 37 deletions src/Resources/config/services.xml

This file was deleted.

24 changes: 0 additions & 24 deletions src/Resources/config/services/date_time_normalizer.xml

This file was deleted.

146 changes: 146 additions & 0 deletions tests/DependencyInjection/PayseraNormalizationExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php
declare(strict_types=1);

namespace Paysera\Bundle\NormalizationBundle\Tests\DependencyInjection;

use Paysera\Bundle\NormalizationBundle\DependencyInjection\PayseraNormalizationExtension;
use Paysera\Component\Normalization\CoreDenormalizer;
use Paysera\Component\Normalization\CoreNormalizer;
use Paysera\Component\Normalization\DataFilter;
use Paysera\Component\Normalization\Normalizer\DateTimeImmutableNormalizer;
use Paysera\Component\Normalization\Normalizer\DateTimeNormalizer;
use Paysera\Component\Normalization\NormalizerRegistryInterface;
use Paysera\Component\Normalization\Registry\GroupedNormalizerRegistryProvider;
use Paysera\Component\Normalization\TypeGuesser;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Yoast\PHPUnitPolyfills\TestCases\TestCase;

class PayseraNormalizationExtensionTest extends TestCase
{
const DATE_TIME_CONFIGS = [['register_normalizers' => ['date_time' => ['format' => 'U']]]];

/**
* @dataProvider loadProvider
* @param array<array<mixed>> $configs
* @param array<string, array<string, mixed>> $expected
*/
public function testLoadRegistersTheServices(array $configs, array $expected)
{
$container = new ContainerBuilder();
$empty = new ContainerBuilder();

(new PayseraNormalizationExtension())->load($configs, $container);

$this->assertSame(self::normalize($expected), self::normalize([
'definitions' => array_diff_key($container->getDefinitions(), $empty->getDefinitions()),
'aliases' => array_diff_key($container->getAliases(), $empty->getAliases()),
'parameters' => $container->getParameterBag()->all(),
]));
}

/**
* @return array<string, array{array<array<mixed>>, array<string, array<string, mixed>>}>
*/
public static function loadProvider(): array
{
$provider = new Reference('paysera_normalization.normalizer_registry_provider');
$definitions = [
'paysera_normalization.normalizer_registry_provider' =>
(new Definition(GroupedNormalizerRegistryProvider::class))->setLazy(true),
'paysera_normalization.normalizer_registry' => (new Definition(NormalizerRegistryInterface::class))
->setLazy(true)
->setFactory([$provider, 'getDefaultNormalizerRegistry']),
'paysera_normalization.type_guesser' => new Definition(TypeGuesser::class),
'paysera_normalization.data_filter' => new Definition(DataFilter::class),
'paysera_normalization.core_normalizer' => new Definition(CoreNormalizer::class, [
$provider,
new Reference('paysera_normalization.type_guesser'),
new Reference('paysera_normalization.data_filter'),
]),
'paysera_normalization.core_denormalizer' => new Definition(CoreDenormalizer::class, [$provider]),
];
$aliases = [
CoreNormalizer::class => new Alias('paysera_normalization.core_normalizer'),
CoreDenormalizer::class => new Alias('paysera_normalization.core_denormalizer'),
];
$format = '%paysera_normalization.date_time_normalizer.format%';

return [
'without the date_time normalizers' => [
[],
['definitions' => $definitions, 'aliases' => $aliases, 'parameters' => []],
],
'with the date_time normalizers' => [
self::DATE_TIME_CONFIGS,
[
'definitions' => $definitions + [
'paysera_normalization.date_time_normalizer' =>
(new Definition(DateTimeNormalizer::class, [$format]))
->addTag('paysera_normalization.autoconfigured_normalizer'),
'paysera_normalization.date_time_immutable_normalizer' =>
(new Definition(DateTimeImmutableNormalizer::class, [$format]))
->addTag('paysera_normalization.autoconfigured_normalizer')
->addTag(
'paysera_normalization.mixed_type_denormalizer',
['type' => 'DateTimeInterface']
),
],
'aliases' => $aliases,
'parameters' => ['paysera_normalization.date_time_normalizer.format' => 'U'],
],
],
];
}

public function testLoadRaisesNoDeprecation()
{
$deprecations = [];
set_error_handler(function (int $type, string $message) use (&$deprecations): bool {
$deprecations[] = $message;

return true;
}, E_USER_DEPRECATED);
try {
(new PayseraNormalizationExtension())->load(self::DATE_TIME_CONFIGS, new ContainerBuilder());
} finally {
restore_error_handler();
}

$this->assertSame([], $deprecations);
}

/**
* @param array<string, array<string, mixed>> $services
* @return array<string, mixed>
*/
private static function normalize(array $services): array
{
ksort($services['definitions']);
ksort($services['aliases']);

return self::export($services);
}

/**
* @param mixed $value
* @return mixed
*/
private static function export($value)
{
if (is_object($value)) {
return [get_class($value) => self::export((array)$value)];
}
if (!is_array($value)) {
return $value;
}
$exported = [];
foreach ($value as $key => $item) {
$exported[$key] = self::export($item);
}

return $exported;
}
}
Loading
Loading