diff --git a/CHANGELOG.md b/CHANGELOG.md index b7704a68..76bc5a78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * Generated classes include scalar typehints and return type * Nullable typehints are generated instead of a default `null` value -* A fluent interface isn't generated anymore +* A fluent interface isn't generated anymore by default unless you set the `fluentMutatorMethods` config flag to `true` * Useless PHPDoc (`@param` and `@return` annotations when a typehint exist and mutator description) isn't generated anymore * `DateTimeInterface` is used instead of `DateTime` when possible * Add the ability to not generate mutator methods (useful when generating public properties) diff --git a/composer.json b/composer.json index 7f4f0bb1..49fbdd92 100644 --- a/composer.json +++ b/composer.json @@ -31,8 +31,8 @@ }, "require-dev": { "doctrine/orm": "^2.2", - "symfony/validator": "^2.7 || ^3.0 || ^4.0", - "symfony/filesystem": "^2.7 || ^3.0 || ^4.0" + "symfony/filesystem": "^2.7 || ^3.0 || ^4.0", + "symfony/validator": "^2.7 || ^3.0 || ^4.0" }, "suggest": { "doctrine/collections": "For Doctrine collections", diff --git a/src/AnnotationGenerator/PhpDocAnnotationGenerator.php b/src/AnnotationGenerator/PhpDocAnnotationGenerator.php index 1ed1ac18..0523c7d2 100644 --- a/src/AnnotationGenerator/PhpDocAnnotationGenerator.php +++ b/src/AnnotationGenerator/PhpDocAnnotationGenerator.php @@ -113,7 +113,7 @@ public function generateSetterAnnotations(string $className, string $fieldName): */ public function generateAdderAnnotations(string $className, string $fieldName): array { - if (!$this->isDocUseful($className, $fieldName)) { + if (!$this->isDocUseful($className, $fieldName, true)) { return []; } @@ -125,16 +125,16 @@ public function generateAdderAnnotations(string $className, string $fieldName): */ public function generateRemoverAnnotations(string $className, string $fieldName): array { - if (!$this->isDocUseful($className, $fieldName)) { + if (!$this->isDocUseful($className, $fieldName, true)) { return []; } return [sprintf('@param %s $%s', $this->toPhpType($this->classes[$className]['fields'][$fieldName], true), $fieldName)]; } - private function isDocUseful(string $className, string $fieldName): bool + private function isDocUseful(string $className, string $fieldName, $adderOrRemover = false): bool { - $typeHint = $this->classes[$className]['fields'][$fieldName]['typeHint'] ?? false; + $typeHint = $this->classes[$className]['fields'][$fieldName][$adderOrRemover ? 'adderRemoverTypeHint' : 'typeHint'] ?? false; return false === $typeHint || 'array' === $typeHint; } diff --git a/src/TypesGenerator.php b/src/TypesGenerator.php index 440546d1..6f5ff3b8 100644 --- a/src/TypesGenerator.php +++ b/src/TypesGenerator.php @@ -264,6 +264,10 @@ public function generate(array $config): void foreach ($class['fields'] as &$field) { $field['isEnum'] = isset($classes[$field['range']]) && $classes[$field['range']]['isEnum']; $field['typeHint'] = $this->fieldToTypeHint($field, $classes) ?? false; + + if ('array' === $field['typeHint']) { + $field['adderRemoverTypeHint'] = $this->fieldToAdderRemoverTypeHint($field, $classes) ?? false; + } } } @@ -404,7 +408,7 @@ public function generate(array $config): void } } - if (!empty($interfaceMappings) && $config['doctrine']['resolveTargetEntityConfigPath']) { + if (!$interfaceMappings && $config['doctrine']['resolveTargetEntityConfigPath']) { $file = $config['output'].'/'.$config['doctrine']['resolveTargetEntityConfigPath']; $dir = dirname($file); if (!file_exists($dir)) { @@ -511,14 +515,19 @@ private function isDatatype(string $type): bool private function fieldToTypeHint(array $field, array $classes): ?string { - if ($field['isEnum']) { - return null; - } - if ($field['isArray']) { return 'array'; } + return $this->fieldToAdderRemoverTypeHint($field, $classes); + } + + private function fieldToAdderRemoverTypeHint(array $field, array $classes): ?string + { + if ($field['isEnum']) { + return 'string'; + } + switch ($field['range']) { case 'Boolean': return 'bool'; diff --git a/src/TypesGeneratorConfiguration.php b/src/TypesGeneratorConfiguration.php index 88a0a530..b38b8591 100644 --- a/src/TypesGeneratorConfiguration.php +++ b/src/TypesGeneratorConfiguration.php @@ -101,6 +101,7 @@ function ($rdfa) { ->scalarNode('author')->defaultFalse()->info('The value of the phpDoc\'s @author annotation')->example('Kévin Dunglas ')->end() ->enumNode('fieldVisibility')->values(['private', 'protected', 'public'])->defaultValue('private')->cannotBeEmpty()->info('Visibility of entities fields')->end() ->booleanNode('mutatorMethods')->defaultTrue()->info('Set this flag to false to not generate getter, setter, adder and remover methods')->end() + ->booleanNode('fluentMutatorMethods')->defaultFalse()->info('Set this flag to true to generate fluent setter, adder and remover methods')->end() ->arrayNode('types') ->beforeNormalization() ->always() diff --git a/templates/class.php.twig b/templates/class.php.twig index 552e58a7..a1af9e01 100644 --- a/templates/class.php.twig +++ b/templates/class.php.twig @@ -67,9 +67,13 @@ use {{ use }}; * {{ annotation }} {% endfor %} */ - public function add{{ field.name|ucfirst }}({% if field.typeHint and not field.isEnum %}{{ field.typeHint }} {% endif %}${{ field.name }}): void + public function add{{ field.name|ucfirst }}({% if field.typeHint and not field.isEnum %}{{ field.adderRemoverTypeHint }} {% endif %}${{ field.name }}): {% if config.fluentMutatorMethods %}self{% else %}void{% endif %} { $this->{{ field.name }}[] = {% if field.isEnum %}(string) {% endif %}${{ field.name }}; +{% if config.fluentMutatorMethods %} + + return $this; +{% endif %} } /** @@ -77,7 +81,7 @@ use {{ use }}; * {{ annotation }} {% endfor %} */ - public function remove{{ field.name|ucfirst }}({% if field.typeHint %}{{ field.typeHint }} {% endif %}${{ field.name }}): void + public function remove{{ field.name|ucfirst }}({% if field.typeHint %}{{ field.adderRemoverTypeHint }} {% endif %}${{ field.name }}): {% if config.fluentMutatorMethods %}self{% else %}void{% endif %} { {% if config.doctrine.useCollection and field.isArray and field.typeHint and not field.isEnum %} $this->{{ field.name }}->removeElement({% if field.isEnum %}(string) {% endif %}${{ field.name }}); @@ -86,6 +90,10 @@ use {{ use }}; if (false !== $key) { unset($this->{{ field.name }}[$key]); } +{% endif %} +{% if config.fluentMutatorMethods %} + + return $this; {% endif %} } {% else %} @@ -94,9 +102,14 @@ use {{ use }}; * {{ annotation }} {% endfor %} */ - public function set{{ field.name|ucfirst }}({% if field.typeHint %}{% if field.isNullable %}?{% endif %}{{ field.typeHint }} {% endif %}${{ field.name }}): void + public function set{{ field.name|ucfirst }}({% if field.typeHint %}{% if field.isNullable %}?{% endif %}{{ field.typeHint }} {% endif %}${{ field.name }}): {% if config.fluentMutatorMethods %}self{% else %}void{% endif %} { $this->{{ field.name }} = ${{ field.name }}; + +{% if config.fluentMutatorMethods %} + + return $this; +{% endif %} } {% endif %} diff --git a/tests/Command/DumpConfigurationTest.php b/tests/Command/DumpConfigurationTest.php index dc1556ae..e52b4ae7 100644 --- a/tests/Command/DumpConfigurationTest.php +++ b/tests/Command/DumpConfigurationTest.php @@ -98,6 +98,9 @@ interface: AppBundle\Model # Example: Acme\Model # Set this flag to false to not generate getter, setter, adder and remover methods mutatorMethods: true + # Set this flag to true to generate fluent setter, adder and remover methods + fluentMutatorMethods: false + # Schema.org's types to use types: diff --git a/tests/Command/GenerateTypesCommandTest.php b/tests/Command/GenerateTypesCommandTest.php index 7a1242f7..1ef23453 100644 --- a/tests/Command/GenerateTypesCommandTest.php +++ b/tests/Command/GenerateTypesCommandTest.php @@ -56,4 +56,44 @@ public function getArguments() [__DIR__.'/../../build/mongodb/ecommerce/', __DIR__.'/../config/mongodb/ecommerce.yaml'], ]; } + + public function testFluentMutators() + { + $outputDir = __DIR__.'/../../build/fluent-mutators'; + $config = __DIR__.'/../config/fluent-mutators.yaml'; + + $this->fs->mkdir($outputDir); + + $commandTester = new CommandTester(new GenerateTypesCommand()); + $this->assertEquals(0, $commandTester->execute(['output' => $outputDir, 'config' => $config])); + + $organization = file_get_contents($outputDir.'/AppBundle/Entity/Person.php'); + + $this->assertContains(<<<'PHP' + public function setUrl(?string $url): self + { + $this->url = $url; + + return $this; + } +PHP + , $organization); + + $this->assertContains(<<<'PHP' + public function addFriends(Person $friends): self + { + $this->friends[] = $friends; + + return $this; + } + + public function removeFriends(Person $friends): self + { + $this->friends->removeElement($friends); + + return $this; + } +PHP + , $organization); + } } diff --git a/tests/TypesGeneratorTest.php b/tests/TypesGeneratorTest.php index 0bdc24db..242a3de1 100644 --- a/tests/TypesGeneratorTest.php +++ b/tests/TypesGeneratorTest.php @@ -170,6 +170,10 @@ private function getConfig(): array ], 'generateId' => true, 'useInterface' => false, + 'doctrine' => [ + 'useCollection' => true, + 'resolveTargetEntityConfigPath' => null, + ], ]; } diff --git a/tests/config/fluent-mutators.yaml b/tests/config/fluent-mutators.yaml new file mode 100644 index 00000000..cb0ebb7c --- /dev/null +++ b/tests/config/fluent-mutators.yaml @@ -0,0 +1,41 @@ +rdfa: [{ uri: tests/data/schema.rdfa, format: rdfa }] +relations: [tests/data/v1.owl] + +fluentMutatorMethods: true +types: + Person: + properties: + name: ~ + familyName: ~ + givenName: ~ + additionalName: ~ + gender: ~ + address: { range: PostalAddress } + # Custom range and custom ORM\Column content + birthDate: { range: DateTime, ormColumn: 'type="datetimetz", nullable=true, options={"comment" = "Birthdate with timezone."}' } + telephone: ~ + email: ~ + jobTitle: ~ + # Default relation table name would be "person_organization" for all following fields, but we customize them + affiliation: ~ + brand: { relationTableName: "person_brand"} + memberOf: { range: "Organization", cardinality: (1..*), relationTableName: "person_memberof"} + worksFor: { range: "Organization", cardinality: (0..*), relationTableName: "person_worksfor"} + # url field is a custom one without definition, it should render error + url: ~ + friends: { range: "Person", cardinality: (0..*) } + PostalAddress: + # Disable the generation of the class hierarchy for this type + properties: + # Force the type of the addressCountry property to text + addressCountry: { range: "Text" } + addressLocality: ~ + addressRegion: ~ + postOfficeBoxNumber: ~ + postalCode: ~ + streetAddress: ~ + Organization: + properties: + name: ~ + # Custom property with custom ORM\Column content + adminCode: {range: Text, ormColumn: 'type="string", length=3, unique=true, nullable=false, options={"comment" = "A code for central administration."}' }