05.02.2026・TechStuff
05.02.2026・TechStuff

Shopware 6: Don’t make assumptions about paths and were your plugin lives

Fabian Blechschmidt

We have a template which generates documents for us. Unfortunately it broke with the following error:

php.CRITICAL: Uncaught Error: Failed opening required '/var/www/customer-deploy/shared/custom/plugins/NameOfThePlugin/src/Twig/../../../../../vendor/tecnickcom/tcpdf/examples/barcodes/tcpdf_barcodes_2d_include.php' (include_path='.:/usr/share/php') {"exception":"[object] (Error(code: 0): Failed opening required '/var/www/customer-deploy/shared/custom/plugins/NameOfThePlugin/src/Twig/../../../../../vendor/tecnickcom/tcpdf/examples/barcodes/tcpdf_barcodes_2d_include.php' (include_path='.:/usr/share/php') at /var/www/customer-deploy/shared/custom/plugins/NameOfThePlugin/src/Twig/TwigExtension.php:95)"} []

In short, the plugin tries to find vendor/tecnickcom/tcpdf/examples/barcodes/tcpdf_barcodes_2d_include.php. But it is looking in shared/vendor and not in /vendor.

Why is this?

Because we use deployer to throw code on the server and the custom/plugins directory is shared between the releases. In other words, our server look like this:

.
├── current -> releases/20260129012927
├── htdocs -> /var/www/share/example.org/current/public
├── releases
│   ├── 20251023075935
│   └── 20260129012927
└── shared
    ├── custom
        └── plugins
    └── var

Due to the fact that the plugin file is including the tcpdf example this way:

require_once __DIR__ . '/../../../../../vendor/tecnickcom/tcpdf/examples/barcodes/tcpdf_barcodes_2d_include.php';

It breaks, because the relative path is wrong. I assume, even if we have installed it via composer, it would still break.

There are at least three ways to install a plugin:

  1. Put it in custom/plugins (install via ZIP upload or from the store)
  2. Put it in custom/static-plugins
  3. Install it via composer and it ends up in vendor

So even if we don’t use deployer, this relative link only works for 1 + 2.

TL;DR

Don’t do relative links into vendor because it doesn’t work in all circumstances.

So, How Then?

Get the root directory from Symfony and pass it to the service, like so.

<?php declare(strict_types=1);

namespace Swag\MyPlugin\Service;

final class VendorDirLocator
{
    public function __construct(private readonly string $projectDir) {}

    public function getProjectDir(): string
    {
        return $this->projectDir;
    }

    public function getVendorDir(): string
    {
        $vendor = $this->projectDir . '/vendor';

        $real = realpath($vendor);
        if ($real === false) {
            throw new \RuntimeException('Project vendor directory not found at: ' . $vendor);
        }

        return $real;
    }
}
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services
                               https://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="Swag\MyPlugin\Service\VendorDirLocator">
            <argument>%kernel.project_dir%</argument>
        </service>
    </services>
</container>