Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"phpstan/phpstan-strict-rules": "^2.0",
"phpunit/phpunit": "^9.6.20",
"ramsey/uuid": "^4.2",
"symfony/cache": "^5.4"
"symfony/cache": "^5.4",
"symfony/uid": "^5.4 || ^6.4 || ^7.3"
},
"config": {
"sort-packages": true,
Expand Down
10 changes: 10 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,16 @@ services:
tags: [phpstan.doctrine.typeDescriptor]
arguments:
uuidTypeName: Ramsey\Uuid\Doctrine\UuidBinaryOrderedTimeType
-
class: PHPStan\Type\Doctrine\Descriptors\Symfony\UuidTypeDescriptor
tags: [phpstan.doctrine.typeDescriptor]
arguments:
uuidTypeName: Symfony\Bridge\Doctrine\Types\UuidType
-
class: PHPStan\Type\Doctrine\Descriptors\Symfony\UlidTypeDescriptor
tags: [phpstan.doctrine.typeDescriptor]
arguments:
uuidTypeName: Symfony\Bridge\Doctrine\Types\UlidType

# Doctrine Collection
-
Expand Down
4 changes: 2 additions & 2 deletions src/Type/Doctrine/Descriptors/Ramsey/UuidTypeDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace PHPStan\Type\Doctrine\Descriptors\Ramsey;

use PHPStan\Rules\Doctrine\ORM\FakeTestingUuidType;
use PHPStan\Rules\Doctrine\ORM\FakeTestingRamseyUuidType;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Doctrine\Descriptors\DoctrineTypeDescriptor;
use PHPStan\Type\ObjectType;
Expand All @@ -20,7 +20,7 @@ class UuidTypeDescriptor implements DoctrineTypeDescriptor
'Ramsey\Uuid\Doctrine\UuidType',
'Ramsey\Uuid\Doctrine\UuidBinaryType',
'Ramsey\Uuid\Doctrine\UuidBinaryOrderedTimeType',
FakeTestingUuidType::class,
FakeTestingRamseyUuidType::class,
];

private string $uuidTypeName;
Expand Down
64 changes: 64 additions & 0 deletions src/Type/Doctrine/Descriptors/Symfony/UlidTypeDescriptor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Doctrine\Descriptors\Symfony;

use PHPStan\Rules\Doctrine\ORM\FakeTestingSymfonyUlidType;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Doctrine\Descriptors\DoctrineTypeDescriptor;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use Symfony\Component\Uid\Ulid;
use function in_array;
use function sprintf;

class UlidTypeDescriptor implements DoctrineTypeDescriptor
{

private const SUPPORTED_UUID_TYPES = [
'Symfony\Bridge\Doctrine\Types\UlidType',
FakeTestingSymfonyUlidType::class,
];

private string $uuidTypeName;

public function __construct(
string $uuidTypeName
)
{
if (!in_array($uuidTypeName, self::SUPPORTED_UUID_TYPES, true)) {
throw new ShouldNotHappenException(sprintf(
'Unexpected UUID column type "%s" provided',
$uuidTypeName,
));
}

$this->uuidTypeName = $uuidTypeName;
}

public function getType(): string
{
/** @var class-string<\Doctrine\DBAL\Types\Type> */
return $this->uuidTypeName;
}

public function getWritableToPropertyType(): Type
{
return new ObjectType(Ulid::class);
}

public function getWritableToDatabaseType(): Type
{
return TypeCombinator::union(
new StringType(),
new ObjectType(Ulid::class),
);
}

public function getDatabaseInternalType(): Type
{
return new StringType();
}

}
64 changes: 64 additions & 0 deletions src/Type/Doctrine/Descriptors/Symfony/UuidTypeDescriptor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Doctrine\Descriptors\Symfony;

use PHPStan\Rules\Doctrine\ORM\FakeTestingSymfonyUuidType;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Doctrine\Descriptors\DoctrineTypeDescriptor;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use Symfony\Component\Uid\Uuid;
use function in_array;
use function sprintf;

class UuidTypeDescriptor implements DoctrineTypeDescriptor
{

private const SUPPORTED_UUID_TYPES = [
'Symfony\Bridge\Doctrine\Types\UuidType',
FakeTestingSymfonyUuidType::class,
];

private string $uuidTypeName;

public function __construct(
string $uuidTypeName
)
{
if (!in_array($uuidTypeName, self::SUPPORTED_UUID_TYPES, true)) {
throw new ShouldNotHappenException(sprintf(
'Unexpected UUID column type "%s" provided',
$uuidTypeName,
));
}

$this->uuidTypeName = $uuidTypeName;
}

public function getType(): string
{
/** @var class-string<\Doctrine\DBAL\Types\Type> */
return $this->uuidTypeName;
}

public function getWritableToPropertyType(): Type
{
return new ObjectType(Uuid::class);
}

public function getWritableToDatabaseType(): Type
{
return TypeCombinator::union(
new StringType(),
new ObjectType(Uuid::class),
);
}

public function getDatabaseInternalType(): Type
{
return new StringType();
}

}
50 changes: 46 additions & 4 deletions tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
use PHPStan\Type\Doctrine\Descriptors\EnumType;
use PHPStan\Type\Doctrine\Descriptors\IntegerType;
use PHPStan\Type\Doctrine\Descriptors\JsonType;
use PHPStan\Type\Doctrine\Descriptors\Ramsey\UuidTypeDescriptor;
use PHPStan\Type\Doctrine\Descriptors\Ramsey\UuidTypeDescriptor as RamseyUuidTypeDescriptor;
use PHPStan\Type\Doctrine\Descriptors\ReflectionDescriptor;
use PHPStan\Type\Doctrine\Descriptors\SimpleArrayType;
use PHPStan\Type\Doctrine\Descriptors\StringType;
use PHPStan\Type\Doctrine\Descriptors\Symfony\UlidTypeDescriptor as SymfonyUlidTypeDescriptor;
use PHPStan\Type\Doctrine\Descriptors\Symfony\UuidTypeDescriptor as SymfonyUuidTypeDescriptor;
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
use function array_unshift;
use function class_exists;
Expand All @@ -41,6 +43,8 @@ class EntityColumnRuleTest extends RuleTestCase

private ?string $objectManagerLoader = null;

private bool $useSymfonyUuid = false;

protected function getRule(): Rule
{
if (!Type::hasType(CustomType::NAME)) {
Expand All @@ -49,8 +53,23 @@ protected function getRule(): Rule
if (!Type::hasType(CustomNumericType::NAME)) {
Type::addType(CustomNumericType::NAME, CustomNumericType::class);
}
if (!Type::hasType(FakeTestingUuidType::NAME)) {
Type::addType(FakeTestingUuidType::NAME, FakeTestingUuidType::class);
if ($this->useSymfonyUuid) {
if (!Type::hasType(FakeTestingSymfonyUuidType::NAME)) {
Type::addType(FakeTestingSymfonyUuidType::NAME, FakeTestingSymfonyUuidType::class);
} else {
// Override Ramsay definition
Type::overrideType(FakeTestingSymfonyUuidType::NAME, FakeTestingSymfonyUuidType::class);
}
if (!Type::hasType(FakeTestingSymfonyUlidType::NAME)) {
Type::addType(FakeTestingSymfonyUlidType::NAME, FakeTestingSymfonyUlidType::class);
}
} else {
if (!Type::hasType(FakeTestingRamseyUuidType::NAME)) {
Type::addType(FakeTestingRamseyUuidType::NAME, FakeTestingRamseyUuidType::class);
} else {
// Override Symfony definition
Type::overrideType(FakeTestingRamseyUuidType::NAME, FakeTestingRamseyUuidType::class);
}
}
if (!Type::hasType('carbon')) {
Type::addType('carbon', CarbonType::class);
Expand All @@ -76,8 +95,10 @@ protected function getRule(): Rule
new IntegerType(),
new StringType(),
new SimpleArrayType(),
new UuidTypeDescriptor(FakeTestingUuidType::class),
new EnumType(),
new RamseyUuidTypeDescriptor(FakeTestingRamseyUuidType::class),
new SymfonyUuidTypeDescriptor(FakeTestingSymfonyUuidType::class),
new SymfonyUlidTypeDescriptor(FakeTestingSymfonyUlidType::class),
new ReflectionDescriptor(CarbonImmutableType::class, $this->createReflectionProvider(), self::getContainer()),
new ReflectionDescriptor(CarbonType::class, $this->createReflectionProvider(), self::getContainer()),
new ReflectionDescriptor(CustomType::class, $this->createReflectionProvider(), self::getContainer()),
Expand Down Expand Up @@ -461,6 +482,27 @@ public function testBug677(?string $objectManagerLoader): void
$this->analyse([__DIR__ . '/data/bug-677.php'], []);
}

/**
* @dataProvider dataObjectManagerLoader
*/
public function testSymfonyUuid(?string $objectManagerLoader): void
{
$this->allowNullablePropertyForRequiredField = true;
$this->objectManagerLoader = $objectManagerLoader;
$this->useSymfonyUuid = true;

$this->analyse([__DIR__ . '/data/EntityWithSymfonyUid.php'], [
[
'Property PHPStan\Rules\Doctrine\ORM\EntityWithSymfonyUid::$uuidInvalidType type mapping mismatch: database can contain Symfony\Component\Uid\Uuid but property expects string.',
32,
],
[
'Property PHPStan\Rules\Doctrine\ORM\EntityWithSymfonyUid::$ulidInvalidType type mapping mismatch: database can contain Symfony\Component\Uid\Ulid but property expects string.',
44,
],
]);
}

/**
* @dataProvider dataObjectManagerLoader
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* From https://github.com/ramsey/uuid-doctrine/blob/fafebbe972cdaba9274c286ea8923e2de2579027/src/UuidType.php
* Copyright (c) 2012-2022 Ben Ramsey <[email protected]>
*/
final class FakeTestingUuidType extends GuidType
final class FakeTestingRamseyUuidType extends GuidType
{

public const NAME = 'uuid';
Expand Down
113 changes: 113 additions & 0 deletions tests/Rules/Doctrine/ORM/FakeTestingSymfonyUlidType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Doctrine\ORM;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\Type;
use InvalidArgumentException;
use Symfony\Component\Uid\AbstractUid;
use Symfony\Component\Uid\Ulid;
use function is_string;

/**
* From https://github.com/symfony/doctrine-bridge/blob/f5a2780640342e3bf0599fbfc9b3dd759db9b037/Types/UuidType.php
* Copyright (c) Fabien Potencier <[email protected]>
*/
final class FakeTestingSymfonyUlidType extends Type
{

public const NAME = 'ulid';

/**
* @not-deprecated
*/
public function getName(): string
{
return self::NAME;
}

protected function getUidClass(): string
{
return Ulid::class;
}

/**
* {@inheritdoc}
*/
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
{
if ($this->hasNativeGuidType($platform)) {
return $platform->getGuidTypeDeclarationSQL($column);
}

return $platform->getBinaryTypeDeclarationSQL([
'length' => '16',
'fixed' => true,
]);
}

/**
* {@inheritdoc}
*
* @throws ConversionException
*/
public function convertToPHPValue($value, AbstractPlatform $platform): ?AbstractUid
{
if ($value instanceof AbstractUid || $value === null) {
return $value;
}

if (!is_string($value)) {
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'string', AbstractUid::class]);
}

try {
/** @phpstan-ignore-next-line method.dynamicName */
return $this->getUidClass()::fromString($value);
} catch (InvalidArgumentException $e) {
throw ConversionException::conversionFailed($value, $this->getName(), $e);
}
}

/**
* {@inheritdoc}
*
* @throws ConversionException
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
$toString = $this->hasNativeGuidType($platform) ? 'toRfc4122' : 'toBinary';

if ($value instanceof AbstractUid) {
/** @phpstan-ignore-next-line method.dynamicName */
return $value->$toString();
}

if ($value === null || $value === '') {
return null;
}

if (!is_string($value)) {
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'string', AbstractUid::class]);
}

try {
/** @phpstan-ignore-next-line method.dynamicName */
return $this->getUidClass()::fromString($value)->$toString();
} catch (InvalidArgumentException $e) {
throw ConversionException::conversionFailed($value, $this->getName());
}
}

public function requiresSQLCommentHint(AbstractPlatform $platform): bool
{
return true;
}

private function hasNativeGuidType(AbstractPlatform $platform): bool
{
return $platform->getGuidTypeDeclarationSQL([]) !== $platform->getStringTypeDeclarationSQL(['fixed' => true, 'length' => 36]);
}

}
Loading
Loading