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

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

Fork me on GitHub

Отзывов (12)

  1. klay:

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

  2. klay:

    И кстати не file_exists() и is_file(), а Kohana::find_file()

    • radik:

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

  3. klay:

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

    • radik:

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

  4. klay:

    Я бы не стал изобретать uploads_dir() а обратился бы к Upload::$default_directory

  5. Дмитрий:

    Исходников нет, только сам фреймворк

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

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