Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found #143

Closed
MPParsley opened this issue Sep 13, 2020 · 31 comments
Closed

Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found #143

MPParsley opened this issue Sep 13, 2020 · 31 comments

Comments

@MPParsley
Copy link

How is drupal-check installed?

drupal-check is installed as a dependency to my project

Environment:

  • OS: macOS
  • PHP Version: 7.3
  • Drupal core: 9.0.x

Describe the bug
Reflection error occurs when using multiple arguments to test different files.
$ vendor/bin/drupal-check --deprecations --no-progress --drupal-root=../drupal --exclude-dir=vendor cookiebot_consent.module src/Event/LibrariesEvent.php

This issue was partially fixed in phpstan/phpstan#3522 but still occurs when using different files as arguments.

cookiebot_consent.module

  $event = new LibrariesEvent($extension);

src/Event/LibrariesEvent.php

use Symfony\Contracts\EventDispatcher\Event;

class LibrariesEvent extends Event {

Symfony\Contracts\EventDispatcher\Event

namespace Symfony\Contracts\EventDispatcher;

use Psr\EventDispatcher\StoppableEventInterface;

if (interface_exists(StoppableEventInterface::class)) {
    class Event implements StoppableEventInterface
    {
    }
} else {
    class Event
    {
    }
}

Console output

 ------ -------------------------------------------------------------------- 
  Line   cookiebot_consent.module                                            
 ------ -------------------------------------------------------------------- 
         Reflection error: Psr\EventDispatcher\StoppableEventInterface not   
         found.                                                              
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols  
 ------ -------------------------------------------------------------------- 

 ------ -------------------------------------------------------------------- 
  Line   src/Event/LibrariesEvent.php                                        
 ------ -------------------------------------------------------------------- 
         Reflection error: Psr\EventDispatcher\StoppableEventInterface not   
         found.                                                              
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols  
  52     Reflection error: Psr\EventDispatcher\StoppableEventInterface not   
         found.                                                              
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols  
 ------ -------------------------------------------------------------------- 
@ondrejmirtes
Copy link
Contributor

@MPParsley Can you follow this guide to make sure PHPStan has a way to discover those symbols? https://phpstan.org/user-guide/discovering-symbols

Maybe you need to add some directories as scanDirectories, I'm not familiar with a Drupal project structure...

@MPParsley
Copy link
Author

MPParsley commented Sep 21, 2020

Since we updated phpstan/phpstan to 0.12.43 we're seeing a lot of these Reflection errors when running drupal-check, here's another one:

 ------ --------------------------------------------------------------------- 
         Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found.  
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols   
 ------ --------------------------------------------------------------------- 

Looks like something changed in ondrejmirtes/better-reflection that's triggering this.

@ondrejmirtes
Copy link
Contributor

So can you follow the advice from my previous comment? https://github.com/mglaman/drupal-check/issues/186#issuecomment-691712665 Where are these classes/traits located and is discovering symbols configured correctly to find them?

@pookmish
Copy link

pookmish commented Sep 21, 2020

I've encountered same report as https://github.com/mglaman/drupal-check/issues/186#issuecomment-696295272. The PhpunitCompatibilityTrait trait exists as part of drupal/core dependency in the path web/core/tests/Drupal/Tests/PhpunitCompatibilityTrait.php.

This is noticeable after installing drupal core using composer composer create-project drupal/recommended-project:^8.9 my_site_name_dir as described in this guide. Then requiring mglaman/drupal-check and executing a check on some custom code.

@MPParsley
Copy link
Author

@ondrejmirtes, the advice in mglaman/drupal-check#186 (comment) is too cumbersome to implement; Drupal/Symfony contain a large amount of classes that are using the same pattern:

namespace Symfony\Contracts\EventDispatcher;

use Psr\EventDispatcher\StoppableEventInterface;

if (interface_exists(StoppableEventInterface::class)) {
    class Event implements StoppableEventInterface
    {
    }
} else {
    class Event
    {
    }
}

Afaik, from a php perspective interface_exists(StoppableEventInterface::class) is not a bug.

@ondrejmirtes
Copy link
Contributor

I'm just telling you that the right directories need to be put into scanDirectories. and it should work.

@MPParsley
Copy link
Author

MPParsley commented Sep 22, 2020

Thanks @ondrejmirtes for the tip but the purpose of scanDirectories is to exclude files from being scanned but it should be fine to also scan the Symfony (and other) dependencies. Also, it would be really hard to define "the right directories" in a generic fashion up front.

Note that the phpstan config for this project lives in https://github.com/mglaman/phpstan-drupal/blob/master/phpstan.neon

@ondrejmirtes
Copy link
Contributor

You misunderstand how it works :) You don't want to analyze Symfony\Contracts\EventDispatcher\Event but you want to discover the class in case your code uses it.

@MPParsley
Copy link
Author

Well, now I'm confused ;-)

Third party code outside of Composer dependencies #
If your project uses some code that isn’t part of your Composer dependencies, but you don’t wish to analyse it, you can take advantage of scanFiles and scanDirectories config options:

Symfony\Contracts\EventDispatcher\Event is a composer dependency so it should be discovered (by the autoloader), and as far as I can tell it is. It's the reflection engine that seems to be stumbling over StoppableEventInterface that doesn't exist (and never will)?

@ondrejmirtes
Copy link
Contributor

This exact case is tested here so it should work... https://github.com/phpstan/phpstan/tree/master/e2e/symfony-event

@MPParsley
Copy link
Author

MPParsley commented Sep 22, 2020

Since phpstan 0.12.43 I'm seeing lots of these:

 ------ --------------------------------------------------------------------- 
  Line   sg_kdb/tests/src/Kernel/Plugin/migrate/source/KdbProductTest.php     
 ------ --------------------------------------------------------------------- 
         Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found.  
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols   
 ------ --------------------------------------------------------------------- 

 ------ --------------------------------------------------------------------- 
  Line   sg_kdb/tests/src/Kernel/Plugin/migrate/source/KdbRegulationTest.php  
 ------ --------------------------------------------------------------------- 
         Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found.  
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols   
 ------ --------------------------------------------------------------------- 

 ------ --------------------------------------------------------------------- 
  Line   sg_migrate/tests/src/Kernel/Plugin/migrate/source/CityListTest.php   
 ------ --------------------------------------------------------------------- 
         Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found.  
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols   
 ------ --------------------------------------------------------------------- 

 ------ --------------------------------------------------------------------- 
  Line   sg_migrate/tests/src/Kernel/Plugin/migrate/source/GentUserTest.php   
 ------ --------------------------------------------------------------------- 
         Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found.  
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols   
 ------ --------------------------------------------------------------------- 

 ------ --------------------------------------------------------------------- 
  Line   sg_migrate/tests/src/Kernel/Plugin/migrate/source/OgNodeTest.php     
 ------ --------------------------------------------------------------------- 
         Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found.  
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols   
 ------ --------------------------------------------------------------------- 

 ------ --------------------------------------------------------------------- 
  Line   sg_migrate/tests/src/Kernel/Plugin/migrate/source/PrCoTest.php       
 ------ --------------------------------------------------------------------- 
         Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found.  
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols   
 ------ --------------------------------------------------------------------- 

 ------ --------------------------------------------------------------------- 
  Line   sg_paragraphs/tests/src/Unit/FieldValidationFactoryTest.php          
 ------ --------------------------------------------------------------------- 
         Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found.  
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols   
 ------ --------------------------------------------------------------------- 

 ------ --------------------------------------------------------------------- 
  Line   sg_paragraphs/tests/src/Unit/FieldValidationTest.php                 
 ------ --------------------------------------------------------------------- 
         Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found.  
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols   
 ------ --------------------------------------------------------------------- 

 ------ --------------------------------------------------------------------------- 
  Line   sg_paragraphs/tests/src/Unit/FieldValidator/ContentFieldValidatorTest.php  
 ------ --------------------------------------------------------------------------- 
         Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found.        
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols         
 ------ --------------------------------------------------------------------------- 

 ------ ------------------------------------------------------------------------ 
  Line   sg_paragraphs/tests/src/Unit/FieldValidator/LinkFieldValidatorTest.php  
 ------ ------------------------------------------------------------------------ 
         Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found.     
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols      
 ------ ------------------------------------------------------------------------ 

 ------ --------------------------------------------------------------------- 
  Line   sg_search/tests/src/Kernel/SearchBlockTest.php                       
 ------ --------------------------------------------------------------------- 
         Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found.  
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols   
 ------ --------------------------------------------------------------------- 

 ------ ------------------------------------------------------------------------------------ 
  Line   sg_topic/tests/src/Unit/EventSubscriber/TopicLinkRedirectRequestSubscriberTest.php  
 ------ ------------------------------------------------------------------------------------ 
         Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found.                 
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols                  
 ------ ------------------------------------------------------------------------------------ 

 [ERROR] Found 12 errors  
<?php

namespace Drupal\Tests;

use Drupal\TestTools\PhpUnitCompatibility\RunnerVersion;

// In order to manage different method signatures between PHPUnit versions, we
// dynamically load a compatibility trait dependent on the PHPUnit runner
// version.
if (!trait_exists(PhpunitVersionDependentTestCompatibilityTrait::class, FALSE)) {
  class_alias("Drupal\TestTools\PhpUnitCompatibility\PhpUnit" . RunnerVersion::getMajor() . "\TestCompatibilityTrait", PhpunitVersionDependentTestCompatibilityTrait::class);
}

As a workaround I'm now downgrading phpstan/phpstan to 0.12.42
- Downgrading phpstan/phpstan (0.12.43 => 0.12.42): Loading from cache

Seems like trait_exists is different from interface_exists and is not yet covered?

@ondrejmirtes
Copy link
Contributor

PhpunitCompatibilityTrait can be a different issue from the StoppableEventInterface. I think that PhpunitCompatibilityTrait isn't in an autoloader at all (it comes from a tests directory) so PHPStan needs to have a way to discover it. I'm repeating myself over and over - the documentation page "Discovering symbols" should have everything you need.

@MPParsley
Copy link
Author

Drupal tests are autoloaded, see https://www.drupal.org/docs/develop/standards/psr-4-namespaces-and-autoloading-in-drupal-8

In 0.12.43 it's broken and in one patch version lower 0.12.42 it works, of course I could be wrong but there seems to be a bug in play.

namespace Drupal\Tests\sg_paragraphs\Unit;

use Drupal\Tests\UnitTestCase;

class FieldValidationTest extends UnitTestCase {

There are no complaints about UnitTestCase so it seems to be properly autoloaded.

namespace Drupal\Tests;

abstract class UnitTestCase extends TestCase {

  use PhpunitCompatibilityTrait;

PhpunitCompatibilityTrait lives in the same namespace as UnitTestCase but somehow it's not found.

@ondrejmirtes
Copy link
Contributor

Please create a small reproducing repository that shows your problem, thank you.

@pookmish
Copy link

I've provided a repo that produces the PhpunitCompatibilityTrait issue. clone the repo, run composer install and then run drupal-check on the web/modules directory

https://github.com/pookmish/drupal-check-issue

My steps to produce this repo:

  1. composer create-project drupal/recommended-project:^8.9 drupal-check-issue
  2. composer require mglaman/drupal-check drupal/core-dev:^8.9 --dev
  3. Built out the custom Unit test class that extends UnitTestCase.
  4. vendor/bin/drupal-check web/modules/custom/

You can also see that composer require phpstan/phpstan:0.12.42 --dev and running the drupal-check again does not produce the same issue.

@ondrejmirtes
Copy link
Contributor

I looked into this: I've grabbed and modified the generated PHPStan config by drupal-check:

parameters:
	tipsOfTheDay: false
	reportUnmatchedIgnoredErrors: false
	excludes_analyse:
		- */tests/Drupal/Tests/Listeners/Legacy/*
		- */tests/fixtures/*.php
		- */settings*.php
		- */node_modules/*

	drupal:
		drupal_root: /Users/ondrej/Downloads/drupal-check-issue/web

	customRulesetUsed: true
	ignoreErrors:
		- "#\\Drupal calls should be avoided in classes, use dependency injection instead#"
		- "#Plugin definitions cannot be altered.#"
		- "#Missing cache backend declaration for performance.#"
		- "#Plugin manager has cache backend specified but does not declare cache tags.#"

	bootstrapFiles:
		- /Users/ondrej/Downloads/drupal-check-issue/vendor/mglaman/drupal-check/src/Command/../../error-bootstrap.php

includes:
	- /Users/ondrej/Downloads/drupal-check-issue/vendor/mglaman/drupal-check/src/Command/../../../../phpstan/phpstan-deprecation-rules/rules.neon
	- /Users/ondrej/Downloads/drupal-check-issue/vendor/mglaman/drupal-check/src/Command/../../../../mglaman/phpstan-drupal/extension.neon

And simply added (following the Discovering symbols guide):

	scanFiles:
		- /Users/ondrej/Downloads/drupal-check-issue/web/core/tests/Drupal/Tests/PhpunitCompatibilityTrait.php

And it fixed the issue. So this is something that should be tackled in phpstan-drupal or drupal-check... I can't speak for why it worked before but this is clearly a misconfigured situation...

@MPParsley
Copy link
Author

MPParsley commented Sep 22, 2020

Adding items to the scanFiles section would mean that we create technical debt here for every change where someone decides to add such a trait.

This is not a maintainable approach, the phpstan-drupal & drupal-check are generic packages meant to support deprecation checking. If possible we should avoid coupling them to the internals (such as specific traits) of Drupal.

We should first identify why phpstan 0.12.43 is no longer able to find PhpunitCompatibilityTrait.

@ondrejmirtes
Copy link
Contributor

Using scanFiles is not the only way you know, anything from Discovering symbols would work. The whole tests directory can be put into scanDirectories or the DrupalAutoloader in phpstan-drupal can be modified to also find classes from tests.

I don't think that investigating why the trait in 0.12.43 cannot be found would be productive, the example project setup is such that it was never meant to work.

ilmarioranen referenced this issue in UH-StudentServices/student_guide Sep 25, 2020
… positivies and errors with upgrade_status deprecation discovery.

More info:
https://www.drupal.org/project/upgrade_status/issues/3172382 / Running "drush upgrade_status:analyze" leads to reflection errors

"Experiencing similar errors. Found out the issue started happening after the latest
release of phpstan/phpstan 0.12.43. Issue has been reported on the mglaman/drupal-check project:
https://github.com/mglaman/drupal-check/issues/186

Quick fix for a project using this would be to manually require the previous version for the time being:
composer require phpstan/phpstan:"0.12.42"

https://github.com/mglaman/drupal-check/issues/186
https://phpstan.org/user-guide/discovering-symbols
-> Downgrade seems like the best option now, although PHPStan dev points to fixing it by changing config based on this page
MPParsley referenced this issue in district09/php_package_qa-drupal Sep 28, 2020
After the latest release of phpstan/phpstan 0.12.43 we run into reflection errors.
Issue has been reported on the mglaman/drupal-check project:
mglaman/drupal-check#186

Quick fix for a project using this would be to manually require the previous version for the time being:
composer require phpstan/phpstan:"0.12.42"
arlina-espinoza referenced this issue in apigee/apigee-m10n-drupal Sep 30, 2020
* [#246] Add CI testing matrix against D8 and D9.

* [#246] Remove wikimedia/composer-merge-plugin from CI as it's causing double updates and timing out.

* [#246] Set core_version_requirement: ^8.7.7 || ^9, the same minimum as apigee_edge.

* [#246] Use phpstan/phpstan 0.12.42 due to bug https://github.com/mglaman/drupal-check/issues/186

* [#246] Add $defaultTheme to functional/functionalJS tests.

* [#246] Remove apigee_mock_client and replace with apigee_mock_api_client from apigee_edge.

* [#246] Fix deprecations in kernel tests.

* [#246] Fix deprecations in kernel tests.

* [#246] Fix deprecations in functional tests.

* [#246] Fix deprecations in functional JS tests.

* [#246] Fix deprecations in apigee_m10n_add_credit_kernel.

* [#246] Fix deprecations in apigee_m10n_add_credit and apigee_m10n_teams.

* [#246] Require the latest D9 compatible version of apigee_edge.
@mglaman
Copy link
Owner

mglaman commented Oct 5, 2020

Moving this to PHPStan Drupal

@mglaman mglaman transferred this issue from mglaman/drupal-check Oct 5, 2020
@mglaman
Copy link
Owner

mglaman commented Oct 8, 2020

#144 fixes the tests here and uncovers this bug

@mglaman
Copy link
Owner

mglaman commented Oct 8, 2020

So this path is covered in the autoloader provided by PHPStan Drupal.

		- /Users/ondrej/Downloads/drupal-check-issue/web/core/tests/Drupal/Tests/PhpunitCompatibilityTrait.php

\PHPStan\Drupal\DrupalAutoloader::addCoreNamespaces

        // Add core test namespaces.
        $core_tests_dir = $this->drupalRoot . '/core/tests/Drupal';
        $this->namespaces['Drupal\\BuildTests'] = $core_tests_dir . '/BuildTests';
        $this->namespaces['Drupal\\FunctionalJavascriptTests'] = $core_tests_dir . '/FunctionalJavascriptTests';
        $this->namespaces['Drupal\\FunctionalTests'] =  $core_tests_dir . '/FunctionalTests';
        $this->namespaces['Drupal\\KernelTests'] = $core_tests_dir . '/KernelTests';
        $this->namespaces['Drupal\\Tests'] = $core_tests_dir . '/Tests';
        $this->namespaces['Drupal\\TestSite'] = $core_tests_dir . '/TestSite';
        $this->namespaces['Drupal\\TestTools'] = $core_tests_dir . '/TestTools';
        $this->namespaces['Drupal\\Tests\\TestSuites'] = $this->drupalRoot . '/core/tests/TestSuites';

For some reason the modifications done to the autoloader cause this to break.

I wonder if its due to the trait having this line of code?

<?php

namespace Drupal\Tests;

use Drupal\TestTools\PhpUnitCompatibility\RunnerVersion;

// In order to manage different method signatures between PHPUnit versions, we
// dynamically load a compatibility trait dependent on the PHPUnit runner
// version.
if (!trait_exists(PhpunitVersionDependentTestCompatibilityTrait::class, FALSE)) {
  class_alias("Drupal\TestTools\PhpUnitCompatibility\PhpUnit" . RunnerVersion::getMajor() . "\TestCompatibilityTrait", PhpunitVersionDependentTestCompatibilityTrait::class);
}

Going down the debug train to see why the autoloader hacks provided here broke things

@MPParsley
Copy link
Author

A similar issue existed with interface_exists in phpstan/phpstan#3521

@mglaman
Copy link
Owner

mglaman commented Oct 8, 2020

@ondrejmirtes I'm trying to track down what's happening. Because the BetterReflection picks up the file.. kind of. (Made some screenshots in #145 )

But what I thought was interesting is this:

	scanDirectories:
		- '%drupal.drupal_root%/core/tests/Drupal/Tests'
	scanFiles:
		- '%drupal.drupal_root%/core/tests/Drupal/Tests/PhpunitCompatibilityTrait.php'

The trait error still occurs with a directory scan, but not with file scan.

@ondrejmirtes
Copy link
Contributor

Weird, try to reproduce the issue with a vanilla PHPStan install, or in OptimizedDirectorySourceLocatorTest in phpstan-src...

@mglaman
Copy link
Owner

mglaman commented Oct 8, 2020

@ondrejmirtes 👍 will do. I'm trying to do some investigative work to see where/what/how. If it is in PHPStan's reflection code or the BetterReflection library.

@mglaman
Copy link
Owner

mglaman commented Oct 9, 2020

Merging the PR to get a conflict in so phpstan-drupal "works." The conflict is on phpstan/phpstan:^ 0.12.43.

Notes:

I can't easily tell why. But I want to just get a green release out and then continue debugging.

@ondrejmirtes
Copy link
Contributor

I think the conflicted release isn't necessary. Does this problem apply to all phpstan-drupal users? If not then you're blocking them from getting other useful stuff and bugfixes...

@mglaman mglaman changed the title Reflection error: Psr\EventDispatcher\StoppableEventInterface not found. Reflection error: Drupal\Tests\PhpunitCompatibilityTrait not found Oct 9, 2020
@mglaman
Copy link
Owner

mglaman commented Oct 9, 2020

@ondrejmirtes it unfortunately is, right now. Across the board, all users are hitting reflection errors for Drupal\Tests\PhpunitCompatibilityTrait. This is my quick fix to stop getting pings/bug reports while I fix it.

It could be fixed if I could have the phpstan-drupal extension affect the configuration to define scanDirs – since Drupal can have various vendor setups.

@mglaman
Copy link
Owner

mglaman commented Oct 12, 2020

Going to try the latest PHPStan. This fix stuck out at me

DependencyResolver - get current function reflection instead of obtaining it through ReflectionProvider (phpstan/phpstan-src@0e42941), #3909

@mglaman
Copy link
Owner

mglaman commented Oct 13, 2020

I found the error! It's due to an alias defined for PHPUnit compatibility between PHPUnit 6 and 7. I also double checked, it's not due to it being defined dynamically.

See: https://git.drupalcode.org/project/drupal/-/blob/8.8.x/core/tests/Drupal/Tests/PhpunitCompatibilityTrait.php#L10

```php
// In order to manage different method signatures between PHPUnit versions, we
// dynamically load a compatibility trait dependent on the PHPUnit runner
// version.
if (!trait_exists(PhpunitVersionDependentTestCompatibilityTrait::class, FALSE)) {
  class_alias("Drupal\TestTools\PhpUnitCompatibility\PhpUnit" . RunnerVersion::getMajor() . "\TestCompatibilityTrait", PhpunitVersionDependentTestCompatibilityTrait::class);
}

When I comment out that line of code and target the trait directly, non-aliased, everything works fine: ie use \Drupal\TestTools\PhpUnitCompatibility\PhpUnit7\TestCompatibilityTrait;.

When I modify the statement to be hardcoded and not rely on RunnerVersion::getMajor() it still breaks. So there is something about defining the trait alias that makes reflection break.

@mglaman
Copy link
Owner

mglaman commented Oct 14, 2020

From the PHPStan issue, the fix is to include the file in bootstrapFiles. Which the extension cannot influence, but it can manually include it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants