Kohana file upload — Загрузка файлов Kohana — Часть 1

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

Скачать исходные коды урока

Kohana file upload — Загрузка файлов Kohana — Часть 1: 14 комментариев

  1. Спасибо за статью.

    На мой взгляд лучше было эту запись убрать:
    // 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-данные.

  2. Более вдумчиво перечитал. Вынужден с вами во всём согласится ) Хороший код. Мне нравится

    1. Вынужден с вами не согласиться, потому что Kohana::find_file() будет на много медленнее работать чем file_exists() и is_file() т.к. при использовании данного метода нужно всегда помнить о существовании каскадной файловой системе которую использует Kohana.

  3. А вообще конечно спасибо!
    Хотелось бы узнать, не планирует ли автор сделать модуль, например используя текущие наработки и вот это: habrahabr.ru/post/140400/

    1. Как вариант я планирую в дальнейшем сделать руководство по теме ajax загрузки файлов…, но сделать смогу только как появится время так, что быть может еще не скоро.

    1. Дело ваше конечно, но думаю этот вариант не прокатит т.к. свойство должно быть вычисляемое, а в php такое невозможно :)

    1. Упс…, действительно. Cделал себе заметку в todo list в ближайшее время исправлю данную оплошность.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *