Skip to content

Commit

Permalink
minor #198 [CI] Update edges (ogizanagi)
Browse files Browse the repository at this point in the history
This PR was merged into the 2.x-dev branch.

Discussion
----------

[CI] Update edges

- [x] Fixe issues
- [x] Makefile updates

[6.2 deprecations](https://github.com/Elao/PhpEnums/actions/runs/3206390855/jobs/5240080224#step:17:246):

```
 13x: Since symfony/http-kernel 6.2: Starting from 7.0, "Symfony\Component\HttpKernel\HttpKernel::handle()" will catch \Throwable exceptions and convert them to HttpFoundation responses. Pass $catchThrowable=true to adapt to this behavior now.
    9x in QueryBodyBackedEnumValueResolverTest::testResolver from Elao\Enum\Tests\Integration\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver
    4x in BackedEnumValueResolverTest::testResolver from Elao\Enum\Tests\Integration\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver

  1x: The "Elao\Enum\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver" class implements "Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface" that is deprecated since Symfony 6.2, implement ValueResolverInterface instead.
    1x in EnumTypeSQLEnumTest::setUp from Elao\Enum\Tests\Integration\Bridge\Doctrine\DBAL\Type

  1x: The "Elao\Enum\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver\QueryBodyBackedEnumValueResolver" class implements "Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface" that is deprecated since Symfony 6.2, implement ValueResolverInterface instead.
    1x in EnumTypeSQLEnumTest::setUp from Elao\Enum\Tests\Integration\Bridge\Doctrine\DBAL\Type
```

Commits
-------

6f296f3 [CI] Update edges
  • Loading branch information
ogizanagi committed Oct 13, 2022
2 parents a296673 + 6f296f3 commit b20e8e8
Show file tree
Hide file tree
Showing 16 changed files with 253 additions and 80 deletions.
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ indent_size = 2
[*.md]
trim_trailing_whitespace = false
max_line_length = 120

[*.{yml,yaml}]
indent_size = 2
22 changes: 19 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,31 @@ jobs:
os: 'ubuntu-latest'
php: '8.1'
symfony: '6.0.*@dev'
mongodb: true
mysql: true
allow-unstable: true

- name: 'Test Symfony 6.1 [Linux, PHP 8.1]'
os: 'ubuntu-latest'
php: '8.1'
symfony: '6.1.*@dev'
allow-unstable: true
mysql: true
mongodb: true

# Bleeding edge (unreleased dev versions where failures are allowed)
- name: 'Test next Symfony [Linux, PHP 8.1] (allowed failure)'
os: 'ubuntu-latest'
php: '8.1'
symfony: '6.1.*@dev'
symfony: '6.2.*@dev'
composer-flags: '--ignore-platform-req php'
allow-unstable: true
allow-failure: true
mysql: true
mongodb: true

- name: 'Test next Symfony [Linux, PHP 8.2] (allowed failure)'
os: 'ubuntu-latest'
php: '8.2'
symfony: '6.2.*@dev'
composer-flags: '--ignore-platform-req php'
allow-unstable: true
allow-failure: true
Expand Down
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ install.61:
symfony composer config minimum-stability dev
symfony composer update

## Install - Install Symfony 6.2 deps
install.62: setup
install.62: export SYMFONY_REQUIRE = 6.2.*@dev
install.62:
symfony composer config minimum-stability dev
symfony composer update

## Install - Add Doctrine ODM deps
deps.odm.add:
symfony composer require --no-update --no-interaction --dev "doctrine/mongodb-odm:^2.3" "doctrine/mongodb-odm-bundle:^4.4.1"
Expand Down
14 changes: 9 additions & 5 deletions src/Bridge/Symfony/Bundle/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@

use Elao\Enum\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver;
use Elao\Enum\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver\QueryBodyBackedEnumValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver as SymfonyBackedEnumValueResolver;

return static function (ContainerConfigurator $container) {
$container->services()
->set(BackedEnumValueResolver::class)->tag('controller.argument_value_resolver', [
'priority' => 105, // Prior RequestAttributeValueResolver
])
;
if (!class_exists(SymfonyBackedEnumValueResolver::class)) {
$container->services()
->set(BackedEnumValueResolver::class)->tag('controller.argument_value_resolver', [
'priority' => 105, // Prior RequestAttributeValueResolver
])
;
}

$container->services()
->set(QueryBodyBackedEnumValueResolver::class)->tag('controller.argument_value_resolver', [
'priority' => 110, // Prior BackedEnumValueResolver
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,30 @@
namespace Elao\Enum\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver as SymfonyBackedEnumValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

// Suggest using Symfony 6.1+ resolver
if (class_exists(SymfonyBackedEnumValueResolver::class)) {
trigger_deprecation(
'elao/enum',
'2.1',
'The "%s" class is deprecated with "symfony/http-kernel" >= 6.1, use "%s" instead.',
BackedEnumValueResolver::class,
SymfonyBackedEnumValueResolver::class
);
}

// Legacy (<6.1) resolver
/**
* Attempt to resolve backed enum cases from request attributes, for a route path parameter,
* leading to a 404 Not Found if the attribute value isn't a valid backing value for the enum type.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*
* @final
*/
class BackedEnumValueResolver implements ArgumentValueResolverInterface
{
Expand Down Expand Up @@ -53,7 +68,14 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable
}

if (!\is_int($value) && !\is_string($value)) {
throw new \LogicException(sprintf('Could not resolve the "%s $%s" controller argument: expecting an int or string, got %s.', $argument->getType(), $argument->getName(), get_debug_type($value)));
throw new \LogicException(
sprintf(
'Could not resolve the "%s $%s" controller argument: expecting an int or string, got "%s".',
$argument->getType(),
$argument->getName(),
get_debug_type($value)
)
);
}

/** @var class-string<\BackedEnum> $enumType */
Expand All @@ -62,7 +84,14 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable
try {
yield $enumType::from($value);
} catch (\ValueError $error) {
throw new NotFoundHttpException(sprintf('Could not resolve the "%s $%s" controller argument: %s', $argument->getType(), $argument->getName(), $error->getMessage()), $error);
throw new NotFoundHttpException(
sprintf(
'Could not resolve the "%s $%s" controller argument: %s',
$argument->getType(),
$argument->getName(),
$error->getMessage()
), $error
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,76 +17,138 @@
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;

class QueryBodyBackedEnumValueResolver implements ArgumentValueResolverInterface
/**
* @internal
*/
function resolveValues(Request $request, ArgumentMetadata $argument): array
{
public function supports(Request $request, ArgumentMetadata $argument): bool
{
if (!is_a($argument->getType(), \BackedEnum::class, true)) {
return false;
}
$from = $argument->getAttributes(BackedEnumFromQuery::class, ArgumentMetadata::IS_INSTANCEOF)[0]
?? $argument->getAttributes(BackedEnumFromBody::class, ArgumentMetadata::IS_INSTANCEOF)[0]
?? null;

$resolvedValues = $this->resolveValues($request, $argument);
if (null === $from) {
return [];
}

if ([] === $resolvedValues) {
// do not support if no value was resolved at all.
// letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver be used
// or \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error.
return false;
}
$key = $argument->getName();

if (!$argument->isNullable() && \in_array(null, $resolvedValues, true)) {
// do not support if the argument isn't nullable but a null value was found,
// letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error
return false;
}
$bag = match (true) {
$from instanceof BackedEnumFromQuery => $request->query,
$from instanceof BackedEnumFromBody => $request->request,
};

return true;
if (!$bag->has($key)) {
return [];
}

private function resolveValues(Request $request, ArgumentMetadata $argument): array
{
$from = $argument->getAttributes(BackedEnumFromQuery::class, ArgumentMetadata::IS_INSTANCEOF)[0]
?? $argument->getAttributes(BackedEnumFromBody::class, ArgumentMetadata::IS_INSTANCEOF)[0]
?? null;
$values = $argument->isVariadic() ? $bag->all($key) : $bag->get($key);

if (null === $from) {
return [];
if (!$argument->isVariadic()) {
$values = [$values];
}

foreach ($values as &$value) {
// Consider empty string from query/body as null
if ($value === '') {
$value = null;
}
}

$key = $argument->getName();
return $values;
}

$bag = match (true) {
$from instanceof BackedEnumFromQuery => $request->query,
$from instanceof BackedEnumFromBody => $request->request,
};
// Legacy (<6.2) resolver
if (!interface_exists(ValueResolverInterface::class)) {
/**
* @final
*/
class QueryBodyBackedEnumValueResolver implements ArgumentValueResolverInterface
{
public function supports(Request $request, ArgumentMetadata $argument): bool
{
if (!is_a($argument->getType(), \BackedEnum::class, true)) {
return false;
}

if (!$bag->has($key)) {
return [];
}
$resolvedValues = resolveValues($request, $argument);

$values = $argument->isVariadic() ? $bag->all($key) : $bag->get($key);
if ([] === $resolvedValues) {
// do not support if no value was resolved at all.
// letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver be used
// or \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error.
return false;
}

if (!$argument->isVariadic()) {
$values = [$values];
if (!$argument->isNullable() && \in_array(null, $resolvedValues, true)) {
// do not support if the argument isn't nullable but a null value was found,
// letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error
return false;
}

return true;
}

foreach ($values as &$value) {
// Consider empty string from query/body as null
if ($value === '') {
$value = null;
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
$values = resolveValues($request, $argument);

foreach ($values as $value) {
if ($value === null) {
yield null;

continue;
}

/** @var class-string<\BackedEnum> $enumType */
$enumType = $argument->getType();

try {
yield $enumType::from($value);
} catch (\ValueError|\TypeError $error) {
throw new BadRequestException(sprintf(
'Could not resolve the "%s $%s" controller argument: %s',
$argument->getType(),
$argument->getName(),
$error->getMessage(),
));
}
}
}

return $values;
}

return;
}

/**
* @final
*/
class QueryBodyBackedEnumValueResolver implements ValueResolverInterface
{
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
$values = $this->resolveValues($request, $argument);
if (!is_a($argument->getType(), \BackedEnum::class, true)) {
return [];
}

$resolvedValues = resolveValues($request, $argument);

if ([] === $resolvedValues) {
// do not support if no value was resolved at all.
// letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver be used
// or \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error.
return [];
}

if (!$argument->isNullable() && \in_array(null, $resolvedValues, true)) {
// do not support if the argument isn't nullable but a null value was found,
// letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error
return [];
}

foreach ($values as $value) {
foreach ($resolvedValues as $value) {
if ($value === null) {
yield null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ framework:
secret: 'elao'
form: true
router:
resource: '%kernel.project_dir%/config/routing.yml'
resource: '%kernel.project_dir%/config/routing.yaml'
strict_requirements: '%kernel.debug%'
utf8: true
session:
Expand Down
2 changes: 2 additions & 0 deletions tests/Fixtures/Integration/Symfony/config/config_6.2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
framework:
catch_all_throwables: true
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ doctrine:
alias: AppMySQL

elao_enum:
doctrine:
types:
suit_sql_enum:
class: App\Enum\Suit
type: enum
default: !php/const App\Enum\Suit::Spades
doctrine:
types:
suit_sql_enum:
class: App\Enum\Suit
type: enum
default: !php/const App\Enum\Suit::Spades
10 changes: 7 additions & 3 deletions tests/Fixtures/Integration/Symfony/src/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,18 @@ class_exists(DoctrineMongoDBBundle::class) ? new DoctrineMongoDBBundle() : null,

public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load($this->getProjectDir() . '/config/config.yml');
$loader->load($this->getProjectDir() . '/config/config.yaml');

if (str_starts_with($_ENV['DOCTRINE_DBAL_URL'], 'pdo-mysql:')) {
$loader->load($this->getProjectDir() . '/config/mysql.yml');
$loader->load($this->getProjectDir() . '/config/mysql.yaml');
}

if (self::VERSION_ID >= 60200) {
$loader->load($this->getProjectDir() . '/config/config_6.2.yaml');
}

if (class_exists(DoctrineMongoDBBundle::class)) {
$loader->load($this->getProjectDir() . '/config/mongodb.yml');
$loader->load($this->getProjectDir() . '/config/mongodb.yaml');
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function (Response $response) {
yield 'invalid value' => [
function (KernelBrowser $client) {
$this->expectException(NotFoundHttpException::class);
$this->expectExceptionMessage('Could not resolve the "App\Enum\Suit $suit" controller argument: "foo" is not a valid backing value for enum "App\Enum\Suit"');
$this->expectExceptionMessage('Could not resolve the "App\Enum\Suit $suit" controller argument: "foo" is not a valid backing value for enum');

$client->request(Request::METHOD_GET, '/resolver/from-attributes/foo');
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public function requestProvider(): iterable
yield 'invalid value' => [
function (KernelBrowser $client) {
$this->expectException(BadRequestHttpException::class);
$this->expectExceptionMessage('Could not resolve the "App\Enum\Suit $suit" controller argument: "foo" is not a valid backing value for enum "App\Enum\Suit"');
$this->expectExceptionMessage('Could not resolve the "App\Enum\Suit $suit" controller argument: "foo" is not a valid backing value for enum');

$client->request(Request::METHOD_GET, '/resolver/from-query?' . http_build_query([
'suit' => 'foo',
Expand Down
Loading

0 comments on commit b20e8e8

Please sign in to comment.