diff --git a/src/Entity/AbstractEntity.php b/src/Entity/AbstractEntity.php index 89ea23df..01bdcf2b 100644 --- a/src/Entity/AbstractEntity.php +++ b/src/Entity/AbstractEntity.php @@ -490,6 +490,10 @@ protected function initProperty(PropertyMetadata $metadata, string $name, bool $ { $this->validated[$name] = true; + if (!isset($this->data[$name]) && !array_key_exists($name, $this->data)) { + $this->data[$name] = $this->persistedId === null ? $metadata->defaultValue : null; + } + if ($metadata->wrapper !== null) { $wrapper = $this->createPropertyWrapper($metadata); if ($initValue || isset($this->data[$metadata->name])) { @@ -499,9 +503,6 @@ protected function initProperty(PropertyMetadata $metadata, string $name, bool $ return; } - if (!isset($this->data[$name]) && !array_key_exists($name, $this->data)) { - $this->data[$name] = $this->persistedId === null ? $metadata->defaultValue : null; - } if ($this->data[$name] !== null) { // data type coercion diff --git a/src/Entity/PropertyWrapper/BackedEnumWrapper.php b/src/Entity/PropertyWrapper/BackedEnumWrapper.php index ec6dff90..4be8c02f 100644 --- a/src/Entity/PropertyWrapper/BackedEnumWrapper.php +++ b/src/Entity/PropertyWrapper/BackedEnumWrapper.php @@ -5,6 +5,7 @@ use BackedEnum; use Nextras\Orm\Entity\ImmutableValuePropertyWrapper; +use Nextras\Orm\Exception\InvalidArgumentException; use Nextras\Orm\Exception\NullValueException; use function array_key_first; use function assert; @@ -29,8 +30,13 @@ public function convertToRawValue(mixed $value): mixed { if ($value === null) return null; $type = array_key_first($this->propertyMetadata->types); - assert($value instanceof $type); - assert($value instanceof BackedEnum); + if ($value instanceof BackedEnum === false) { + throw new InvalidArgumentException('Value must be of type BackedEnum.'); + } + if ($value instanceof $type === false) { + throw new InvalidArgumentException('Value must be of type ' . $type . '.'); + } + return $value->value; } @@ -42,9 +48,16 @@ public function convertFromRawValue(mixed $value): ?BackedEnum throw new NullValueException($this->propertyMetadata); } - assert(is_int($value) || is_string($value)); $type = array_key_first($this->propertyMetadata->types); - assert(is_subclass_of($type, BackedEnum::class)); - return $type::from($value); + if (is_int($value) || is_string($value)) { + if (is_subclass_of($type, BackedEnum::class)) { + return $type::from($value); + } + } + if ($value instanceof BackedEnum && $value instanceof $type) { + return $value; + } + + throw new InvalidArgumentException('Invalid value for enum.'); } } diff --git a/src/Entity/Reflection/ModifierParser.php b/src/Entity/Reflection/ModifierParser.php index 7415c066..da204884 100644 --- a/src/Entity/Reflection/ModifierParser.php +++ b/src/Entity/Reflection/ModifierParser.php @@ -3,12 +3,14 @@ namespace Nextras\Orm\Entity\Reflection; +use BackedEnum; use Nette\Utils\Reflection; use Nextras\Orm\Entity\Reflection\Parser\Token; use Nextras\Orm\Entity\Reflection\Parser\TokenLexer; use Nextras\Orm\Entity\Reflection\Parser\TokenStream; use Nextras\Orm\Exception\InvalidStateException; use ReflectionClass; +use ReflectionEnum; class ModifierParser @@ -189,6 +191,9 @@ private function processKeyword(string $value, ReflectionClass $reflectionClass) $reflection = new ReflectionClass($className); } + if ($reflection->isEnum() && is_subclass_of($className, BackedEnum::class)) { + return (new ReflectionEnum($className))->getCase($const)->getValue(); + } $enum = []; $constants = $reflection->getConstants(); if (str_contains($const, '*')) { diff --git a/tests/cases/integration/Collection/collection.enumOnEntities.phpt b/tests/cases/integration/Collection/collection.enumOnEntities.phpt new file mode 100644 index 00000000..a5d3f6c1 --- /dev/null +++ b/tests/cases/integration/Collection/collection.enumOnEntities.phpt @@ -0,0 +1,43 @@ +orm->books->findBy([ + 'genre' => [ + GenreEnum::HORROR, + GenreEnum::THRILLER, + GenreEnum::SCIFI, + GenreEnum::FANTASY, + ], + ]); + $collection = $collection->orderBy('id'); + Assert::same(3, $collection->countStored()); + + foreach ($collection as $book) { + Assert::type(GenreEnum::class, $book->genre); + } + } + +} + + +$test = new CollectionEnumOnEntitiesTest(); +$test->run(); diff --git a/tests/cases/integration/Entity/entity.enumProps.phpt b/tests/cases/integration/Entity/entity.enumProps.phpt new file mode 100644 index 00000000..4c93d0f1 --- /dev/null +++ b/tests/cases/integration/Entity/entity.enumProps.phpt @@ -0,0 +1,130 @@ +orm->books->findAll()->fetch(); + + Assert::notNull($book); + + Assert::same(GenreEnum::SCIFI, $book->genre); + } + + + public function testAddEntityWithEnum(): void + { + $bookName = 'Book 5'; + + $book = $this->createBookEntity($bookName); + $book->genre = GenreEnum::ROMANCE; + $this->orm->books->persistAndFlush($book); + + Assert::same(GenreEnum::ROMANCE, $book->genre); + + $entity = $this->orm->books->getBy(['title' => $bookName]); + Assert::notNull($entity); + + Assert::type(GenreEnum::class, $entity->genre); + + Assert::same(GenreEnum::ROMANCE, $entity->genre); + } + + + private function createBookEntity(string $title): Book + { + $book = new Book(); + $book->title = $title; + $book->publishedAt = new DateTimeImmutable('2021-12-14 21:10:02'); + $book->price = new Money(150, Currency::CZK); + $this->createAuthorToBook($book); + $this->createPublisherToBook($book); + $this->orm->books->persist($book); + + return $book; + } + + + private function createAuthorToBook(Book $book): void + { + $author1 = new Author(); + $author1->name = 'Writer 1'; + $author1->web = 'http://example.com/1'; + $this->orm->authors->persist($author1); + + $book->author = $author1; + } + + + private function createPublisherToBook(Book $book): void + { + $publisher1 = new Publisher(); + $publisher1->name = 'Nextras publisher A'; + $this->orm->publishers->persist($publisher1); + + $book->publisher = $publisher1; + } + + + public function testAddEntityWithDefaultEnum(): void + { + $bookName = 'Book 6'; + + $book = $this->createBookEntity($bookName); + $this->orm->books->persistAndFlush($book); + + Assert::same(GenreEnum::FANTASY, $book->genre); + + $entity = $this->orm->books->getBy(['title' => $bookName]); + Assert::notNull($entity); + + Assert::type(GenreEnum::class, $entity->genre); + + Assert::same(GenreEnum::FANTASY, $entity->genre); + } + + + public function testAddEntityWithUnknownEnum(): void + { + $bookName = 'Book 7'; + + $book = $this->createBookEntity($bookName); + // @phpstan-ignore-next-line + $book->genre = 'documentary'; + + Assert::exception(function () use ($book) { + $this->orm->books->persistAndFlush($book); + }, InvalidArgumentException::class); + + } + +} + + +$test = new EntityEnumPropTest(); +$test->run(); diff --git a/tests/db/array-data.php b/tests/db/array-data.php index bf3c0b08..06ebd913 100644 --- a/tests/db/array-data.php +++ b/tests/db/array-data.php @@ -3,6 +3,7 @@ namespace NextrasTests\Orm; +use inc\model\book\GenreEnum; use Nextras\Dbal\Utils\DateTimeImmutable; @@ -43,6 +44,7 @@ $book1->author = $author1; $book1->translator = $author1; $book1->publisher = $publisher1; +$book1->genre = GenreEnum::SCIFI; $book1->publishedAt = new \DateTimeImmutable('2021-12-14 21:10:04'); $book1->price = new Money(50, Currency::CZK); $book1->tags->set([$tag1, $tag2]); @@ -52,6 +54,7 @@ $book2->title = 'Book 2'; $book2->author = $author1; $book2->publisher = $publisher2; +$book2->genre = GenreEnum::HORROR; $book2->publishedAt = new \DateTimeImmutable('2021-12-14 21:10:02'); $book2->price = new Money(150, Currency::CZK); $book2->tags->set([$tag2, $tag3]); @@ -62,6 +65,7 @@ $book3->author = $author2; $book3->translator = $author2; $book3->publisher = $publisher3; +$book3->genre = GenreEnum::THRILLER; $book3->publishedAt = new \DateTimeImmutable('2021-12-14 21:10:03'); $book3->price = new Money(20, Currency::CZK); $book3->tags->set([$tag3]); @@ -72,6 +76,7 @@ $book4->author = $author2; $book4->translator = $author2; $book4->publisher = $publisher1; +$book4->genre = GenreEnum::ROMANCE; $book4->nextPart = $book3; $book4->publishedAt = new \DateTimeImmutable('2021-12-14 21:10:01'); $book4->price = new Money(220, Currency::CZK); diff --git a/tests/db/mssql-data.sql b/tests/db/mssql-data.sql index c7d9ecf2..c8b4093c 100644 --- a/tests/db/mssql-data.sql +++ b/tests/db/mssql-data.sql @@ -36,10 +36,10 @@ SET IDENTITY_INSERT tags OFF; DBCC checkident ('tags', reseed, 3) WITH NO_INFOMSGS; SET IDENTITY_INSERT books ON; -INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, published_at, price, price_currency) VALUES (1, 1, 1, 'Book 1', NULL, 1, '2021-12-14 21:10:04', 50, 'CZK'); -INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, published_at, price, price_currency) VALUES (2, 1, NULL, 'Book 2', NULL, 2, '2021-12-14 21:10:02', 150, 'CZK'); -INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, published_at, price, price_currency) VALUES (3, 2, 2, 'Book 3', NULL, 3, '2021-12-14 21:10:03', 20, 'CZK'); -INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, published_at, price, price_currency) VALUES (4, 2, 2, 'Book 4', 3, 1, '2021-12-14 21:10:01', 220, 'CZK'); +INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, published_at, genre, price, price_currency) VALUES (1, 1, 1, 'Book 1', NULL, 1, '2021-12-14 21:10:04', 'sciFi', 50, 'CZK'); +INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, published_at, genre, price, price_currency) VALUES (2, 1, NULL, 'Book 2', NULL, 2, '2021-12-14 21:10:02', 'horror', 150, 'CZK'); +INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, published_at, genre, price, price_currency) VALUES (3, 2, 2, 'Book 3', NULL, 3, '2021-12-14 21:10:03', 'thriller', 20, 'CZK'); +INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, published_at, genre, price, price_currency) VALUES (4, 2, 2, 'Book 4', 3, 1, '2021-12-14 21:10:01', 'romance', 220, 'CZK'); SET IDENTITY_INSERT books OFF; DBCC checkident ('books', reseed, 4) WITH NO_INFOMSGS; diff --git a/tests/db/mssql-init.sql b/tests/db/mssql-init.sql index f8581592..d47dd5c5 100644 --- a/tests/db/mssql-init.sql +++ b/tests/db/mssql-init.sql @@ -43,6 +43,7 @@ CREATE TABLE books next_part int, publisher_id int NOT NULL, published_at datetimeoffset NOT NULL, + genre varchar(20) NOT NULL, printed_at datetimeoffset, ean_id int, price int, diff --git a/tests/db/mysql-data.sql b/tests/db/mysql-data.sql index cd814b70..229c2fa1 100644 --- a/tests/db/mysql-data.sql +++ b/tests/db/mysql-data.sql @@ -25,10 +25,10 @@ INSERT INTO tags (id, name, is_global) VALUES (1, 'Tag 1', 'y'); INSERT INTO tags (id, name, is_global) VALUES (2, 'Tag 2', 'y'); INSERT INTO tags (id, name, is_global) VALUES (3, 'Tag 3', 'n'); -INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, published_at, price, price_currency) VALUES (1, 1, 1, 'Book 1', NULL, 1, '2021-12-14 21:10:04', 50, 'CZK'); -INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, published_at, price, price_currency) VALUES (2, 1, NULL, 'Book 2', NULL, 2, '2021-12-14 21:10:02', 150, 'CZK'); -INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, published_at, price, price_currency) VALUES (3, 2, 2, 'Book 3', NULL, 3, '2021-12-14 21:10:03', 20, 'CZK'); -INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, published_at, price, price_currency) VALUES (4, 2, 2, 'Book 4', 3, 1, '2021-12-14 21:10:01', 220, 'CZK'); +INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, genre, published_at, price, price_currency) VALUES (1, 1, 1, 'Book 1', NULL, 1, 'sciFi', '2021-12-14 21:10:04', 50, 'CZK'); +INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, genre, published_at, price, price_currency) VALUES (2, 1, NULL, 'Book 2', NULL, 2, 'horror', '2021-12-14 21:10:02', 150, 'CZK'); +INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, genre, published_at, price, price_currency) VALUES (3, 2, 2, 'Book 3', NULL, 3, 'thriller', '2021-12-14 21:10:03', 20, 'CZK'); +INSERT INTO books (id, author_id, translator_id, title, next_part, publisher_id, genre, published_at, price, price_currency) VALUES (4, 2, 2, 'Book 4', 3, 1, 'romance', '2021-12-14 21:10:01', 220, 'CZK'); INSERT INTO books_x_tags (book_id, tag_id) VALUES (1, 1); INSERT INTO books_x_tags (book_id, tag_id) VALUES (1, 2); diff --git a/tests/db/mysql-init.sql b/tests/db/mysql-init.sql index e3f62a59..2ae9e55c 100644 --- a/tests/db/mysql-init.sql +++ b/tests/db/mysql-init.sql @@ -42,6 +42,7 @@ CREATE TABLE books title varchar(50) NOT NULL, next_part int, publisher_id int NOT NULL, + genre varchar(20) NOT NULL, published_at DATETIME NOT NULL, printed_at DATETIME, ean_id int, diff --git a/tests/db/pgsql-data.sql b/tests/db/pgsql-data.sql index f7382a51..ebc52b1f 100644 --- a/tests/db/pgsql-data.sql +++ b/tests/db/pgsql-data.sql @@ -33,10 +33,10 @@ INSERT INTO "tags" ("id", "name", "is_global") VALUES (3, 'Tag 3', 'n'); SELECT setval('tags_id_seq', 3, true); -INSERT INTO "books" ("id", "author_id", "translator_id", "title", "next_part", "publisher_id", "published_at", "price", "price_currency") VALUES (1, 1, 1, 'Book 1', NULL, 1, '2021-12-14 21:10:04', 50, 'CZK'); -INSERT INTO "books" ("id", "author_id", "translator_id", "title", "next_part", "publisher_id", "published_at", "price", "price_currency") VALUES (2, 1, NULL, 'Book 2', NULL, 2, '2021-12-14 21:10:02', 150, 'CZK'); -INSERT INTO "books" ("id", "author_id", "translator_id", "title", "next_part", "publisher_id", "published_at", "price", "price_currency") VALUES (3, 2, 2, 'Book 3', NULL, 3, '2021-12-14 21:10:03', 20, 'CZK'); -INSERT INTO "books" ("id", "author_id", "translator_id", "title", "next_part", "publisher_id", "published_at", "price", "price_currency") VALUES (4, 2, 2, 'Book 4', 3, 1, '2021-12-14 21:10:01', 220, 'CZK'); +INSERT INTO "books" ("id", "author_id", "translator_id", "title", "next_part", "publisher_id", "published_at", "genre", "price", "price_currency") VALUES (1, 1, 1, 'Book 1', NULL, 1, '2021-12-14 21:10:04', 'sciFi', 50, 'CZK'); +INSERT INTO "books" ("id", "author_id", "translator_id", "title", "next_part", "publisher_id", "published_at", "genre", "price", "price_currency") VALUES (2, 1, NULL, 'Book 2', NULL, 2, '2021-12-14 21:10:02', 'horror', 150, 'CZK'); +INSERT INTO "books" ("id", "author_id", "translator_id", "title", "next_part", "publisher_id", "published_at", "genre", "price", "price_currency") VALUES (3, 2, 2, 'Book 3', NULL, 3, '2021-12-14 21:10:03', 'thriller', 20, 'CZK'); +INSERT INTO "books" ("id", "author_id", "translator_id", "title", "next_part", "publisher_id", "published_at", "genre", "price", "price_currency") VALUES (4, 2, 2, 'Book 4', 3, 1, '2021-12-14 21:10:01', 'romance', 220, 'CZK'); SELECT setval('books_id_seq', 4, true); diff --git a/tests/db/pgsql-init.sql b/tests/db/pgsql-init.sql index a35bc331..16e253ae 100644 --- a/tests/db/pgsql-init.sql +++ b/tests/db/pgsql-init.sql @@ -43,6 +43,7 @@ CREATE TABLE "books" "next_part" int, "publisher_id" int NOT NULL, "published_at" TIMESTAMP NOT NULL, + "genre" varchar(20) NOT NULL, "printed_at" TIMESTAMP, "ean_id" int, "price" int, diff --git a/tests/inc/model/book/Book.php b/tests/inc/model/book/Book.php index e078c74d..1e26c141 100644 --- a/tests/inc/model/book/Book.php +++ b/tests/inc/model/book/Book.php @@ -4,6 +4,7 @@ use DateTimeImmutable; +use inc\model\book\GenreEnum; use Nextras\Orm\Entity\Entity; use Nextras\Orm\Relationships\ManyHasMany; @@ -18,6 +19,7 @@ * @property Book|null $previousPart {1:1 Book::$nextPart} * @property Ean|null $ean {1:1 Ean::$book, isMain=true, cascade=[persist, remove]} * @property Publisher $publisher {m:1 Publisher::$books} + * @property GenreEnum $genre {default GenreEnum::FANTASY} * @property DateTimeImmutable $publishedAt {default "2021-12-31 23:59:59"} * @property DateTimeImmutable|null $printedAt * @property Money|null $price {embeddable} diff --git a/tests/inc/model/book/GenreEnum.php b/tests/inc/model/book/GenreEnum.php new file mode 100644 index 00000000..2d254d2b --- /dev/null +++ b/tests/inc/model/book/GenreEnum.php @@ -0,0 +1,13 @@ +