This guide demonstrates how to add a one-to-one image association to an entity in Sylius 2.x. We'll use the PaymentMethod entity as an example, but the same approach applies to any other entity.
Prerequisites
Your project uses Sylius 2.x
is installed and configured
Writable public/media/image/ directory
Doctrine migrations are enabled
Step 1: Create the Image Entity
<?php
// src/Entity/Payment/PaymentMethodImage.php
namespace App\Entity\Payment;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Core\Model\Image;
#[ORM\Entity]
#[ORM\Table(name: 'sylius_payment_method_image')]
class PaymentMethodImage extends Image
{
#[ORM\OneToOne(inversedBy: 'image', targetEntity: PaymentMethod::class)]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
protected $owner;
public function __construct()
{
$this->type = 'default';
}
}
Step 2: Update the Owner Entity
<?php
// src/Entity/Payment/PaymentMethod.php
namespace App\Entity\Payment;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Core\Model\ImageAwareInterface;
use Sylius\Component\Core\Model\ImageInterface;
use Sylius\Component\Core\Model\PaymentMethod as BasePaymentMethod;
#[ORM\Entity]
#[ORM\Table(name: 'sylius_payment_method')]
class PaymentMethod extends BasePaymentMethod implements ImageAwareInterface
{
#[ORM\OneToOne(mappedBy: 'owner', targetEntity: PaymentMethodImage::class, cascade: ['all'], orphanRemoval: true)]
protected ?PaymentMethodImage $image = null;
public function getImage(): ?ImageInterface
{
return $this->image;
}
public function setImage(?ImageInterface $image): void
{
$image?->setOwner($this);
$this->image = $image;
}
}
Step 3: Create the Image Form Type
<?php
// src/Form/Type/PaymentMethodImageType.php
namespace App\Form\Type;
use App\Entity\Payment\PaymentMethodImage;
use Sylius\Bundle\CoreBundle\Form\Type\ImageType;
use Symfony\Component\Form\FormBuilderInterface;
final class PaymentMethodImageType extends ImageType
{
public function __construct()
{
parent::__construct(PaymentMethodImage::class, ['sylius']);
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
parent::buildForm($builder, $options);
$builder->remove('type');
}
public function getBlockPrefix(): string
{
return 'payment_method_image';
}
}
<?php
// src/Form/Extension/PaymentMethodTypeExtension.php
namespace App\Form\Extension;
use App\Form\Type\PaymentMethodImageType;
use Sylius\Bundle\PaymentBundle\Form\Type\PaymentMethodType;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
final class PaymentMethodTypeExtension extends AbstractTypeExtension
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('image', PaymentMethodImageType::class, [
'label' => 'sylius.ui.image',
'required' => false,
]);
}
public static function getExtendedTypes(): iterable
{
return [PaymentMethodType::class];
}
}
<?php
// src/EventSubscriber/ImageUploadSubscriber.php
namespace App\EventSubscriber;
use Sylius\Component\Core\Model\ImageAwareInterface;
use Sylius\Component\Core\Uploader\ImageUploaderInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use Webmozart\Assert\Assert;
final class ImageUploadSubscriber implements EventSubscriberInterface
{
public function __construct(private ImageUploaderInterface $uploader)
{
}
public static function getSubscribedEvents(): array
{
return [
'sylius.payment_method.pre_create' => 'uploadImage',
'sylius.payment_method.pre_update' => 'uploadImage',
];
}
public function uploadImage(GenericEvent $event): void
{
$subject = $event->getSubject();
Assert::isInstanceOf($subject, ImageAwareInterface::class);
$this->uploadSubjectImage($subject);
}
private function uploadSubjectImage(ImageAwareInterface $subject): void
{
$image = $subject->getImage();
if (null === $image) return;
if ($image->hasFile()) $this->uploader->upload($image);
if (null === $image->getPath()) $subject->setImage(null);
}
}