PHP 8.0–8.5 native reflection coverage: close API gaps found by audit - #213
Merged
lisachenko merged 13 commits intoAug 1, 2026
Merged
Conversation
… version ReflectionEngine now defaults to createForNewestSupportedVersion() so that PHP 8.5 sources (pipe operator, etc.) can be statically reflected on older runtimes. An explicit PhpVersion can still be passed to init() to pin an older grammar. Covered by a PHP 8.5 syntax stub that is parsed but never loaded. Fixes #210
…ag, raw-value/lazy delegation) getHooks() is now composed statically from the AST hook nodes (keyed by PropertyHookType value, native ordering) and isDynamic() answers false for declared/promoted properties. Object-bound members (isLazy, getRawValue, setRawValue, setRawValueWithoutLazyInitialization, skipLazyInitialization) initialize the internal reflection and delegate to the native parent. Fixes #207
Enum cases now answer getModifiers()/hasType()/getType()/isDeprecated() statically from the EnumCase AST node, matching native semantics (IS_PUBLIC only, untyped, #[\Deprecated]-aware), and isFinal() is aligned with native reflection which reports false for enum cases. The lazy-object API (newLazyGhost, newLazyProxy, resetAsLazyGhost/Proxy, initializeLazyObject, isUninitializedLazyObject, markLazyObjectAsInitialized, getLazyInitializer) delegates to the native parent after initializing the internal reflection, for both classes and enums. Fixes #205 Fixes #208
PHP 8.5 allows the final modifier on constructor-promoted properties. isFinal() now reads the FINAL flag from the promoted Param node as well, instead of only from Property nodes, while keeping the implicit finality of private(set) properties. getModifiers() picks the bit up automatically. Fixes #212
…] support Tentative return type accessors, closure introspection (getClosureCalledClass, getClosureUsedVariables), isAnonymous()/isStatic() on functions and createFromMethodName() on methods now answer statically from the AST instead of fataling on parser-based instances. isDeprecated() honors the PHP 8.4 #[\Deprecated] attribute for functions, methods and class constants via a pure-AST attribute check. Fixes #204 Fixes #206
PropertyHookType::$value is not recognized as string by PHPStan's stubs, widening the inferred key type to int|string; cast the key explicitly to match the declared array<string, ReflectionMethod> return type.
…ing in resolver NodeExpressionResolver now handles PHP 8.5 closures and arrow functions in constant expressions by name-resolving, pretty-printing and evaluating the node as a static unbound closure, and first-class callables of undefined functions fail with a descriptive message instead of a missing-handler error. Object instantiation for new-in-initializers no longer triggers the autoloader: the class source is located explicitly through the registered locator (same for static-call targets), keeping reflection side-effect free. Fixes #203 Fixes #211
Drop a redundant is_callable() guard already covered by function_exists(), narrow the closure node type before pretty-printing, and mark isClassDefinitionLoaded() as impure since its result changes after the located file is included.
…ibutes() The flags argument is now honored: IS_INSTANCEOF matches attributes whose class extends or implements the requested name, resolving ancestry without autoloading (native reflection for already-loaded classes, parser-based reflection through the locator otherwise). Invalid flag values raise the same ValueError as native reflection. Fixes #202
Mirrors the native PHP 8.4 ReflectionConstant API (the internal class is final, so it cannot be extended) on top of the AST: getName, getShortName, getNamespaceName, getValue, isDeprecated and __toString are resolved statically, and the PHP 8.5 getAttributes() extension works for attributes on constants including #[\Deprecated]. ReflectionFileNamespace exposes the new reflections via getReflectionConstant()/getReflectionConstants() without changing the existing getConstants() API. define()-style constants stay out of scope for static analysis. Fixes #209
Annotate the resolved attribute name as class-string and print non-stringable constant values via their debug type instead of an unchecked string cast.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the PHP 8.0–8.5 native reflection coverage gaps found by a full audit of the library against native reflection (mechanical API diff + runtime probes against PHP 8.4.19, PHP 8.5 grammar via php-parser 5.8). One commit per audit ticket:
ReflectionEngineparses with the newest supported PHP grammar instead of the host version, so PHP 8.5 sources (pipe operator, etc.) are reflectable on a PHP 8.4 runtime; an explicitPhpVersioncan still be pinned viainit().ReflectionProperty::getHooks()composed statically from AST hook nodes;isDynamic()answersfalse;isLazy/getRawValue/setRawValue/setRawValueWithoutLazyInitialization/skipLazyInitializationinitialize the internal reflection and delegate.getModifiers()/hasType()/getType()/isDeprecated()statically (andisFinal()now matches native, which reportsfalsefor enum cases); the PHP 8.4 lazy-object API (newLazyGhost,newLazyProxy,resetAsLazyGhost/Proxy,initializeLazyObject,isUninitializedLazyObject,markLazyObjectAsInitialized,getLazyInitializer) delegates to the native parent for classes and enums.finalon promoted constructor properties (PHP 8.5) reflected inisFinal()/getModifiers().getClosureUsedVariables(),getClosureCalledClass(),ReflectionFunction::isAnonymous()/isStatic(),ReflectionMethod::createFromMethodName()answer statically instead of fataling;isDeprecated()honors#[\Deprecated](PHP 8.4) for functions, methods and class constants.getAttributes()honorsReflectionAttribute::IS_INSTANCEOF, resolving attribute-class ancestry without autoloading (native reflection for loaded classes, parser-based otherwise), and validates flags with the nativeValueError.Go\ParserReflection\ReflectionConstantmirrors the native PHP 8.4 API forconst-declared global/namespaced constants (the internal class isfinal, so it cannot be extended), including the PHP 8.5getAttributes()extension and#[\Deprecated]; exposed viaReflectionFileNamespace::getReflectionConstant(s)().All static behavior operates purely on the AST — no
class_exists()-style calls that can trigger autoloading; methods that inherently need runtime state initialize the internal reflection and delegate to the native parent.Known residual limitations (documented deliberately)
getAttributes($name)filtering (flags = 0) remains case-sensitive as before, while native is case-insensitive; the newIS_INSTANCEOFpath is case-insensitive.getConstants()batch (ReflectionFileNamespace::findConstants()/collectSelfConstants()have no per-constant isolation); with the new closure handlers the previously reported symptom no longer occurs.ReflectionConstantdoes not passinstanceof \ReflectionConstant(native class is final);define()-style constants are out of scope for static analysis.Test plan
class_exists(..., false)spies, autoload-spy for the resolver) plus native-parity assertions where stubs are loadable on PHP 8.4; PHP 8.5 syntax stubs are parse-only and never included.🤖 Generated with Claude Code
https://claude.ai/code/session_019wBszKXQG1s3UFRqpaRSF1