Composer + Yii + Imagine: небольшое приложение для загрузки картинок

Владимир | | PHP, Web разработка, Yii.

yii composer

Совсем недавно фреймворк Yii включили в репозиторий packagist.org. Новость очень хорошая, т.к. теперь можно использовать один менеджер зависимостей Composer для обновления и фреймворка, и дополнительных библиотек.

В этой статье мы рассмотрим пример создания несложного приложения на основе Yii, которое позволит загружать картинки и автоматически создавать их миниатюры. Задача тривиальная, но мне хотелось показать подключение дополнительных библиотек с помощью Composer, а в packagist как раз входит Imagine (очень удобная библиотека для работы с изображениями).

Код приложения с инструкцией по установке доступен на GitHub.

Source

Теперь перейдём непосредственно к созданию приложения.

Шаг 1. Устанавливаем Composer.

Тут всё предельно просто. Для Windows есть инсталлятор. Если вы работаете в Linux или MacOS нужно будет выполнить несколько команд из консоли. И инсталлятор, и команды можно взять на официальном сайте.

Шаг 2. Создаём папку для приложения.

В моём случае это yii-composer. Т.е. приложение доступно по адресу

http://localhost/yii-composer

Но, естественно, название и размещение может быть любым.

Шаг 3. Создаем composer.json

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

{
    "require": {
        "yiisoft/yii": "dev-master",
        "imagine/Imagine": "dev-master"
    }
}

Как видите, данные записаны в JSON формате. Здесь мы указываем менеджеру зависимостей, что нужно загрузить Yii фреймворк и библиотеку Imagine. Преимущество такого подхода заключается в том, что Composer сам отслеживает возможные зависимости. Т.е. когда разработчик библиотеки выкладывает её в репозиторий, он указывает необходимые условия для её работы (например, версию PHP), списки зависимостей и возможных конфликтов с другими библиотеками.

Шаг 4. Загружаем компоненты.

Для этого нужно выполнить команду:

composer install

или composer update (если install уже выполнялась, и вы изменяли composer.json).

В результате Composer загрузит указанные пакеты, и мы получим следующую структуру папок.

folder-structure

Как видите, Composer создал папку vendor и загрузил в неё библиотеку imagine и фреймворк.

Шаг 5. Создаем приложение.

Здесь мы используем стандартную утилиту yiic. Я решил, что приложение должно находиться в папке public_html, но вы можете использовать другую структуру папок.

В любом случае, алгоритм следующий.
Из папки /vendor/yiisoft/yii/framework
выполняем команду

yiic webapp ../../../../public_html

Вместо ../../../../public_html можно указать свою папку для приложения.

Yiic создаст нужные файлы, и мы увидим результат по адресу.
http://localhost/yii-composer/public_html

Шаг 6. База данных.

Данные о картинках будем хранить в базе данных в таблице images со следующими полями:
id – первичный ключ;
title – название картинки;
url – URL картинки.

После этого указываем параметры подключения в файлах protected/config/main.php и protected/config/console.php.

'db'=>array(
	'connectionString' => 'mysql:host=localhost;dbname=yii-composer',
	'emulatePrepare' => true,
	'username' => 'root',
	'password' => 'your_pass',
	'charset' => 'utf8',
),

Шаг 7. Создаём модель, представление и контроллер.

Для этого в файле protected/config/main.php раскомментируем модуль gii и указываем какой-нибудь пароль.

Заходим в админку gii
/public_html/index.php?r=gii/default/login

Создаем с помощью гененратора модель (Images), контроллер (для модели Images) и представления.

На данном этапе посмотреть результат можно на странице
/index.php?r=images

На этом этапе я подробно не останавливаюсь, т.к. процедура хорошо описана в документации Yii.

Шаг 8. Настраиваем представление.

Нам нужно добавить в форму поле для загрузки файла и убирать поле url (т.к. url картинки будет генерироваться автоматически).

Добавляем поле для загрузки файла (файл views/images/_form.php)

<?php $form=$this->beginWidget('CActiveForm', array(
	'id'=>'images-form',
	'enableAjaxValidation'=>false,
	'htmlOptions'=>array(
			'enctype'=>'multipart/form-data',
		),
)); ?>
...
<div class="row">
	<?php echo $form->labelEx($model,'image'); ?>
	<?php echo $form->fileField($model,'image'); ?>
	<?php echo $form->error($model,'image'); ?>
</div>

Шаг 9. Подключаем библиотеки, загруженные с помощью Composer

Composer автоматически создаёт загрузчик (файл vendor/autoload.php), который соответствует спецификации PSR-0. На практике это означает, что для того, чтобы использовать библиотеки, загруженные с помощью Composer, достаточно подключить загрузчик в файле index.php фреймворка.

Т.е. для данного примера добавляем в файл public_html/index.php строку:

require_once('../vendor/autoload.php');

Теперь можно использовать Imagine. Например,

$imagine = new Imagine\Gd\Imagine();

Как видите, при создании объекта необходимо путь к классу Imagine начиная от папки vendor/imagine/lib. Т.е. фактически вам без разницы, где именно Composer хранит библиотеки. Вы можете использовать примеры из документации к Imagine без какой-либо дополнительной настройки. Достигается это за счёт того, что Composer автоматически создаёт файл vendor/composer/autoload_namespaces.php, который возвращает реальное размещение библиотек. В данном случае:

return array(
    'Imagine' => $vendorDir . '/imagine/Imagine/lib/',
);

Шаг 10. Добавляем обработчик загрузки файла в модель.

Для этого добавляем свойство $image в класс Images (файл protected/models/images.php)

public $image;

Затем добавляем это поле в правила проверки данных модели

public function rules()
{
	return array(
		array('title', 'length', 'max'=>255),
		array('title, image', 'required'),
		array('image', 'file', 'types'=>'jpg, jpeg, gif, png'),
		// The following rule is used by search().
		// @todo Please remove those attributes that should not be searched.
		array('id, title, image', 'safe', 'on'=>'search'),
	);
}

В данном случае мы разрешаем загрузку файлов с расширениями jpg, jpeg, gif и png.

И добавляем следующие методы.

public function beforeSave() {
	$this->url = $this->image->getName();
	return parent::beforeSave();
}

Этот метод сохраняет имя загруженного файла в поле url.

Следующий метод создаёт миниатюру.

public function createThumbs() {
	$imagine = new Imagine\Gd\Imagine();

	$size = new Imagine\Image\Box(40, 40);
	$mode = Imagine\Image\ImageInterface::THUMBNAIL_INSET;

	$imagine->open(Yii::app()->params['uploadsDir'].$this->image->getName())
		->thumbnail($size, $mode)
		->save(Yii::app()->params['uploadsDir'].$this->getThumbnailName());
}

В данном случае мы создаём миниатюру размером в 40px по большей стороне и сохраняем её в папку public_html/uploads. Размещение папки указываем в параметрах (файл protected/config/main.php).

Последний метод в модели формирует имя файла миниатюры (добавляет суффикс _40x40 к имени полноразмерной картинки).

public function getThumbnailName() {
	$parts = explode('.', $this->url);
	$parts[count($parts) - 2] .= '_40x40';
	return implode('.', $parts);
}

Шаг 11. Добавляем поддержку загрузки файла в контроллере.

Для этого в файле protected/controllers/ImagesController.php изменяем метод actionCreate

$model->image=CUploadedFile::getInstance($model,'image');
if($model->save()){
	if ($model->image->saveAs(Yii::app()->params['uploadsDir'].$model->image->getName())) {
		$model->createThumbs();
	}
	$this->redirect(array('view','id'=>$model->id));
}

Здесь выполняется две проверки. Первая – при вызове save – проверяется заполнение полей модели. При второй проверке проверяется, успешно ли сохранён оригинальный файл, и после этого создаётся миниатюра.

Шаг 12. Показываем картинки.

Для этого в файл представления protected/views/view.php добавляем строки.

<?php
	echo CHtml::image(Yii::app()->baseUrl.'/uploads/'.$model->url, $model->title);
	echo CHtml::image(Yii::app()->baseUrl.'/uploads/'.$model->getThumbnailName(), $model->title);
?>

Естественно, на практике выводить миниатюру под картинкой, особого смысла нет. Но в этом примере дизайн странице не рассматриваем, поэтому я привёл только код, необходимый для отображения картинки.

Заключение.

Статья получилась довольно объёмной, но большая её часть – обычный код для работы с файлами. Подключение Composer сводится к подключению загрузчика. В результате у вас упрощается подключение дополнительных библиотек. Кроме того, в этом случае можно оставить в системе контроля версий только код приложения, т.к. любой разработчик всегда сможет получить нужные версии библиотек с помощью всего одной команды (composer install).

Ещё советую почитать статью Creating Yii applications with composer.

P.S. А для тех, кто интересуется разработкой с использованием Flash — FlashAdverts

  • Александр Шестаков

    Спасибо, давно думал как подступиться к Composer — особого смысла пока не вижу — возможно я что то упускаю.
    Какие преимущества дает использование Composer?
    И не совсем про Composer — а про Imagine
    Почему так мало библиотек, которые используют создание thumbnails на лету? При первом обращении? phpThumb — древняя, давно не обновляющаяся библиотека. Ее реинкарнация phpThumbs и все больше не знаю. Любое изменение дизайна приводит к переписыванию кода в модели. И как будут обрабатываться уже созданные файлы — если вам захочется сделать превью 50 на 50? Загружать все заново?

    • >> Какие преимущества дает использование Composer?

      Принципиально новых возможностей вы не получите. Немного упрощается подключение библиотек, т.к. используется один загрузчик (при этом библиотека должна соответствовать PSR-0, а в идеале должна быть добавлена в packagelist). Отслеживаются зависимости между библиотеками. Удобно обновлять/откатывать библиотеки (достаточно правки composer.json). Код библиотек можно убрать из CVS.

      >> Почему так мало библиотек, которые используют создание thumbnails на лету?

      Изменение дизайна не должно приводить к изменению кода в модели. В этой статье я прописал 40х40 исключительно в демонстрационных целях. В реальном приложении все размеры миниатюр записываются в виде параметров в файле конфигурации (config/main.php). Затем в модели считываем массив с размерами и создаём соответствующие миниатюры.

      Соответственно, если нужно изменить размер — правим файл конфигурации, а не модель.

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

      Что касается создания миниатюр «на лету». Для этого не обязательно использовать PHP, например, есть модуль для nginx. Я не сравнивал, но nginx должен обрабатывать такие запросы быстрее php.

    • Ray Vaigmi

      Здравствуйте.

      Я не буду сейчас разбрасываться умными речами, терминами, и сыпать слэнгом программистов, сам уже занимаюсь разработкой сайтов 4 года, и хочу сказать огромное спасибо создателям Composer, можно конечно скачать архив на «гитхабе», но данная программа просто упрощает жизнь!!! Я стал экономить столько времени, раньше мне приходилось в ручную создавать папки, создавать в них файлы, настраивать кодировку, проверять код файлов, всё ли я там написал, потом приходилось всё это согласовывать с Базой Данных, по сто раз обновлять страницу пока не устраню ошибки. Что касается дизайна, то тут надо учитывать архитектуру бутстрапа (если используете), понимать, что скрипты могут использовать классы и айди в css, если это игнорировать, то не будет работать, достаточно использовать существующие значения в коде, и просто их менять под себя, не надо трогать модели и БД если не знаете где менять, если задача заключается только в изменении внешности, достаточно позаботится о стилях CSS и всё, на «гитхабе» уже есть решение, идентичное вашим пожеланиям, там можно легко настраивать миниатюру! Не бойтесь консоли Composer, поймёте раз, как ею пользоваться, будете самым счастливым программистом в мире )))

  • Valera

    Попробовал повторить пример из статьи — вроде все ок но файл почему то не загружаеться — и запись в базу не сохраняеться, условие «if($model->save()){..}» не проходит но ошибок не выводит никаких при том что валидация работает нормально — если подсунуть файл с каким нить «не тем» расширением — выводит ошибку.. Не подскажете в чем может быть проблема? С Yii только знакомлюсь..

    • Сам по себе yii ошибки валидации не выведет. Т.е. $model->save() вернет false и всё. Вам нужно использовать getErrors() для того, чтобы получить список ошибок.

      Например,
      if($model->save()){

      }
      else {
      $errores = $model->getErrors();
      $this->render('index', array('model' => $errores));
      }

      • Valera

        $model->getErrors() — пустой Array( )

        • Тогда включите логгирование параметров. Например, так:

          'db'=>array(
          'enableProfiling'=>true,
          'enableParamLogging' => true,
          ),
          'log'=>array(
          'class'=>'CLogRouter',
          'routes'=>array(

          array(
          'class'=>'CProfileLogRoute',
          'levels'=>'profile',
          'enabled'=>true,
          ),
          ),
          ),

          В лог будут записаны все запросы к БД. Поищите нужный вам и попробуйте выполнить его вручную (из консоли mysql).

  • konstantin

    Сделал все как написано, проверил по исходникам на GitHub, но у меня не работает, выдает ошибку «Title cannot be blank». Не могу понять почему, я только знакомлюсь с Yii.

    • Эта ошибка возникает когда приложение не получило значение поля title формы. Проверка выполняется этим правилом:

      public function rules()
      {
      ...
      array('title, image', 'required'),
      ...
      }

      Посмотрите с помощью firebug какие именно данные отправляются на сервер при сохранении картинки (в POST запросе).