Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: document event by combining attributes and reflection #15

Merged
merged 7 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Attribute/AbstractProperty.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Ferror\AsyncapiDocBundle\Attribute;

abstract class AbstractProperty
abstract class AbstractProperty implements PropertyInterface
{
public function __construct(
public string $name,
Expand Down
27 changes: 24 additions & 3 deletions src/Attribute/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
use Ferror\AsyncapiDocBundle\Schema\V2\ChannelType;

#[Attribute(Attribute::TARGET_CLASS)]
class Message implements PropertyInterface
class Message
{
/**
* @param PropertyInterface[] $properties
* @param array<Property|PropertyArray|PropertyEnum|PropertyObject|PropertyArrayObject> $properties
*/
public function __construct(
public readonly string $name,
Expand All @@ -31,8 +31,29 @@ public function toArray(): array
];
}

public function addProperty(PropertyInterface $property): void
public function addProperty(Property|PropertyArray|PropertyEnum|PropertyObject|PropertyArrayObject $property): void
{
$this->properties[] = $property;
}

public function enrich(self $self): self
{
// UPDATE EXISTING
foreach ($this->properties as $property) {
foreach ($self->properties as $selfProperty) {
$property->enrich($selfProperty);
}
}

// ADD MISSING
$propertiesNames = array_map(fn ($property) => $property->name, $this->properties);

foreach ($self->properties as $property) {
if (!in_array($property->name, $propertiesNames, true)) {
$this->properties[] = $property;
}
}

return $this;
}
}
29 changes: 25 additions & 4 deletions src/Attribute/Property.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
use Ferror\AsyncapiDocBundle\Schema\PropertyType;

#[Attribute(Attribute::TARGET_PROPERTY)]
class Property extends AbstractProperty implements PropertyInterface
class Property extends AbstractProperty
{
public function __construct(
string $name,
string $description = '',
public readonly PropertyType $type = PropertyType::STRING,
public readonly ?Format $format = null,
public readonly ?string $example = null,
public PropertyType $type = PropertyType::STRING,
public ?Format $format = null,
public ?string $example = null,
bool $required = true,
) {
parent::__construct($name, $description, $required);
Expand All @@ -30,4 +30,25 @@ public function toArray(): array
'example' => $this->example,
]);
}

public function enrich(Property|PropertyArray|PropertyEnum|PropertyObject|PropertyArrayObject $property): void
{
if ($property->name === $this->name && $property::class === $this::class) {
if (empty($this->format)) {
$this->format = $property->format;
}

if ($this->type !== $property->type && $this->type === PropertyType::STRING) {
$this->type = $property->type;
}

if (empty($this->example)) {
$this->example = $property->example;
}

if (empty($this->description)) {
$this->description = $property->description;
}
}
}
}
6 changes: 5 additions & 1 deletion src/Attribute/PropertyArray.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use Ferror\AsyncapiDocBundle\Schema\PropertyType;

#[Attribute(Attribute::TARGET_PROPERTY)]
class PropertyArray extends AbstractProperty implements PropertyInterface
class PropertyArray extends AbstractProperty
{
public function __construct(
string $name,
Expand All @@ -31,4 +31,8 @@ public function toArray(): array
'itemsType' => $this->itemsType->value,
]);
}

public function enrich(Property|PropertyArray|PropertyEnum|PropertyObject|PropertyArrayObject $property): void
{
}
}
6 changes: 5 additions & 1 deletion src/Attribute/PropertyArrayObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Ferror\AsyncapiDocBundle\Schema\Format;

#[Attribute(Attribute::TARGET_PROPERTY)]
class PropertyArrayObject extends AbstractProperty implements PropertyInterface
class PropertyArrayObject extends AbstractProperty
{
public function __construct(
string $name,
Expand All @@ -29,4 +29,8 @@ public function toArray(): array
'example' => $this->example,
]);
}

public function enrich(Property|PropertyArray|PropertyEnum|PropertyObject|PropertyArrayObject $property): void
{
}
}
20 changes: 19 additions & 1 deletion src/Attribute/PropertyEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
use Attribute;
use Ferror\AsyncapiDocBundle\PropertyTypeTranslator;
use Ferror\AsyncapiDocBundle\Schema\Format;
use Ferror\AsyncapiDocBundle\Schema\PropertyType;
use InvalidArgumentException;
use ReflectionEnum;
use ReflectionEnumBackedCase;
use ReflectionException;

#[Attribute(Attribute::TARGET_PROPERTY)]
class PropertyEnum extends AbstractProperty implements PropertyInterface
class PropertyEnum extends AbstractProperty
{
public function __construct(
string $name,
Expand All @@ -25,6 +27,9 @@ public function __construct(
parent::__construct($name, $description, $required);
}

/**
* @throws ReflectionException
*/
public function toArray(): array
{
$enum = new ReflectionEnum($this->enum);
Expand All @@ -43,4 +48,17 @@ public function toArray(): array
'example' => $this->example,
]);
}

public function enrich(Property|PropertyArray|PropertyEnum|PropertyObject|PropertyArrayObject $property): void
{
if ($property->name === $this->name && $property::class === $this::class) {
if (empty($this->format)) {
$this->format = $property->format;
}

if (empty($this->example)) {
$this->example = $property->example;
}
}
}
}
2 changes: 2 additions & 0 deletions src/Attribute/PropertyInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@
interface PropertyInterface
{
public function toArray(): array;

public function enrich(Property|PropertyArray|PropertyEnum|PropertyObject|PropertyArrayObject $property): void;
}
6 changes: 5 additions & 1 deletion src/Attribute/PropertyObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Attribute;

#[Attribute(Attribute::TARGET_PROPERTY)]
class PropertyObject extends AbstractProperty implements PropertyInterface
class PropertyObject extends AbstractProperty
{
public function __construct(
string $name,
Expand All @@ -26,4 +26,8 @@ public function toArray(): array
'items' => array_map(static fn(PropertyInterface $property) => $property->toArray(), $this->items),
]);
}

public function enrich(Property|PropertyArray|PropertyEnum|PropertyObject|PropertyArrayObject $property): void
{
}
}
21 changes: 14 additions & 7 deletions src/DocumentationEditor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,38 @@

namespace Ferror\AsyncapiDocBundle;

use Ferror\AsyncapiDocBundle\DocumentationStrategy\PrioritisedDocumentationStrategy;

use Ferror\AsyncapiDocBundle\Attribute\Message;
use Ferror\AsyncapiDocBundle\DocumentationStrategy\PrioritisedDocumentationStrategyInterface;

final readonly class DocumentationEditor
{
/**
* @param PrioritisedDocumentationStrategy[] $documentationStrategies
* @param PrioritisedDocumentationStrategyInterface[] $documentationStrategies
*/
public function __construct(
private array $documentationStrategies,
) {
}

public function document(string $class): array
public function document(string $class): Message
{
$result = [];
$strategies = [];

foreach ($this->documentationStrategies as $documentationStrategy) {
$strategies[$documentationStrategy->priority] = $documentationStrategy->strategy;
$strategies[$documentationStrategy::getDefaultPriority()] = $documentationStrategy;
}

$firstStrategy = array_shift($strategies);

$documentedMessage = $firstStrategy->document($class);

foreach ($strategies as $documentationStrategy) {
$result = array_merge($result, $documentationStrategy->document($class)->toArray());
$message = $documentationStrategy->document($class);

$documentedMessage = $documentedMessage->enrich($message);
}

return $result;
return $documentedMessage;
}
}
12 changes: 11 additions & 1 deletion src/DocumentationStrategy/AttributeDocumentationStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,24 @@
use Ferror\AsyncapiDocBundle\Attribute\Message;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionException;

final readonly class AttributeDocumentationStrategy implements DocumentationStrategyInterface
final readonly class AttributeDocumentationStrategy implements PrioritisedDocumentationStrategyInterface
{
public function __construct(
private PropertyExtractor $propertyExtractor = new PropertyExtractor(),
) {
}

public static function getDefaultPriority(): int
{
return 10;
}

/**
* @throws DocumentationStrategyException
* @throws ReflectionException
*/
public function document(string $class): Message
{
$reflection = new ReflectionClass($class);
Expand Down
14 changes: 0 additions & 14 deletions src/DocumentationStrategy/PrioritisedDocumentationStrategy.php

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Ferror\AsyncapiDocBundle\DocumentationStrategy;

interface PrioritisedDocumentationStrategyInterface extends DocumentationStrategyInterface
{
public static function getDefaultPriority(): int;
}
16 changes: 6 additions & 10 deletions src/DocumentationStrategy/PropertyExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@
use ReflectionAttribute;
use ReflectionClass;
use ReflectionException;
use RuntimeException;

class PropertyExtractor
{
/**
* @return iterable<PropertyInterface>
* @return iterable<Property|PropertyArray|PropertyEnum|PropertyObject|PropertyArrayObject>
*
* @throws ReflectionException
* @throws RuntimeException
*/
public function extract(string $class): iterable
{
Expand Down Expand Up @@ -53,14 +51,12 @@ public function extract(string $class): iterable
$propertyAttributes = $property->getAttributes(PropertyArrayObject::class);
}

if (empty($propertyAttributes)) {
throw new RuntimeException('Property attribute for ' . $property->name . ' not found');
}
if ($propertyAttributes) {
$propertyAttribute = $propertyAttributes[0]->newInstance();
$propertyAttribute->name = $property->name;

$propertyAttribute = $propertyAttributes[0]->newInstance();
$propertyAttribute->name = $property->name;

yield $propertyAttribute;
yield $propertyAttribute;
}
}
}
}
33 changes: 25 additions & 8 deletions src/DocumentationStrategy/ReflectionDocumentationStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@
use ReflectionException;
use ReflectionNamedType;

final readonly class ReflectionDocumentationStrategy implements DocumentationStrategyInterface
final readonly class ReflectionDocumentationStrategy implements PrioritisedDocumentationStrategyInterface
{
public static function getDefaultPriority(): int
{
return 20;
}

/**
* @param class-string $class
*
Expand All @@ -38,13 +43,25 @@ public function document(string $class): Message
$type = $property->getType();
$name = $property->getName();

$message->addProperty(
new Property(
name: $name,
type: PropertyType::fromNative($type?->getName()),
required: $type && !$type->allowsNull(),
)
);
if (null === $type) {
break;
}

// ATM we don't support array types in ReflectionStrategy
if (in_array($type->getName(), ['array', 'object', 'resource'])) {
break;
}

// ATM we support only builtin types like integer, string, boolean, float
if ($type->isBuiltin()) {
$message->addProperty(
new Property(
name: $name,
type: PropertyType::fromNative($type->getName()),
required: !$type->allowsNull(),
)
);
}
}

return $message;
Expand Down
Loading
Loading