Skip to content
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
11 changes: 11 additions & 0 deletions src/Infer/Analyzer/ClassAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
namespace Dedoc\Scramble\Infer\Analyzer;

use Dedoc\Scramble\Infer\Context;
use Dedoc\Scramble\Infer\Definition\AttributeDefinition;
use Dedoc\Scramble\Infer\Definition\ClassDefinition;
use Dedoc\Scramble\Infer\Definition\ClassPropertyDefinition;
use Dedoc\Scramble\Infer\Definition\PropertyVisibility;
use Dedoc\Scramble\Infer\Extensions\Event\ClassDefinitionCreatedEvent;
use Dedoc\Scramble\Infer\Scope\Index;
use Dedoc\Scramble\Support\Type\TemplateType;
Expand Down Expand Up @@ -59,6 +61,11 @@ public function analyze(string $name): ClassDefinition
type: $reflectionProperty->hasDefaultValue()
? (TypeHelper::createTypeFromValue($reflectionProperty->getDefaultValue()) ?: new UnknownType)
: new UnknownType,
isStatic: $reflectionProperty->isStatic(),
visibility: PropertyVisibility::fromReflectionProperty($reflectionProperty),
attributes: AttributeDefinition::fromReflectionAttributesArray($reflectionProperty->getAttributes()),
docComment: $reflectionProperty->getDocComment() ?: null,
declaringFileName: $reflectionProperty->getDeclaringClass()->getFileName() ?: null,
);
} else {
$expectedTemplateTypeName = 'T'.Str::studly($reflectionProperty->name);
Expand All @@ -76,6 +83,10 @@ public function analyze(string $name): ClassDefinition
defaultType: $reflectionProperty->hasDefaultValue()
? PropertyAnalyzer::from($reflectionProperty)->getDefaultType()
: null,
visibility: PropertyVisibility::fromReflectionProperty($reflectionProperty),
attributes: AttributeDefinition::fromReflectionAttributesArray($reflectionProperty->getAttributes()),
docComment: $reflectionProperty->getDocComment() ?: null,
declaringFileName: $reflectionProperty->getDeclaringClass()->getFileName() ?: null,
);

if (! $existingPropertyTemplateType) {
Expand Down
32 changes: 32 additions & 0 deletions src/Infer/Definition/AttributeDefinition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Dedoc\Scramble\Infer\Definition;

use ReflectionAttribute;

class AttributeDefinition
{
public const IS_INSTANCEOF = ReflectionAttribute::IS_INSTANCEOF;

public function __construct(
public string $name,
public array $arguments = [],
) {}

/**
* @param ReflectionAttribute[] $reflectionAttributes
* @return self[]
*/
public static function fromReflectionAttributesArray(array $reflectionAttributes): array
{
return array_map(static::fromReflectionAttribute(...), $reflectionAttributes);
}

public static function fromReflectionAttribute(ReflectionAttribute $reflectionAttribute): self
{
return new self(
name: $reflectionAttribute->getName(),
arguments: $reflectionAttribute->getArguments(),
);
}
}
44 changes: 44 additions & 0 deletions src/Infer/Definition/ClassPropertyDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,56 @@

namespace Dedoc\Scramble\Infer\Definition;

use Dedoc\Scramble\Infer\Services\FileNameResolver;
use Dedoc\Scramble\Support\PhpDoc;
use Dedoc\Scramble\Support\Type\Type;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;

class ClassPropertyDefinition
{
private ?PhpDocNode $docNode = null;

public function __construct(
public Type $type,
public ?Type $defaultType = null,
public readonly bool $isStatic = false,
public readonly PropertyVisibility $visibility = PropertyVisibility::Public,
/** @var AttributeDefinition[] */
public readonly array $attributes = [],
public ?string $docComment = null,
public ?string $declaringFileName = null,
) {}

public function getDocNode(): ?PhpDocNode
{
if (! $this->docComment) {
return null;
}

return $this->docNode ??= PhpDoc::parse(
$this->docComment,
$this->declaringFileName ? FileNameResolver::createForFile($this->declaringFileName) : null,
);
}

/**
* @return AttributeDefinition[]
*/
public function getAttributes(?string $name = null, int $flags = 0): array
{
if ($name === null) {
return $this->attributes;
}

return array_values(array_filter(
$this->attributes,
function (AttributeDefinition $attribute) use ($name, $flags) {
if ($flags & AttributeDefinition::IS_INSTANCEOF) {
return is_a($attribute->name, $name, true);
}

return $attribute->name === $name;
},
));
}
}
21 changes: 21 additions & 0 deletions src/Infer/Definition/PropertyVisibility.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Dedoc\Scramble\Infer\Definition;

use ReflectionProperty;

enum PropertyVisibility
{
case Private;
case Protected;
case Public;

public static function fromReflectionProperty(ReflectionProperty $property): self
{
return match (true) {
$property->isPrivate() => self::Private,
$property->isProtected() => self::Protected,
default => self::Public,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

use Dedoc\Scramble\Infer\Contracts\ClassDefinitionBuilder;
use Dedoc\Scramble\Infer\Contracts\Index as IndexContract;
use Dedoc\Scramble\Infer\Definition\AttributeDefinition;
use Dedoc\Scramble\Infer\Definition\ClassDefinition;
use Dedoc\Scramble\Infer\Definition\ClassPropertyDefinition;
use Dedoc\Scramble\Infer\Definition\LazyShallowClassDefinition;
use Dedoc\Scramble\Infer\Definition\PropertyVisibility;
use Dedoc\Scramble\Support\Type\TemplateType;
use Dedoc\Scramble\Support\Type\TypeHelper;
use Dedoc\Scramble\Support\Type\UnknownType;
Expand Down Expand Up @@ -49,6 +51,11 @@ public function build(): LazyShallowClassDefinition
type: $reflectionProperty->hasDefaultValue()
? (TypeHelper::createTypeFromValue($reflectionProperty->getDefaultValue()) ?: new UnknownType)
: new UnknownType,
isStatic: $reflectionProperty->isStatic(),
visibility: PropertyVisibility::fromReflectionProperty($reflectionProperty),
attributes: AttributeDefinition::fromReflectionAttributesArray($reflectionProperty->getAttributes()),
docComment: $reflectionProperty->getDocComment() ?: null,
declaringFileName: $reflectionProperty->getDeclaringClass()->getFileName() ?: null,
);
} else {
$classDefinitionData->properties[$reflectionProperty->name] = new ClassPropertyDefinition(
Expand All @@ -59,6 +66,10 @@ public function build(): LazyShallowClassDefinition
defaultType: $reflectionProperty->hasDefaultValue()
? TypeHelper::createTypeFromValue($reflectionProperty->getDefaultValue())
: null,
visibility: PropertyVisibility::fromReflectionProperty($reflectionProperty),
attributes: AttributeDefinition::fromReflectionAttributesArray($reflectionProperty->getAttributes()),
docComment: $reflectionProperty->getDocComment() ?: null,
declaringFileName: $reflectionProperty->getDeclaringClass()->getFileName() ?: null,
);
$classDefinitionData->templateTypes[] = $t;
}
Expand Down
28 changes: 28 additions & 0 deletions src/Infer/Handler/PropertyHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

namespace Dedoc\Scramble\Infer\Handler;

use Dedoc\Scramble\Infer\Definition\AttributeDefinition;
use Dedoc\Scramble\Infer\Definition\ClassPropertyDefinition;
use Dedoc\Scramble\Infer\Definition\PropertyVisibility;
use Dedoc\Scramble\Infer\Scope\Scope;
use Dedoc\Scramble\Support\Type\TemplateType;
use Dedoc\Scramble\Support\Type\TypeHelper;
use Illuminate\Support\Str;
use PhpParser\Node;
use ReflectionProperty;

class PropertyHandler
{
Expand All @@ -29,9 +32,34 @@ public function leave(Node\Stmt\Property $node, Scope $scope)
? TypeHelper::createTypeFromTypeNode($node->type)
: null;

$attributes = [];
$docComment = $node->getDocComment()?->getText();
$declaringFileName = null;

try {
$reflectionProperty = new ReflectionProperty($classDefinition->name, $prop->name->name);
$attributes = AttributeDefinition::fromReflectionAttributesArray($reflectionProperty->getAttributes());

if (! $docComment) {
$docComment = $reflectionProperty->getDocComment() ?: null;
}

$declaringFileName = $reflectionProperty->getDeclaringClass()->getFileName() ?: null;
} catch (\ReflectionException) {
}

$propertyDefinition = new ClassPropertyDefinition(
type: new TemplateType($scope->makeConflictFreeTemplateName('T'.Str::studly($prop->name->name)), $annotatedType),
defaultType: $prop->default ? $scope->getType($prop->default) : null,
isStatic: $node->isStatic(),
visibility: match (true) {
$node->isPrivate() => PropertyVisibility::Private,
$node->isProtected() => PropertyVisibility::Protected,
default => PropertyVisibility::Public,
},
attributes: $attributes,
docComment: $docComment,
declaringFileName: $declaringFileName,
);
$ownProperties[$prop->name->name] = $propertyDefinition;
}
Expand Down
4 changes: 4 additions & 0 deletions src/ScrambleServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
use Dedoc\Scramble\Support\Type\VoidType;
use Dedoc\Scramble\Support\TypeToSchemaExtensions\ArrayableToSchema;
use Dedoc\Scramble\Support\TypeToSchemaExtensions\BinaryFileResponseToSchema;
use Dedoc\Scramble\Support\TypeToSchemaExtensions\CarbonInterfaceToSchema;
use Dedoc\Scramble\Support\TypeToSchemaExtensions\CollectionToSchema;
use Dedoc\Scramble\Support\TypeToSchemaExtensions\CursorPaginatorTypeToSchema;
use Dedoc\Scramble\Support\TypeToSchemaExtensions\EloquentCollectionToSchema;
Expand All @@ -72,6 +73,7 @@
use Dedoc\Scramble\Support\TypeToSchemaExtensions\LengthAwarePaginatorTypeToSchema;
use Dedoc\Scramble\Support\TypeToSchemaExtensions\PaginatedResourceResponseTypeToSchema;
use Dedoc\Scramble\Support\TypeToSchemaExtensions\PaginatorTypeToSchema;
use Dedoc\Scramble\Support\TypeToSchemaExtensions\PlainObjectToSchema;
use Dedoc\Scramble\Support\TypeToSchemaExtensions\ResourceCollectionTypeToSchema;
use Dedoc\Scramble\Support\TypeToSchemaExtensions\ResourceResponseTypeToSchema;
use Dedoc\Scramble\Support\TypeToSchemaExtensions\ResponsableTypeToSchema;
Expand Down Expand Up @@ -232,6 +234,7 @@ functions: [
$parameters['infer'] ?? $application->make(Infer::class),
$parameters['context'],
typeToSchemaExtensionsClasses: $parameters['typeToSchemaExtensions'] ?? array_merge([
PlainObjectToSchema::class,
ResponsableTypeToSchema::class,
ArrayableToSchema::class,
EnumToSchema::class,
Expand All @@ -251,6 +254,7 @@ functions: [
JsonApiAnonymousCollectionTypeToSchema::class,
JsonApiResourceResponseToSchemaExtension::class,
JsonApiPaginatedResourceResponseToSchemaExtension::class,
CarbonInterfaceToSchema::class,
VoidTypeToSchema::class,
], $typesToSchemaExtensions),
exceptionToResponseExtensionsClasses: $parameters['exceptionToResponseExtensions'] ?? array_merge([
Expand Down
7 changes: 1 addition & 6 deletions src/Support/Generator/TypeTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Dedoc\Scramble\Support\Generator;

use Carbon\CarbonInterface;
use Dedoc\Scramble\Extensions\ExceptionToResponseExtension;
use Dedoc\Scramble\Extensions\TypeToSchemaExtension;
use Dedoc\Scramble\Infer;
Expand Down Expand Up @@ -370,11 +369,7 @@ private function transformUncached(Type $type): OpenApiType
} elseif ($type instanceof \Dedoc\Scramble\Support\Type\MixedType) {
$openApiType = new MixedType;
} elseif ($type instanceof \Dedoc\Scramble\Support\Type\ObjectType) {
if ($type->isInstanceOf(CarbonInterface::class)) {
$openApiType = (new StringType)->format('date-time');
} else {
$openApiType = new ObjectType;
}
$openApiType = new ObjectType;
} elseif ($type instanceof \Dedoc\Scramble\Support\Type\IntersectionType) {
$openApiType = (new AllOf)->setItems(array_map(
fn ($t) => $this->transform($t),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@ public function afterClassDefinitionCreated(ClassDefinitionCreatedEvent $event):

$definition->properties['collects'] = new ClassPropertyDefinition(
type: new GenericClassStringType($tCollects),
attributes: [],
);

$definition->properties['collection'] = new ClassPropertyDefinition(
type: new Generic(Collection::class, [
new IntegerType,
$tCollects,
]),
attributes: [],
);

$definition->methods['toArray'] = new ShallowFunctionDefinition(
Expand Down
21 changes: 21 additions & 0 deletions src/Support/TypeToSchemaExtensions/CarbonInterfaceToSchema.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Dedoc\Scramble\Support\TypeToSchemaExtensions;

use Carbon\CarbonInterface;
use Dedoc\Scramble\Extensions\TypeToSchemaExtension;
use Dedoc\Scramble\Support\Generator\Types\StringType;
use Dedoc\Scramble\Support\Type\Type;

class CarbonInterfaceToSchema extends TypeToSchemaExtension
{
public function shouldHandle(Type $type): bool
{
return $type->isInstanceOf(CarbonInterface::class);
}

public function toSchema(Type $type)
{
return (new StringType)->format('date-time');
}
}
14 changes: 8 additions & 6 deletions src/Support/TypeToSchemaExtensions/CollectionToSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,29 @@
namespace Dedoc\Scramble\Support\TypeToSchemaExtensions;

use Dedoc\Scramble\Extensions\TypeToSchemaExtension;
use Dedoc\Scramble\Support\Generator\Types\ObjectType as OpenApiObjectType;
use Dedoc\Scramble\Support\Type\ArrayType;
use Dedoc\Scramble\Support\Type\Generic;
use Dedoc\Scramble\Support\Type\ObjectType;
use Dedoc\Scramble\Support\Type\Type;
use Illuminate\Support\Collection;

class CollectionToSchema extends TypeToSchemaExtension
{
public function shouldHandle(Type $type)
{
return $type instanceof Generic
&& count($type->templateTypes) === 2
&& $type->isInstanceOf(Collection::class);
return $type instanceof ObjectType && $type->isInstanceOf(Collection::class);
}

/**
* @param Generic $type
* @param ObjectType $type
*/
public function toSchema(Type $type)
{
$type = new ArrayType(value: $type->templateTypes[1]/* TValue */);
if ($type instanceof Generic && array_key_exists(1, $type->templateTypes)) {
return $this->openApiTransformer->transform(new ArrayType(value: $type->templateTypes[1]/* TValue */));
}

return $this->openApiTransformer->transform($type);
return new OpenApiObjectType;
}
}
Loading
Loading