diff --git a/composer.json b/composer.json index 3707ea55..202fa8ff 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "require-dev": { "doctrine/annotations": "^1.14.3 || ^2.0.1", "phpunit/phpunit": "^10.1", - "vimeo/psalm": "^5.26", + "vimeo/psalm": "^5.26 || ^6.10", "spiral/code-style": "^2.2", "spiral/dumper": "^3.3" }, diff --git a/src/Annotation/Column.php b/src/Annotation/Column.php index aec7a99a..f9758bd2 100644 --- a/src/Annotation/Column.php +++ b/src/Annotation/Column.php @@ -4,7 +4,6 @@ namespace Cycle\Annotated\Annotation; -use Cycle\ORM\Parser\Typecast; use Doctrine\Common\Annotations\Annotation\Target; use JetBrains\PhpStorm\ExpectedValues; use Spiral\Attributes\NamedArgumentConstructor; @@ -46,6 +45,7 @@ class Column * @param bool $readonlySchema Set to true to disable schema synchronization for the assigned column. * @param mixed ...$attributes Other database specific attributes. Use named notation to define them. * For example: #[Column('smallInt', unsigned: true, zerofill: true)] + * @param bool $obsolete The property should not be displayed in schema but must remains in the database. */ public function __construct( #[ExpectedValues(values: ['primary', 'bigPrimary', 'enum', 'boolean', @@ -68,6 +68,7 @@ public function __construct( protected mixed $typecast = null, protected bool $castDefault = false, protected bool $readonlySchema = false, + protected bool $obsolete = false, mixed ...$attributes, ) { if ($default !== null) { @@ -135,6 +136,11 @@ public function isReadonlySchema(): bool return $this->readonlySchema; } + public function isObsolete(): bool + { + return $this->obsolete; + } + /** * @return array */ diff --git a/src/Annotation/Relation/BelongsTo.php b/src/Annotation/Relation/BelongsTo.php index 09efbcbb..1b663bfd 100644 --- a/src/Annotation/Relation/BelongsTo.php +++ b/src/Annotation/Relation/BelongsTo.php @@ -33,6 +33,7 @@ class BelongsTo extends Relation * Defaults to {@see $fkAction}. * @param bool $indexCreate Create an index on innerKey. * @param non-empty-string $load Relation load approach. + * @param bool $obsolete The property should not be displayed in schema but must remains in the database. */ public function __construct( string $target, @@ -55,9 +56,10 @@ public function __construct( #[ExpectedValues(values: ['lazy', 'eager'])] string $load = 'lazy', ?Inverse $inverse = null, + bool $obsolete = false, ) { $this->inverse = $inverse; - parent::__construct($target, $load); + parent::__construct($target, $load, $obsolete); } } diff --git a/src/Annotation/Relation/Embedded.php b/src/Annotation/Relation/Embedded.php index 8023865f..f35c697d 100644 --- a/src/Annotation/Relation/Embedded.php +++ b/src/Annotation/Relation/Embedded.php @@ -23,16 +23,18 @@ class Embedded extends Relation * @param non-empty-string $target Entity to embed. * @param 'eager'|'lazy' $load Relation load approach. * @param string|null $prefix Prefix for embedded entity columns. + * @param bool $obsolete The property should not be displayed in schema but must remains in the database. */ public function __construct( string $target, #[ExpectedValues(values: ['lazy', 'eager'])] string $load = 'eager', ?string $prefix = null, + bool $obsolete = false, ) { $this->embeddedPrefix = $prefix; - parent::__construct($target, $load); + parent::__construct($target, $load, $obsolete); } public function getInverse(): ?Inverse diff --git a/src/Annotation/Relation/HasMany.php b/src/Annotation/Relation/HasMany.php index 273aca47..650e79c7 100644 --- a/src/Annotation/Relation/HasMany.php +++ b/src/Annotation/Relation/HasMany.php @@ -35,6 +35,7 @@ class HasMany extends Relation * @param bool $indexCreate Create an index on outerKey. * @param non-empty-string|null $collection Collection that will contain loaded entities. * @param non-empty-string $load Relation load approach. + * @param bool $obsolete The property should not be displayed in schema but must remains in the database. */ public function __construct( string $target, @@ -60,9 +61,10 @@ public function __construct( #[ExpectedValues(values: ['lazy', 'eager'])] string $load = 'lazy', ?Inverse $inverse = null, + bool $obsolete = false, ) { $this->inverse = $inverse; - parent::__construct($target, $load); + parent::__construct($target, $load, $obsolete); } } diff --git a/src/Annotation/Relation/HasOne.php b/src/Annotation/Relation/HasOne.php index 069eb163..93d76c44 100644 --- a/src/Annotation/Relation/HasOne.php +++ b/src/Annotation/Relation/HasOne.php @@ -33,6 +33,7 @@ class HasOne extends Relation * Defaults to {@see $fkAction}. * @param bool $indexCreate Create index on outerKey. * @param non-empty-string $load Relation load approach. + * @param bool $obsolete The property should not be displayed in schema but must remains in the database. */ public function __construct( string $target, @@ -55,9 +56,10 @@ public function __construct( #[ExpectedValues(values: ['lazy', 'eager'])] string $load = 'lazy', ?Inverse $inverse = null, + bool $obsolete = false, ) { $this->inverse = $inverse; - parent::__construct($target, $load); + parent::__construct($target, $load, $obsolete); } } diff --git a/src/Annotation/Relation/ManyToMany.php b/src/Annotation/Relation/ManyToMany.php index 3dd39baf..63309058 100644 --- a/src/Annotation/Relation/ManyToMany.php +++ b/src/Annotation/Relation/ManyToMany.php @@ -42,6 +42,7 @@ class ManyToMany extends Relation * @param bool $indexCreate Create index on [throughInnerKey, throughOuterKey]. * @param non-empty-string|null $collection Collection that will contain loaded entities. * @param non-empty-string $load Relation load approach. + * @param bool $obsolete The property should not be displayed in schema but must remains in the database. */ public function __construct( string $target, @@ -71,9 +72,10 @@ public function __construct( #[ExpectedValues(values: ['lazy', 'eager'])] string $load = 'lazy', ?Inverse $inverse = null, + bool $obsolete = false, ) { $this->inverse = $inverse; - parent::__construct($target, $load); + parent::__construct($target, $load, $obsolete); } } diff --git a/src/Annotation/Relation/RefersTo.php b/src/Annotation/Relation/RefersTo.php index 6a97d97f..5b308885 100644 --- a/src/Annotation/Relation/RefersTo.php +++ b/src/Annotation/Relation/RefersTo.php @@ -33,6 +33,7 @@ class RefersTo extends Relation * Defaults to {@see $fkAction}. * @param bool $indexCreate Create an index on outerKey. * @param non-empty-string $load Relation load approach. + * @param bool $obsolete The property should not be displayed in schema but must remains in the database. */ public function __construct( string $target, @@ -55,8 +56,9 @@ public function __construct( #[ExpectedValues(values: ['lazy', 'eager'])] string $load = 'lazy', ?Inverse $inverse = null, + bool $obsolete = false, ) { $this->inverse = $inverse; - parent::__construct($target, $load); + parent::__construct($target, $load, $obsolete); } } diff --git a/src/Annotation/Relation/Relation.php b/src/Annotation/Relation/Relation.php index b53c1b8d..dfbf9102 100644 --- a/src/Annotation/Relation/Relation.php +++ b/src/Annotation/Relation/Relation.php @@ -12,10 +12,12 @@ abstract class Relation implements RelationInterface /** * @param non-empty-string|null $target * @param non-empty-string $load + * @param bool $obsolete The property should not be displayed in schema but must remains in the database. Useful for the further safe DROP COLUMN. */ public function __construct( protected ?string $target, protected string $load = 'lazy', + protected bool $obsolete = false, ) {} /** diff --git a/src/Configurator.php b/src/Configurator.php index 03a6b1d3..f5eddda5 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -261,6 +261,8 @@ public function initField(string $name, Column $column, \ReflectionClass $class, $field->getAttributes()->set($k, $v); } + $field->getAttributes()->set('obsolete', $column->isObsolete()); + return $field; } diff --git a/tests/Annotated/Fixtures/Fixtures26/Address.php b/tests/Annotated/Fixtures/Fixtures26/Address.php new file mode 100644 index 00000000..02399cf6 --- /dev/null +++ b/tests/Annotated/Fixtures/Fixtures26/Address.php @@ -0,0 +1,31 @@ +address = new Address(); + $this->bornCity = $bornCity; + } +} diff --git a/tests/Annotated/Functional/Driver/Common/ObsoleteTest.php b/tests/Annotated/Functional/Driver/Common/ObsoleteTest.php new file mode 100644 index 00000000..daa078be --- /dev/null +++ b/tests/Annotated/Functional/Driver/Common/ObsoleteTest.php @@ -0,0 +1,70 @@ + [__DIR__ . '/../../../Fixtures/Fixtures26'], + 'exclude' => [], + ]), + ); + + $locator = $tokenizer->classLocator(); + + $r = new Registry($this->dbal); + + $schema = (new Compiler())->compile($r, [ + new Embeddings(new TokenizerEmbeddingLocator($locator, $reader), $reader), + new Entities(new TokenizerEntityLocator($locator, $reader), $reader), + new ResetTables(), + new MergeColumns($reader), + new GenerateRelations(), + new ValidateEntities(), + new RenderTables(), + new RenderRelations(), + new MergeIndexes($reader), + new SyncTables(), + new GenerateTypecast(), + ]); + + $this->assertArrayNotHasKey('user:address:address', $schema); + // user + $this->assertArrayNotHasKey('skype', $schema['user'][SchemaInterface::COLUMNS]); + $this->assertArrayNotHasKey('skype', $schema['user'][SchemaInterface::TYPECAST]); + $this->assertArrayNotHasKey('passport', $schema['user'][SchemaInterface::RELATIONS]); + $this->assertArrayNotHasKey('address', $schema['user'][SchemaInterface::RELATIONS]); + $this->assertArrayNotHasKey('bornCity', $schema['user'][SchemaInterface::RELATIONS]); + // passport + $this->assertArrayNotHasKey('user', $schema['passport'][SchemaInterface::RELATIONS]); + // city + $this->assertArrayNotHasKey('bornUsers', $schema['city'][SchemaInterface::RELATIONS]); + } +} diff --git a/tests/Annotated/Functional/Driver/MySQL/ObsoleteTest.php b/tests/Annotated/Functional/Driver/MySQL/ObsoleteTest.php new file mode 100644 index 00000000..8d4675ee --- /dev/null +++ b/tests/Annotated/Functional/Driver/MySQL/ObsoleteTest.php @@ -0,0 +1,17 @@ +