Installation:
composer require sylius/promotion
Add the bundle to config/bundles.php (if using Symfony) or register the component manually in Laravel.
First Use Case:
promotions table).PromotionChecker service to evaluate if a cart qualifies for a promotion:
use Sylius\Component\Promotion\Checker\PromotionCheckerInterface;
$promotionChecker = app(PromotionCheckerInterface::class);
$isEligible = $promotionChecker->isEligible($cart, $promotion);
Key Classes to Explore:
Promotion (core entity)PromotionChecker (evaluates eligibility)PromotionApplicator (applies discounts to carts)Rule and Action (custom logic for promotions)Define Promotions:
$promotion = new Promotion();
$promotion->setName('Summer Sale');
$promotion->setDescription('10% off for orders over $100');
$promotion->setStartsAt(new \DateTime('now'));
$promotion->setEndsAt(new \DateTime('+1 month'));
Rules and Actions:
CartTotalRule, ProductRule) and actions (e.g., PercentageDiscountAction):
$rule = new CartTotalRule();
$rule->setComparison('gt'); // Greater than
$rule->setValue(100); // $100
$promotion->addRule($rule);
$action = new PercentageDiscountAction();
$action->setValue(10); // 10%
$promotion->addAction($action);
Check and Apply:
// Check eligibility
if ($promotionChecker->isEligible($cart, $promotion)) {
$promotionApplicator = app(PromotionApplicatorInterface::class);
$promotionApplicator->apply($cart, $promotion);
}
Coupon Support:
Coupon entities for limited-use promotions:
$coupon = new Coupon();
$coupon->setCode('SUMMER20');
$coupon->setPromotion($promotion);
$coupon->setUsageLimit(100);
Laravel-Specific:
AppServiceProvider:
$this->app->bind(PromotionCheckerInterface::class, function ($app) {
return new PromotionChecker($app->make(PromotionRepositoryInterface::class));
});
cart.item_added).Custom Rules/Actions:
RuleInterface or ActionInterface for domain-specific logic.class LoyaltyCustomerRule implements RuleInterface {
public function isEligible(CartInterface $cart, PromotionInterface $promotion): bool {
return $cart->getCustomer()->isLoyal();
}
}
Database Schema:
promotion, rule, action, and coupon tables.Schema::create('sylius_promotion', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->dateTime('starts_at')->nullable();
$table->dateTime('ends_at')->nullable();
// ... other fields
});
Circular Dependencies:
Performance:
$cacheKey = "promo_eligible_{$promotion->getId()}_{$cart->getId()}";
if (cache()->has($cacheKey)) {
return cache()->get($cacheKey);
}
Time-Based Promotions:
starts_at/ends_at are handled correctly in your timezone. Use Carbon for consistency:
$now = Carbon::now();
if ($promotion->getStartsAt() && $promotion->getStartsAt()->gt($now)) {
return false;
}
Coupon Exhaustion:
DB::transaction(function () use ($coupon) {
if ($coupon->getUsageLimit() && $coupon->getUsageCount() >= $coupon->getUsageLimit()) {
throw new \RuntimeException('Coupon limit reached');
}
$coupon->incrementUsageCount();
});
Eligibility Issues:
foreach ($promotion->getRules() as $rule) {
logger()->debug(
sprintf('Rule %s (%s) eligible: %s',
get_class($rule),
$rule->getConfiguration(),
$rule->isEligible($cart, $promotion)
)
);
}
Action Conflicts:
Database Constraints:
promotion, rule, and action tables may cause issues if not set up correctly. Use Sylius’ migrations as a reference.Custom Rule/Action Storage:
configuration) for flexibility:
$rule->setConfiguration(['operator' => 'gt', 'value' => 100]);
Promotion Events:
promotion.applied, promotion.expired). Example:
event(new PromotionApplied($cart, $promotion));
API Integration:
Route::get('/api/promotions', function () {
return PromotionResource::collection(Promotion::all());
});
Testing:
$promotion = $this->createMock(PromotionInterface::class);
$promotion->method('getActions')->willReturn([$this->createMock(ActionInterface::class)]);
How can I help you explore Laravel packages today?