БлогNot. PHP: сервис одним файлом

PHP: сервис одним файлом

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

1. Форма + текст

Если речь идёт о Web-приложении, которое должно выдавать результаты "текстом" и на ту же страницу, с которой вводились данные формы, удобной представляется следующая организация скрипта (листинг):

<!DOCTYPE html>
<html lang="ru">
<head>
 <meta charset="utf-8">
 <title>Заголовок страницы</title>
</head>
<body>

<?php
 //Блок 0 - пара функций для безопасной обработки данных
 function trimall($string) { return trim(preg_replace("/\s+/"," ",$string)); }
 
 function magic ($path) {
  @ini_set('magic_quotes_runtime', '0'); @ini_set('magic_quotes_sybase', '0');
  if (@get_magic_quotes_gpc()=='1') $path=stripslashes($path); //magic_quotes_gpc не меняется программно - см. доки
  return $path;
 }
 
 //Блок 1 - обработка разрешённых скрипту параметров
 $params = array ('name','gender','education','php','cpp','java','text','action');
 //список разрешенных в скрипте параметров, все переменные создадутся автоматически из данных $_POST/$_GET или пустыми
 foreach ($params as $num=>$var) {
  if (!empty($_POST[$var])) $$var = trimall(htmlspecialchars(magic($_POST[$var])));
  else if (!empty($_GET[$var])) $$var = trimall(htmlspecialchars(magic($_GET[$var])));
  else $$var = '';
 }
 
 //Блок 2 - вывод и заполнение формы 
 echo '
 <form name="f1" method="post" action="'.$_SERVER['PHP_SELF'].'">
  <table align="center" border="0" cellpadding="4" cellspacing="0" width="90%">
   <caption>Пример формы с обработкой и сохранением ввода после отправки</caption>
   <tr>
    <td>Имя:</td>
    <td>
     <input type="text" name="name" maxlength="40" size="40" value="'.$name.'">
    </td>
   </tr>
   <tr>
    <td>Пол:</td>
    <td>
     <input name="gender" value="m" type="radio" '.($gender=='m'?'checked':'').'>Мужской
     <input name="gender" value="f" type="radio" '.($gender=='f'?'checked':'').'>Женский
    </td>
   </tr>
   <tr>
    <td>Образование:</td>
    <td>
     <select name="education" size="1">';
 $select_values = array ('none'=>'Не выбрано','middle'=>'Среднее или ниже',
  'high'=>'Высшее или ученая степень'); //Возможные значения списка => подписи
 foreach ($select_values as $key=>$value) {
  echo '<option value="'.$key.'"'.($education==$key?' selected':'').'>'.$value;
 }
 echo '
     </select>
    </td>
   </tr>
   <tr>
    <td>Какие языки программирования Вы знаете:</td>
    <td>';
 $checkbox_values = array ('php'=>'PHP','cpp'=>'C++','java'=>'Java'); //Переменные флажков => подписи	 
 foreach ($checkbox_values as $key=>$value) {
  echo '<input type="checkbox" name="'.$key.'" value="1"'.($$key==1?' checked':'').'>'.$value;
 }	  
 echo '
    </td>
   </tr>
   <tr>
    <td>Ваш комментарий (до 1 Кб):</td>
    <td>
     <textarea name="text" rows="6" cols="72" maxlength="1024">'.(!empty($text)?"$text":'').'</textarea>
    </td>
   </tr>
   <tr>
    <td>&nbsp;</td>
    <td>
     <input type="submit" name="action" value="Отправить"> 
     <input type="reset" value="Отмена">
    </td>
   </tr>
  </table>
 </form>';

 //Блок 3 - расчёт и вывод результатов
 if (!empty($action)) { 
  //Здесь обаботка данных и вывод результатов обработки
  echo 'Имя="'.$name.'"<br>'.
   'Пол="'.$gender.'"<br>'.
   'Образование="'.$education.'"<br>'.
   'PHP="'.$php.'"<br>'.
   'C++="'.$cpp.'"<br>'.
   'Java="'.$java.'"<br>'.
   'Текст=<br>'.nl2br($text);
 }
?>
</body></html>

Здесь я сознательно использовал все основные интерфейсные элементы классического HTML4, чтобы показать, как в них передавать данные "с сохранением" предыдущего ввода.

Этот код сохраняет заполненную часть формы, "чистит" данные от лишних разделителей (функция trimall), убирает внедрённую в данные разметку (htmlspecialchars) и решает php-шные проблемы с кавычками (magic, актуально для версий PHP ниже 5.4, в современных версиях эту функцию и её вызов можно удалить).

При работе с кириллицей в кодировке Юникода UTF-8 имеет смысл один раз вызвать в начале скрипта

mb_internal_encoding('UTF-8');

Обработчиком формы назначен текущий файл, как бы он ни назывался (указание $_SERVER['PHP_SELF'])

Форма выводится операторами echo. Так как эти операторы используют одинарные кавычки-апострофы, их не должно быть в формируемой разметке HTML.

Мне кажется, это в данном случае лучше, чем писать просто HTML вне тега <?php ... ?> и потом делать кучу PHP-вставок для подстановки значений в форму.

Разрешены и GET, и POST. Также обратите внимание, как выглядят списки $select_values и $checkbox_values, применяемые для программного создания нескольких элементов списка и группы радиокнопок.

Если на странице результатов не должна отображаться форма, возьмите блоки 2 и 3 в единый условный оператор, например, так:

if (empty($action)) { 
 //Блок 2
}
else {
 //Блок 3
}

В этом случае в блок 3 имеет смысл встроить ссылочку (а лучше кнопку) для возврата к странице с формой. Почему лучше кнопку? Потому что тогда можно через скрытые поля вернуть ранее введённые данные обратно скрипту, вот код:

echo '<form name="f2" method="post" action="'.$_SERVER['PHP_SELF'].'">';
foreach ($params as $p) if ($p!='action') echo '<input type="hidden" name="'.$p.'" value="'.$$p.'">';
echo '<input type="submit" name="back" value="Назад"> ';
echo '</form>';

Здесь не возвращается параметр action (от кнопки), так как именно по его наличию в данных мы судим, нужно выводить форму или результат.

Если результаты должны открываться в новом окне (вкладке) браузера, просто добавьте в открывающей части тега первой формы стандартный атрибут target:

<form name="f1" method="post" target="_blank" action="'.$_SERVER['PHP_SELF'].'">

Разумеется, есть и другие альтернативы, например, вывести текст с помощью яваскрипта (или яваскрипт-библиотеки вроде JQuery) в заранее созданный раздел <div>. Но для этого надо быть уверенным, что яваскрипт у пользователя включён :)

Ну и PHP-фреймворков тоже хватает, только вот и скорость приложения, и скорость разработки окажутся ниже...

 Посмотреть этот скрипт в работе

2. Форма + графика

При работе с графикой принцип организации скрипта меняется не сильно. Основное отличие - заголовки HTML-документа надо создавать только для страницы с формой, а результат выдаётся в виде картинки, так что там применяется директива header ('Content-Type: image/gif');

Точно так же скрипт может выводить картинку-результат в текущее окно или в новое. Но для случая текущего окна мы не сможем добавить кнопку-форму для возврата к набранным данным, ведь мы передаём другой тип содержимого (картинку GIF).

Так как подобного готового сервиса под рукой не нашлось, написал маленькую "рисовалку узоров". Она похожа на этот сервис, опишу параметры, разрешённые в адресе URL:

  • n - число шагов рекурсии, целое число от 1 до 9 включительно, по умолчанию 3;
  • r - радиус первой окружности в пикселах, целое число от 10 до 500 включительно, по умолчанию 100;
  • back - цвет фона рисунка, должен быть передан в виде RRGGBB, где RR, GG, BB - 16-ричные интенсивности красного, зелёного и синего со значениями от 00 до FF включительно (в десятичных числах от 0 до 255), то есть, применяется типовая система записи цвета RGB. Если передано любое значение back меньше 0, фон будет прозрачным. По умолчанию белый;
  • lines - цвет линий, также в виде RRGGBB, по умолчанию красный.

Размер картинки определяется автоматически, но не может превышать 1000 пикселов. Картинка всегда квадратная.

Полный листинг скрипта приводится ниже:

<?php
if (!isset($_GET['submit'])) {
 echo '
  <!DOCTYPE html>
  <html lang="ru">
  <head>
   <meta charset="utf-8">
   <title>Rounder</title>
  </head>
  <body>
  <form method="get" action="'.$_SERVER['PHP_SELF'].'" target="_blank">
  <table border="0">
   <tr>
    <td><p>Порядок рекурсии</p></td>
    <td>
     <select name="n" size="1">
      <option value="1">1
      <option value="2">2
      <option value="3" selected>3
      <option value="4">4
      <option value="5">5
      <option value="6">6
      <option value="7">7
      <option value="8">8
      <option value="9">9
     </select>
    </td>
   </tr>
   <tr>
    <td><p>Начальный радиус, пикселов</p></td>
    <td>
     <select name="r" size="1">
      <option value="50">50
      <option value="100" selected>100
      <option value="200">200
      <option value="300">300
      <option value="400">400
      <option value="500">500
     </select>
    </td>
   </tr>
   <tr>
    <td><p>Цвет фона RRGGBB (-1 = прозрачный)</p></td>
    <td>
     <input type="text" name="back" value="-1" size="6" maxlength="6">
    </td>
   </tr>
   <tr>
    <td><p>Цвет линий RRGGBB</p></td>
    <td>
     <input type="text" name="lines" value="FF0000" size="6" maxlength="6">
    </td>
   </tr>
   <tr>
    <td><p><small>Сторона картинки - до 1000 пикселов, время выполнения - до 15 сек.</small></p></td>
    <td>
     <input type="submit" name="submit" value="OK">
    </td>
   </tr>
  </table>
  </form>
  </body></html>
 ';
 exit(0);
}

set_time_limit (15); //Лимит времени выполнения

function Circle($img,$color,$x,$y,$r,$n) {
 if ($n>=0) {
  imageellipse ($img,$x,$y,$r<<1,$r<<1,$color);
  Circle($img,$color,$x+$r,$y,round($r/2),$n-1);
  Circle($img,$color,$x,$y-$r,round($r/2),$n-1);
  Circle($img,$color,$x-$r,$y,round($r/2),$n-1);
  Circle($img,$color,$x,$y+$r,round($r/2),$n-1);
 }
}

//Обработка параметров
function get_rgb_from_hex ($hex) { //Цвет RRGGBB (Hex) в массив (r,g,b) десятичные
 $rgb=array();
 for ($i=0; $i<6; $i+=2) $rgb[]=hexdec(substr($hex,$i,2));
 return $rgb;
}

$params = array ('n','r','back','lines');
foreach ($params as $num=>$var) {
 if (!empty($_GET[$var])) $$var = intval($_GET[$var]);
 else $$var=0;
}

if ($n<1 or $n>9) $n=3; // Порядок рекурсии
if ($r<10 or $r>500) $r=100; // Начальный радиус 10-500
$width = 0; $pow2n = 1;
for ($i=0; $i<=$n; $i++) { $width+=round($r/$pow2n); $pow2n<<=1; }
$width=$width*2+8;
if ($width>1000) $width=1000; // Размер стороны картинки обрезается до 1000 пикселов

$transparent=false; //Прозрачность
$rback=$gback=$bback=255;
if (isset($_GET['back'])) { //Цвет фона RRGGBB, если меньше 0 - прозрачный
 $back=htmlspecialchars(trim($_GET['back']));
 if (preg_match("#^[0-9A-Fa-f]{6}$#",$back)) list ($rback,$gback,$bback) = get_rgb_from_hex ($back);
 else if (intval($back)<0) $transparent=true;
}
$rlines=255; $glines=$blines=0;
if (isset($_GET['lines'])) { //Цвет линий RRGGBB
 $lines=htmlspecialchars(trim($_GET['lines']));
 if (preg_match("#^[0-9A-Fa-f]{6}$#",$lines)) list ($rlines,$glines,$blines) = get_rgb_from_hex ($lines);
}

//Отрисовка
if (!extension_loaded('gd')) die('Cannot initialize GD library');
$img = @imagecreatetruecolor($width, $width) or die ('Cannot initialize new GD image stream');
$back = imagecolorallocate($img,$rback,$gback,$bback); 
$lines = imagecolorallocate($img,$rlines,$glines,$blines);
imagefilledrectangle ($img, 0, 0, $width, $width, $back);
if ($transparent) imagecolortransparent($img, $back);
Circle($img,$lines,round($width/2),round($width/2),$r,$n);

//Вывод
header ('Content-Type: image/gif');
imagepng ($img);
imagedestroy($img);
?>

В тексте скрипта видно, что все ограничения на значения входных данных обязательно проверяются на стороне сервера. Это нужно делать всегда, ведь нетрудно написать "альтернативную" клиентскую форму, в которой ваших ограничений не будет и которая "положит" сервер, например, попытавшись создать сверхогромную картинку.

 Посмотреть этот скрипт в работе

07.06.2013, 16:31 [14274 просмотра]


теги: программирование php сервис

К этой статье пока нет комментариев, Ваш будет первым