1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\Config\Tests\Resource; 13 14use PHPUnit\Framework\TestCase; 15use Symfony\Component\Config\Resource\ReflectionClassResource; 16use Symfony\Component\EventDispatcher\EventSubscriberInterface; 17use Symfony\Component\Messenger\Handler\MessageSubscriberInterface; 18use Symfony\Contracts\Service\ServiceSubscriberInterface; 19 20class ReflectionClassResourceTest extends TestCase 21{ 22 public function testToString() 23 { 24 $res = new ReflectionClassResource(new \ReflectionClass('ErrorException')); 25 26 $this->assertSame('reflection.ErrorException', (string) $res); 27 } 28 29 public function testSerializeUnserialize() 30 { 31 $res = new ReflectionClassResource(new \ReflectionClass(DummyInterface::class)); 32 $ser = unserialize(serialize($res)); 33 34 $this->assertTrue($res->isFresh(0)); 35 $this->assertTrue($ser->isFresh(0)); 36 37 $this->assertSame((string) $res, (string) $ser); 38 } 39 40 public function testIsFresh() 41 { 42 $res = new ReflectionClassResource(new \ReflectionClass(__CLASS__)); 43 $mtime = filemtime(__FILE__); 44 45 $this->assertTrue($res->isFresh($mtime), '->isFresh() returns true if the resource has not changed in same second'); 46 $this->assertTrue($res->isFresh($mtime + 10), '->isFresh() returns true if the resource has not changed'); 47 $this->assertTrue($res->isFresh($mtime - 86400), '->isFresh() returns true if the resource has not changed'); 48 } 49 50 public function testIsFreshForDeletedResources() 51 { 52 $now = time(); 53 $tmp = sys_get_temp_dir().'/tmp.php'; 54 file_put_contents($tmp, '<?php class ReflectionClassResourceTestClass {}'); 55 require $tmp; 56 57 $res = new ReflectionClassResource(new \ReflectionClass('ReflectionClassResourceTestClass')); 58 $this->assertTrue($res->isFresh($now)); 59 60 unlink($tmp); 61 $this->assertFalse($res->isFresh($now), '->isFresh() returns false if the resource does not exist'); 62 } 63 64 /** 65 * @dataProvider provideHashedSignature 66 */ 67 public function testHashedSignature($changeExpected, $changedLine, $changedCode, $setContext = null) 68 { 69 if ($setContext) { 70 $setContext(); 71 } 72 73 $code = <<<'EOPHP' 74/* 0*/ 75/* 1*/ class %s extends ErrorException 76/* 2*/ { 77/* 3*/ const FOO = 123; 78/* 4*/ 79/* 5*/ public $pub = []; 80/* 6*/ 81/* 7*/ protected $prot; 82/* 8*/ 83/* 9*/ private $priv; 84/*10*/ 85/*11*/ public function pub($arg = null) {} 86/*12*/ 87/*13*/ protected function prot($a = []) {} 88/*14*/ 89/*15*/ private function priv() {} 90/*16*/ 91/*17*/ public function ccc($bar = A_CONSTANT_THAT_FOR_SURE_WILL_NEVER_BE_DEFINED_CCCCCC) {} 92/*18*/ } 93EOPHP; 94 95 static $expectedSignature, $generateSignature; 96 97 if (null === $expectedSignature) { 98 eval(sprintf($code, $class = 'Foo'.str_replace('.', '_', uniqid('', true)))); 99 $r = new \ReflectionClass(ReflectionClassResource::class); 100 $generateSignature = $r->getMethod('generateSignature'); 101 $generateSignature->setAccessible(true); 102 $generateSignature = $generateSignature->getClosure($r->newInstanceWithoutConstructor()); 103 $expectedSignature = implode("\n", iterator_to_array($generateSignature(new \ReflectionClass($class)))); 104 } 105 106 $code = explode("\n", $code); 107 if (null !== $changedCode) { 108 $code[$changedLine] = $changedCode; 109 } 110 eval(sprintf(implode("\n", $code), $class = 'Foo'.str_replace('.', '_', uniqid('', true)))); 111 $signature = implode("\n", iterator_to_array($generateSignature(new \ReflectionClass($class)))); 112 113 if ($changeExpected) { 114 $this->assertNotSame($expectedSignature, $signature); 115 } else { 116 $this->assertSame($expectedSignature, $signature); 117 } 118 } 119 120 public function provideHashedSignature() 121 { 122 yield [0, 0, "// line change\n\n"]; 123 yield [1, 0, '/** class docblock */']; 124 yield [1, 1, 'abstract class %s']; 125 yield [1, 1, 'final class %s']; 126 yield [1, 1, 'class %s extends Exception']; 127 yield [1, 1, 'class %s implements '.DummyInterface::class]; 128 yield [1, 3, 'const FOO = 456;']; 129 yield [1, 3, 'const BAR = 123;']; 130 yield [1, 4, '/** pub docblock */']; 131 yield [1, 5, 'protected $pub = [];']; 132 yield [1, 5, 'public $pub = [123];']; 133 yield [1, 6, '/** prot docblock */']; 134 yield [1, 7, 'private $prot;']; 135 yield [0, 8, '/** priv docblock */']; 136 yield [0, 9, 'private $priv = 123;']; 137 yield [1, 10, '/** pub docblock */']; 138 yield [1, 11, 'public function pub(...$arg) {}']; 139 yield [1, 11, 'public function pub($arg = null): Foo {}']; 140 yield [0, 11, "public function pub(\$arg = null) {\nreturn 123;\n}"]; 141 yield [1, 12, '/** prot docblock */']; 142 yield [1, 13, 'protected function prot($a = [123]) {}']; 143 yield [0, 14, '/** priv docblock */']; 144 yield [0, 15, '']; 145 146 if (\PHP_VERSION_ID >= 70400) { 147 // PHP7.4 typed properties without default value are 148 // undefined, make sure this doesn't throw an error 149 yield [1, 5, 'public array $pub;']; 150 yield [0, 7, 'protected int $prot;']; 151 yield [0, 9, 'private string $priv;']; 152 } 153 154 yield [1, 17, 'public function ccc($bar = 187) {}']; 155 yield [1, 17, 'public function ccc($bar = ANOTHER_ONE_THAT_WILL_NEVER_BE_DEFINED_CCCCCCCCC) {}']; 156 yield [1, 17, null, static function () { \define('A_CONSTANT_THAT_FOR_SURE_WILL_NEVER_BE_DEFINED_CCCCCC', 'foo'); }]; 157 } 158 159 public function testEventSubscriber() 160 { 161 $res = new ReflectionClassResource(new \ReflectionClass(TestEventSubscriber::class)); 162 $this->assertTrue($res->isFresh(0)); 163 164 TestEventSubscriber::$subscribedEvents = [123]; 165 $this->assertFalse($res->isFresh(0)); 166 167 $res = new ReflectionClassResource(new \ReflectionClass(TestEventSubscriber::class)); 168 $this->assertTrue($res->isFresh(0)); 169 } 170 171 public function testMessageSubscriber() 172 { 173 $res = new ReflectionClassResource(new \ReflectionClass(TestMessageSubscriber::class)); 174 $this->assertTrue($res->isFresh(0)); 175 176 TestMessageSubscriberConfigHolder::$handledMessages = ['SomeMessageClass' => []]; 177 $this->assertFalse($res->isFresh(0)); 178 179 $res = new ReflectionClassResource(new \ReflectionClass(TestMessageSubscriber::class)); 180 $this->assertTrue($res->isFresh(0)); 181 182 TestMessageSubscriberConfigHolder::$handledMessages = ['OtherMessageClass' => []]; 183 $this->assertFalse($res->isFresh(0)); 184 185 $res = new ReflectionClassResource(new \ReflectionClass(TestMessageSubscriber::class)); 186 $this->assertTrue($res->isFresh(0)); 187 } 188 189 public function testServiceSubscriber() 190 { 191 $res = new ReflectionClassResource(new \ReflectionClass(TestServiceSubscriber::class)); 192 $this->assertTrue($res->isFresh(0)); 193 194 TestServiceSubscriber::$subscribedServices = [123]; 195 $this->assertFalse($res->isFresh(0)); 196 197 $res = new ReflectionClassResource(new \ReflectionClass(TestServiceSubscriber::class)); 198 $this->assertTrue($res->isFresh(0)); 199 } 200 201 public function testIgnoresObjectsInSignature() 202 { 203 $res = new ReflectionClassResource(new \ReflectionClass(TestServiceWithStaticProperty::class)); 204 $this->assertTrue($res->isFresh(0)); 205 206 TestServiceWithStaticProperty::$initializedObject = new TestServiceWithStaticProperty(); 207 $this->assertTrue($res->isFresh(0)); 208 } 209} 210 211interface DummyInterface 212{ 213} 214 215class TestEventSubscriber implements EventSubscriberInterface 216{ 217 public static $subscribedEvents = []; 218 219 public static function getSubscribedEvents(): array 220 { 221 return self::$subscribedEvents; 222 } 223} 224 225class TestMessageSubscriber implements MessageSubscriberInterface 226{ 227 public static function getHandledMessages(): iterable 228 { 229 foreach (TestMessageSubscriberConfigHolder::$handledMessages as $key => $subscribedMessage) { 230 yield $key => $subscribedMessage; 231 } 232 } 233} 234class TestMessageSubscriberConfigHolder 235{ 236 public static $handledMessages = []; 237} 238 239class TestServiceSubscriber implements ServiceSubscriberInterface 240{ 241 public static $subscribedServices = []; 242 243 public static function getSubscribedServices(): array 244 { 245 return self::$subscribedServices; 246 } 247} 248 249class TestServiceWithStaticProperty 250{ 251 public static $initializedObject; 252} 253