It’s sometimes necessary to have a repository for a purpose with a narrower scope. The most popular way is simply to provide a configuration adjustment for Sylius Resource:

sylius_resource:
  resources:
    sylius.product:
      classes:
        repository: BitBag\SyliusMyPlugin\Repository\ProductRepository
        model: Sylius\Component\Core\Model\Product

And it should be altered in your e-commerce solution configuration files (/config/_sylius.yaml and similar). If your plug-in provides such a resource+repository, it’s okay to keep it.

But as the project grows up, you — eventually — come across a situation where two plug-ins will be competing for „more important” repository implementation. Using the above approach you gonna create a plug-in that may exclude support of another one (it’s impossible to specify two different repositories for a single resource).

Of course, a developer may end up with a huge README file with a bunch of instructions to follow, eg. to provide a trait for an already existing application-level repository but it’s not necessary.

One decoration… wrapper to rule them all

The aim is to create a wrapper for the already existing repository. Nothing more, nothing less. In most cases, your repository implementation simply provides additional fetching methods and this is how we deal with it seamlessly.

First, you need to create a service that utilizes a stock-provided repository:

services:
  bitbag.sylius_my_plugin.repository.fancy_product:
    class: BitBag\SyliusMyPlugin\Repository\FancyProductRepository
    arguments:
      - '@sylius.repository.product'

Once the service is available, let’s head up to implementation:

namespace BitBag\SyliusMyPlugin\Repository;

use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository;
use Sylius\Component\Core\Model\ProductInterface;

class PaymentRepository implements ProductRepositoryInterface
{
    /** @var EntityRepository */
    private $baseRepository;

    public function __construct(EntityRepository $baseRepository)
    {
        $this->baseRepository = $baseRepository;
    }

    /**
     * @psalm-suppress MixedReturnStatement
     * @psalm-suppress MixedInferredReturnType
     */
    public function find(int $id): ?ProductInterface
    {
        return $this->baseRepository->find($id);
    }

    /**
     * @psalm-suppress MixedReturnStatement
     * @psalm-suppress MixedInferredReturnType
     */
    public function getOneByFancyCondition(string $answer): ProductInterfaceInterface
    {
        $qb = $this->baseRepository->createQueryBuilder('p');
        $qb
            ->select('p')
            ->where('p.code=:code')
            ->andWhere('p.id > 1000')
            ->setParameters([
                'code' => $answer
            ])
        ;

        return $qb->getQuery()->getSingleResult();
    }
}

And then, we have a repository to create:

namespace BitBag\SyliusMyPlugin\Repository;

use Sylius\Component\Core\Model\ProductInterface;

interface ProductRepositoryInterface
{
    public function find(int $id): ?ProductInterface;

    public function getOneByFancyCondition(string $answer): ProductInterface;
}

Controversies

Now I can hear your unasked question: „why don’t you extend the stock interface?”. Been there, done that, it’s possible, but — mostly — not worth your effort:

  • you can always inject sylius.repository.product services alongside;
  • probably you rely on your own repository method,
  • once you decide to extend a stock interface, you have to keep in mind that your repository implementation is a subject to be maintained along with Sylius upgrades; decoration decouples your repository from stock one making your plug-in more upgrade-proof,
  • also, if you still don’t give up on extending, pay attention to wrapping all the methods from the stock interface in pretty repetitive manner,
public function findOneByCustomer(CustomerInterface $customer){
    return $this->baseRepository->findOneByCustomer($customer);
}

How to use this repository

There’s nothing undiscovered here. Simply inject a newly created repository and play with it

services:
  bitbag.sylius_my_plugin.service:
    class: BitBag\SyliusMyPlugin\Service
    arguments:
      - '@bitbag.sylius_my_plugin.repository.fancy_product'

And implementation:

namespace BitBag\SyliusMyPlugin;

use Sylius\Component\Core\Model\ProductInterface;
use BitBag\SyliusMyPlugin\Repository\ProductRepositoryInterface;

class Service
{
    private ProductRepositoryInterface $productRepository;
    
    public function __construct(ProductRepositoryInterface $productRepository){
        $this->productRepository = $productRepository;
    }
    
    public function __invoke(): string
    {
        $product = $this->productRepository->getOneByFancyCondition('42 is the answer');
        return $product->getShortDescription();
    }

Our team consists of more than 50 experienced people, if you are looking for advice related to technical aspects, or want to set up a new eCommerce – contact us.