Skip to content

Commit

Permalink
feat: Add support for enums
Browse files Browse the repository at this point in the history
  • Loading branch information
MontealegreLuis committed May 20, 2022
1 parent f03b3bf commit eed0fd7
Show file tree
Hide file tree
Showing 51 changed files with 836 additions and 141 deletions.
12 changes: 9 additions & 3 deletions docs/format.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ currentMenu: format
* Static methods and functions are shown underlined
* Abstract classes, abstract methods and interfaces names are shown in *italics*

## Traits and Attributes
## Traits, Attributes, and Enums

[Traits](https://www.php.net/manual/en/language.oop5.traits.php) and class [attributes](https://www.php.net/manual/en/language.attributes.overview.php)(annotations) will be shown with a [UML stereotype](https://www.uml-diagrams.org/stereotype.html).
Traits will be shown with the `<<trait>>` stereotype above its name, and attributes (annotations) will be shown with the `<<attribute>>` stereotype above its name.
[Traits](https://www.php.net/manual/en/language.oop5.traits.php), class [attributes](https://www.php.net/manual/en/language.attributes.overview.php)(annotations), and [enumerations](https://www.php.net/manual/en/language.enumerations.php) will be shown with a [UML stereotype](https://www.uml-diagrams.org/stereotype.html) above its name.
Traits will be shown with the `<<trait>>` stereotype, attributes (annotations) will be shown with the `<<attribute>>` stereotype, and enumerations will be shown with the `<<enum>>` stereotype.

## Enumerations

Case definitions in enumerations will be shown without a visibility modifier (`+`, `-`, `#`) and to differentiate them from constants, cases won't be in italics.

If an enum defines constants, they will be shown above its cases.

## Relationships

Expand Down
51 changes: 51 additions & 0 deletions src/Code/EnumDefinition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php declare(strict_types=1);
/**
* This source file is subject to the license that is bundled with this package in the file LICENSE.
*/

namespace PhUml\Code;

use PhUml\Code\Methods\Method;
use PhUml\Code\Properties\Constant;
use PhUml\Code\Properties\EnumCase;
use PhUml\Code\Properties\HasConstants;
use PhUml\Code\Properties\WithConstants;

final class EnumDefinition extends Definition implements HasConstants, UseTraits, ImplementsInterfaces
{
use WithConstants;
use WithTraits;
use WithInterfaces;

/**
* @param Method[] $methods
* @param EnumCase[] $cases
* @param Constant[] $constants
* @param Name[] $interfaces
* @param Name[] $traits
*/
public function __construct(
Name $name,
private readonly array $cases,
array $methods = [],
array $constants = [],
array $interfaces = [],
array $traits = [],
) {
parent::__construct($name, $methods);
$this->constants = $constants;
$this->traits = $traits;
$this->interfaces = $interfaces;
}

public function hasProperties(): bool
{
return $this->cases !== [] || $this->constants !== [];
}

/** @return EnumCase[] */
public function cases(): array
{
return $this->cases;
}
}
20 changes: 20 additions & 0 deletions src/Code/Properties/EnumCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php declare(strict_types=1);
/**
* This source file is subject to the license that is bundled with this package in the file LICENSE.
*/

namespace PhUml\Code\Properties;

use Stringable;

final class EnumCase implements Stringable
{
public function __construct(private readonly string $name)
{
}

public function __toString(): string
{
return $this->name;
}
}
2 changes: 1 addition & 1 deletion src/Code/WithInterfaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
trait WithInterfaces
{
/** @var Name[] */
private array $interfaces;
private readonly array $interfaces;

/** @return Name[] */
public function interfaces(): array
Expand Down
2 changes: 1 addition & 1 deletion src/Graphviz/Builders/DirectedEdgesBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private function addAssociations(ClassDefinition $class, HasType $property, Code

private function isAssociationResolved(EdgeKey $key): bool
{
return array_key_exists((string) $key, $this->edges) && $this->edges[(string) $key] === true;
return array_key_exists((string) $key, $this->edges) && $this->edges[(string) $key];
}

private function markAssociationResolvedFor(EdgeKey $key): void
Expand Down
7 changes: 1 addition & 6 deletions src/Graphviz/Builders/EdgeKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

final class EdgeKey implements Stringable
{
private string $key;
private readonly string $key;

public static function from(Name $name, TypeDeclaration $type): EdgeKey
{
Expand All @@ -23,11 +23,6 @@ private function __construct(string $key)
$this->key = $key;
}

public function equals(EdgeKey $anotherKey): bool
{
return $this->key === $anotherKey->key;
}

public function __toString(): string
{
return $this->key;
Expand Down
31 changes: 31 additions & 0 deletions src/Graphviz/Builders/EnumGraphBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php declare(strict_types=1);
/**
* This source file is subject to the license that is bundled with this package in the file LICENSE.
*/

namespace PhUml\Graphviz\Builders;

use PhUml\Code\Codebase;
use PhUml\Code\EnumDefinition;
use PhUml\Graphviz\Edge;
use PhUml\Graphviz\HasDotRepresentation;
use PhUml\Graphviz\Node;

final class EnumGraphBuilder
{
/** @return HasDotRepresentation[] */
public function extractFrom(EnumDefinition $enum, Codebase $codebase): array
{
$dotElements = [new Node($enum)];

foreach ($enum->interfaces() as $interface) {
$dotElements[] = Edge::implementation($codebase->get($interface), $enum);
}

foreach ($enum->traits() as $usedTrait) {
$dotElements[] = Edge::use($codebase->get($usedTrait), $enum);
}

return $dotElements;
}
}
14 changes: 7 additions & 7 deletions src/Graphviz/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PhUml\Code\ClassDefinition;
use PhUml\Code\Definition;
use PhUml\Code\InterfaceDefinition;
use PhUml\Code\TraitDefinition;

/**
* `ClassDefinition`, `InterfaceDefinition` and `TraitDefinition` objects can be nodes
Expand All @@ -32,12 +33,11 @@ public function dotTemplate(): string

public function labelTemplate(): string
{
if ($this->definition instanceof ClassDefinition) {
return 'class';
}
if ($this->definition instanceof InterfaceDefinition) {
return 'interface';
}
return 'trait';
return match ($this->definition::class) {
ClassDefinition::class => 'class',
InterfaceDefinition::class => 'interface',
TraitDefinition::class => 'trait',
default => 'enum',
};
}
}
38 changes: 38 additions & 0 deletions src/Parser/Code/Builders/EnumDefinitionBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php declare(strict_types=1);
/**
* This source file is subject to the license that is bundled with this package in the file LICENSE.
*/

namespace PhUml\Parser\Code\Builders;

use PhpParser\Node\Stmt\Enum_;
use PhUml\Code\EnumDefinition;
use PhUml\Code\Name;
use PhUml\Parser\Code\Builders\Names\InterfaceNamesBuilder;
use PhUml\Parser\Code\Builders\Names\TraitNamesBuilder;

final class EnumDefinitionBuilder
{
use InterfaceNamesBuilder;
use TraitNamesBuilder;

public function __construct(
private readonly MembersBuilder $membersBuilder,
private readonly UseStatementsBuilder $useStatementsBuilder,
) {
}

public function build(Enum_ $enum): EnumDefinition
{
$useStatements = $this->useStatementsBuilder->build($enum);

return new EnumDefinition(
new Name((string) $enum->namespacedName),
$this->membersBuilder->enumCases($enum->stmts),
$this->membersBuilder->methods($enum->getMethods(), $useStatements),
$this->membersBuilder->constants($enum->stmts),
$this->buildInterfaces($enum->implements),
$this->buildTraits($enum->stmts),
);
}
}
13 changes: 2 additions & 11 deletions src/Parser/Code/Builders/Members/TypeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,14 @@ private function fromParsedType(Identifier|Name|ComplexType|null $type): TypeDec
$type instanceof Name, $type instanceof Identifier => TypeDeclaration::from((string) $type),
$type === null => TypeDeclaration::absent(),
$type instanceof UnionType => TypeDeclaration::fromCompositeType(
$this->fromCompositeType($type),
array_map(strval(...), $type->types),
CompositeType::UNION
),
$type instanceof IntersectionType => TypeDeclaration::fromCompositeType(
$this->fromCompositeType($type),
array_map(strval(...), $type->types),
CompositeType::INTERSECTION
),
default => throw new RuntimeException(sprintf('%s is not supported', $type::class)),
};
}

/** @return string[] */
private function fromCompositeType(UnionType|IntersectionType $type): array
{
return array_map(
static fn (Identifier|Name $name): string => (string) $name,
$type->types
);
}
}
34 changes: 24 additions & 10 deletions src/Parser/Code/Builders/MembersBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\EnumCase as ParsedEnumCase;
use PhpParser\Node\Stmt\Property as ParsedProperty;
use PhUml\Code\Methods\Method;
use PhUml\Code\Properties\Constant;
use PhUml\Code\Properties\EnumCase;
use PhUml\Code\Properties\Property;
use PhUml\Code\UseStatements;
use PhUml\Parser\Code\Builders\Members\ConstantsBuilder;
Expand Down Expand Up @@ -70,6 +72,20 @@ public function properties(array $members, ?ClassMethod $constructor, UseStateme
return array_merge($this->propertiesBuilder->build($filteredProperties, $useStatements), $properties);
}

/** @return Property[] */
private function fromPromotedProperties(ClassMethod $constructor, UseStatements $useStatements): array
{
$promotedProperties = array_filter(
$constructor->getParams(),
static fn (Node\Param $param) => $param->flags !== 0
);

/** @var Node\Param[] $filteredPromotedProperties */
$filteredPromotedProperties = $this->filters->apply($promotedProperties);

return $this->propertiesBuilder->fromPromotedProperties($filteredPromotedProperties, $useStatements);
}

/**
* @param ClassMethod[] $methods
* @return Method[]
Expand All @@ -82,17 +98,15 @@ public function methods(array $methods, UseStatements $useStatements): array
return $this->methodsBuilder->build($filteredMethods, $useStatements);
}

/** @return Property[] */
private function fromPromotedProperties(ClassMethod $constructor, UseStatements $useStatements): array
/**
* @param Node[] $members
* @return EnumCase[]
*/
public function enumCases(array $members): array
{
$promotedProperties = array_filter(
$constructor->getParams(),
static fn (Node\Param $param) => $param->flags !== 0
);

/** @var Node\Param[] $filteredPromotedProperties */
$filteredPromotedProperties = $this->filters->apply($promotedProperties);
/** @var ParsedEnumCase[] $cases */
$cases = array_filter($members, static fn ($member): bool => $member instanceof ParsedEnumCase);

return $this->propertiesBuilder->fromPromotedProperties($filteredPromotedProperties, $useStatements);
return array_map(static fn (ParsedEnumCase $case) => new EnumCase((string) $case->name), $cases);
}
}
13 changes: 8 additions & 5 deletions src/Parser/Code/Builders/TagTypeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,14 @@ public function typeFromTag(string $comment, TagName $tagName, callable $filter

private function resolveType(?Type $type): ?TagType
{
return match (true) {
$type === null => null,
$type instanceof Nullable => TagType::nullable((string) $type->getActualType()),
$type instanceof Compound => TagType::union(array_map(strval(...), $type->getIterator()->getArrayCopy())),
$type instanceof Intersection => TagType::intersection(array_map(strval(...), $type->getIterator()->getArrayCopy())),
if ($type === null) {
return null;
}

return match ($type::class) {
Nullable::class => TagType::nullable((string) $type->getActualType()),
Compound::class => TagType::union(array_map(strval(...), $type->getIterator()->getArrayCopy())),
Intersection::class => TagType::intersection(array_map(strval(...), $type->getIterator()->getArrayCopy())),
default => $this->fromType($type)
};
}
Expand Down
3 changes: 2 additions & 1 deletion src/Parser/Code/Builders/UseStatementsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

use PhpParser\Node\Name as ParsedName;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Enum_;
use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Trait_;
Expand All @@ -18,7 +19,7 @@

final class UseStatementsBuilder
{
public function build(Class_|Interface_|Trait_ $definition): UseStatements
public function build(Class_|Interface_|Trait_|Enum_ $definition): UseStatements
{
$uses = [];

Expand Down
20 changes: 14 additions & 6 deletions src/Parser/Code/ExternalDefinitionsResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,27 @@

use PhUml\Code\ClassDefinition;
use PhUml\Code\Codebase;
use PhUml\Code\EnumDefinition;
use PhUml\Code\InterfaceDefinition;
use PhUml\Code\Name;
use PhUml\Code\TraitDefinition;

/**
* It checks the parent of a definition, the interfaces it implements, and the traits it uses
* looking for external definitions
* It looks for external definitions from the parent of a definition, the interfaces it implements, and the traits it
* uses
*
* An external definition is a class, trait or interface from a third party library, or a built-in class or interface
*/
final class ExternalDefinitionsResolver implements RelationshipsResolver
{
public function resolve(Codebase $codebase): void
{
/** @var ClassDefinition|InterfaceDefinition|TraitDefinition $definition */
/** @var ClassDefinition|InterfaceDefinition|TraitDefinition|EnumDefinition $definition */
foreach ($codebase->definitions() as $definition) {
match (true) {
$definition instanceof ClassDefinition => $this->resolveForClass($definition, $codebase),
$definition instanceof InterfaceDefinition => $this->resolveInterfaces($definition->parents(), $codebase),
match ($definition::class) {
ClassDefinition::class => $this->resolveForClass($definition, $codebase),
EnumDefinition::class => $this->resolveForEnum($definition, $codebase),
InterfaceDefinition::class => $this->resolveInterfaces($definition->parents(), $codebase),
default => $this->resolveTraits($definition->traits(), $codebase),
};
}
Expand All @@ -41,6 +43,12 @@ private function resolveForClass(ClassDefinition $definition, Codebase $codebase
$this->resolveExternalParentClass($definition, $codebase);
}

private function resolveForEnum(EnumDefinition $definition, Codebase $codebase): void
{
$this->resolveInterfaces($definition->interfaces(), $codebase);
$this->resolveTraits($definition->traits(), $codebase);
}

/** @param Name[] $interfaces */
private function resolveInterfaces(array $interfaces, Codebase $codebase): void
{
Expand Down
Loading

0 comments on commit eed0fd7

Please sign in to comment.