Search code examples
shopwareshopware6

Shopware 6 database transactions for repositories?


In Shopware 6 we have implemented a voucher plugin that works with several database tables. To avoid race conditions or inconsistent data, it would make sense to use database transactions.

How can we do this when working with multiple entity repositories?

At SQL level it should look something like this:

begin;
SELECT code FROM voucher_pool;
INSERT INTO voucher_code (code, active, value, valid_until) VALUES ('ABCDEF', 1, 20, '2025-01-01');
INSERT INTO voucher_code_transaction (code_id, value) VALUES (X'...', +20);
UPDATE voucher_pool SET redeemed=1 WHERE code='ABCDEF';
commit;

At Shopware level, voucher_code and voucher_code_transaction can be used as one request. But we are not sure how to add the voucher_pool table to the same transaction. It should look like this:

// begin transaction
try {
    $availableCode = $this->voucherPoolRepository->search($criteria, $context)->first();
    $this->voucherCodeRepository->upsert([
        [
            'code'         => $availableCode->getCode(),
            'active'       => true,
            'value'        => 20,
            'valid_until'  => '2025-01-01',
            'transactions' => [
                [
                    'value' => 20,
                ],
            ],
        ],
    ], $context);
    $this->voucherPoolRepository->update([
        [
            'id'       => $availableCode->getId(),
            'redeemed' => true,
        ],
    ], $context);

    // commit
} catch (Throwable) {
    // rollback
}

In other shop systems, such as Magento, there is a transaction object to which you can add objects to trigger a combined save at once. Is there something like this in Shopware 6? Or are transactions only possible with the Connection object from Doctrine? e.g.

/** @var \Doctrine\DBAL\Connection $connection */
$connection->beginTransaction();
$this->voucherCodeRepository->upsert(...);
$this->voucherPoolRepository->update(...);
$connection->commit();

Solution

  • You can combine multiple write operations of different entities using the SyncService.

    use Shopware\Core\Framework\Context;
    use Shopware\Core\Framework\Api\Sync\SyncBehavior;
    use Shopware\Core\Framework\Api\Sync\SyncOperation;
    use Shopware\Core\Framework\Api\Sync\SyncService;
    
    $this->container->get(SyncService::class)->sync([
        new SyncOperation(
            'write',
            VoucherCodeDefinition::ENTITY_NAME,
            SyncOperation::ACTION_UPSERT,
            $codePayload
        ),
        new SyncOperation(
            'write',
            VoucherPoolDefinition::ENTITY_NAME,
            SyncOperation::ACTION_UPSERT,
            $poolPayload
        ),
    ], Context::createDefaultContext(), new SyncBehavior());