Уроки 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.