From 9288a5e87fcbe349cabbc2fdf4eea3e06e96efd3 Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Mon, 10 Jun 2024 19:12:52 +0200 Subject: [PATCH 1/9] [FEATURE] Command to generate XSD Schemas for ViewHelpers --- bin/fluid | 8 +- src/Schema/SchemaGenerator.php | 145 ++++++++++++ src/Schema/ViewHelperFinder.php | 84 +++++++ src/Schema/ViewHelperMetadata.php | 34 +++ src/Schema/ViewHelperMetadataFactory.php | 120 ++++++++++ src/Tools/ConsoleRunner.php | 76 ++++-- tests/Functional/CommandTest.php | 15 ++ .../ViewHelpers/AbstractViewHelper.php | 12 + .../Sub/ArbitraryArgumentsViewHelper.php | 29 +++ .../ViewHelpers/Sub/DeprecatedViewHelper.php | 32 +++ .../WithDocumentationViewHelper.php | 43 ++++ .../ViewHelpers/WithoutClassSuffix.php | 29 +++ .../WithoutDocumentationViewHelper.php | 29 +++ .../WithoutInterfaceViewHelper.php | 12 + .../Fixtures/WrongDirectoryViewHelper.php | 29 +++ tests/Unit/Schema/SchemaGeneratorTest.php | 219 ++++++++++++++++++ .../Schema/ViewHelperMetadataFactoryTest.php | 111 +++++++++ 17 files changed, 1010 insertions(+), 17 deletions(-) create mode 100644 src/Schema/SchemaGenerator.php create mode 100644 src/Schema/ViewHelperFinder.php create mode 100644 src/Schema/ViewHelperMetadata.php create mode 100644 src/Schema/ViewHelperMetadataFactory.php create mode 100644 tests/Unit/Schema/Fixtures/ViewHelpers/AbstractViewHelper.php create mode 100644 tests/Unit/Schema/Fixtures/ViewHelpers/Sub/ArbitraryArgumentsViewHelper.php create mode 100644 tests/Unit/Schema/Fixtures/ViewHelpers/Sub/DeprecatedViewHelper.php create mode 100644 tests/Unit/Schema/Fixtures/ViewHelpers/WithDocumentationViewHelper.php create mode 100644 tests/Unit/Schema/Fixtures/ViewHelpers/WithoutClassSuffix.php create mode 100644 tests/Unit/Schema/Fixtures/ViewHelpers/WithoutDocumentationViewHelper.php create mode 100644 tests/Unit/Schema/Fixtures/ViewHelpers/WithoutInterfaceViewHelper.php create mode 100644 tests/Unit/Schema/Fixtures/WrongDirectoryViewHelper.php create mode 100644 tests/Unit/Schema/SchemaGeneratorTest.php create mode 100644 tests/Unit/Schema/ViewHelperMetadataFactoryTest.php diff --git a/bin/fluid b/bin/fluid index 147e81d7a..9c8426f32 100755 --- a/bin/fluid +++ b/bin/fluid @@ -11,16 +11,16 @@ declare(strict_types=1); use TYPO3Fluid\Fluid\Tools\ConsoleRunner; if (file_exists(__DIR__ . '/../autoload.php')) { - require_once __DIR__ . '/../autoload.php'; + $autoloader = require_once __DIR__ . '/../autoload.php'; } elseif (file_exists(__DIR__ . '/../vendor/autoload.php')) { - require_once __DIR__ . '/../vendor/autoload.php'; + $autoloader = require_once __DIR__ . '/../vendor/autoload.php'; } elseif (file_exists(__DIR__ . '/../../../autoload.php')) { - require_once __DIR__ . '/../../../autoload.php'; + $autoloader = require_once __DIR__ . '/../../../autoload.php'; } $runner = new ConsoleRunner(); try { - echo $runner->handleCommand($argv); + echo $runner->handleCommand($argv, $autoloader); } catch (InvalidArgumentException $error) { echo PHP_EOL . 'ERROR! ' . $error->getMessage() . PHP_EOL . PHP_EOL; } diff --git a/src/Schema/SchemaGenerator.php b/src/Schema/SchemaGenerator.php new file mode 100644 index 000000000..1748fc7e9 --- /dev/null +++ b/src/Schema/SchemaGenerator.php @@ -0,0 +1,145 @@ +createXmlRootElement($xmlNamespace); + foreach ($viewHelpers as $metadata) { + $xsdElement = $file->addChild('xsd:element'); + + $xsdElement->addAttribute('name', $metadata->tagName); + + // Add deprecation information to ViewHelper documentation + $documentation = $metadata->documentation; + if (isset($metadata->docTags['@deprecated'])) { + $documentation .= "\n.. attention::\n**Deprecated** " . $metadata->docTags['@deprecated']; + } + $documentation = trim($documentation); + + // Add documentation to xml + if ($documentation !== '') { + $xsdAnnotation = $xsdElement->addChild('xsd:annotation'); + $xsdDocumentation = $xsdAnnotation->addChild('xsd:documentation'); + $this->appendWithCdata($xsdDocumentation, $documentation); + } + + $xsdComplexType = $xsdElement->addChild('xsd:complexType'); + + // Allow text as well as subelements + $xsdComplexType->addAttribute('mixed', 'true'); + + // Allow a sequence of arbitrary subelements of any type + $xsdSequence = $xsdComplexType->addChild('xsd:sequence'); + $xsdAny = $xsdSequence->addChild('xsd:any'); + $xsdAny->addAttribute('minOccurs', '0'); + + // Add argument definitions to xml + foreach ($metadata->argumentDefinitions as $argumentDefinition) { + $default = $argumentDefinition->getDefaultValue(); + $type = $argumentDefinition->getType(); + + $xsdAttribute = $xsdComplexType->addChild('xsd:attribute'); + $xsdAttribute->addAttribute('type', $this->convertPhpTypeToXsdType($type)); + $xsdAttribute->addAttribute('name', $argumentDefinition->getName()); + if ($argumentDefinition->isRequired()) { + $xsdAttribute->addAttribute('use', 'required'); + } else { + $xsdAttribute->addAttribute('default', $this->encodeFluidVariable($default)); + } + + // Add PHP type to documentation text + // TODO check if there is a better field for this + $documentation = $argumentDefinition->getDescription(); + $documentation .= "\n@type $type"; + $documentation = trim($documentation); + + // Add documentation for argument to xml + $xsdAnnotation = $xsdAttribute->addChild('xsd:annotation'); + $xsdDocumentation = $xsdAnnotation->addChild('xsd:documentation'); + $this->appendWithCdata($xsdDocumentation, $documentation); + } + + if ($metadata->allowsArbitraryArguments) { + $xsdComplexType->addChild('xsd:anyAttribute'); + } + } + + return $file; + } + + private function appendWithCdata(\SimpleXMLElement $parent, string $text): \SimpleXMLElement + { + $parentDomNode = dom_import_simplexml($parent); + $parentDomNode->appendChild($parentDomNode->ownerDocument->createCDATASection($text)); + return simplexml_import_dom($parentDomNode); + } + + private function createXmlRootElement(string $targetNamespace): \SimpleXMLElement + { + return new \SimpleXMLElement( + '', + ); + } + + private function convertPhpTypeToXsdType(string $type): string + { + switch ($type) { + case 'integer': + return 'xsd:integer'; + case 'float': + return 'xsd:float'; + case 'double': + return 'xsd:double'; + case 'boolean': + case 'bool': + return 'xsd:boolean'; + case 'string': + return 'xsd:string'; + case 'array': + case 'mixed': + default: + return 'xsd:anySimpleType'; + } + } + + private function encodeFluidVariable(mixed $input, bool $isRoot = true): string + { + if (is_array($input)) { + $fluidArray = []; + foreach ($input as $key => $value) { + $fluidArray[] = $this->encodeFluidVariable($key, false) . ': ' . $this->encodeFluidVariable($value, false); + } + return '{' . implode(', ', $fluidArray) . '}'; + } + + if (is_string($input) && !$isRoot) { + return "'" . addcslashes($input, "'") . "'"; + } + + if (is_bool($input)) { + return ($input) ? 'true' : 'false'; + } + + if (is_null($input)) { + return 'NULL'; + } + + return (string)$input; + } +} diff --git a/src/Schema/ViewHelperFinder.php b/src/Schema/ViewHelperFinder.php new file mode 100644 index 000000000..4f28db971 --- /dev/null +++ b/src/Schema/ViewHelperFinder.php @@ -0,0 +1,84 @@ +viewHelperMetadataFactory = $viewHelperMetadataFactory ?? new ViewHelperMetadataFactory(); + } + + /** + * @return ViewHelperMetadata[] + */ + public function findViewHelpersInComposerProject(ClassLoader $autoloader): array + { + $viewHelpers = []; + foreach ($autoloader->getPrefixesPsr4() as $namespace => $paths) { + foreach ($paths as $path) { + $viewHelpers = array_merge($viewHelpers, $this->findViewHelperFilesInPath($namespace, $path)); + } + } + return $viewHelpers; + } + + /** + * @return ViewHelperMetadata[] + */ + private function findViewHelperFilesInPath(string $namespace, string $path): array + { + $viewHelpers = []; + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS | FilesystemIterator::CURRENT_AS_PATHNAME), + ); + foreach ($iterator as $filePath) { + // Naming convention: ViewHelper files need to have "ViewHelper" suffix + if (!str_ends_with((string)$filePath, self::FILE_SUFFIX)) { + continue; + } + + // Guesstimate PHP namespace based on file path + $pathInPackage = substr($filePath, strlen($path) + 1, -4); + $className = $namespace . str_replace('/', '\\', $pathInPackage); + $phpNamespace = substr($className, 0, strrpos($className, '\\')); + + // Make sure that we generated the correct namespace for the file; + // This prevents duplicate class declarations if files are part of + // multiple/overlapping namespaces + // The alternative would be to use PHP-Parser for the whole finding process, + // but then we would have to check for correct interface implementation of + // ViewHelper classes manually + $phpCode = file_get_contents($filePath); + if (!preg_match('#namespace\s+' . preg_quote($phpNamespace, '#') . '\s*;#', $phpCode)) { + continue; + } + + try { + $viewHelpers[] = $this->viewHelperMetadataFactory->createFromViewhelperClass($className); + } catch (\InvalidArgumentException) { + // Just ignore this class + } + } + return $viewHelpers; + } +} diff --git a/src/Schema/ViewHelperMetadata.php b/src/Schema/ViewHelperMetadata.php new file mode 100644 index 000000000..4273c1833 --- /dev/null +++ b/src/Schema/ViewHelperMetadata.php @@ -0,0 +1,34 @@ + $docTags + * @param array $argumentDefinitions + */ + public function __construct( + public readonly string $className, + public readonly string $namespace, + public readonly string $name, + public readonly string $tagName, + public readonly string $documentation, + public readonly string $xmlNamespace, + public readonly array $docTags, + public readonly array $argumentDefinitions, + public readonly bool $allowsArbitraryArguments, + ) {} +} diff --git a/src/Schema/ViewHelperMetadataFactory.php b/src/Schema/ViewHelperMetadataFactory.php new file mode 100644 index 000000000..49f9830cf --- /dev/null +++ b/src/Schema/ViewHelperMetadataFactory.php @@ -0,0 +1,120 @@ +isAbstract()) { + throw new \InvalidArgumentException('Metadata cannot be fetched from abstract ViewHelpers: ' . $className); + } + + $docComment = (new \ReflectionClass($className))->getDocComment(); + if ($docComment === false) { + $documentation = ''; + $docTags = []; + } else { + $docComment = $this->extractTextFromDocComment((string)$docComment); + + // Parse phpdoc tags + $parts = preg_split('#^(@[a-z-]+)#m', $docComment, -1, PREG_SPLIT_DELIM_CAPTURE); + $documentation = trim(array_shift($parts)); + + // Collect phpdoc tags + $docTags = []; + $currentTag = null; + foreach ($parts as $part) { + if (str_starts_with($part, '@')) { + $docTags[$part] = ''; + $currentTag = $part; + } else { + $docTags[$currentTag] .= $part; + } + } + $docTags = array_map(trim(...), $docTags); + } + + [$namespace, $name] = $this->splitClassName($className); + + return new ViewHelperMetadata( + className: $className, + namespace: $namespace, + name: $name, + tagName: $this->generateTagName($name), + documentation: $documentation, + xmlNamespace: $this->generateXmlNamespace($namespace), + docTags: $docTags, + argumentDefinitions: (new \ReflectionClass($className))->newInstanceWithoutConstructor()->prepareArguments(), + allowsArbitraryArguments: is_subclass_of($className, AbstractTagBasedViewHelper::class), + ); + } + + private function extractTextFromDocComment(string $docComment): string + { + // Remove opening comment tag + $docComment = preg_replace('#^/\*\*#', '', $docComment); + + // Remove closing comment tag + $docComment = preg_replace('#\*/$#', '', $docComment); + + // Remove * and leading whitespace in each line + $docComment = preg_replace('#^\s*\* ?#m', '', $docComment); + + return $docComment; + } + + private function generateXmlNamespace(string $phpNamespace): string + { + return 'http://typo3.org/ns/' . str_replace('\\', '/', $phpNamespace); + } + + private function generateTagName(string $viewHelperName): string + { + $withoutSuffix = substr($viewHelperName, 0, -strlen(self::CLASS_SUFFIX)); + return implode('.', array_map(lcfirst(...), explode('\\', $withoutSuffix))); + } + + /** + * @return array{0: string, 1: string} + */ + private function splitClassName(string $className): array + { + $splitPos = strrpos($className, '\\' . self::NAMESPACE_NAME_DIVIDER . '\\') + strlen('\\' . self::NAMESPACE_NAME_DIVIDER); + $namespace = substr($className, 0, $splitPos); + $name = substr($className, $splitPos + 1); + return [$namespace, $name]; + } +} diff --git a/src/Tools/ConsoleRunner.php b/src/Tools/ConsoleRunner.php index 82a01f883..fdb17a87f 100644 --- a/src/Tools/ConsoleRunner.php +++ b/src/Tools/ConsoleRunner.php @@ -9,12 +9,15 @@ namespace TYPO3Fluid\Fluid\Tools; +use Composer\Autoload\ClassLoader; use TYPO3Fluid\Fluid\Core\Cache\FluidCacheWarmupResult; use TYPO3Fluid\Fluid\Core\Cache\SimpleFileCache; use TYPO3Fluid\Fluid\Core\Variables\JSONVariableProvider; use TYPO3Fluid\Fluid\Core\Variables\StandardVariableProvider; use TYPO3Fluid\Fluid\Core\Variables\VariableProviderInterface; use TYPO3Fluid\Fluid\Exception; +use TYPO3Fluid\Fluid\Schema\SchemaGenerator; +use TYPO3Fluid\Fluid\Schema\ViewHelperFinder; use TYPO3Fluid\Fluid\View\TemplatePaths; use TYPO3Fluid\Fluid\View\TemplateView; use TYPO3Fluid\Fluid\View\ViewInterface; @@ -26,6 +29,7 @@ final class ConsoleRunner { private const COMMAND_HELP = 'help'; private const COMMAND_RUN = 'run'; + private const COMMAND_SCHEMA = 'schema'; private const ARGUMENT_HELP = 'help'; private const ARGUMENT_SOCKET = 'socket'; @@ -40,10 +44,12 @@ final class ConsoleRunner private const ARGUMENT_LAYOUTROOTPATHS = 'layoutRootPaths'; private const ARGUMENT_PARTIALROOTPATHS = 'partialRootPaths'; private const ARGUMENT_RENDERINGCONTEXT = 'renderingContext'; + private const ARGUMENT_DESTINATION = 'destination'; private array $commandDesccriptions = [ self::COMMAND_HELP => 'Show this help screen', self::COMMAND_RUN => 'Run fluid code, either interactively or file-based', + self::COMMAND_SCHEMA => 'Generate xsd schema files based on all available ViewHelper classes', ]; private array $argumentDescriptions = [ @@ -64,12 +70,16 @@ final class ConsoleRunner ], self::COMMAND_HELP => [ ], + self::COMMAND_SCHEMA => [ + self::ARGUMENT_HELP => 'Shows usage examples', + self::ARGUMENT_DESTINATION => 'Destination folder where the schema files should be written to', + ], ]; /** * @param string[] $arguments */ - public function handleCommand(array $arguments): string + public function handleCommand(array $arguments, ClassLoader $autoloader): string { array_shift($arguments); @@ -91,6 +101,9 @@ public function handleCommand(array $arguments): string case self::COMMAND_HELP: return $this->handleHelpCommand(); + case self::COMMAND_SCHEMA: + return $this->handleSchemaCommand($arguments, $autoloader); + case self::COMMAND_RUN: default: return $this->handleRunCommand($arguments); @@ -103,6 +116,43 @@ private function handleHelpCommand(): string $this->dumpSupportedCommands($this->commandDesccriptions); } + /** + * @param array $arguments + */ + private function handleSchemaCommand(array $arguments, ClassLoader $autoloader): string + { + if (isset($arguments[self::ARGUMENT_HELP])) { + return $this->dumpHelpHeader() . + $this->dumpSupportedParameters($this->argumentDescriptions[self::COMMAND_SCHEMA]); + } + + $allViewHelpers = (new ViewHelperFinder())->findViewHelpersInComposerProject($autoloader); + + $groupedByNamespace = []; + foreach ($allViewHelpers as $viewHelper) { + $groupedByNamespace[$viewHelper->xmlNamespace] ??= []; + $groupedByNamespace[$viewHelper->xmlNamespace][] = $viewHelper; + } + + $destination = (isset($arguments[self::ARGUMENT_DESTINATION])) ? rtrim($arguments[self::ARGUMENT_DESTINATION], '/') . '/' : './'; + if (!file_exists($destination)) { + mkdir($destination, recursive: true); + } elseif (!is_dir($destination)) { + throw new \InvalidArgumentException( + 'Invalid destination folder: ' . $destination, + ); + } + + foreach ($groupedByNamespace as $xmlNamespace => $viewHelpers) { + $schema = (new SchemaGenerator())->generate($xmlNamespace, $viewHelpers); + $fileName = str_replace('http://typo3.org/ns/', '', $xmlNamespace); + $fileName = str_replace('/', '_', $fileName); + $fileName = preg_replace('#[^0-9a-zA-Z_]#', '', $fileName); + file_put_contents($destination . 'schema_' . $fileName . '.xsd', $schema->asXml()); + } + return ''; + } + /** * @param array $arguments */ @@ -391,18 +441,6 @@ private function dumpHelpHeader() PHP_EOL . PHP_EOL; } - /** - * @param array $commands - */ - private function dumpSupportedCommands(array $commands): string - { - $commandString = 'Supported commands:' . PHP_EOL . PHP_EOL; - foreach ($commands as $name => $description) { - $commandString .= "\t" . 'bin/fluid ' . str_pad($name, 20, ' ') . ' # ' . $description . PHP_EOL; - } - return $commandString . PHP_EOL; - } - /** * @param array $parameters */ @@ -415,6 +453,18 @@ private function dumpSupportedParameters(array $parameters): string return $parameterString . PHP_EOL; } + /** + * @param array $commands + */ + private function dumpSupportedCommands(array $commands): string + { + $commandString = 'Supported commands:' . PHP_EOL . PHP_EOL; + foreach ($commands as $name => $description) { + $commandString .= "\t" . 'bin/fluid ' . str_pad($name, 20, ' ') . ' # ' . $description . PHP_EOL; + } + return $commandString . PHP_EOL; + } + /** * @return string */ diff --git a/tests/Functional/CommandTest.php b/tests/Functional/CommandTest.php index c6ecab19e..9064caf5e 100644 --- a/tests/Functional/CommandTest.php +++ b/tests/Functional/CommandTest.php @@ -21,16 +21,31 @@ public static function getCommandTestValues(): array 'Use the CLI utility in the following modes', 'Exception', ], + [ + '%s help', + 'Supported commands:', + 'Exception', + ], [ 'echo "Hello world!" | %s', 'Hello world!', 'Exeption', ], + [ + 'echo "Hello world!" | %s run', + 'Hello world!', + 'Exeption', + ], [ 'echo "{foo}" | %s --variables "{\\"foo\\": \\"bar\\"}"', 'bar', 'Exception', 'foo', ], + [ + 'echo "{foo}" | %s run --variables "{\\"foo\\": \\"bar\\"}"', + 'bar', + 'Exception', 'foo', + ], ]; } diff --git a/tests/Unit/Schema/Fixtures/ViewHelpers/AbstractViewHelper.php b/tests/Unit/Schema/Fixtures/ViewHelpers/AbstractViewHelper.php new file mode 100644 index 000000000..5d960d62e --- /dev/null +++ b/tests/Unit/Schema/Fixtures/ViewHelpers/AbstractViewHelper.php @@ -0,0 +1,12 @@ +registerArgument('value', 'string', 'A test argument'); + } + + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string + { + return ''; + } +} diff --git a/tests/Unit/Schema/Fixtures/ViewHelpers/Sub/DeprecatedViewHelper.php b/tests/Unit/Schema/Fixtures/ViewHelpers/Sub/DeprecatedViewHelper.php new file mode 100644 index 000000000..0488a68cb --- /dev/null +++ b/tests/Unit/Schema/Fixtures/ViewHelpers/Sub/DeprecatedViewHelper.php @@ -0,0 +1,32 @@ +registerArgument('value', 'string', 'A test argument'); + } + + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string + { + return ''; + } +} diff --git a/tests/Unit/Schema/Fixtures/ViewHelpers/WithDocumentationViewHelper.php b/tests/Unit/Schema/Fixtures/ViewHelpers/WithDocumentationViewHelper.php new file mode 100644 index 000000000..97c06379a --- /dev/null +++ b/tests/Unit/Schema/Fixtures/ViewHelpers/WithDocumentationViewHelper.php @@ -0,0 +1,43 @@ + + * + * @internal + */ +final class WithDocumentationViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + public function initializeArguments(): void + { + $this->registerArgument('value', 'string', 'A test argument'); + } + + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string + { + return ''; + } +} diff --git a/tests/Unit/Schema/Fixtures/ViewHelpers/WithoutClassSuffix.php b/tests/Unit/Schema/Fixtures/ViewHelpers/WithoutClassSuffix.php new file mode 100644 index 000000000..3c7465abe --- /dev/null +++ b/tests/Unit/Schema/Fixtures/ViewHelpers/WithoutClassSuffix.php @@ -0,0 +1,29 @@ +registerArgument('value', 'string', 'A test argument'); + } + + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string + { + return ''; + } +} diff --git a/tests/Unit/Schema/Fixtures/ViewHelpers/WithoutDocumentationViewHelper.php b/tests/Unit/Schema/Fixtures/ViewHelpers/WithoutDocumentationViewHelper.php new file mode 100644 index 000000000..1033e504a --- /dev/null +++ b/tests/Unit/Schema/Fixtures/ViewHelpers/WithoutDocumentationViewHelper.php @@ -0,0 +1,29 @@ +registerArgument('value', 'string', 'A test argument'); + } + + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string + { + return ''; + } +} diff --git a/tests/Unit/Schema/Fixtures/ViewHelpers/WithoutInterfaceViewHelper.php b/tests/Unit/Schema/Fixtures/ViewHelpers/WithoutInterfaceViewHelper.php new file mode 100644 index 000000000..b2e57429f --- /dev/null +++ b/tests/Unit/Schema/Fixtures/ViewHelpers/WithoutInterfaceViewHelper.php @@ -0,0 +1,12 @@ +registerArgument('value', 'string', 'A test argument'); + } + + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string + { + return ''; + } +} diff --git a/tests/Unit/Schema/SchemaGeneratorTest.php b/tests/Unit/Schema/SchemaGeneratorTest.php new file mode 100644 index 000000000..f04484746 --- /dev/null +++ b/tests/Unit/Schema/SchemaGeneratorTest.php @@ -0,0 +1,219 @@ + [ + 'http://typo3.org/ns/Vendor/Package/ViewHelpers', + [ + new ViewHelperMetadata( + 'Vendor\\Package\\ViewHelpers\\MyViewHelper', + 'Vendor\\Package', + 'MyViewHelper', + 'myViewHelper', + '', + 'http://typo3.org/ns/Vendor/Package/ViewHelpers', + [], + ['value' => new ArgumentDefinition('value', 'string', '', true)], + false, + ), + ], + '' . "\n" . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . "\n", + ], + 'viewHelperWithDocumentation' => [ + 'http://typo3.org/ns/Vendor/Package/ViewHelpers', + [ + new ViewHelperMetadata( + 'Vendor\\Package\\ViewHelpers\\MyViewHelper', + 'Vendor\\Package', + 'MyViewHelper', + 'myViewHelper', + "This is a ViewHelper documentation\nwith newlines", + 'http://typo3.org/ns/Vendor/Package/ViewHelpers', + [], + ['value' => new ArgumentDefinition('value', 'string', 'Argument description', false, 'default value')], + false, + ), + ], + '' . "\n" . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . "\n", + ], + 'deprecatedViewHelper' => [ + 'http://typo3.org/ns/Vendor/Package/ViewHelpers', + [ + new ViewHelperMetadata( + 'Vendor\\Package\\ViewHelpers\\MyViewHelper', + 'Vendor\\Package', + 'MyViewHelper', + 'myViewHelper', + '', + 'http://typo3.org/ns/Vendor/Package/ViewHelpers', + ['@deprecated' => 'since 1.2.3, will be removed in 2.0.0'], + [], + false, + ), + ], + '' . "\n" . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . "\n", + ], + 'argumentTypes' => [ + 'http://typo3.org/ns/Vendor/Package/ViewHelpers', + [ + new ViewHelperMetadata( + 'Vendor\\Package\\ViewHelpers\\MyViewHelper', + 'Vendor\\Package', + 'MyViewHelper', + 'myViewHelper', + '', + 'http://typo3.org/ns/Vendor/Package/ViewHelpers', + [], + [ + 'intArg' => new ArgumentDefinition('intArg', 'integer', '', false, 123), + 'arrayArg' => new ArgumentDefinition('arrayArg', 'array', '', false, ['one', 'two' => [3]]), + 'boolArg' => new ArgumentDefinition('boolArg', 'bool', '', false, true), + ], + false, + ), + ], + '' . "\n" . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . "\n", + ], + 'arbitraryArguments' => [ + 'http://typo3.org/ns/Vendor/Package/ViewHelpers', + [ + new ViewHelperMetadata( + 'Vendor\\Package\\ViewHelpers\\MyViewHelper', + 'Vendor\\Package', + 'MyViewHelper', + 'myViewHelper', + '', + 'http://typo3.org/ns/Vendor/Package/ViewHelpers', + [], + [], + true, + ), + ], + '' . "\n" . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . "\n", + ], + 'multipleViewHelpers' => [ + 'http://typo3.org/ns/Vendor/Package/ViewHelpers', + [ + new ViewHelperMetadata( + 'Vendor\\Package\\ViewHelpers\\MyViewHelper', + 'Vendor\\Package', + 'MyViewHelper', + 'myViewHelper', + '', + 'http://typo3.org/ns/Vendor/Package/ViewHelpers', + [], + [], + false, + ), + new ViewHelperMetadata( + 'Vendor\\Package\\ViewHelpers\\Sub\\MyOtherViewHelper', + 'Vendor\\Package', + 'Sub\\MyOtherViewHelper', + 'sub.myOtherViewHelper', + '', + 'http://typo3.org/ns/Vendor/Package/ViewHelpers', + [], + [], + false, + ), + ], + '' . "\n" . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . "\n", + ], + ]; + } + + /** + * @param ViewHelperMetadata[] $viewHelpers + */ + #[Test] + #[DataProvider('generateSchemaDataProvider')] + public function generateSchema(string $xmlNamespace, array $viewHelpers, string $expected): void + { + $xml = (new SchemaGenerator())->generate($xmlNamespace, $viewHelpers); + self::assertEquals($expected, $xml->asXML()); + } +} diff --git a/tests/Unit/Schema/ViewHelperMetadataFactoryTest.php b/tests/Unit/Schema/ViewHelperMetadataFactoryTest.php new file mode 100644 index 000000000..5caf60db4 --- /dev/null +++ b/tests/Unit/Schema/ViewHelperMetadataFactoryTest.php @@ -0,0 +1,111 @@ + [ + 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\WithoutDocumentationViewHelper', + 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers', + 'WithoutDocumentationViewHelper', + 'withoutDocumentation', + '', + 'http://typo3.org/ns/TYPO3Fluid/Fluid/Tests/Unit/Schema/Fixtures/ViewHelpers', + [], + false, + ], + 'WithDocumentationViewHelper' => [ + 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\WithDocumentationViewHelper', + 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers', + 'WithDocumentationViewHelper', + 'withDocumentation', + "This is an example documentation with multiple lines\nof text.\n\nExamples\n========\n\nWe usually have some examples\n\n::\n ", + 'http://typo3.org/ns/TYPO3Fluid/Fluid/Tests/Unit/Schema/Fixtures/ViewHelpers', + ['@internal' => ''], + false, + ], + 'DeprecatedViewHelper' => [ + 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\Sub\\DeprecatedViewHelper', + 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers', + 'Sub\\DeprecatedViewHelper', + 'sub.deprecated', + '', + 'http://typo3.org/ns/TYPO3Fluid/Fluid/Tests/Unit/Schema/Fixtures/ViewHelpers', + ['@deprecated' => 'this is a deprecation message'], + false, + ], + 'ArbitraryArgumentsViewHelper' => [ + 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\Sub\\ArbitraryArgumentsViewHelper', + 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers', + 'Sub\\ArbitraryArgumentsViewHelper', + 'sub.arbitraryArguments', + '', + 'http://typo3.org/ns/TYPO3Fluid/Fluid/Tests/Unit/Schema/Fixtures/ViewHelpers', + [], + true, + ], + ]; + } + + #[Test] + #[DataProvider('createObjectDataProvider')] + public function createObject( + string $className, + string $namespace, + string $name, + string $tagName, + string $documentation, + string $xmlNamespace, + array $docTags, + bool $allowsArbitraryArguments, + ): void { + $object = (new ViewHelperMetadataFactory())->createFromViewHelperClass($className); + self::assertSame($className, $object->className); + self::assertSame($namespace, $object->namespace); + self::assertSame($name, $object->name); + self::assertSame($tagName, $object->tagName); + self::assertSame($documentation, $object->documentation); + self::assertSame($xmlNamespace, $object->xmlNamespace); + self::assertSame($docTags, $object->docTags); + self::assertSame($allowsArbitraryArguments, $object->allowsArbitraryArguments); + self::assertEquals( + ['value' => new ArgumentDefinition('value', 'string', 'A test argument', false)], + $object->argumentDefinitions, + ); + } + + public static function createObjectFailureDataProvider(): iterable + { + return [ + 'NonexistentViewHelper' => ['TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\NonexistentViewHelper'], + 'WithoutInterfaceViewHelper' => ['TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\WithoutInterfaceViewHelper'], + 'WithoutClassSuffix' => ['TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\WithoutClassSuffix'], + 'AbstractViewHelper' => ['TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\AbstractViewHelper'], + 'WrongDirectoryViewHelper' => ['TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\WrongDirectoryViewHelper'], + ]; + } + + #[Test] + #[DataProvider('createObjectFailureDataProvider')] + public function createObjectFailure(string $className): void + { + self::expectException(\InvalidArgumentException::class); + (new ViewHelperMetadataFactory())->createFromViewhelperClass($className); + } +} From 79d3030854b1d7dfc866f46f7ecc77194f45caa8 Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Thu, 13 Jun 2024 12:01:23 +0200 Subject: [PATCH 2/9] [TASK] Improve tests for CLI --- phpunit.xml.dist | 2 + tests/Functional/CommandTest.php | 64 --------- .../CodeFromStdinDefaultCommandTest.phpt | 9 ++ tests/Functional/Tools/CodeFromStdinTest.phpt | 11 ++ .../Tools/CodeWithVariableTest.phpt | 13 ++ .../Tools/HelpArgumentDefaultCommandTest.phpt | 121 ++++++++++++++++++ tests/Functional/Tools/HelpCommandTest.phpt | 18 +++ .../Functional/Tools/InvalidArgumentTest.phpt | 10 ++ .../Functional/Tools/InvalidCommandTest.phpt | 10 ++ 9 files changed, 194 insertions(+), 64 deletions(-) delete mode 100644 tests/Functional/CommandTest.php create mode 100644 tests/Functional/Tools/CodeFromStdinDefaultCommandTest.phpt create mode 100644 tests/Functional/Tools/CodeFromStdinTest.phpt create mode 100644 tests/Functional/Tools/CodeWithVariableTest.phpt create mode 100644 tests/Functional/Tools/HelpArgumentDefaultCommandTest.phpt create mode 100644 tests/Functional/Tools/HelpCommandTest.phpt create mode 100644 tests/Functional/Tools/InvalidArgumentTest.phpt create mode 100644 tests/Functional/Tools/InvalidCommandTest.phpt diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 24edb1d8f..9512694d0 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -17,9 +17,11 @@ tests/Unit + tests/Unit tests/Functional + tests/Functional diff --git a/tests/Functional/CommandTest.php b/tests/Functional/CommandTest.php deleted file mode 100644 index 9064caf5e..000000000 --- a/tests/Functional/CommandTest.php +++ /dev/null @@ -1,64 +0,0 @@ - Hello world! |" | fluid +--STDIN-- +| Hello world! | +--FILE-- + Hello world! |" | fluid run +--ARGS-- +run +--STDIN-- +| Hello world! | +--FILE-- +setSource() +upon instantiation. If working with custom VariableProviders, check the documentation +for each VariableProvider to know which source types are supported. + +Cache warmup can be triggered by calling: + + ./bin/fluid run --warmup --cacheDirectory "/path/to/cache" + +And should you require it you can pass the class name of a custom RenderingContext (which can return a +custom FluidCacheWarmer instance!): + + ./bin/fluid run --warmup --renderingContext "My\Custom\RenderingContext" + +Furthermore, should you require special bootstrapping of a framework, you can specify +an entry point containing a bootstrap (with or without output, does not matter) which +will be required/included as part of the initialisation. + + ./bin/fluid run --warmup --renderingContext "My\Custom\RenderingContext" --bootstrap /path/to/bootstrap.php + +Or using a public, static function on a class which bootstraps: + + ./bin/fluid run --warmup --renderingContext "My\Custom\RenderingContext" --bootstrap MyBootstrapClass::bootstrapMethod + +When passing a class-and-method bootstrap it is important that the method has no +required arguments and is possible to call as static method. + +Note: the bootstrapping can also be used for other cases, but be careful to use +a bootstrapper which does not cause output if you intend to render templates. + +A WebSocket mode is available. When starting the CLI utility in WebSocket mode, +very basic HTTP requests are rendered directly by listening on an IP:PORT combination: + + sudo ./bin/fluid run --socket 0.0.0.0:8080 --templateRootPaths /path/to/files/ + +Pointing your browser to http://localhost:8080 should then render the requested +file from the given path, defaulting to `index.html` when URI ends in `/`. + +Note that when started this way, there is no DOCUMENT_ROOT except for the root +path you define as templateRootPaths. In this mode, the *FIRST* templateRootPath +gets used as if it were the DOCUMENT_ROOT. + +Note also that this mode does not provide any $_SERVER or other variables of use +as would be done through for example Apache or Nginx. + +An additional SocketServer mode is available. When started in SocketServer mode, +the CLI utility can be used as upstream (SCGI currently) in Nginx: + + sudo ./bin/fluid run --socket /var/run/fluid.sock + +Example SCGI config for Nginx: + + location ~ \.html$ { + scgi_pass unix:/var/run/fluid.sock; + include scgi_params; + } + +End of help text for FLuid CLI. diff --git a/tests/Functional/Tools/HelpCommandTest.phpt b/tests/Functional/Tools/HelpCommandTest.phpt new file mode 100644 index 000000000..2f1f8254d --- /dev/null +++ b/tests/Functional/Tools/HelpCommandTest.phpt @@ -0,0 +1,18 @@ +--TEST-- +fluid help +--ARGS-- +help +--FILE-- + Date: Thu, 13 Jun 2024 12:11:59 +0200 Subject: [PATCH 3/9] [TASK] Simplify diff --- src/Tools/ConsoleRunner.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Tools/ConsoleRunner.php b/src/Tools/ConsoleRunner.php index fdb17a87f..8abe2b4cb 100644 --- a/src/Tools/ConsoleRunner.php +++ b/src/Tools/ConsoleRunner.php @@ -441,18 +441,6 @@ private function dumpHelpHeader() PHP_EOL . PHP_EOL; } - /** - * @param array $parameters - */ - private function dumpSupportedParameters(array $parameters): string - { - $parameterString = 'Supported parameters:' . PHP_EOL . PHP_EOL; - foreach ($parameters as $name => $description) { - $parameterString .= "\t" . '--' . str_pad($name, 20, ' ') . ' # ' . $description . PHP_EOL; - } - return $parameterString . PHP_EOL; - } - /** * @param array $commands */ @@ -465,6 +453,18 @@ private function dumpSupportedCommands(array $commands): string return $commandString . PHP_EOL; } + /** + * @param array $parameters + */ + private function dumpSupportedParameters(array $parameters): string + { + $parameterString = 'Supported parameters:' . PHP_EOL . PHP_EOL; + foreach ($parameters as $name => $description) { + $parameterString .= "\t" . '--' . str_pad($name, 20, ' ') . ' # ' . $description . PHP_EOL; + } + return $parameterString . PHP_EOL; + } + /** * @return string */ From eacf541d742648c3e39305c14bf947220fad552a Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Thu, 13 Jun 2024 16:21:18 +0200 Subject: [PATCH 4/9] [TASK] Use ::class syntax for existing fixture classes [TASK] Use ::class syntax for existing fixture classes --- .../Schema/ViewHelperMetadataFactoryTest.php | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/Unit/Schema/ViewHelperMetadataFactoryTest.php b/tests/Unit/Schema/ViewHelperMetadataFactoryTest.php index 5caf60db4..440fbdcce 100644 --- a/tests/Unit/Schema/ViewHelperMetadataFactoryTest.php +++ b/tests/Unit/Schema/ViewHelperMetadataFactoryTest.php @@ -13,6 +13,14 @@ use PHPUnit\Framework\Attributes\Test; use TYPO3Fluid\Fluid\Core\ViewHelper\ArgumentDefinition; use TYPO3Fluid\Fluid\Schema\ViewHelperMetadataFactory; +use TYPO3Fluid\Fluid\Tests\Unit\Schema\Fixtures\ViewHelpers\AbstractViewHelper; +use TYPO3Fluid\Fluid\Tests\Unit\Schema\Fixtures\ViewHelpers\Sub\ArbitraryArgumentsViewHelper; +use TYPO3Fluid\Fluid\Tests\Unit\Schema\Fixtures\ViewHelpers\Sub\DeprecatedViewHelper; +use TYPO3Fluid\Fluid\Tests\Unit\Schema\Fixtures\ViewHelpers\WithDocumentationViewHelper; +use TYPO3Fluid\Fluid\Tests\Unit\Schema\Fixtures\ViewHelpers\WithoutClassSuffix; +use TYPO3Fluid\Fluid\Tests\Unit\Schema\Fixtures\ViewHelpers\WithoutDocumentationViewHelper; +use TYPO3Fluid\Fluid\Tests\Unit\Schema\Fixtures\ViewHelpers\WithoutInterfaceViewHelper; +use TYPO3Fluid\Fluid\Tests\Unit\Schema\Fixtures\WrongDirectoryViewHelper; use TYPO3Fluid\Fluid\Tests\UnitTestCase; class ViewHelperMetadataFactoryTest extends UnitTestCase @@ -21,7 +29,7 @@ public static function createObjectDataProvider(): iterable { return [ 'WithoutDocumentationViewHelper' => [ - 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\WithoutDocumentationViewHelper', + WithoutDocumentationViewHelper::class, 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers', 'WithoutDocumentationViewHelper', 'withoutDocumentation', @@ -31,7 +39,7 @@ public static function createObjectDataProvider(): iterable false, ], 'WithDocumentationViewHelper' => [ - 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\WithDocumentationViewHelper', + WithDocumentationViewHelper::class, 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers', 'WithDocumentationViewHelper', 'withDocumentation', @@ -41,7 +49,7 @@ public static function createObjectDataProvider(): iterable false, ], 'DeprecatedViewHelper' => [ - 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\Sub\\DeprecatedViewHelper', + DeprecatedViewHelper::class, 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers', 'Sub\\DeprecatedViewHelper', 'sub.deprecated', @@ -51,7 +59,7 @@ public static function createObjectDataProvider(): iterable false, ], 'ArbitraryArgumentsViewHelper' => [ - 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\Sub\\ArbitraryArgumentsViewHelper', + ArbitraryArgumentsViewHelper::class, 'TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers', 'Sub\\ArbitraryArgumentsViewHelper', 'sub.arbitraryArguments', @@ -94,10 +102,10 @@ public static function createObjectFailureDataProvider(): iterable { return [ 'NonexistentViewHelper' => ['TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\NonexistentViewHelper'], - 'WithoutInterfaceViewHelper' => ['TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\WithoutInterfaceViewHelper'], - 'WithoutClassSuffix' => ['TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\WithoutClassSuffix'], - 'AbstractViewHelper' => ['TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\ViewHelpers\\AbstractViewHelper'], - 'WrongDirectoryViewHelper' => ['TYPO3Fluid\\Fluid\\Tests\\Unit\\Schema\\Fixtures\\WrongDirectoryViewHelper'], + 'WithoutInterfaceViewHelper' => [WithoutInterfaceViewHelper::class], + 'WithoutClassSuffix' => [WithoutClassSuffix::class], + 'AbstractViewHelper' => [AbstractViewHelper::class], + 'WrongDirectoryViewHelper' => [WrongDirectoryViewHelper::class], ]; } From 76676829bfb7168ee1c32baaa8d88d4a7172204c Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Thu, 13 Jun 2024 16:24:31 +0200 Subject: [PATCH 5/9] [TASK] Render deprecation as phpdoc instead of RST [TASK] Render deprecation as phpdoc instead of RST --- src/Schema/SchemaGenerator.php | 4 ++-- tests/Unit/Schema/SchemaGeneratorTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Schema/SchemaGenerator.php b/src/Schema/SchemaGenerator.php index 1748fc7e9..70d87a461 100644 --- a/src/Schema/SchemaGenerator.php +++ b/src/Schema/SchemaGenerator.php @@ -25,10 +25,10 @@ public function generate(string $xmlNamespace, array $viewHelpers): \SimpleXMLEl $xsdElement->addAttribute('name', $metadata->tagName); - // Add deprecation information to ViewHelper documentation $documentation = $metadata->documentation; + // Add deprecation information to ViewHelper documentation if (isset($metadata->docTags['@deprecated'])) { - $documentation .= "\n.. attention::\n**Deprecated** " . $metadata->docTags['@deprecated']; + $documentation .= "\n@deprecated " . $metadata->docTags['@deprecated']; } $documentation = trim($documentation); diff --git a/tests/Unit/Schema/SchemaGeneratorTest.php b/tests/Unit/Schema/SchemaGeneratorTest.php index f04484746..7785d79f2 100644 --- a/tests/Unit/Schema/SchemaGeneratorTest.php +++ b/tests/Unit/Schema/SchemaGeneratorTest.php @@ -94,7 +94,7 @@ public static function generateSchemaDataProvider(): iterable '' . "\n" . '' . '' . - '' . + '' . '' . '' . '' . From 054b24e120079fc345a6c0106eba4adbb82d9f0d Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Thu, 13 Jun 2024 16:30:01 +0200 Subject: [PATCH 6/9] [BUGFIX] Prevent exception if default value is object --- src/Schema/SchemaGenerator.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Schema/SchemaGenerator.php b/src/Schema/SchemaGenerator.php index 70d87a461..5be703475 100644 --- a/src/Schema/SchemaGenerator.php +++ b/src/Schema/SchemaGenerator.php @@ -140,6 +140,12 @@ private function encodeFluidVariable(mixed $input, bool $isRoot = true): string return 'NULL'; } + // Generally, this wouldn't be correct, since it's not the correct representation, + // but in the context of XSD files we merely need to provide *any* string representation + if (is_object($input)) { + return ''; + } + return (string)$input; } } From c02747352b88413244c49099982c993a14e80bfb Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Thu, 13 Jun 2024 16:42:53 +0200 Subject: [PATCH 7/9] [TASK] Clarify method name --- src/Schema/SchemaGenerator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Schema/SchemaGenerator.php b/src/Schema/SchemaGenerator.php index 5be703475..8a3283aea 100644 --- a/src/Schema/SchemaGenerator.php +++ b/src/Schema/SchemaGenerator.php @@ -60,7 +60,7 @@ public function generate(string $xmlNamespace, array $viewHelpers): \SimpleXMLEl if ($argumentDefinition->isRequired()) { $xsdAttribute->addAttribute('use', 'required'); } else { - $xsdAttribute->addAttribute('default', $this->encodeFluidVariable($default)); + $xsdAttribute->addAttribute('default', $this->createFluidRepresentation($default)); } // Add PHP type to documentation text @@ -118,12 +118,12 @@ private function convertPhpTypeToXsdType(string $type): string } } - private function encodeFluidVariable(mixed $input, bool $isRoot = true): string + private function createFluidRepresentation(mixed $input, bool $isRoot = true): string { if (is_array($input)) { $fluidArray = []; foreach ($input as $key => $value) { - $fluidArray[] = $this->encodeFluidVariable($key, false) . ': ' . $this->encodeFluidVariable($value, false); + $fluidArray[] = $this->createFluidRepresentation($key, false) . ': ' . $this->createFluidRepresentation($value, false); } return '{' . implode(', ', $fluidArray) . '}'; } From 21385090572246c4be979e990adc289e03a3a5bb Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Thu, 13 Jun 2024 16:43:06 +0200 Subject: [PATCH 8/9] [TASK] Simplify destination validation code --- src/Tools/ConsoleRunner.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tools/ConsoleRunner.php b/src/Tools/ConsoleRunner.php index 8abe2b4cb..472f41a0a 100644 --- a/src/Tools/ConsoleRunner.php +++ b/src/Tools/ConsoleRunner.php @@ -134,7 +134,7 @@ private function handleSchemaCommand(array $arguments, ClassLoader $autoloader): $groupedByNamespace[$viewHelper->xmlNamespace][] = $viewHelper; } - $destination = (isset($arguments[self::ARGUMENT_DESTINATION])) ? rtrim($arguments[self::ARGUMENT_DESTINATION], '/') . '/' : './'; + $destination = rtrim($arguments[self::ARGUMENT_DESTINATION] ?? '.', '/') . '/'; if (!file_exists($destination)) { mkdir($destination, recursive: true); } elseif (!is_dir($destination)) { From 1361eb1fa729102987ffe8db057d4ea3ad3a5d20 Mon Sep 17 00:00:00 2001 From: Simon Praetorius Date: Thu, 13 Jun 2024 17:07:03 +0200 Subject: [PATCH 9/9] [TASK] Add ext-simplexml as suggested dependency --- composer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 334b7a32c..1b95a8042 100644 --- a/composer.json +++ b/composer.json @@ -17,13 +17,15 @@ }, "require-dev": { "ext-json": "*", + "ext-simplexml": "*", "friendsofphp/php-cs-fixer": "^3.54.0", "phpstan/phpstan": "^1.10.14", "phpstan/phpstan-phpunit": "^1.3.11", "phpunit/phpunit": "^10.2.6" }, "suggest": { - "ext-json": "PHP JSON is needed when using JSONVariableProvider: A relatively rare use case" + "ext-json": "PHP JSON is needed when using JSONVariableProvider: A relatively rare use case", + "ext-simplexml": "SimpleXML is required for the XSD schema generator" }, "autoload": { "psr-4": {