[Promotion] Add version locking to promotions and coupons by NoResponseMate · Pull Request #18921 · Sylius/Sylius

📝 Walkthrough

Walkthrough

Adds optimistic locking to Promotion and PromotionCoupon (version fields, ORM mappings, migrations), updates interfaces to be Versioned, and refactors AtomicOrderPromotionsUsageModifier to obtain entity locks via the EntityManager and delegate increment/decrement to a decorated modifier.

Changes

Cohort / File(s) Summary
Core Entity Models
src/Sylius/Component/Core/Model/Promotion.php, src/Sylius/Component/Core/Model/PromotionCoupon.php
Added protected version property (default 1) with getVersion(): ?int and setVersion(?int): void accessors.
Entity Interfaces
src/Sylius/Component/Core/Model/PromotionInterface.php, src/Sylius/Component/Core/Model/PromotionCouponInterface.php
Now extend VersionedInterface, adding versioning to the public contract.
Doctrine ORM Configuration
src/Sylius/Bundle/CoreBundle/Resources/config/doctrine/model/Promotion.orm.xml, src/Sylius/Bundle/CoreBundle/Resources/config/doctrine/model/PromotionCoupon.orm.xml
Added integer version field mappings with version="true" to enable optimistic locking.
Database Migrations
src/Sylius/Bundle/CoreBundle/Migrations/Version20260216120000.php, src/Sylius/Bundle/CoreBundle/Migrations/Version20260216120001.php
New migrations adding version INT NOT NULL DEFAULT 1 columns to sylius_promotion and sylius_promotion_coupon (standard and PostgreSQL variants).
Modifier Refactor
src/Sylius/Bundle/CoreBundle/Doctrine/ORM/Promotion/Modifier/AtomicOrderPromotionsUsageModifier.php
Reworked to accept optional Connection, a decorated OrderPromotionsUsageModifierInterface, and EntityManagerInterface; removed direct SQL updates and now refreshes and locks relevant entities (optimistic locking) before delegating increment/decrement to the decorated modifier; constructor usage of Connection is deprecated.
Service Configuration
src/Sylius/Bundle/CoreBundle/Resources/config/services/promotion.php
Updated service args for sylius.modifier.promotion.order_usage.atomic to pass null, the decorated service (.inner), and doctrine.orm.entity_manager; removed constructor arg from non-atomic modifier.
Tests
src/Sylius/Bundle/CoreBundle/tests/Doctrine/ORM/Promotion/Modifier/AtomicOrderPromotionsUsageModifierTest.php, src/Sylius/Component/Core/tests/Model/PromotionCouponTest.php
Added tests verifying EntityManager refresh/lock interactions and delegation to the decorated modifier; added test confirming PromotionCoupon implements VersionedInterface and default version equals 1.

Sequence Diagram

sequenceDiagram
    participant Client as Client Code
    participant AOUM as AtomicOrderPromotionsUsageModifier
    participant EM as EntityManager
    participant DM as Decorated Modifier
    participant DB as Database

    Client->>AOUM: increment(order, promotions, coupon?)
    Note over AOUM: Refresh & optimistic lock phase
    AOUM->>EM: refresh(promotion)
    EM->>DB: SELECT promotion
    DB-->>EM: promotion row (version=N)
    AOUM->>EM: lock(promotion, OPTIMISTIC, N)
    EM-->>AOUM: locked (version checked)

    alt coupon present
        AOUM->>EM: refresh(coupon)
        EM->>DB: SELECT coupon
        DB-->>EM: coupon row (version=M)
        AOUM->>EM: lock(coupon, OPTIMISTIC, M)
        EM-->>AOUM: locked (version checked)
    end

    Note over AOUM: Delegate actual usage change
    AOUM->>DM: increment(order, promotions, coupon?)
    DM->>DB: apply updates (via repositories/ORM)
    DB-->>DM: updates persisted
    DM-->>AOUM: done
    AOUM-->>Client: complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nudged the versions up to one,
Locks in place before the fun,
I delegate with careful hops,
No rogue SQL, no sudden drops,
Hooray — concurrency now runs!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding version locking to promotions and coupons. It is concise, clear, and directly related to the primary objective of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.