Appearance
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