Skip to content

Commit 58330b8

Browse files
committed
Refactor - Improve reflection handling and enhance test coverage in StopwatchDecorator
1 parent 9430e7c commit 58330b8

File tree

2 files changed

+83
-31
lines changed

2 files changed

+83
-31
lines changed

src/Decorator/StopwatchDecorator.php

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,16 @@ public function __construct(
4646
*/
4747
public function decorate(object $service): object
4848
{
49-
$class = new ReflectionClass($service);
49+
$reflection = new ReflectionClass($service);
5050

51-
if ($this->shouldSkipDecoration($class)) {
51+
if ($this->shouldSkipDecoration($reflection)) {
5252
return $service;
5353
}
5454

55-
[$prefixInterceptors, $suffixInterceptors] = $this->getPrefixAndSuffixInterceptors($class);
55+
[$prefixInterceptors, $suffixInterceptors] = $this->getPrefixAndSuffixInterceptors($reflection);
5656

5757
/** @var T */
58-
return $this->createProxyWithInterceptors($service, $prefixInterceptors, $suffixInterceptors);
58+
return $this->createProxy($service, $reflection, $prefixInterceptors, $suffixInterceptors) ?? $service;
5959
}
6060

6161
// Validation methods
@@ -139,24 +139,6 @@ private function decorateReturnValue(mixed &$returnValue): void
139139

140140
// Proxy creation methods
141141

142-
/**
143-
* @param array<string, Closure> $prefixInterceptors
144-
* @param array<string, Closure> $suffixInterceptors
145-
*/
146-
private function createProxyWithInterceptors(
147-
object $service,
148-
array $prefixInterceptors,
149-
array $suffixInterceptors,
150-
): object {
151-
$reflection = new ReflectionClass($service);
152-
153-
if ($reflection->isFinal()) {
154-
return $service;
155-
}
156-
157-
return $this->createProxy($service, $reflection, $prefixInterceptors, $suffixInterceptors) ?? $service;
158-
}
159-
160142
/**
161143
* @param array<string, Closure> $prefixInterceptors
162144
* @param array<string, Closure> $suffixInterceptors
@@ -343,15 +325,10 @@ private function getDefaultValueString(\ReflectionParameter $param): string
343325
{
344326
$result = '';
345327

346-
try {
347-
if ($param->isDefaultValueAvailable()) {
348-
/** @psalm-suppress MixedAssignment */
349-
$defaultValue = $param->getDefaultValue();
350-
$result = ' = ' . var_export($defaultValue, true);
351-
}
352-
} catch (Throwable) {
353-
// Default value cannot be determined for internal classes or certain parameter types
354-
$result = '';
328+
if ($param->isDefaultValueAvailable()) {
329+
/** @psalm-suppress MixedAssignment */
330+
$defaultValue = $param->getDefaultValue();
331+
$result = ' = ' . var_export($defaultValue, true);
355332
}
356333

357334
return $result;

tests/Integration/Decorator/StopwatchDecoratorTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,45 @@ final public function someMethod(): string
8484
self::assertSame('test', $result->someMethod());
8585
}
8686

87+
#[TestDox('Test that `decorate` method returns service as-is when class itself is final')]
88+
public function testThatDecoratorReturnsTheSameInstanceForFinalClass(): void
89+
{
90+
$service = new FinalTestService();
91+
92+
$stopWatch = $this->getMockBuilder(Stopwatch::class)->disableOriginalConstructor()->getMock();
93+
94+
$decorator = new StopwatchDecorator($stopWatch, new NullLogger());
95+
96+
$result = $decorator->decorate($service);
97+
98+
// Since the class is final, it should return the same instance
99+
self::assertSame($service, $result);
100+
self::assertSame('final-test', $result->testMethod());
101+
}
102+
103+
#[TestDox('Test that decorator handles parameters with internal class default values')]
104+
public function testThatDecoratorHandlesInternalClassDefaultValues(): void
105+
{
106+
$service = new ServiceWithInternalDefaults();
107+
108+
$stopWatch = $this->getMockBuilder(Stopwatch::class)->disableOriginalConstructor()->getMock();
109+
110+
$stopWatch
111+
->expects($this->once())
112+
->method('start');
113+
114+
$stopWatch
115+
->expects($this->once())
116+
->method('stop');
117+
118+
$decorator = new StopwatchDecorator($stopWatch, new NullLogger());
119+
120+
$result = $decorator->decorate($service);
121+
122+
// The decorated service should work with methods that have internal class default values
123+
self::assertSame('test', $result->methodWithInternalDefault());
124+
}
125+
87126
/**
88127
* @throws Throwable
89128
*/
@@ -164,3 +203,39 @@ public static function dataProviderTestThatDecorateMethodReturnsExpected(): Gene
164203
yield [stdClass::class, new stdClass()];
165204
}
166205
}
206+
207+
/**
208+
* Helper final class for testing
209+
*/
210+
final class FinalTestService
211+
{
212+
public function testMethod(): string
213+
{
214+
return 'final-test';
215+
}
216+
}
217+
218+
/**
219+
* Helper class for testing internal class default values
220+
* Extends an internal class to potentially trigger reflection edge cases
221+
*/
222+
class ServiceWithInternalDefaults extends \SplFileInfo
223+
{
224+
public function __construct()
225+
{
226+
parent::__construct(__FILE__);
227+
}
228+
229+
/**
230+
* Method with parameter that has complex default values
231+
* This is for testing the catch block in getDefaultValueString when the decorator
232+
* tries to get the default value via reflection for certain edge cases
233+
*
234+
* @param array<string, mixed> $options
235+
*/
236+
public function methodWithInternalDefault(array $options = []): string
237+
{
238+
return 'test';
239+
}
240+
}
241+

0 commit comments

Comments
 (0)