|
| 1 | +# Emitters |
| 2 | + |
| 3 | +Emitters are used to _emit_ a [PSR-7](https://www.php-fig.org/psr/psr-7) |
| 4 | +response. This should generally happen when running under a traditional PHP |
| 5 | +server API that uses output buffers, such as Apache or php-fpm. |
| 6 | + |
| 7 | +Emitters are described by `Zend\HttpHandlerRunner\Emitter\EmitterInterface`: |
| 8 | + |
| 9 | +```php |
| 10 | +use Psr\Http\Message\ResponseInterface; |
| 11 | + |
| 12 | +interface EmitterInterface |
| 13 | +{ |
| 14 | + public function emit(ResponseInterface $response) : bool; |
| 15 | +} |
| 16 | +``` |
| 17 | + |
| 18 | +Typically, such emitters will perform the following: |
| 19 | + |
| 20 | +- Emit a response status line. |
| 21 | +- Emit all response headers. |
| 22 | +- Emit the response body. |
| 23 | + |
| 24 | +(The first two items may be swapped in order; many SAPIs allow emitting multiple |
| 25 | +status lines, and will use the last one present. As such, most implementations |
| 26 | +will emit the status line after the headers to ensure the correct one is emitted |
| 27 | +by the SAPI.) |
| 28 | + |
| 29 | +The `emit()` method allows returning a boolean. This value can be checked to |
| 30 | +determine if the emitter was able to emit the response. This capability is used |
| 31 | +by the provided `EmitterStack` to allow composing multiple emitters that can |
| 32 | +introspect the response to determine whether or not they are capable of emitting |
| 33 | +it. |
| 34 | + |
| 35 | +## SapiEmitter |
| 36 | + |
| 37 | +`Zend\HttpHandlerRunner\Emitter\SapiEmitter` accepts the response instance, and |
| 38 | +uses the built-in PHP function `header()` to emit both the headers as well as |
| 39 | +the status line. It then uses `echo` to emit the response body. |
| 40 | + |
| 41 | +Internally, it also does a number of verifications: |
| 42 | + |
| 43 | +- If headers have been previously sent, it will raise an exception. |
| 44 | +- If output has been previously sent, it will raise an exception. |
| 45 | + |
| 46 | +These are performed in order to ensure the integrity of the response emitted. |
| 47 | + |
| 48 | +It also filters header names to normalize them; this is done in part to ensure |
| 49 | +that if multiple headers of the same name are emitted, the SAPI will report them |
| 50 | +correctly. |
| 51 | + |
| 52 | +This emitter can _always_ handle a response, and thus _always_ returns true. |
| 53 | + |
| 54 | +## SapiStreamEmitter |
| 55 | + |
| 56 | +`Zend\HttpHandlerRunner\Emitter\SapiStreamEmitter` behaves similarly to the |
| 57 | +`SapiEmitter`, with two key differences: |
| 58 | + |
| 59 | +- It allows emitting a a _content range_, if a `Content-Range` header is |
| 60 | + specified in the response. |
| 61 | +- It will iteratively emit a range of bytes from the response, based on the |
| 62 | + buffer length provided to the emitter during construction. This is |
| 63 | + particularly useful when returning _files_ or _downloads_. |
| 64 | + |
| 65 | +The emitter accepts an integer argument to the constructor, indicating the |
| 66 | +maximum buffer length; by default, this is 8192 bytes. |
| 67 | + |
| 68 | +This emitter can _always_ handle a response, and thus _always_ returns true. |
| 69 | + |
| 70 | +## EmitterStack |
| 71 | + |
| 72 | +`Zend\HttpHandlerRunner\Emitter\EmitterStack` allows providing a |
| 73 | +last-in-first-out (LIFO) stack of emitters instead of a single emitter. If an |
| 74 | +emitter is incapable of handling the response and returns `false`, the stack |
| 75 | +will move to the next emitter. If an emitter returns `true`, the stack |
| 76 | +short-circuits and immediately returns. |
| 77 | + |
| 78 | +The `EmitterStack` extends `SplStack`, and thus allows you to add emitters using |
| 79 | +any of the methods that class defines; we recommend adding them in LIFO order |
| 80 | +using `push()`: |
| 81 | + |
| 82 | +```php |
| 83 | +$stack->push($last); |
| 84 | +$stack->push($second); |
| 85 | +$stack->push($first); |
| 86 | +``` |
| 87 | + |
| 88 | +### Conditionally using the SapiStreamEmitter |
| 89 | + |
| 90 | +The `SapiStreamEmitter` is capable of emitting any response. However, for |
| 91 | +in-memory responses, you may want to use the more efficient `SapiEmitter`. How |
| 92 | +can you do this? |
| 93 | + |
| 94 | +One way is to check for response artifacts that indicate a file download, such |
| 95 | +as the `Content-Disposition` or `Content-Range` headers; if those headers are |
| 96 | +not present, you could return `false` from the emitter, and otherwise continue. |
| 97 | +You could achieve this by decorating the `SapiStreamEmitter`: |
| 98 | + |
| 99 | +```php |
| 100 | +$sapiStreamEmitter = new SapiStreamEmitter($maxBufferLength); |
| 101 | +$conditionalEmitter = new class ($sapiStreamEmitter) implements EmitterInterface { |
| 102 | + private $emitter; |
| 103 | + |
| 104 | + public function __construct(EmitterInterface $emitter) |
| 105 | + { |
| 106 | + $this->emitter = $emitter; |
| 107 | + } |
| 108 | + |
| 109 | + public function emit(ResponseInterface) : bool |
| 110 | + { |
| 111 | + if (! $response->hasHeader('Content-Disposition') |
| 112 | + && ! $response->hasHeader('Content-Range') |
| 113 | + ) { |
| 114 | + return false; |
| 115 | + } |
| 116 | + return $this->emitter->emit($response); |
| 117 | + } |
| 118 | +}; |
| 119 | + |
| 120 | +$stack = new EmitterStack(); |
| 121 | +$stack->push(new SapiEmitter()); |
| 122 | +$stack->push($conditionalEmitter); |
| 123 | +``` |
| 124 | + |
| 125 | +In this way, you can have the best of both worlds, using the memory-efficient |
| 126 | +`SapiStreamEmitter` for large file downloads or streaming buffers, and the |
| 127 | +general-purpose `SapiEmitter` for everything else. |
0 commit comments