Skip to content

Commit

Permalink
Add support for constructor parameter types
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenvanassche committed Jun 21, 2023
1 parent 76bc861 commit 6a85dbb
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 21 deletions.
48 changes: 34 additions & 14 deletions src/Support/Annotations/DataCollectableAnnotationReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\ContextFactory;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use Spatie\LaravelData\Contracts\BaseData;
use Spatie\LaravelData\Contracts\BaseDataCollectable;
Expand All @@ -24,7 +25,7 @@ public static function create(): self
return new self();
}

/** @return array<string, \Spatie\LaravelData\Support\Annotations\DataCollectableAnnotation> */
/** @return array<string, DataCollectableAnnotation> */
public function getForClass(ReflectionClass $class): array
{
return collect($this->get($class))->keyBy(fn (DataCollectableAnnotation $annotation) => $annotation->property)->all();
Expand All @@ -35,9 +36,15 @@ public function getForProperty(ReflectionProperty $property): ?DataCollectableAn
return Arr::first($this->get($property));
}

/** @return \Spatie\LaravelData\Support\Annotations\DataCollectableAnnotation[] */
/** @return array<string, DataCollectableAnnotation> */
public function getForMethod(ReflectionMethod $method): array
{
return collect($this->get($method))->keyBy(fn (DataCollectableAnnotation $annotation) => $annotation->property)->all();
}

/** @return DataCollectableAnnotation[] */
protected function get(
ReflectionProperty|ReflectionClass $reflection
ReflectionProperty|ReflectionClass|ReflectionMethod $reflection
): array {
$comment = $reflection->getDocComment();

Expand All @@ -47,13 +54,14 @@ protected function get(

$comment = str_replace('?', '', $comment);

$kindPattern = '(?:@property|@var)\s*';
$kindPattern = '(?:@property|@var|@param)\s*';
$fqsenPattern = '[\\\\a-z0-9_\|]+';
$typesPattern = '[\\\\a-z0-9_\\|\\[\\]]+';
$keyPattern = '(?:int|string|\(int\|string\)|array-key)';
$parameterPattern = '\s*\$?(?<parameter>[a-z0-9_]+)?';

preg_match_all(
"/{$kindPattern}(?<dataClass>{$fqsenPattern})\[\]{$parameterPattern}/i",
"/{$kindPattern}(?<types>{$typesPattern}){$parameterPattern}/i",
$comment,
$arrayMatches,
);
Expand All @@ -71,15 +79,27 @@ protected function get(
}

protected function resolveArrayAnnotations(
ReflectionProperty|ReflectionClass $reflection,
ReflectionProperty|ReflectionClass|ReflectionMethod $reflection,
array $arrayMatches
): array {
$annotations = [];

foreach ($arrayMatches['dataClass'] as $index => $dataClass) {
foreach ($arrayMatches['types'] as $index => $types) {
$parameter = $arrayMatches['parameter'][$index];

$dataClass = $this->resolveDataClass($reflection, $dataClass);
$arrayType = Arr::first(
explode('|', $types),
fn(string $type) => str_contains($type, '[]'),
);

if(empty($arrayType)){
continue;
}

$dataClass = $this->resolveDataClass(
$reflection,
str_replace('[]', '', $arrayType)
);

if ($dataClass === null) {
continue;
Expand All @@ -96,7 +116,7 @@ protected function resolveArrayAnnotations(
}

protected function resolveCollectionAnnotations(
ReflectionProperty|ReflectionClass $reflection,
ReflectionProperty|ReflectionClass|ReflectionMethod $reflection,
array $collectionMatches
): array {
$annotations = [];
Expand All @@ -121,7 +141,7 @@ protected function resolveCollectionAnnotations(
}

protected function resolveDataClass(
ReflectionProperty|ReflectionClass $reflection,
ReflectionProperty|ReflectionClass|ReflectionMethod $reflection,
string $class
): ?string {
if (str_contains($class, '|')) {
Expand Down Expand Up @@ -150,7 +170,7 @@ protected function resolveDataClass(
}

protected function resolveCollectionClass(
ReflectionProperty|ReflectionClass $reflection,
ReflectionProperty|ReflectionClass|ReflectionMethod $reflection,
string $class
): ?string {
if (str_contains($class, '|')) {
Expand Down Expand Up @@ -190,7 +210,7 @@ protected function resolveCollectionClass(
}

protected function resolveFcqn(
ReflectionProperty|ReflectionClass $reflection,
ReflectionProperty|ReflectionClass|ReflectionMethod $reflection,
string $class
): ?string {
$context = $this->getContext($reflection);
Expand All @@ -201,9 +221,9 @@ protected function resolveFcqn(
}


protected function getContext(ReflectionProperty|ReflectionClass $reflection): Context
protected function getContext(ReflectionProperty|ReflectionClass|ReflectionMethod $reflection): Context
{
$reflectionClass = $reflection instanceof ReflectionProperty
$reflectionClass = $reflection instanceof ReflectionProperty || $reflection instanceof ReflectionMethod
? $reflection->getDeclaringClass()
: $reflection;

Expand Down
7 changes: 7 additions & 0 deletions src/Support/DataClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ public static function create(ReflectionClass $class): self

$dataCollectablePropertyAnnotations = DataCollectableAnnotationReader::create()->getForClass($class);

if($constructor){
$dataCollectablePropertyAnnotations = array_merge(
$dataCollectablePropertyAnnotations,
DataCollectableAnnotationReader::create()->getForMethod($constructor)
);
}

$properties = self::resolveProperties(
$class,
$constructor,
Expand Down
22 changes: 22 additions & 0 deletions tests/Fakes/CollectionAnnotationsData.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,27 @@ class CollectionAnnotationsData
public array $propertyR;

public array $propertyS;

public array $propertyT;

/**
* @param \Spatie\LaravelData\Tests\Fakes\SimpleData[]|null $propertyA
* @param null|\Spatie\LaravelData\Tests\Fakes\SimpleData[] $propertyB
* @param ?\Spatie\LaravelData\Tests\Fakes\SimpleData[] $propertyC
* @param ?\Spatie\LaravelData\Tests\Fakes\SimpleData[] $propertyD
* @param \Spatie\LaravelData\DataCollection<\Spatie\LaravelData\Tests\Fakes\SimpleData> $propertyE
* @param ?\Spatie\LaravelData\DataCollection<\Spatie\LaravelData\Tests\Fakes\SimpleData> $propertyF
* @param SimpleData[] $propertyG
*/
public function method(
array $propertyA,
?array $propertyB,
?array $propertyC,
array $propertyD,
DataCollection $propertyE,
?DataCollection $propertyF,
array $propertyG,
) {

}
}
14 changes: 14 additions & 0 deletions tests/Support/Annotations/DataCollectableAnnotationReaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,17 @@ function (string $property, ?DataCollectableAnnotation $expected) {
new DataCollectableAnnotation(SimpleData::class, property: 'propertyT'),
]);
});

it('can get data class for a data collection by method annotation', function () {
$annotations = app(DataCollectableAnnotationReader::class)->getForMethod(new ReflectionMethod(CollectionAnnotationsData::class, 'method'));

expect($annotations)->toEqualCanonicalizing([
new DataCollectableAnnotation(SimpleData::class, property: 'propertyA'),
new DataCollectableAnnotation(SimpleData::class, property: 'propertyB'),
new DataCollectableAnnotation(SimpleData::class, property: 'propertyC'),
new DataCollectableAnnotation(SimpleData::class, property: 'propertyD'),
new DataCollectableAnnotation(SimpleData::class, property: 'propertyE'),
new DataCollectableAnnotation(SimpleData::class, property: 'propertyF'),
new DataCollectableAnnotation(SimpleData::class, property: 'propertyG'),
]);
});
30 changes: 23 additions & 7 deletions tests/Support/DataTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@
use Spatie\LaravelData\Lazy;
use Spatie\LaravelData\Optional;
use Spatie\LaravelData\PaginatedDataCollection;
use Spatie\LaravelData\Support\Annotations\DataCollectableAnnotationReader;
use Spatie\LaravelData\Support\DataClass;
use Spatie\LaravelData\Support\DataType;
use Spatie\LaravelData\Support\Factories\DataTypeFactory;
use Spatie\LaravelData\Support\Lazy\ClosureLazy;
use Spatie\LaravelData\Support\Lazy\ConditionalLazy;
use Spatie\LaravelData\Support\Lazy\InertiaLazy;
Expand All @@ -42,12 +41,9 @@

function resolveDataType(object $class, string $property = 'property'): DataType
{
$reflectionProperty = new ReflectionProperty($class, $property);
$class = DataClass::create(new ReflectionClass($class));

return DataTypeFactory::create()->build(
$reflectionProperty,
DataCollectableAnnotationReader::create()->getForClass($reflectionProperty->getDeclaringClass())[$property] ?? null,
);
return $class->properties->get($property)->type;
}

it('can deduce a type without definition', function () {
Expand Down Expand Up @@ -932,6 +928,26 @@ public function __construct(
->dataCollectableClass->toBe('array');
});

it('can annotate data collections using constructor parameter annotations', function () {
class TestDataTypeWithClassAnnotatedConstructorParam
{
/**
* @param array<SimpleData> $property
*/
public function __construct(
public array $property,
) {
}
}

$type = resolveDataType(new \TestDataTypeWithClassAnnotatedConstructorParam([]));

expect($type)
->kind->toBe(DataTypeKind::Array)
->dataClass->toBe(SimpleData::class)
->dataCollectableClass->toBe('array');
});

it('can deduce the types of lazy', function () {
$type = resolveDataType(new class () {
public SimpleData|Lazy $property;
Expand Down

0 comments on commit 6a85dbb

Please sign in to comment.