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

[Messenger] ease testing and allow forking the middleware stack #31204

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 26, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions 2 src/Symfony/Component/Messenger/Middleware/StackInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* Implementations must be cloneable, and each clone must unstack the stack independently.
*
* @experimental in 4.2
*/
interface StackInterface
Expand Down
65 changes: 54 additions & 11 deletions 65 src/Symfony/Component/Messenger/Middleware/StackMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,74 @@
*/
class StackMiddleware implements MiddlewareInterface, StackInterface
{
private $middlewareIterator;
private $stack;
private $offset = 0;

public function __construct(\Iterator $middlewareIterator = null)
/**
* @param iterable|MiddlewareInterface[]|MiddlewareInterface|null $middlewareIterator
*/
public function __construct($middlewareIterator = null)
{
$this->middlewareIterator = $middlewareIterator;
$this->stack = new MiddlewareStack();

if (null === $middlewareIterator) {
return;
}

if ($middlewareIterator instanceof \Iterator) {
$this->stack->iterator = $middlewareIterator;
} elseif ($middlewareIterator instanceof MiddlewareInterface) {
$this->stack->stack[] = $middlewareIterator;
} elseif (!\is_iterable($middlewareIterator)) {
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)));
} else {
$this->stack->iterator = (function () use ($middlewareIterator) {
yield from $middlewareIterator;
})();
}
}

public function next(): MiddlewareInterface
{
if (null === $iterator = $this->middlewareIterator) {
if (null === $next = $this->stack->next($this->offset)) {
return $this;
}
$iterator->next();

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

return $this;
}

return $iterator->current();
return $next;
}

public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
return $envelope;
}
}

/**
* @internal
*/
class MiddlewareStack
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't you simply drop the iterator, store middleware as array in StackMiddleware and advance the pointer manually?

Copy link
Member Author

Choose a reason for hiding this comment

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

That would defeat laziness, which is a desired property.

{
public $iterator;
public $stack = [];

public function next(int $offset): ?MiddlewareInterface
{
if (isset($this->stack[$offset])) {
return $this->stack[$offset];
}

if (null === $this->iterator) {
return null;
}

$this->iterator->next();

if (!$this->iterator->valid()) {
return $this->iterator = null;
}

return $this->stack[] = $this->iterator->current();
Copy link
Member

@weaverryan weaverryan Apr 23, 2019

Choose a reason for hiding this comment

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

I had to stare at this for a minute. When you clone the StackMiddleware, each clone will still maintain this same, one instance of this MiddlewareStack, but will now maintain their own StackMiddleware.offset property (so, that property will move independently). The first StackMiddleware that uses an item from the iterator will use it, but store it on the stack so that it's available when the other StackMiddleware asks for that same $offset.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;
use Symfony\Component\Messenger\Middleware\StackMiddleware;

/**
* @author Nicolas Grekas <p@tchwork.com>
Expand All @@ -25,23 +26,26 @@ abstract class MiddlewareTestCase extends TestCase
{
protected function getStackMock(bool $nextIsCalled = true)
{
if (!$nextIsCalled) {
$stack = $this->createMock(StackInterface::class);
$stack
->expects($this->never())
->method('next')
;

return $stack;
}

$nextMiddleware = $this->getMockBuilder(MiddlewareInterface::class)->getMock();
$nextMiddleware
->expects($nextIsCalled ? $this->once() : $this->never())
->expects($this->once())
->method('handle')
->willReturnCallback(function (Envelope $envelope, StackInterface $stack): Envelope {
return $envelope;
})
;

$stack = $this->createMock(StackInterface::class);
$stack
->expects($nextIsCalled ? $this->once() : $this->never())
->method('next')
->willReturn($nextMiddleware)
;

return $stack;
return new StackMiddleware($nextMiddleware);
}

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

$stack = $this->createMock(StackInterface::class);
$stack
->expects($this->once())
->method('next')
->willReturn($nextMiddleware)
;

return $stack;
return new StackMiddleware($nextMiddleware);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Messenger\Tests\Middleware;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBus;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;

class StackMiddlewareTest extends TestCase
{
public function testClone()
{
$middleware1 = $this->getMockBuilder(MiddlewareInterface::class)->getMock();
$middleware1
->expects($this->once())
->method('handle')
->willReturnCallback(function (Envelope $envelope, StackInterface $stack): Envelope {
$fork = clone $stack;

$stack->next()->handle($envelope, $stack);
$fork->next()->handle($envelope, $fork);

return $envelope;
})
;

$middleware2 = $this->getMockBuilder(MiddlewareInterface::class)->getMock();
$middleware2
->expects($this->exactly(2))
->method('handle')
->willReturnCallback(function (Envelope $envelope, StackInterface $stack): Envelope {
return $envelope;
})
;

$bus = new MessageBus([$middleware1, $middleware2]);

$bus->dispatch(new \stdClass());
}
}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.