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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.3] - 2025-01-25

### Changed
- Upgrade `phpdocumentor/reflection-docblock` from ^5.3 to ^6.0
- Upgrade `phpdocumentor/type-resolver` from ^1.6.1 to ^2.0
- Inject `DocBlockFactoryInterface` via constructor in `ReturnEntity`

### Fixed
- Fix `extractValueType` to correctly return value type for multi-parameter generics (`array<K, V>`)

## [1.0.1] - 2025-11-23

### Fixed
Expand Down Expand Up @@ -170,6 +180,7 @@ Stable release. Major update to PHP 8 attributes.

Please refer to the git history for changes in earlier versions.

[1.0.3]: https://github.com/ray-di/Ray.MediaQuery/compare/1.0.1...1.0.3
[1.0.1]: https://github.com/ray-di/Ray.MediaQuery/compare/1.0.0...1.0.1
[1.0.0]: https://github.com/ray-di/Ray.MediaQuery/compare/1.0.0-rc2...1.0.0
[1.0.0-rc2]: https://github.com/ray-di/Ray.MediaQuery/compare/1.0.0-rc1...1.0.0-rc2
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"koriym/csv-entities": "^1.0",
"koriym/null-object": "^1.0.1",
"pagerfanta/pagerfanta": "^3.5 || ^4.7",
"phpdocumentor/reflection-docblock": "^5.3",
"phpdocumentor/type-resolver": "^1.6.1",
"phpdocumentor/reflection-docblock": "^6.0",
"phpdocumentor/type-resolver": "^2.0",
"ray/aop": "^2.19",
"ray/aura-sql-module": "^1.17",
"ray/di": "^2.19",
Expand Down
3 changes: 3 additions & 0 deletions src/MediaQueryDbModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Ray\MediaQuery;

use Override;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use Ray\Di\AbstractModule;
use Ray\MediaQuery\Annotation\DbQuery;
use Ray\MediaQuery\Annotation\Qualifier\FactoryMethod;
Expand Down Expand Up @@ -38,6 +40,7 @@ protected function configure(): void
[DbQueryInterceptor::class],
);
$this->bind()->annotatedWith(SqlDir::class)->toInstance($this->configs->sqlDir);
$this->bind(DocBlockFactoryInterface::class)->toInstance(DocBlockFactory::createInstance());
$this->bind(ReturnEntityInterface::class)->to(ReturnEntity::class);
$this->bind(FetchFactoryInterface::class)->to(FetchFactory::class);
$this->bind()->annotatedWith(FactoryMethod::class)->toInstance('factory');
Expand Down
35 changes: 27 additions & 8 deletions src/ReturnEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,29 @@

use Override;
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\PseudoTypes\Generic;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\Collection;
use phpDocumentor\Reflection\Types\ContextFactory;
use phpDocumentor\Reflection\Types\Object_;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionType;

use function array_key_last;
use function assert;
use function class_exists;
use function is_a;
use function substr;

final class ReturnEntity implements ReturnEntityInterface
{
public function __construct(
private DocBlockFactoryInterface $docBlockFactory,
) {
}

/** @inheritDoc */
#[Override]
public function __invoke(ReflectionMethod $method): string|null
Expand Down Expand Up @@ -49,17 +56,32 @@ private function getReturnTypeName(ReflectionType $reflectionType): string
return (string) $reflectionType;
}

private function extractValueType(Type|null $type): Type|null
{
if ($type instanceof Array_) {
return $type->getValueType();
}

if ($type instanceof Generic) {
$types = $type->getTypes();
$lastKey = array_key_last($types);

return $lastKey !== null ? $types[$lastKey] : null;
}

return null;
}

/** @return ?class-string */
private function docblock(ReflectionMethod $method): string|null
{
$factory = DocBlockFactory::createInstance();
$context = (new ContextFactory())->createFromReflector($method);
$docComment = $method->getDocComment();
if ($docComment === false) {
return null;
}

$docblock = $factory->create($docComment, $context);
$docblock = $this->docBlockFactory->create($docComment, $context);
$returns = $docblock->getTagsByName('return');
if (! isset($returns[0])) {
return null;
Expand All @@ -68,11 +90,8 @@ private function docblock(ReflectionMethod $method): string|null
$return = $returns[0];
assert($return instanceof Return_);
$type = $return->getType();
if (! $type instanceof Array_ && ! $type instanceof Collection) {
return null;
}

$valueType = $type->getValueType();
$valueType = $this->extractValueType($type);
if (! $valueType instanceof Object_) {
return null;
}
Expand Down
28 changes: 18 additions & 10 deletions tests/ReturnTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,88 +4,96 @@

namespace Ray\MediaQuery;

use phpDocumentor\Reflection\DocBlockFactory;
use PHPUnit\Framework\TestCase;
use Ray\MediaQuery\Entity\FakeEntity;
use ReflectionMethod;

class ReturnTypeTest extends TestCase
{
private ReturnEntity $returnEntity;

protected function setUp(): void
{
$this->returnEntity = new ReturnEntity(DocBlockFactory::createInstance());
}

public function testReturnItem(): void
{
$method = new ReflectionMethod(new FakeReturn(), 'item');
$entity = (new ReturnEntity())($method);
$entity = ($this->returnEntity)($method);

$this->assertSame(FakeEntity::class, $entity);
}

public function testReturnList(): void
{
$method = new ReflectionMethod(new FakeReturn(), 'list');
$entity = (new ReturnEntity())($method);
$entity = ($this->returnEntity)($method);

$this->assertSame(FakeEntity::class, $entity);
}

public function testNoReturnType(): void
{
$method = new ReflectionMethod(new FakeReturn(), 'noReturn');
$entity = (new ReturnEntity())($method);
$entity = ($this->returnEntity)($method);

$this->assertSame(null, $entity);
}

public function testnoPhpDoc(): void
{
$method = new ReflectionMethod(new FakeReturn(), 'noPhpDoc');
$entity = (new ReturnEntity())($method);
$entity = ($this->returnEntity)($method);

$this->assertSame(null, $entity);
}

public function testNoPhpDocFakePages(): void
{
$method = new ReflectionMethod(new FakeReturn(), 'noPhpDocFakePages');
$entity = (new ReturnEntity())($method);
$entity = ($this->returnEntity)($method);

$this->assertSame(null, $entity);
}

public function testNoReturnDoc(): void
{
$method = new ReflectionMethod(new FakeReturn(), 'noReturnDoc');
$entity = (new ReturnEntity())($method);
$entity = ($this->returnEntity)($method);

$this->assertSame(null, $entity);
}

public function testNoReturnDocFakePages(): void
{
$method = new ReflectionMethod(new FakeReturn(), 'noReturnDocFakePages');
$entity = (new ReturnEntity())($method);
$entity = ($this->returnEntity)($method);

$this->assertSame(null, $entity);
}

public function testNonEntityGeneric(): void
{
$method = new ReflectionMethod(new FakeReturn(), 'nonEntityGeneric');
$entity = (new ReturnEntity())($method);
$entity = ($this->returnEntity)($method);

$this->assertSame(null, $entity);
}

public function testInvalidReturnType(): void
{
$method = new ReflectionMethod(new FakeReturn(), 'invalidReturnType');
$entity = (new ReturnEntity())($method);
$entity = ($this->returnEntity)($method);

$this->assertSame(null, $entity);
}

public function testReturnArray(): void
{
$method = new ReflectionMethod(new FakeReturn(), 'returnArray');
$entity = (new ReturnEntity())($method);
$entity = ($this->returnEntity)($method);

$this->assertSame(null, $entity);
}
Expand Down
Loading