-
Guillaume Perréal authored2ec19af7
<?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;
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;
use Irstea\NgModelGeneratorBundle\Models\ClassName;
use Irstea\NgModelGeneratorBundle\Models\Declaration;
use Irstea\NgModelGeneratorBundle\Models\PHPClass;
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;
/**
* Class Serializer.
*/
final class ModelGenerator
{
/** @var MetadataFactoryInterface */
private $metadataFactory;
/** @var Environment */
private $twigEnv;
/** @var Documentation */
private $documentation;
/** @var ContextFactoryInterface */
private $contextFactory;
/**
* Serializer constructor.
*
* @param MetadataFactoryInterface $metadataFactory
* @param ContextFactoryInterface $contextFactory
* @param TypeFactoryInterface $typeFactory
* @param Environment $twigEnv
*/
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
public function __construct(
MetadataFactoryInterface $metadataFactory,
ContextFactoryInterface $contextFactory,
Environment $twigEnv
) {
$this->metadataFactory = $metadataFactory;
$this->twigEnv = $twigEnv;
$this->contextFactory = $contextFactory;
}
/**
* Génère les modèles Typescript.
* Cette méthode n'est pas réentrante.
*
* @param Documentation $doc
* @param MultiFileWriter $writer
*/
public function generate(Documentation $doc, MultiFileWriter $writer): void
{
if ($this->documentation !== null) {
throw new DomainException(__METHOD__ . ' is not reentrant');
}
try {
$this->documentation = $doc;
$this->doGenerate($writer);
} finally {
unset(
$this->documentation,
$this->typeFactory
);
}
}
/**
* @param MultiFileWriter $writer
*/
private function doGenerate(MultiFileWriter $writer): void
{
$context = [
'title' => $this->documentation->getTitle(),
'version' => $this->documentation->getVersion(),
'description' => $this->documentation->getDescription() ?: '',
'repositories' => $this->extractRepositories(),
'repoImports' => [],
'declarations' => [],
];
$isResourceDeclaration = function ($item) {
return $item instanceof Declaration
&& !($item instanceof Repository)
&& !\in_array($item->getName(), ['UUID', 'IRI', 'Collection', 'DateTime'], true);
};
foreach (
IteratorBuilder::from($context['repositories'])
->unique()
->recurse(\RecursiveIteratorIterator::CHILD_FIRST)
->where($isResourceDeclaration)
as $type) {
$context['declarations'][$type->getName()] = $type;
}
foreach (
IteratorBuilder::from($context['repositories'])
->unique()
->recurseWhere(
\RecursiveIteratorIterator::SELF_FIRST,
function ($item) {
return $item instanceof Repository
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
|| !($item instanceof Declaration);
}
)
->where($isResourceDeclaration)
as $type) {
$context['repoImports'][] = $type->getUsage();
}
sort($context['repoImports']);
$this->generateFile($writer, 'common.ts', $context);
$this->generateFile($writer, 'resources.ts', $context);
$this->generateFile($writer, 'repositories.ts', $context);
$this->generateFile($writer, 'metadata.ts', $context);
$this->generateFile($writer, 'index.ts', $context);
}
/**
* @param MultiFileWriter $writer
*/
private function generateFile(MultiFileWriter $writer, string $path, array $context): void
{
$fileWriter = $writer->newFile($path);
try {
$template = sprintf('@NgModelGenerator/%s.twig', basename($path));
$context['filename'] = $path;
$fileWriter->write($this->twigEnv->render($template, $context));
} finally {
$fileWriter->close();
}
}
/**
* Retourne un iterateur sur les métadonnées des ressources.
*
* @return \Iterator
*/
private function getResourceMetadata(): \Iterator
{
$classNames = iterator_to_array($this->documentation->getResourceNameCollection()->getIterator());
$classes = array_map([PHPClass::class, 'get'], $classNames);
usort($classes, [PHPClass::class, 'baseNameOrdering']);
foreach ($classes as $class) {
yield $class => $this->metadataFactory->getResourceMetadata($class);
}
}
/**
* @return Repository[]
*/
private function extractRepositories(): array
{
$repositories = [];
// // Premier passage pour prégénérer des références aux ressources
// foreach ($this->getResourceMetadata() as $class => $resourceMeta) {
// /* @var ClassName $class */
// /* @var ResourceMetadata $resourceMeta */
// $ref = $this->typeFactory->defer($class->getFullName());
// $this->typeFactory->add($resourceMeta->getBaseName(), $ref);
// }
// Maintenant on génére les repositories
foreach ($this->getResourceMetadata() as $class => $resourceMeta) {
/* @var ClassName $class */
/* @var ResourceMetadata $resourceMeta */
try {
$repo = $this->buildRepositories($resourceMeta);
$repositories[$repo->getName()] = $repo;
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
} catch (Exception $ex) {
printf("%s while mapping %s: %s\n%s\n", get_class($ex), $class, $ex->getMessage(), $ex->getTraceAsString());
}
}
ksort($repositories);
return $repositories;
}
/**
* @param ResourceMetadata $resourceMeta
*
* @return Repository
*/
private function buildRepositories(ResourceMetadata $resourceMeta): Repository
{
$defaultNormalization = $resourceMeta->getDefaultNormalization();
$defaultContext = $this->contextFactory->create($defaultNormalization, true);
$defaultTypeRef = $defaultContext->createType(TypeHelper::fromClassName($resourceMeta->getFullName()));
/** @var Representation $defaultType */
$defaultType = $defaultTypeRef->findType(Representation::class);
$properties = $defaultType->getProperties();
if (!$properties) {
throw new EmptyModelException($resourceMeta->getFullName(), "no properties");
}
$identifiers = \array_filter(
$properties,
function(Property $property): bool {
return $property->getName(){0} !== '@' && $property->isIdentifier();
}
);
if (!$identifiers) {
throw new MissingIdentifierException($resourceMeta->getFullName());
}
if (count($identifiers) > 1) {
throw new TooManyIdentifiersException($resourceMeta->getFullName());
}
$identifier = array_shift($identifiers);
$operations = [];
$pathParser = new PathParser($properties);
$opsMeta = $resourceMeta->getOperations();
if (isset($opsMeta['getitem'])) {
$get = $opsMeta['getitem'];
$iri = $pathParser->parse($get->getPath(), $get->getRequirements());
} else {
$iri = null;
}
foreach ($opsMeta as $operation) {
$mapper = new OperationMapper($this->contextFactory, $pathParser, $operation, $iri);
$operations[] = $mapper();
}
if (!$operations) {
throw new EmptyModelException($resourceMeta->getFullName(), "no operations");
}
281282283284
return new Repository($resourceMeta, $defaultType, $identifier, $iri, $operations);
}
}