diff --git a/src/Metadata/Link.php b/src/Metadata/Link.php index 2aa9b447f7..2601e32029 100644 --- a/src/Metadata/Link.php +++ b/src/Metadata/Link.php @@ -120,7 +120,7 @@ public function getFromProperty(): ?string public function withFromProperty(string $fromProperty): self { - $self = $this; + $self = clone $this; $self->fromProperty = $fromProperty; return $self; diff --git a/src/Metadata/Put.php b/src/Metadata/Put.php index 3cf5c3b368..9c74fd74ff 100644 --- a/src/Metadata/Put.php +++ b/src/Metadata/Put.php @@ -197,9 +197,12 @@ class: $class, ); } - public function withAllowCreate(bool $allowCreate): void + public function withAllowCreate(bool $allowCreate): static { - $this->allowCreate = $allowCreate; + $self = clone $this; + $self->allowCreate = $allowCreate; + + return $self; } public function getAllowCreate(): ?bool diff --git a/src/Metadata/Tests/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php b/src/Metadata/Tests/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php index f9eef140bc..7f416b6794 100644 --- a/src/Metadata/Tests/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php +++ b/src/Metadata/Tests/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php @@ -210,6 +210,16 @@ class: AttributeConfigOperations::class, ]), $attributesResourceMetadataCollectionFactory->create(AttributeConfigOperations::class)); } + public function testCreateWithAllowCreateDefault(): void + { + $attributeResourceMetadataCollectionFactory = new AttributesResourceMetadataCollectionFactory(defaults: ['allow_create' => true]); + + $operation = $attributeResourceMetadataCollectionFactory->create(AttributeResource::class)->getOperation('_api_AttributeResource_put'); + + $this->assertInstanceOf(Put::class, $operation); + $this->assertTrue($operation->getAllowCreate()); + } + public function testCreateShouldNotOverrideWithDefault(): void { $attributeResourceMetadataCollectionFactory = new AttributesResourceMetadataCollectionFactory( diff --git a/src/Metadata/Tests/WithersTest.php b/src/Metadata/Tests/WithersTest.php new file mode 100644 index 0000000000..5683ab72b2 --- /dev/null +++ b/src/Metadata/Tests/WithersTest.php @@ -0,0 +1,127 @@ + + * + * 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\Metadata\Tests; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Error; +use ApiPlatform\Metadata\ErrorResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\GraphQl\DeleteMutation; +use ApiPlatform\Metadata\GraphQl\Mutation; +use ApiPlatform\Metadata\GraphQl\Query as GraphQlQuery; +use ApiPlatform\Metadata\GraphQl\QueryCollection; +use ApiPlatform\Metadata\GraphQl\Subscription; +use ApiPlatform\Metadata\HeaderParameter; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\McpResource; +use ApiPlatform\Metadata\McpTool; +use ApiPlatform\Metadata\McpToolCollection; +use ApiPlatform\Metadata\NotExposed; +use ApiPlatform\Metadata\Operations; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Metadata\Put; +use ApiPlatform\Metadata\Query; +use ApiPlatform\Metadata\QueryParameter; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; +use Symfony\Component\TypeInfo\Type; + +final class WithersTest extends TestCase +{ + #[DataProvider('provideMetadata')] + public function testWithersReturnANewInstance(object $metadata): void + { + foreach ((new \ReflectionObject($metadata))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + if (!str_starts_with($method->getName(), 'with') || $method->isStatic()) { + continue; + } + + $arguments = []; + foreach (\array_slice($method->getParameters(), 0, $method->getNumberOfRequiredParameters()) as $parameter) { + $arguments[] = $this->createArgument($parameter, $metadata); + } + + $copy = $metadata->{$method->getName()}(...$arguments); + + $this->assertInstanceOf($metadata::class, $copy, \sprintf('%s::%s() must return the metadata.', $metadata::class, $method->getName())); + $this->assertNotSame($metadata, $copy, \sprintf('%s::%s() must return a new instance.', $metadata::class, $method->getName())); + } + } + + public static function provideMetadata(): iterable + { + yield [new ApiResource()]; + yield [new ApiProperty()]; + yield [new Link()]; + yield [new QueryParameter()]; + yield [new HeaderParameter()]; + yield [new Get()]; + yield [new GetCollection()]; + yield [new Post()]; + yield [new Put()]; + yield [new Patch()]; + yield [new Delete()]; + yield [new Query()]; + yield [new NotExposed()]; + yield [new Error()]; + yield [new ErrorResource()]; + yield [new GraphQlQuery()]; + yield [new QueryCollection()]; + yield [new Mutation()]; + yield [new DeleteMutation()]; + yield [new Subscription()]; + yield [new McpTool()]; + yield [new McpToolCollection()]; + yield [new McpResource('file://foo')]; + } + + private function createArgument(\ReflectionParameter $parameter, object $metadata): mixed + { + $type = $parameter->getType(); + if (null === $type || $type->allowsNull()) { + return null; + } + + foreach ($type instanceof \ReflectionUnionType ? $type->getTypes() : [$type] as $namedType) { + $name = $namedType instanceof \ReflectionNamedType ? $namedType->getName() : null; + + if ('self' === $name || (null !== $name && is_a($metadata, $name))) { + return $metadata; + } + + switch ($name) { + case 'bool': + return true; + case 'false': + return false; + case 'int': + return 1; + case 'string': + return 'foo'; + case 'array': + return []; + case Operations::class: + return new Operations(); + case Type::class: + return Type::string(); + } + } + + $this->fail(\sprintf('Cannot create an argument for $%s of %s::%s().', $parameter->getName(), $metadata::class, $parameter->getDeclaringFunction()->getName())); + } +}