@@ -199,9 +199,21 @@ public function getModifiers(): int
199199 if ($ this ->isReadOnly ()) {
200200 $ modifiers += self ::IS_READONLY ;
201201 }
202+ if (PHP_VERSION_ID >= 80400 && $ this ->isAbstract ()) {
203+ $ modifiers += self ::IS_ABSTRACT ;
204+ }
205+ if (PHP_VERSION_ID >= 80400 && $ this ->isFinal ()) {
206+ $ modifiers += self ::IS_FINAL ;
207+ }
208+ if (PHP_VERSION_ID >= 80400 && $ this ->isProtectedSet ()) {
209+ $ modifiers += self ::IS_PROTECTED_SET ;
210+ }
211+ if (PHP_VERSION_ID >= 80400 && $ this ->isPrivateSet ()) {
212+ $ modifiers += self ::IS_PRIVATE_SET ;
213+ }
202214
203215 // Handle PHP 8.4+ asymmetric visibility modifiers
204- // Note: IS_PRIVATE_SET and IS_PROTECTED_SET are only added for properties with explicit
216+ // Note: IS_PRIVATE_SET and IS_PROTECTED_SET are only added for properties with explicit
205217 // asymmetric visibility syntax like "public private(set) $prop", not for regular readonly properties
206218 // TODO: Implement when nikic/php-parser supports asymmetric visibility syntax
207219
@@ -277,6 +289,18 @@ public function getDefaultValue(): mixed
277289 return $ this ->defaultValue ;
278290 }
279291
292+ /**
293+ * @inheritDoc
294+ */
295+ public function isAbstract (): bool
296+ {
297+ if ($ this ->propertyOrPromotedParam instanceof Property) {
298+ return $ this ->propertyOrPromotedParam ->isAbstract ();
299+ }
300+
301+ return false ;
302+ }
303+
280304 /**
281305 * @inheritDoc
282306 */
@@ -287,6 +311,22 @@ public function isDefault(): bool
287311 return true ;
288312 }
289313
314+ /**
315+ * {@inheritDoc}
316+ *
317+ * @see Property::isFinal()
318+ */
319+ public function isFinal (): bool
320+ {
321+ $ explicitFinal = false ;
322+ if ($ this ->propertyOrPromotedParam instanceof Property) {
323+ $ explicitFinal = $ this ->propertyOrPromotedParam ->isFinal ();
324+ }
325+
326+ // Property with private(set) modifier is implicitly final
327+ return $ explicitFinal || $ this ->isPrivateSet ();
328+ }
329+
290330 /**
291331 * {@inheritDoc}
292332 *
@@ -298,6 +338,17 @@ public function isPrivate(): bool
298338 return $ this ->propertyOrPromotedParam ->isPrivate ();
299339 }
300340
341+ /**
342+ * @inheritDoc
343+ *
344+ * @see Property::isPrivateSet()
345+ * @see Param::isPrivateSet()
346+ */
347+ public function isPrivateSet (): bool
348+ {
349+ return ($ this ->propertyOrPromotedParam ->isPrivateSet () && !$ this ->propertyOrPromotedParam ->isPrivate ());
350+ }
351+
301352 /**
302353 * {@inheritDoc}
303354 *
@@ -309,6 +360,22 @@ public function isProtected(): bool
309360 return $ this ->propertyOrPromotedParam ->isProtected ();
310361 }
311362
363+ /**
364+ * @inheritDoc
365+ *
366+ * @see Property::isProtectedSet()
367+ * @see Param::isProtectedSet()
368+ */
369+ public function isProtectedSet (): bool
370+ {
371+ /*
372+ * Behavior of readonly is to imply protected(set), not private(set).
373+ * A readonly property may still be explicitly declared private(set), in which case it will also be implicitly final
374+ */
375+ return ($ this ->propertyOrPromotedParam ->isProtectedSet () && !$ this ->propertyOrPromotedParam ->isProtected ())
376+ || ($ this ->isPublic () && $ this ->isReadonly () && !$ this ->isPrivateSet () && !$ this ->propertyOrPromotedParam ->isPublicSet ());
377+ }
378+
312379 /**
313380 * {@inheritDoc}
314381 *
@@ -352,7 +419,6 @@ public function isReadOnly(): bool
352419 return $ this ->propertyOrPromotedParam ->isReadonly () || $ this ->getDeclaringClass ()->isReadOnly ();
353420 }
354421
355-
356422 /**
357423 * {@inheritDoc}
358424 */
@@ -369,6 +435,14 @@ public function isInitialized(?object $object = null): bool
369435 return $ this ->hasDefaultValue ();
370436 }
371437
438+ /**
439+ * @inheritDoc
440+ */
441+ public function isVirtual (): bool
442+ {
443+ return $ this ->propertyOrPromotedParam ->isVirtual ();
444+ }
445+
372446 /**
373447 * {@inheritDoc}
374448 */
0 commit comments