<?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; use Irstea\NgModelGeneratorBundle\Exceptions\DomainException; use Irstea\NgModelGeneratorBundle\Metadata\PropertyMetadata; /** * Class ClassInfo. */ final class ClassInfo implements ClassName { public const UNDEFINED = 'UNDEFINED'; public const IRI = 'IRI'; public const UNION = 'UNION'; public const INTERFACE = 'INTERFACE'; /** @var ClassName */ private $class; /** @var self|false|null */ private $parent = false; /** @var PropertyMetadata[] */ private $virtualProperties = []; /** @var PropertyMetadata[] */ private $concreteProperties = []; /** @var self[] */ private $children = []; /** @var string */ private $type = self::UNDEFINED; /** @var bool */ private $abstract; /** @var bool */ private $resource; /** * ClassInfo constructor. * * @param ClassName $class * @param PropertyMetadata[] $properties * @param bool $abstract * @param bool $resource */ public function __construct(ClassName $class, array $properties = [], bool $abstract = false, bool $resource = false) { $this->class = $class; $this->abstract = $abstract; foreach ($properties as $property) { $this->virtualProperties[$property->getName()] = $property; } $this->concreteProperties = $abstract ? [] : $this->virtualProperties; $this->resource = $resource; } /** * {@inheritdoc} */ public function getNamespace(): string { return $this->class->getNamespace(); } /** * {@inheritdoc} */ public function getBaseName(): string { return $this->class->getBaseName(); } /** * {@inheritdoc} */ public function getFullName(): string { return $this->class->getFullName(); } /** * Get properties. * * @return PropertyMetadata[] */ public function getVirtualProperties(): array { return $this->virtualProperties; } /** * Get properties. * * @return PropertyMetadata[] */ public function getConcreteProperties(): array { return $this->concreteProperties; } /** * Get abstract. * * @return bool */ public function isAbstract(): bool { return $this->abstract; } /** * Get resource. * * @return bool */ public function isResource(): bool { return $this->resource; } /** * Get parent. * * @return ClassInfo|null */ public function getParent(): ?ClassInfo { return $this->parent ?: null; } /** * Set parent. * * @param ClassInfo|null $parent */ public function setParent(?ClassInfo $parent): void { if ($parent === $this->parent) { return; } if ($parent === $this) { throw new DomainException('A class cannot be its own parent'); } if ($this->parent !== false) { throw new DomainException('Can only set parent once'); } $this->parent = $parent; if ($parent) { $parent->addChild($this); } } /** * Get children. * * @return ClassInfo[] */ public function getChildren(): array { return $this->children; } /** * @param ClassInfo $child * * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ private function addChild(ClassInfo $child): void { if (\in_array($child, $this->children, true)) { return; } $this->children[] = $child; $child->setParent($this); } /** * Test type. * * @param string $type * * @return bool */ public function isType(string $type): bool { return $this->type === $type; } /** * @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 isUndefined(): bool { return $this->isType(self::UNDEFINED); } /** * @return bool */ public function isIRI(): bool { return $this->isType(self::IRI); } /** * {@inheritdoc} */ public function jsonSerialize() { return [ 'class' => $this->class->getFullName(), 'type' => $this->type, 'abstract' => $this->abstract, 'parent' => $this->parent ? $this->parent->getFullName() : null, 'children' => array_map( function (ClassInfo $ci) { return $ci->getFullName(); }, $this->children ), 'virtualProperties' => $this->virtualProperties, 'concreteProperties' => $this->concreteProperties, ]; } /** * @return self[]|\Generator */ public function iterateConcreteDescendants(): \Generator { if (!$this->abstract) { yield $this; } foreach ($this->children as $child) { yield from $child->iterateConcreteDescendants(); } } /** * @return self[]|\Generator */ public function iterateInterfaceDescendants(): \Generator { if ($this->isInterface()) { yield $this; } foreach ($this->children as $child) { yield from $child->iterateInterfaceDescendants(); } } /** * @return string */ public function __toString() { return $this->getFullName(); } /** * Fait remonter les propriétés communes des sous-classes. */ public function rearrangeHiearchy(): void { if ($this->parent) { return; } $this->bubbleUpProperties(); $this->sinkDownProperties(); } private function bubbleUpProperties(): void { if (!$this->children) { return; } $classProperties = []; if (!$this->abstract) { $classProperties[] = $this->virtualProperties; } foreach ($this->children as $child) { $child->bubbleUpProperties(); $classProperties[] = $child->virtualProperties; } switch (\count($classProperties)) { case 0: return; case 1: $commonProperties = array_shift($classProperties); break; default: $commonProperties = array_intersect_key(...$classProperties); } if (!$commonProperties) { return; } $this->virtualProperties = array_replace($this->virtualProperties, $commonProperties); } /** * @return bool */ private function sinkDownProperties(): bool { if ($this->parent) { $this->virtualProperties = array_replace($this->parent->virtualProperties, $this->virtualProperties); if (!$this->abstract) { $this->concreteProperties = array_diff_key($this->virtualProperties, $this->parent->getAllConcreteProperties()); } } elseif (!$this->abstract) { $this->concreteProperties = $this->virtualProperties; } $hasInterfaceChildren = false; foreach ($this->children as $child) { if ($child->sinkDownProperties()) { $hasInterfaceChildren = true; } } if ($this->concreteProperties) { $this->type = self::INTERFACE; $hasInterfaceChildren = true; } elseif ($hasInterfaceChildren) { $this->type = self::UNION; } else { $this->type = self::IRI; } return $hasInterfaceChildren; } /** * Get properties. * * @return HasName[] * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ private function getAllConcreteProperties(): array { if ($this->parent) { return array_replace($this->parent->getAllConcreteProperties(), $this->concreteProperties); } return $this->concreteProperties; } }