Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 585491d

Browse filesBrowse files
committed
Move the documentation from the main PR to RST format.
1 parent 989d594 commit 585491d
Copy full SHA for 585491d

File tree

Expand file treeCollapse file tree

2 files changed

+235
-0
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+235
-0
lines changed
21.9 KB
Loading

‎components/message.rst

Copy file name to clipboard
+235Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
.. index::
2+
single: Message
3+
single: Components; Message
4+
5+
The Message Component
6+
=====================
7+
8+
The Message component helps application to send and receive messages
9+
to/from other applications or via
10+
11+
Concepts
12+
--------
13+
14+
.. image:: /_images/components/message/overview.png
15+
16+
1. **Sender**
17+
Responsible for serializing and sending the message to _something_. This something can be a message broker or a 3rd
18+
party API for example.
19+
20+
2. **Receiver**
21+
Responsible for deserializing and forwarding the messages to handler(s). This can be a message queue puller or an API
22+
endpoint for example.
23+
24+
3. **Handler**
25+
Given a received message, contains the user business logic related to the message. In practice, that is just a PHP
26+
callable.
27+
28+
Bus
29+
---
30+
31+
The bus is used to dispatch messages. MessageBus' behaviour is in its ordered middleware stack. When using
32+
the message bus with Symfony's FrameworkBundle, the following middlewares are configured for you:
33+
34+
1. `LoggingMiddleware` (log the processing of your messages)
35+
2. `SendMessageMiddleware` (enable asynchronous processing)
36+
3. `HandleMessageMiddleware` (call the registered handle)
37+
38+
use App\Message\MyMessage;
39+
40+
$result = $this->get('message_bus')->handle(new MyMessage(/* ... */));
41+
42+
Handlers
43+
--------
44+
45+
Once dispatched to the bus, messages will be handled by a "message handler". A message handler is a PHP callable
46+
(i.e. a function or an instance of a class) that will do the required processing for your message. It _might_ return a
47+
result.
48+
49+
namespace App\MessageHandler;
50+
51+
use App\Message\MyMessage;
52+
53+
class MyMessageHandler
54+
{
55+
public function __invoke(MyMessage $message)
56+
{
57+
// Message processing...
58+
}
59+
}
60+
61+
62+
<service id="App\Handler\MyMessageHandler">
63+
<tag name="message_handler" />
64+
</service>
65+
66+
**Note:** If the message cannot be guessed from the handler's type-hint, use the `handles` attribute on the tag.
67+
68+
### Asynchronous messages
69+
70+
Using the Message Component is useful to decouple your application but it also very useful when you want to do some
71+
asychronous processing. This means that your application will produce a message to a queuing system and consume this
72+
message later in the background, using a _worker_.
73+
74+
#### Adapters
75+
76+
The communication with queuing system or 3rd parties is for delegated to libraries for now. You can use one of the
77+
following adapters:
78+
79+
- [PHP Enqueue bridge](https://github.com/sroze/enqueue-bridge) to use one of their 10+ compatible queues such as
80+
RabbitMq, Amazon SQS or Google Pub/Sub.
81+
82+
Routing
83+
-------
84+
85+
When doing asynchronous processing, the key is to route the message to the right sender. As the routing is
86+
application-specific and not message-specific, the configuration can be made within the `framework.yaml`
87+
configuration file as well:
88+
89+
framework:
90+
message:
91+
routing:
92+
'My\Message\MessageAboutDoingOperationalWork': my_operations_queue_sender
93+
94+
Such configuration would only route the `MessageAboutDoingOperationalWork` message to be asynchronous, the rest of the
95+
messages would still be directly handled.
96+
97+
If you want to do route all the messages to a queue by default, you can use such configuration:
98+
99+
framework:
100+
message:
101+
routing:
102+
'My\Message\MessageAboutDoingOperationalWork': my_operations_queue_sender
103+
'*': my_default_sender
104+
105+
Note that you can also route a message to multiple senders at the same time:
106+
107+
framework:
108+
message:
109+
routing:
110+
'My\Message\AnImportantMessage': [my_default_sender, my_audit_sender]
111+
112+
Same bus received and sender
113+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
114+
115+
To allow us to receive and send messages on the same bus and prevent a loop, the message bus is equipped with the
116+
`WrapIntoReceivedMessage` received. It will wrap the received messages into `ReceivedMessage` objects and the
117+
`SendMessageMiddleware` middleware will know it should not send these messages.
118+
119+
Your own sender
120+
---------------
121+
122+
Using the `SenderInterface`, you can easily create your own message sender. Let's say you already have an
123+
`ImportantAction` message going through the message bus and handled by a handler. Now, you also want to send this
124+
message as an email.
125+
126+
1. Create your sender
127+
128+
namespace App\MessageSender;
129+
130+
use Symfony\Component\Message\SenderInterface;
131+
use App\Message\ImportantAction;
132+
133+
class ImportantActionToEmailSender implements SenderInterface
134+
{
135+
private $toEmail;
136+
private $mailer;
137+
138+
public function __construct(\Swift_Mailer $mailer, string $toEmail)
139+
{
140+
$this->mailer = $mailer;
141+
$this->toEmail = $toEmail;
142+
}
143+
144+
public function send($message)
145+
{
146+
if (!$message instanceof ImportantAction) {
147+
throw new \InvalidArgumentException(sprintf('Producer only supports "%s" messages.', ImportantAction::class));
148+
}
149+
150+
$this->mailer->send(
151+
(new \Swift_Message('Important action made'))
152+
->setTo($this->toEmail)
153+
->setBody(
154+
'<h1>Important action</h1><p>Made by '.$message->getUsername().'</p>',
155+
'text/html'
156+
)
157+
);
158+
}
159+
}
160+
161+
2. Register your sender service
162+
163+
services:
164+
App\MessageSender\ImportantActionToEmailSender:
165+
arguments:
166+
- "@mailer"
167+
- "%to_email%"
168+
169+
tags:
170+
- message.sender
171+
172+
3. Route your important message to the sender
173+
174+
framework:
175+
message:
176+
routing:
177+
'App\Message\ImportantAction': [App\MessageSender\ImportantActionToEmailSender, ~]
178+
179+
**Note:** this example shows you how you can at the same time send your message and directly handle it using a `null`
180+
(`~`) sender.
181+
182+
Your own receiver
183+
-----------------
184+
185+
A consumer is responsible of receiving messages from a source and dispatching them to the application.
186+
187+
Let's say you already proceed some "orders" on your application using a `NewOrder` message. Now you want to integrate with
188+
a 3rd party or a legacy application but you can't use an API and need to use a shared CSV file with new orders.
189+
190+
You will read this CSV file and dispatch a `NewOrder` message. All you need to do is your custom CSV consumer and Symfony will do the rest.
191+
192+
1. Create your receiver
193+
194+
namespace App\MessageReceiver;
195+
196+
use Symfony\Component\Message\ReceiverInterface;
197+
use Symfony\Component\Serializer\SerializerInterface;
198+
199+
use App\Message\NewOrder;
200+
201+
class NewOrdersFromCsvFile implements ReceiverInterface
202+
{
203+
private $serializer;
204+
private $filePath;
205+
206+
public function __construct(SerializerInteface $serializer, string $filePath)
207+
{
208+
$this->serializer = $serializer;
209+
$this->filePath = $filePath;
210+
}
211+
212+
public function receive() : \Generator
213+
{
214+
$ordersFromCsv = $this->serializer->deserialize(file_get_contents($this->filePath), 'csv');
215+
216+
foreach ($ordersFromCsv as $orderFromCsv) {
217+
yield new NewOrder($orderFromCsv['id'], $orderFromCsv['account_id'], $orderFromCsv['amount']);
218+
}
219+
}
220+
}
221+
222+
2. Register your receiver service
223+
224+
services:
225+
App\MessageReceiver\NewOrdersFromCsvFile:
226+
arguments:
227+
- "@serializer"
228+
- "%new_orders_csv_file_path%"
229+
230+
tags:
231+
- message.receiver
232+
233+
3. Use your consumer
234+
235+
$ bin/console message:consume App\MessageReceived\NewOrdersFromCsvFile

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.