Closed Ocramius closed 2 years ago
/cc @asgrim @ondrejmirtes @kukulich
Ok, I suggest to remove the monkey patching feature :)
@asgrim the mutator API is your project: wdyt?
Hi, I'm really enjoying. the 5.0.x. branch. Previously my fork has been based off BR 4.3. Now I successfully based my new fork on top of 5.0.x and established a downgrading process so that it works on PHP 7.1+. PHPStan's tests and PHPStan-on-PHPStan are now green with the new fork. But there's a bit more work to be done so a few more bugfixes might be submitted.
This is a list of what it does additionally/differently that might be interesting to the original version:
BetterReflection::populate()
method so that I can provide my own instances of services to BR (https://github.com/ondrejmirtes/BetterReflection/commit/ad54d35a6092080c9fc1d58e2fb545a47223bde0) and that the whole reflection is consistent.isPresentClass
, isPresentFunction
for PhpStormStubsSourceStubber (https://github.com/ondrejmirtes/BetterReflection/commit/09fd5c2c2f241710143aadb6a489cca96531f600) - so I can ask which symbols should not be present on the $phpVersion provided to the stubber's constructor. They return ?bool
. In case of false
PHPStan reports the symbol as undefined.isBuiltIn()
still answers correctly.ReflectionConstant::populateValue()
(https://github.com/ondrejmirtes/BetterReflection/commit/2c2f032045004a02c860b0ecdc88544524b75f17) - PHPStan has its own version of AutoloadSourceLocator, which can reflect on constants from an unknown file just based on defined()
returning true
. I need to provide the constant's value to BR.CompiledValue
allows resource
(https://github.com/ondrejmirtes/BetterReflection/commit/0c3ec4b9d9a500592a8f54da561f5b00defe1424) - people do all sorts of crazy sh*t - https://3v4l.org/hNlDD. Also thanks to this, STDIN
etc. can be reflected.Foo
implements method Foo
, it still works as a constructor on PHP 8, it's just deprecated (https://github.com/ondrejmirtes/BetterReflection/commit/6f597e60df0946c1b4f6fa935b352e04f9ce9642)Additional things are not part of my fork, but are commited directly to phpstan-src:
class Foo
and function bar()
defined in a single file, function bar()
becomes loaded and defined after class Foo
is autoloaded. This requires me to scan the whole file for all symbols when asked for class Foo
.OptimizedDirectorySourceLocator
takes a page from Composer's classmap generator for quick scanning of files (https://github.com/phpstan/phpstan-src/blob/8737c3b4690661445338db2a6c31d340a0eac4bd/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php#L189-L243).Let me know if anything from this list is interesting to land in BetterReflection. Thanks.
Hi, I'm really enjoying. the 5.0.x. branch. Previously my fork has been based off BR 4.3. Now I successfully based my new fork on top of 5.0.x and established a downgrading process so that it works on PHP 7.1+. PHPStan's tests and PHPStan-on-PHPStan are now green with the new fork. But there's a bit more work to be done so a few more bugfixes might be submitted.
This is a list of what it does additionally/differently that might be interesting to the original version:
BetterReflection::populate()
method so that I can provide my own instances of services to BR (ondrejmirtes@ad54d35) and that the whole reflection is consistent.
The root BetterReflection
class is not supposed to be re-used in libraries: it's an "entry-point" for "doing it quickly, based on assumptions", but it is not an extension point.
For example, roave/backward-compatibility-check
only uses it for getting an AstLocator
: https://github.com/Roave/BackwardCompatibilityCheck/blob/7d37292fc3ab6a94682812f39a18860a13318cfd/bin/roave-backward-compatibility-check.php#L59
Consider BetterReflection
(the class) a facade: as soon as you want more, get rid of it :)
- More methods
isPresentClass
,isPresentFunction
for PhpStormStubsSourceStubber (ondrejmirtes@09fd5c2) - so I can ask which symbols should not be present on the $phpVersion provided to the stubber's constructor. They return?bool
. In case offalse
PHPStan reports the symbol as undefined.
I'd say that the PhpStormStubsSourceStubber
should not return any symbols that aren't declared in the specified version. Instantiating different PhpStormStubsSourceStubber
with different versions would give you the possibility of comparing declared symbols.
- PhpStormStubsSourceStubber reports filename (ondrejmirtes@e416d4d) - so PHPStan can read the PHPDocs resolved with the correct namespace and use statements. It might be weird for internal symbols to have filenames, but it works as long as
isBuiltIn()
still answers correctly.
Wouldn't it make more sense to read the docblock directly? The API for that is already provided...
ReflectionConstant::populateValue()
(ondrejmirtes@2c2f032) - PHPStan has its own version of AutoloadSourceLocator, which can reflect on constants from an unknown file just based ondefined()
returningtrue
. I need to provide the constant's value to BR.
This is a no-go: long-term, all symbols should move towards @psalm-immutable
. The AutoloadSourceLocator
(and the SourceLocator
concept itself) probably needs tweaking to provide the constant value upfront, but populating the value post-definition is backwards, in this case.
CompiledValue
allowsresource
(ondrejmirtes@0c3ec4b) - people do all sorts of crazy sh*t - https://3v4l.org/hNlDD. Also thanks to this,STDIN
etc. can be reflected.
This makes sense: some constants are just like that. Allowing resource
would probably allow the previous point, but we can't design a native union type for resource
, can we?
- ReflectionClass::getParentClass() - be more resilient against errors (ondrejmirtes@1149f44) - I don't want to have reflection errors when someone extends an unknown class. I want to fail gracefully and have it reported with a PHPStan rule. Same should go for implemented interfaces but it's a much rarer problem.
This generally goes against the design of this library: undefined edges of the graph should lead to crashes. It is possible to build resiliency in userland (perhaps with an out-of-the-box implementation) by having a fallback "catch-all" source locator somewhere, registered at the end of all source locators, which will provide you with a symbol regardless, and mark it as unknown by adding some sort of signature or docblock definition to it.
- Support old-style constructors - When class
Foo
implements methodFoo
, it still works as a constructor on PHP 8, it's just deprecated (ondrejmirtes@6f597e6)
Argh, I thought this was gone for good! This is not a BC break - please feel free to send a patch, and it will be included in 5.1.0
- Constants from interface are considered final implicitly before PHP 8.1 (ondrejmirtes@fdd3ed9).
Also here, not a BC break: we can perhaps include this in 5.1.0
, given patches and tests.
Additional things are not part of my fork, but are commited directly to phpstan-src:
- Custom AutoloadSourceLocator (https://github.com/phpstan/phpstan-src/blob/master/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php) - the main thing that it does is that it knows when you have
class Foo
andfunction bar()
defined in a single file,function bar()
becomes loaded and defined afterclass Foo
is autoloaded. This requires me to scan the whole file for all symbols when asked forclass Foo
.
Not sure this needs to be in core: it seems like an implementation of the abstraction provided by this library.
- Custom DirectorySourceLocator, PsrAutoloaderLocator, SingleFileSourceLocator that are faster in some situations than the versions in BR. For example my
OptimizedDirectorySourceLocator
takes a page from Composer's classmap generator for quick scanning of files (https://github.com/phpstan/phpstan-src/blob/8737c3b4690661445338db2a6c31d340a0eac4bd/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php#L189-L243).
Totally worth using the classmap generator as input, and I even attempted doing that in the past, but remember that including anything is not really viable in core BetterReflection
. Such a class would be interesting with an integration test, but would require composer/composer
as dev dependency to guarantee compatibility. Perhaps separate package that just does that (with a massive disclaimer that it's including the analyzed sources)?
@ondrejmirtes of the above, the only one that we would need to do immediately, and that is a BC break, is this one:
CompiledValue
allowsresource
(ondrejmirtes@0c3ec4b) - people do all sorts of crazy sh*t - https://3v4l.org/hNlDD. Also thanks to this,STDIN
etc. can be reflected.
@asgrim the mutator API is your project: wdyt?
@kukulich I'm fine with removing the monkey patching feature if it isn't useful. It isn't the main point of the library indeed, but more of a "look what you can do with this". :+1:
At some point in the future I might make a new library that uses BR to do the monkey patching, just for fun :grin:
The root BetterReflection class is not supposed to be re-used in libraries: it's an "entry-point" for "doing it quickly, based on assumptions", but it is not an extension point.
I'm not using it like that, the problem is that there are some places in BR that do this:
return (new BetterReflection())->reflector()->reflectClass($className);
So I need to provide the correct Reflector etc. for those places.
Instantiating different PhpStormStubsSourceStubber with different versions would give you the possibility of comparing declared symbols.
That seems wasteful. I just need to know whether a Symbol
is an internal symbol at all. If not, I can look for it in other source locators. If yes, there are two situations: it IS defined in the current analysis PHP version, or it IS NOT defined in the current analysis PHP version. Let's say PHPStan is running on PHP 7.4 and the user calls a function that was added in PHP 7.4, but they want to analyse their codebase as if they were running PHP 7.1. If PhpStormStubsSourceStubber tells me a simple yes/no (no in this case), I'd come to conclusion that the function exists but is defined in runtime in a different source locator. But the correct conclusion is that the function is NOT defined.
Wouldn't it make more sense to read the docblock directly? The API for that is already provided...
The PHPDoc string doesn't tell the whole story. It doesn't tell me the current namespace and use statements, so I can't resolve relative names in it correctly.
Constants from interface are considered final implicitly before PHP 8.1
This is a difficult problem to solve because ReflectionClassConstant
doesn't have the PHP version it should consider for analysis. The only place that currently decides differently based on PHP version is PhpStormStubsSourceStubber
. In my fork I solved it by adding public static $phpVersion = PHP_VERSION_ID;
to BetterReflection class.
but remember that including anything is not really viable in core BetterReflection
I don't get it - the code doesn't include()
anything, I just copy-pasted the relevant code from Composer (while giving credit in the docblock).
Alright, I'm taking back the point about (new BetterReflection())->reflector()
, looks like it's not in any path that would get executed by PHPStan. This is a different situation than it was on BR 4.x 👍
Instantiating different PhpStormStubsSourceStubber with different versions would give you the possibility of comparing declared symbols.
That seems wasteful. I just need to know whether a
Symbol
is an internal symbol at all. If not, I can look for it in other source locators. If yes, there are two situations: it IS defined in the current analysis PHP version, or it IS NOT defined in the current analysis PHP version. Let's say PHPStan is running on PHP 7.4 and the user calls a function that was added in PHP 7.4, but they want to analyse their codebase as if they were running PHP 7.1. If PhpStormStubsSourceStubber tells me a simple yes/no (no in this case), I'd come to conclusion that the function exists but is defined in runtime in a different source locator. But the correct conclusion is that the function is NOT defined.
Shouldn't a source locator for 7.1 be instantiated in that case? :thinking: Perhaps I'm not fully understanding the problem: can we perhaps talk about it in a call, next week?
I'd come to conclusion that the function exists but is defined in runtime in a different source locator.
FWIW, having completely different reflectors/locators is the primary/first use-case of this library: roave/backward-compatibility-check
instantiates separate source locators for separate codebase versions ( https://github.com/Roave/BackwardCompatibilityCheck/blob/7d37292fc3ab6a94682812f39a18860a13318cfd/bin/roave-backward-compatibility-check.php#L64-L81 )
The PHPDoc string doesn't tell the whole story. It doesn't tell me the current namespace and use statements, so I can't resolve relative names in it correctly.
Constants from interface are considered final implicitly before PHP 8.1
The sources of that file should do that though, no? :thinking: EDIT: possibly other tools wanting a file, rather than the source string?
Constants from interface are considered final implicitly before PHP 8.1
This is a difficult problem to solve because
ReflectionClassConstant
doesn't have the PHP version it should consider for analysis. The only place that currently decides differently based on PHP version isPhpStormStubsSourceStubber
. In my fork I solved it by addingpublic static $phpVersion = PHP_VERSION_ID;
to BetterReflection class.
Ah, now I see the complexity: the behavior of the whole reflector changes there! I really wonder which injection point would be necessary, for constants to behave differently based on PHP version... :thinking:
but remember that including anything is not really viable in core BetterReflection
I don't get it - the code doesn't
include()
anything, I just copy-pasted the relevant code from Composer (while giving credit in the docblock).
Ah, I see from the linked code that you are actually processing the contents via regex: very fragile, but understandable. This sort of "weak contract" is precisely why I'd need composer/composer
as a dependency, because the structure of those autoloader files is potentially subject to changes, hence the BC boundary being so thin. This really should live in its own package that uses roave/better-reflection
and composer/composer
in its dependencies, similar to how we pinned down roave/infection-static-analysis-plugin
( https://github.com/Roave/infection-static-analysis-plugin/tree/767ffca9f37bf18fce1031b723650e594bdbc7f6#stability )
CompiledValue
allowsresource
(ondrejmirtes@0c3ec4b) - people do all sorts of crazy sh*t - https://3v4l.org/hNlDD. Also thanks to this,STDIN
etc. can be reflected.
https://github.com/Roave/BetterReflection/pull/905 ✅
- Support old-style constructors - When class
Foo
implements methodFoo
, it still works as a constructor on PHP 8, it's just deprecated (ondrejmirtes@6f597e6)
Wouldn't it make more sense to read the docblock directly? The API for that is already provided...
The PHPDoc string doesn't tell the whole story. It doesn't tell me the current namespace and use statements, so I can't resolve relative names in it correctly.
I think there should be better solution but there's probably bug/unsolved use-cases in PhpStormSourceStubber
- I have to think about it.
Perhaps I'm not fully understanding the problem: can we perhaps talk about it in a call, next week?
Yeah, sure :) To rephrase the problem one more time, I need to wrap PhpInternalSourceLocator (with ReflectionSourceStubber) in my own PhpVersionBlacklistSourceLocator (https://github.com/phpstan/phpstan-src/blob/master/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php) that calls these extra PhpStormStubsSourceStubber methods so that internal PHP functions from runtime version are not considered defined if they don't exist in the older PHP version set by the user.
The usefulness is demonstrated in the PHPStan playground: https://phpstan.org/r/656bc0ac-949b-45ef-919c-9365d4ebffac - it always runs on PHP 8.0 but sets different phpVersion
for its PHPStan config when running analysis. If I didn't use PhpVersionBlacklistSourceLocator, the linked code snippet wouldn't report an error on PHP 7.x.
because the structure of those autoloader files is potentially subject to changes
It's not parsing Composer autoloader files, it's parsing the PHP files in the analysed codebase to look for defined classes, interfaces, traits, enums, functions...
Perhaps I'm not fully understanding the problem: can we perhaps talk about it in a call, next week?
Yeah, sure :) To rephrase the problem one more time, I need to wrap PhpInternalSourceLocator (with ReflectionSourceStubber) in my own PhpVersionBlacklistSourceLocator (https://github.com/phpstan/phpstan-src/blob/master/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php) that calls these extra PhpStormStubsSourceStubber methods so that internal PHP functions from runtime version are not considered defined if they don't exist in the older PHP version set by the user.
The usefulness is demonstrated in the PHPStan playground: https://phpstan.org/r/656bc0ac-949b-45ef-919c-9365d4ebffac - it always runs on PHP 8.0 but sets different
phpVersion
for its PHPStan config when running analysis. If I didn't use PhpVersionBlacklistSourceLocator, the linked code snippet wouldn't report an error on PHP 7.x.
Just to make sure: isn't this already covered by the internal source locator having the PHP version specified as a parameter? I would expect the example from the playground to be 3 different PHPStan processes, each started with a different input PHP version :thinking:
because the structure of those autoloader files is potentially subject to changes
It's not parsing Composer autoloader files, it's parsing the PHP files in the analysed codebase to look for defined classes, interfaces, traits, enums, functions...
Ah, that makes much more sense, yes. It's basically replicating https://github.com/composer/composer/blob/4f789a5f6d3bff8f3650202e875a069249b911dc/src/Composer/Autoload/ClassMapGenerator.php#L216-L285
We can probably include that in the repository, with some name like WonkyAssOptimisticFileStructureSourceLocator
or such (I know composer does stop parsing symbols, in some cases, but I don't remember the scenarios). Testing that will not be a pleasure :P
- PhpStormStubsSourceStubber reports filename (ondrejmirtes@e416d4d) - so PHPStan can read the PHPDocs resolved with the correct namespace and use statements. It might be weird for internal symbols to have filenames, but it works as long as
isBuiltIn()
still answers correctly.
Thanks to @kukulich's work in #910, this should now be possible without file location.
isn't this already covered by the internal source locator having the PHP version specified as a parameter
I'm not sure, but we can set up a call to talk about this :)
I would expect the example from the playground to be 3 different PHPStan processes, each started with a different input PHP version
It starts different PHPStan processes, but all run on PHP 8.0, just with a different phpVersion
in phpstan.neon
:)
BTW I just found an inconsistency between native PHP reflection and BR's Adapter regarding enum cases (BR doesn't do this correctly https://3v4l.org/4gqGD) which results in this (https://phpstan.org/r/5a32012d-ac0d-46c1-b5dd-aa83a3f42067) - an object should be inferred instead of integer 1
. So please hold on with the release a bit longer, thanks :)
BTW I just found an inconsistency between native PHP reflection and BR's Adapter regarding enum cases (BR doesn't do this correctly https://3v4l.org/4gqGD) which results in this (https://phpstan.org/r/5a32012d-ac0d-46c1-b5dd-aa83a3f42067) - an object should be inferred instead of integer
1
. So please hold on with the release a bit longer, thanks :)
@ondrejmirtes I'd have some free time on Friday (I know, it's the 24th), but IMO we should at this point:
Would be a good xmas present :D
I agree, release the kraken! 😊
I have 5.0.x integrated in PHPStan and it's super solid. I'll be able to release PHPStan with Enums support shortly after thanks to BR, I just need a few finishing touches that I hopefully find the time for during the holidays 😊
@Ocramius that be an amazing Christmas present, but don't feel bad to take longer if you have to.
Closing here: will focus on writing release notes and finishing up all pending details, but no more BEE SEE BREKS!
This is just a placeholder to make sure everyone is aware that
5.0.0
is around the corner: if nobody replies to this issue within a couple days, I will release5.0.0
from5.0.x
(after attempting to put together some release notes @_@ )If you need to break BC further, now is the time.