Description
Scenario
namespace MyLib;
use External\Foo;
use External\Bar;
class MyClass extends Foo, implements Bar {}
Assuming MyClass
is part of our package.
Assuming External\Foo
and External\Bar
are from require-dev
or completely missing.
roave/backward-compatibility-check
will not be able to compare two versions of this class, as it will generate two empty stubs:
namespace External {interface Foo{}}
namespace External {interface Bar{}}
These are invalid, since the extends
statement does not allow interfaces.
The stubbing mechanism also has no way to determine if Foo
and Bar
are to be classes or interfaces, since no requirement is passed to it (and it is not possible to do that anyway).
The problem
The root problem is not the stubbing, which is really just a mechanism to prevent basic crashes for rare optional dependency scenarios. The issue lies with how we treat dependencies in first place. If something is in our sources and we rely on external classes, said external classes must be part of the "require"
section of composer.json
before proceeding.
Further issues with missing "require"
Consider following example:
class ComparedClass
{
public function changedMethod(A $a) : B;
}
If A
and B
are unknown to the system, a change in a dependency can lead to downstream BC breaks due to BC breaks in A
and B
themselves, specifically in variance and contravariance. Stubbing out these two symbols can indeed simplify operations at first, but can also shadow issues when the libraries where A
and B
come from change.
Related: #74
Proposed solution
In order to raise awareness of the problem and improve the overall ecosystem, I endorse that downstream consumers of this library also run maglnet/composer-require-checker
before running this tool, or if this tool crashes on undefined symbols. This is a documentation issue (and this ticket is to be transformed into a documentation page).
/cc @maglnet maybe you can suggest how to make this user-friendly?
Activity