From 2ec19af77e010235ff459a491d04e7f3f33be5f6 Mon Sep 17 00:00:00 2001
From: Perreal Guillaume <guillaume.perreal@irstea.fr>
Date: Tue, 20 Aug 2019 13:46:13 +0200
Subject: [PATCH] =?UTF-8?q?Fonctionnement=20de=20base=20r=C3=A9staur=C3=A9?=
 =?UTF-8?q?.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 composer.json                                 |   1 +
 composer.lock                                 | Bin 222354 -> 225135 bytes
 src/php/Exceptions/EmptyModelException.php    |  20 ++++++
 src/php/Exceptions/InvalidModelException.php  |  44 ++++++++++++
 .../Exceptions/MissingIdentifierException.php |  20 ++++++
 .../TooManyIdentifiersException.php           |  20 ++++++
 src/php/ModelGenerator.php                    |  63 +++++++++---------
 src/php/Models/Types/Deferred.php             |  38 -----------
 .../Types/Factory/DeferrableTypeFactory.php   |   8 ++-
 src/php/Models/Types/Factory/TypeCache.php    |  27 ++++++--
 src/php/Models/Types/Reference.php            |  54 +++++++--------
 src/php/SerializationMapper.php               |  32 +--------
 12 files changed, 190 insertions(+), 137 deletions(-)
 create mode 100644 src/php/Exceptions/EmptyModelException.php
 create mode 100644 src/php/Exceptions/InvalidModelException.php
 create mode 100644 src/php/Exceptions/MissingIdentifierException.php
 create mode 100644 src/php/Exceptions/TooManyIdentifiersException.php
 delete mode 100644 src/php/Models/Types/Deferred.php

diff --git a/composer.json b/composer.json
index c767394..fcbde08 100644
--- a/composer.json
+++ b/composer.json
@@ -52,6 +52,7 @@
         "ramsey/uuid": "^3.8",
         "ramsey/uuid-doctrine": "^1.5",
         "symfony/framework-bundle": "^4.3",
+        "symfony/var-dumper": "^4.3",
         "symfony/yaml": "^4.3"
     },
     "scripts": {
diff --git a/composer.lock b/composer.lock
index 75227eb0e125a54c689cf7e965eb08d9054b24d6..2bd2df8c4617d8e30fdf121a1713f94e248acc31 100644
GIT binary patch
delta 736
zcma))&ubGw6vx@!#)u&_S|a$Xj*-%c3HvMAY<fzJq(Ko|ZAB0&`(rj4+@0C(Zfetm
zP^x(HU<a9_;!QlLAm&;`3ZA6rfapJ<|3Ps#){~bW9`ktfeeZqVo7w)F{rx@reu@KH
zO*3U(b8J;NE1D_Ex~gbOMKTnp>OiNurE*VIE-`+nazdv~Z(3>gcwTruVfQ_sHuh(W
zup-NnS+6U$Vwq5`>vqjnO{G$?rCQyRt*Tzr<k+W+XlriG2+Bz&48#rOn}+L(hs<ZI
z7rDahVa3gB+)4W5{yVz!ImJJc+1@=K(A>;4YZQbQn|{J&X#Q@C?Y!hCQph5BjeU5-
zonq20VT!&mXQ*gr=#AIs*sm&=V{aQm`@vXMm;;3+0+Iw6L;w?9US4XKbr41Y!rc%=
z1pEyYNdUru=MX#)ec}!r<htc?4S+DPj1U629vwt117i{a6M}vS?MrkupC6-@5RTk~
zo?(wX1Oe<Kf&n%>n3Rc7d~Og7LA+DCx1S#m(2NP=Iw0yna2p0O>D74SE#i3wwn1!S
z%$8i52#fz7q#TZwAhZJHvs{|jDIOg=@xO#MJ^mSMT;Z0f_vL&d$~t{)MKM<>CKwk+
zml+VbBK`I;lfGk&n-3SS3y`O;c8^LPw0Z_cp$D881FrinNmiF|B~$o`ztC9#F&HvT
x7t)`*D=8bA{Vv^1=Y{0g$%PV==7l3{vLrOwnMonbK8^U(+biMlct_Yg`v*rs_M-p*

delta 78
zcmaEVk9X2Z-VMTx3YMuRMyW}OW~RpG7KY|VX_l!eMn(n(mc}L)$p!|diOt50?Z%8C
g%(UH@k=es-x;#HK=k#s2nWQGg6>onR#M~$Y0F`PN5&!@I

diff --git a/src/php/Exceptions/EmptyModelException.php b/src/php/Exceptions/EmptyModelException.php
new file mode 100644
index 0000000..5d9ba0f
--- /dev/null
+++ b/src/php/Exceptions/EmptyModelException.php
@@ -0,0 +1,20 @@
+<?php declare(strict_types=1);
+/**
+ * Copyright (C) 2019 IRSTEA
+ * All rights reserved.
+ *
+ * @copyright 2019 IRSTEA
+ * @author guillaume.perreal
+ */
+
+
+namespace Irstea\NgModelGeneratorBundle\Exceptions;
+
+
+/**
+ * Class EmptyModelException
+ */
+final class EmptyModelException extends InvalidModelException
+{
+
+}
diff --git a/src/php/Exceptions/InvalidModelException.php b/src/php/Exceptions/InvalidModelException.php
new file mode 100644
index 0000000..c00379a
--- /dev/null
+++ b/src/php/Exceptions/InvalidModelException.php
@@ -0,0 +1,44 @@
+<?php declare(strict_types=1);
+/**
+ * Copyright (C) 2019 IRSTEA
+ * All rights reserved.
+ *
+ * @copyright 2019 IRSTEA
+ * @author guillaume.perreal
+ */
+
+
+namespace Irstea\NgModelGeneratorBundle\Exceptions;
+
+
+use Throwable;
+
+/**
+ * Class InvalidModelException
+ */
+class InvalidModelException extends DomainException
+{
+    /**
+     * @var string
+     */
+    private $className;
+
+    /**
+     * {@inheritDoc}
+     */
+    public function __construct(string $className, $message = '', Throwable $previous = null)
+    {
+        parent::__construct($message, 0, $previous);
+        $this->className = $className;
+    }
+
+    /**
+     * Get className.
+     *
+     * @return string
+     */
+    public function getClassName(): string
+    {
+        return $this->className;
+    }
+}
diff --git a/src/php/Exceptions/MissingIdentifierException.php b/src/php/Exceptions/MissingIdentifierException.php
new file mode 100644
index 0000000..be89b73
--- /dev/null
+++ b/src/php/Exceptions/MissingIdentifierException.php
@@ -0,0 +1,20 @@
+<?php declare(strict_types=1);
+/**
+ * Copyright (C) 2019 IRSTEA
+ * All rights reserved.
+ *
+ * @copyright 2019 IRSTEA
+ * @author guillaume.perreal
+ */
+
+
+namespace Irstea\NgModelGeneratorBundle\Exceptions;
+
+
+/**
+ * Class MissingIdentifierException
+ */
+final class MissingIdentifierException extends InvalidModelException
+{
+
+}
diff --git a/src/php/Exceptions/TooManyIdentifiersException.php b/src/php/Exceptions/TooManyIdentifiersException.php
new file mode 100644
index 0000000..7932050
--- /dev/null
+++ b/src/php/Exceptions/TooManyIdentifiersException.php
@@ -0,0 +1,20 @@
+<?php declare(strict_types=1);
+/**
+ * Copyright (C) 2019 IRSTEA
+ * All rights reserved.
+ *
+ * @copyright 2019 IRSTEA
+ * @author guillaume.perreal
+ */
+
+
+namespace Irstea\NgModelGeneratorBundle\Exceptions;
+
+
+/**
+ * Class TooManyIdentifiersException
+ */
+final class TooManyIdentifiersException extends InvalidModelException
+{
+
+}
diff --git a/src/php/ModelGenerator.php b/src/php/ModelGenerator.php
index 63ac7ce..a4c4c05 100644
--- a/src/php/ModelGenerator.php
+++ b/src/php/ModelGenerator.php
@@ -23,7 +23,10 @@ namespace Irstea\NgModelGeneratorBundle;
 
 use ApiPlatform\Core\Documentation\Documentation;
 use Irstea\NgModelGeneratorBundle\Exceptions\DomainException;
+use Irstea\NgModelGeneratorBundle\Exceptions\EmptyModelException;
 use Irstea\NgModelGeneratorBundle\Exceptions\Exception;
+use Irstea\NgModelGeneratorBundle\Exceptions\MissingIdentifierException;
+use Irstea\NgModelGeneratorBundle\Exceptions\TooManyIdentifiersException;
 use Irstea\NgModelGeneratorBundle\Iterators\IteratorBuilder;
 use Irstea\NgModelGeneratorBundle\Metadata\MetadataFactoryInterface;
 use Irstea\NgModelGeneratorBundle\Metadata\ResourceMetadata;
@@ -34,8 +37,10 @@ use Irstea\NgModelGeneratorBundle\Models\Types\Factory\TypeFactoryInterface;
 use Irstea\NgModelGeneratorBundle\Models\Types\Factory\TypeHelper;
 use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property;
 use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Repository;
+use Irstea\NgModelGeneratorBundle\Models\Types\Resources\Representation;
 use Irstea\NgModelGeneratorBundle\Models\Types\Type;
 use Irstea\NgModelGeneratorBundle\Writers\MultiFileWriter;
+use PHPStan\Type\ResourceType;
 use Twig\Environment;
 
 /**
@@ -202,11 +207,9 @@ final class ModelGenerator
             /* @var ResourceMetadata $resourceMeta */
             try {
                 $repo = $this->buildRepositories($resourceMeta);
-                if ($repo) {
-                    $repositories[$repo->getName()] = $repo;
-                }
+                $repositories[$repo->getName()] = $repo;
             } catch (Exception $ex) {
-                printf("Error while mapping %s: %s\n%s\n", $class, $ex->getMessage(), $ex->getTraceAsString());
+                printf("%s while mapping %s: %s\n%s\n", get_class($ex), $class, $ex->getMessage(), $ex->getTraceAsString());
             }
         }
 
@@ -218,48 +221,46 @@ final class ModelGenerator
     /**
      * @param ResourceMetadata $resourceMeta
      *
-     * @return Repository|null
+     * @return Repository
      */
-    private function buildRepositories(ResourceMetadata $resourceMeta): ?Repository
+    private function buildRepositories(ResourceMetadata $resourceMeta): Repository
     {
         $defaultNormalization = $resourceMeta->getDefaultNormalization();
 
         $defaultContext = $this->contextFactory->create($defaultNormalization, true);
 
-        /**
-         * @var Type
-         * @var Property   $identifier
-         * @var Property[] $properties
-         */
-        [$defaultRepr, $identifier, $properties] = $defaultContext->createType(TypeHelper::fromClassName($resourceMeta->getFullName()));
+        $defaultTypeRef = $defaultContext->createType(TypeHelper::fromClassName($resourceMeta->getFullName()));
 
-        if (!$properties) {
-            printf(
-                "No property found for %s (groups: [%s])\n",
-                $resourceMeta->getBaseName(),
-                implode(', ', $defaultNormalization->getGroups())
-            );
+        /** @var Representation $defaultType */
+        $defaultType = $defaultTypeRef->findType(Representation::class);
+
+        $properties = $defaultType->getProperties();
 
-            return null;
+        if (!$properties) {
+            throw new EmptyModelException($resourceMeta->getFullName(), "no properties");
         }
 
-        if (!$identifier) {
-            printf(
-                "No identifier found for %s (groups: [%s])\n",
-                $resourceMeta->getBaseName(),
-                implode(', ', $defaultNormalization->getGroups())
-            );
+        $identifiers = \array_filter(
+            $properties,
+            function(Property $property): bool {
+                return $property->getName(){0} !== '@' && $property->isIdentifier();
+            }
+        );
+
+        if (!$identifiers) {
+            throw new MissingIdentifierException($resourceMeta->getFullName());
+        }
 
-            return null;
+        if (count($identifiers) > 1) {
+            throw new TooManyIdentifiersException($resourceMeta->getFullName());
         }
 
+        $identifier = array_shift($identifiers);
+
         $operations = [];
         $pathParser = new PathParser($properties);
 
         $opsMeta = $resourceMeta->getOperations();
-        if (!$opsMeta) {
-            return null;
-        }
 
         if (isset($opsMeta['getitem'])) {
             $get = $opsMeta['getitem'];
@@ -274,9 +275,9 @@ final class ModelGenerator
         }
 
         if (!$operations) {
-            return null;
+            throw new EmptyModelException($resourceMeta->getFullName(), "no operations");
         }
 
-        return new Repository($resourceMeta, $defaultRepr, $identifier, $iri, $operations);
+        return new Repository($resourceMeta, $defaultType, $identifier, $iri, $operations);
     }
 }
diff --git a/src/php/Models/Types/Deferred.php b/src/php/Models/Types/Deferred.php
deleted file mode 100644
index dd4e722..0000000
--- a/src/php/Models/Types/Deferred.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of "irstea/ng-model-generator-bundle".
- *
- * "irstea/ng-model-generator-bundle" generates Typescript interfaces for Angular using api-platform metadata.
- * Copyright (C) 2018-2019 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\Types;
-
-/**
- * Interface Deferred.
- */
-interface Deferred extends Type
-{
-    /**
-     * @param callable $callback
-     */
-    public function resolveWith(callable $callback): Type;
-
-    /**
-     * @param Type $result
-     */
-    public function resolve(Type $result): Type;
-}
diff --git a/src/php/Models/Types/Factory/DeferrableTypeFactory.php b/src/php/Models/Types/Factory/DeferrableTypeFactory.php
index bd47ecc..c12428e 100644
--- a/src/php/Models/Types/Factory/DeferrableTypeFactory.php
+++ b/src/php/Models/Types/Factory/DeferrableTypeFactory.php
@@ -35,8 +35,12 @@ final class DeferrableTypeFactory extends AbstractTypeFactoryDecorator
      */
     public function createType(PropertyType $type, ContextInterface $context): Type
     {
-        $type = new Reference(TypeHelper::getTypeKey($type));
+        $key = TypeHelper::getTypeKey($type);
 
-        return $type;
+        $deferred = function () use ($type, $context) : Type {
+            return parent::createType($type, $context);
+        };
+
+        return new Reference($deferred, $key);
     }
 }
diff --git a/src/php/Models/Types/Factory/TypeCache.php b/src/php/Models/Types/Factory/TypeCache.php
index 6455d4b..e6db859 100644
--- a/src/php/Models/Types/Factory/TypeCache.php
+++ b/src/php/Models/Types/Factory/TypeCache.php
@@ -21,6 +21,7 @@
 
 namespace Irstea\NgModelGeneratorBundle\Models\Types\Factory;
 
+use Assert\Assertion;
 use Irstea\NgModelGeneratorBundle\Models\Types\Type;
 use Symfony\Component\PropertyInfo\Type as PropertyType;
 
@@ -30,16 +31,27 @@ use Symfony\Component\PropertyInfo\Type as PropertyType;
 final class TypeCache extends AbstractTypeFactoryDecorator
 {
     /**
-     * @var array
+     * @var Type[]
      */
-    private $cache = [];
+    private $typeCache = [];
+
+    /**
+     * @var bool[]
+     */
+    private $supportCache = [];
 
     /**
      * {@inheritdoc}
      */
     public function supportsType(PropertyType $type, ContextInterface $context): bool
     {
-        return \array_key_exists(TypeHelper::getTypeKey($type), $this->cache) || parent::supportsType($type, $context);
+        $key = TypeHelper::getTypeKey($type);
+
+        if (!\array_key_exists($key, $this->supportCache)) {
+            $this->supportCache[$key] = parent::supportsType($type, $context);
+        }
+
+        return $this->supportCache[$key];
     }
 
     /**
@@ -47,11 +59,14 @@ final class TypeCache extends AbstractTypeFactoryDecorator
      */
     public function createType(PropertyType $type, ContextInterface $context): Type
     {
+        Assertion::true($this->supportsType($type, $context));
+
         $key = TypeHelper::getTypeKey($type);
-        if (\array_key_exists($key, $this->cache)) {
-            return $this->cache[$key];
+
+        if (!\array_key_exists($key, $this->typeCache)) {
+            $this->typeCache[$key] = parent::createType($type, $context);
         }
 
-        return $this->cache[$key] = parent::createType($type, $context);
+        return $this->typeCache[$key];
     }
 }
diff --git a/src/php/Models/Types/Reference.php b/src/php/Models/Types/Reference.php
index 61f41ad..ed9ff8a 100644
--- a/src/php/Models/Types/Reference.php
+++ b/src/php/Models/Types/Reference.php
@@ -21,10 +21,12 @@
 
 namespace Irstea\NgModelGeneratorBundle\Models\Types;
 
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CircularReference;
+
 /**
  * Class Ref.
  */
-class Reference extends AbstractType implements Deferred
+class Reference extends AbstractType
 {
     private const UNRESOLVED = 0;
     private const RESOLVING = 1;
@@ -39,15 +41,20 @@ class Reference extends AbstractType implements Deferred
     /** @var int */
     private $state = self::UNRESOLVED;
 
+    /** @var callable */
+    private $deferred;
+
     /**
      * Reference constructor.
      *
+     * @param callable $deferred
      * @param string $name
      */
-    public function __construct(string $name = 'anonymous reference')
+    public function __construct(callable $deferred, string $name = 'anonymous reference')
     {
         $this->name = $name;
         $this->target = Unresolved::get();
+        $this->deferred = $deferred;
     }
 
     /**
@@ -57,34 +64,17 @@ class Reference extends AbstractType implements Deferred
      */
     public function getTarget(): Type
     {
-        return $this->target;
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function resolveWith(callable $callback): Type
-    {
-        switch ($this->state) {
-            case self::UNRESOLVED:
-                $this->state = self::RESOLVING;
-
-                return $this->resolve($callback());
-            case self::RESOLVING:
-                return $this;
-            case self::RESOLVED:
-                return $this->target;
+        if ($this->state === self::RESOLVING) {
+            throw new \RuntimeException("circular reference");
         }
-    }
 
-    /**
-     * {@inheritdoc}
-     */
-    public function resolve(Type $result): Type
-    {
-        if ($this->state !== self::RESOLVED) {
+        if ($this->state === self::UNRESOLVED) {
+            $this->state = self::RESOLVING;
+
+            $deferred = $this->deferred;
+            $this->target = $deferred();
+
             $this->state = self::RESOLVED;
-            $this->target = $result;
         }
 
         return $this->target;
@@ -95,7 +85,11 @@ class Reference extends AbstractType implements Deferred
      */
     private function dereference(): Type
     {
-        return $this->target instanceof Reference ? $this->target->dereference() : $this->target;
+        $target = $this->getTarget();
+        if ($target instanceof self && $target !== $this) {
+            return $target->dereference();
+        }
+        return $target;
     }
 
     /**
@@ -127,7 +121,7 @@ class Reference extends AbstractType implements Deferred
      */
     public function getIterator()
     {
-        yield $this->target;
+        yield $this->getTarget();
     }
 
     /**
@@ -151,6 +145,6 @@ class Reference extends AbstractType implements Deferred
      */
     public function findType(string $typeClass): ?Type
     {
-        return parent::findType($typeClass) ?: $this->target->findType($typeClass);
+        return parent::findType($typeClass) ?: $this->getTarget()->findType($typeClass);
     }
 }
diff --git a/src/php/SerializationMapper.php b/src/php/SerializationMapper.php
index 67a92a3..7598966 100644
--- a/src/php/SerializationMapper.php
+++ b/src/php/SerializationMapper.php
@@ -85,7 +85,7 @@ final class SerializationMapper implements TypeFactoryInterface
      */
     public function createType(PropertyType $type, ContextInterface $context): Type
     {
-        Assertion::true($this->createType($type, $context));
+        Assertion::true($this->supportsType($type, $context));
 
         $class = PHPClass::get($type->getClassName());
         $repr = $this->serialization->getRepresentationOf($class);
@@ -93,31 +93,6 @@ final class SerializationMapper implements TypeFactoryInterface
         return $this->mapRepresentation($repr, $context);
     }
 
-    /**
-     * @param ContextInterface $context
-     *
-     * @return array
-     */
-    public function getResourceData(ContextInterface $context): array
-    {
-        $resource = $this->serialization->getRoot();
-        $resourceName = $resource->getFullName();
-        $classInfo = $this->getClassInfo($resource);
-
-        $properties = array_map([$this, 'mapProperty'], $classInfo->getVirtualProperties());
-
-        $identifiers = array_filter(
-            $properties,
-            function (Property $property) {
-                return $property->isIdentifier();
-            }
-        );
-
-        $identifier = $identifiers ? array_shift($identifiers) : null;
-
-        return [$context->createType(TypeHelper::fromClassName($resourceName)), $identifier, $properties];
-    }
-
     /**
      * @param RepresentationMetadata $repr
      * @param ContextInterface       $context
@@ -149,10 +124,7 @@ final class SerializationMapper implements TypeFactoryInterface
                 case 0:
                     throw new DomainException(sprintf('Union with no children: %s', $repr));
                 case 1:
-                    $ref = new Reference($types[0]->getUsage());
-                    $ref->resolve($types[0]);
-
-                    return $ref;
+                    return $types[0];
                 default:
                     return new Alias($repr->getName(), Union::create($types), $desc);
             }
-- 
GitLab