Search code examples
phpshopwareshopware6

Namespace collision with composer dependecies for Shopware 6 Plugin


I'm writing unit tests for my Shopware Plugin. I've setup the same PHPUnit environment as SwagPayPal.

What I did:

  • composer.json
[...]
"require": {
  "ext-json": "*",
  "php": ">= 8.0",
  "goetas-webservices/xsd2php-runtime": "^0.2.16",
  "enqueue/stomp": "^0.10.16",
  "shopware/core": "6.4.12.0"
},
"autoload": {
  "psr-4": {
    "Namespace\\Plugin\\": "src/"
  }
},
"autoload-dev": {
  "psr-4": {
    "Namespace\\Plugin\\Test\\": "tests/"
  }
},
[...]

My tests were all working fine. I now added the following code in one test:

$customerRepository = $this->getContainer()->get('customer.repository');
$customerRepository->upsert([$customerData], Context::createDefaultContext());

After calling upsert it crashes with Compile Error: Cannot declare class Shopware\Core\Checkout\Promotion\Cart\PromotionProcessor, because the name is already in use

I can get it working when I run sudo rm -rf var/cache/test_* but only once. When running it a second time it crashes again.

It seems it loads Shopware from the vendor folder of the plugin and then also from the root folder. I don't get why it's working the first time, but not a second time.

Edit: I checked all my namespace declarations in the PHP test files and I'm pretty sure they're correct. So I think it's another cause I can't find.


Solution

  • I just updated my tests/TestBootstrap.php to fix my problem.

    1. I removed the ->setClassLoader(require dirname(__DIR__) . '/vendor/autoload.php') call, so it doesn't load the composer generated stuff anymore.

    2. I added the following code so it adds the PSR-4 namespace prefixes for my plugin:

      $pluginComposerDir = realpath(__DIR__ . '/..');
      $pluginComposerJson = json_decode(file_get_contents($pluginComposerDir . '/composer.json'));
      
      foreach (['autoload', 'autoload-dev'] as $loader) {
          if (is_object($pluginComposerJson->$loader) && is_object($pluginComposerJson->$loader->{'psr-4'})) {
              foreach ($pluginComposerJson->$loader->{'psr-4'} as $namespace => $path) {
                  $classLoader->addPsr4($namespace, $pluginComposerDir . '/' . $path);
              }
          }
      }
      

    So now I have best of both worlds:

    • My plugin only adds the autoload-dev namespace(s) for tests.
    • It doesn't load the composer dependecies of my plugin, so I get no collisions with the Shopware root or other dependencies
    • I need to install my plugin dependencies in the Shopware root and if I don't do it Shopware won't install my extension and warn me about the failed dependencies:
      Failed Dependency: Required plugin/package "goetas-webservices/xsd2php-runtime ^0.2.16" is missing or not installed and activated
      Failed Dependency: Required plugin/package "enqueue/stomp ^0.10.16" is missing or not installed and activated
      

    Full tests/TestBootstrap.php:

    <?php declare(strict_types=1);
    // based on tests/TestBootstrap.php of SwagPayPal
    
    /*
     * (c) shopware AG <[email protected]>
     * For the full copyright and license information, please view the LICENSE
     * file that was distributed with this source code.
     */
    
    use Shopware\Core\TestBootstrapper;
    
    if (is_readable(__DIR__ . '/../../../../vendor/shopware/platform/src/Core/TestBootstrapper.php')) {
        // from Shopware root vendor dir
        require __DIR__ . '/../../../../vendor/shopware/platform/src/Core/TestBootstrapper.php';
    } elseif (is_readable(__DIR__ . '/../../../../vendor/shopware/core/TestBootstrapper.php')) {
        // from Shopware root vendor dir
        require __DIR__ . '/../../../../vendor/shopware/core/TestBootstrapper.php';
    } else {
        throw new Exception('TestBootstrapper not found. Please install Shopware root dependencies.');
    }
    
    $classLoader = (new TestBootstrapper())
        ->setProjectDir($_SERVER['PROJECT_ROOT'] ?? dirname(__DIR__, 4))
        ->setLoadEnvFile(true)
        ->setForceInstallPlugins(true)
        ->addCallingPlugin()
        ->bootstrap()
        ->getClassLoader();
    
    $pluginComposerDir = realpath(__DIR__ . '/..');
    $pluginComposerJson = json_decode(file_get_contents($pluginComposerDir . '/composer.json'));
    
    foreach (['autoload', 'autoload-dev'] as $loader) {
        if (is_object($pluginComposerJson->$loader) && is_object($pluginComposerJson->$loader->{'psr-4'})) {
            foreach ($pluginComposerJson->$loader->{'psr-4'} as $namespace => $path) {
                $classLoader->addPsr4($namespace, $pluginComposerDir . '/' . $path);
            }
        }
    }
    
    return $classLoader;
    

    Edit: adjusted tests/TestBootstrap.php to load TestBootrapper class from Shopware root.