Приветствую вас постоянные читатели моего блога, а так же случайно попавшие посетители! В очередной раз просматривая топики официального блога Kohana наткнулся на вопрос связанный такой тривиальной вещью как загрузка файлов в Kohana, я же как наивный полагал, что этот вопрос уже кто то обязательно должен был поднять и осветить но воспользовавшись поиском в Google и Yandex не нашел ни одного нормального материала где бы была нормально освещена эта тема, именно поэтому я решил заполнить данный пробел и создать руководство по организации загрузки файлов в Kohana. И так поехали ….
И так начнем с лирического отступления, я надеюсь на то, что вы смогли сами установить Kohana и увидеть в своем окне «hello, world!» :) …
В этой части статьи мы с вами создадим простое приложение на Kohana которое сможет загружать на сервер изображения, а так же выводить их миниатюры на страницу.
1. Создание / Настройка маршрута
Я немного отредактировал стандартный маршрут в файле application/bootstrap.php результат представлен ниже:
/** * Set the routes. Each route must have a minimum of a name, a URI and a set of * defaults for the URI. */ Route::set('default', '(<controller>(/<action>(/<id>)))') ->defaults(array( 'controller' => 'files', 'action' => 'view', )); // Preview route Route::set('preview', 'files/<filename>', array('filename' => '.+')) ->defaults(array( 'controller' => 'files', 'action' => 'preview', ));
Прошу вас обратить внимание на то, что мы создали маршрут с именем preview который понадобится нам в дальнейшем для вывода и генерации превью изображений.
2. Настройка списка используемых модулей
Для того что бы нам реализовать то, что мы я задумал нам понадобится модуль cache и image которые входят в стандартную поставку Kohana, для того что бы активировать модули нам опять же нужно поправить пару строчек в файле application/bootstrap.php
/** * Enable modules. Modules are referenced by a relative or absolute path. */ Kohana::modules(array( // 'auth' => MODPATH.'auth', // Basic authentication 'cache' => MODPATH.'cache', // Caching with multiple backends // 'codebench' => MODPATH.'codebench', // Benchmarking tool // 'database' => MODPATH.'database', // Database access 'image' => MODPATH.'image', // Image manipulation // 'orm' => MODPATH.'orm', // Object Relationship Mapping // 'unittest' => MODPATH.'unittest', // Unit testing // 'userguide' => MODPATH.'userguide', // User guide and API documentation ));
3. Создание контроллера
На основании созданного ранее мною маршрута класс контроллера будет иметь имя Controller_Files, а его код располагаться в файле application/classes/controller/files.php листинг этого файла представлен ниже:
<?php defined('SYSPATH') or die('No direct script access.'); /** * Files controller class * * @author Novichkov Sergey(Radik) <novichkovsergey@yandex.ru> * @copyright Copyrights (c) 2012 Novichkov Sergey */ class Controller_Files extends Controller { /** * Uploads directory * * @return string */ private function uploads_dir() { return DOCROOT . 'uploads' . DIRECTORY_SEPARATOR; } /** * @var View */ public $template = NULL; /** * View all tree action */ public function action_view() { // create template $this->template = View::factory('files'); // initialize files array $files = array(); // scan uploads directory and build files array $uploads = new DirectoryIterator($this->uploads_dir()); foreach ($uploads as $file) /** @var DirectoryIterator $file **/ { if ($file->isFile()) { $files[] = $file->getFilename(); } } // set values to template $this->template->set(array( // files list 'files' => $files, // errors from user session 'errors' => Session::instance()->get_once('errors', array()), // message from user session 'message' => Session::instance()->get_once('message'), )); // render template $this->response->body($this->template->render()); } /** * Generate preview action */ public function action_preview() { // build image file name $filename = $this->uploads_dir() . $this->request->param('filename'); // check if file exists if (! file_exists($filename) OR ! is_file($filename)) { throw new HTTP_Exception_404('Picture not found'); } /** @var Image $image **/ // trying get picture preview from cache if (($image = Cache::instance()->get($filename)) === NULL) { // create new picture preview $image = Image::factory($filename) ->resize(100, 100) ->render(); // store picture in cache Cache::instance()->set($filename, $image, Date::MONTH); } // gets image type $info = getimagesize($filename); $mime = image_type_to_mime_type($info[2]); // display image $this->response->headers('Content-type', $mime); $this->response->body($image); } /** * Upload file action */ public function action_upload() { // check request method if ($this->request->method() === Request::POST) { // create validation object $validation = Validation::factory($_FILES) ->label('image', 'Picture') ->rules('image', array( array('Upload::not_empty'), array('Upload::image'), )); // check input data if ($validation->check()) { // process upload Upload::save($validation['image'], NULL, $this->uploads_dir()); // set user message Session::instance()->set('message', 'Image is successfully uploaded'); } // set user errors Session::instance()->set('errors', $validation->errors('upload')); } // redirect to home page $this->request->redirect('/'); } } // End Controller Files
Тут думаю вам все будет понятно т.к. практически каждую строчку в коде контроллера я снабдил комментариями (хоть и на буржуйском :)).
4. Создание представления
Код шаблона вывода, а так же формы загрузки представлен ниже:
<?php defined('SYSPATH') or die('No direct script access.'); /** * @var array $files * @var array $errors * @var string $message * * @author Novichkov Sergey(Radik) <novichkovsergey@yandex.ru> * @copyright Copyrights (c) 2012 Novichkov Sergey */ ?><!DOCTYPE html> <html> <head> <title>Pictures</title> <style type="text/css"> *{ margin: 0; padding: 0; } html, body{ width: 100%; height: 100%; } a:hover{ text-decoration: none; } #wrap{ margin: 0 auto; padding-top: 20px; width: 960px; } #wrap h2{ margin-bottom: 15px; } #wrap .pictures img{ vertical-align: middle; margin-bottom: 10px; } #wrap .message{ padding: 5px; border: 3px solid #00f; color: #00f; margin-bottom: 20px; } #wrap .error{ padding: 5px; border: 3px solid #f00; color: #f00; margin-bottom: 20px; } #wrap .row, #wrap label{ display: block; margin-bottom: 5px; } </style> </head> <body> <div id="wrap"> <h2>Files</h2> <?php if (empty($files)) : ?> <p>Uploaded files not found</p> <?php else : ?> <div class="pictures"> <?php foreach ($files as $file) : ?> <img src="<?php echo Route::url('preview', array('filename' => $file)) ?>" alt=""> <?php endforeach; ?> </div> <?php endif; ?> <h2>Upload</h2> <?php if ($message) : ?> <div class="message"><?php echo HTML::chars($message) ?></div> <?php endif; ?> <?php foreach ($errors as $error) : ?> <div class="error"><?php echo HTML::chars($error) ?></div> <?php endforeach; ?> <form action="<?php echo Route::url('default', array('controller' => 'files', 'action' => 'upload')) ?>" method="post" enctype="multipart/form-data"> <label for="image_control">For upload picture please select the file and click upload button</label> <div class="row"> <input type="file" name="image" id="image_control"> <input type="submit" value="Upload"> </div> </form> </div> </body> </html>
Единственное, что я хотел бы пояснить и быть может будет не понятно вам так это то, что для отображения превью изображения мы подменяем URL (вспомните про ранее созданный маршрут preview и вам стразу станет ясна логика работы)
5. Сообщения валидации
Из-за неизвестного божьего плана, а скорее всего тайному заговору ошибки разработчиков фреймворк не снабжен понятными простому смертному сообщениями правил валидации загрузки файлов … но не беда мы сами исправим эту оплошность для этого создадим файл application/messages/upload.php с следующим содержанием:
<?php defined('SYSPATH') or die('No direct script access.'); return array( 'Upload::not_empty' => ':field not selected.', 'Upload::image' => ':field not valid picture file.' );
6. Результат
Ну и собственно теперь мы с вами можем откинуться на кресло и насладиться результатом нашего непосильного труда, вот что у меня получилось:
В следующей части статьи мы разберем как все это добро, что предоставляет нам Kohana можно использовать в процессе работы с изображениями информацию о которых мы будем хранить в БД и в процессе изучения реализуем небольшое файловое хранилище :)
Спасибо за статью.
На мой взгляд лучше было эту запись убрать:
// gets image type
$info = getimagesize($filename);7
$mime = image_type_to_mime_type($info[2]);
А данные о mime получать из $image и сохранять в кеш саму картинку и данные о mime. Т.е. :
$mime = $image->mime;
Cache::instance()->set($filename, (object) array(‘img’ => $image, ‘mime’ => $mime), Date::MONTH);
Чтоб не вычислять отдельно mime-данные.
Можно было бы и так, спасибо за предложение.
Более вдумчиво перечитал. Вынужден с вами во всём согласится ) Хороший код. Мне нравится
И кстати не file_exists() и is_file(), а Kohana::find_file()
Вынужден с вами не согласиться, потому что Kohana::find_file() будет на много медленнее работать чем file_exists() и is_file() т.к. при использовании данного метода нужно всегда помнить о существовании каскадной файловой системе которую использует Kohana.
И правда..
А вообще конечно спасибо!
Хотелось бы узнать, не планирует ли автор сделать модуль, например используя текущие наработки и вот это: habrahabr.ru/post/140400/
Как вариант я планирую в дальнейшем сделать руководство по теме ajax загрузки файлов…, но сделать смогу только как появится время так, что быть может еще не скоро.
Я бы не стал изобретать uploads_dir() а обратился бы к Upload::$default_directory
Дело ваше конечно, но думаю этот вариант не прокатит т.к. свойство должно быть вычисляемое, а в php такое невозможно :)
Это внутреннее свойство Kohana
Исходников нет, только сам фреймворк
Упс…, действительно. Cделал себе заметку в todo list в ближайшее время исправлю данную оплошность.
Исходники поправил, теперь все гуд :)