Skip to content

Commit fb0d71a

Browse files
authored
Allow annotation classes with a variadic parameter (#479)
1 parent ad78521 commit fb0d71a

File tree

2 files changed

+173
-0
lines changed

2 files changed

+173
-0
lines changed

lib/Doctrine/Common/Annotations/DocParser.php

+21
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,10 @@ class_exists(NamedArgumentConstructor::class);
613613
$metadata['default_property'] = reset($metadata['properties']);
614614
} elseif ($metadata['has_named_argument_constructor']) {
615615
foreach ($constructor->getParameters() as $parameter) {
616+
if ($parameter->isVariadic()) {
617+
break;
618+
}
619+
616620
$metadata['constructor_args'][$parameter->getName()] = [
617621
'position' => $parameter->getPosition(),
618622
'default' => $parameter->isOptional() ? $parameter->getDefaultValue() : null,
@@ -942,6 +946,23 @@ private function Annotation()
942946

943947
if (self::$annotationMetadata[$name]['has_named_argument_constructor']) {
944948
if (PHP_VERSION_ID >= 80000) {
949+
foreach ($values as $property => $value) {
950+
if (! isset(self::$annotationMetadata[$name]['constructor_args'][$property])) {
951+
throw AnnotationException::creationError(sprintf(
952+
<<<'EXCEPTION'
953+
The annotation @%s declared on %s does not have a property named "%s"
954+
that can be set through its named arguments constructor.
955+
Available named arguments: %s
956+
EXCEPTION
957+
,
958+
$originalName,
959+
$this->context,
960+
$property,
961+
implode(', ', array_keys(self::$annotationMetadata[$name]['constructor_args']))
962+
));
963+
}
964+
}
965+
945966
return $this->instantiateAnnotiation($originalName, $this->context, $name, $values);
946967
}
947968

tests/Doctrine/Tests/Common/Annotations/DocParserTest.php

+152
Original file line numberDiff line numberDiff line change
@@ -1644,6 +1644,18 @@ public function testNamedArgumentsConstructorInterfaceWithDefaultValue(): void
16441644
self::assertSame(1234, $result[0]->getBar());
16451645
}
16461646

1647+
public function testNamedArgumentsConstructorInterfaceWithExtraArguments(): void
1648+
{
1649+
$docParser = $this->createTestParser();
1650+
1651+
$this->expectException(AnnotationException::class);
1652+
$this->expectExceptionMessageMatches(
1653+
'/does not have a property named "invalid"\s.*\sAvailable named arguments: foo, bar/'
1654+
);
1655+
1656+
$docParser->parse('/** @NamedAnnotation(foo="baz", invalid="uh oh") */');
1657+
}
1658+
16471659
public function testNamedArgumentsConstructorAnnotation(): void
16481660
{
16491661
$result = $this
@@ -1692,6 +1704,18 @@ public function testNamedArgumentsConstructorAnnotationWithDefaultProperty(): vo
16921704
self::assertSame(1234, $result[0]->getBar());
16931705
}
16941706

1707+
public function testNamedArgumentsConstructorAnnotationWithExtraArguments(): void
1708+
{
1709+
$docParser = $this->createTestParser();
1710+
1711+
$this->expectException(AnnotationException::class);
1712+
$this->expectExceptionMessageMatches(
1713+
'/does not have a property named "invalid"\s.*\sAvailable named arguments: foo, bar/'
1714+
);
1715+
1716+
$docParser->parse('/** @AnotherNamedAnnotation(foo="baz", invalid="uh oh") */');
1717+
}
1718+
16951719
public function testNamedArgumentsConstructorAnnotationWithDefaultPropertyAsArray(): void
16961720
{
16971721
$result = $this
@@ -1744,6 +1768,115 @@ public function testNamedArgumentsConstructorAnnotationWithWrongArgumentType():
17441768
}
17451769
}
17461770

1771+
public function testAnnotationWithConstructorWithVariadicParamAndExtraNamedArguments(): void
1772+
{
1773+
$parser = $this->createTestParser();
1774+
$docblock = <<<'DOCBLOCK'
1775+
/**
1776+
* @SomeAnnotationWithConstructorWithVariadicParam(name = "Some data", foo = "Foo", bar = "Bar")
1777+
*/
1778+
DOCBLOCK;
1779+
1780+
$this->expectException(AnnotationException::class);
1781+
$this->expectExceptionMessageMatches(
1782+
'/does not have a property named "foo"\s.*\sAvailable named arguments: name/'
1783+
);
1784+
1785+
$parser->parse($docblock);
1786+
}
1787+
1788+
public function testAnnotationWithConstructorWithVariadicParamAndExtraNamedArgumentsShuffled(): void
1789+
{
1790+
$parser = $this->createTestParser();
1791+
$docblock = <<<'DOCBLOCK'
1792+
/**
1793+
* @SomeAnnotationWithConstructorWithVariadicParam(foo = "Foo", name = "Some data", bar = "Bar")
1794+
*/
1795+
DOCBLOCK;
1796+
1797+
$this->expectException(AnnotationException::class);
1798+
$this->expectExceptionMessageMatches(
1799+
'/does not have a property named "foo"\s.*\sAvailable named arguments: name/'
1800+
);
1801+
1802+
$parser->parse($docblock);
1803+
}
1804+
1805+
public function testAnnotationWithConstructorWithVariadicParamAndCombinedNamedAndPositionalArguments(): void
1806+
{
1807+
$parser = $this->createTestParser();
1808+
$docblock = <<<'DOCBLOCK'
1809+
/**
1810+
* @SomeAnnotationWithConstructorWithVariadicParam("Some data", "Foo", bar = "Bar")
1811+
*/
1812+
DOCBLOCK;
1813+
1814+
$this->expectException(AnnotationException::class);
1815+
$this->expectExceptionMessageMatches(
1816+
'/does not have a property named "bar"\s.*\sAvailable named arguments: name/'
1817+
);
1818+
1819+
$parser->parse($docblock);
1820+
}
1821+
1822+
public function testAnnotationWithConstructorWithVariadicParamPassOneNamedArgument(): void
1823+
{
1824+
$parser = $this->createTestParser();
1825+
$docblock = <<<'DOCBLOCK'
1826+
/**
1827+
* @SomeAnnotationWithConstructorWithVariadicParam(name = "Some data", data = "Foo")
1828+
*/
1829+
DOCBLOCK;
1830+
1831+
$this->expectException(AnnotationException::class);
1832+
$this->expectExceptionMessageMatches(
1833+
'/does not have a property named "data"\s.*\sAvailable named arguments: name/'
1834+
);
1835+
1836+
$parser->parse($docblock);
1837+
}
1838+
1839+
public function testAnnotationWithConstructorWithVariadicParamPassPositionalArguments(): void
1840+
{
1841+
$parser = $this->createTestParser();
1842+
$docblock = <<<'DOCBLOCK'
1843+
/**
1844+
* @SomeAnnotationWithConstructorWithVariadicParam("Some data", "Foo", "Bar")
1845+
*/
1846+
DOCBLOCK;
1847+
1848+
$result = $parser->parse($docblock);
1849+
self::assertCount(1, $result);
1850+
$annot = $result[0];
1851+
1852+
self::assertInstanceOf(SomeAnnotationWithConstructorWithVariadicParam::class, $annot);
1853+
1854+
self::assertSame('Some data', $annot->name);
1855+
// Positional extra arguments will be ignored
1856+
self::assertSame([], $annot->data);
1857+
}
1858+
1859+
public function testAnnotationWithConstructorWithVariadicParamNoArgs(): void
1860+
{
1861+
$parser = $this->createTestParser();
1862+
1863+
// Without variadic arguments
1864+
$docblock = <<<'DOCBLOCK'
1865+
/**
1866+
* @SomeAnnotationWithConstructorWithVariadicParam("Some data")
1867+
*/
1868+
DOCBLOCK;
1869+
1870+
$result = $parser->parse($docblock);
1871+
self::assertCount(1, $result);
1872+
$annot = $result[0];
1873+
1874+
self::assertInstanceOf(SomeAnnotationWithConstructorWithVariadicParam::class, $annot);
1875+
1876+
self::assertSame('Some data', $annot->name);
1877+
self::assertSame([], $annot->data);
1878+
}
1879+
17471880
/**
17481881
* Override for BC with PHPUnit <8
17491882
*/
@@ -1850,6 +1983,25 @@ public function getBar(): int
18501983
}
18511984
}
18521985

1986+
/**
1987+
* @Annotation
1988+
* @NamedArgumentConstructor
1989+
*/
1990+
class SomeAnnotationWithConstructorWithVariadicParam
1991+
{
1992+
public function __construct(string $name, string ...$data)
1993+
{
1994+
$this->name = $name;
1995+
$this->data = $data;
1996+
}
1997+
1998+
/** @var string[] */
1999+
public $data;
2000+
2001+
/** @var string */
2002+
public $name;
2003+
}
2004+
18532005
/** @Annotation */
18542006
class SettingsAnnotation
18552007
{

0 commit comments

Comments
 (0)