Skip to content

Commit 4791853

Browse files
committed
Apply fixed Coderabbitai review.
1 parent aaaa39c commit 4791853

11 files changed

Lines changed: 163 additions & 13 deletions

File tree

src/Reflector.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@
1414
use ReflectionUnionType;
1515

1616
use function array_map;
17+
use function array_shift;
1718
use function class_exists;
1819
use function count;
20+
use function enum_exists;
21+
use function interface_exists;
1922
use function is_object;
23+
use function trait_exists;
2024

2125
/**
2226
* Provides lightweight reflection utilities for classes and properties.
@@ -38,7 +42,7 @@ final class Reflector
3842
/**
3943
* Maximum number of cached reflection classes.
4044
*/
41-
private const MAX_REFLECTION_CLASS_CACHE_SIZE = 1024;
45+
private const MAX_REFLECTION_CLASS_CACHE_SIZE = 512;
4246

4347
/**
4448
* Stores reflection instances keyed by class name.
@@ -234,7 +238,7 @@ public static function propertyAttributeInstances(
234238
* SomeClass::class,
235239
* 'someProperty',
236240
* SomeAttribute::class,
237-
* ReflectionAttribute::IS_INSTANCEOF,
241+
* ReflectionAttribute::IS_INSTANCEOF,
238242
* );
239243
* ```
240244
*
@@ -278,7 +282,7 @@ public static function propertyTypeNames(object|string $class, string $property)
278282
if ($type instanceof ReflectionNamedType) {
279283
$typeName = $type->getName();
280284

281-
if ($type->allowsNull() && $typeName !== 'null') {
285+
if ($type->allowsNull() && $typeName !== 'null' && $typeName !== 'mixed') {
282286
return [$typeName, 'null'];
283287
}
284288

@@ -331,7 +335,12 @@ private static function reflectionClass(object|string $class): ReflectionClass
331335
{
332336
$className = is_object($class) ? $class::class : $class;
333337

334-
if (!class_exists($className)) {
338+
if (
339+
!class_exists($className)
340+
&& !interface_exists($className)
341+
&& !trait_exists($className)
342+
&& !enum_exists($className)
343+
) {
335344
throw new InvalidArgumentException(
336345
Message::REFLECTOR_TARGET_INVALID->getMessage($className),
337346
);
@@ -345,7 +354,7 @@ private static function reflectionClass(object|string $class): ReflectionClass
345354

346355
if (!$reflectionClass->isAnonymous()) {
347356
if (count(self::$reflectionClassCache) >= self::MAX_REFLECTION_CLASS_CACHE_SIZE) {
348-
self::$reflectionClassCache = [];
357+
array_shift(self::$reflectionClassCache);
349358
}
350359

351360
self::$reflectionClassCache[$className] = $reflectionClass;

tests/ReflectorTest.php

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PHPForge\Helper\Exception\Message;
99
use PHPForge\Helper\Reflector;
1010
use PHPForge\Helper\Tests\Support\Attribute\{Label, Marker};
11+
use PHPForge\Helper\Tests\Support\Contract\{LeftContract, Status, UsesTimestamp};
1112
use PHPForge\Helper\Tests\Support\Model\ReflectionFixture;
1213
use PHPUnit\Framework\TestCase;
1314
use ReflectionClass;
@@ -18,13 +19,14 @@
1819
* Unit tests for the {@see Reflector} helper.
1920
*
2021
* Test coverage.
21-
* - Caches reflection class instances for repeated lookups and resets the cache when its size limit is reached.
22+
* - Caches reflection class instances for repeated lookups and evicts the oldest entry at cache-size limit.
2223
* - Detects whether a property exists on the reflection target.
2324
* - Extracts class and property attributes, including filtered lookups.
2425
* - Resolves first matching property attribute instances or returns `null` when no match exists.
25-
* - Resolves property type names for untyped, named, nullable, union, and intersection declarations.
26+
* - Resolves property type names for mixed, untyped, named, nullable, union, and intersection declarations.
2627
* - Returns reflection properties and throws {@see InvalidArgumentException} for missing properties.
27-
* - Returns short class names for named and anonymous classes and throws.
28+
* - Returns short names for class, enum, interface, trait, and anonymous targets and throws
29+
* {@see InvalidArgumentException} for invalid targets.
2830
*
2931
* @copyright Copyright (C) 2026 Terabytesoftw.
3032
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
@@ -211,7 +213,10 @@ public function testPropertyAttributeInstancesReturnsInstantiatedAttributes(): v
211213
}
212214

213215
self::assertSame(
214-
['primary', 'secondary'],
216+
[
217+
'primary',
218+
'secondary',
219+
],
215220
$instanceValues,
216221
'Should preserve declaration order for instantiated repeatable attributes.',
217222
);
@@ -320,7 +325,16 @@ public function testPropertyTypeNamesReturnsExpectedNamesForUnionProperty(): voi
320325
);
321326
}
322327

323-
public function testReflectionClassCacheIsResetWhenCacheSizeLimitIsReached(): void
328+
public function testPropertyTypeNamesReturnsMixedWithoutExplicitNull(): void
329+
{
330+
self::assertSame(
331+
['mixed'],
332+
Reflector::propertyTypeNames(ReflectionFixture::class, 'payload'),
333+
"Should return only 'mixed' without appending an explicit null type.",
334+
);
335+
}
336+
337+
public function testReflectionClassCacheEvictsOldestEntryWhenCacheSizeLimitIsReached(): void
324338
{
325339
$reflectorClass = new ReflectionClass(Reflector::class);
326340

@@ -343,14 +357,19 @@ public function testReflectionClassCacheIsResetWhenCacheSizeLimitIsReached(): vo
343357
$cacheAfterInsert = $cacheProperty->getValue();
344358

345359
self::assertCount(
346-
1,
360+
$cacheLimit,
361+
$cacheAfterInsert,
362+
'Should keep cache size at the configured limit after inserting a new class.',
363+
);
364+
self::assertArrayNotHasKey(
365+
'cached-0',
347366
$cacheAfterInsert,
348-
'Should reset cache and keep only the latest reflected class after limit is reached.',
367+
'Should evict the oldest cached entry when cache size limit is reached.',
349368
);
350369
self::assertArrayHasKey(
351370
ReflectionFixture::class,
352371
$cacheAfterInsert,
353-
'Should keep the class being reflected after cache reset.',
372+
'Should cache the newly reflected class after evicting the oldest entry.',
354373
);
355374
}
356375

@@ -406,6 +425,24 @@ public function testShortNameReturnsEmptyStringForAnonymousClass(): void
406425
);
407426
}
408427

428+
public function testShortNameReturnsShortNameForEnumTarget(): void
429+
{
430+
self::assertSame(
431+
'Status',
432+
Reflector::shortName(Status::class),
433+
'Should return the short name for enum targets.',
434+
);
435+
}
436+
437+
public function testShortNameReturnsShortNameForInterfaceTarget(): void
438+
{
439+
self::assertSame(
440+
'LeftContract',
441+
Reflector::shortName(LeftContract::class),
442+
'Should return the short name for interface targets.',
443+
);
444+
}
445+
409446
public function testShortNameReturnsShortNameForNamedClass(): void
410447
{
411448
self::assertSame(
@@ -415,6 +452,15 @@ public function testShortNameReturnsShortNameForNamedClass(): void
415452
);
416453
}
417454

455+
public function testShortNameReturnsShortNameForTraitTarget(): void
456+
{
457+
self::assertSame(
458+
'UsesTimestamp',
459+
Reflector::shortName(UsesTimestamp::class),
460+
'Should return the short name for trait targets.',
461+
);
462+
}
463+
418464
public function testThrowsInvalidArgumentExceptionForInvalidReflectionTarget(): void
419465
{
420466
$this->expectException(InvalidArgumentException::class);

tests/Support/Attribute/Label.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66

77
use Attribute;
88

9+
/**
10+
* Stub repeatable attribute used for reflection attribute tests.
11+
*
12+
* @copyright Copyright (C) 2026 Terabytesoftw.
13+
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
14+
*/
915
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
1016
final class Label
1117
{

tests/Support/Attribute/Marker.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,11 @@
66

77
use Attribute;
88

9+
/**
10+
* Stub marker attribute used for filtered reflection attribute tests.
11+
*
12+
* @copyright Copyright (C) 2026 Terabytesoftw.
13+
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
14+
*/
915
#[Attribute(Attribute::TARGET_PROPERTY)]
1016
final class Marker {}

tests/Support/Contract/BothContract.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,10 @@
44

55
namespace PHPForge\Helper\Tests\Support\Contract;
66

7+
/**
8+
* Stub implementation satisfying both intersection contracts for tests.
9+
*
10+
* @copyright Copyright (C) 2026 Terabytesoftw.
11+
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
12+
*/
713
final class BothContract implements LeftContract, RightContract {}

tests/Support/Contract/LeftContract.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,10 @@
44

55
namespace PHPForge\Helper\Tests\Support\Contract;
66

7+
/**
8+
* Stub contract used for intersection-type reflection tests.
9+
*
10+
* @copyright Copyright (C) 2026 Terabytesoftw.
11+
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
12+
*/
713
interface LeftContract {}

tests/Support/Contract/RightContract.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,10 @@
44

55
namespace PHPForge\Helper\Tests\Support\Contract;
66

7+
/**
8+
* Stub contract used for intersection-type reflection tests.
9+
*
10+
* @copyright Copyright (C) 2026 Terabytesoftw.
11+
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
12+
*/
713
interface RightContract {}

tests/Support/Contract/Status.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PHPForge\Helper\Tests\Support\Contract;
6+
7+
/**
8+
* Stub enum used to validate enum reflection targets in tests.
9+
*
10+
* @copyright Copyright (C) 2026 Terabytesoftw.
11+
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
12+
*/
13+
enum Status
14+
{
15+
case ACTIVE;
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PHPForge\Helper\Tests\Support\Contract;
6+
7+
/**
8+
* Stub trait used to validate trait reflection targets in tests.
9+
*
10+
* @copyright Copyright (C) 2026 Terabytesoftw.
11+
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
12+
*/
13+
trait UsesTimestamp
14+
{
15+
public int $timestamp = 0;
16+
}

tests/Support/Model/ReflectionFixture.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,37 @@
1010
use PHPForge\Helper\Tests\Support\Contract\LeftContract;
1111
use PHPForge\Helper\Tests\Support\Contract\RightContract;
1212

13+
/**
14+
* Stub model exposing varied property metadata for reflection helper tests.
15+
*
16+
* @copyright Copyright (C) 2026 Terabytesoftw.
17+
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
18+
*/
1319
#[Label('model')]
1420
final class ReflectionFixture
1521
{
1622
public int $id = 1;
23+
1724
public LeftContract&RightContract $intersection;
25+
1826
#[Label('primary')]
1927
#[Label('secondary')]
2028
#[Marker]
2129
public string $name = '';
30+
2231
public string|null $nullable = null;
32+
33+
public mixed $payload = null;
34+
2335
public static string $static = 'static';
36+
2437
public int|string $union = 1;
38+
2539
/**
2640
* @var mixed
2741
*/
2842
public $untyped;
43+
2944
private string $hidden = 'hidden';
3045

3146
public function __construct()

0 commit comments

Comments
 (0)