Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions lib/Doctrine/ORM/AbstractQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Doctrine\ORM;

use BackedEnum;
use Countable;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
Expand Down Expand Up @@ -424,6 +425,10 @@ public function processParameterValue($value)
return $value->name;
}

if ($value instanceof BackedEnum) {
return $value->value;
}

if (! is_object($value)) {
return $value;
}
Expand Down
14 changes: 13 additions & 1 deletion lib/Doctrine/ORM/Query/ParameterTypeInferer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Doctrine\ORM\Query;

use BackedEnum;
use DateInterval;
use DateTimeImmutable;
use DateTimeInterface;
Expand Down Expand Up @@ -54,8 +55,19 @@ public static function inferType($value)
return Types::DATEINTERVAL;
}

if ($value instanceof BackedEnum) {
return is_int($value->value)
? Types::INTEGER
: Types::STRING;
}

if (is_array($value)) {
return is_int(current($value))
$firstValue = current($value);
if ($firstValue instanceof BackedEnum) {
$firstValue = $firstValue->value;
}

return is_int($firstValue)
? Connection::PARAM_INT_ARRAY
: Connection::PARAM_STR_ARRAY;
}
Expand Down
6 changes: 6 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,16 @@

<rule ref="Generic.WhiteSpace.ScopeIndent.Incorrect">
<!-- see https://github.com/squizlabs/PHP_CodeSniffer/issues/3474 -->
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/AccessLevel.php</exclude-pattern>
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/City.php</exclude-pattern>
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/Suit.php</exclude-pattern>
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/UserStatus.php</exclude-pattern>
</rule>
<rule ref="Generic.WhiteSpace.ScopeIndent.IncorrectExact">
<!-- see https://github.com/squizlabs/PHP_CodeSniffer/issues/3474 -->
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/AccessLevel.php</exclude-pattern>
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/City.php</exclude-pattern>
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/Suit.php</exclude-pattern>
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/UserStatus.php</exclude-pattern>
</rule>
</ruleset>
6 changes: 6 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@
<file name="lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php"/>
</errorLevel>
</MissingDependency>
<NoInterfaceProperties>
<errorLevel type="suppress">
<!-- see https://github.com/vimeo/psalm/issues/7364 -->
<referencedClass name="BackedEnum"/>
</errorLevel>
</NoInterfaceProperties>
<ParadoxicalCondition>
<errorLevel type="suppress">
<!-- See https://github.com/vimeo/psalm/issues/3381 -->
Expand Down
12 changes: 12 additions & 0 deletions tests/Doctrine/Tests/Models/Enums/AccessLevel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\Enums;

enum AccessLevel: int
{
case Admin = 1;
case User = 2;
case Guests = 3;
}
12 changes: 12 additions & 0 deletions tests/Doctrine/Tests/Models/Enums/City.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\Enums;

enum City: string
{
case Paris = 'Paris';
case Cannes = 'Cannes';
case StJulien = 'St Julien';
}
11 changes: 11 additions & 0 deletions tests/Doctrine/Tests/Models/Enums/UserStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\Enums;

enum UserStatus: string
{
case Active = 'active';
case Inactive = 'inactive';
}
62 changes: 62 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/QueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
use Doctrine\Tests\Models\CMS\CmsArticle;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\Enums\AccessLevel;
use Doctrine\Tests\Models\Enums\UserStatus;
use Doctrine\Tests\OrmFunctionalTestCase;
use Exception;

Expand Down Expand Up @@ -165,6 +167,66 @@ public function testInvalidInputParameterThrowsException(): void
->getSingleResult();
}

/**
* @requires PHP 8.1
*/
public function testUseStringEnumCaseAsParameter(): void
{
$user = new CmsUser();
$user->name = 'John';
$user->username = 'john';
$user->status = 'inactive';
$this->_em->persist($user);

$user = new CmsUser();
$user->name = 'Jane';
$user->username = 'jane';
$user->status = 'active';
$this->_em->persist($user);

unset($user);

$this->_em->flush();
$this->_em->clear();

$result = $this->_em->createQuery('SELECT u FROM ' . CmsUser::class . ' u WHERE u.status = :status')
->setParameter('status', UserStatus::Active)
->getResult();

self::assertCount(1, $result);
self::assertSame('jane', $result[0]->username);
}

/**
* @requires PHP 8.1
*/
public function testUseIntegerEnumCaseAsParameter(): void
{
$user = new CmsUser();
$user->name = 'John';
$user->username = 'john';
$user->status = '1';
$this->_em->persist($user);

$user = new CmsUser();
$user->name = 'Jane';
$user->username = 'jane';
$user->status = '2';
$this->_em->persist($user);

unset($user);

$this->_em->flush();
$this->_em->clear();

$result = $this->_em->createQuery('SELECT u FROM ' . CmsUser::class . ' u WHERE u.status = :status')
->setParameter('status', AccessLevel::User)
->getResult();

self::assertCount(1, $result);
self::assertSame('jane', $result[0]->username);
}

public function testSetParameters(): void
{
$parameters = new ArrayCollection();
Expand Down
40 changes: 25 additions & 15 deletions tests/Doctrine/Tests/ORM/Query/ParameterTypeInfererTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,36 @@
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\ParameterTypeInferer;
use Doctrine\Tests\Models\Enums\AccessLevel;
use Doctrine\Tests\Models\Enums\UserStatus;
use Doctrine\Tests\OrmTestCase;
use Generator;

use const PHP_VERSION_ID;

class ParameterTypeInfererTest extends OrmTestCase
{
/** @psalm-return list<array{mixed, int|string}> */
public function providerParameterTypeInferer(): array
/** @psalm-return Generator<string, array{mixed, (int|string)}> */
public function providerParameterTypeInferer(): Generator
{
return [
[1, Types::INTEGER],
['bar', ParameterType::STRING],
['1', ParameterType::STRING],
[new DateTime(), Types::DATETIME_MUTABLE],
[new DateTimeImmutable(), Types::DATETIME_IMMUTABLE],
[new DateInterval('P1D'), Types::DATEINTERVAL],
[[2], Connection::PARAM_INT_ARRAY],
[['foo'], Connection::PARAM_STR_ARRAY],
[['1','2'], Connection::PARAM_STR_ARRAY],
[[], Connection::PARAM_STR_ARRAY],
[true, Types::BOOLEAN],
];
yield 'integer' => [1, Types::INTEGER];
yield 'string' => ['bar', ParameterType::STRING];
yield 'numeric_string' => ['1', ParameterType::STRING];
yield 'datetime_object' => [new DateTime(), Types::DATETIME_MUTABLE];
yield 'datetime_immutable_object' => [new DateTimeImmutable(), Types::DATETIME_IMMUTABLE];
yield 'date_interval_object' => [new DateInterval('P1D'), Types::DATEINTERVAL];
yield 'array_of_int' => [[2], Connection::PARAM_INT_ARRAY];
yield 'array_of_string' => [['foo'], Connection::PARAM_STR_ARRAY];
yield 'array_of_numeric_string' => [['1', '2'], Connection::PARAM_STR_ARRAY];
yield 'empty_array' => [[], Connection::PARAM_STR_ARRAY];
yield 'boolean' => [true, Types::BOOLEAN];

if (PHP_VERSION_ID >= 80100) {
yield 'int_backed_enum' => [AccessLevel::Admin, Types::INTEGER];
yield 'string_backed_enum' => [UserStatus::Active, Types::STRING];
yield 'array_of_int_backed_enum' => [[AccessLevel::Admin], Connection::PARAM_INT_ARRAY];
yield 'array_of_string_backed_enum' => [[UserStatus::Active], Connection::PARAM_STR_ARRAY];
}
}

/**
Expand Down
35 changes: 35 additions & 0 deletions tests/Doctrine/Tests/ORM/Query/QueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,21 @@
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsGroup;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\Enums\AccessLevel;
use Doctrine\Tests\Models\Enums\City;
use Doctrine\Tests\Models\Enums\UserStatus;
use Doctrine\Tests\Models\Generic\DateTimeModel;
use Doctrine\Tests\OrmTestCase;
use Generator;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;

use function array_map;
use function assert;
use function method_exists;

use const PHP_VERSION_ID;

class QueryTest extends OrmTestCase
{
/** @var EntityManagerMock */
Expand Down Expand Up @@ -236,6 +242,9 @@ public function testCollectionParameters(): void
self::assertEquals($cities, $parameter->getValue());
}

/**
* @psalm-return Generator<string, array{iterable}>
*/
public function provideProcessParameterValueIterable(): Generator
{
$baseArray = [
Expand All @@ -251,6 +260,10 @@ public function provideProcessParameterValueIterable(): Generator
yield 'simple_array' => [$baseArray];
yield 'doctrine_collection' => [new ArrayCollection($baseArray)];
yield 'generator' => [$gen()];

if (PHP_VERSION_ID >= 80100) {
yield 'array_of_enum' => [array_map([City::class, 'from'], $baseArray)];
}
}

/**
Expand Down Expand Up @@ -322,6 +335,28 @@ public function testProcessParameterValueNull(): void
self::assertNull($query->processParameterValue(null));
}

/**
* @requires PHP 8.1
*/
public function testProcessParameterValueBackedEnum(): void
{
$query = $this->entityManager->createQuery('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.status = :status');

self::assertSame(['active'], $query->processParameterValue([UserStatus::Active]));
self::assertSame([2], $query->processParameterValue([AccessLevel::User]));
}

/**
* @requires PHP 8.1
*/
public function testProcessParameterValueBackedEnumArray(): void
{
$query = $this->entityManager->createQuery('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.status IN (:status)');

self::assertSame(['active'], $query->processParameterValue([UserStatus::Active]));
self::assertSame([2], $query->processParameterValue([AccessLevel::User]));
}

public function testDefaultQueryHints(): void
{
$config = $this->entityManager->getConfiguration();
Expand Down