diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSlim4ServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSlim4ServerCodegen.java index b2b8fd31e76..4ec5c1f1166 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSlim4ServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpSlim4ServerCodegen.java @@ -42,6 +42,8 @@ public class PhpSlim4ServerCodegen extends PhpSlimServerCodegen { protected String psr7Implementation = "slim-psr7"; protected String mockDirName = "Mock"; protected String mockPackage = ""; + protected String utilsDirName = "Utils"; + protected String utilsPackage = ""; public PhpSlim4ServerCodegen() { super(); @@ -51,6 +53,7 @@ public class PhpSlim4ServerCodegen extends PhpSlimServerCodegen { .build(); mockPackage = invokerPackage + "\\" + mockDirName; + utilsPackage = invokerPackage + "\\" + utilsDirName; outputFolder = "generated-code" + File.separator + "slim4"; embeddedTemplateDir = templateDir = "php-slim4-server"; @@ -86,8 +89,9 @@ public class PhpSlim4ServerCodegen extends PhpSlimServerCodegen { super.processOpts(); if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) { - // Update the invokerPackage for the default mockPackage + // Update mockPackage and utilsPackage mockPackage = invokerPackage + "\\" + mockDirName; + utilsPackage = invokerPackage + "\\" + utilsDirName; } // make mock src path available in mustache template @@ -95,6 +99,11 @@ public class PhpSlim4ServerCodegen extends PhpSlimServerCodegen { additionalProperties.put("mockSrcPath", "./" + toSrcPath(mockPackage, srcBasePath)); additionalProperties.put("mockTestPath", "./" + toSrcPath(mockPackage, testBasePath)); + // same for utils package + additionalProperties.put("utilsPackage", utilsPackage); + additionalProperties.put("utilsSrcPath", "./" + toSrcPath(utilsPackage, srcBasePath)); + additionalProperties.put("utilsTestPath", "./" + toSrcPath(utilsPackage, testBasePath)); + if (additionalProperties.containsKey(PSR7_IMPLEMENTATION)) { this.setPsr7Implementation((String) additionalProperties.get(PSR7_IMPLEMENTATION)); } @@ -132,6 +141,12 @@ public class PhpSlim4ServerCodegen extends PhpSlimServerCodegen { supportingFiles.add(new SupportingFile("openapi_data_mocker_interface.mustache", toSrcPath(mockPackage, srcBasePath), toInterfaceName("OpenApiDataMocker") + ".php")); supportingFiles.add(new SupportingFile("openapi_data_mocker.mustache", toSrcPath(mockPackage, srcBasePath), "OpenApiDataMocker.php")); supportingFiles.add(new SupportingFile("openapi_data_mocker_test.mustache", toSrcPath(mockPackage, testBasePath), "OpenApiDataMockerTest.php")); + + // traits of ported utils + supportingFiles.add(new SupportingFile("string_utils_trait.mustache", toSrcPath(utilsPackage, srcBasePath), toTraitName("StringUtils") + ".php")); + supportingFiles.add(new SupportingFile("string_utils_trait_test.mustache", toSrcPath(utilsPackage, testBasePath), toTraitName("StringUtils") + "Test.php")); + supportingFiles.add(new SupportingFile("model_utils_trait.mustache", toSrcPath(utilsPackage, srcBasePath), toTraitName("ModelUtils") + ".php")); + supportingFiles.add(new SupportingFile("model_utils_trait_test.mustache", toSrcPath(utilsPackage, testBasePath), toTraitName("ModelUtils") + "Test.php")); } /** diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/composer.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/composer.mustache index cc4f128875f..68b2c89e92f 100644 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/composer.mustache +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/composer.mustache @@ -43,11 +43,13 @@ "test": [ "@test-apis", "@test-models", - "@test-mock" + "@test-mock", + "@test-utils" ], "test-apis": "phpunit --testsuite Apis", "test-models": "phpunit --testsuite Models", "test-mock": "phpunit --testsuite Mock", + "test-utils": "phpunit --testsuite Utils", "phpcs": "phpcs", "phplint": "phplint ./ --exclude=vendor" } diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/model_utils_trait.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/model_utils_trait.mustache new file mode 100644 index 00000000000..7f068fa1c77 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/model_utils_trait.mustache @@ -0,0 +1,132 @@ + ModelReturn (after camelize) + } + + // model name starts with number + if (preg_match('/^\d.*/', $name) === 1) { + $name = 'model_' . $name; // e.g. 200Response => Model200Response (after camelize) + } + + // add prefix and/or suffic only if name does not start wth \ (e.g. \DateTime) + if (preg_match('/^\\\\.*/', $name) !== 1) { + if (is_string($modelNamePrefix) && !empty($modelNamePrefix)) { + $name = $modelNamePrefix . '_' . $name; + } + + if (is_string($modelNameSuffix) && !empty($modelNameSuffix)) { + $name = $name . '_' . $modelNameSuffix; + } + } + + // camelize the model name + // phone_number => PhoneNumber + return self::camelize($name); + } +} +{{/apiInfo}} diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/model_utils_trait_test.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/model_utils_trait_test.mustache new file mode 100644 index 00000000000..f3d365eba67 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/model_utils_trait_test.mustache @@ -0,0 +1,111 @@ +assertSame($expectedRef, ModelUtils::getSimpleRef($ref)); + } + + public function provideRefs() + { + return [ + 'Reference Object OAS 3.0' => [ + '#/components/schemas/Pet', 'Pet', + ], + 'Reference Object Swagger 2.0' => [ + '#/definitions/Pet', 'Pet', + ], + 'Underscored classname' => [ + '#/components/schemas/_foobar_Objects', '_foobar_Objects', + ], + 'Relative Documents With Embedded Schema' => [ + 'definitions.json#/Pet', null, + ], + 'null as argument' => [ + null, null, + ], + 'number as argument' => [ + 156, null, + ], + ]; + } + + /** + * @covers ::toModelName + * @dataProvider provideModelNames + */ + public function testToModelName($name, $prefix, $suffix, $expectedModel) + { + $this->assertSame($expectedModel, ModelUtils::toModelName($name, $prefix, $suffix)); + } + + public function provideModelNames() + { + return [ + // fixtures from modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/StringUtilsTest.java + ['abcd', null, null, 'Abcd'], + ['some-value', null, null, 'SomeValue'], + ['some_value', null, null, 'SomeValue'], + ['$type', null, null, 'Type'], + ['123', null, null, 'Model123'], + ['$123', null, null, 'Model123'], + ['return', null, null, 'ModelReturn'], + ['200Response', null, null, 'Model200Response'], + ['abcd', 'SuperModel', null, 'SuperModelAbcd'], + ['abcd', null, 'WithEnd', 'AbcdWithEnd'], + ['abcd', 'WithStart', 'AndEnd', 'WithStartAbcdAndEnd'], + ['_foobar_Objects', null, null, 'FoobarObjects'], + [null, null, null, null], + ]; + } +} +{{/apiInfo}} diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/phpunit.xml.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/phpunit.xml.mustache index 81e19e73605..82f86439083 100644 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/phpunit.xml.mustache +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/phpunit.xml.mustache @@ -20,12 +20,16 @@ {{mockTestPath}} + + {{utilsTestPath}} + {{apiSrcPath}} {{modelSrcPath}} {{mockSrcPath}} + {{utilsSrcPath}} diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/string_utils_trait.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/string_utils_trait.mustache new file mode 100644 index 00000000000..6d5d15feff6 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/string_utils_trait.mustache @@ -0,0 +1,141 @@ + 0) { + $str .= strtoupper(substr($z, 0, 1)) . substr($z, 1); + } + } + $word = $str; + + // Uppercase the class name. + $p = '/(\.?)(\w)([^\.]*)$/'; + $word = preg_replace_callback($p, function ($matches) { + $rep = $matches[1] . strtoupper($matches[2]) . $matches[3]; + $rep = preg_replace('/\$/', '\\\$', $rep); + return $rep; + }, $word); + + // Remove all underscores (underscore_case to camelCase) + $p = '/(_)(.)/'; + while (preg_match($p, $word, $matches) === 1) { + $original = $matches[2][0]; + $upperCase = strtoupper($original); + if ($original === $upperCase) { + $word = preg_replace($p, '$2', $word); + } else { + $word = preg_replace($p, $upperCase, $word); + } + } + + // Remove all hyphens (hyphen-case to camelCase) + $p = '/(-)(.)/'; + while (preg_match($p, $word, $matches) === 1) { + $upperCase = strtoupper($matches[2][0]); + $word = preg_replace($p, $upperCase, $word); + } + + if ($lowercaseFirstLetter === true && strlen($word) > 0) { + $i = 0; + $charAt = substr($word, $i, 1); + while ( + $i + 1 < strlen($word) + && !( + ($charAt >= 'a' && $charAt <= 'z') + || ($charAt >= 'A' && $charAt <= 'Z') + ) + ) { + $i++; + $charAt = substr($word, $i, 1); + } + $i++; + $word = strtolower(substr($word, 0, $i)) . substr($word, $i); + } + + // remove all underscore + $word = str_replace('_', '', $word); + + return $word; + } + + /** + * Checks whether string is reserved php keyword. + * This is recreated method of @link modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java class. + * + * @param string $word Checked string + * + * @return bool + */ + public static function isReservedWord($word) + { + if (is_string($word) === false) { + return false; + } + // __halt_compiler is ommited because class names with underscores not allowed anyway + return in_array( + strtolower($word), + ['abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'final', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list', 'namespace', 'new', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var', 'while', 'xor'] + ); + } +} +{{/apiInfo}} diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/string_utils_trait_test.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/string_utils_trait_test.mustache new file mode 100644 index 00000000000..9698d6bcf6c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/string_utils_trait_test.mustache @@ -0,0 +1,108 @@ +assertSame($expectedWord, StringUtils::camelize($word, $lowercaseFirstLetter)); + } + + public function provideWordsForCamelizeTest() + { + return [ + // fixtures from modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/StringUtilsTest.java + ['openApiServer/model/pet', null, 'OpenApiServerModelPet'], + ['abcd', null, 'Abcd'], + ['some-value', null, 'SomeValue'], + ['some-Value', null, 'SomeValue'], + ['some_value', null, 'SomeValue'], + ['some_Value', null, 'SomeValue'], + ['$type', null, '$Type'], + + ['abcd', true, 'abcd'], + ['some-value', true, 'someValue'], + ['some_value', true, 'someValue'], + ['Abcd', true, 'abcd'], + ['$type', true, '$type'], + + ['123', true, '123'], + ['$123', true, '$123'], + ]; + } + + /** + * @covers ::isReservedWord + * @dataProvider provideWordsForIsReservedTest + */ + public function testisReservedWord($word, $expected) + { + $this->assertSame($expected, StringUtils::isReservedWord($word)); + } + + public function provideWordsForIsReservedTest() + { + return [ + ['return', true], + ['switch', true], + ['class', true], + ['interface', true], + ['ABSTRACT', true], + ['Trait', true], + ['final', true], + ['foobar', false], + ['DateTime', false], + ['Pet', false], + [123, false], + [null, false], + ]; + } +} +{{/apiInfo}} diff --git a/samples/server/petstore/php-slim4/composer.json b/samples/server/petstore/php-slim4/composer.json index 4a45af2568d..d7d8142f6d1 100644 --- a/samples/server/petstore/php-slim4/composer.json +++ b/samples/server/petstore/php-slim4/composer.json @@ -30,11 +30,13 @@ "test": [ "@test-apis", "@test-models", - "@test-mock" + "@test-mock", + "@test-utils" ], "test-apis": "phpunit --testsuite Apis", "test-models": "phpunit --testsuite Models", "test-mock": "phpunit --testsuite Mock", + "test-utils": "phpunit --testsuite Utils", "phpcs": "phpcs", "phplint": "phplint ./ --exclude=vendor" } diff --git a/samples/server/petstore/php-slim4/lib/Utils/ModelUtilsTrait.php b/samples/server/petstore/php-slim4/lib/Utils/ModelUtilsTrait.php new file mode 100644 index 00000000000..97bb2ae884a --- /dev/null +++ b/samples/server/petstore/php-slim4/lib/Utils/ModelUtilsTrait.php @@ -0,0 +1,123 @@ + ModelReturn (after camelize) + } + + // model name starts with number + if (preg_match('/^\d.*/', $name) === 1) { + $name = 'model_' . $name; // e.g. 200Response => Model200Response (after camelize) + } + + // add prefix and/or suffic only if name does not start wth \ (e.g. \DateTime) + if (preg_match('/^\\\\.*/', $name) !== 1) { + if (is_string($modelNamePrefix) && !empty($modelNamePrefix)) { + $name = $modelNamePrefix . '_' . $name; + } + + if (is_string($modelNameSuffix) && !empty($modelNameSuffix)) { + $name = $name . '_' . $modelNameSuffix; + } + } + + // camelize the model name + // phone_number => PhoneNumber + return self::camelize($name); + } +} diff --git a/samples/server/petstore/php-slim4/lib/Utils/StringUtilsTrait.php b/samples/server/petstore/php-slim4/lib/Utils/StringUtilsTrait.php new file mode 100644 index 00000000000..bd0464fa598 --- /dev/null +++ b/samples/server/petstore/php-slim4/lib/Utils/StringUtilsTrait.php @@ -0,0 +1,132 @@ + 0) { + $str .= strtoupper(substr($z, 0, 1)) . substr($z, 1); + } + } + $word = $str; + + // Uppercase the class name. + $p = '/(\.?)(\w)([^\.]*)$/'; + $word = preg_replace_callback($p, function ($matches) { + $rep = $matches[1] . strtoupper($matches[2]) . $matches[3]; + $rep = preg_replace('/\$/', '\\\$', $rep); + return $rep; + }, $word); + + // Remove all underscores (underscore_case to camelCase) + $p = '/(_)(.)/'; + while (preg_match($p, $word, $matches) === 1) { + $original = $matches[2][0]; + $upperCase = strtoupper($original); + if ($original === $upperCase) { + $word = preg_replace($p, '$2', $word); + } else { + $word = preg_replace($p, $upperCase, $word); + } + } + + // Remove all hyphens (hyphen-case to camelCase) + $p = '/(-)(.)/'; + while (preg_match($p, $word, $matches) === 1) { + $upperCase = strtoupper($matches[2][0]); + $word = preg_replace($p, $upperCase, $word); + } + + if ($lowercaseFirstLetter === true && strlen($word) > 0) { + $i = 0; + $charAt = substr($word, $i, 1); + while ( + $i + 1 < strlen($word) + && !( + ($charAt >= 'a' && $charAt <= 'z') + || ($charAt >= 'A' && $charAt <= 'Z') + ) + ) { + $i++; + $charAt = substr($word, $i, 1); + } + $i++; + $word = strtolower(substr($word, 0, $i)) . substr($word, $i); + } + + // remove all underscore + $word = str_replace('_', '', $word); + + return $word; + } + + /** + * Checks whether string is reserved php keyword. + * This is recreated method of @link modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java class. + * + * @param string $word Checked string + * + * @return bool + */ + public static function isReservedWord($word) + { + if (is_string($word) === false) { + return false; + } + // __halt_compiler is ommited because class names with underscores not allowed anyway + return in_array( + strtolower($word), + ['abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'final', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list', 'namespace', 'new', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var', 'while', 'xor'] + ); + } +} diff --git a/samples/server/petstore/php-slim4/phpunit.xml.dist b/samples/server/petstore/php-slim4/phpunit.xml.dist index 50cde966d40..d0904268796 100644 --- a/samples/server/petstore/php-slim4/phpunit.xml.dist +++ b/samples/server/petstore/php-slim4/phpunit.xml.dist @@ -20,12 +20,16 @@ ./test/Mock + + ./test/Utils + ./lib/Api ./lib/Model ./lib/Mock + ./lib/Utils diff --git a/samples/server/petstore/php-slim4/test/Utils/ModelUtilsTraitTest.php b/samples/server/petstore/php-slim4/test/Utils/ModelUtilsTraitTest.php new file mode 100644 index 00000000000..9ed05378f2b --- /dev/null +++ b/samples/server/petstore/php-slim4/test/Utils/ModelUtilsTraitTest.php @@ -0,0 +1,102 @@ +assertSame($expectedRef, ModelUtils::getSimpleRef($ref)); + } + + public function provideRefs() + { + return [ + 'Reference Object OAS 3.0' => [ + '#/components/schemas/Pet', 'Pet', + ], + 'Reference Object Swagger 2.0' => [ + '#/definitions/Pet', 'Pet', + ], + 'Underscored classname' => [ + '#/components/schemas/_foobar_Objects', '_foobar_Objects', + ], + 'Relative Documents With Embedded Schema' => [ + 'definitions.json#/Pet', null, + ], + 'null as argument' => [ + null, null, + ], + 'number as argument' => [ + 156, null, + ], + ]; + } + + /** + * @covers ::toModelName + * @dataProvider provideModelNames + */ + public function testToModelName($name, $prefix, $suffix, $expectedModel) + { + $this->assertSame($expectedModel, ModelUtils::toModelName($name, $prefix, $suffix)); + } + + public function provideModelNames() + { + return [ + // fixtures from modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/StringUtilsTest.java + ['abcd', null, null, 'Abcd'], + ['some-value', null, null, 'SomeValue'], + ['some_value', null, null, 'SomeValue'], + ['$type', null, null, 'Type'], + ['123', null, null, 'Model123'], + ['$123', null, null, 'Model123'], + ['return', null, null, 'ModelReturn'], + ['200Response', null, null, 'Model200Response'], + ['abcd', 'SuperModel', null, 'SuperModelAbcd'], + ['abcd', null, 'WithEnd', 'AbcdWithEnd'], + ['abcd', 'WithStart', 'AndEnd', 'WithStartAbcdAndEnd'], + ['_foobar_Objects', null, null, 'FoobarObjects'], + [null, null, null, null], + ]; + } +} diff --git a/samples/server/petstore/php-slim4/test/Utils/StringUtilsTraitTest.php b/samples/server/petstore/php-slim4/test/Utils/StringUtilsTraitTest.php new file mode 100644 index 00000000000..d06aa405eeb --- /dev/null +++ b/samples/server/petstore/php-slim4/test/Utils/StringUtilsTraitTest.php @@ -0,0 +1,99 @@ +assertSame($expectedWord, StringUtils::camelize($word, $lowercaseFirstLetter)); + } + + public function provideWordsForCamelizeTest() + { + return [ + // fixtures from modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/StringUtilsTest.java + ['openApiServer/model/pet', null, 'OpenApiServerModelPet'], + ['abcd', null, 'Abcd'], + ['some-value', null, 'SomeValue'], + ['some-Value', null, 'SomeValue'], + ['some_value', null, 'SomeValue'], + ['some_Value', null, 'SomeValue'], + ['$type', null, '$Type'], + + ['abcd', true, 'abcd'], + ['some-value', true, 'someValue'], + ['some_value', true, 'someValue'], + ['Abcd', true, 'abcd'], + ['$type', true, '$type'], + + ['123', true, '123'], + ['$123', true, '$123'], + ]; + } + + /** + * @covers ::isReservedWord + * @dataProvider provideWordsForIsReservedTest + */ + public function testisReservedWord($word, $expected) + { + $this->assertSame($expected, StringUtils::isReservedWord($word)); + } + + public function provideWordsForIsReservedTest() + { + return [ + ['return', true], + ['switch', true], + ['class', true], + ['interface', true], + ['ABSTRACT', true], + ['Trait', true], + ['final', true], + ['foobar', false], + ['DateTime', false], + ['Pet', false], + [123, false], + [null, false], + ]; + } +}