From 2f301f18d6a3a1a8adf1c0c0435d55d27e93971c Mon Sep 17 00:00:00 2001 From: Martin Helmich Date: Fri, 16 Jan 2026 12:15:42 +0100 Subject: [PATCH 1/6] chore: bump PHP to 8.5 and dependencies, fix resulting errors --- .github/workflows/php.yml | 2 +- README.md | 2 +- composer.json | 4 +-- psalm.xml | 20 +++---------- src/Codegen/PropertyGenerator.php | 2 +- src/Command/GenerateSpecCommand.php | 12 +++++++- src/Generator/NamespaceInferrer.php | 11 ++++++- .../PropertyCollectionFilterFactory.php | 2 +- src/Generator/PropertyBuilder.php | 1 + src/Generator/SchemaToClass.php | 9 +++--- src/Generator/SchemaToEnum.php | 13 +++++---- src/Util/ErrorCorrection.php | 29 +++++++++++++++++++ .../Fixtures/AdditionalProps/Output/Foo.php | 18 ++++++------ .../Fixtures/AllOfRef/Output/Foo.php | 18 ++++++------ tests/Generator/Fixtures/Basic/Output/Foo.php | 16 +++++----- .../Fixtures/DefaultValue/Output/Foo.php | 18 ++++++------ .../DefaultValueAsOptional/Output/Foo.php | 14 ++++----- .../Output/Foo.php | 16 +++++----- .../Fixtures/JsonFile/Output/Foo.php | 10 +++---- .../Generator/Fixtures/RefList/Output/Foo.php | 12 ++++---- .../Fixtures/ReservedNames/Output/Foo.php | 16 +++++----- .../SpecialCharacterNames/Output/Foo.php | 10 +++---- .../Fixtures/UnionCollapsing/Output/Foo.php | 10 +++---- 23 files changed, 152 insertions(+), 113 deletions(-) create mode 100644 src/Util/ErrorCorrection.php diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 34951a42..03d2c8ea 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -43,4 +43,4 @@ jobs: run: ./vendor/bin/psalm --php-version=8.2 - name: Run type checker (PHPStan) - run: ./vendor/bin/phpstan analyze + run: ./vendor/bin/phpstan analyze --memory-limit=1G diff --git a/README.md b/README.md index 2a231738..71c1495b 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ echo "Hello, " . $user->getGivenName() . "\n"; ## Compatibility -This tool requires PHP 8.2 or newer to run. +This tool requires PHP 8.5 or newer to run. The generated code can be backwards-compatible up until PHP 5.6. Use the `--target-php` flag to set the desired PHP version that the generated code should be compatible with. When [using a configuration file](#using-configuration-files), use the `targetPHPVersion` property. diff --git a/composer.json b/composer.json index 15268089..4da02fec 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ } ], "require": { - "php": "^8.2", + "php": "^8.5", "symfony/console": "~3.0|~4.0|~5.0|~6.0|~7.0", "symfony/yaml": "~3.0|~4.0|~5.0|~6.0|~7.0", "justinrainbow/json-schema": "^6.0", @@ -33,7 +33,7 @@ ], "require-dev": { "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^5.26", + "vimeo/psalm": "^6.14.3", "phpstan/phpstan": "^2.1.2", "phpspec/prophecy": "^1.17", "phpspec/prophecy-phpunit": "^2.0" diff --git a/psalm.xml b/psalm.xml index 4c8cc6c5..14f9f400 100644 --- a/psalm.xml +++ b/psalm.xml @@ -4,6 +4,7 @@ allowStringToStandInForClass="true" findUnusedBaselineEntry="true" findUnusedCode="false" + ensureOverrideAttribute="false" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" @@ -39,28 +40,15 @@ - - - - - - - - - - - - - + - - + - + diff --git a/src/Codegen/PropertyGenerator.php b/src/Codegen/PropertyGenerator.php index bdf8236d..5feb17dc 100644 --- a/src/Codegen/PropertyGenerator.php +++ b/src/Codegen/PropertyGenerator.php @@ -51,7 +51,7 @@ public static function fromReflection(PropertyReflection $reflectionProperty): Z $property = ZendPropertyGenerator::fromReflection($reflectionProperty); if ($reflectionProperty->hasType()) { - $property->setTypeHint($reflectionProperty->getType() . ""); + $property->setTypeHint((string)$reflectionProperty->getType()); } return $property; diff --git a/src/Command/GenerateSpecCommand.php b/src/Command/GenerateSpecCommand.php index 92c40687..3b02fb08 100644 --- a/src/Command/GenerateSpecCommand.php +++ b/src/Command/GenerateSpecCommand.php @@ -2,6 +2,7 @@ declare(strict_types = 1); namespace Helmich\Schema2Class\Command; +use Exception; use Helmich\Schema2Class\Generator\GeneratorException; use Helmich\Schema2Class\Generator\GeneratorRequest; use Helmich\Schema2Class\Generator\NamespaceInferrer; @@ -56,14 +57,23 @@ protected function configure(): void */ protected function execute(InputInterface $input, OutputInterface $output): int { + $workingDir = getcwd(); + if ($workingDir === false) { + throw new GeneratorException("cannot determine current working directory"); + } + /** @var string $specFile */ - $specFile = $input->getArgument("specfile") ?: getcwd() . "/.s2c.yaml"; + $specFile = $input->getArgument("specfile") ?: $workingDir . DIRECTORY_SEPARATOR . ".s2c.yaml"; if (!file_exists($specFile)) { throw new LoadingException($specFile, "specification file not found"); } $contents = file_get_contents($specFile); + if ($contents === false) { + throw new LoadingException($specFile, "cannot read specification file"); + } + $parsed = Yaml::parse($contents); $specification = Specification::buildFromInput($parsed); diff --git a/src/Generator/NamespaceInferrer.php b/src/Generator/NamespaceInferrer.php index 791e1b29..c9add02d 100644 --- a/src/Generator/NamespaceInferrer.php +++ b/src/Generator/NamespaceInferrer.php @@ -19,8 +19,13 @@ public function inferNamespaceFromTargetDirectory(string $directory): string return substr($string, strlen($prefix) + $additional); }; + $workingDirectory = getcwd(); + if ($workingDirectory === false) { + throw new GeneratorException("could not determine current working directory"); + } + if ($directory[0] !== "/") { - $directory = getcwd() . "/" . $directory; + $directory = $workingDirectory . PATH_SEPARATOR . $directory; } list($root, $composer) = $this->getComposerJSONForDirectory($directory); @@ -58,6 +63,10 @@ private function getComposerJSONForDirectory(string $directory): array while ($directory !== "/" && $directory !== "") { if (file_exists($directory . "/composer.json")) { $contents = file_get_contents($directory . "/composer.json"); + if ($contents === false) { + throw new GeneratorException("cannot read composer.json in directory $directory"); + } + return [$directory, json_decode($contents, true)]; } diff --git a/src/Generator/Property/PropertyCollectionFilterFactory.php b/src/Generator/Property/PropertyCollectionFilterFactory.php index e5464745..f98693c7 100644 --- a/src/Generator/Property/PropertyCollectionFilterFactory.php +++ b/src/Generator/Property/PropertyCollectionFilterFactory.php @@ -26,7 +26,7 @@ public function __construct(PropertyCollection $properties) public function apply(PropertyInterface $property): bool { $matchingProperties = $this->propertyNamesCaseInsensitive[strtolower($property->key())]; - $matchingPropertiesWithDifferentCase = array_filter($matchingProperties, fn($name) => $name !== $property->key()); + $matchingPropertiesWithDifferentCase = array_filter($matchingProperties, fn(string $name) => $name !== $property->key()); if (PropertyQuery::isDeprecated($property) && count($matchingPropertiesWithDifferentCase) > 0) { return false; diff --git a/src/Generator/PropertyBuilder.php b/src/Generator/PropertyBuilder.php index 17b39867..b74c06d6 100644 --- a/src/Generator/PropertyBuilder.php +++ b/src/Generator/PropertyBuilder.php @@ -68,6 +68,7 @@ public static function buildPropertyFromSchema(GeneratorRequest $req, string $na } } + /** @psalm-suppress PossiblyFalseOperand */ throw new GeneratorException("cannot map type " . json_encode($definition)); } diff --git a/src/Generator/SchemaToClass.php b/src/Generator/SchemaToClass.php index fc193a4d..9b4e9023 100644 --- a/src/Generator/SchemaToClass.php +++ b/src/Generator/SchemaToClass.php @@ -7,6 +7,7 @@ use Helmich\Schema2Class\Generator\Property\IntersectProperty; use Helmich\Schema2Class\Generator\Property\NestedObjectProperty; use Helmich\Schema2Class\Generator\Property\PropertyCollection; +use Helmich\Schema2Class\Util\ErrorCorrection; use Helmich\Schema2Class\Writer\WriterInterface; use Laminas\Code\DeclareStatement; use Laminas\Code\Generator\ClassGenerator; @@ -120,11 +121,11 @@ public function schemaToClass(GeneratorRequest $req): void $file->setDeclares([DeclareStatement::strictTypes(1)]); } - $content = $file->generate(); - // Do some corrections because the Zend code generation library is stupid. - $content = preg_replace('/ : \\\\self/', ' : self', $content); - $content = preg_replace('/\\\\' . preg_quote($req->getTargetNamespace(), '/') . '\\\\/', '', $content); + $correction = new ErrorCorrection($req->getTargetNamespace()); + $content = $file->generate() + |> $correction->replaceIncorrectlyNamespacedSelf(...) + |> $correction->replaceIncorrectFQCNs(...); $this->writer->writeFile($filename, $content); } diff --git a/src/Generator/SchemaToEnum.php b/src/Generator/SchemaToEnum.php index d5bf7206..cc41757b 100644 --- a/src/Generator/SchemaToEnum.php +++ b/src/Generator/SchemaToEnum.php @@ -2,6 +2,7 @@ namespace Helmich\Schema2Class\Generator; +use Helmich\Schema2Class\Util\ErrorCorrection; use Helmich\Schema2Class\Writer\WriterInterface; use Laminas\Code\DeclareStatement; use Laminas\Code\Generator\EnumGenerator\EnumGenerator; @@ -57,11 +58,11 @@ public function schemaToEnum(GeneratorRequest $req): void $file->setDeclares([DeclareStatement::strictTypes(1)]); - $content = $file->generate(); - // Do some corrections because the Zend code generation library is stupid. - $content = preg_replace('/ : \\\\self/', ' : self', $content); - $content = preg_replace('/\\\\' . preg_quote($req->getTargetNamespace(), '/') . '\\\\/', '', $content); + $correction = new ErrorCorrection($req->getTargetNamespace()); + $content = $file->generate() + |> $correction->replaceIncorrectlyNamespacedSelf(...) + |> $correction->replaceIncorrectFQCNs(...); $this->writer->writeFile($filename, $content); } @@ -123,11 +124,11 @@ public static function enumCaseName(string|int $value): string } /** - * @param string $value - * @return string + * @psalm-suppress InvalidNullableReturnType */ private static function enumCaseNameString(string $value): string { + /** @psalm-suppress NullableReturnStatement */ return preg_replace('/[^a-zA-Z0-9]/', '', $value); } diff --git a/src/Util/ErrorCorrection.php b/src/Util/ErrorCorrection.php new file mode 100644 index 00000000..6c7f057f --- /dev/null +++ b/src/Util/ErrorCorrection.php @@ -0,0 +1,29 @@ +targetNamespace, '/') . '\\\\/', '', $content); + } +} \ No newline at end of file diff --git a/tests/Generator/Fixtures/AdditionalProps/Output/Foo.php b/tests/Generator/Fixtures/AdditionalProps/Output/Foo.php index e04e57c0..7a239667 100644 --- a/tests/Generator/Fixtures/AdditionalProps/Output/Foo.php +++ b/tests/Generator/Fixtures/AdditionalProps/Output/Foo.php @@ -46,7 +46,7 @@ public function __construct() /** * @return string|null */ - public function getName() : ?string + public function getName(): ?string { return $this->name ?? null; } @@ -54,7 +54,7 @@ public function getName() : ?string /** * @return mixed[]|null */ - public function getParams() : ?array + public function getParams(): ?array { return $this->params ?? null; } @@ -63,7 +63,7 @@ public function getParams() : ?array * @param string $name * @return self */ - public function withName(string $name) : self + public function withName(string $name): self { $validator = new \JsonSchema\Validator(); $validator->validate($name, self::$internalValidationSchema['properties']['name']); @@ -80,7 +80,7 @@ public function withName(string $name) : self /** * @return self */ - public function withoutName() : self + public function withoutName(): self { $clone = clone $this; unset($clone->name); @@ -92,7 +92,7 @@ public function withoutName() : self * @param mixed[] $params * @return self */ - public function withParams(array $params) : self + public function withParams(array $params): self { $validator = new \JsonSchema\Validator(); $validator->validate($params, self::$internalValidationSchema['properties']['params']); @@ -109,7 +109,7 @@ public function withParams(array $params) : self /** * @return self */ - public function withoutParams() : self + public function withoutParams(): self { $clone = clone $this; unset($clone->params); @@ -125,7 +125,7 @@ public function withoutParams() : self * @return Foo Created instance * @throws \InvalidArgumentException */ - public static function buildFromInput(array|object $input, bool $validate = true) : Foo + public static function buildFromInput(array|object $input, bool $validate = true): Foo { $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; if ($validate) { @@ -152,7 +152,7 @@ public static function buildFromInput(array|object $input, bool $validate = true * * @return array Converted array */ - public function toJson() : array + public function toJson(): array { $output = []; if (isset($this->name)) { @@ -173,7 +173,7 @@ public function toJson() : array * @return bool Validation result * @throws \InvalidArgumentException */ - public static function validateInput(array|object $input, bool $return = false) : bool + public static function validateInput(array|object $input, bool $return = false): bool { $validator = new \JsonSchema\Validator(); $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; diff --git a/tests/Generator/Fixtures/AllOfRef/Output/Foo.php b/tests/Generator/Fixtures/AllOfRef/Output/Foo.php index a1565146..d1ea0896 100644 --- a/tests/Generator/Fixtures/AllOfRef/Output/Foo.php +++ b/tests/Generator/Fixtures/AllOfRef/Output/Foo.php @@ -61,7 +61,7 @@ public function __construct(string $city, string $street, string $country) /** * @return string */ - public function getCity() : string + public function getCity(): string { return $this->city; } @@ -69,7 +69,7 @@ public function getCity() : string /** * @return string */ - public function getStreet() : string + public function getStreet(): string { return $this->street; } @@ -77,7 +77,7 @@ public function getStreet() : string /** * @return string */ - public function getCountry() : string + public function getCountry(): string { return $this->country; } @@ -86,7 +86,7 @@ public function getCountry() : string * @param string $city * @return self */ - public function withCity(string $city) : self + public function withCity(string $city): self { $validator = new \JsonSchema\Validator(); $validator->validate($city, self::$internalValidationSchema['properties']['city']); @@ -104,7 +104,7 @@ public function withCity(string $city) : self * @param string $street * @return self */ - public function withStreet(string $street) : self + public function withStreet(string $street): self { $validator = new \JsonSchema\Validator(); $validator->validate($street, self::$internalValidationSchema['properties']['street']); @@ -122,7 +122,7 @@ public function withStreet(string $street) : self * @param string $country * @return self */ - public function withCountry(string $country) : self + public function withCountry(string $country): self { $validator = new \JsonSchema\Validator(); $validator->validate($country, self::$internalValidationSchema['properties']['country']); @@ -144,7 +144,7 @@ public function withCountry(string $country) : self * @return Foo Created instance * @throws \InvalidArgumentException */ - public static function buildFromInput(array|object $input, bool $validate = true) : Foo + public static function buildFromInput(array|object $input, bool $validate = true): Foo { $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; if ($validate) { @@ -165,7 +165,7 @@ public static function buildFromInput(array|object $input, bool $validate = true * * @return array Converted array */ - public function toJson() : array + public function toJson(): array { $output = []; $output['city'] = $this->city; @@ -183,7 +183,7 @@ public function toJson() : array * @return bool Validation result * @throws \InvalidArgumentException */ - public static function validateInput(array|object $input, bool $return = false) : bool + public static function validateInput(array|object $input, bool $return = false): bool { $validator = new \JsonSchema\Validator(); $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; diff --git a/tests/Generator/Fixtures/Basic/Output/Foo.php b/tests/Generator/Fixtures/Basic/Output/Foo.php index 77b9973c..6d9ef82e 100644 --- a/tests/Generator/Fixtures/Basic/Output/Foo.php +++ b/tests/Generator/Fixtures/Basic/Output/Foo.php @@ -46,7 +46,7 @@ public function __construct(string $fooBar) /** * @return string|null */ - public function getFoo() : ?string + public function getFoo(): ?string { return $this->foo ?? null; } @@ -54,7 +54,7 @@ public function getFoo() : ?string /** * @return string */ - public function getFooBar() : string + public function getFooBar(): string { return $this->fooBar; } @@ -63,7 +63,7 @@ public function getFooBar() : string * @param string $foo * @return self */ - public function withFoo(string $foo) : self + public function withFoo(string $foo): self { $validator = new \JsonSchema\Validator(); $validator->validate($foo, self::$internalValidationSchema['properties']['foo']); @@ -80,7 +80,7 @@ public function withFoo(string $foo) : self /** * @return self */ - public function withoutFoo() : self + public function withoutFoo(): self { $clone = clone $this; unset($clone->foo); @@ -92,7 +92,7 @@ public function withoutFoo() : self * @param string $fooBar * @return self */ - public function withFooBar(string $fooBar) : self + public function withFooBar(string $fooBar): self { $validator = new \JsonSchema\Validator(); $validator->validate($fooBar, self::$internalValidationSchema['properties']['foo_bar']); @@ -114,7 +114,7 @@ public function withFooBar(string $fooBar) : self * @return Foo Created instance * @throws \InvalidArgumentException */ - public static function buildFromInput(array|object $input, bool $validate = true) : Foo + public static function buildFromInput(array|object $input, bool $validate = true): Foo { $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; if ($validate) { @@ -137,7 +137,7 @@ public static function buildFromInput(array|object $input, bool $validate = true * * @return array Converted array */ - public function toJson() : array + public function toJson(): array { $output = []; if (isset($this->foo)) { @@ -156,7 +156,7 @@ public function toJson() : array * @return bool Validation result * @throws \InvalidArgumentException */ - public static function validateInput(array|object $input, bool $return = false) : bool + public static function validateInput(array|object $input, bool $return = false): bool { $validator = new \JsonSchema\Validator(); $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; diff --git a/tests/Generator/Fixtures/DefaultValue/Output/Foo.php b/tests/Generator/Fixtures/DefaultValue/Output/Foo.php index 569ebbcf..af69ac3d 100644 --- a/tests/Generator/Fixtures/DefaultValue/Output/Foo.php +++ b/tests/Generator/Fixtures/DefaultValue/Output/Foo.php @@ -48,7 +48,7 @@ public function __construct() /** * @return int */ - public function getLimit() : int + public function getLimit(): int { return $this->limit; } @@ -56,7 +56,7 @@ public function getLimit() : int /** * @return int */ - public function getSkip() : int + public function getSkip(): int { return $this->skip; } @@ -65,7 +65,7 @@ public function getSkip() : int * @param int $limit * @return self */ - public function withLimit(int $limit) : self + public function withLimit(int $limit): self { $validator = new \JsonSchema\Validator(); $validator->validate($limit, self::$internalValidationSchema['properties']['limit']); @@ -82,7 +82,7 @@ public function withLimit(int $limit) : self /** * @return self */ - public function withoutLimit() : self + public function withoutLimit(): self { $clone = clone $this; $clone->limit = 10000; @@ -94,7 +94,7 @@ public function withoutLimit() : self * @param int $skip * @return self */ - public function withSkip(int $skip) : self + public function withSkip(int $skip): self { $validator = new \JsonSchema\Validator(); $validator->validate($skip, self::$internalValidationSchema['properties']['skip']); @@ -111,7 +111,7 @@ public function withSkip(int $skip) : self /** * @return self */ - public function withoutSkip() : self + public function withoutSkip(): self { $clone = clone $this; $clone->skip = 0; @@ -127,7 +127,7 @@ public function withoutSkip() : self * @return Foo Created instance * @throws \InvalidArgumentException */ - public static function buildFromInput(array|object $input, bool $validate = true) : Foo + public static function buildFromInput(array|object $input, bool $validate = true): Foo { $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; if ($validate) { @@ -154,7 +154,7 @@ public static function buildFromInput(array|object $input, bool $validate = true * * @return array Converted array */ - public function toJson() : array + public function toJson(): array { $output = []; if (isset($this->limit)) { @@ -175,7 +175,7 @@ public function toJson() : array * @return bool Validation result * @throws \InvalidArgumentException */ - public static function validateInput(array|object $input, bool $return = false) : bool + public static function validateInput(array|object $input, bool $return = false): bool { $validator = new \JsonSchema\Validator(); $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; diff --git a/tests/Generator/Fixtures/DefaultValueAsOptional/Output/Foo.php b/tests/Generator/Fixtures/DefaultValueAsOptional/Output/Foo.php index 01821cc1..97b6ac9d 100644 --- a/tests/Generator/Fixtures/DefaultValueAsOptional/Output/Foo.php +++ b/tests/Generator/Fixtures/DefaultValueAsOptional/Output/Foo.php @@ -48,7 +48,7 @@ public function __construct() /** * @return int */ - public function getLimit() : int + public function getLimit(): int { return $this->limit; } @@ -56,7 +56,7 @@ public function getLimit() : int /** * @return int */ - public function getSkip() : int + public function getSkip(): int { return $this->skip; } @@ -65,7 +65,7 @@ public function getSkip() : int * @param int $limit * @return self */ - public function withLimit(int $limit) : self + public function withLimit(int $limit): self { $validator = new \JsonSchema\Validator(); $validator->validate($limit, self::$internalValidationSchema['properties']['limit']); @@ -83,7 +83,7 @@ public function withLimit(int $limit) : self * @param int $skip * @return self */ - public function withSkip(int $skip) : self + public function withSkip(int $skip): self { $validator = new \JsonSchema\Validator(); $validator->validate($skip, self::$internalValidationSchema['properties']['skip']); @@ -105,7 +105,7 @@ public function withSkip(int $skip) : self * @return Foo Created instance * @throws \InvalidArgumentException */ - public static function buildFromInput(array|object $input, bool $validate = true) : Foo + public static function buildFromInput(array|object $input, bool $validate = true): Foo { $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; if ($validate) { @@ -132,7 +132,7 @@ public static function buildFromInput(array|object $input, bool $validate = true * * @return array Converted array */ - public function toJson() : array + public function toJson(): array { $output = []; $output['limit'] = $this->limit; @@ -149,7 +149,7 @@ public function toJson() : array * @return bool Validation result * @throws \InvalidArgumentException */ - public static function validateInput(array|object $input, bool $return = false) : bool + public static function validateInput(array|object $input, bool $return = false): bool { $validator = new \JsonSchema\Validator(); $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; diff --git a/tests/Generator/Fixtures/DuplicateWithDifferentCasing/Output/Foo.php b/tests/Generator/Fixtures/DuplicateWithDifferentCasing/Output/Foo.php index a2b2603d..e61302f5 100644 --- a/tests/Generator/Fixtures/DuplicateWithDifferentCasing/Output/Foo.php +++ b/tests/Generator/Fixtures/DuplicateWithDifferentCasing/Output/Foo.php @@ -58,7 +58,7 @@ public function __construct(string $fooBar) /** * @return string */ - public function getFooBar() : string + public function getFooBar(): string { return $this->fooBar; } @@ -67,7 +67,7 @@ public function getFooBar() : string * @return string|null * @deprecated */ - public function getBar() : ?string + public function getBar(): ?string { return $this->bar ?? null; } @@ -76,7 +76,7 @@ public function getBar() : ?string * @param string $fooBar * @return self */ - public function withFooBar(string $fooBar) : self + public function withFooBar(string $fooBar): self { $validator = new \JsonSchema\Validator(); $validator->validate($fooBar, self::$internalValidationSchema['properties']['fooBar']); @@ -95,7 +95,7 @@ public function withFooBar(string $fooBar) : self * @return self * @deprecated */ - public function withBar(string $bar) : self + public function withBar(string $bar): self { $validator = new \JsonSchema\Validator(); $validator->validate($bar, self::$internalValidationSchema['properties']['bar']); @@ -112,7 +112,7 @@ public function withBar(string $bar) : self /** * @return self */ - public function withoutBar() : self + public function withoutBar(): self { $clone = clone $this; unset($clone->bar); @@ -128,7 +128,7 @@ public function withoutBar() : self * @return Foo Created instance * @throws \InvalidArgumentException */ - public static function buildFromInput(array|object $input, bool $validate = true) : Foo + public static function buildFromInput(array|object $input, bool $validate = true): Foo { $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; if ($validate) { @@ -156,7 +156,7 @@ public static function buildFromInput(array|object $input, bool $validate = true * * @return array Converted array */ - public function toJson() : array + public function toJson(): array { $output = []; if (isset($this->foobar)) { @@ -178,7 +178,7 @@ public function toJson() : array * @return bool Validation result * @throws \InvalidArgumentException */ - public static function validateInput(array|object $input, bool $return = false) : bool + public static function validateInput(array|object $input, bool $return = false): bool { $validator = new \JsonSchema\Validator(); $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; diff --git a/tests/Generator/Fixtures/JsonFile/Output/Foo.php b/tests/Generator/Fixtures/JsonFile/Output/Foo.php index f4a03dc2..e6fc4090 100644 --- a/tests/Generator/Fixtures/JsonFile/Output/Foo.php +++ b/tests/Generator/Fixtures/JsonFile/Output/Foo.php @@ -43,7 +43,7 @@ public function __construct(int $id) /** * @return int */ - public function getId() : int + public function getId(): int { return $this->id; } @@ -52,7 +52,7 @@ public function getId() : int * @param int $id * @return self */ - public function withId(int $id) : self + public function withId(int $id): self { $validator = new \JsonSchema\Validator(); $validator->validate($id, self::$internalValidationSchema['properties']['id']); @@ -74,7 +74,7 @@ public function withId(int $id) : self * @return Foo Created instance * @throws \InvalidArgumentException */ - public static function buildFromInput(array|object $input, bool $validate = true) : Foo + public static function buildFromInput(array|object $input, bool $validate = true): Foo { $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; if ($validate) { @@ -93,7 +93,7 @@ public static function buildFromInput(array|object $input, bool $validate = true * * @return array Converted array */ - public function toJson() : array + public function toJson(): array { $output = []; $output['id'] = $this->id; @@ -109,7 +109,7 @@ public function toJson() : array * @return bool Validation result * @throws \InvalidArgumentException */ - public static function validateInput(array|object $input, bool $return = false) : bool + public static function validateInput(array|object $input, bool $return = false): bool { $validator = new \JsonSchema\Validator(); $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; diff --git a/tests/Generator/Fixtures/RefList/Output/Foo.php b/tests/Generator/Fixtures/RefList/Output/Foo.php index 5f6426ab..c5444fd1 100644 --- a/tests/Generator/Fixtures/RefList/Output/Foo.php +++ b/tests/Generator/Fixtures/RefList/Output/Foo.php @@ -40,7 +40,7 @@ public function __construct() /** * @return \Helmich\Schema2Class\Example\CustomerAddress[]|null */ - public function getFoo() : ?array + public function getFoo(): ?array { return $this->foo ?? null; } @@ -49,7 +49,7 @@ public function getFoo() : ?array * @param \Helmich\Schema2Class\Example\CustomerAddress[] $foo * @return self */ - public function withFoo(array $foo) : self + public function withFoo(array $foo): self { $clone = clone $this; $clone->foo = $foo; @@ -60,7 +60,7 @@ public function withFoo(array $foo) : self /** * @return self */ - public function withoutFoo() : self + public function withoutFoo(): self { $clone = clone $this; unset($clone->foo); @@ -76,7 +76,7 @@ public function withoutFoo() : self * @return Foo Created instance * @throws \InvalidArgumentException */ - public static function buildFromInput(array|object $input, bool $validate = true) : Foo + public static function buildFromInput(array|object $input, bool $validate = true): Foo { $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; if ($validate) { @@ -98,7 +98,7 @@ public static function buildFromInput(array|object $input, bool $validate = true * * @return array Converted array */ - public function toJson() : array + public function toJson(): array { $output = []; if (isset($this->foo)) { @@ -116,7 +116,7 @@ public function toJson() : array * @return bool Validation result * @throws \InvalidArgumentException */ - public static function validateInput(array|object $input, bool $return = false) : bool + public static function validateInput(array|object $input, bool $return = false): bool { $validator = new \JsonSchema\Validator(); $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; diff --git a/tests/Generator/Fixtures/ReservedNames/Output/Foo.php b/tests/Generator/Fixtures/ReservedNames/Output/Foo.php index f891fd10..f50c3a9e 100644 --- a/tests/Generator/Fixtures/ReservedNames/Output/Foo.php +++ b/tests/Generator/Fixtures/ReservedNames/Output/Foo.php @@ -46,7 +46,7 @@ public function __construct(string $schema) /** * @return string|null */ - public function getFoo() : ?string + public function getFoo(): ?string { return $this->foo ?? null; } @@ -54,7 +54,7 @@ public function getFoo() : ?string /** * @return string */ - public function getSchema() : string + public function getSchema(): string { return $this->schema; } @@ -63,7 +63,7 @@ public function getSchema() : string * @param string $foo * @return self */ - public function withFoo(string $foo) : self + public function withFoo(string $foo): self { $validator = new \JsonSchema\Validator(); $validator->validate($foo, self::$internalValidationSchema['properties']['foo']); @@ -80,7 +80,7 @@ public function withFoo(string $foo) : self /** * @return self */ - public function withoutFoo() : self + public function withoutFoo(): self { $clone = clone $this; unset($clone->foo); @@ -92,7 +92,7 @@ public function withoutFoo() : self * @param string $schema * @return self */ - public function withSchema(string $schema) : self + public function withSchema(string $schema): self { $validator = new \JsonSchema\Validator(); $validator->validate($schema, self::$internalValidationSchema['properties']['schema']); @@ -114,7 +114,7 @@ public function withSchema(string $schema) : self * @return Foo Created instance * @throws \InvalidArgumentException */ - public static function buildFromInput(array|object $input, bool $validate = true) : Foo + public static function buildFromInput(array|object $input, bool $validate = true): Foo { $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; if ($validate) { @@ -137,7 +137,7 @@ public static function buildFromInput(array|object $input, bool $validate = true * * @return array Converted array */ - public function toJson() : array + public function toJson(): array { $output = []; if (isset($this->foo)) { @@ -156,7 +156,7 @@ public function toJson() : array * @return bool Validation result * @throws \InvalidArgumentException */ - public static function validateInput(array|object $input, bool $return = false) : bool + public static function validateInput(array|object $input, bool $return = false): bool { $validator = new \JsonSchema\Validator(); $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; diff --git a/tests/Generator/Fixtures/SpecialCharacterNames/Output/Foo.php b/tests/Generator/Fixtures/SpecialCharacterNames/Output/Foo.php index c4b4954b..38c89fc5 100644 --- a/tests/Generator/Fixtures/SpecialCharacterNames/Output/Foo.php +++ b/tests/Generator/Fixtures/SpecialCharacterNames/Output/Foo.php @@ -38,7 +38,7 @@ public function __construct(string $fooBar) /** * @return string */ - public function getFooBar() : string + public function getFooBar(): string { return $this->fooBar; } @@ -47,7 +47,7 @@ public function getFooBar() : string * @param string $fooBar * @return self */ - public function withFooBar(string $fooBar) : self + public function withFooBar(string $fooBar): self { $validator = new \JsonSchema\Validator(); $validator->validate($fooBar, self::$internalValidationSchema['properties']['foo:bar']); @@ -69,7 +69,7 @@ public function withFooBar(string $fooBar) : self * @return Foo Created instance * @throws \InvalidArgumentException */ - public static function buildFromInput(array|object $input, bool $validate = true) : Foo + public static function buildFromInput(array|object $input, bool $validate = true): Foo { $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; if ($validate) { @@ -88,7 +88,7 @@ public static function buildFromInput(array|object $input, bool $validate = true * * @return array Converted array */ - public function toJson() : array + public function toJson(): array { $output = []; $output['foo:bar'] = $this->fooBar; @@ -104,7 +104,7 @@ public function toJson() : array * @return bool Validation result * @throws \InvalidArgumentException */ - public static function validateInput(array|object $input, bool $return = false) : bool + public static function validateInput(array|object $input, bool $return = false): bool { $validator = new \JsonSchema\Validator(); $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; diff --git a/tests/Generator/Fixtures/UnionCollapsing/Output/Foo.php b/tests/Generator/Fixtures/UnionCollapsing/Output/Foo.php index 7876c7ec..bb93a307 100644 --- a/tests/Generator/Fixtures/UnionCollapsing/Output/Foo.php +++ b/tests/Generator/Fixtures/UnionCollapsing/Output/Foo.php @@ -47,7 +47,7 @@ public function __construct(string $foo) /** * @return string */ - public function getFoo() : string + public function getFoo(): string { return $this->foo; } @@ -56,7 +56,7 @@ public function getFoo() : string * @param string $foo * @return self */ - public function withFoo(string $foo) : self + public function withFoo(string $foo): self { $clone = clone $this; $clone->foo = $foo; @@ -72,7 +72,7 @@ public function withFoo(string $foo) : self * @return Foo Created instance * @throws \InvalidArgumentException */ - public static function buildFromInput(array|object $input, bool $validate = true) : Foo + public static function buildFromInput(array|object $input, bool $validate = true): Foo { $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; if ($validate) { @@ -94,7 +94,7 @@ public static function buildFromInput(array|object $input, bool $validate = true * * @return array Converted array */ - public function toJson() : array + public function toJson(): array { $output = []; $output['foo'] = match (true) { @@ -112,7 +112,7 @@ public function toJson() : array * @return bool Validation result * @throws \InvalidArgumentException */ - public static function validateInput(array|object $input, bool $return = false) : bool + public static function validateInput(array|object $input, bool $return = false): bool { $validator = new \JsonSchema\Validator(); $input = is_array($input) ? \JsonSchema\Validator::arrayToObjectRecursive($input) : $input; From 89485e4cd47d25fc56b59fb7cf30af32c5d3653d Mon Sep 17 00:00:00 2001 From: Martin Helmich Date: Fri, 16 Jan 2026 12:19:14 +0100 Subject: [PATCH 2/6] fix(ci): adjust workflow configuration --- .github/workflows/php.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 03d2c8ea..0bed1c53 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -9,7 +9,7 @@ jobs: build: strategy: matrix: - php-versions: ['8.2', '8.3'] + php-versions: ['8.5'] symfony-versions: ['~5.4', '~6.4', '~7.0'] runs-on: ubuntu-latest @@ -40,7 +40,7 @@ jobs: # Unit tests need to run before the type checker, because they generate code # that the type checker needs to see. - name: Run type checker (psalm) - run: ./vendor/bin/psalm --php-version=8.2 + run: ./vendor/bin/psalm --php-version=8.5 - name: Run type checker (PHPStan) run: ./vendor/bin/phpstan analyze --memory-limit=1G From cf9aee28176c7e232dc3e0cb6ff3525963fa5126 Mon Sep 17 00:00:00 2001 From: Martin Helmich Date: Fri, 16 Jan 2026 12:20:19 +0100 Subject: [PATCH 3/6] chore: drop symfony 5 support --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 0bed1c53..75fc2358 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: php-versions: ['8.5'] - symfony-versions: ['~5.4', '~6.4', '~7.0'] + symfony-versions: ['~6.4', '~7.0'] runs-on: ubuntu-latest steps: From 7ea63707560e8c5b1d85282c0d48ba3202c39c74 Mon Sep 17 00:00:00 2001 From: Martin Helmich Date: Fri, 16 Jan 2026 12:26:28 +0100 Subject: [PATCH 4/6] cleanup: drop forked PropertyGenerator --- src/Codegen/PropertyGenerator.php | 261 ------------------------------ src/Generator/Generator.php | 5 +- src/Generator/SchemaToClass.php | 5 +- 3 files changed, 6 insertions(+), 265 deletions(-) delete mode 100644 src/Codegen/PropertyGenerator.php diff --git a/src/Codegen/PropertyGenerator.php b/src/Codegen/PropertyGenerator.php deleted file mode 100644 index 5feb17dc..00000000 --- a/src/Codegen/PropertyGenerator.php +++ /dev/null @@ -1,261 +0,0 @@ -hasType()) { - $property->setTypeHint((string)$reflectionProperty->getType()); - } - - return $property; - } - - /** - * Generate from array - * - * @configkey name string [required] Class Name - * @configkey const bool - * @configkey defaultvalue null|bool|string|int|float|array|ValueGenerator - * @configkey flags int - * @configkey abstract bool - * @configkey final bool - * @configkey static bool - * @configkey visibility string - * @configkey omitdefaultvalue bool - * - * @throws Exception\InvalidArgumentException - * @param array $array - * @psalm-suppress LessSpecificImplementedReturnType - * @return \Laminas\Code\Generator\PropertyGenerator - */ - public static function fromArray(array $array): ZendPropertyGenerator - { - if (! isset($array['name'])) { - throw new Exception\InvalidArgumentException( - 'Property generator requires that a name is provided for this object' - ); - } - - $property = new self($array['name']); - foreach ($array as $name => $value) { - /** @var string $normalizedKey */ - $normalizedKey = str_replace(['.', '-', '_'], '', $name); - switch (strtolower($normalizedKey)) { - case 'const': - $property->setConst($value); - break; - case 'defaultvalue': - $property->setDefaultValue($value); - break; - case 'docblock': - $docBlock = $value instanceof DocBlockGenerator ? $value : DocBlockGenerator::fromArray($value); - $property->setDocBlock($docBlock); - break; - case 'flags': - $property->setFlags($value); - break; - case 'abstract': - $property->setAbstract($value); - break; - case 'final': - $property->setFinal($value); - break; - case 'static': - $property->setStatic($value); - break; - case 'visibility': - $property->setVisibility($value); - break; - case 'omitdefaultvalue': - $property->omitDefaultValue($value); - break; - } - } - - return $property; - } - - /** - * @param string $name - * @param PropertyValueGenerator|string|array $defaultValue - * @param int $flags - */ - public function __construct($name = null, $defaultValue = null, $flags = self::FLAG_PUBLIC) - { - if (null !== $name) { - $this->setName($name); - } - - if (null !== $defaultValue) { - $this->setDefaultValue($defaultValue); - } - if ($flags !== self::FLAG_PUBLIC) { - $this->setFlags($flags); - } - } - - /** - * @param bool $const - * @return PropertyGenerator - */ - public function setConst($const) - { - if ($const) { - $this->setFlags(self::FLAG_CONSTANT); - } else { - $this->removeFlag(self::FLAG_CONSTANT); - } - - return $this; - } - - /** - * @return bool - */ - public function isConst() - { - return (bool) ($this->flags & self::FLAG_CONSTANT); - } - - /** - * @param PropertyValueGenerator|mixed $defaultValue - * @param string $defaultValueType - * @param PropertyValueGenerator::OUTPUT_* $defaultValueOutputMode - * - * @psalm-suppress LessSpecificImplementedReturnType - * @psalm-suppress MoreSpecificImplementedParamType - * @return \Laminas\Code\Generator\PropertyGenerator - */ - public function setDefaultValue( - $defaultValue, - $defaultValueType = PropertyValueGenerator::TYPE_AUTO, - $defaultValueOutputMode = PropertyValueGenerator::OUTPUT_MULTIPLE_LINE - ): ZendPropertyGenerator { - if (! $defaultValue instanceof PropertyValueGenerator) { - $defaultValue = new PropertyValueGenerator($defaultValue, $defaultValueType, $defaultValueOutputMode); - } - - $this->defaultValue = $defaultValue; - - return $this; - } - - /** - * @param string $typeHint - */ - public function setTypeHint(string $typeHint): void - { - $this->typeHint = $typeHint; - } - - /** - * @return PropertyValueGenerator|null - * @psalm-suppress ImplementedReturnTypeMismatch Impossible to fix -- the annotations from the parent lib are just garbage - */ - public function getDefaultValue(): ?PropertyValueGenerator - { - return $this->defaultValue; - } - - /** - * @throws Exception\RuntimeException - * @psalm-return non-empty-string - * @return string - */ - public function generate(): string - { - $name = $this->getName(); - $defaultValue = $this->getDefaultValue(); - - $output = ''; - - if (($docBlock = $this->getDocBlock()) !== null) { - $docBlock->setIndentation(' '); - $output .= $docBlock->generate(); - } - - if ($this->isConst()) { - if ($defaultValue !== null && ! $defaultValue->isValidConstantType()) { - throw new Exception\RuntimeException(sprintf( - 'The property %s is said to be ' - . 'constant but does not have a valid constant value.', - $this->name - )); - } - $output .= $this->indentation . $this->getVisibility() . ' const ' . $name . ' = ' - . ($defaultValue !== null ? $defaultValue->generate() : 'null;'); - - return $output; - } - - $output .= $this->indentation - . $this->getVisibility() - . ($this->isStatic() ? ' static' : '') - . ($this->typeHint !== null ? ' ' . $this->typeHint : '') - . ' $' . $name; - - if ($this->omitDefaultValue) { - return $output . ';'; - } - - return $output . ' = ' . ($defaultValue !== null ? $defaultValue->generate() : 'null;'); - } - - /** - * @param bool $omit - * @return PropertyGenerator - */ - public function omitDefaultValue(bool $omit = true): self - { - $this->omitDefaultValue = $omit; - - return $this; - } -} diff --git a/src/Generator/Generator.php b/src/Generator/Generator.php index 963022df..48438c4f 100644 --- a/src/Generator/Generator.php +++ b/src/Generator/Generator.php @@ -4,7 +4,6 @@ namespace Helmich\Schema2Class\Generator; -use Helmich\Schema2Class\Codegen\PropertyGenerator; use Helmich\Schema2Class\Generator\Property\CodeFormatting; use Helmich\Schema2Class\Generator\Property\DefaultPropertyDecorator; use Helmich\Schema2Class\Generator\Property\OptionalPropertyDecorator; @@ -19,6 +18,8 @@ use Laminas\Code\Generator\DocBlockGenerator; use Laminas\Code\Generator\MethodGenerator; use Laminas\Code\Generator\ParameterGenerator; +use Laminas\Code\Generator\PropertyGenerator; +use Laminas\Code\Generator\TypeGenerator; class Generator { @@ -71,7 +72,7 @@ public function generateProperties(PropertyCollection $properties): array $typeHint = $property->typeHint($this->generatorRequest->getTargetPHPVersion()); if ($this->generatorRequest->isAtLeastPHP("7.4") && $typeHint !== null) { - $prop->setTypeHint($typeHint); + $prop->setType(TypeGenerator::fromTypeString($typeHint)); } if (!$isOptional) { diff --git a/src/Generator/SchemaToClass.php b/src/Generator/SchemaToClass.php index 9b4e9023..91851cff 100644 --- a/src/Generator/SchemaToClass.php +++ b/src/Generator/SchemaToClass.php @@ -3,7 +3,6 @@ namespace Helmich\Schema2Class\Generator; -use Helmich\Schema2Class\Codegen\PropertyGenerator; use Helmich\Schema2Class\Generator\Property\IntersectProperty; use Helmich\Schema2Class\Generator\Property\NestedObjectProperty; use Helmich\Schema2Class\Generator\Property\PropertyCollection; @@ -15,6 +14,8 @@ use Laminas\Code\Generator\DocBlockGenerator; use Laminas\Code\Generator\EnumGenerator\EnumGenerator; use Laminas\Code\Generator\FileGenerator; +use Laminas\Code\Generator\PropertyGenerator; +use Laminas\Code\Generator\TypeGenerator; use Symfony\Component\Console\Output\OutputInterface; class SchemaToClass @@ -60,7 +61,7 @@ public function schemaToClass(GeneratorRequest $req): void )); if ($req->isAtLeastPHP("7.4")) { - $schemaProperty->setTypeHint("array"); + $schemaProperty->setType(TypeGenerator::fromTypeString("array")); } $properties = [$schemaProperty]; From 2a61291bb6164112e170952669617eb2e393048e Mon Sep 17 00:00:00 2001 From: Martin Helmich Date: Fri, 16 Jan 2026 12:28:03 +0100 Subject: [PATCH 5/6] chore: bump Symfony to 7.4 --- .github/workflows/php.yml | 2 +- composer.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 75fc2358..23261d83 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: php-versions: ['8.5'] - symfony-versions: ['~6.4', '~7.0'] + symfony-versions: ['~6.4', '~7.4'] runs-on: ubuntu-latest steps: diff --git a/composer.json b/composer.json index 4da02fec..22a657fb 100644 --- a/composer.json +++ b/composer.json @@ -11,8 +11,8 @@ ], "require": { "php": "^8.5", - "symfony/console": "~3.0|~4.0|~5.0|~6.0|~7.0", - "symfony/yaml": "~3.0|~4.0|~5.0|~6.0|~7.0", + "symfony/console": "~6.4|~7.4", + "symfony/yaml": "~6.4|~7.4", "justinrainbow/json-schema": "^6.0", "ext-json": "*", "composer/semver": "^3.0", From d85e864c5fe71331fff1651e1d15028a82ad0a01 Mon Sep 17 00:00:00 2001 From: Martin Helmich Date: Fri, 16 Jan 2026 13:12:55 +0100 Subject: [PATCH 6/6] feat: use new PHP 8.5-style cloning when possible --- src/Generator/Generator.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Generator/Generator.php b/src/Generator/Generator.php index 48438c4f..c80c2c3f 100644 --- a/src/Generator/Generator.php +++ b/src/Generator/Generator.php @@ -376,14 +376,23 @@ public function generateSetterMethod(PropertyInterface $property): MethodGenerat $docBlock = new DocBlockGenerator(null, null, $tags); $docBlock->setWordWrap(false); + $body = $setterValidation . "\$clone = clone \$this; +\$clone->$name = \$$name; + +return \$clone;"; + + if ($this->generatorRequest->isAtLeastPHP("8.5")) { + $body = $setterValidation . "return clone(\$this, [ + '$name' => \$$name, +]); +\$clone->$name = \$$name;"; + } + $setMethod = new MethodGenerator( 'with' . $camelCaseName, [new ParameterGenerator($name, $typeHint)], MethodGenerator::FLAG_PUBLIC, - $setterValidation . "\$clone = clone \$this; -\$clone->$name = \$$name; - -return \$clone;", + $body, $docBlock );