DevGang
Авторизоваться

Обработка представлений как файлов в Cake2

На самом деле, отличий от Cake1.3 не так уж много. Но, так как я только что поэкспериментировал с этим в 2.0, для примеров я буду использовать эту версию.

С чего начать

Пропустите этот пункт если хотите перейти сразу к делу.

В файле routes.php нужно добавить Router::parseExtensions(); (или только конкретное).

Это указывает Cake на то, что URL-адреса заканчивающиеся на “.xyz” будут обрабатываться как (встроенные файлы или как загрузка вложения).

Настройка

Не забудьте добавить RequestHandler к списку компонентов-контроллера.

public $components = array('RequestHandler');

Предположим, что вы хотите отобразить счет в pdf формате. Обычный URL-адрес будет /invoices/view/1.

Далее, устанавливаем ссылку в представлении к файлу:

$this->Html->link('View as PDF', array('action'=>'view', 'ext'=>'pdf', 1));

Поскольку мы используем компонент RequestHandler, Cake автоматически определяет, что это pdf-файл, и:

а) установит правильный заголовок (application/pdf)

b) попробует найти указанное pdf представление в /View/Invoices/pdf/index.ctp и pdf макет в /View/Layouts/pdf/default.ctp

Вот и всё.

Скачать прямо сейчас

Файлы типа pdf могут отображаться inline(встроенными). Поэтому браузер обычно не требует их загрузки.

Если это то что вы хотите, вам необходимо вызвать $this->response->download($filename); в действии контроллера.

Примечание: Если ваш браузер не понимает формат файла (в данном случае pdf), возможно это вызовет немедленную загрузку.

Ошибки браузера

Во время моих проверок я обнаружил, что файлы обрабатываемые inline (Content-Disposition: inline; filename=”…”) не используют указанное имя файла  при сохранении. Вместо этого, они будут сохранены с именем в URL-адресе. В приведенном выше примере это было бы “1.pdf”. Я поискал информацию: и как оказалось это известная недоработка браузера, которую еще не исправили.

Хорошо, но никто ведь не хочет, чтобы его счет выглядел как “1.pdf”. И что мы можем с этим сделать?

Я нашел неплохо работающее решение:

$this->Html->link('View as PDF', array('action'=>'view', 'ext'=>'pdf', 1, 'invoice-2011-11-01_some_customer_tag'));

Как видите, нужно просто добавить имя файла в URL – после идентификатора(!).

Поскольку это не более чем “косметика имени файла” нам нет необходимости добавлять второй переданный параметр в метод:

public function view($id = null) {}

Он будет игнорироваться в самом действии.

Таким образом, мы получим сгенерированный URL-адрес  /invoices/view/1/invoice-2011-11-01_some_customer_tag.pdf и результат сохраненного файла “invoice-2011-11-01_some_customer_tag.pdf”.

Пример для PDFs

Быстрый пример как выводить ваши материалы в виде pdf, используя Dompdf.

В макете (default.ctp в /pdf/):

App::import('Vendor', 'dompdf/dompdf.php');
$dompdf = new DOMPDF();
$dompdf->load_html(utf8_decode($content_for_layout), Configure::read('App.encoding'));
$dompdf->render();
echo $dompdf->output();

Мне показалось, что библиотека глючит без utf8_decode – хоть и заявляется поддержка utf8.

Если у вас уже имеются pdf-файлы, которые хотите просто отобразить или и использовать для загрузки вы можете напрямую взаимодействовать с объектом Response:

$this->autoRender = false; // Tell CakePHP that we don't need any view rendering in this case
$this->response->body(file_get_contents($pathToPdfFile));
$this->response->type('pdf'); // Only necessary if the action is not accessed with `.pdf` extension

Пример для ics (iСalendar) файлов

Простой пример использования IcalHelper c плагином Tools 

Пожалуйста, обратите внимание: на последующий запрос регистрации. До тех пор, вам необходимо или вручную исправить ResponseClass (быстрое решение), добавив недостающий MIME-тип 'ics' => 'text/calendar', или определить его в контроллере с помощью $this->response->type(array('ics' => 'text/calendar')); и самостоятельно установить все пути…

Код контроллера:

$reservation = ...;
$this->plugin = 'Tools'; //not necessary, only that I want to store the layout once (in the plugin)
$this->set(compact('reservation'));
$this->helpers[] = 'Tools.Ical';

Макет (в /Plugin/Tools/View/Layouts/ics/default.ctp):

<?php echo $content_for_layout; ?>

Представление (в /View/Reservations/ics/export.ctp):

$data = array(
        'start' => $this->Time->toAtom($reservation['Reservation']['time']),
        'end' => $this->Time->toAtom(strtotime($reservation['Reservation']['time'])+HOUR),
        'summary' => 'Reservation',
        'description' => $reservation['Reservation']['headcount'].' persons @ location foo',
        'organizer' => 'CEO',
        'class' => 'public',
        'timestamp' => '2010-10-08 22:23:34',
        'id' => 'reservation-'.$reservation['Reservation']['id'],
        'location' => $reservation['Restaurant']['address'],
);
$this->Ical->add($data);
echo $this->Ical->generate();

Осталось только связать действие таким образом:

echo $this->Html->link($this->Html->image('icons/calendar.gif', array('title'=>'Download reservation as ical-file')), array('action'=>'export', $reservation['Reservation']['id'], 'ext'=>'ical'), array('escape'=>false));

Общий пример использования file()

Он будет работать со всеми типами данных, если они уже доступны как file(passthrough):

public function download() {
        $this->autoRender = false; // tell CakePHP that we don't need any view rendering in this case
        $this->response->file('/path/to/file/' . $fileNameWithExtension, array('download' => true, 'name' => 'my-filename.ext'));
}

file() также устанавливает правильный тип заголовков. В большинстве случаев этого достаточно.

Если же нет, то после file() добавляем следующее:

$this->response->type('ext');

Кроме того, возможно объявляем расширение, если оно еще неизвестно CakePHP.

Подробнее тут

Это будет работать со всеми типами “динамически генерируемых данных”, в нашем случае с изображением:

public function display() {
        $this->autoRender = false; // tell CakePHP that we don't need any view rendering in this case
        $content = 'binary data of generated jpeg image (on the fly maybe even)';
        $this->response->body($content);
        $this->response->type('jpg');
}

При необходимости, можно также использовать download() , чтобы выполнить принудительную загрузку вместо отображения inline.

Старайтесь избегать возврата объекта Response. Особенно не стоит использовать send() , таким образом вы вызываете его дважды (Диспетчеризация контроллера автоматически вызовет этот метод в конце запроса) и в итоге получите необычные результаты.

Загрузка записей в формате CSV/JSON и пр.

В случае если вы хотите, чтобы ваше индексированное представление данных было загружаемым, попробуйте также использовать расширения представления.

Здесь мы ссылаемся на то же действие, но с необходимым расширением:

echo $this->Html->link('As CSV', array('action' => 'index', 'ext' => 'csv'));

В нашем случае для CSV мы можем использовать CsvView чтобы легко переключаться с обычного вывода HTML на CSV:

if (!empty($this->request['params']['ext'])) {
        $this->viewClass = 'CsvView.Csv';
        $this->set(compact('data', '_serialize'));
        return;
}

Если необходимо, чтобы принудительная загрузка выполнялась только в отдельных случаях, используйте маленькую хитрость строки запроса, что и выше:

if ($this->request->query('download')) {
        $this->response->download($this->request->params['action'] . '.' . $this->request->params['ext']);
}

И ссылку:

echo $this->Html->link('Download as CSV', array('action' => 'index', 'ext' => 'csv', '?' => array('download' => 1)));

Для того чтобы только ссылка содержащая .../action.csv?download=1  загружалась принудительно, а обычная сначала отображала данные в браузере.

То же самое относится к JSON и другим подходам, классов представления.

Смотрите пример в sandbox.

Автоматическое переключение классов просмотра

public $components = [
        'RequestHandler' => [
               'viewClassMap' => ['csv' => 'CsvView.Csv']
        ]
];

Если расширение cvs, будет использоваться CsvView.

#PHP #CakePHP
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу