Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

Commit 13d6cb4

Browse files
committed
Imports EmitterStack from zend-expressive
This patch imports the EmitterStack originally defined in zend-expressive. It makes the following changes: - Type-hints against package EmitterInterface. - Updates emit() logic to test for boolean true and false values, per interface changes in this package. - Introduces InvalidEmitterException class for indicating invalid emitters added to the EmitterStack.
1 parent d2cd6b5 commit 13d6cb4

File tree

3 files changed

+280
-0
lines changed

3 files changed

+280
-0
lines changed

src/Emitter/EmitterStack.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
/**
3+
* @see https://github.com/zendframework/zend-httphandlerrunner for the canonical source repository
4+
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (https://www.zend.com)
5+
* @license https://github.com/zendframework/zend-httphandlerrunner/blob/master/LICENSE.md New BSD License
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Zend\HttpHandlerRunner\Emitter;
11+
12+
use Psr\Http\Message\ResponseInterface;
13+
use SplStack;
14+
use Zend\HttpHandlerRunner\Emitter\EmitterInterface;
15+
use Zend\HttpHandlerRunner\Exception;
16+
17+
/**
18+
* Provides an EmitterInterface implementation that acts as a stack of Emitters.
19+
*
20+
* The implementations emit() method iterates itself.
21+
*
22+
* When iterating the stack, the first emitter to return a boolean
23+
* true value will short-circuit iteration.
24+
*/
25+
class EmitterStack extends SplStack implements EmitterInterface
26+
{
27+
/**
28+
* Emit a response
29+
*
30+
* Loops through the stack, calling emit() on each; any that return a
31+
* boolean true value will short-circuit, skipping any remaining emitters
32+
* in the stack.
33+
*
34+
* As such, return a boolean false value from an emitter to indicate it
35+
* cannot emit the response, allowing the next emitter to try.
36+
*/
37+
public function emit(ResponseInterface $response) : bool
38+
{
39+
foreach ($this as $emitter) {
40+
if (false !== $emitter->emit($response)) {
41+
return true;
42+
}
43+
}
44+
45+
return false;
46+
}
47+
48+
/**
49+
* Set an emitter on the stack by index.
50+
*
51+
* @param mixed $index
52+
* @param EmitterInterface $emitter
53+
* @return void
54+
* @throws InvalidArgumentException if not an EmitterInterface instance
55+
*/
56+
public function offsetSet($index, $emitter)
57+
{
58+
$this->validateEmitter($emitter);
59+
parent::offsetSet($index, $emitter);
60+
}
61+
62+
/**
63+
* Push an emitter to the stack.
64+
*
65+
* @param EmitterInterface $emitter
66+
* @return void
67+
* @throws InvalidArgumentException if not an EmitterInterface instance
68+
*/
69+
public function push($emitter)
70+
{
71+
$this->validateEmitter($emitter);
72+
parent::push($emitter);
73+
}
74+
75+
/**
76+
* Unshift an emitter to the stack.
77+
*
78+
* @param EmitterInterface $emitter
79+
* @return void
80+
* @throws InvalidArgumentException if not an EmitterInterface instance
81+
*/
82+
public function unshift($emitter)
83+
{
84+
$this->validateEmitter($emitter);
85+
parent::unshift($emitter);
86+
}
87+
88+
/**
89+
* Validate that an emitter implements EmitterInterface.
90+
*
91+
* @param mixed $emitter
92+
* @throws Exception\InvalidEmitterException for non-emitter instances
93+
*/
94+
private function validateEmitter($emitter) : void
95+
{
96+
if (! $emitter instanceof EmitterInterface) {
97+
throw Exception\InvalidEmitterException::forEmitter($emitter);
98+
}
99+
}
100+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
/**
3+
* @see https://github.com/zendframework/zend-httphandlerrunner for the canonical source repository
4+
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (https://www.zend.com)
5+
* @license https://github.com/zendframework/zend-httphandlerrunner/blob/master/LICENSE.md New BSD License
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Zend\HttpHandlerRunner\Exception;
11+
12+
use InvalidArgumentException;
13+
use Zend\HttpHandlerRunner\Emitter;
14+
15+
class InvalidEmitterException extends InvalidArgumentException implements ExceptionInterface
16+
{
17+
/**
18+
* @var mixed $emitter Invalid emitter type
19+
*/
20+
public static function forEmitter($emitter) : self
21+
{
22+
return new self(sprintf(
23+
'%s can only compose %s implementations; received %s',
24+
Emitter\EmitterStack::class,
25+
Emitter\EmitterInterface::class,
26+
is_object($emitter) ? get_class($emitter) : gettype($emitter)
27+
));
28+
}
29+
}

test/Emitter/EmitterStackTest.php

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
2+
/**
3+
* @see https://github.com/zendframework/zend-httphandlerrunner for the canonical source repository
4+
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (https://www.zend.com)
5+
* @license https://github.com/zendframework/zend-httphandlerrunner/blob/master/LICENSE.md New BSD License
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace ZendTest\HttpHandlerRunner\Emitter;
11+
12+
use PHPUnit\Framework\TestCase;
13+
use Prophecy\Argument;
14+
use Psr\Http\Message\ResponseInterface;
15+
use SplStack;
16+
use Zend\HttpHandlerRunner\Emitter\EmitterInterface;
17+
use Zend\HttpHandlerRunner\Emitter\EmitterStack;
18+
use Zend\HttpHandlerRunner\Exception;
19+
20+
/**
21+
* @covers Zend\HttpHandlerRunner\Emitter\EmitterStack
22+
*/
23+
class EmitterStackTest extends TestCase
24+
{
25+
/** @var EmitterStack */
26+
private $emitter;
27+
28+
public function setUp()
29+
{
30+
$this->emitter = new EmitterStack();
31+
}
32+
33+
public function testIsAnSplStack()
34+
{
35+
$this->assertInstanceOf(SplStack::class, $this->emitter);
36+
}
37+
38+
public function testIsAnEmitterImplementation()
39+
{
40+
$this->assertInstanceOf(EmitterInterface::class, $this->emitter);
41+
}
42+
43+
public function nonEmitterValues()
44+
{
45+
return [
46+
'null' => [null],
47+
'true' => [true],
48+
'false' => [false],
49+
'zero' => [0],
50+
'int' => [1],
51+
'zero-float' => [0.0],
52+
'float' => [1.1],
53+
'string' => ['emitter'],
54+
'array' => [[$this->prophesize(EmitterInterface::class)->reveal()]],
55+
'object' => [(object) []],
56+
];
57+
}
58+
59+
/**
60+
* @dataProvider nonEmitterValues
61+
*
62+
* @param mixed $value
63+
*/
64+
public function testCannotPushNonEmitterToStack($value)
65+
{
66+
$this->expectException(Exception\InvalidEmitterException::class);
67+
$this->emitter->push($value);
68+
}
69+
70+
/**
71+
* @dataProvider nonEmitterValues
72+
*
73+
* @param mixed $value
74+
*/
75+
public function testCannotUnshiftNonEmitterToStack($value)
76+
{
77+
$this->expectException(Exception\InvalidEmitterException::class);
78+
$this->emitter->unshift($value);
79+
}
80+
81+
/**
82+
* @dataProvider nonEmitterValues
83+
*
84+
* @param mixed $value
85+
*/
86+
public function testCannotSetNonEmitterToSpecificIndex($value)
87+
{
88+
$this->expectException(Exception\InvalidEmitterException::class);
89+
$this->emitter->offsetSet(0, $value);
90+
}
91+
92+
public function testOffsetSetReplacesExistingValue()
93+
{
94+
$first = $this->prophesize(EmitterInterface::class);
95+
$replacement = $this->prophesize(EmitterInterface::class);
96+
$this->emitter->push($first->reveal());
97+
$this->emitter->offsetSet(0, $replacement->reveal());
98+
$this->assertSame($replacement->reveal(), $this->emitter->pop());
99+
}
100+
101+
public function testUnshiftAddsNewEmitter()
102+
{
103+
$first = $this->prophesize(EmitterInterface::class);
104+
$second = $this->prophesize(EmitterInterface::class);
105+
$this->emitter->push($first->reveal());
106+
$this->emitter->unshift($second->reveal());
107+
$this->assertSame($first->reveal(), $this->emitter->pop());
108+
}
109+
110+
public function testEmitLoopsThroughEmittersUntilOneReturnsTrueValue()
111+
{
112+
$first = $this->prophesize(EmitterInterface::class);
113+
$first->emit()->shouldNotBeCalled();
114+
115+
$second = $this->prophesize(EmitterInterface::class);
116+
$second->emit(Argument::type(ResponseInterface::class))
117+
->willReturn(true);
118+
119+
$third = $this->prophesize(EmitterInterface::class);
120+
$third->emit(Argument::type(ResponseInterface::class))
121+
->willReturn(false);
122+
123+
$this->emitter->push($first->reveal());
124+
$this->emitter->push($second->reveal());
125+
$this->emitter->push($third->reveal());
126+
127+
$response = $this->prophesize(ResponseInterface::class);
128+
129+
$this->assertTrue($this->emitter->emit($response->reveal()));
130+
}
131+
132+
public function testEmitReturnsFalseIfLastEmmitterReturnsFalse()
133+
{
134+
$first = $this->prophesize(EmitterInterface::class);
135+
$first->emit(Argument::type(ResponseInterface::class))
136+
->willReturn(false);
137+
138+
$this->emitter->push($first->reveal());
139+
140+
$response = $this->prophesize(ResponseInterface::class);
141+
142+
$this->assertFalse($this->emitter->emit($response->reveal()));
143+
}
144+
145+
public function testEmitReturnsFalseIfNoEmittersAreComposed()
146+
{
147+
$response = $this->prophesize(ResponseInterface::class);
148+
149+
$this->assertFalse($this->emitter->emit($response->reveal()));
150+
}
151+
}

0 commit comments

Comments
 (0)