@@ -2272,8 +2272,8 @@ class ClassInfo {
2272
2272
public $ isDeprecated ;
2273
2273
/** @var bool */
2274
2274
public $ isStrictProperties ;
2275
- /** @var bool */
2276
- public $ allowsDynamicProperties ;
2275
+ /** @var array{0: string, 1: \PhpParser\Node\Arg[]}[] */
2276
+ public $ attributes ;
2277
2277
/** @var bool */
2278
2278
public $ isNotSerializable ;
2279
2279
/** @var Name[] */
@@ -2292,6 +2292,7 @@ class ClassInfo {
2292
2292
public $ cond ;
2293
2293
2294
2294
/**
2295
+ * @param array{0: string, 1: \PhpParser\Node\Arg[]}[] $attributes
2295
2296
* @param Name[] $extends
2296
2297
* @param Name[] $implements
2297
2298
* @param ConstInfo[] $constInfos
@@ -2307,7 +2308,7 @@ public function __construct(
2307
2308
?SimpleType $ enumBackingType ,
2308
2309
bool $ isDeprecated ,
2309
2310
bool $ isStrictProperties ,
2310
- bool $ allowsDynamicProperties ,
2311
+ array $ attributes ,
2311
2312
bool $ isNotSerializable ,
2312
2313
array $ extends ,
2313
2314
array $ implements ,
@@ -2324,7 +2325,7 @@ public function __construct(
2324
2325
$ this ->enumBackingType = $ enumBackingType ;
2325
2326
$ this ->isDeprecated = $ isDeprecated ;
2326
2327
$ this ->isStrictProperties = $ isStrictProperties ;
2327
- $ this ->allowsDynamicProperties = $ allowsDynamicProperties ;
2328
+ $ this ->attributes = $ attributes ;
2328
2329
$ this ->isNotSerializable = $ isNotSerializable ;
2329
2330
$ this ->extends = $ extends ;
2330
2331
$ this ->implements = $ implements ;
@@ -2413,9 +2414,7 @@ function (Name $item) {
2413
2414
$ code .= $ property ->getDeclaration ($ allConstInfos );
2414
2415
}
2415
2416
2416
- if ($ this ->allowsDynamicProperties ) {
2417
- $ code .= "\tzend_add_class_attribute(class_entry, zend_ce_allow_dynamic_properties->name, 0); \n" ;
2418
- }
2417
+ $ code .= generateAttributesCode ($ this ->attributes , "zend_add_class_attribute(class_entry " , "class_ $ escapedName " , $ allConstInfos );
2419
2418
2420
2419
if ($ attributeInitializationCode = generateAttributeInitialization ($ this ->funcInfos , $ this ->cond )) {
2421
2420
$ code .= "\n" . $ attributeInitializationCode ;
@@ -2460,8 +2459,10 @@ private function getFlagsAsString(): string
2460
2459
$ flags [] = "ZEND_ACC_NO_DYNAMIC_PROPERTIES " ;
2461
2460
}
2462
2461
2463
- if ($ this ->allowsDynamicProperties ) {
2464
- $ flags [] = "ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES " ;
2462
+ foreach ($ this ->attributes as list ($ name )) {
2463
+ if ($ name === "AllowDynamicProperties " ) {
2464
+ $ flags [] = "ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES " ;
2465
+ }
2465
2466
}
2466
2467
2467
2468
if ($ this ->isNotSerializable ) {
@@ -2979,6 +2980,30 @@ public function getVariableName(): string {
2979
2980
}
2980
2981
}
2981
2982
2983
+ /**
2984
+ * @param array{0: string, 1: \PhpParser\Node\Arg[]}[] $attributes
2985
+ * @param iterable<ConstInfo> $allConstInfos
2986
+ */
2987
+ function generateAttributesCode (array $ attributes , string $ invocation , string $ nameSuffix , iterable $ allConstInfos ): string {
2988
+ $ code = "" ;
2989
+ foreach ($ attributes as list ($ name , $ args )) {
2990
+ $ escapedAttributeName = strtr ($ name , '\\' , '_ ' );
2991
+ $ code .= "\tzend_string *attribute_name_ {$ escapedAttributeName }_ $ nameSuffix = zend_string_init( \"" . addcslashes ($ name , "\\" ) . "\", " . strlen ($ name ) . ", 1); \n" ;
2992
+ $ code .= "\t" . ($ args ? "zend_attribute *attribute_ {$ escapedAttributeName }_ $ nameSuffix = " : "" ) . "$ invocation, attribute_name_ {$ escapedAttributeName }_ $ nameSuffix, " . count ($ args ) . "); \n" ;
2993
+ $ code .= "\tzend_string_release(attribute_name_ {$ escapedAttributeName }_ $ nameSuffix); \n" ;
2994
+ foreach ($ args as $ i => $ arg ) {
2995
+ $ value = EvaluatedValue::createFromExpression ($ arg ->value , null , null , $ allConstInfos );
2996
+ $ zvalName = "attribute_ {$ escapedAttributeName }_class_ {$ escapedName }_arg $ i " ;
2997
+ $ code .= $ value ->initializeZval ($ zvalName , $ allConstInfos );
2998
+ $ code .= "\tZVAL_COPY_VALUE(&attribute_ {$ escapedAttributeName }_ $ nameSuffix.args[ $ i].value, & $ zvalName); \n" ;
2999
+ if ($ arg ->name ) {
3000
+ $ code .= "\tattribute_ {$ escapedAttributeName }_ $ nameSuffix.args[ $ i].name = zend_string_init( \"{$ arg ->name ->name }\", " . strlen ($ arg ->name ->name ) . ", 1); \n" ;
3001
+ }
3002
+ }
3003
+ }
3004
+ return $ code ;
3005
+ }
3006
+
2982
3007
/** @return DocCommentTag[] */
2983
3008
function parseDocComment (DocComment $ comment ): array {
2984
3009
$ commentText = substr ($ comment ->getText (), 2 , -2 );
@@ -3286,6 +3311,7 @@ function parseClass(
3286
3311
$ isStrictProperties = false ;
3287
3312
$ isNotSerializable = false ;
3288
3313
$ allowsDynamicProperties = false ;
3314
+ $ attributes = [];
3289
3315
3290
3316
if ($ comment ) {
3291
3317
$ tags = parseDocComment ($ comment );
@@ -3304,12 +3330,11 @@ function parseClass(
3304
3330
3305
3331
foreach ($ class ->attrGroups as $ attrGroup ) {
3306
3332
foreach ($ attrGroup ->attrs as $ attr ) {
3307
- switch ($ attr ->name ->toCodeString ()) {
3308
- case '\\AllowDynamicProperties ' :
3333
+ $ attributes [] = [$ attr ->name ->toString (), $ attr ->args ];
3334
+ switch ($ attr ->name ->toString ()) {
3335
+ case 'AllowDynamicProperties ' :
3309
3336
$ allowsDynamicProperties = true ;
3310
3337
break ;
3311
- default :
3312
- throw new Exception ("Unhandled attribute {$ attr ->name ->toCodeString ()}. " );
3313
3338
}
3314
3339
}
3315
3340
}
@@ -3348,7 +3373,7 @@ function parseClass(
3348
3373
? SimpleType::fromNode ($ class ->scalarType ) : null ,
3349
3374
$ isDeprecated ,
3350
3375
$ isStrictProperties ,
3351
- $ allowsDynamicProperties ,
3376
+ $ attributes ,
3352
3377
$ isNotSerializable ,
3353
3378
$ extends ,
3354
3379
$ implements ,
0 commit comments