VR
Virtual Reality On-line   Delphi
Новости   |     Журнал    |    Хаkер   |     Магазин   |   Проекты
[   Вход    ]
[Kарта сайтa]
[ Download  ]
[  Конкурс  ]
[  Анекдоты ]
[  Ссылки   ]
[  Реклама  ]
[ Почтальон ]
[ О проекте ]






TopList
Основы PHP.
Делаем форум
:

Привет. В этот раз я расскажу про основы ПХП, классного языка для создания динамических html-страничек (да и не только станичек). Язык относительно простой, так что имея некоторый опыт программинга (наличие которого предполается), ты его легко освоишь. Я в нем лишь чайник (пока), но единственный способ научиться программировать - писать программы. Например, написать форум Хоррифика. Я не люблю разбирать все по разделам, поэтому буду рассказывать по ходу дела.

Итак, окно форума состоит из трех фреймов. Я вынес их в index.html:


<HTML>
<HEAD>
    <TITLE>Programmers&Administrators boards</TITLE>
</HEAD>
<FRAMESET ROWS="100%" COLS="210,*" border=0>
    <FRAME NAME="menu" SRC="show.php?action=menu" NORESIZE FRAMEBORDER=1 
        SCROLLING="YES" frameborder="NO" border=0 MARGINHEIGHT=5 
        MARGINWIDTH=5 framespacing=0>
    <FRAMESET ROWS="50%,50%" border=1>
        <FRAME NAME="headers" SRC="show.php?action=headers" SCROLLING="YES" 
            FRAMEBORDER=1 frameborder="YES" border=1 MARGINHEIGHT=5 
            MARGINWIDTH=5 framespacing=0>
        <FRAME NAME="messages" SRC="show.php?action=messages" SCROLLING="YES" 
          FRAMEBORDER=1 frameborder="YES" border=1 MARGINHEIGHT=5 
          MARGINWIDTH=5 framespacing=0>
    </FRAMESET>
</FRAMESET>
<NOFRAMES>
<BODY bgcolor="#FFFFFF">
<P>
Sorry, you must use a frames-capable browser (such as Microsoft Internet Explorer 3.0 
   or higher or Netscape Navigator 2.0 or higher).
</BODY>
</NOFRAMES>
</HTML>

Хорошо видны ссылки на файл show.php. Дабы в него можно было передать какие-то параметры, используется ? (знак вопроса), после которого идут параметры в формате переменная=значение&переменная=значение... Этот метод передачи параметров называется GET. Теперь будем заполнять первый фрейм. Для этого нам надо откуда-то взять названия разделов и все это вывести. Создаем файл show.php:


<?php

?>

Такой импровизированный тэг даст понять веб-серверу, что надо отдать содержимое этого тэга на съедение PHP, который все это переварит и выс.. выдаст контент, то есть станичку. (Кстати, его можно вставлять и посреди страницы, и не один раз). Теперь внутри этого "тега" пишем:


 $disp='<html>';

 $disp.='</html>';
 echo $disp;

Знаком доллара в ПХП обозначаются переменные. Их не надо заранее объявлять, тип определяется автоматически. Операция присваивания обозначается знаком "равно". После оператора, как в любом нормальном языке, ставится точка с запятой. А что там за точка во второй строке? А это сокращенная запись выражений в стиле Си:

$Dlinnaya_peremennaya=$Dlinnaya_peremennaya+$Eshe_odna_super_dlinnaya_peremen 
эквивалентна $Super_dlinnaya_peremennaya += $Eshe_odna_super_dlinnaya_peremennaya

Короче, если есть строка $a = $a + $b, то убираем вторую $a, и _перед_ "равно" ставим знак операции (точка является операцией соединения строк).

Теперь, если второй строчкой написать $disp.='Hello, world!';, то веб-сервер выдаст то, что всегда первым делом предлагают вывести при изучении нового языка. А что такое echo? Это операция вывода. В данном случае она отдаст веб-серверу содержимое $disp, в которую мы пихаем содержимое страницы.

Ну хорошо, где/как будем данные доставать? Из базы данных. Не важно какой, ПХП поддерживает туеву хучу СУБД. Я решил воспользоваться самой распространенной среди веб-строителей - MySQL, хоть я и сторонник InterBase (научился у DJB фразе "Not reliable!"). Пусть у нас есть база "forum", в ней таблицы razd - названия разделов, и menu - пункты меню. Как их создать, см. ЗЫ. Коннектимся к СУБД:
$dbh=mysql_connect('адрес хоста','логин','пароль') or die('Не могу к базе приконнектиться');
mysql_select_db('forum',$dbh) or die('Не могу базу открыть');
mysql_query('SET OPTION CHARACTER SET cp1251_win');

В переменную $dbh заносится какая-то хрень, идентифицирующая соединение с СУБД. Функция die() заставит ПХП завершить работу и выдать указанную фразу в случае ошибки. mysql_select_db выбирает базу в данном соединении, а третья строка говорит MySQL, что мы типа русские и хотим использовать кодировку win1251.

Так, хорошо, к базе приконнектились, а страничка? Как мы узнаем, что именно генерить-то надо? Для этого мы передали параметр action, который, если включена опция register_globals, будет обычной переменной в нашем скрипте. Для маньяков, у которых register_globals отключена, есть массив $_GET ($HTTP_GET_VARS в старых версиях), из которого можно выдернуть переменную следующим образом: $_GET['action'] (если используется метод передачи данных POST, то соответственно $_POST ($HTTP_POST_VARS)).

Ну и как разбирать-то будем? Есть оператор switch, опять же, в стиле Си:


switch($action) {

 case "menu": 
 ...
 break;
 
 case "headers":
 ...
 break;
}

Такой вот эквивалент нашему дельфяжному case. Смысл такой: $action сравнивается со значением после слова case, и, если совпадает, то выполняется код до слова break. Фигурные скобки {} эквивалентны begin end. Таким образом, если юзер запросит левый фрейм, то есть установит $action='menu', то выполнится одно, если $action='headers', то другое, а если кулхацкер Вася Пупкин задаст $action='hrehoten', то он получит пустую станицу, так как ни один обработчик не будет вызван.

Ну, давайте будем генерить меню:
$disp.='<body bgcolor="#000000" >'; - установим черный цвет фона
$res=mysql_query('select name,id from razd order by orderid,id');- запросим названия разделов. В $res записывается результат запроса.


while ($row=mysql_fetch_row($res)) {
..
} 

О, что-то новенькое. Оператор while особо ничем не отличается от Делфи, а вот проверка условия опять в стиле доставшего всех Си (но удобно, блин!). Замутка такая: выражение $a=$b (a присвоить значение b) само имеет значение $b, то есть mysql_fetch_row($res) читает очередную запись из $res, это записывается в $row, да еще и while-у передается.(то есть можно написать $q=$w=$e=$r :-)). А когда mysql_fetch_row() даст NULL, то while наконец-то успокоится, так как это не что иное как false.


// это комментарий
$disp.='<table width="100%" CELLPADDING="2" CELLSPACING="0"'.
'BORDER="1" BGCOLOR=#555555 BORDERCOLOR=#999999>'.
'<th BGCOLOR="#999999"><font color="white"><b>'.$row[0].'</b></font><th>';
// создали таблицу на страничке
$res2=mysql_query('select name,id,hint from menu where groupid='.$row[1].' order by orderid,id');
// запросили пункты меню из данного раздела (groupid в menu = id в razd)
	while ($row2=mysql_fetch_row($res2)) {
	  $disp.='<tr><td><a href="show.php?action=headers&menuid='.$row2[1].
	'" target="headers"><font color="white"><ABBR title="'.$row2[2].'">'.
	$row2[0].'</ABBR></font></a></tr>';
	// создаем строчки таблицы
	}

Здесь мы создаем линки, кликнув по которым в правый верхний фрейм (target="headers") загрузится страница show.php?action=headers&menuid=<номер меню>Я еще попытался добавить простейшие хинты к ним, но MS Експлопер их почему-то игнорирует (у меня-то Опера, там все круто)

Функция mysql_fetch_row() выдает массив с пронумерованными элементами (то есть обычный), в котором содержится очередная запись из набора данных. Однако некоторые любят mysql_fetch_assoc(), которая выдает массив с именованными полями, например: $row['name']. Есть также универсальная функция mysql_fetch_array(), которой второй параметр указывает что надо вернуть (более подробную информацию ищите в мануале).

Ну, менюшки вроде грузятся. Переходим ко второму фрейму:


case "headers":                                                             
	if (!isset($menuid)) break;

Ага, всем знакомый оператор условного перехода (во как if по-умному называется :-)). Но оригинальное отличие: нет слова then (ладно хоть else оставили), то есть сразу идет оператор. Если захочешь вставить несколько операторов, то не забудь про эквивалент begin .. end;, то бишь фигурные скобки. Еще условие обязательно надо заключать в скобки, иначе ПХП будет ругаться. Функция isset() чем-то похожа на Assigned() в Delphi: она возвращает TRUE, если переменная существует, и FALSE, если наоборот. Есть еще почти такая же функция empty(), но она возвращает FALSE, если переменная равна нулю или NULL. А наличие $menuid для нас критично, тк мы должны выбрать мессаги из определенного раздела.

"Эй, чувак, неувязочка у тебя, - скажет читатель, - при наличии $menuid условие не выполнится и наоборот! Да еще восклицательный знак какой-то.."

Не, все нормально: восклицательный знак эквивалентен слову not в паскале, то есть он true меняет false, и наоборот.

Мессаги надо как-то группировать, чтобы знать, кто кому отвечает и соответственно делать отступы. Для них я сделал таблицу messages:


id - primary key
menuid - id темы, в которую постят мессаги
groupid - номер группы мессаг
lev - отступ
head - заголовок
nick - имя юзера
link - мыло
txt - текст мессаги
instime - время вставки

В приведенном выше куске исходника мы передаем menuid, по которому и идет выборка.

Полный текст обработчика:


//Если юзер запросил заголоки мессаг
case "headers":                                                             
 // $menuid нам необходим
 if (!isset($menuid)) break;
 // формируем линк `создание вопроса`
 $disp.='<body><a href="show.php?action=insert&menuid='.$menuid.
 '" target="messages">Добавить вопрос</a><hr><table width="100%" 
        CELLPADDING="0" CELLSPACING="0" BORDER="0" '.
 // пошла таблица с заголовками таблицы заголовков :-)
 'BGCOLOR="#FFFFFF" BORDERCOLOR="#FFFFFF"><tr><td align="left" width="60%"><font'.
 ' face="Verdana" size="-2"<Тема</font></td><td width="20%" align="right"><font'.
 ' face="Verdana" size="-2">Имя</font></td><td width="20%" align="right"><font'.
 ' face="Verdana" size="-2">Дата [время]</font></td></tr></table>';
 $disp.='<table width="100%" CELLPADDING="0" CELLSPACING="0" BORDER="0" BGCOLOR="#FFFFFF" 
      BORDERCOLOR="#FFFFFF">';
 //запрашиваем заголовки мессаг.
 $res=mysql_query('select head,id,nick,link,instime,lev from messages where menuid='.$menuid.' 
   order by groupid desc,lev asc , id asc');
 while ($row=mysql_fetch_row($res)) {
  $disp.='<tr><td align="left" width="58%" class="myclass2">';
  for ($i=0;$i<$row[5];$i++) { 
   $disp.='&nbsp;&nbsp;';
   };
  $disp.='<a href="show.php?action=messages&id='.$row[1].'" 
    target="messages">'.$row[0].'</a></td>'.
  '<td width="20%" align="right" class="myclass2">'. ((empty($row[3])) ? $row[2] : 
  '<a href="mailto:'.$row[3].'">'.$row[2].'</a>') .'</td>';
  $disp.='<td width="22%" align="right" class="myclass2">'.$row[4].'</td></tr>';
  }
 $disp.='</table>';
 break;

Так как номер группы постоянно увеличивается, то, чтобы сначала показать новые сообщения, сортируем по groupid по убыванию (order by groupid desc), по lev по возрастанию - чтобы сначала первый уровень показать, затем второй.., а по id - так, для верности (см. "//запрашиваем заголовки.."). Затем в цикле вставляем линки, а перед линком надо вставить пробелы (чтобы явно сказать браузеру, что мы хотим вставить пробел, используется такая шняга: &nbsp;). Для этого мы выбрали значение lev из таблицы. Тут появляется оператор цикла for, который, как вы, наверное, уже догадались, в стиле вездесущего Си: в скобках через точку с запятой указываются три вещи: инициализация переменной цикла, условие, операция приращения переменной цикла.

Здесь надо отметить операцию ++ - это аналог inc() в паскале, но более хитрый: если его поставить после переменной ($a++), то значение этого выражения будет равно переменной до увеличения на единицу, а если перед переменной (++$a), то все это будет равно переменной, увеличенной на единицу. В цикле, конечно, пофиг, где эти плюсы ставить, но потом..

Теперь формируем линк: мы должны передать action=messages, то есть надо показать саму мессагу, и что именно показать - id записи, так как оно уникально. target="messages" укажет, что эта хрень должна загрузиться в третий фрейм по имени messages.

Хорошо, линк есть, отображаем имя как линк на емайл. А что делать, если юзер не указал мыло? Естественно, не делать имя линком. Ладно, а где тогда ветвление? Ну, внимательные, наверно, обратили внимание на эту строку:


((empty($row[3])) ? $row[2] : '<a href="mailto:'.$row[3].'">'.$row[2].'</a>')

Здесь использован так называемый тернарный оператор: это такая "встраиваемая" версия if-а. Все очень просто: сначала условие, вопросительный знак, что подставляем, если условие истинно, затем двоеточие, и что подставляем, если условие ложно. Но приоритет этой операции очень низок, поэтому рекомендуется все это хозяйство помещать в скобки. Здесь вроде все.

Теперь самое простое: показать мессагу.


//юзер запросил мессагу
   case "messages":
	// без id мы никуда
	if (!isset($id)) break;
	$disp.='<body><a href="show.php?action=insert&id='.$id.'" target="messages">
            Добавить ответ</a><hr>';
	$row=mysql_fetch_row(mysql_query('select txt from messages where id='.$id));
	$disp.=StripSlashes($row[0]);
   break;

ID нам дано, делать нечего - выбрали, показали.. А про StripSlashes немного ниже скажу.

Теперь рекомендую вручную забить несколько мессаг и посмотреть, как это работает, и работает ли вообще :-). Если не работает, то сверяйтесь с исходником.

Теперь приступаем к самому сложному - вставке мессаг. Такие вещи надо разрабатывать внимательно, так как кулхацкер Вася Пупкин не дремлет.

Что небходимо для вставки мессаги? Имя юзера, его емайл, заголовок и текст сообщения, ID темы, ID группы, если это ответ, а также lev, то есть отступ. Давайте так: если это вопрос, то передаем ID темы, а если это ответ, то передаем ID мессаги, на которую отвечаем.


case "insert":
if (isset($id)) {
$row=mysql_fetch_row(mysql_query('select head,menuid,groupid,lev from messages where id='.$id)) or die('Error in insert');

$subj=$row[0];
$menuid=$row[1];
$groupid=$row[2];
$lev=$row[3]+1;
} else {
if (!isset($menuid)) break;
$lev=0;
unset($groupid);
};

Сначала проверяем наличие ID - если есть, то запрашиваем данные этой мессаги, и явно устанавливаем их.

Если нам передали menuid, то явно устанавливаем lev и уничтожаем groupid с помощью процедуры unset().


if (empty($head) or empty($nick) or empty($text)) { 
...

Затем проверяем наличие необходимых параметров - здесь необходимо использовать функцию empty(), так как переменная может существовать и быть пустой. Если чего-то не хватает, то показываем форму (не буду приводить этот кусок, он большой и неинтересный); если все в порядке, то переходим к вставке:


...
} else { 
//вставка мессаги
 if (empty($groupid)) {
  $row=mysql_fetch_row(mysql_query('select max(groupid)+1 from messages where menuid='.$menuid));
   if (empty($row[0])) { $groupid=1; } else { $groupid=$row[0]; };
 }
 $sql='select count(*) from messages where menuid='.$menuid. ' and groupid='.$groupid. 
 ' and txt="'.AddSlashes(HTMLSpecialChars($text)).'"';
 $row=mysql_fetch_row(mysql_query($sql));
 if ($row[0]>0) break;
 $sql='insert into messages (menuid,groupid,lev,head,nick,link,txt,instime)'.
 'values ('.$menuid. ',' .$groupid. ',' .$lev.  ',"' .AddSlashes(HTMLSpecialChars($head)). 
 '","' .AddSlashes(HTMLSpecialChars($nick)). '","' .AddSlashes(HTMLSpecialChars($email)). 
 '","' .AddSlashes(HTMLSpecialChars($text)). '", \'now\')';
 mysql_query($sql);
 Header('Location: send.html');
};

Сначала проверяем наличие $groupid - если отсутствует, то выбираем максимальный, вернее, на единицу больше. Про этот запрос нужно громко кричать "Not reliable!!", потому что если два юзера одновременно выполнят такой запрос и получат одинаковые результаты, то оба вопроса окажутся в одной группе. Но вероятность этого достаточно низка, поэтому, как говорят физики, этим можно пренебречь. Затем смотрим, а вдруг уже есть такая мессага? (ну любят люди F5 жать) Если есть, то выходим. Теперь формируем запрос вставки. Для того, чтобы нехорошие люди не вставляли html-тэги, в ПХП есть специальная функция HTMLSpecialChars(), которая преобразует все спецсимволы в корректные html-эквиваленты. А чтобы сам ПХП и MySQL корректно обработали всякие кавычки и прочее перед ними ставится слэш. И есть функция AddSlashes(), которая их расставляет. Обязательно используй эту функцию! Иначе кулхацкер Вася Пупкин может задать какой-нибудь параметр вот так:

"Vasya; select password from table_with_passwords ". И СУБД вместо ваших данных запросит не то, и в худшем случае Вася получит страницу с паролями. Однако, при выводе данных эти слеши нам нафиг не нужны, поэтому для их убирания используется функция StripSlashes. Процедура Header() добавляет http-заголовки, в данном случае предлагает броузеру пойти на.. страницу send.html, которая скажет юзеру, что сообщение принято. Но она работает, если ты еще ничего не вывел (именно поэтому я использую промежуточную переменную)

Ну, вроде все. Счастливого вам форумостроения!

ЗЫ:

Мини-FAQ

  • Вот открыл я index.html, а в окошках ПХП-шные исходники :-(
    А ПХП у тебя на веб-сервере установлен? Или ты решил, что этот язык интегрирован в твой Интернет Експлопер v.847927645+E18? Если все установлено, то попробуй сменить расширение с php на php3 или 4.
  • А где взять ПХП?
    http://php.net
  • Как мне поставить ПХП на <что-нибудь>?
    Я его ставил только на Пингвинуксе под Апач, поэтому не стал рассказывать..
  • У меня что-то не так работает. Может версия не та?
    У меня стоит PHP v. 4.2.2
  • Я прочитал, но не все (ничего) понял. Что я должен знать?
    Основы програмирования, SQL и HTML
    А может я так отстойно объясняю..
  • Кто такой DJB и как переводится "Not reliable"?
    DJB - профессор Dan J. Берштейн (из какого-то инстика из Америки), который пишет очень надежные в плане безопасности проги, а "Not reliable" - это его любимая фраза, которая переводится как "Не надежно"
  • Где включать register_globals?
    См. php.ini или мануал
  • Строчки исходника здесь и в самом исходнике отличаются
    Я постоянно дорабатывал исходник и не всегда вспоминал про статью.
  • Ты обещал про создание таблиц рассказать
    Да, было дело.
    
    Создание razd:
    CREATE TABLE razd (
      id int(10) unsigned NOT NULL auto_increment,
      name varchar(20) NOT NULL, - название раздела
      orderid int(11) NOT NULL default 0, - порядок сортировки
      PRIMARY KEY  (id));
    
    Создание menu:
    CREATE TABLE menu (
      id int(10) unsigned NOT NULL auto_increment,
      name varchar(30) NOT NULL, - название темы
      orderid int(11) default 0, - порядок сортировки
      groupid int(11) NOT NULL, - ID раздела
      hint varchar(50) default NULL, - подсказка
      PRIMARY KEY  (id)
    
    Создание messages:
    CREATE TABLE messages (
      id int(10) unsigned NOT NULL auto_increment,
      txt text NOT NULL, - текст сообщения
      groupid int(11) NOT NULL, - ID группы сообщений
      menuid int(11) NOT NULL, - ID темы
      head varchar(100) NOT NULL, - заголовок
      nick varchar(30) NOT NULL, - имя юзера
      link varchar(30), - мыло юзера
      instime datetime NOT NULL, - дата/время вставки
      lev int(11) default 0, - количесто отступаемых пробелов
      PRIMARY KEY  (id)
    

    А вообще, в исходниках есть файл export.sql - там все команды. Просто запускаешь mysql -p < export.sql

     Исходники примера забирай здесь


  • Copyright©: Horrific aka Флёнов Михаил
    Design by FMk group©