diff --git a/composer.json b/composer.json index 693d0ba7..a5dc07d2 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "require": { "php": ">=8.0", "spiral/tokenizer": "^2.8", - "cycle/orm": "^2.0.0", + "cycle/orm": "^2.2.0", "cycle/schema-builder": "^2.1.0", "doctrine/annotations": "^1.13", "spiral/attributes": "^2.8", diff --git a/src/Annotation/Column.php b/src/Annotation/Column.php index 7e534c20..5344c44b 100644 --- a/src/Annotation/Column.php +++ b/src/Annotation/Column.php @@ -29,7 +29,7 @@ final class Column * @param bool $primary Explicitly set column as a primary key. * @param bool $nullable Set column as nullable. * @param mixed|null $default Default column value. - * @param non-empty-string|null $typecast Typecast rule name. + * @param callable|non-empty-string|null $typecast Typecast rule name. * Regarding the default Typecast handler {@see Typecast} the value can be `callable` or * one of ("int"|"float"|"bool"|"datetime") based on column type. * If you want to use another rule you should add in the `typecast` argument of the {@see Entity} attribute @@ -39,7 +39,7 @@ final class Column public function __construct( #[ExpectedValues(values: ['primary', 'bigPrimary', 'enum', 'boolean', 'integer', 'tinyInteger', 'bigInteger', 'string', 'text', 'tinyText', 'longText', 'double', 'float', 'decimal', 'datetime', 'date', 'time', - 'timestamp', 'binary', 'tinyBinary', 'longBinary', 'json', + 'timestamp', 'binary', 'tinyBinary', 'longBinary', 'json', 'uuid', ])] private string $type, private ?string $name = null, diff --git a/src/Configurator.php b/src/Configurator.php index c90fc76e..f4d8550a 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -251,36 +251,41 @@ public function initField(string $name, Column $column, \ReflectionClass $class, */ public function resolveName(?string $name, \ReflectionClass $class): ?string { - if ($name === null || class_exists($name, true) || interface_exists($name, true)) { + if ($name === null || $this->exists($name)) { return $name; } - $resolved = sprintf( + $resolved = \sprintf( '%s\\%s', $class->getNamespaceName(), - ltrim(str_replace('/', '\\', $name), '\\') + \ltrim(\str_replace('/', '\\', $name), '\\') ); - if (class_exists($resolved, true) || interface_exists($resolved, true)) { - return ltrim($resolved, '\\'); + if ($this->exists($resolved)) { + return \ltrim($resolved, '\\'); } return $name; } + private function exists(string $name): bool + { + return \class_exists($name, true) || \interface_exists($name, true); + } + private function resolveTypecast(mixed $typecast, \ReflectionClass $class): mixed { - if (is_string($typecast) && strpos($typecast, '::') !== false) { + if (\is_string($typecast) && \str_contains($typecast, '::')) { // short definition - $typecast = explode('::', $typecast); + $typecast = \explode('::', $typecast); // resolve class name $typecast[0] = $this->resolveName($typecast[0], $class); } - if (is_string($typecast)) { + if (\is_string($typecast)) { $typecast = $this->resolveName($typecast, $class); - if (class_exists($typecast)) { + if (\class_exists($typecast) && \method_exists($typecast, 'typecast')) { $typecast = [$typecast, 'typecast']; } } diff --git a/tests/Annotated/Fixtures/Fixtures19/BackedEnum.php b/tests/Annotated/Fixtures/Fixtures19/BackedEnum.php new file mode 100644 index 00000000..051f52fb --- /dev/null +++ b/tests/Annotated/Fixtures/Fixtures19/BackedEnum.php @@ -0,0 +1,11 @@ +expectException(AnnotationException::class); - $this->expectErrorMessage( - 'Some of required arguments [`type`] is missed on `Cycle\Annotated\Tests\Fixtures\Fixtures4\User.id.`' - ); + // $this->expectErrorMessage( + // 'Some of required arguments [`type`] is missed on `Cycle\Annotated\Tests\Fixtures\Fixtures4\User.id.`' + // ); $tokenizer = new Tokenizer(new TokenizerConfig([ 'directories' => [__DIR__ . '/../../../Fixtures/Fixtures4'], diff --git a/tests/Annotated/Functional/Driver/Common/TypecastTest.php b/tests/Annotated/Functional/Driver/Common/TypecastTest.php index 1691d538..54b03155 100644 --- a/tests/Annotated/Functional/Driver/Common/TypecastTest.php +++ b/tests/Annotated/Functional/Driver/Common/TypecastTest.php @@ -4,14 +4,27 @@ namespace Cycle\Annotated\Tests\Functional\Driver\Common; +use Cycle\Annotated\Embeddings; +use Cycle\Annotated\MergeIndexes; use Cycle\Annotated\Tests\Fixtures\Fixtures1\Typecast\Typecaster; use Cycle\Annotated\Tests\Fixtures\Fixtures1\Typecast\UuidTypecaster; +use Cycle\Annotated\Tests\Fixtures\Fixtures19\BackedEnumWrapper; use Cycle\ORM\Schema; +use Cycle\ORM\SchemaInterface; use Cycle\Schema\Compiler; +use Cycle\Schema\Generator\GenerateRelations; +use Cycle\Schema\Generator\GenerateTypecast; +use Cycle\Schema\Generator\RenderRelations; +use Cycle\Schema\Generator\RenderTables; +use Cycle\Schema\Generator\ResetTables; +use Cycle\Schema\Generator\SyncTables; use Cycle\Schema\Registry; use Cycle\Annotated\Entities; use Cycle\Annotated\MergeColumns; use Spiral\Attributes\ReaderInterface; +use Spiral\Tokenizer\Config\TokenizerConfig; +use Spiral\Tokenizer\Tokenizer; +use Cycle\Annotated\Tests\Fixtures\Fixtures19\BackedEnum; abstract class TypecastTest extends BaseTest { @@ -54,4 +67,42 @@ public function testEntityWithDefinedTypecastAsArray(ReaderInterface $reader) $schema['tag'][Schema::TYPECAST_HANDLER] ); } + + /** + * @dataProvider allReadersProvider + * @requires PHP >= 8.1 + */ + public function testBackedEnum(ReaderInterface $reader) + { + $tokenizer = new Tokenizer(new TokenizerConfig([ + 'directories' => [__DIR__ . '/../../../Fixtures/Fixtures19'], + 'exclude' => [], + ])); + + $locator = $tokenizer->classLocator(); + + $r = new Registry($this->dbal); + + $schema = (new Compiler())->compile($r, [ + new Embeddings($locator, $reader), + new Entities($locator, $reader), + new ResetTables(), + new MergeColumns($reader), + new GenerateRelations(), + new RenderTables(), + new RenderRelations(), + new MergeIndexes($reader), + new SyncTables(), + new GenerateTypecast(), + ]); + + $this->assertSame( + [ + 'bid' => 'int', + 'be' => BackedEnum::class, + 'bew' => [BackedEnumWrapper::class, 'typecast'], + ], + $schema['booking'][SchemaInterface::TYPECAST] + ); + } }