Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php declare(strict_types=1);

namespace App\Dispatcher;

use Ibexa\Messenger\Stamp\DeduplicateStamp;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;

final class SomeClassThatSchedulesExecutionInTheBackground
{
private MessageBusInterface $bus;

public function __construct(MessageBusInterface $bus)
{
$this->bus = $bus;
}

public function schedule(object $message): void
{
// Dispatch directly. Message is wrapped with envelope without any stamps.
$this->bus->dispatch($message);

// Alternatively, wrap with stamps. In this case, DeduplicateStamp ensures
// that if similar command exists in the queue (or is being processed)
// it will not be queued again.
$envelope = Envelope::wrap(
$message,
[new DeduplicateStamp('command-name-1')]
);

$this->bus->dispatch($envelope);
}
}
8 changes: 8 additions & 0 deletions code_samples/background_tasks/src/Message/SomeMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php declare(strict_types=1);

namespace App\Message;

class SomeMessage
{
// Add properties and methods as needed for your message.
}
13 changes: 13 additions & 0 deletions code_samples/background_tasks/src/MessageHandler/SomeHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php declare(strict_types=1);

namespace App\MessageHandler;

use App\Message\SomeMessage;

final class SomeHandler
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2025-10-01 at 18 07 01

Asking just to be sure: Symfony doc mentions that if you choose to avoid using the Attribute, then the MessageHandlerInterface interface needs to be implemented.

Can we skip it in this case?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only true for Symfony 5.x. On Symfony 6 and above, that interface does not exist.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so this is ok for v5, but the interface should be added when backporting to 4.6?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On 4.6 MessageHandlerInterface is indeed required. I don't remember if you can create handlers without it, or just tag the service properly. I think it's mostly used to mark service, but unsure.

{
public function __invoke(SomeMessage $message): void
{
// Handle message.
}
}
2 changes: 2 additions & 0 deletions docs/api/event_reference/discounts_events.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ The events below are dispatched when managing [discounts](discounts_guide.md):
|---|---|
|[BeforeCreateDiscountEvent](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeCreateDiscountEvent.html)| [DiscountServiceInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html) |
|[CreateDiscountEvent](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-CreateDiscountEvent.html)| [DiscountServiceInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html) |
|[BeforeEnableDiscountEvent](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeEnableDiscountEvent.html)| [DiscountServiceInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html) |
|[EnableDiscountEvent](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-EnableDiscountEvent.html)| [DiscountServiceInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html) |
|[BeforeDeleteDiscountEvent](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeDeleteDiscountEvent.html)| [DiscountServiceInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html) |
|[DeleteDiscountEvent](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-DeleteDiscountEvent.html)| [DiscountServiceInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html) |
|[BeforeUpdateDiscountEvent](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Event-BeforeUpdateDiscountEvent.html)| [DiscountServiceInterface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountServiceInterface.html)|
Expand Down
29 changes: 29 additions & 0 deletions docs/discounts/configure_discounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,35 @@ ibexa:
products_list_per_page_limit: 15
```

## Discount re-indexing

Discounts feature uses [[= product_name_base =]] Messenger to reindex discounts and product prices in the background.
This way changes are processed efficiently without slowing down the system and disrupting the user experience.

When triggered periodically, the `ibexa:discounts:reindex` command identifies discounts that require re-indexing, ensuring catalog prices always remain up-to-date.
If there are edits to discounts that should result in changed product catalog prices, messages are dispatched to the [[= product_name_base =]] Messenger's queue and consumed by a background worker.
The worker passes the messages to the handler, which then starts the re-indexing process at the most convenient moment.

To run discount re-indexing in the background:

1\. Make sure that the transport layer is [defined properly](background_tasks.md#configure-package) in [[= product_name_base =]] Messenger configuration.

2\. Make sure that the worker starts together with the application to watch the transport bus:

``` bash
php bin/console messenger:consume ibexa.messenger.transport --bus=ibexa.messenger.bus
```

3\. Use a scheduler of your choice, for example, [cron](https://en.wikipedia.org/wiki/Cron), to periodically run the following command:

``` bash
php bin/console ibexa:discounts:reindex
```

!!! note "Deploying Symfony Messenger"

For more information about deploying the Messenger to production, see [Symfony documentation](https://symfony.com/doc/current/messenger.html#deploying-to-production).

## Rate limiting

To prevent malicious actors from trying all the possible discount code combinations using brute-force attacks, the [`/discounts_codes/{cartIdentifier}/apply` endpoint](/api/rest_api/rest_api_reference/rest_api_reference.html#discount-codes-apply-discount-to-cart) is rate limited using the [Rate Limiter Symfony component]([[= symfony_doc =]]/rate_limiter.html).
Expand Down
9 changes: 9 additions & 0 deletions docs/discounts/discounts_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@ You can also limit the usage per customer:
- limited use: every customer can use the code a specified number of times
- unlimited

### Discount re-indexing

Discounts affect the prices shown in the product catalog.
When a discount is created, updated, or expires, the product catalog must be re-indexed to ensure that the search results and product listings display correct prices.

To prevent performance disruptions which could occur if re-indexing occurred immediately, [[= product_name =]] uses the [[= product_name_base =]] Messenger's [background queue](background_tasks.md) to process re-indexing tasks in the background.

By [configuring the process](configure_discounts.md#discount-re-indexing), you ensure that re-indexing is performed at the most convenient time to maintain your application's overall stability.

## Capabilities

### Management
Expand Down
102 changes: 102 additions & 0 deletions docs/infrastructure_and_maintenance/background_tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
description: Use Ibexa Messenger to run processes in the background and conserve system resources.
month_change: true
---

# Background tasks

Some operations in [[= product_name =]] don’t have to run immediately when a user clicks a button, for example, re-indexing product prices or processing bulk data.
Running such operations in real time could slow down the system and disrupt the user experience.

To solve this, [[= product_name =]] provides a package called [[= product_name_base =]] Messenger, which is an overlay to [Symfony Messenger](https://symfony.com/doc/current/messenger.html), and it's job is to queue tasks and run them in the background.
[[= product_name =]] sends messages (or commands) that represent the work tto be done later.
These messages are stored in a queue and picked up by a background worker, which ensures that resource-heavy tasks are executed at a convenient time, without putting excessive load on the system.

[[= product_name_base =]] Messenger supports multiple storage backends, such as Doctrine, Redis, and PostgreSQL, and gives developers the flexibility to create their own message handlers for custom use cases.

## How it works

[[= product_name_base =]] Messenger uses a command bus as a queue that stores messages, or commands, which tell the system what you want to happen, and separates them from the handler, which is the code that actually performs the task.

The process works as follows:

1. A message PHP object is dispatched, for example, `ProductPriceReindex`.
2. The message is wrapped in an envelope, which may contain additional metadata, called stamps, for example, `DeduplicateStamp`.
3. The message is placed in the transport queue.
It can be a Doctrine table, a Redis queue, and so on.
4. A worker process continuously reads messages from the queue, pulls them into the default bus `ibexa.messenger.bus` and assigns them to the right handler.
5. A handler service processes the message (executes the command).
You can register multiple handlers for different jobs.

Here is an example of how you can extend your code and use [[= product_name_base =]] Messenger to process your tasks:

### Configure package

Create a config file, for example, `config/packages/ibexa_messenger.yaml` and define your transport:

``` yaml
ibexa_messenger:

# The DSN of the transport, as expected by Symfony Messenger transport factory.
transport_dsn: 'doctrine://default?table_name=ibexa_messenger_messages&auto_setup=false'
deduplication_lock_storage:
enabled: true

# Doctrine DBAL primary connection or custom service
type: doctrine # One of "doctrine"; "custom"; "service"

# The service ID of a custom Lock Store, if "service" type is selected
service: null

# The DSN of the lock store, if "custom" type is selected
dsn: null
```
!!! note "Supported transports"
You can define different transports: [[= product_name_base =]] Messenger has been tested to work with Redis, MySQL, PostgreSQL.
For more information, see [Symfony Messenger documentation](https://symfony.com/doc/current/messenger.html#transports-async-queued-messages) or [Symfony Messenger tutorial](https://symfonycasts.com/screencast/messenger/install#installing-messenger).
### Start worker
Use a process manager of your choice to run the following command, or make it start together with the server:
``` bash
php bin/console messenger:consume ibexa.messenger.transport --bus=ibexa.messenger.bus --siteaccess=<OPTIONAL>`
```

In multi-repository setups, the worker process always works for a repository that you indicate by using the `--siteaccess` option, therefore you may need to run multiple workers, one for each SiteAccess.

!!! warning "Multi-repository setups"

Doctrine transport works across multiple repositories without issues, but other transports may need to be adjusted, so that queues across different repositories are not accidentally shared.

!!! note "Deploying [[= product_name_base =]] Messenger"

Additional considerations regarding the deployment of Symfony Messenger to production, which you can find in [Symfony documentation](https://symfony.com/doc/current/messenger.html#deploying-to-production) apply to [[= product_name_base =]] Messenger as well.

### Dispatch message

Dispatch a message from your code like in the following example:

``` php
[[= include_file("code_samples/background_tasks/src/Dispatcher/SomeClassThatSchedulesExecutionInTheBackground.php") =]]
```

### Register handler

Create the handler class:

``` php
[[= include_file("code_samples/background_tasks/src/MessageHandler/SomeHandler.php") =]]
```

Add a service definition to `config/services.yaml`:

``` yaml
services:
Ibexa\Bundle\Foo\Message\SomeHandler:
tags:
- name: messenger.message_handler
bus: ibexa.messenger.bus
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ page_type: landing_page
"infrastructure_and_maintenance/cache/cache",
"infrastructure_and_maintenance/clustering/clustering",
"infrastructure_and_maintenance/performance",
"infrastructure_and_maintenance/background_tasks",
"infrastructure_and_maintenance/databases",
"infrastructure_and_maintenance/environments",
"infrastructure_and_maintenance/support_and_maintenance_faq",
Expand Down
12 changes: 7 additions & 5 deletions docs/search/discounts_search_reference/discounts_criteria.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@ Search Criteria are found in the `Ibexa\Contracts\Discounts\Value\Query\Criterio

| Criterion | Description |
|---|---|
| [CreatedAtCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-CreatedAtCriterion.html) | Find discounts with given creation date|
| [CreatorCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-CreatorCriterion.html) | Find discounts created by specific users|
| [CreatedAtCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-CreatedAtCriterion.html) | Find discounts with given creation date |
| [CreatorCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-CreatorCriterion.html) | Find discounts created by specific users |
| [EndDateCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-EndDateCriterion.html) | Find discounts by their end date. For permanent discounts, the end date is set to `null` |
| [IndexedAtCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IndexedAtCriterion.html) | Find discounts based on the date and time when they were indexed |
| [IdentifierCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IdentifierCriterion.html) | Find discounts by their identifier |
| [IsEnabledCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IsEnabledCriterion.html) | Find discounts by their status|
| [IsEnabledCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-IsEnabledCriterion.html) | Find discounts by their status |
| [LogicalAnd](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-LogicalAnd.html) | Composite criterion to group multiple criterions using the AND condition |
| [LogicalOr](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-LogicalOr.html) | Composite criterion to group multiple criterions using the OR condition |
| [NameCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-NameCriterion.html) | Find discounts by their name |
| [PriorityCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-PriorityCriterion.html) | Find discounts by their priority |
| [StartDateCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-StartDateCriterion.html) | Find discounts with given start date|
| [TypeCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-TypeCriterion.html) | Find cart or catalog discounts by using constants from the [DiscountType](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountType.html) class|
| [StartDateCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-StartDateCriterion.html) | Find discounts with given start date |
| [TypeCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-TypeCriterion.html) | Find cart or catalog discounts by using constants from the [DiscountType](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountType.html) class |
| [UpdatedAtCriterion](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-Criterion-UpdatedAtCriterion.html) | Find discounts based on the date and time when they were indexed |

You can use the [FieldValueCriterion's constants](/api/php_api/php_api_reference/classes/Ibexa-Contracts-CoreSearch-Values-Query-Criterion-FieldValueCriterion.html#constants) like `FieldValueCriterion::COMPARISON_CONTAINS` or `FieldValueCriterion::COMPARISON_STARTS_WITH` to specify the operator for the condition.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Sort Clauses are found in the [`Ibexa\Contracts\Discounts\Value\Query\SortClause
| [EndDate](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-EndDate.html)| Sort by discount's end date |
| [Id](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Id.html)| Sort by discount's database ID |
| [Identifier](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Identifier.html)| Sort by discount identifier |
| [OverridePrioritization](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-OverridePrioritization.html)| Sort prioritizing discounts with discount code over automatic ones |
| [Priority](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Priority.html)| Sort by discount priority |
| [StartDate](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-StartDate.html)| Sort by discount start date |
| [Type](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-Query-SortClause-Type.html)| Sort by the place where the discount activates: catalog or cart. When sorting with ascending order, cart discounts are returned first. |
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,7 @@ nav:
- DevOps: infrastructure_and_maintenance/devops.md
- Backup: infrastructure_and_maintenance/backup.md
- Performance: infrastructure_and_maintenance/performance.md
- Background tasks: infrastructure_and_maintenance/background_tasks.md
- Environments: infrastructure_and_maintenance/environments.md
- Sessions: infrastructure_and_maintenance/sessions.md
- Logging: infrastructure_and_maintenance/logging.md
Expand Down