Skip to content

Commit f98b0ed

Browse files
committed
[PropertyInfo] Fix ReflectionExtractor handling of underscore-only property names
1 parent 88b0d7f commit f98b0ed

File tree

3 files changed

+90
-19
lines changed

3 files changed

+90
-19
lines changed

src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,10 @@ private function isMethodAccessible(\ReflectionClass $class, string $methodName,
757757
*/
758758
private function camelize(string $string): string
759759
{
760+
if ('' === ltrim($string, '_')) {
761+
return $string;
762+
}
763+
760764
return str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
761765
}
762766

src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php7ParentDummy;
2929
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php81Dummy;
3030
use Symfony\Component\PropertyInfo\Tests\Fixtures\SnakeCaseDummy;
31+
use Symfony\Component\PropertyInfo\Tests\Fixtures\UnderscoreDummy;
3132
use Symfony\Component\PropertyInfo\Tests\Fixtures\VirtualProperties;
3233
use Symfony\Component\PropertyInfo\Type;
3334

@@ -361,31 +362,31 @@ public static function defaultValueProvider()
361362
/**
362363
* @dataProvider getReadableProperties
363364
*/
364-
public function testIsReadable($property, $expected)
365+
public function testIsReadable(string $class, string $property, bool $expected)
365366
{
366-
$this->assertSame(
367-
$expected,
368-
$this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property, [])
369-
);
367+
$this->assertSame($expected, $this->extractor->isReadable($class, $property, []));
370368
}
371369

372370
public static function getReadableProperties()
373371
{
374372
return [
375-
['bar', false],
376-
['baz', false],
377-
['parent', true],
378-
['a', true],
379-
['b', false],
380-
['c', true],
381-
['d', true],
382-
['e', false],
383-
['f', false],
384-
['Id', true],
385-
['id', true],
386-
['Guid', true],
387-
['guid', false],
388-
['element', false],
373+
[Dummy::class, 'bar', false],
374+
[Dummy::class, 'baz', false],
375+
[Dummy::class, 'parent', true],
376+
[Dummy::class, 'a', true],
377+
[Dummy::class, 'b', false],
378+
[Dummy::class, 'c', true],
379+
[Dummy::class, 'd', true],
380+
[Dummy::class, 'e', false],
381+
[Dummy::class, 'f', false],
382+
[Dummy::class, 'Id', true],
383+
[Dummy::class, 'id', true],
384+
[Dummy::class, 'Guid', true],
385+
[Dummy::class, 'guid', false],
386+
[Dummy::class, 'element', false],
387+
[UnderscoreDummy::class, '_', true],
388+
[UnderscoreDummy::class, '__', true],
389+
[UnderscoreDummy::class, '___', false],
389390
];
390391
}
391392

@@ -560,6 +561,10 @@ public static function readAccessorProvider(): array
560561
[Dummy::class, 'foo', true, PropertyReadInfo::TYPE_PROPERTY, 'foo', PropertyReadInfo::VISIBILITY_PUBLIC, false],
561562
[Php71Dummy::class, 'foo', true, PropertyReadInfo::TYPE_METHOD, 'getFoo', PropertyReadInfo::VISIBILITY_PUBLIC, false],
562563
[Php71Dummy::class, 'buz', true, PropertyReadInfo::TYPE_METHOD, 'getBuz', PropertyReadInfo::VISIBILITY_PUBLIC, false],
564+
[UnderscoreDummy::class, '_', true, PropertyReadInfo::TYPE_METHOD, 'get_', PropertyReadInfo::VISIBILITY_PUBLIC, false],
565+
[UnderscoreDummy::class, '__', true, PropertyReadInfo::TYPE_METHOD, 'get__', PropertyReadInfo::VISIBILITY_PUBLIC, false],
566+
[UnderscoreDummy::class, 'foo_bar', false, null, null, null, null],
567+
[UnderscoreDummy::class, '_foo_', false, null, null, null, null],
563568
];
564569
}
565570

@@ -792,4 +797,35 @@ public static function provideVirtualPropertiesMutator(): iterable
792797
yield ['virtualSetHookOnly', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PUBLIC];
793798
yield ['virtualHook', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PUBLIC];
794799
}
800+
801+
/**
802+
* @dataProvider camelizeProvider
803+
*/
804+
public function testCamelize(string $input, string $expected)
805+
{
806+
$reflection = new \ReflectionClass($this->extractor);
807+
$method = $reflection->getMethod('camelize');
808+
809+
if (\PHP_VERSION_ID < 80500) {
810+
$method->setAccessible(true);
811+
}
812+
813+
$this->assertSame($expected, $method->invoke($this->extractor, $input));
814+
}
815+
816+
public static function camelizeProvider(): iterable
817+
{
818+
yield 'single underscore' => ['_', '_'];
819+
yield 'double underscore' => ['__', '__'];
820+
yield 'triple underscore' => ['___', '___'];
821+
822+
yield 'snake case' => ['foo_bar', 'FooBar'];
823+
yield 'double snake case' => ['foo__bar', 'FooBar'];
824+
yield 'leading underscore' => ['_foo', 'Foo'];
825+
yield 'trailing underscore' => ['foo_', 'Foo'];
826+
yield 'leading and trailing' => ['_foo_bar_', 'FooBar'];
827+
yield 'empty string' => ['', ''];
828+
yield 'no underscore' => ['fooBar', 'FooBar'];
829+
yield 'pascal case' => ['FooBar', 'FooBar'];
830+
}
795831
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
12+
namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
13+
14+
/**
15+
* Fixture class for testing underscore-only properties (issue #61425).
16+
*/
17+
class UnderscoreDummy
18+
{
19+
private float $_;
20+
private float $__;
21+
22+
public function get_(): float
23+
{
24+
return $this->_;
25+
}
26+
27+
public function get__(): float
28+
{
29+
return $this->__;
30+
}
31+
}

0 commit comments

Comments
 (0)