Consultez la FAQ sur le ZF avant de poster une question
Vous n'êtes pas identifié.
Pages: 1
Bonjour, en prenant exemple sur le module DOMPDFModule, je souhaite créer un Model "FileStreamModel" et une Strategy en adéquation qui renverrait le Stream d'un fichier directement dans la réponse justement lorsque c'est un FileStreamModel qui est renvoyé à la fin de mon action.
Le but est d'arriver à cela dans mon action, sans avoir à manipuler l'objet response et ses headers depuis le controller :
[lang=php] public function viewPdfAction() { $fsm = new FileStreamModel(); $fsm->setContentDisposition(FileStreamModel::CONTENT_DISPOSITION_INLINE); $fsm->setContentType('application/octet-stream'); $fsm->setFilePath('chemin/vers/mon/fichier.pdf'); return $fsm; }
Aujourd'hui, mon FileStreamModel ressemble à cela (j'ai fait des méthodes pour accéder aux options requises plus facilement, c'est discutable, mais là n'est pas la question, enfin moi j'aime bien en tout cas) :
[lang=php]<?php namespace Users\View\Model; use Zend\View\Model\ViewModel; class FileStreamModel extends ViewModel { const CONTENT_DISPOSITION_INLINE = 'inline'; const CONTENT_DISPOSITION_ATTACHMENT = 'attachment'; const OPT_CONTENT_TYPE = 'content-type'; const OPT_CONTENT_DISPOSITION = 'content-disposition'; const OPT_FILE_PATH = 'filepath'; /** * FileStream probably won't need to be captured into a * a parent container by default. * * @var string */ protected $captureTo = null; /** * FileStream is usually terminal * * @var bool */ protected $terminate = true; public function getContentType($default = null) { return $this->getOption(self::OPT_CONTENT_TYPE, $default); } public function setContentType($contentType) { return $this->setOption(self::OPT_CONTENT_TYPE, $contentType); } public function getContentDisposition($default = self::CONTENT_DISPOSITION_INLINE) { return $this->getOption(self::OPT_CONTENT_DISPOSITION, $default); } public function setContentDisposition($contentDisposition) { return $this->setOption(self::OPT_CONTENT_DISPOSITION, $contentDisposition); } public function getFilePath($default = null) { return $this->getOption(self::OPT_FILE_PATH, $default); } public function setFilePath($filename) { return $this->setOption(self::OPT_FILE_PATH, $filename); } }
J'ai fait ensuite un Renderer mais qui ne render rien en fait puisque c'est un Stream que je vais retourner directement dans la réponse. Du coup, je ne suis même pas sûr que cela soit nécessaire. Mais le voici :
[lang=php]<?php namespace Users\View\Renderer; use Zend\View\Renderer\RendererInterface as Renderer; use Zend\View\Resolver\ResolverInterface as Resolver; class FileStreamRenderer implements Renderer { private $resolver = null; public function getEngine() { return null; } public function render($nameOrModel, $values = null) { return null; } public function setResolver(Resolver $resolver) { $this->resolver = $resolver; return $this; } }
La classe la plus importante, ma strategy s'occupe donc de comparer le model pour détecter si c'est à lui d'agir. Lorsqu'il crois un FileStreamModel, il manipule la réponse et les headers d'après les options du Model lui-même et la réponse contient donc le Stream du fichier porté par le Model. Ma Strategy ressemble aujourd'hui à cela :
[lang=php]<?php namespace Users\View\Strategy; use Zend\EventManager\EventManagerInterface; use Zend\EventManager\ListenerAggregateInterface; use Zend\View\ViewEvent; use Users\View\Model\FileStreamModel; use Zend\Http\Response\Stream; use Users\View\Renderer\FileStreamRenderer; use Zend\Http\Headers; class FileStreamStrategy implements ListenerAggregateInterface { protected $renderer; /** * @var \Zend\Stdlib\CallbackHandler[] */ protected $listeners = array(); public function __construct(FileStreamRenderer $fileStreamRenderer) { $this->renderer = $fileStreamRenderer; } /** * Attach the aggregate to the specified event manager * * @param EventManagerInterface $events * @param int $priority * @return void */ public function attach(EventManagerInterface $events, $priority = 1) { $this->listeners[] = $events->attach(ViewEvent::EVENT_RENDERER, array($this, 'selectRenderer'), $priority); $this->listeners[] = $events->attach(ViewEvent::EVENT_RESPONSE, array($this, 'injectResponse'), $priority); } public function selectRenderer(ViewEvent $e) { $model = $e->getModel(); if ($model instanceof FileStreamModel) { return $this->renderer; } return; } public function detach(EventManagerInterface $events) { foreach ($this->listeners as $index => $listener) { if ($events->detach($listener)) { unset($this->listeners[$index]); } } } public function injectResponse(ViewEvent $e) { /* @var $model FileStreamModel */ $model = $e->getModel(); if (!($model instanceof FileStreamModel)) { // Discovered model is not ours; do nothing return; } $filePath = $model->getFilePath(); $contentDisposition = $model->getContentDisposition(); $headers = new Headers(); $headers->addHeaderLine('Content-Disposition', $contentDisposition.'; filename="' . basename($filePath).'"'); $headers->addHeaderLine('Content-Type', $model->getContentType()); $headers->addHeaderLine('Content-Length', filesize($filePath)); $headers->addHeaderLine('Expires', '@0'); $headers->addHeaderLine('Cache-Control', 'must-revalidate'); $headers->addHeaderLine('Pragma', 'public'); $response = new Stream(); $response->setHeaders($headers); $response->setStatusCode(200); $response->setStreamName(basename($filePath)); $response->setStream(fopen($filePath, 'r')); $e->setResponse($response); } }
J'ai fait 2 factory qui me permettent d'instancier correctement mon renderer et ma strategy par le Service Manager :
[lang=php]<?php namespace Users\Mvc\Service; use Zend\ServiceManager\FactoryInterface; use Zend\ServiceManager\ServiceLocatorInterface; use Users\View\Renderer\FileStreamRenderer; class ViewFileStreamRendererFactory implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) { $viewManager = $serviceLocator->get('ViewManager'); $fileStreamRenderer = new FileStreamRenderer(); $fileStreamRenderer->setResolver($viewManager->getResolver()); return $fileStreamRenderer; } }
et
[lang=php]<?php namespace Users\Mvc\Service; use Zend\ServiceManager\FactoryInterface; use Zend\ServiceManager\ServiceLocatorInterface; use Users\View\Strategy\FileStreamStrategy; class ViewFileStreamStrategyFactory implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) { $fsRenderer = $serviceLocator->get('ViewFileStreamRenderer'); $fsStrategy = new FileStreamStrategy($fsRenderer); return $fsStrategy; } }
Bien entendu, dans mon module.config.php, j'ai ajouté pour déclarer mes factory et ma strategy :
[lang=php]'service_manager' => array( 'factories' => array( 'ViewFileStreamStrategy' => 'Users\Mvc\Service\ViewFileStreamStrategyFactory', 'ViewFileStreamRenderer' => 'Users\Mvc\Service\ViewFileStreamRendererFactory' ) ), 'view_manager' => array( 'strategies' => array( 'ViewFileStreamStrategy' ) )
Du coup, après tant d'efforts, j'avoue commencer à douter de la pertinence de ce bordel quand je vois comment je galère lol... mais bon.
Donc, en déboguant, je passe bien dans ma strategy, dans la partie du code qui crée la réponse avec les headers qui vont bien mais au final, dans le navigateur, j'ai une page blanche, sans contenu, dont aucun header http n'est correct...
Si quelqu'un a déjà rencontré ce problème dans ce contexte, je veux bien un peu d'aide...
Merci beaucoup.
EDIT : Correction de la comparaison (!$model instanceof FileStreamModel)
Dernière modification par berturion (02-06-2015 15:44:12)
Hors ligne
Hello,
A tu un peu plus avancé depuis ton post?
Sinon, pour essayer de faire avancer ton schmilblick, (déjà tu a un cas assez complexe pour moi à froid xD)
En affichant toutes les erreurs tu n'a pas un message qui pourrais nous donner un peu plus de précisions?
Dernière modification par flobrflo (04-06-2015 10:18:41)
Hors ligne
flobrflo a écrit:
Hello,
A tu un peu plus avancé depuis ton post?
Sinon, pour essayer de faire avancer ton schmilblick, (déjà tu a un cas assez complexe pour moi à froid xD)
En affichant toutes les erreurs tu n'a pas un message qui pourrais nous donner un peu plus de précisions?
Merci pour ta réponse.
Oui j'ai un petit peu "avancé" (c'est un bien grand mot) et j'ai essayé dans ma Strategy de modifier les Headers existants au lieu d'en instancier des nouveaux. Dans ce cas, le navigateur (Firefox) les récupère bien et charge le lecteur de PDF (dans les headers, je mets un Content-Type "application/pdf"). Par contre il se met à attendre indéfiniment un contenu qui n'arrive jamais (mon Stream).
J'en ai déduit que je devais faire la même chose avec l'objet Response, c'est-à-dire le récupérer pour le manipuler et non en instancier un nouveau. Et effectivement, si je me contente de mettre un contenu dans l'objet Response existant, le navigateur l'affiche bien.
Le problème c'est que je veux renvoyer un objet "Stream" (qui étend Response) mais du coup là je bloque, je ne vois pas comment faire sans l'instancier ou caster la Response en Stream...
Je n'ai aucun message d'erreur et j'ai du mal à voir par quel code du framework ça finit par passer pour obtenir ce résultat. Pour l'instant, j'ai mis ça de côté pour avancer sur mon projet mais je ne laisse pas tomber, j'aimerais vraiment comprendre pourquoi j'obtiens ça... En tout cas, dès que j'ai la réponse, je vous le ferai savoir.
A moins que quelqu'un sache résoudre se problème ?
Hors ligne
Pages: 1