
Приветствую вас постоянные читатели моего блога, а так же случайно попавшие посетители! В очередной раз просматривая топики официального блога 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 в ближайшее время исправлю данную оплошность.
Исходники поправил, теперь все гуд :)