RSS-подписка

RSS-лента

Новые статьи

Последние комментарии


Получать обновления на эл. почту

Ваш e-mail:

Рассылка новостей от Loco

Yii: Добавляем поле загрузки картинки (изображения) к статье, переименовываем и сохраняем её в папку на сервере и в БД

Yii: Добавляем поле загрузки картинки (изображения) к статье, переименовываем и сохраняем её в папку на сервере и в БД добавляем поле загрузки картинки (файла изображения) к статье, сохранение в папку на сервере + запись имени файла в базу

Этот же способ, но для Yii 2 смотрите - http://yiico.ru/blog/493-zagruzka-izobrazhenii-i-failov-v-yii2-ih-sohranenie-v-papku-na-servere-i-v-bd

До недавнего времени у нас было поле input для пути к файлу изображения. При добавлении новой статьи приходилось закачивать картинку по ftp и вручную прописывать в это поле путь к ней. Надоело, теперь мы изменили поле с типа input  на тип file. Картинка загружается с компьютера, переименовывается и сохраняется в папку согласно дате (если материал добавлен в 2011 году, то изображение сохраняется в папку 2011, в 2012 - в папку 2012; а в начало имени файла прописывается месяц и день добавления статьи).

К каждой статье можно загрузить одно изображение.

Мы также посчитали правильным хранить имя изображения в базе данных, для чего добавили в таблицу со статьями поле image. Теперь всё удобно работает.

Расскажем как мы это реализовавыли.

В модель статей добавляем 2 новых атрибута в самом верху:

class Material extends CActiveRecord
{
	public $icon; // атрибут для хранения загружаемой картинки статьи
	public $del_img; // атрибут для удаления уже загруженной картинки

Переменная $icon для хранения загружаемой картинки и $del_img для того, чтобы пользователь мог удалить уже загруженную картинку.

Эти атрибуты надо описать так же, как и атрибуты, значения которых хранятся в базе данных: определить правила валидации rules() и названия полей в понятном для человека виде attributeLabels()

В том же файле модели статей дополняем:



public function rules()
	{
		// NOTE: you should only define rules for those attributes that
		// will receive user inputs.
		return array(
			array('title, alias, anons, description, keywords, content, status', 'required'),
			array('del_img', 'boolean'),
			array('status', 'in', 'range'=>array(1,2,3)),
			array('title', 'length', 'max'=>128),
			array('source, image', 'length', 'max'=>255),
			array('icon', 'file',
			'types'=>'jpg, gif, png',
			'maxSize'=>1024 * 1024 * 5, // 5 MB
			'allowEmpty'=>'true',
			'tooLarge'=>'Файл весит больше 5 MB. Пожалуйста, загрузите файл меньшего размера.',
			),
			array('id, title, status, source', 'safe', 'on'=>'search'),
		);
	}


И названия атрибутов:


 public function attributeLabels()
	{
		return array(
			'id' => 'Id',
			'title' => 'Заголовок',
			'alias' => 'Псевдоним',
			'anons' => 'Анонс',
			'content' => 'Статья полностью',
			'status' => 'Статус',
			'create_time' => 'Create Time',
			'update_time' => 'Update Time',
			'source' => 'Источник (URL без http://)',
			'author_id' => 'Автор',
			'icon' => 'Картинка к статье',
			'del_img'=>'Удалить картинку?',
			'count_views' => 'Количество просмотров',
		);
	}

Эти новые атрибуты отличает то, что их значения нигде не хранятся (нужны только чтобы загрузить картинку на сервер или удалить уже загруженную).



Если для какой-то статьи картинка не загружена, нужно будет вывести пустую картинку («Нет изображения»). 

Для этого в контроллере protected/controllers/MaterialController.php создаю действие material_image()

public function material_image($id, $create_year, $alias, $title, $image, $width='150', $class='material_img')
	{
		if(isset($image) && file_exists($_SERVER['DOCUMENT_ROOT'].
		Yii::app()->urlManager->baseUrl.
		'/images/'.$create_year.'/'.$image))
			return CHtml::image(Yii::app()->getBaseUrl(true).'/images/'.$create_year.'/'.$image, $title,
			array(
			'width'=>$width,
			'class'=>$class,
			));
		else
			return CHtml::image(Yii::app()->getBaseUrl(true).'/images/pics/noimage.gif','Нет картинки',
			array(
			'width'=>$width,
			'class'=>$class
			));
	}

Обратите внимание на Yii::app()->getBaseUrl(true) - это делает url абсолютным. Результатом этой функция будет HTML тег image с нужной картинкой, или с картинкой noimage.gif.

Теперь в форме /protected/views/material/_form.php добавляем вывод картинки к статье, чекбокс на удаление этой картинки и поле выбора новой картинки:



<div class="row pull-1 span-3 box">
		<?php echo $form->labelEx($model,'icon'); ?>
		<?php // Вывод уже загруженной картинки или изображения No_photo
		echo $this->material_image($model->id, substr($model->create_time, 0, 4), $model->alias, $model->title, $model->image, '150','small_img left');?>
		<br clear="all">
		<?php //Если картинка для данного товара загружена, предложить её удалить, отметив чекбокс
		if(isset($model->image) && file_exists($_SERVER['DOCUMENT_ROOT'].
		Yii::app()->urlManager->baseUrl.
		'/images/'.substr($model->create_time, 0, 4).'/'.$model->image))
					echo $form->checkBox($model,'del_img',array('class'=>'span-1'));
			echo $form->labelEx($model,'del_img',array('class'=>'span-2'));
		}
		?> 
		<br />
		<?php //Поле загрузки файла
		echo CHtml::activeFileField($model, 'icon'); ?>
	</div>

Не забудьте дописать, что форма теперь стала типа multipart: 

<?php $form=$this->beginWidget('CActiveForm', array(
    'id'=>'file-form',
    'enableAjaxValidation'=>false,
    'htmlOptions'=>array('enctype'=>'multipart/form-data'),
)); ?>

Обратим внимание: на $form->checkBox($model,'del_img' ); и CHtml::activeFileField($model, 'icon'). Там имена новых атрибутов модели, которые не хранятся в базе данных.

Теперь, чтобы обработать данные формы вернемся в контроллер /protected/controllers/MaterialController.php Изменим методы «создать» и «обновить»:



public function actionCreate()
	{
		$model=new Material;
		// Uncomment the following line if AJAX validation is needed
		// $this->performAjaxValidation($model);
		if(isset($_POST['Material'])){
			$model->attributes=$_POST['Material'];
			//Полю icon присвоить значения поля формы icon
			$model->icon=CUploadedFile::getInstance($model,'icon');
			if ($model->icon){
				$sourcePath = pathinfo($model->icon->getName());	
				$fileName = date('m-d').'-'.$model->alias.'.'.$sourcePath['extension'];
				$model->image = $fileName;
			}
			if($model->save()){
				//Если поле загрузки файла не было пустым, то            
				if ($model->icon){				
					//сохранить файл на сервере в каталог images/2011 под именем 
					//month-day-alias.jpg
					$file = $_SERVER['DOCUMENT_ROOT'].
					Yii::app()->urlManager->baseUrl.
					'/images/'.date('Y').'/'.$fileName;
					$model->icon->saveAs($file);
				}
				$this->redirect(array('view','id'=>$model->id));
			}
		}
		
		$this->render('create',array(
			'model'=>$model,
			'sectionnames'=>$sectionnames
		));
	}

Выделенная строка $model->image = $fileName обязательно вызывается до сохранения формы, иначе в базу данных не запишется значение в поле image. 

Аналогично в действии обновить:

public function actionUpdate()
	{
		$model=$this->loadModel();
		if(isset($_POST['Material']))
		{
			$model->attributes=$_POST['Material'];
			
			$model->icon=CUploadedFile::getInstance($model,'icon');
			if ($model->icon){
				$sourcePath = pathinfo($model->icon->getName());
			
				$fileName = substr($model->create_time, 5, 5).'-'.$model->alias.'.'.$sourcePath['extension'];
				$model->image = $fileName;
			}
			if($model->save()){
				//Если отмечен чекбокс «удалить файл»            
				if($model->del_img)
				{
					if(file_exists($_SERVER['DOCUMENT_ROOT'].
					Yii::app()->urlManager->baseUrl.
					'/images/'.substr($model->create_time, 0, 4).'/'.$model->image))
					{
						//удаляем файл
						unlink('./images/'.substr($model->create_time, 0, 4).'/'.$model->image);
						$model->image = '';
        			}
        		}
        		
				//Если поле загрузки файла не было пустым, то            
				if ($model->icon){
					$file = './images/'.substr($model->create_time, 0, 4).'/'.$fileName;
					//сохранить файл на сервере под именем 
					//month-day-alias.jpg Если файл с таким именем существует, он будет заменен.
					$model->icon->saveAs($file);
				}
				$this->redirect(array('view','id'=>$model->id));
			}
		}
		$this->render('update',array(
			'model'=>$model,
			'sectionnames'=>$sectionnames
		));
	}


Теперь добавляем вывод картинки в статью в файлах /protected/views/material/view.php и _view.php:


<?php echo $this->material_image($data->id, substr($data->create_time, 0, 4), $data->alias, $data->title, $data->image, '150','small_img left');?>

С помощью метода material_image() выводим картинки.
Стили: стили css в файле /css/main.css
Добавляю:


.material_img
{
margin: 10px 10px 10px 0;
float:left
}

Чтобы картинку текст обтекал справа, и


div.view
{
padding: 10px;
margin: 10px 0;
border: 1px solid #C9E0ED;
min-height: 250px;
height: auto !important;
height: 250px;
}

Чтобы не расползался список статей, каждая статья имеет ячейку не менее 250 пикселей высотой.

В следующей статье научимся делать ресайз картинки (сделаем несколько картинок разного размера для одной исходной и сохраним в разные папки).

Edit 12.05.2012: Несколько иначе загрузку картинок показал Юрий Беляков - http://belyakov.su/content/yii-magazin-3_1-dopilivaem-book (там используется проверка is_object, код покороче).

almix
Разработчик Loco, автор статей по веб-разработке на Yii, CodeIgniter, MODx и прочих инструментах. Создатель Team Sense.

Вы можете почитать все статьи от almix'а.



Другие статьи по этой теме:

Комментарии (12)     Подпишитесь на RSS комментариев к этой статье.

12 комментариев

#172
Егор говорит:
January 11, 2012 at 11:45 pm
Как установить чтобы дату видело? Ругается просто что не возможно найти ?
#174
Саша говорит:
January 12, 2012 at 12:36 am
Не думаю, что проблема с create_time, тогда бы вы сказали 'время', скорее всего датой вы называете переменную $data, так как других дат здесь нет. Невозможно пишется слитно. Но речь не об этом. Ругается фреймворк Yii скорее всего на то, что не определена $data, значит там определена $model, посмотрите по соседям. В некоторых файлах видов, _form.php и view.php используется именно $model. Тогда измените для них $data на $model.
Шерлок счастливчик, или несчастный человек?
#175
Егор говорит:
January 13, 2012 at 04:42 pm
Я вот по примеру Вашего источника делаю "книжку",
и вот...

public function data_img($id, $width='100',
$class='data_img')

{

Не подскажите почему может выполнятся только условие else выводит картинку нет фото, и никак не высвечивает id_data.jpg
if(file_exists($_SERVER['DOCUMENT_ROOT'].
Yii::app()->urlManager->baseUrl.
'/data_img/'.$id.'_data.jpg'))

return CHtml::image( '/data_img/'.$id.'_data.jpg',
array(
'width'=>$width,
'class'=>$class,
));

else
return CHtml::image('/data_img/no_photo.gif','No photo',
array(
'width'=>$width,
'class'=>$class
));
}
#176
Саша говорит:
January 13, 2012 at 10:02 pm
Интересно, какую книжку делаете? Расскажите)

У меня в процессе использования тоже было так, добавил проверку на установку поля image:

if(isset($model->image) && file_exists($_SERVER['DOCUMENT_ROOT'].....
#407
drg говорит:
March 31, 2012 at 05:20 pm
У Вас в экшене update  $model->image = '';  уже после сохранения, так что в базе оно не сохраниться
#411
almix говорит:
April 2, 2012 at 07:59 pm
drg, надо поменять местами условия $model->del_img и $model->save(), вроде того?! 
#460
Юрий говорит:
May 16, 2012 at 03:10 pm
" в 2012 - в папку 2010" - исправьте опечатку
#461
almix говорит:
May 19, 2012 at 01:44 am
Исправил Юрий. Спасибо!
#470
darl говорит:
May 22, 2012 at 05:52 pm
А почему бы хранить в базе не имя фото. а относительный путь с именем? Код немного поменьше будет.
#472
almix говорит:
May 23, 2012 at 05:10 pm
Можно относительный путь. Особо роли не играет, в дальнейшем в чём-то проще будет.
#690
Алексей говорит:
October 22, 2012 at 07:49 am
А вот если картинку уже выбрали при добавлении статьи, но какое-то другое поле не прошло валидацию, форма рисуется снова с надписями об ошибке, а выбор картинки пропал, приходится заново выбирать, как тут быть?
#691
almix говорит:
October 22, 2012 at 04:32 pm

Алексей, вы описали интересную проблему!

Она обсуждалась на форуме Yiiframework - http://www.yiiframework.com/forum/index.php/topic/33889-file-upload-field-becomes-empty/page__view__findpost__p__163258 (ответ Maurizio Domba прольёт свет на проблему. Возможны 2 распространённых решения:

  1. включить ajax-валидацию у формы, тогда по мере заполнения будут показываться ошибки и их можно исправлять по ходу заполнения до отправки всей формы (как это сделано у нас на loco.ru при отправке комментария);
  2. загрузку картинки сделать в отдельной форме (как на многих форумах реализована загрузка аватарки).