diff --git a/features/hal/table_inheritance.feature b/features/hal/table_inheritance.feature index 55ef27b7cad..24b8929c81b 100644 --- a/features/hal/table_inheritance.feature +++ b/features/hal/table_inheritance.feature @@ -75,6 +75,7 @@ Feature: Table inheritance "href": "/dummy_table_inheritances/2" } }, + "swaggerThanParent": true, "id": 2, "name": "Foobarbaz inheritance" } diff --git a/features/main/table_inheritance.feature b/features/main/table_inheritance.feature index 1c3617f5f92..77c2bf48a2e 100644 --- a/features/main/table_inheritance.feature +++ b/features/main/table_inheritance.feature @@ -548,6 +548,9 @@ Feature: Table inheritance "type": "string", "pattern": "^single item$" }, + "bar": { + "type": ["string", "null"] + }, "fooz": { "type": "string", "pattern": "fooz" diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index 7b141132aaa..ff00bbf8b94 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -472,17 +472,17 @@ protected function extractAttributes(object $object, ?string $format = null, arr */ protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false): array|bool { - if (!$this->resourceClassResolver->isResourceClass($context['resource_class'])) { + $contextResourceClass = $context['resource_class']; + if (!$this->resourceClassResolver->isResourceClass($contextResourceClass)) { return parent::getAllowedAttributes($classOrObject, $context, $attributesAsString); } - $resourceClass = $this->resourceClassResolver->getResourceClass(null, $context['resource_class']); // fix for abstract classes and interfaces $options = $this->getFactoryOptions($context); - $propertyNames = $this->propertyNameCollectionFactory->create($resourceClass, $options); + $propertyNames = $this->propertyNameCollectionFactory->create($contextResourceClass, $options); $allowedAttributes = []; foreach ($propertyNames as $propertyName) { - $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName, $options); + $propertyMetadata = $this->propertyMetadataFactory->create($contextResourceClass, $propertyName, $options); if ( $this->isAllowedAttribute($classOrObject, $propertyName, null, $context) diff --git a/src/Serializer/Tests/AbstractItemNormalizerTest.php b/src/Serializer/Tests/AbstractItemNormalizerTest.php index 49266814339..9fc8f3de6bd 100644 --- a/src/Serializer/Tests/AbstractItemNormalizerTest.php +++ b/src/Serializer/Tests/AbstractItemNormalizerTest.php @@ -43,6 +43,10 @@ use ApiPlatform\Serializer\Tests\Fixtures\ApiResource\PropertyCollectionIriOnlyRelation; use ApiPlatform\Serializer\Tests\Fixtures\ApiResource\RelatedDummy; use ApiPlatform\Serializer\Tests\Fixtures\ApiResource\SecuredDummy; +use ApiPlatform\Serializer\Tests\Fixtures\Polymorphism\ApiResource\Author; +use ApiPlatform\Serializer\Tests\Fixtures\Polymorphism\ApiResource\Book; +use ApiPlatform\Serializer\Tests\Fixtures\Polymorphism\Entity\FictionBook; +use ApiPlatform\Serializer\Tests\Fixtures\Polymorphism\Entity\TechnicalBook; use Doctrine\Common\Collections\ArrayCollection; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; @@ -1979,6 +1983,171 @@ public function testDenormalizeReportsAllMissingConstructorArguments(): void $this->assertSame(['title', 'rating', 'comment'], $e->getMissingConstructorArguments()); } } + + public function testNormalizePolymorphicFictionBook(): void + { + $author = new Author(); + $author->setName('author name'); + + $fictionBook = new FictionBook(); + $fictionBook->setTitle('The Hobbit'); + $fictionBook->setAuthor($author); + $fictionBook->setIsbn('978-0-345-33312-1'); + $fictionBook->setGenre('Fantasy'); + $fictionBook->setPageCount(310); + + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(FictionBook::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'author', 'isbn', 'bookType', 'genre', 'pageCount'])); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + + if (!method_exists(PropertyInfoExtractor::class, 'getType')) { + $stringType = new LegacyType(LegacyType::BUILTIN_TYPE_STRING); + $intType = new LegacyType(LegacyType::BUILTIN_TYPE_INT); + $authorType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, Author::class); + + $propertyMetadataFactoryProphecy->create(FictionBook::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$stringType])->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(FictionBook::class, 'author', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$authorType])->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(FictionBook::class, 'isbn', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$stringType])->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(FictionBook::class, 'bookType', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$stringType])->withReadable(true)->withWritable(false)); + $propertyMetadataFactoryProphecy->create(FictionBook::class, 'genre', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$stringType])->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(FictionBook::class, 'pageCount', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$intType])->withReadable(true)->withWritable(true)); + } else { + $propertyMetadataFactoryProphecy->create(FictionBook::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(FictionBook::class, 'author', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::object(Author::class))->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(FictionBook::class, 'isbn', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(FictionBook::class, 'bookType', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withReadable(true)->withWritable(false)); + $propertyMetadataFactoryProphecy->create(FictionBook::class, 'genre', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(FictionBook::class, 'pageCount', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::int())->withReadable(true)->withWritable(true)); + } + + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $iriConverterProphecy->getIriFromResource($fictionBook, Argument::cetera())->willReturn('/books/1'); + $iriConverterProphecy->getIriFromResource($author, Argument::cetera())->willReturn('/authors/1'); + + $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); + $propertyAccessorProphecy->getValue($fictionBook, 'title')->willReturn('The Hobbit'); + $propertyAccessorProphecy->getValue($fictionBook, 'author')->willReturn($author); + $propertyAccessorProphecy->getValue($fictionBook, 'isbn')->willReturn('978-0-345-33312-1'); + $propertyAccessorProphecy->getValue($fictionBook, 'bookType')->willReturn('fiction'); + $propertyAccessorProphecy->getValue($fictionBook, 'genre')->willReturn('Fantasy'); + $propertyAccessorProphecy->getValue($fictionBook, 'pageCount')->willReturn(310); + + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->getResourceClass(null, FictionBook::class)->willReturn(Book::class); + $resourceClassResolverProphecy->getResourceClass($fictionBook, null)->willReturn(FictionBook::class); + $resourceClassResolverProphecy->getResourceClass($author, Author::class)->willReturn(Author::class); + $resourceClassResolverProphecy->isResourceClass(FictionBook::class)->willReturn(true); + $resourceClassResolverProphecy->isResourceClass(Author::class)->willReturn(true); + + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->willImplement(NormalizerInterface::class); + $serializerProphecy->normalize('The Hobbit', null, Argument::type('array'))->willReturn('The Hobbit'); + $serializerProphecy->normalize('/authors/1', null, Argument::type('array'))->willReturn('/authors/1'); + $serializerProphecy->normalize('978-0-345-33312-1', null, Argument::type('array'))->willReturn('978-0-345-33312-1'); + $serializerProphecy->normalize('fiction', null, Argument::type('array'))->willReturn('fiction'); + $serializerProphecy->normalize('Fantasy', null, Argument::type('array'))->willReturn('Fantasy'); + $serializerProphecy->normalize(310, null, Argument::type('array'))->willReturn(310); + + $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {}; + $normalizer->setSerializer($serializerProphecy->reveal()); + + $expected = [ + 'title' => 'The Hobbit', + 'author' => '/authors/1', + 'isbn' => '978-0-345-33312-1', + 'bookType' => 'fiction', + 'genre' => 'Fantasy', + 'pageCount' => 310, + ]; + $result = $normalizer->normalize($fictionBook, null, ['resources' => []]); + $this->assertSame($expected, $result); + } + + public function testNormalizePolymorphicTechnicalBook(): void + { + $author = new Author(); + $author->setName('author name'); + + $technicalBook = new TechnicalBook(); + $technicalBook->setTitle('Design Patterns'); + $technicalBook->setAuthor($author); + $technicalBook->setIsbn('978-0-201-63361-0'); + $technicalBook->setProgrammingLanguage('C++'); + $technicalBook->setDifficultyLevel('advanced'); + $technicalBook->setTopic('Software Design'); + + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(TechnicalBook::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'author', 'isbn', 'bookType', 'programmingLanguage', 'difficultyLevel', 'topic'])); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + + if (!method_exists(PropertyInfoExtractor::class, 'getType')) { + $stringType = new LegacyType(LegacyType::BUILTIN_TYPE_STRING); + $authorType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, Author::class); + + $propertyMetadataFactoryProphecy->create(TechnicalBook::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$stringType])->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(TechnicalBook::class, 'author', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$authorType])->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(TechnicalBook::class, 'isbn', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$stringType])->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(TechnicalBook::class, 'bookType', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$stringType])->withReadable(true)->withWritable(false)); + $propertyMetadataFactoryProphecy->create(TechnicalBook::class, 'programmingLanguage', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$stringType])->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(TechnicalBook::class, 'difficultyLevel', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$stringType])->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(TechnicalBook::class, 'topic', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$stringType])->withReadable(true)->withWritable(true)); + } else { + $propertyMetadataFactoryProphecy->create(TechnicalBook::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(TechnicalBook::class, 'author', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::object(Author::class))->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(TechnicalBook::class, 'isbn', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(TechnicalBook::class, 'bookType', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withReadable(true)->withWritable(false)); + $propertyMetadataFactoryProphecy->create(TechnicalBook::class, 'programmingLanguage', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(TechnicalBook::class, 'difficultyLevel', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(TechnicalBook::class, 'topic', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withReadable(true)->withWritable(true)); + } + + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $iriConverterProphecy->getIriFromResource($technicalBook, Argument::cetera())->willReturn('/books/2'); + $iriConverterProphecy->getIriFromResource($author, Argument::cetera())->willReturn('/authors/1'); + + $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); + $propertyAccessorProphecy->getValue($technicalBook, 'title')->willReturn('Design Patterns'); + $propertyAccessorProphecy->getValue($technicalBook, 'author')->willReturn($author); + $propertyAccessorProphecy->getValue($technicalBook, 'isbn')->willReturn('978-0-201-63361-0'); + $propertyAccessorProphecy->getValue($technicalBook, 'bookType')->willReturn('technical'); + $propertyAccessorProphecy->getValue($technicalBook, 'programmingLanguage')->willReturn('C++'); + $propertyAccessorProphecy->getValue($technicalBook, 'difficultyLevel')->willReturn('advanced'); + $propertyAccessorProphecy->getValue($technicalBook, 'topic')->willReturn('Software Design'); + + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->getResourceClass(null, TechnicalBook::class)->willReturn(Book::class); + $resourceClassResolverProphecy->getResourceClass($technicalBook, null)->willReturn(TechnicalBook::class); + $resourceClassResolverProphecy->getResourceClass($author, Author::class)->willReturn(Author::class); + $resourceClassResolverProphecy->isResourceClass(TechnicalBook::class)->willReturn(true); + $resourceClassResolverProphecy->isResourceClass(Author::class)->willReturn(true); + + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->willImplement(NormalizerInterface::class); + $serializerProphecy->normalize('Design Patterns', null, Argument::type('array'))->willReturn('Design Patterns'); + $serializerProphecy->normalize('/authors/1', null, Argument::type('array'))->willReturn('/authors/1'); + $serializerProphecy->normalize('978-0-201-63361-0', null, Argument::type('array'))->willReturn('978-0-201-63361-0'); + $serializerProphecy->normalize('technical', null, Argument::type('array'))->willReturn('technical'); + $serializerProphecy->normalize('C++', null, Argument::type('array'))->willReturn('C++'); + $serializerProphecy->normalize('advanced', null, Argument::type('array'))->willReturn('advanced'); + $serializerProphecy->normalize('Software Design', null, Argument::type('array'))->willReturn('Software Design'); + + $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {}; + $normalizer->setSerializer($serializerProphecy->reveal()); + + $expected = [ + 'title' => 'Design Patterns', + 'author' => '/authors/1', + 'isbn' => '978-0-201-63361-0', + 'bookType' => 'technical', + 'programmingLanguage' => 'C++', + 'difficultyLevel' => 'advanced', + 'topic' => 'Software Design', + ]; + $result = $normalizer->normalize($technicalBook, null, ['resources' => []]); + $this->assertSame($expected, $result); + } } class ObjectWithBasicProperties diff --git a/src/Serializer/Tests/Fixtures/Polymorphism/ApiResource/Author.php b/src/Serializer/Tests/Fixtures/Polymorphism/ApiResource/Author.php new file mode 100644 index 00000000000..5bc79629a3c --- /dev/null +++ b/src/Serializer/Tests/Fixtures/Polymorphism/ApiResource/Author.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Serializer\Tests\Fixtures\Polymorphism\ApiResource; + +use ApiPlatform\Metadata\ApiResource; +use Doctrine\ORM\Mapping as ORM; + +#[ApiResource] +#[ORM\Entity] +class Author +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] + private ?int $id = null; + + #[ORM\Column(type: 'string', length: 255)] + private string $name; + + public function __construct(string $name = '') + { + $this->name = $name; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } +} diff --git a/src/Serializer/Tests/Fixtures/Polymorphism/ApiResource/Book.php b/src/Serializer/Tests/Fixtures/Polymorphism/ApiResource/Book.php new file mode 100644 index 00000000000..96792ae1ab3 --- /dev/null +++ b/src/Serializer/Tests/Fixtures/Polymorphism/ApiResource/Book.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Serializer\Tests\Fixtures\Polymorphism\ApiResource; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Serializer\Tests\Fixtures\Polymorphism\Entity\FictionBook; +use ApiPlatform\Serializer\Tests\Fixtures\Polymorphism\Entity\TechnicalBook; +use Doctrine\ORM\Mapping as ORM; + +#[ApiResource( + operations: [ + new GetCollection(), + ], +)] +#[ORM\Entity] +#[ORM\InheritanceType('SINGLE_TABLE')] +#[ORM\DiscriminatorColumn(name: 'book_type', type: 'string')] +#[ORM\DiscriminatorMap([ + 'fiction' => FictionBook::class, + 'technical' => TechnicalBook::class, +])] +abstract class Book +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] + private ?int $id = null; + + #[ORM\Column(type: 'string', length: 255)] + private string $title; + + #[ORM\ManyToOne(targetEntity: Author::class)] + #[ORM\JoinColumn(nullable: false)] + private Author $author; + + #[ORM\Column(type: 'string', length: 20)] + private string $isbn; + + public function __construct(string $title = '', ?Author $author = null, string $isbn = '') + { + $this->title = $title; + $this->author = $author ?? new Author('Unknown'); + $this->isbn = $isbn; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getTitle(): string + { + return $this->title; + } + + public function setTitle(string $title): self + { + $this->title = $title; + + return $this; + } + + public function getAuthor(): Author + { + return $this->author; + } + + public function setAuthor(Author $author): self + { + $this->author = $author; + + return $this; + } + + public function getIsbn(): string + { + return $this->isbn; + } + + public function setIsbn(string $isbn): self + { + $this->isbn = $isbn; + + return $this; + } + + abstract public function getBookType(): string; +} diff --git a/src/Serializer/Tests/Fixtures/Polymorphism/Entity/FictionBook.php b/src/Serializer/Tests/Fixtures/Polymorphism/Entity/FictionBook.php new file mode 100644 index 00000000000..2bc710a84c0 --- /dev/null +++ b/src/Serializer/Tests/Fixtures/Polymorphism/Entity/FictionBook.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Serializer\Tests\Fixtures\Polymorphism\Entity; + +use ApiPlatform\Serializer\Tests\Fixtures\Polymorphism\ApiResource\Author; +use ApiPlatform\Serializer\Tests\Fixtures\Polymorphism\ApiResource\Book; +use Doctrine\ORM\Mapping as ORM; + +#[ORM\Entity] +class FictionBook extends Book +{ + public const BOOK_TYPE = 'fiction'; + + #[ORM\Column(type: 'string', length: 100, nullable: true)] + private ?string $genre = null; + + #[ORM\Column(type: 'integer', nullable: true)] + private ?int $pageCount = null; + + public function __construct( + string $title = '', + ?Author $author = null, + string $isbn = '', + ?string $genre = null, + ?int $pageCount = null, + ) { + parent::__construct($title, $author, $isbn); + $this->genre = $genre; + $this->pageCount = $pageCount; + } + + public function getBookType(): string + { + return self::BOOK_TYPE; + } + + public function getGenre(): ?string + { + return $this->genre; + } + + public function setGenre(?string $genre): self + { + $this->genre = $genre; + + return $this; + } + + public function getPageCount(): ?int + { + return $this->pageCount; + } + + public function setPageCount(?int $pageCount): self + { + $this->pageCount = $pageCount; + + return $this; + } +} diff --git a/src/Serializer/Tests/Fixtures/Polymorphism/Entity/TechnicalBook.php b/src/Serializer/Tests/Fixtures/Polymorphism/Entity/TechnicalBook.php new file mode 100644 index 00000000000..b259a32d222 --- /dev/null +++ b/src/Serializer/Tests/Fixtures/Polymorphism/Entity/TechnicalBook.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Serializer\Tests\Fixtures\Polymorphism\Entity; + +use ApiPlatform\Serializer\Tests\Fixtures\Polymorphism\ApiResource\Author; +use ApiPlatform\Serializer\Tests\Fixtures\Polymorphism\ApiResource\Book; +use Doctrine\ORM\Mapping as ORM; + +#[ORM\Entity] +class TechnicalBook extends Book +{ + public const BOOK_TYPE = 'technical'; + + #[ORM\Column(type: 'string', length: 100, nullable: true)] + private ?string $programmingLanguage = null; + + #[ORM\Column(type: 'string', length: 50, nullable: true)] + private ?string $difficultyLevel = null; + + #[ORM\Column(type: 'string', length: 255, nullable: true)] + private ?string $topic = null; + + public function __construct( + string $title = '', + ?Author $author = null, + string $isbn = '', + ?string $programmingLanguage = null, + ?string $difficultyLevel = null, + ?string $topic = null, + ) { + parent::__construct($title, $author, $isbn); + $this->programmingLanguage = $programmingLanguage; + $this->difficultyLevel = $difficultyLevel; + $this->topic = $topic; + } + + public function getBookType(): string + { + return self::BOOK_TYPE; + } + + public function getProgrammingLanguage(): ?string + { + return $this->programmingLanguage; + } + + public function setProgrammingLanguage(?string $programmingLanguage): self + { + $this->programmingLanguage = $programmingLanguage; + + return $this; + } + + public function getDifficultyLevel(): ?string + { + return $this->difficultyLevel; + } + + public function setDifficultyLevel(?string $difficultyLevel): self + { + $this->difficultyLevel = $difficultyLevel; + + return $this; + } + + public function getTopic(): ?string + { + return $this->topic; + } + + public function setTopic(?string $topic): self + { + $this->topic = $topic; + + return $this; + } +}