{"action":"created","issue":{"url":"https://api.github.com/repos/zendframework/zf2/issues/7568","labels_url":"https://api.github.com/repos/zendframework/zf2/issues/7568/labels{/name}","comments_url":"https://api.github.com/repos/zendframework/zf2/issues/7568/comments","events_url":"https://api.github.com/repos/zendframework/zf2/issues/7568/events","html_url":"https://github.com/zendframework/zf2/issues/7568","id":84366650,"number":7568,"title":"Help on responding a Zend\\Http\\Response\\Stream from a Strategy on EVENT_RESPONSE","user":{"login":"berturion","id":4692881,"avatar_url":"https://avatars.githubusercontent.com/u/4692881?v=3","gravatar_id":"","url":"https://api.github.com/users/berturion","html_url":"https://github.com/berturion","followers_url":"https://api.github.com/users/berturion/followers","following_url":"https://api.github.com/users/berturion/following{/other_user}","gists_url":"https://api.github.com/users/berturion/gists{/gist_id}","starred_url":"https://api.github.com/users/berturion/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/berturion/subscriptions","organizations_url":"https://api.github.com/users/berturion/orgs","repos_url":"https://api.github.com/users/berturion/repos","events_url":"https://api.github.com/users/berturion/events{/privacy}","received_events_url":"https://api.github.com/users/berturion/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":2,"created_at":"2015-06-03T05:13:11Z","updated_at":"2015-06-05T23:20:25Z","closed_at":null,"body":"I Created my own Model \"FileStreamModel\" with a strategy in charge of responding a Stream, according to FileStreamModel options (like the file path and other things).\r\n\r\nMy goal is to simplify the controller action in this way:\r\n\r\n    public function viewPdfAction() {\r\n        $fsm = new FileStreamModel();\r\n        $fsm->setContentDisposition(FileStreamModel::CONTENT_DISPOSITION_INLINE);\r\n        $fsm->setContentType('application/pdf');\r\n        $fsm->setFilePath('chemin/vers/mon/fichier.pdf');\r\n        return $fsm;\r\n    }\r\n\r\nMy FileStreamModel looks like this:\r\n\r\n    <?php\r\n    namespace Users\\View\\Model;\r\n    use Zend\\View\\Model\\ViewModel;\r\n    class FileStreamModel extends ViewModel\r\n    {\r\n        const CONTENT_DISPOSITION_INLINE = 'inline';\r\n        const CONTENT_DISPOSITION_ATTACHMENT = 'attachment';\r\n        const OPT_CONTENT_TYPE = 'content-type';\r\n        const OPT_CONTENT_DISPOSITION = 'content-disposition';\r\n        const OPT_FILE_PATH = 'filepath';\r\n        protected $captureTo = null;\r\n        protected $terminate = true;\r\n        public function getContentType($default = null)\r\n        {\r\n            return $this->getOption(self::OPT_CONTENT_TYPE, $default);\r\n        }\r\n        public function setContentType($contentType)\r\n        {\r\n            return $this->setOption(self::OPT_CONTENT_TYPE, $contentType);\r\n        }\r\n        public function getContentDisposition($default = self::CONTENT_DISPOSITION_INLINE)\r\n        {\r\n            return $this->getOption(self::OPT_CONTENT_DISPOSITION, $default);\r\n        }\r\n        public function setContentDisposition($contentDisposition)\r\n        {\r\n            return $this->setOption(self::OPT_CONTENT_DISPOSITION, $contentDisposition);\r\n        }\r\n        public function getFilePath($default = null)\r\n        {\r\n            return $this->getOption(self::OPT_FILE_PATH, $default);\r\n        }\r\n        public function setFilePath($filename)\r\n        {\r\n            return $this->setOption(self::OPT_FILE_PATH, $filename);\r\n        }\r\n    }\r\n\r\nI have a renderer, I'm not sure it is necessary because it does nothing, but here it is:\r\n\r\n    <?php\r\n    namespace Users\\View\\Renderer;\r\n    use Zend\\View\\Renderer\\RendererInterface as Renderer;\r\n    use Zend\\View\\Resolver\\ResolverInterface as Resolver;    \r\n    class FileStreamRenderer implements Renderer\r\n    {\r\n        private $resolver = null;\r\n        public function getEngine()\r\n        {\r\n            return null;\r\n        }\r\n        public function render($nameOrModel, $values = null)\r\n        {\r\n            return null;\r\n        }\r\n        public function setResolver(Resolver $resolver)\r\n        {\r\n            $this->resolver = $resolver;\r\n            return $this;\r\n        }\r\n    }\r\n\r\nThe most important class is my Strategy. The response is processed on EVENT_RESPONSE when the model is a FileStreamModel. In that case, I set a new response with new headers in the ViewEvent. The response is of type `Zend\\Http\\Response\\Stream`:\r\n\r\n    <?php\r\n    namespace Users\\View\\Strategy;\r\n    use Zend\\EventManager\\EventManagerInterface;\r\n    use Zend\\EventManager\\ListenerAggregateInterface;\r\n    use Zend\\View\\ViewEvent;\r\n    use Users\\View\\Model\\FileStreamModel;\r\n    use Zend\\Http\\Response\\Stream;\r\n    use Users\\View\\Renderer\\FileStreamRenderer;\r\n    use Zend\\Http\\Headers;\r\n    class FileStreamStrategy implements ListenerAggregateInterface\r\n    {\r\n        protected $renderer;\r\n        protected $listeners = array();\r\n        public function __construct(FileStreamRenderer $fileStreamRenderer) {\r\n            $this->renderer = $fileStreamRenderer;\r\n        }\r\n        public function attach(EventManagerInterface $events, $priority = 1)\r\n        {\r\n            $this->listeners[] = $events->attach(ViewEvent::EVENT_RENDERER, array($this, 'selectRenderer'), $priority);\r\n            $this->listeners[] = $events->attach(ViewEvent::EVENT_RESPONSE, array($this, 'injectResponse'), $priority);\r\n        }\r\n        public function selectRenderer(ViewEvent $e)\r\n        {\r\n            $model = $e->getModel();\r\n            if ($model instanceof FileStreamModel) {\r\n                return $this->renderer;\r\n            }\r\n            return;\r\n        }\r\n        public function detach(EventManagerInterface $events)\r\n        {\r\n            foreach ($this->listeners as $index => $listener) {\r\n                if ($events->detach($listener)) {\r\n                    unset($this->listeners[$index]);\r\n                }\r\n            }\r\n        }\r\n        public function injectResponse(ViewEvent $e)\r\n        {\r\n            /* @var $model FileStreamModel */\r\n            $model = $e->getModel();\r\n            if (!($model instanceof FileStreamModel)) {\r\n                return;\r\n            }\r\n            $filePath = $model->getFilePath();\r\n            $contentDisposition = $model->getContentDisposition();\r\n            $headers = new Headers();\r\n            $headers->addHeaderLine('Content-Disposition', $contentDisposition.'; filename=\"' . basename($filePath).'\"');\r\n            $headers->addHeaderLine('Content-Type', $model->getContentType());\r\n            $headers->addHeaderLine('Content-Length', filesize($filePath));\r\n            $headers->addHeaderLine('Expires', '@0');\r\n            $headers->addHeaderLine('Cache-Control', 'must-revalidate');\r\n            $headers->addHeaderLine('Pragma', 'public');\r\n\r\n            $response = new Stream();\r\n            $response->setHeaders($headers);\r\n            $response->setStatusCode(200);\r\n            $response->setStreamName(basename($filePath));\r\n            $response->setStream(fopen($filePath, 'r'));\r\n            $e->setResponse($response);\r\n        }\r\n    }\r\n\r\nHere are my factories:\r\n\r\n    <?php\r\n    namespace Users\\Mvc\\Service;\r\n    use Zend\\ServiceManager\\FactoryInterface;\r\n    use Zend\\ServiceManager\\ServiceLocatorInterface;\r\n    use Users\\View\\Renderer\\FileStreamRenderer;\r\n    class ViewFileStreamRendererFactory implements FactoryInterface\r\n    {\r\n        public function createService(ServiceLocatorInterface $serviceLocator)\r\n        {\r\n            $viewManager = $serviceLocator->get('ViewManager');\r\n            $fileStreamRenderer = new FileStreamRenderer();\r\n            $fileStreamRenderer->setResolver($viewManager->getResolver());\r\n            return $fileStreamRenderer;\r\n        }\r\n    }\r\n\r\n    <?php\r\n    namespace Users\\Mvc\\Service;\r\n    use Zend\\ServiceManager\\FactoryInterface;\r\n    use Zend\\ServiceManager\\ServiceLocatorInterface;\r\n    use Users\\View\\Strategy\\FileStreamStrategy;\r\n    class ViewFileStreamStrategyFactory implements FactoryInterface\r\n    {\r\n        public function createService(ServiceLocatorInterface $serviceLocator)\r\n        {\r\n            $fsRenderer = $serviceLocator->get('ViewFileStreamRenderer');\r\n            $fsStrategy = new FileStreamStrategy($fsRenderer);\r\n            return $fsStrategy;\r\n        }\r\n    }\r\n\r\nDeclarations on `module.config.php`:\r\n\r\n    'service_manager' => array(\r\n        'factories' => array(\r\n            'ViewFileStreamStrategy' => 'Users\\Mvc\\Service\\ViewFileStreamStrategyFactory',\r\n            'ViewFileStreamRenderer' => 'Users\\Mvc\\Service\\ViewFileStreamRendererFactory'\r\n        )\r\n    ),\r\n    'view_manager' => array(\r\n        'strategies' => array(\r\n            'ViewFileStreamStrategy'\r\n        )\r\n    )\r\n\r\nWhile debugging, I see that the strategy is called and sets the response. But my result in the browser is a blank page with wrong headers, no content at all.\r\n\r\nI obtained a 'better' result by getting the headers from the response (instead of setting new ones) and populating them. In that case, ma browser (Firefox) opens its PDF Reader and waits for a content that never comes.\r\n\r\nI think it's because I set a new response from the ViewEvent instead of using the one in there. But I need the response to be a `Zend\\Http\\Response\\Stream` object, so I don't know how to do differently.\r\n\r\nI checked that my PDF exists. I use 'realpath' to get it. And I tested a 'classic' action by returning the Stream response from the Action, the PDF file is correctly displayed in that case.\r\n\r\nCan someone show me the way to respond a Stream from the Strategy ?"},"comment":{"url":"https://api.github.com/repos/zendframework/zf2/issues/comments/109479602","html_url":"https://github.com/zendframework/zf2/issues/7568#issuecomment-109479602","issue_url":"https://api.github.com/repos/zendframework/zf2/issues/7568","id":109479602,"user":{"login":"sasezaki","id":42755,"avatar_url":"https://avatars.githubusercontent.com/u/42755?v=3","gravatar_id":"","url":"https://api.github.com/users/sasezaki","html_url":"https://github.com/sasezaki","followers_url":"https://api.github.com/users/sasezaki/followers","following_url":"https://api.github.com/users/sasezaki/following{/other_user}","gists_url":"https://api.github.com/users/sasezaki/gists{/gist_id}","starred_url":"https://api.github.com/users/sasezaki/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sasezaki/subscriptions","organizations_url":"https://api.github.com/users/sasezaki/orgs","repos_url":"https://api.github.com/users/sasezaki/repos","events_url":"https://api.github.com/users/sasezaki/events{/privacy}","received_events_url":"https://api.github.com/users/sasezaki/received_events","type":"User","site_admin":false},"created_at":"2015-06-05T23:20:25Z","updated_at":"2015-06-05T23:20:25Z","body":"I think prolic's [HumusStreamResponseSender](https://github.com/prolic/HumusStreamResponseSender) module is helpful, please try."}}