Completed
Pull Request — master (#276)
by Maksim
01:50
created

AMQPBackend::getContext()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
3
/*
4
 * This file is part of the Sonata Project package.
5
 *
6
 * (c) Thomas Rabaix <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sonata\NotificationBundle\Backend;
13
14
use Interop\Amqp\AmqpConsumer;
15
use Interop\Amqp\AmqpContext;
16
use Interop\Amqp\AmqpMessage;
17
use Interop\Amqp\AmqpQueue;
18
use Interop\Amqp\AmqpTopic;
19
use Interop\Amqp\Impl\AmqpBind;
20
use PhpAmqpLib\Channel\AMQPChannel;
21
use Sonata\NotificationBundle\Consumer\ConsumerEvent;
22
use Sonata\NotificationBundle\Exception\HandlingException;
23
use Sonata\NotificationBundle\Iterator\AMQPMessageIterator;
24
use Sonata\NotificationBundle\Model\Message;
25
use Sonata\NotificationBundle\Model\MessageInterface;
26
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
27
use ZendDiagnostics\Result\Failure;
28
use ZendDiagnostics\Result\Success;
29
30
/**
31
 * Consumer side of the rabbitMQ backend.
32
 */
33
class AMQPBackend implements BackendInterface
34
{
35
    /**
36
     * @var AMQPBackendDispatcher
37
     */
38
    protected $dispatcher = null;
39
40
    /**
41
     * @var string
42
     */
43
    protected $exchange;
44
45
    /**
46
     * @var string
47
     */
48
    protected $queue;
49
50
    /**
51
     * @var string
52
     */
53
    protected $key;
54
55
    /**
56
     * @var string
57
     */
58
    protected $recover;
59
60
    /**
61
     * @var null|string
62
     */
63
    protected $deadLetterExchange;
64
65
    /**
66
     * @var null|string
67
     */
68
    protected $deadLetterRoutingKey;
69
70
    /**
71
     * @var null|int
72
     */
73
    protected $ttl;
74
75
    /**
76
     * @var null|int
77
     */
78
    private $prefetchCount;
79
80
    /**
81
     * @var AmqpConsumer
82
     */
83
    private $consumer;
84
85
    /**
86
     * @param string   $exchange
87
     * @param string   $queue
88
     * @param string   $recover
89
     * @param string   $key
90
     * @param string   $deadLetterExchange
91
     * @param string   $deadLetterRoutingKey
92
     * @param null|int $ttl
93
     */
94
    public function __construct($exchange, $queue, $recover, $key, $deadLetterExchange = null, $deadLetterRoutingKey = null, $ttl = null, $prefetchCount = null)
95
    {
96
        $this->exchange = $exchange;
97
        $this->queue = $queue;
98
        $this->recover = $recover;
99
        $this->key = $key;
100
        $this->deadLetterExchange = $deadLetterExchange;
101
        $this->deadLetterRoutingKey = $deadLetterRoutingKey;
102
        $this->ttl = $ttl;
103
        $this->prefetchCount = $prefetchCount;
104
    }
105
106
    /**
107
     * @param AMQPBackendDispatcher $dispatcher
108
     */
109
    public function setDispatcher(AMQPBackendDispatcher $dispatcher)
110
    {
111
        $this->dispatcher = $dispatcher;
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function initialize()
118
    {
119
        $args = [];
120
        if (null !== $this->deadLetterExchange) {
121
            $args['x-dead-letter-exchange'] = $this->deadLetterExchange;
122
123
            if (null !== $this->deadLetterRoutingKey) {
124
                $args['x-dead-letter-routing-key'] = $this->deadLetterRoutingKey;
125
            }
126
        }
127
128
        if (null !== $this->ttl) {
129
            $args['x-message-ttl'] = $this->ttl;
130
        }
131
132
        $queue = $this->getContext()->createQueue($this->queue);
133
        $queue->addFlag(AmqpQueue::FLAG_DURABLE);
134
        $queue->setArguments($args);
135
        $this->getContext()->declareQueue($queue);
0 ignored issues
show
Compatibility introduced by
$queue of type object<Interop\Queue\PsrQueue> is not a sub-type of object<Interop\Amqp\AmqpQueue>. It seems like you assume a child interface of the interface Interop\Queue\PsrQueue to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
136
137
        $topic = $this->getContext()->createTopic($this->exchange);
138
        $topic->setType(AmqpTopic::TYPE_DIRECT);
139
        $topic->addFlag(AmqpTopic::FLAG_DURABLE);
140
        $this->getContext()->declareTopic($topic);
0 ignored issues
show
Compatibility introduced by
$topic of type object<Interop\Queue\PsrTopic> is not a sub-type of object<Interop\Amqp\AmqpTopic>. It seems like you assume a child interface of the interface Interop\Queue\PsrTopic to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
141
142
        $this->getContext()->bind(new AmqpBind($queue, $topic, $this->key));
0 ignored issues
show
Documentation introduced by
$queue is of type object<Interop\Queue\PsrQueue>, but the function expects a object<Interop\Amqp\AmqpDestination>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$topic is of type object<Interop\Queue\PsrTopic>, but the function expects a object<Interop\Amqp\AmqpDestination>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
143
144
        if (null !== $this->deadLetterExchange && null === $this->deadLetterRoutingKey) {
145
            $deadLetterTopic = $this->getContext()->createTopic($this->deadLetterExchange);
146
            $deadLetterTopic->setType(AmqpTopic::TYPE_DIRECT);
147
            $deadLetterTopic->addFlag(AmqpTopic::FLAG_DURABLE);
148
            $this->getContext()->declareTopic($deadLetterTopic);
0 ignored issues
show
Compatibility introduced by
$deadLetterTopic of type object<Interop\Queue\PsrTopic> is not a sub-type of object<Interop\Amqp\AmqpTopic>. It seems like you assume a child interface of the interface Interop\Queue\PsrTopic to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
149
150
            $this->getContext()->bind(new AmqpBind($queue, $deadLetterTopic, $this->key));
0 ignored issues
show
Documentation introduced by
$queue is of type object<Interop\Queue\PsrQueue>, but the function expects a object<Interop\Amqp\AmqpDestination>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$deadLetterTopic is of type object<Interop\Queue\PsrTopic>, but the function expects a object<Interop\Amqp\AmqpDestination>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
151
        }
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157
    public function publish(MessageInterface $message)
158
    {
159
        $body = json_encode([
160
            'type' => $message->getType(),
161
            'body' => $message->getBody(),
162
            'createdAt' => $message->getCreatedAt()->format('U'),
163
            'state' => $message->getState(),
164
        ]);
165
166
        $amqpMessage = $this->getContext()->createMessage($body);
167
        $amqpMessage->setContentType('text/plain'); // application/json ?
168
        $amqpMessage->setTimestamp($message->getCreatedAt()->format('U'));
169
        $amqpMessage->setDeliveryMode(AmqpMessage::DELIVERY_MODE_PERSISTENT);
170
        $amqpMessage->setRoutingKey($this->key);
171
172
        $topic = $this->getContext()->createTopic($this->exchange);
173
174
        $this->getContext()->createProducer()->send($topic, $amqpMessage);
175
    }
176
177
    /**
178
     * {@inheritdoc}
179
     */
180
    public function create($type, array $body)
181
    {
182
        $message = new Message();
183
        $message->setType($type);
184
        $message->setBody($body);
185
        $message->setState(MessageInterface::STATE_OPEN);
186
187
        return $message;
188
    }
189
190
    /**
191
     * {@inheritdoc}
192
     */
193
    public function createAndPublish($type, array $body)
194
    {
195
        $this->publish($this->create($type, $body));
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201
    public function getIterator()
202
    {
203
        $context = $this->getContext();
204
205
        if (null !== $this->prefetchCount) {
206
            $context->setQos(null, $this->prefetchCount, false);
207
        }
208
209
        $this->consumer = $this->getContext()->createConsumer($this->getContext()->createQueue($this->queue));
210
        $this->consumer->setConsumerTag('sonata_notification_'.uniqid());
211
212
        return new AMQPMessageIterator($this->getChannel(), $this->consumer);
0 ignored issues
show
Compatibility introduced by
$this->consumer of type object<Interop\Queue\PsrConsumer> is not a sub-type of object<Interop\Amqp\AmqpConsumer>. It seems like you assume a child interface of the interface Interop\Queue\PsrConsumer to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug Best Practice introduced by
The return type of return new \Sonata\Notif...el(), $this->consumer); (Sonata\NotificationBundl...tor\AMQPMessageIterator) is incompatible with the return type declared by the interface Sonata\NotificationBundl...dInterface::getIterator of type Sonata\NotificationBundl...essageIteratorInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
Deprecated Code introduced by
The method Sonata\NotificationBundl...QPBackend::getChannel() has been deprecated with message: since 3.2, will be removed in 4.x

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
213
    }
214
215
    /**
216
     * {@inheritdoc}
217
     */
218
    public function handle(MessageInterface $message, EventDispatcherInterface $dispatcher)
219
    {
220
        $event = new ConsumerEvent($message);
221
222
        /** @var AmqpMessage $amqpMessage */
223
        $amqpMessage = $message->getValue('interopMessage]');
224
225
        try {
226
            $dispatcher->dispatch($message->getType(), $event);
227
228
            $this->consumer->acknowledge($amqpMessage);
229
230
            $message->setCompletedAt(new \DateTime());
231
            $message->setState(MessageInterface::STATE_DONE);
232
        } catch (HandlingException $e) {
233
            $message->setCompletedAt(new \DateTime());
234
            $message->setState(MessageInterface::STATE_ERROR);
235
236
            $this->consumer->acknowledge($amqpMessage);
237
238
            throw new HandlingException('Error while handling a message', 0, $e);
239
        } catch (\Exception $e) {
240
            $message->setCompletedAt(new \DateTime());
241
            $message->setState(MessageInterface::STATE_ERROR);
242
243
            $this->consumer->reject($amqpMessage, $this->recover);
0 ignored issues
show
Documentation introduced by
$this->recover is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
244
245
            throw new HandlingException('Error while handling a message', 0, $e);
246
        }
247
    }
248
249
    /**
250
     * {@inheritdoc}
251
     */
252
    public function getStatus()
253
    {
254
        try {
255
            $this->getContext();
256
        } catch (\Exception $e) {
257
            return new Failure($e->getMessage());
258
        }
259
260
        return new Success('Channel is running (RabbitMQ)');
261
    }
262
263
    /**
264
     * {@inheritdoc}
265
     */
266
    public function cleanup()
267
    {
268
        throw new \RuntimeException('Not implemented');
269
    }
270
271
    /**
272
     * @deprecated since 3.2, will be removed in 4.x
273
     *
274
     * @return AMQPChannel
275
     */
276
    protected function getChannel()
277
    {
278
        if (null === $this->dispatcher) {
279
            throw new \RuntimeException('Unable to retrieve AMQP channel without dispatcher.');
280
        }
281
282
        return $this->dispatcher->getChannel();
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\NotificationBundl...ispatcher::getChannel() has been deprecated with message: since 3.2, will be removed in 4.x

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
283
    }
284
285
    /**
286
     * @return AmqpContext
287
     */
288
    protected function getContext()
289
    {
290
        if (null === $this->dispatcher) {
291
            throw new \RuntimeException('Unable to retrieve AMQP context without dispatcher.');
292
        }
293
294
        return $this->dispatcher->getContext();
295
    }
296
}
297