Commit f3ee1892 authored by Guillaume Perréal's avatar Guillaume Perréal
Browse files

Amélioration de la vérification des ressources et des IRI.

parent 846f449e
......@@ -138,6 +138,12 @@ final class MetadataFactory implements MetadataFactoryInterface
$metadata->getShortName(),
$metadata->getDescription(),
$classMeta->isAbstract(),
$this->getOperationSerialization(
null,
$class,
true,
$metadata->getAttribute('normalization_context', [])['groups'] ?? []
),
$this->getOperations($class)
);
}
......@@ -298,16 +304,21 @@ final class MetadataFactory implements MetadataFactoryInterface
/**
* @parma string $operationName
*
* @param PHPClass $class
* @param bool $normalization
* @param string[] $groups
* @param OperationDef|null $opDef
* @param PHPClass $class
* @param bool $normalization
* @param string[] $groups
*
* @throws \ApiPlatform\Core\Exception\PropertyNotFoundException
* @throws \ApiPlatform\Core\Exception\ResourceClassNotFoundException
* @throws \ReflectionException
*
* @return SerializationMetadata
*/
private function getOperationSerialization(OperationDef $opDef, PHPClass $class, bool $normalization, array $groups): SerializationMetadata
private function getOperationSerialization(?OperationDef $opDef, PHPClass $class, bool $normalization, array $groups): SerializationMetadata
{
sort($groups);
$key = sprintf('%s:%d:%s:%s', $class->getFullName(), $normalization, $opDef->getName(), implode('+', $groups));
$key = sprintf('%s:%d:%s:%s', $class->getFullName(), $normalization, $opDef ? $opDef->getName() : '_Default_', implode('+', $groups));
if (!isset($this->serializations[$key])) {
$this->serializations[$key] = $this->doGetSerialization($class, $normalization, $opDef, $groups);
}
......@@ -327,9 +338,9 @@ final class MetadataFactory implements MetadataFactoryInterface
*
* @return SerializationMetadata
*/
private function doGetSerialization(PHPClass $class, bool $normalization, OperationDef $opDef, array $groups): SerializationMetadata
private function doGetSerialization(PHPClass $class, bool $normalization, ?OperationDef $opDef, array $groups): SerializationMetadata
{
$selfNamePrefix = Inflector::classify($opDef->getName());
$selfNamePrefix = $opDef ? Inflector::classify($opDef->getName()) : implode('', $groups);
$otherNamePrefix = $selfNamePrefix . $class->getBaseName();
if ($normalization) {
......@@ -340,9 +351,9 @@ final class MetadataFactory implements MetadataFactoryInterface
$selfNamePrefix = '';
}
$propFilter = 'filterGetProperty';
} elseif ($opDef->isCreateItem()) {
} elseif ($opDef && $opDef->isCreateItem()) {
$propFilter = 'filterCreateProperty';
} elseif ($opDef->isUpdateItem()) {
} elseif ($opDef && $opDef->isUpdateItem()) {
$propFilter = 'filterUpdateProperty';
} else {
$propFilter = 'filterAnyProperty';
......
......@@ -35,7 +35,8 @@ class PropertyMetadata implements \JsonSerializable
/** @var Type */
private $type;
/** @var bool */private $identifier;
/** @var bool */
private $identifier;
/** @var bool */
private $readable;
......@@ -201,13 +202,26 @@ class PropertyMetadata implements \JsonSerializable
*/
private function serializeType(?Type $type)
{
return $type ? [
'builtin_type' => $type->getBuiltinType(),
'class_name' => $type->getClassName(),
'nullable' => $type->isNullable(),
'collection' => $type->isCollection(),
'collection_key_type' => $this->serializeType($type->getCollectionKeyType()),
'collection_value_type' => $this->serializeType($type->getCollectionValueType()),
] : null;
if (!$type) {
return null;
}
if ($type->getCollectionValueType()) {
return [
'key_type' => $this->serializeType($type->getCollectionKeyType()),
'value_type' => $this->serializeType($type->getCollectionValueType()),
'nullable' => $type->isNullable(),
];
}
if ($type->getClassName()) {
return [
'class_name' => $type->getClassName(),
'nullable' => $type->isNullable(),
];
}
return [
'builtin_type' => $type->getBuiltinType(),
'nullable' => $type->isNullable(),
];
}
}
......@@ -41,15 +41,21 @@ class ResourceMetadata implements \JsonSerializable
/** @var OperationMetadata[] */
private $operations = [];
/**
* @var SerializationMetadata
*/
private $defaultNormalization;
/**
* ResourceMetadata constructor.
*
* @param PHPClass $class
* @param PHPClass|null $parentClass
* @param string $shortName
* @param string $description
* @param bool $abstract
* @param OperationMetadata[] $operations
* @param PHPClass $class
* @param PHPClass|null $parentClass
* @param string $shortName
* @param string $description
* @param bool $abstract
* @param SerializationMetadata $defaultNormalization
* @param OperationMetadata[] $operations
*/
public function __construct(
PHPClass $class,
......@@ -57,12 +63,14 @@ class ResourceMetadata implements \JsonSerializable
string $shortName,
string $description,
bool $abstract,
SerializationMetadata $defaultNormalization,
array $operations
) {
$this->class = $class;
$this->parentClass = $parentClass;
$this->abstract = $abstract;
$this->description = $description;
$this->defaultNormalization = $defaultNormalization;
foreach ($operations as $operation) {
$this->operations[$operation->getName() . $operation->getType()] = $operation->withResource($this);
......@@ -130,6 +138,16 @@ class ResourceMetadata implements \JsonSerializable
return $this->abstract;
}
/**
* Get class.
*
* @return PHPClass
*/
public function getClass(): PHPClass
{
return $this->class;
}
/**
* Get operations.
*
......@@ -140,6 +158,16 @@ class ResourceMetadata implements \JsonSerializable
return $this->operations;
}
/**
* Get defaultNormalization.
*
* @return SerializationMetadata
*/
public function getDefaultNormalization(): SerializationMetadata
{
return $this->defaultNormalization;
}
/**
* {@inheritdoc}
*/
......
......@@ -138,6 +138,22 @@ final class SerializationMetadata implements \JsonSerializable
return $this->normalization;
}
/**
* @return PHPClass[]
*/
public function getRootSubClasses(): array
{
$subclasses = [];
$rootClassName = $this->getRoot()->getFullName();
foreach ($this->representations as $className => $meta) {
if (\is_subclass_of($className, $rootClassName, true)) {
$subclasses[$className] = PHPClass::get($className);
}
}
return $subclasses;
}
/**
* {@inheritdoc}
*/
......
......@@ -33,6 +33,7 @@ use Irstea\NgModelGeneratorBundle\Models\Types\BuiltinType;
use Irstea\NgModelGeneratorBundle\Models\Types\Objects\InterfaceType;
use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property;
use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Repository;
use Irstea\NgModelGeneratorBundle\Models\Types\Reference;
use Irstea\NgModelGeneratorBundle\Models\Types\Resources\UUID;
use Irstea\NgModelGeneratorBundle\Models\Types\Type;
use Symfony\Component\PropertyInfo\Type as PHPType;
......@@ -103,9 +104,9 @@ final class ModelGenerator
{
$this->typeFactory = $this->createTypeFactory();
[$repositories, $iriPatterns] = $this->extractRepositories();
$repos = $this->extractRepositories();
$declarations = $this->extractDeclarations($repositories);
$declarations = $this->extractDeclarations($repos);
return $this->twigEnv->render(
'@NgModelGenerator/typescript_models.ts.twig',
......@@ -113,10 +114,13 @@ final class ModelGenerator
'title' => $this->documentation->getTitle(),
'version' => $this->documentation->getVersion(),
'description' => $this->documentation->getDescription() ?: '',
'resources' => array_keys($iriPatterns),
'repositories' => $repositories,
'repositories' => array_filter(
$declarations,
function (Declaration $d) {
return $d instanceof Repository;
}
),
'declarations' => $declarations,
'iriPatterns' => $iriPatterns,
]
);
}
......@@ -204,50 +208,65 @@ final class ModelGenerator
}
/**
* @return array
* @return Repository[]
*/
private function extractRepositories(): array
{
$repositories = [];
$iriPatterns = [];
/**
* @var PHPClass
* @var ResourceMetadata $resourceMeta
*/
foreach ($this->getResourceMetadata() as $class => $resourceMeta) {
// printf("\n\n===================================\n\n%s\n", \json_encode($resourceMeta, \JSON_PRETTY_PRINT));
// continue;
$repoName = $resourceMeta->getShortName() . 'Repository';
$repositories[$repoName] = $this->typeFactory->getOrCreate(
$repoName,
function () use ($resourceMeta): Type {
return $this->buildRepositories($resourceMeta);
}
);
}
function () use ($resourceMeta, &$iriPatterns): Type {
$operations = [];
ksort($repositories);
foreach ($resourceMeta->getOperations() as $operation) {
$mapper = new OperationMapper(
$this->typeFactory,
$operation
);
$operations[] = $operation = $mapper();
return $repositories;
}
if ($operation->getName() === 'get') {
$iriPatterns[$resourceMeta->getShortName()] = $operation->getPath();
}
}
/**
* @param ResourceMetadata $resourceMeta
*
* @return Repository
*/
private function buildRepositories(ResourceMetadata $resourceMeta): Repository
{
$operations = [];
$iri = null;
return new Repository($resourceMeta->getShortName(), $operations);
}
);
foreach ($resourceMeta->getOperations() as $operation) {
$mapper = new OperationMapper($this->typeFactory, $operation);
$operations[] = $op = $mapper();
if ($operation->getOpDef()->isGetItem()) {
$iri = $op->getPath();
}
}
ksort($repositories);
ksort($iriPatterns);
$resourceRepr = $resourceMeta->getDefaultNormalization()->getRepresentationOf($resourceMeta->getClass());
return [$repositories, $iriPatterns];
/** @var InterfaceType $resourceType */
$resourceType = Reference::resolveAs(
$this->typeFactory->get($resourceRepr->getName()),
InterfaceType::class
);
return new Repository(
$resourceMeta->getClass(),
$operations,
$resourceType,
$iri
);
}
/**
......
<?php declare(strict_types=1);
/*
* irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata.
* Copyright (C) 2018 IRSTEA
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License and the GNU
* Lesser General Public License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
namespace Irstea\NgModelGeneratorBundle\Models;
use Irstea\NgModelGeneratorBundle\Metadata\PropertyMetadata;
/**
* Class ClassInfo.
*/
final class ClassInfo implements \JsonSerializable
{
public const UNDEFINED = 'UNDEFINED';
public const EMBEDDED = 'EMBEDDED';
public const IRI = 'IRI';
public const UNION = 'UNION';
public const INTERFACE = 'INTERFACE';
/** @var PHPClass */
private $resource;
/** @var ClassInfo|null */
private $parent;
/** @var PropertyMetadata[] */
private $properties;
/** @var ClassInfo[] */
private $children = [];
/** @var int */
private $refCount = 0;
/** @var string */
private $type = self::UNDEFINED;
/**
* ClassInfo constructor.
*
* @param PHPClass $resource
* @param PropertyMetadata[] $properties
*/
public function __construct(
PHPClass $resource,
array $properties = []
) {
$this->resource = $resource;
$this->properties = $properties;
}
public function destroy(): void
{
$this->setType(self::UNDEFINED);
}
/**
* Get resource.
*
* @return PHPClass
*/
public function getResource(): PHPClass
{
return $this->resource;
}
/**
* Get properties.
*
* @return PropertyMetadata[]
*/
public function getProperties(): array
{
return $this->properties;
}
/**
* Get parent.
*
* @return ClassInfo|null
*/
public function getParent(): ?ClassInfo
{
return $this->parent;
}
/**
* Set parent.
*
* @param ClassInfo|null $parent
*/
public function setParent(?ClassInfo $parent): void
{
if ($parent === $this->parent) {
return;
}
$oldParent = $this->parent;
$this->parent = $parent;
if ($oldParent) {
$oldParent->removeChild($this);
}
if ($parent) {
$parent->addChild($this);
}
}
/**
* Get children.
*
* @return ClassInfo[]
*/
public function getChildren(): array
{
return $this->children;
}
/**
* @param ClassInfo $child
*/
public function addChild(ClassInfo $child): void
{
if (\in_array($child, $this->children, true)) {
return;
}
$this->children[] = $child;
$child->setParent($this);
}
/**
* @param ClassInfo $child
*/
public function removeChild(ClassInfo $child): void
{
$key = \array_search($child, $this->children, true);
if ($key === false) {
return;
}
unset($this->children[$key]);
$child->setParent(null);
}
/**
* @param int $amount
*/
public function incRefCount(int $amount = 1): void
{
$this->refCount += $amount;
foreach ($this->children as $child) {
$child->incRefCount($amount);
}
}
/**
* @param int $threshold
*
* @return bool
*/
public function isRefCountAbove(int $threshold): bool
{
return $this->refCount > $threshold;
}
/**
* Test type.
*
* @param string $type
*/
public function isType(string $type): bool
{
return $this->type === $type;
}
/**
* Set type.
*
* @param string $type
*/
public function setType(string $type): void
{
$this->type = $type;
if ($type !== self::INTERFACE && $type !== self::UNION) {
foreach ($this->children as $child) {
$child->setParent($this->parent);
}
}
if ($type !== self::INTERFACE) {
if ($this->parent) {
$this->setParent(null);
}
}
}
/**
* @return bool
*/
public function isInterface(): bool
{
return $this->isType(self::INTERFACE);
}
/**
* @return bool
*/
public function isUnion(): bool
{
return $this->isType(self::UNION);
}
/**
* @return bool
*/
public function isEmbedded(): bool
{
return $this->isType(self::EMBEDDED);
}
/**
* @return bool
*/
public function isUndefined(): bool
{
return $this->isType(self::UNDEFINED);
}
/**
* @return bool
*/
public function isIRI(): bool
{
return $this->isType(self::IRI);
}
/**
* @return \Generator
*/
public function iterateDescendants(?string $typeFilter): \Generator
{
foreach ($this->children as $child) {
if (!$typeFilter || $child->isType($typeFilter)) {
yield $child->getResource() => $child;
}
yield from $child->iterateDescendants($typeFilter);
}
}
/**
* {@inheritdoc}
*/
public function jsonSerialize()
{
return [
'resource' => $this->resource,
'refCount' => $this->refCount,
'type' => $this->type,
'parent' => $this->parent ? $this->parent->getResource() : null,
'children' => array_map(
function (ClassInfo $ci) {
return $ci->getResource();
},
$this->children
),
'properties' => $this->properties,
];
}
}
......@@ -43,10 +43,15 @@ class InterfaceType extends AbstractHierarchicalObject
/** @var Property $property */
foreach ($this->getProperties() as $property) {
if (!$property->isNullable()) {
$tests[] = sprintf('%s in %s', TypescriptHelper::quoteString($property->getName()), $expr);
$tests[] = sprintf(
'(%s in %s && %s)',
TypescriptHelper::quoteString($property->getName()),
$expr,
$property->getType()->checkType($property->getUsage($expr))
);