Уроки PHP - урок 4 - Работа с изображениями, библиотека GD2.
В прошлых уроках мы разобрались как писать запросы к БД, поэтому сейчас мы будем меньше внимания уделять как писать запросы, а будем просто тренероваться их писать. Будем также комбинировать написание запросов с изучением других возможностей PHP, начнем с обработки изображений. В одном из прошлых уроков мы уже загружали файлы, у нас даже есть таблица Files для загруженных файлов. Давайте загружать изображения в эту же таблицу. Но сначала нужно добавить поле для загрузки фотографии в форму создания материала.
$content .= '<label for="bodytext">Сообщение:</label><br />'; $content .= '<textarea name="bodytext" id="bodytext"></textarea><br />'; $content .= '<label for="bodytext">Прикрепить файл:</label><br /><input type="file" name="filename"><br />'; // поле загрузки файла $content .= '<label for="bodytext">Прикрепить изображение:</label><br /><input type="file" name="image">'; // поле загрузки файла
Теперь нужно написать обработку файлов, но для начала добавим еще один столбец в таблице Messages, куда мы будем записывать fid изображений, а данные изображения мы будем записывать в таблицу Files, как и с остальными файлами. Колонку я назвал image_fid ее характеристики следующие INT(11) и NULL по умолчанию. Теперь давайте вставим обработку формы в метод write():
if($_FILES["image"]["size"] > 1024*3*1024){
echo ("Размер файла превышает три мегабайта");
exit;
}
if(is_uploaded_file($_FILES["image"]["tmp_name"])){ // Проверяем загружен ли файл
// Если файл загружен успешно, перемещаем его
// из временной директории в конечную
move_uploaded_file($_FILES["image"]["tmp_name"], "./files/".$_FILES["filename"]["name"]);
} else {
echo("Ошибка загрузки файла");
}
$sql = 'INSERT INTO Files (filename, filepath, filemime, filesize, timestamp)
VALUES ("'. $_FILES['image']['name'] . '",
"files/' . $_FILES['image']['name'] . '",
"'. $_FILES['image']['type'] .'",
'. $_FILES['image']['size'] .',
'. time() . ')';
mysql_query($sql);
Эта обработка не будет ничем отличаться от загрузки обычных файлов, хотя в одном из следующих уроков мы будем разбирать как сделать проверку какой тип файла загружается, чтобы под видом изображений не загружали скажем pdf-документы.
Обработку мы написали, теперь будут загружаться два файла, один из которых изображение.Вот такой массив $_POST и $_FILES у меня получился при распечатке через print_r():
Array ( [title] => test [bodytext] => asdfasf ) Array ( [filename] => Array ( [name] => ip.txt [type] => text/plain [tmp_name] => Y:\tmp\php2C5.tmp [error] => 0 [size] => 13 ) [image] => Array ( [name] => Coat_of_arms_of_Vladimiri_Oblast.png [type] => image/png [tmp_name] => Y:\tmp\php2C6.tmp [error] => 0 [size] => 393743 ) )
Нужно еще изменить запрос вставки сообщения:
$sql = 'INSERT INTO Messages (title, bodytext, created, fid, image_fid) VALUES ("'. $p["title"] . '", "' . $p["bodytext"] . '", ' . time() . ',LAST_INSERT_ID()-1, LAST_INSERT_ID())';
Для определения fid обычного файла и изображения я использую функцию LAST_INSERT_ID(), так как обычный файл вставляет перед изображением, то я отнимаю единицу от последнего id.
Теперь когда изображения вставляются в БД, мы можем выводить эти изображения. Но для работы с изображениями мы сделаем заготовку, отдельный класс, который назовем simpleImage. Создадим новый файл simpleImage.php в папке class и там будем писать код нашего класса.
Открываем файл simpleImage.php и пишем в нем:
<?php
class simpleCMS {
}
?>
Итак, мы создали новый класс для работы с изображениями, теперь нужно будет создать методы для работы с ним. Но сначала я расскажу о видах переменных класса в PHP.
Свойства класса в PHP 5
Раньше в PHP4 все было просто, свойства класса определялись как var, а вот в 5ой версии PHP появилось больше свободы в определение свойств класса. Начнем с оператора public, потому что мы его уже использовали.
Public
Через оператор public мы заявляем открытую, публично доступную переменную класса. Таким образом мы можем менять значение этой переменной в созданном объекте, просто присвоить нужное нам значение, например так:
<?php $obj = new simpleCMS(); $obj->db='newDB'; ?>
Так мы можем обращаться к переменной объекта, еще раз, потому что переменная была определена как public.
Protected
Оператор protected показывает, что свойство или метод доступно внутри класса или из его производного класса (о наследование и производных классах мы поговорим позже). Если мы определим сейчас свойство db как protected и вызовем это свойство в коде, то это вызовет ошибку:
simpleCMS.php ... protected $db = 'testDB'; ... index.php ... $obj = new simpleCMS(); $obj->db='newDB'; ...
Давайте теперь добавим защищенную переменную path (путь к файлу), для того чтобы потом использовать ее в методах класса. Но для того чтобы передавать путь к файлу мы будем использовать конструктор.
Конструктор класса
Конструктор класса используется в PHP для того чтобы задать начальное состояние создаваемому объекту, например мы будем задавать путь к файлу для создаваемого объекта изображения. Конструктор записывается как и все остальные методы, в начале его именни два нижних подчеркивания:
class simpleCMS {
protected $path;
public __construct($image){
$this->path = $image;
}
}
Таким образом мы передаем путь к файлу через параметр конструктора и записываем в защищенную переменную пути к изображению. Теперь нужно изменить метод public_display для класса simpleCMS, чтобы выводились данные о изображениях.
if(!empty($row['image_fid'])){
$sql = "SELECT * FROM Files WHERE fid=".$row['image_fid'];
$image_query = mysql_query($sql) or die(mysql_error());
$image = mysql_fetch_array($image_query);
$content .= '<div class="image">';
$image = new simpleImage($image['filepath']);
$content .= $image->scale(100,100);
$content .= '</div>';
unset($image);
}
Так мы выводим из БД путь к файлу. Теперь, когда мы вывели путь к файлу, давайте писать методы для обработки размеров изображения. Сделаем функции как в друпале:
public function scale($width,$height){
}
public function scale_and_crop($width,$height){
}
Давайте начнем с функции масштабирования scale().
public function scale($width, $height){
$path = $this->path;
$filepath = explode('/',$this->path); //разделим папки и имя файла
$filename = end($filepath); // забираем последний элемент - имя файла
$filename = substr($filename, 0, -4); //убираем из имени файла расширение
$filename = str_replace(' ', '_', $filename); //убираем пробелы из имени файла
$extenstion = substr($this->path, -4); // проверяем расширение файла png, gif, jpg
switch ($extenstion){
case '.png':
$srcImage = ImageCreateFromPNG( $path );
break;
case '.jpg':
$srcImage = ImageCreateFromJPEG( $path );
break;
case '.gif':
$srcImage = ImageCreateFromGIF( $path );
break;
default:
$srcImage = ImageCreateFromGIF( $path );
break;
}
// определяем изначальную высоту и ширину картинки
$srcWidth = ImageSX( $srcImage );
$srcHeight = ImageSY( $srcImage );
// следующий код проверяет если ширина больше высоты
// или высота больше ширины картинки так, чтобы
// при изменении сохранилась правильная пропорция
$ratioWidth = $srcWidth/$width;
$ratioHeight = $srcHeight/$height;
if( $ratioWidth < $ratioHeight){
$destWidth = $srcWidth/$ratioHeight;
$destHeight = $height;
}else{
$destWidth = $width;
$destHeight = $srcHeight/$ratioWidth;
}
// создаем новую картинку с конечными данными ширины и высоты
print $destWidth. '<br />' .$destHeight;
$destImage = imagecreate( $destWidth, $destHeight);
// копируем srcImage (исходная) в destImage (конечную)
ImageCopyResized( $destImage, $srcImage, 0, 0, 0, 0, $destWidth, $destHeight, $srcWidth, $srcHeight );
//создаем jpeg
imagejpeg( $destImage, 'files/presets/'.$width.'_'.$height.'_'. $filename . '.jpg' ,100 ); //необходимо создать папку presets в папке files
$newImagePath = 'files/presets/'.$width.'_'.$height.'_'. $filename . '.jpg';
// освобаждаем память
ImageDestroy( $srcImage );
ImageDestroy( $destImage );
return '<img src="'. $newImagePath . '" width="'. $destWidth.'" height="'. $destHeight . '" />'; // выводим изображение
}
Файлы обработанных изображений мы будем хранить в папке files/presets, нужно чтобы папка presets была создана и на нее были права доступа 777.
Думаю, что я подробно описал алгоритм работы в комментариях, поэтому давайте перейдем к методу scale_and_crop(), правда он мало чем отличается от scale():
public function scale_and_crop($width, $height){
$path = $this->path;
$filepath = explode('/',$this->path); //разделим папки и имя файла
$filename = end($filepath); // забираем последний элемент - имя файла
$filename = substr($filename, 0, -4); //убираем из имени файла расширение
$filename = str_replace(' ', '_', $filename); //убираем пробелы из имени файла
$extenstion = substr($this->path, -4); // проверяем расширение файла png, gif, jpg
switch ($extenstion){
case '.png':
$srcImage = ImageCreateFromPNG( $path );
break;
case '.jpg':
$srcImage = ImageCreateFromJPEG( $path );
break;
case '.gif':
$srcImage = ImageCreateFromGIF( $path );
break;
default:
$srcImage = ImageCreateFromGIF( $path );
break;
}
// определяем изначальную высоту и ширину картинки
$srcWidth = ImageSX( $srcImage );
$srcHeight = ImageSY( $srcImage );
// следующий код проверяет если ширина больше высоты
// или высота больше ширины картинки так, чтобы
// при изменении сохранилась правильная пропорция
$ratioSource = $srcWidth/$srcHeight;
$ratioNew = $width/$height;
if( $ratioSource < $ratioNew){
$newWidth = $srcWidth;
$newHeight = $srcHeight / $ratioNew * $ratioSource; // расчитываем новую высоту изображения пропорционально заданной ширине
}else{
$newWidth = $srcWidth / $ratioSource * $ratioNew; // рассчитываем новую ширину изображения пропорционально заданной высоте
$newHeight = $srcHeight;
}
// создаем новую картинку с конечными данными ширины и высоты
$destImage = imagecreate($width, $height); // отличие от scale()
// копируем srcImage (исходная) в destImage (конечную)
imagecopyresampled( $destImage, $srcImage, 0, 0, 0, 0, $width, $height, $newWidth, $newHeight);
//создаем jpeg
imagejpeg( $destImage, 'files/presets/'.$width.'_'.$height.'_'. $filename . '.jpg' ,100 ); //необходимо создать папку presets в папке files
$newImagePath = 'files/presets/'.$width.'_'.$height.'_'. $filename . '.jpg';
// освобаждаем память
ImageDestroy( $srcImage );
ImageDestroy( $destImage );
return '<img src="'. $newImagePath . '" />'; // выводим изображение
}
Также нужно будет поменять строку вывода в методе display_public():
$content .= '<div class="image">'; $image = new simpleImage($image['filepath']); $content .= $image->scale_and_crop(100,100); $content .= '</div>';
Таким образом картинки будут квадратными и обрезанными, чтобы соблюдать пропорции.
Как в первом, так и во втором случаях картинки генерировались на лету. Генерация картинок занимает время поэтому обычно пути к коротким картинкам запоминают в БД и выводят сразу эти пути, без повторной генерации изображений.
Мы не будем в этом курсе рассматривать как это делать, потому что в построение своей CMS мы будем использовать Zend framework, а эти уроки PHP в первую очередь должны познакомить вас с основами работы PHP.