A generic way to work with Shopware entities in PHP

In this tutorial you will learn how to work with Shopware entities in a generic way from PHP, without having to specifically inject the repository in your services.xml file.

186 views
d

By. Jacob

Edited: 2024-02-16 15:49

In PHP, there is a generic way to work with entities in Shopware; you can use the DefinitionInstanceRegistry to get a list of all entities that are known by Shopware, and even access each individual entity by its name.

Normally you would have to inject each specific repository you want to work with in your services.xml file, but this will convolute things and restrain a developer from working with anything but the repositories that are specifically injected. This is a problem when you need to work with entities in a dynamic fashion, but luckily we have the DefinitionInstanceRegistry class that can help us do this.

Simply inject the DefinitionInstanceRegistry class via your services.xml file into your PHP service.

The services.xml:

<service id="yourOrganizationName\testPlugin\Service\repositoryAccess">
  <argument type="service" id="Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry" />
</service>

And inside your service, to get a list of known entities you may do as shown in this example:

namespace yourOrganizationName\testPlugin\Service;

use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry;

class repositoryAccess
{

    private $definitionInstanceRegistry;

    private $knownEntityNames = [];

    function __construct(DefinitionInstanceRegistry $definitionInstanceRegistry)
    {
        $this->definitionInstanceRegistry = $definitionInstanceRegistry;

        $entityNames = $this->definitionInstanceRegistry->getDefinitions();
        foreach ($entityNames as $entity) {
            $this->knownEntityNames[] = $entity->getEntityName();
        }
        ob_clean();
        var_dump($this->knownEntityNames);
        exit();

    }
}

If you try to run this from a CLI command, it will output a list of entity names known to Shopware. If you run it from a scheduled task, then you may need to write the output to a file instead, in order to see what is happening. I recommend you create a CLI command, because it makes it easier to test the output your plugin is producing.

Normally you would not want to work with the _translation entities, so you can filter those out by doing a str_ends_with() (php 8+). If you need to access translations, then you can use the search method on each individual entity repository, and use addAssociation to get the translated fields. What will be returned depends on the supplied context, which you need to create.

Accessing a repository by its string name

Once you know the name of a given repository, you can also access it via the definitionInstanceRegistry.

$entityRepository = $this->definitionInstanceRegistry->getRepository($this->knownEntityNames['product']);
$productResult = $entityRepository->search((new Criteria())->setLimit(1), $this->createContext());

var_dump($productResult);
exit();

Note the Context::createDefaultContext() can lead to unexpected results, so instead I suggest creating your own. To do so, add another method to your service class called createContext, as shown below:

namespace yourOrganizationName\testPlugin\Service;

use Shopware\Core\Defaults;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\Api\Context\SystemSource;

class repositoryAccess
{

    private $definitionInstanceRegistry;

    private $knownEntityNames = [];

    function __construct(DefinitionInstanceRegistry $definitionInstanceRegistry)
    {
        $this->definitionInstanceRegistry = $definitionInstanceRegistry;

        $entityNames = $this->definitionInstanceRegistry->getDefinitions();
        foreach ($entityNames as $entity) {
            $this->knownEntityNames[] = $entity->getEntityName();
        }
        ob_clean();
        var_dump($this->knownEntityNames);
        exit();

    }

    /**
     * Create a new context based on language and default values
     * @param string $languageId 
     * @return Context 
     */
    private function createContext(string $languageId = Defaults::LANGUAGE_SYSTEM) {
        return new Context(
            new SystemSource(),
            [],                                    
            Defaults::CURRENCY,   
            [$languageId],  
        );
    }
}

Now you can simply call the createContext method whenever you need to access a different translation of an entity. If no language ID is provided, it will simply use the system default language, so if you want to use a specific language ID, you should first look it up in the language repository.

How to obtain language IDs

If you want a list of languages that are actually used in your sales channels, you could either inject the sales_channel.repository in your services.xml file, or access the repository via the definitionInstanceRegistry. But, since you know you are going to work with the sales channels, I suggest just injecting the repository statically.

Here is how you can get a list of used language IDs:

private function defineActiveLanguageIds() {
  $criteria = new Criteria();
  $criteria->addFilter(new EqualsFilter('active', true));
  $criteria->addAssociation('domains');
  $salesChannels = ($this->salesChannelRepository->search($criteria, $this->createContext()))->getElements();

  foreach ($salesChannels as $salesChannelEntity) {
    if (!in_array($salesChannelEntity->getLanguageId(), $this->activeLanguageIds)) {
      $this->activeLanguageIds[] = $salesChannelEntity->getLanguageId();
    }
  }
}

If you need the name of the language as well, then you can add it via addAssociation('language') on your criteria, and return it with the getLanguage() method.

Finally, update your services.xml file with the sales_channel.repository:

<service id="yourOrganizationName\testPlugin\Service\repositoryAccess">
  <argument type="service" id="Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry" />
  <argument type="service" id="sales_channel.repository" />
</service>

And don't forget about your PHP file; add the relevant use statement, and updating your __construct() method with the sales channel repository. E.g:

function __construct(DefinitionInstanceRegistry $definitionInstanceRegistry, EntityRepository $salesChannelRepository) {
  $this->definitionInstanceRegistry = $definitionInstanceRegistry;
  $this->salesChannelRepository = $salesChannelRepository;

}

And the use statement for the EntityRepository at the top of your PHP file:

use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;

Things that will not work

Perhaps counterintuitively, we cannot use EntityRepository. I already tried and it will not work, you will just get a x is dependant on a non-existent class error.

If you ask ChatGPT then it will give you bogus directions and tell you to use DefinitionInstanceRegistry, and Doctrine's EntityManagerInterface; neither seem to work, and indeed, Doctrine\ORM does not even appear to be available in a default Shopware installation. In any case, Shopware has its own way of working with entities, so it is probably best we stick to that.

Tell us what you think:

  1. Sometimes we may to manually clear cached Shopware files to fix namespace issues.
  2. How to obtain the currently selected API language from Shopware vue components.
  3. How to access file system paths from storefront twig files in Shopware.
  4. How to get or change the currently selected sales channel in an Shopware sw-sales-channel-switch component.

More in: Shopware