Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 77f642e

Browse files
feature #31204 [Messenger] ease testing and allow forking the middleware stack (nicolas-grekas)
This PR was merged into the 4.3-dev branch. Discussion ---------- [Messenger] ease testing and allow forking the middleware stack | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #31179 | License | MIT | Doc PR | - A less radical alternative than #31185 that preserves laziness and addresses the linked issue. Commits ------- 3bdf4b0 [Messenger] ease testing and allow forking the middleware stack
2 parents be80868 + 3bdf4b0 commit 77f642e

File tree

4 files changed

+121
-28
lines changed

4 files changed

+121
-28
lines changed

src/Symfony/Component/Messenger/Middleware/StackInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
/**
1515
* @author Nicolas Grekas <[email protected]>
1616
*
17+
* Implementations must be cloneable, and each clone must unstack the stack independently.
18+
*
1719
* @experimental in 4.2
1820
*/
1921
interface StackInterface

src/Symfony/Component/Messenger/Middleware/StackMiddleware.php

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,31 +20,74 @@
2020
*/
2121
class StackMiddleware implements MiddlewareInterface, StackInterface
2222
{
23-
private $middlewareIterator;
23+
private $stack;
24+
private $offset = 0;
2425

25-
public function __construct(\Iterator $middlewareIterator = null)
26+
/**
27+
* @param iterable|MiddlewareInterface[]|MiddlewareInterface|null $middlewareIterator
28+
*/
29+
public function __construct($middlewareIterator = null)
2630
{
27-
$this->middlewareIterator = $middlewareIterator;
31+
$this->stack = new MiddlewareStack();
32+
33+
if (null === $middlewareIterator) {
34+
return;
35+
}
36+
37+
if ($middlewareIterator instanceof \Iterator) {
38+
$this->stack->iterator = $middlewareIterator;
39+
} elseif ($middlewareIterator instanceof MiddlewareInterface) {
40+
$this->stack->stack[] = $middlewareIterator;
41+
} elseif (!\is_iterable($middlewareIterator)) {
42+
throw new \TypeError(sprintf('Argument 1 passed to %s() must be iterable of %s, %s given.', __METHOD__, MiddlewareInterface::class, \is_object($middlewareIterator) ? \get_class($middlewareIterator) : \gettype($middlewareIterator)));
43+
} else {
44+
$this->stack->iterator = (function () use ($middlewareIterator) {
45+
yield from $middlewareIterator;
46+
})();
47+
}
2848
}
2949

3050
public function next(): MiddlewareInterface
3151
{
32-
if (null === $iterator = $this->middlewareIterator) {
52+
if (null === $next = $this->stack->next($this->offset)) {
3353
return $this;
3454
}
35-
$iterator->next();
3655

37-
if (!$iterator->valid()) {
38-
$this->middlewareIterator = null;
56+
++$this->offset;
3957

40-
return $this;
41-
}
42-
43-
return $iterator->current();
58+
return $next;
4459
}
4560

4661
public function handle(Envelope $envelope, StackInterface $stack): Envelope
4762
{
4863
return $envelope;
4964
}
5065
}
66+
67+
/**
68+
* @internal
69+
*/
70+
class MiddlewareStack
71+
{
72+
public $iterator;
73+
public $stack = [];
74+
75+
public function next(int $offset): ?MiddlewareInterface
76+
{
77+
if (isset($this->stack[$offset])) {
78+
return $this->stack[$offset];
79+
}
80+
81+
if (null === $this->iterator) {
82+
return null;
83+
}
84+
85+
$this->iterator->next();
86+
87+
if (!$this->iterator->valid()) {
88+
return $this->iterator = null;
89+
}
90+
91+
return $this->stack[] = $this->iterator->current();
92+
}
93+
}

src/Symfony/Component/Messenger/Test/Middleware/MiddlewareTestCase.php

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Messenger\Envelope;
1616
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
1717
use Symfony\Component\Messenger\Middleware\StackInterface;
18+
use Symfony\Component\Messenger\Middleware\StackMiddleware;
1819

1920
/**
2021
* @author Nicolas Grekas <[email protected]>
@@ -25,23 +26,26 @@ abstract class MiddlewareTestCase extends TestCase
2526
{
2627
protected function getStackMock(bool $nextIsCalled = true)
2728
{
29+
if (!$nextIsCalled) {
30+
$stack = $this->createMock(StackInterface::class);
31+
$stack
32+
->expects($this->never())
33+
->method('next')
34+
;
35+
36+
return $stack;
37+
}
38+
2839
$nextMiddleware = $this->getMockBuilder(MiddlewareInterface::class)->getMock();
2940
$nextMiddleware
30-
->expects($nextIsCalled ? $this->once() : $this->never())
41+
->expects($this->once())
3142
->method('handle')
3243
->willReturnCallback(function (Envelope $envelope, StackInterface $stack): Envelope {
3344
return $envelope;
3445
})
3546
;
3647

37-
$stack = $this->createMock(StackInterface::class);
38-
$stack
39-
->expects($nextIsCalled ? $this->once() : $this->never())
40-
->method('next')
41-
->willReturn($nextMiddleware)
42-
;
43-
44-
return $stack;
48+
return new StackMiddleware($nextMiddleware);
4549
}
4650

4751
protected function getThrowingStackMock(\Throwable $throwable = null)
@@ -53,13 +57,6 @@ protected function getThrowingStackMock(\Throwable $throwable = null)
5357
->willThrowException($throwable ?? new \RuntimeException('Thrown from next middleware.'))
5458
;
5559

56-
$stack = $this->createMock(StackInterface::class);
57-
$stack
58-
->expects($this->once())
59-
->method('next')
60-
->willReturn($nextMiddleware)
61-
;
62-
63-
return $stack;
60+
return new StackMiddleware($nextMiddleware);
6461
}
6562
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[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 Symfony\Component\Messenger\Tests\Middleware;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Messenger\Envelope;
16+
use Symfony\Component\Messenger\MessageBus;
17+
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
18+
use Symfony\Component\Messenger\Middleware\StackInterface;
19+
20+
class StackMiddlewareTest extends TestCase
21+
{
22+
public function testClone()
23+
{
24+
$middleware1 = $this->getMockBuilder(MiddlewareInterface::class)->getMock();
25+
$middleware1
26+
->expects($this->once())
27+
->method('handle')
28+
->willReturnCallback(function (Envelope $envelope, StackInterface $stack): Envelope {
29+
$fork = clone $stack;
30+
31+
$stack->next()->handle($envelope, $stack);
32+
$fork->next()->handle($envelope, $fork);
33+
34+
return $envelope;
35+
})
36+
;
37+
38+
$middleware2 = $this->getMockBuilder(MiddlewareInterface::class)->getMock();
39+
$middleware2
40+
->expects($this->exactly(2))
41+
->method('handle')
42+
->willReturnCallback(function (Envelope $envelope, StackInterface $stack): Envelope {
43+
return $envelope;
44+
})
45+
;
46+
47+
$bus = new MessageBus([$middleware1, $middleware2]);
48+
49+
$bus->dispatch(new \stdClass());
50+
}
51+
}

0 commit comments

Comments
 (0)