Skip to content

Entity and repository wiring

Step 5 of the installation in full. The vendor plugin and this plugin extend several Sylius core entities through traits, and several Sylius repositories need the plugin's traits composed in so vendor-scoped and moderation-aware queries work.

Everything on this page is mandatory in every installation, regardless of which optional features you enable.

Edit your existing classes — don't overwrite them

The snippets below show the traits and interfaces to add, not complete files to paste over what you have. Your entity classes commonly already carry other plugins' traits — sylius/sylius-standard ships the Mollie plugin's ProductTrait on Product, for instance. Add use statements and interfaces alongside what's already there.

Entities

Channel

php
<?php
// src/Entity/Channel/Channel.php

declare(strict_types=1);

namespace App\Entity\Channel;

use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Odiseo\SyliusVendorPlugin\Entity\VendorInterface;
use Odiseo\SyliusVendorPlugin\Entity\VendorsAwareInterface;
use Odiseo\SyliusVendorPlugin\Entity\VendorsTrait;
use Sylius\Component\Core\Model\Channel as BaseChannel;

#[ORM\Entity]
#[ORM\Table(name: 'sylius_channel')]
class Channel extends BaseChannel implements VendorsAwareInterface
{
    /** @var Collection<array-key, VendorInterface> */
    #[ORM\ManyToMany(targetEntity: VendorInterface::class, mappedBy: 'channels')]
    protected Collection $vendors;

    use VendorsTrait {
        __construct as private initializeVendorsCollection;
    }

    public function __construct()
    {
        parent::__construct();

        $this->initializeVendorsCollection();
    }
}

Product

php
<?php
// src/Entity/Product/Product.php

declare(strict_types=1);

namespace App\Entity\Product;

use Doctrine\ORM\Mapping as ORM;
use Odiseo\SyliusMarketplacePlugin\Entity\ProductModerationAwareInterface;
use Odiseo\SyliusMarketplacePlugin\Entity\ProductModerationTrait;
use Odiseo\SyliusVendorPlugin\Entity\VendorAwareInterface;
use Odiseo\SyliusVendorPlugin\Entity\VendorTrait;
use Sylius\Component\Core\Model\Product as BaseProduct;

#[ORM\Entity]
#[ORM\Table(name: 'sylius_product')]
class Product extends BaseProduct implements VendorAwareInterface, ProductModerationAwareInterface
{
    use VendorTrait;
    use ProductModerationTrait;
}

TIP

ProductModerationTrait is what makes product moderation work — without it products are never gated for approval.

ShippingMethod and Adjustment

Every marketplace order ships as vendor-scoped shipments and charges, regardless of which shipping ownership mode you pick — so ShippingMethod and Adjustment need the same treatment, unconditionally, in every installation:

php
<?php
// src/Entity/Shipping/ShippingMethod.php

declare(strict_types=1);

namespace App\Entity\Shipping;

use Doctrine\ORM\Mapping as ORM;
use Odiseo\SyliusMarketplacePlugin\Entity\VendorAwareTrait;
use Odiseo\SyliusVendorPlugin\Entity\VendorAwareInterface;
use Sylius\Component\Core\Model\ShippingMethod as BaseShippingMethod;
use Sylius\Component\Shipping\Model\ShippingMethodTranslationInterface;

#[ORM\Entity]
#[ORM\Table(name: 'sylius_shipping_method')]
class ShippingMethod extends BaseShippingMethod implements VendorAwareInterface
{
    use VendorAwareTrait;

    protected function createTranslation(): ShippingMethodTranslationInterface
    {
        return new ShippingMethodTranslation();
    }
}
php
<?php
// src/Entity/Order/Adjustment.php

declare(strict_types=1);

namespace App\Entity\Order;

use Doctrine\ORM\Mapping as ORM;
use Odiseo\SyliusMarketplacePlugin\Entity\VendorAwareTrait;
use Odiseo\SyliusVendorPlugin\Entity\VendorAwareInterface;
use Sylius\Component\Core\Model\Adjustment as BaseAdjustment;

#[ORM\Entity]
#[ORM\Table(name: 'sylius_adjustment')]
class Adjustment extends BaseAdjustment implements VendorAwareInterface
{
    use VendorAwareTrait;
}

VendorAwareTrait (not the vendor plugin's VendorTrait, which is Product-specific and maps a required, bidirectional association) maps a nullable, unidirectional association — what a shipping method or adjustment needs, since an operator-owned shipping method has no vendor.

Repositories

Several Sylius repositories need the plugin's traits composed in, so vendor-scoped and moderation-aware queries work:

php
<?php
// src/Repository/ProductRepository.php

declare(strict_types=1);

namespace App\Repository;

use Odiseo\SyliusMarketplacePlugin\Repository\ProductRepositoryInterface;
use Odiseo\SyliusMarketplacePlugin\Repository\ProductRepositoryTrait;
use Sylius\Bundle\CoreBundle\Doctrine\ORM\ProductRepository as BaseProductRepository;

class ProductRepository extends BaseProductRepository implements ProductRepositoryInterface
{
    use ProductRepositoryTrait;
}
php
<?php
// src/Repository/ProductVariantRepository.php

declare(strict_types=1);

namespace App\Repository;

use Odiseo\SyliusMarketplacePlugin\Repository\ProductVariantRepositoryInterface;
use Odiseo\SyliusMarketplacePlugin\Repository\ProductVariantRepositoryTrait;
use Sylius\Bundle\CoreBundle\Doctrine\ORM\ProductVariantRepository as BaseProductVariantRepository;

class ProductVariantRepository extends BaseProductVariantRepository implements ProductVariantRepositoryInterface
{
    use ProductVariantRepositoryTrait;
}
php
<?php
// src/Repository/ProductReviewRepository.php

declare(strict_types=1);

namespace App\Repository;

use Odiseo\SyliusMarketplacePlugin\Repository\ProductReviewRepositoryInterface;
use Odiseo\SyliusMarketplacePlugin\Repository\ProductReviewRepositoryTrait;
use Sylius\Bundle\CoreBundle\Doctrine\ORM\ProductReviewRepository as BaseProductReviewRepository;

final class ProductReviewRepository extends BaseProductReviewRepository implements ProductReviewRepositoryInterface
{
    use ProductReviewRepositoryTrait;
}
php
<?php
// src/Repository/ChannelRepository.php

declare(strict_types=1);

namespace App\Repository;

use Odiseo\SyliusMarketplacePlugin\Repository\ChannelRepositoryInterface;
use Odiseo\SyliusMarketplacePlugin\Repository\ChannelRepositoryTrait;
use Sylius\Bundle\ChannelBundle\Doctrine\ORM\ChannelRepository as BaseChannelRepository;

class ChannelRepository extends BaseChannelRepository implements ChannelRepositoryInterface
{
    use ChannelRepositoryTrait;
}

A core Sylius order (and so its shipments) isn't itself vendor-owned — it can bundle items from several vendors. The seller panel's ship / resend-confirmation-email actions rely on the shipment repository to scope a shipment to the seller's own vendor, so this override is required even though Shipment itself doesn't need any mapping changes:

php
<?php
// src/Repository/ShipmentRepository.php

declare(strict_types=1);

namespace App\Repository;

use Odiseo\SyliusMarketplacePlugin\Repository\ShipmentRepositoryInterface;
use Odiseo\SyliusMarketplacePlugin\Repository\ShipmentRepositoryTrait;
use Sylius\Bundle\CoreBundle\Doctrine\ORM\ShipmentRepository as BaseShipmentRepository;

class ShipmentRepository extends BaseShipmentRepository implements ShipmentRepositoryInterface
{
    use ShipmentRepositoryTrait;
}

The shipping method resolvers (both vendor and operator ownership modes — see Shipping ownership mode) type-hint the plugin's own ShippingMethodRepositoryInterface, so this override is mandatory in every installation, not specific to either mode:

php
<?php
// src/Repository/ShippingMethodRepository.php

declare(strict_types=1);

namespace App\Repository;

use Odiseo\SyliusMarketplacePlugin\Repository\ShippingMethodRepositoryInterface;
use Odiseo\SyliusMarketplacePlugin\Repository\ShippingMethodRepositoryTrait;
use Sylius\Bundle\CoreBundle\Doctrine\ORM\ShippingMethodRepository as BaseShippingMethodRepository;

class ShippingMethodRepository extends BaseShippingMethodRepository implements ShippingMethodRepositoryInterface
{
    use ShippingMethodRepositoryTrait;
}

Registering the repositories

Merge, don't append

Merge these repository: lines into the sylius_product, sylius_review, sylius_channel and sylius_shipping blocks your project already has — a project generated from sylius/sylius-standard defines all four in config/packages/_sylius.yaml, next to the model: overrides. Don't paste them as new, separate top-level blocks: YAML silently accepts a repeated top-level key within one file, and Symfony's parser throws Duplicate key "sylius_product" detected the moment two sylius_product: mappings exist in the same file — so a second copy of any of these keys breaks the whole file, taking the pre-existing model: overrides down with it.

Also note product_review is nested one level deeper than the others — under product, alongside reviewer and subject — not a top-level resource of its own:

yml
# config/packages/_sylius.yaml — add the "repository:" lines below into your existing blocks
sylius_product:
    resources:
        product:
            classes:
                # model: App\Entity\Product\Product   <- already there; just add the line below
                repository: App\Repository\ProductRepository
        product_variant:
            classes:
                repository: App\Repository\ProductVariantRepository

sylius_review:
    resources:
        product:
            review:
                classes:
                    repository: App\Repository\ProductReviewRepository

sylius_channel:
    resources:
        channel:
            classes:
                repository: App\Repository\ChannelRepository

sylius_shipping:
    resources:
        shipment:
            classes:
                repository: App\Repository\ShipmentRepository
        shipping_method:
            classes:
                repository: App\Repository\ShippingMethodRepository

by Odiseo