Zend FR

Consultez la FAQ sur le ZF avant de poster une question

Vous n'êtes pas identifié.

#1 02-06-2015 14:59:34

berturion
Nouveau membre
Date d'inscription: 02-06-2015
Messages: 2

Strategy et Model pour retourner un Stream

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 :

Code:

[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) :

Code:

[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 :

Code:

[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 :

Code:

[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 :

Code:

[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

Code:

[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 :

Code:

[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

 

#2 04-06-2015 10:18:23

flobrflo
Membre
Lieu: Marseille
Date d'inscription: 26-04-2013
Messages: 376

Re: Strategy et Model pour retourner un Stream

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

 

#3 05-06-2015 12:15:02

berturion
Nouveau membre
Date d'inscription: 02-06-2015
Messages: 2

Re: Strategy et Model pour retourner un Stream

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

 

Pied de page des forums

Propulsé par PunBB
© Copyright 2002–2005 Rickard Andersson
Traduction par punbb.fr

Graphisme réalisé par l'agence Rodolphe Eveilleau
Développement par Kitpages