diff --git a/composer.json b/composer.json index 97605266d4f9562a8e4d67ddbf529c1445ec62a9..6cfb74e8e9ca35c4e01b857f0429ec54369b3ab6 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,7 @@ "ext-json": "*", "ext-zip": "*", "api-platform/core": "^2.2", + "beberlei/assert": "^3.2", "doctrine/cache": "^1.8", "doctrine/inflector": "^1.3", "psr/container": "^1.0", @@ -34,6 +35,7 @@ "require-dev": { "irstea/php-cs-fixer-config": "^1.0", "irstea/phpmd-config": "^1.0.0", + "irstea/phpstan-config": "^1.0", "jakub-onderka/php-parallel-lint": "^1.0", "maglnet/composer-require-checker": "^1.0", "phploc/phploc": "^4.0", diff --git a/phpstan.neon b/phpstan.neon index d87786cb5e62cecd973f4bb43500496078153c46..e752a17312ed0261d3170e4a7f4776151fcfdf93 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,7 @@ +includes: + - vendor/irstea/phpstan-config/strict.neon + parameters: - level: 7 paths: - src/php diff --git a/src/php/Command/NgModelGenerateCommand.php b/src/php/Command/NgModelGenerateCommand.php index ffe83afb87d241084f253a9da2df92df01a73fcc..8be47ec5b322c82214a2d4ef231d63dce593cf21 100644 --- a/src/php/Command/NgModelGenerateCommand.php +++ b/src/php/Command/NgModelGenerateCommand.php @@ -22,6 +22,7 @@ namespace Irstea\NgModelGeneratorBundle\Command; use ApiPlatform\Core\Documentation\Documentation; +use Assert\Assertion; use Irstea\NgModelGeneratorBundle\ModelGenerator; use Irstea\NgModelGeneratorBundle\Writers\ConsoleWriter; use Irstea\NgModelGeneratorBundle\Writers\DirectoryWriter; @@ -91,6 +92,9 @@ final class NgModelGenerateCommand extends Command if ($input->getOption('restrict')) { $globs = $input->getOption('restrict'); + Assertion::isArray($globs); + Assertion::allString($globs); + $writer = new FilteringFileWriter($writer, function (string $path) use ($globs): bool { foreach ($globs as $glob) { if (fnmatch($glob, $path, FNM_PATHNAME)) { @@ -118,14 +122,17 @@ final class NgModelGenerateCommand extends Command private function openWriter(InputInterface $input, OutputInterface $output): MultiFileWriter { $zipPath = $input->getOption('zip'); + Assertion::nullOrString($zipPath); if ($zipPath) { - $archive = $this->openZipArchive($zipPath, $input->getOption('force')); + $force = (bool) $input->getOption('force'); + $archive = $this->openZipArchive($zipPath, $force); $output->write("Writing to ZIP archive: <info>$zipPath</info>\n"); return new ZipWriter($archive); } $dirPath = $input->getOption('output'); + Assertion::nullOrString($dirPath); if ($dirPath) { $output->write("Writing to directory: <info>$dirPath</info>\n"); diff --git a/src/php/Command/NgModelMetadataCommand.php b/src/php/Command/NgModelMetadataCommand.php index 56dd8de87b09fa2e39c75b21eed77047759eafcb..2a2c72abda93158a36853d61499cb595e194ed31 100644 --- a/src/php/Command/NgModelMetadataCommand.php +++ b/src/php/Command/NgModelMetadataCommand.php @@ -22,8 +22,8 @@ namespace Irstea\NgModelGeneratorBundle\Command; use ApiPlatform\Core\Documentation\Documentation; +use Assert\Assertion; use Irstea\NgModelGeneratorBundle\Metadata\MetadataFactoryInterface; -use Irstea\NgModelGeneratorBundle\ModelGenerator; use Irstea\NgModelGeneratorBundle\Models\PHPClass; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -48,8 +48,8 @@ final class NgModelMetadataCommand extends Command /** * NgModelGenerateCommand constructor. * - * @param ModelGenerator $generator - * @param Documentation $documentation + * @param MetadataFactoryInterface $metadataFactory + * @param Documentation $documentation */ public function __construct( MetadataFactoryInterface $metadataFactory, @@ -78,6 +78,8 @@ final class NgModelMetadataCommand extends Command protected function execute(InputInterface $input, OutputInterface $output) { $classes = $input->getArgument('classes'); + Assertion::isArray($classes); + Assertion::allString($classes); $metadata = []; foreach ($this->documentation->getResourceNameCollection() as $className) { @@ -88,6 +90,8 @@ final class NgModelMetadataCommand extends Command $metadata[$class->getBaseName()] = $this->metadataFactory->getResourceMetadata($class); } - $output->writeln(\json_encode($metadata, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); + $json = \json_encode($metadata, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); + Assertion::string($json); + $output->writeln($json); } } diff --git a/src/php/Iterators/CallbackFilterRecursorIterator.php b/src/php/Iterators/CallbackFilterRecursorIterator.php index 8a7b28f824d5f7514d6eac8cc2c99c253aded252..e9eafe086710b59594d1e007acb70243cceb0a90 100644 --- a/src/php/Iterators/CallbackFilterRecursorIterator.php +++ b/src/php/Iterators/CallbackFilterRecursorIterator.php @@ -57,7 +57,7 @@ class CallbackFilterRecursorIterator extends RecursorIterator * * @return \RecursiveIterator */ - protected function doGetChildren(\Iterator $iter): \RecursiveIterator + protected function doGetChildren(\Traversable $iter): \RecursiveIterator { return new self($iter, $this->filter); } diff --git a/src/php/Iterators/IteratorBuilder.php b/src/php/Iterators/IteratorBuilder.php index b808c1711cd5b485f8599b8776cd0f28aae38d12..72ed6a2223b29c1190f2741c1baad265194d5b7a 100644 --- a/src/php/Iterators/IteratorBuilder.php +++ b/src/php/Iterators/IteratorBuilder.php @@ -97,7 +97,7 @@ final class IteratorBuilder implements \IteratorAggregate } /** - * @param callable $param + * @param callable $where * * @return IteratorBuilder */ diff --git a/src/php/Iterators/RecursorIterator.php b/src/php/Iterators/RecursorIterator.php index f1cab0827322ecb41413ecad79bf08d09f4e8cd9..7b8dea87733abffc9c42966e2ee64afcdd02dc0d 100644 --- a/src/php/Iterators/RecursorIterator.php +++ b/src/php/Iterators/RecursorIterator.php @@ -56,11 +56,11 @@ class RecursorIterator extends \IteratorIterator implements \RecursiveIterator } /** - * @param \Iterator $iter + * @param \Traversable $iter * * @return \RecursiveIterator */ - protected function doGetChildren(\Iterator $iter): \RecursiveIterator + protected function doGetChildren(\Traversable $iter): \RecursiveIterator { return new self($iter); } diff --git a/src/php/Iterators/UniqueFilter.php b/src/php/Iterators/UniqueFilter.php index 29bd3987763b3151c68821ffc334d113d209a548..6092c19ea70b044e7fbf0e623191f87edc472b72 100644 --- a/src/php/Iterators/UniqueFilter.php +++ b/src/php/Iterators/UniqueFilter.php @@ -32,7 +32,7 @@ final class UniqueFilter private $seen = []; /** - * @param $value + * @param mixed $value * * @return bool */ diff --git a/src/php/Metadata/MetadataFactory.php b/src/php/Metadata/MetadataFactory.php index 6878a345444040ade56ee3bba9f1394b8f307cdb..1c8ecab78c5c8055ee1541ba5620cdac21d50d9c 100644 --- a/src/php/Metadata/MetadataFactory.php +++ b/src/php/Metadata/MetadataFactory.php @@ -30,6 +30,7 @@ use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInte use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\ResourceMetadata as APIResourceMetadata; use ApiPlatform\Core\PathResolver\OperationPathResolverInterface; +use Assert\Assertion; use Doctrine\Common\Inflector\Inflector; use Irstea\NgModelGeneratorBundle\Models\ClassName; use Irstea\NgModelGeneratorBundle\Models\PHPClass; @@ -145,8 +146,7 @@ final class MetadataFactory implements MetadataFactoryInterface return new ResourceMetadata( $class, $parentClass ? PHPClass::get($parentClass->getName()) : null, - $metadata->getShortName(), - $metadata->getDescription(), + $metadata->getDescription() ?: '', $classMeta->isAbstract(), $defaultNormalization, $this->getOperations($class) @@ -194,8 +194,11 @@ final class MetadataFactory implements MetadataFactoryInterface OperationType::ITEM => $resourceMetadata->getItemOperations(), OperationType::COLLECTION => $resourceMetadata->getCollectionOperations(), ] as $type => $ops) { + if (!$ops) { + continue; + } foreach ($ops as $name => $operation) { - $operations[] = $this->getOperation($class, $resourceMetadata, $name, $type, $operation); + $operations[] = $this->getOperation($class, $resourceMetadata, (string) $name, $type, $operation); } } @@ -224,22 +227,23 @@ final class MetadataFactory implements MetadataFactoryInterface $method = $this->operationMethodResolver->getCollectionOperationMethod($class->getFullName(), $name); } + $shortName = $resourceMetadata->getShortName(); + Assertion::notNull($shortName); /** @noinspection PhpMethodParametersCountMismatchInspection */ - $path = $this->operationPathResolver->resolveOperationPath($resourceMetadata->getShortName(), $operation, $type, $name); + $path = $this->operationPathResolver->resolveOperationPath($shortName, $operation, $type); // Suppression du suffixe de format, qui n'est pas utilisable en dehors de Symfony $path = \str_replace('.{_format}', '', $path); $getAttribute = function (string $attrName, $default) use ($resourceMetadata, $type, $name) { return $resourceMetadata->getTypedOperationAttribute($type, $name, $attrName, $default, true); - }; if ($type === OperationType::COLLECTION && $method === 'GET') { $filters = $this->getFilters($class, $getAttribute('filters', [])); - $paginationEnabled = (bool)$getAttribute('pagination_enabled', true); - $paginationClientItemsPerPage = (bool)$getAttribute('pagination_client_items_per_page', true); + $paginationEnabled = (bool) $getAttribute('pagination_enabled', true); + $paginationClientItemsPerPage = (bool) $getAttribute('pagination_client_items_per_page', true); $pagination = $this->buildPagination($paginationEnabled, $paginationClientItemsPerPage); } else { @@ -396,8 +400,9 @@ final class MetadataFactory implements MetadataFactoryInterface $properties[$propertyName] = $property; if ($property->isEmbedded()) { $type = $property->getLeafType(); - if ($type->getClassName()) { - $queue[] = PHPClass::get($type->getClassName()); + $className = $type->getClassName(); + if ($className) { + $queue[] = PHPClass::get($className); } } } diff --git a/src/php/Metadata/PropertyMetadata.php b/src/php/Metadata/PropertyMetadata.php index 83b7b49f799aa98fe4e28adbb72a70ec10e2ace6..b9fc8640d565fc5308012bdae7405d23142bae13 100644 --- a/src/php/Metadata/PropertyMetadata.php +++ b/src/php/Metadata/PropertyMetadata.php @@ -21,6 +21,7 @@ namespace Irstea\NgModelGeneratorBundle\Metadata; +use Assert\Assertion; use Irstea\NgModelGeneratorBundle\Models\HasName; use Symfony\Component\PropertyInfo\Type; @@ -66,6 +67,7 @@ class PropertyMetadata implements \JsonSerializable, HasName * @param string $description * @param Type $type * @param bool $identifier + * @param bool $nullable * @param bool $readable * @param bool $writable * @param bool $initializable @@ -132,8 +134,9 @@ class PropertyMetadata implements \JsonSerializable, HasName public function getLeafType(): Type { $type = $this->type; - while ($type && $type->isCollection() && $type->getCollectionValueType()) { + while ($type->isCollection() && $type->getCollectionValueType()) { $type = $type->getCollectionValueType(); + Assertion::notNull($type); } return $type; diff --git a/src/php/Metadata/PropertyMetadataFactory.php b/src/php/Metadata/PropertyMetadataFactory.php index 55d0e2542ac97289c3f118901575397214f3b4a7..a184aef168a0a8313417e45ce84a1bf7ed7d72f8 100644 --- a/src/php/Metadata/PropertyMetadataFactory.php +++ b/src/php/Metadata/PropertyMetadataFactory.php @@ -112,8 +112,8 @@ class PropertyMetadataFactory $typeMeta, $propertyMeta->isIdentifier() ?: false, $nullable, - $propertyMeta->isReadable(), - $propertyMeta->isWritable(), + $propertyMeta->isReadable() ?: false, + $propertyMeta->isWritable() ?: false, (bool) $propertyMeta->isInitializable(), $link, $embedded @@ -121,7 +121,7 @@ class PropertyMetadataFactory } /** - * @param $propertyMeta + * @param APIPropertyMetadata $propertyMeta * * @return array */ @@ -211,9 +211,8 @@ class PropertyMetadataFactory } /** - * @param string $mode + * @param bool $isResource * @param APIPropertyMetadata $propertyMeta - * @param mixed $isResource * * @return bool */ @@ -228,10 +227,10 @@ class PropertyMetadataFactory return $propertyMeta->isWritable() || $propertyMeta->isInitializable(); case self::MODE_READ: - return $propertyMeta->isReadable(); + return $propertyMeta->isReadable() ?: false; case self::MODE_UPDATE: - return $propertyMeta->isWritable(); + return $propertyMeta->isWritable() ?: false; default: return $propertyMeta->isReadable() || $propertyMeta->isWritable(); diff --git a/src/php/Metadata/ResourceClassHierarchy.php b/src/php/Metadata/ResourceClassHierarchy.php index 6d6be8bddbb2424885fc5d101d893adbffb8b8f8..fc4500a1035b724ca065a67f02b2c64caeea9318 100644 --- a/src/php/Metadata/ResourceClassHierarchy.php +++ b/src/php/Metadata/ResourceClassHierarchy.php @@ -59,7 +59,7 @@ final class ResourceClassHierarchy implements ClassHierarchy $reflClass = new \ReflectionClass($className); $reflParent = $reflClass->getParentClass(); if (!$reflParent) { - $this->parents[$className] = null; + unset($this->parents[$className]); return; } diff --git a/src/php/Metadata/ResourceMetadata.php b/src/php/Metadata/ResourceMetadata.php index 37e110561772c864fcbce05bdfbca6049b1eff91..8a7345c91a6b220dc5b6b1578a437a342ee9ed9a 100644 --- a/src/php/Metadata/ResourceMetadata.php +++ b/src/php/Metadata/ResourceMetadata.php @@ -51,7 +51,6 @@ class ResourceMetadata implements ClassName * * @param ClassName $class * @param ClassName|null $parentClass - * @param string $shortName * @param string $description * @param bool $abstract * @param SerializationMetadata $defaultNormalization @@ -60,7 +59,6 @@ class ResourceMetadata implements ClassName public function __construct( ClassName $class, ?ClassName $parentClass, - string $shortName, string $description, bool $abstract, SerializationMetadata $defaultNormalization, diff --git a/src/php/ModelGenerator.php b/src/php/ModelGenerator.php index 971e7d6e6288c340e84267a2fa9cb6596d3fdd53..c2543d9e73cda809da616040a693dbe95b7a0e6e 100644 --- a/src/php/ModelGenerator.php +++ b/src/php/ModelGenerator.php @@ -40,7 +40,6 @@ use Irstea\NgModelGeneratorBundle\Models\Types\StringConst; use Irstea\NgModelGeneratorBundle\Models\Types\Type; use Irstea\NgModelGeneratorBundle\Models\Types\Union; use Irstea\NgModelGeneratorBundle\Writers\MultiFileWriter; -use Irstea\NgModelGeneratorBundle\Writers\Writer; use Symfony\Component\PropertyInfo\Type as PHPType; use Twig\Environment; @@ -58,7 +57,7 @@ final class ModelGenerator /** @var Documentation */ private $documentation; - /** @var TypeFactory */ + /** @var TypeFactoryInterface */ private $typeFactory; /** @var SerializationMapperFactoryInterface */ @@ -82,8 +81,12 @@ final class ModelGenerator * Génère les modèles Typescript. * Cette méthode n'est pas réentrante. * - * @param Documentation $doc - * @param Writer $writer + * @param Documentation $doc + * @param MultiFileWriter $writer + * + * @throws \Twig_Error_Loader + * @throws \Twig_Error_Runtime + * @throws \Twig_Error_Syntax */ public function generate(Documentation $doc, MultiFileWriter $writer): void { @@ -108,8 +111,6 @@ final class ModelGenerator * @throws \Twig_Error_Loader * @throws \Twig_Error_Runtime * @throws \Twig_Error_Syntax - * - * @return string */ private function doGenerate(MultiFileWriter $writer): void { @@ -170,8 +171,6 @@ final class ModelGenerator * @throws \Twig_Error_Loader * @throws \Twig_Error_Runtime * @throws \Twig_Error_Syntax - * - * @return string */ private function generateFile(MultiFileWriter $writer, string $path, array $context): void { diff --git a/src/php/Models/ClassInfo.php b/src/php/Models/ClassInfo.php index cb303e494104c0dc6717b268b758489f9266b10c..b6c93e869d35a76b0229b8070edba76c48f31326 100644 --- a/src/php/Models/ClassInfo.php +++ b/src/php/Models/ClassInfo.php @@ -22,6 +22,7 @@ namespace Irstea\NgModelGeneratorBundle\Models; use Irstea\NgModelGeneratorBundle\Exceptions\DomainException; +use Irstea\NgModelGeneratorBundle\Metadata\PropertyMetadata; /** * Class ClassInfo. @@ -39,10 +40,10 @@ final class ClassInfo implements ClassName /** @var self|false|null */ private $parent = false; - /** @var HasName[] */ + /** @var PropertyMetadata[] */ private $virtualProperties = []; - /** @var HasName[] */ + /** @var PropertyMetadata[] */ private $concreteProperties = []; /** @var self[] */ @@ -57,11 +58,11 @@ final class ClassInfo implements ClassName /** * ClassInfo constructor. * - * @param ClassName $class - * @param HasName[] $properties - * @param bool $abstract + * @param ClassName $class + * @param PropertyMetadata[] $properties + * @param bool $abstract */ - public function __construct(ClassName $class, array $properties = [], bool $abstract) + public function __construct(ClassName $class, array $properties = [], bool $abstract = false) { $this->class = $class; $this->abstract = $abstract; @@ -99,7 +100,7 @@ final class ClassInfo implements ClassName /** * Get properties. * - * @return HasName[] + * @return PropertyMetadata[] */ public function getVirtualProperties(): array { @@ -109,7 +110,7 @@ final class ClassInfo implements ClassName /** * Get properties. * - * @return HasName[] + * @return PropertyMetadata[] */ public function getConcreteProperties(): array { diff --git a/src/php/Models/Types/AbstractType.php b/src/php/Models/Types/AbstractType.php index 018cb9090aa5e2c2acaeb67c4e40713bbe7c85ae..a9976ed960f6abc18c9c41722f4af6809ca8387a 100644 --- a/src/php/Models/Types/AbstractType.php +++ b/src/php/Models/Types/AbstractType.php @@ -62,7 +62,7 @@ abstract class AbstractType implements Type } /** - * @return string + * @return mixed */ public function jsonSerialize() { diff --git a/src/php/Models/Types/Objects/AnonymousObject.php b/src/php/Models/Types/Objects/AnonymousObject.php index ffd2b6c8e0bee8871c3f54311d9e91782a1a3ebc..9799b69f2c22f67fcd086f1e6dbe45459de32d2a 100644 --- a/src/php/Models/Types/Objects/AnonymousObject.php +++ b/src/php/Models/Types/Objects/AnonymousObject.php @@ -167,8 +167,6 @@ class AnonymousObject extends AbstractType } /** - * @param bool $multiline - * * @return array */ protected function getMethodDeclarations(): array diff --git a/src/php/Models/Types/Objects/Repository.php b/src/php/Models/Types/Objects/Repository.php index cc6e36a88f392b228c4d72deed4e27942cb9aec3..e6a1165b74dd6cb5037dea25b6106b38b899071c 100644 --- a/src/php/Models/Types/Objects/Repository.php +++ b/src/php/Models/Types/Objects/Repository.php @@ -122,24 +122,6 @@ final class Repository extends ClassType return $this->identifier; } - /** - * {@inheritdoc} - */ - protected function getMethodDeclarations(): array - { - $parts = $this->getStaticMethodDeclarations(); - - $parts[] = $this->getConstructor(); - - $parts[] = $this->getResolveImplementation(); - - foreach ($this->getOperations() as $operation) { - $parts[] = $operation->getDeclaration() . "\n"; - } - - return $parts; - } - /** * {@inheritdoc} */ @@ -195,77 +177,6 @@ CODE ); } - /** - * @return array - */ - private function getStaticMethodDeclarations(): array - { - if (!$this->iri) { - return []; - } - - $parts = []; - - // Vérification d'IRI - $parts[] = sprintf( - /* @lang typescript */ - <<<'CODE' -/** - * Regexp pour reconnaître une IRI de cette ressource. - */ -public static readonly IRI_PATTERN = /^%s$/ui; - -/** - * Vérifie si le paramètre est une IRI valide de ce repository. - */ -public static isIRI(data: string): data is IRI<%s> { - return %s.IRI_PATTERN.test(data); -} -CODE - , - $this->iri->getPattern(), - $this->resourceType->getUsage(), - $this->getName() - ); - - $idProp = $this->identifier; - $idParam = $this->iri->getIdentifier(); - - if ($idProp && $idParam) { - $idType = $idProp->getType(); - - $parts[] = sprintf( - /* @lang typescript */ - <<<'CODE' - -/** - * Extrait l'identifiant d'une IRI ou vérifie un identifiant. - */ -public static getID(data: %s): %s { - const match = %s.IRI_PATTERN.exec(data); - if (match !== null) { - return %s; - } - if (%s) { - return %s; - } - throw new Error(`Cannot extract %s id from ${data}`); -} -CODE - , - $idParam->getType()->getUsage(), - $idType->getUsage(), - $this->getName(), - $idType->castToStringOrStringArray('match[1]'), - $idType->checkType('data'), - $idType->castToStringOrStringArray('data'), - $this->getResourceName() - ); - } - - return $parts; - } - /** * {@inheritdoc} */ diff --git a/src/php/Models/Types/Operations/Parameter.php b/src/php/Models/Types/Operations/Parameter.php index fdd2c2b181f4f6f7775369f492883aac3889aaf3..a69b263ce4b58e9bb1c32f470595007a61595a27 100644 --- a/src/php/Models/Types/Operations/Parameter.php +++ b/src/php/Models/Types/Operations/Parameter.php @@ -42,7 +42,7 @@ class Parameter implements \IteratorAggregate, \JsonSerializable private $optional = false; /** - * @var string + * @var string|null */ private $default; diff --git a/src/php/Models/Types/Reference.php b/src/php/Models/Types/Reference.php index 78ed019f93f16476bb48ab71ec3993f09ba5d9b8..61f41add399ada1893e63e6df8814410ace0f307 100644 --- a/src/php/Models/Types/Reference.php +++ b/src/php/Models/Types/Reference.php @@ -36,7 +36,7 @@ class Reference extends AbstractType implements Deferred /** @var string */ private $name; - /** @var bool */ + /** @var int */ private $state = self::UNRESOLVED; /** diff --git a/src/php/Models/Types/Union.php b/src/php/Models/Types/Union.php index c488e9f57730665e1d02db68dde1aedac5343c57..52b1ea97c32ad71095129289b7fb8cb2e4a1a529 100644 --- a/src/php/Models/Types/Union.php +++ b/src/php/Models/Types/Union.php @@ -112,7 +112,7 @@ final class Union extends AbstractType case 0: return BuiltinType::get('never'); case 1: - return array_shift($types); + return $types[0]; default: return new Union($types); } diff --git a/src/php/OperationMapper.php b/src/php/OperationMapper.php index 247a7921cb414d755ab20ad035dee9eee52a8b71..20f5c91068239322b2d9f5f4ccc76810b02a6222 100644 --- a/src/php/OperationMapper.php +++ b/src/php/OperationMapper.php @@ -21,6 +21,7 @@ namespace Irstea\NgModelGeneratorBundle; +use Assert\Assertion; use Doctrine\Common\Inflector\Inflector; use Irstea\NgModelGeneratorBundle\Exceptions\InvalidArgumentException; use Irstea\NgModelGeneratorBundle\Metadata\OperationMetadata; @@ -106,7 +107,7 @@ final class OperationMapper $callPath = $path = $this->pathParser->parse($this->operation->getPath(), $this->operation->getRequirements()); - $iriParameterName = null; + $iriParameter = $iriParameterName = null; if ($this->iri) { $iriParameter = new Parameter('iri', Placeholder::get(sprintf('IRI<%s>', $this->operation->getClassName()))); @@ -115,6 +116,7 @@ final class OperationMapper } if ($callPath === $this->iri) { + Assertion::notNull($iriParameter); $opParameters[] = $iriParameter; $iriParameterName = $iriParameter->getName(); $callParameters[] = $iriParameterName . ' as any'; @@ -257,7 +259,9 @@ final class OperationMapper } } - $propName = Inflector::camelize(preg_replace('/\W+/', '_', $name)); + /** @var string $alphanumName */ + $alphanumName = preg_replace('/\W+/', '_', $name); + $propName = Inflector::camelize($alphanumName); $type = $this->resolveFilterType($filterType, $type); diff --git a/src/php/PathParser.php b/src/php/PathParser.php index 653daa220df992449e0052a3d18aad34e3d8bfd1..1b53b70d09a22f5f209cf1ee5a059bd452eab84a 100644 --- a/src/php/PathParser.php +++ b/src/php/PathParser.php @@ -21,6 +21,7 @@ namespace Irstea\NgModelGeneratorBundle; +use Assert\Assertion; use Irstea\NgModelGeneratorBundle\Models\Types\BuiltinType; use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property; use Irstea\NgModelGeneratorBundle\Models\Types\Operations\FixedPathPart; @@ -52,6 +53,7 @@ final class PathParser implements PathParserInterface public function parse(string $path, array $requirements): Path { $stringParts = preg_split('/\{(\w+)\}/', $path, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + Assertion::isArray($stringParts); $parts = []; diff --git a/src/php/SerializationMapper.php b/src/php/SerializationMapper.php index bce64a1d0494a4bcbea437ae00de5757166c2ad2..7ec928aecf34fccf3ad6aa25f9e951f7e1a15053 100644 --- a/src/php/SerializationMapper.php +++ b/src/php/SerializationMapper.php @@ -21,6 +21,7 @@ namespace Irstea\NgModelGeneratorBundle; +use Assert\Assertion; use Irstea\NgModelGeneratorBundle\Exceptions\DomainException; use Irstea\NgModelGeneratorBundle\Metadata\PropertyMetadata; use Irstea\NgModelGeneratorBundle\Metadata\RepresentationMetadata; @@ -164,6 +165,7 @@ final class SerializationMapper implements TypeFactoryInterface $desc = trim(implode("\n", $desc)); if ($classInfo->isUnion()) { + /** @var Type[] $types */ $types = []; foreach ($classInfo->iterateInterfaceDescendants() as $child) { $types[] = $this->get($child->getFullName()); @@ -173,7 +175,10 @@ final class SerializationMapper implements TypeFactoryInterface case 0: throw new DomainException(sprintf('Union with no children: %s', $repr)); case 1: - return new Reference($types[0]); + $ref = new Reference($types[0]->getUsage()); + $ref->resolve($types[0]); + + return $ref; default: return new Alias($repr->getName(), Union::create($types), $desc); } @@ -294,44 +299,53 @@ final class SerializationMapper implements TypeFactoryInterface */ private function mapProperty(PropertyMetadata $propertyMeta): Property { - $typeMeta = $propertyMeta->getType(); - \assert($typeMeta !== null); - - $collections = []; - $leafType = $typeMeta; - while ($leafType->isCollection()) { - $collections[] = $leafType->getCollectionKeyType(); - $leafType = $leafType->getCollectionValueType(); - } - - $leafClassName = $leafType->getClassName(); - if ($propertyMeta->isLink() && !$propertyMeta->isEmbedded()) { - $type = $this->createIRI([PHPClass::get($leafClassName)]); - } else { - $type = $this->get($leafClassName ?: $leafType->getBuiltinType()); - } - - $number = BuiltinType::get('number'); - foreach (\array_reverse($collections) as $indexTypeMeta) { - /** @var APIType $indexTypeMeta */ - $indexType = $this->get($indexTypeMeta->getClassName() ?: $indexTypeMeta->getBuiltinType()); - if ($indexType === $number) { - $type = new ArrayType($type); - } else { - throw new DomainException("Cannot create collection with key $indexType"); - } - } - return new Property( $propertyMeta->getName(), $propertyMeta->getDescription() ?: '', - $type, + $this->mapType($propertyMeta->getType(), $propertyMeta->isLink() && !$propertyMeta->isEmbedded()), $propertyMeta->isIdentifier(), $propertyMeta->isNullable(), !$propertyMeta->isWritable() ); } + /** + * @param APIType $type + * @param bool $isLink + * + * @return Type + */ + private function mapType(?APIType $type, bool $isLink = false): Type + { + if ($type === null) { + return BuiltinType::get('any'); + } + + if (!$type->isCollection()) { + $className = $type->getClassName(); + if ($className === null) { + return $this->get($type->getBuiltinType()); + } + if ($isLink) { + return $this->createIRI([PHPClass::get($className)]); + } + + return $this->get($className); + } + + $indexType = $type->getCollectionKeyType(); + Assertion::notNull($indexType, 'Cannot handle collection with undefined index type'); + + $builtinType = $indexType->getBuiltinType(); + if ($builtinType !== 'int') { + throw new \InvalidArgumentException("Cannot handle collection with index type $builtinType"); + } + + $valueType = $this->mapType($type->getCollectionValueType(), $isLink); + + return new ArrayType($valueType); + } + /** * @param ClassName $class * @@ -343,7 +357,11 @@ final class SerializationMapper implements TypeFactoryInterface $this->init(); } - return $this->classInfo[$class->getFullName()]; + $name = $class->getFullName(); + $info = $this->classInfo[$name] ?? null; + Assertion::notNull($info, 'Unknown class $name'); + + return $info; } private function init(): void @@ -363,8 +381,9 @@ final class SerializationMapper implements TypeFactoryInterface // Crée les liens de parenté foreach ($reprs as $className => $repr) { $classInfo = $this->classInfo[$className]; - if ($repr->getParent()) { - $classInfo->setParent($this->classInfo[$repr->getParent()->getFullName()]); + $parent = $repr->getParent(); + if ($parent) { + $classInfo->setParent($this->classInfo[$parent->getFullName()]); } } @@ -372,6 +391,5 @@ final class SerializationMapper implements TypeFactoryInterface foreach ($this->classInfo as $className => $classInfo) { $classInfo->rearrangeHiearchy(); } - //echo json_encode($this->classInfo, \JSON_PRETTY_PRINT) . "\n"; } } diff --git a/src/php/TypeFactoryInterface.php b/src/php/TypeFactoryInterface.php index bfd16413b05bbeee397c32936ec8f7d54ac80b06..4b2ae3f9e80eb61b8c3f3260c76ba3afd57dbeb1 100644 --- a/src/php/TypeFactoryInterface.php +++ b/src/php/TypeFactoryInterface.php @@ -46,7 +46,7 @@ interface TypeFactoryInterface /** * @param string $name * - * @return Type + * @return Deferred */ public function defer(string $name): Deferred; diff --git a/src/php/Writers/DirectoryWriter.php b/src/php/Writers/DirectoryWriter.php index e11a674108cc4013cb9cf4b0a74bd2152688f7e8..4eca46ad18a6521c037bcfcc5a5ba0d9a44a9c80 100644 --- a/src/php/Writers/DirectoryWriter.php +++ b/src/php/Writers/DirectoryWriter.php @@ -21,6 +21,8 @@ namespace Irstea\NgModelGeneratorBundle\Writers; +use Assert\Assertion; + /** * Class DirectoryWriter. */ @@ -52,7 +54,10 @@ final class DirectoryWriter implements MultiFileWriter throw new \RuntimeException(sprintf('Directory "%s" was not created', $fileDir)); } - return new StreamWriter(fopen($filePath, 'wb')); + $fh = fopen($filePath, 'wb'); + Assertion::isResource($fh); + + return new StreamWriter($fh); } public function close(): void diff --git a/src/php/Writers/ZipWriter.php b/src/php/Writers/ZipWriter.php index 9f3e917f59ac62864b75826b8881f4f491f054fc..b60380456847d8773fb3bd09d1937261a0e7581b 100644 --- a/src/php/Writers/ZipWriter.php +++ b/src/php/Writers/ZipWriter.php @@ -32,7 +32,7 @@ class ZipWriter implements MultiFileWriter /** * ZipWriter constructor. * - * @param $archive + * @param \ZipArchive $archive */ public function __construct(\ZipArchive $archive) {