Раздел "Кодинг". Содержание:
Статьи:
PHP - Что такое РНР
PHP и Web. Кэширование
Классы в php3
Триада PHP & MySQL & gd library - Сервис счетчиков обращений
Работа с Cookies на PHP
Подпишитесь на рассылку от ведущего раздела "Кодинг"
Раздел "Кодинг". Статьи:
PHP - Что такое РНР
 | Андрей Кухарчик |
 | Компьютерная газета |
 | |
Интернет уже давно прочно вошел в нашу жизнь. Это смелое утверждение можно доказывать или опровергать много раз, но так или иначе все меняется, а с этим спорить никто не будет.
Прошли те времена, когда многие пользователи персональных компьютеров в целях экономии дискового пространства удаляли программу "Интернет Эксплорер" из всеми нами обожаемой операционной системы, так как просто не нуждались в ней. Теперь даже те, у кого нет доступа к Паутине, стараются не трогать эту программу, так как форматы сети Интернет давно уже перешли в другие сферы нашей компьютерной жизни. А уж если в компьютере поселился модем, избежать наличия браузера просто невозможно, так как очень хочется хоть одним глазком посмотреть, а что же там, за гранью телефонной линии? И когда знакомство состоялось, отказаться от преимуществ сети уже не так-то просто. Через некоторое время начинаешь осознавать, что надо как-то вложить себя и свои идеи и проявиться на бескрайних просторах Глобальной сети. Сделать свою страничку стало даже престижно, и пусть в ней мало полезного, а счетчик фиксирует только Ваши не частые посещения, сделанное может стать началом серьезного проекта и изменить всю Вашу жизнь.
Путешествуя по просторам глобальной сети Интернет, Вы, конечно же, не раз обращали внимание на гиперссылки, которые порой достигают внешне очень больших размеров и просто приводят в недоумение наличием странных символов. И конечно, все обращали внимание на формы, которые требовалось заполнить и получить что-то взамен. Но немногие задумываются над тем, как все это работает и что все это значит.
Предлагаю Вам окунуться в мир программирования. Этот мир во многом ни на что не похож, но у него есть свои неписаные законы и правила, свои плюсы и минусы. Мир, который открывается перед нами, виден еще только на один шаг, но этот шаг надо осилить, а дальше идти будет уже гораздо легче.
История РНР
Начинать нужно всегда с самого простого, и сейчас я попробую познакомить Вас с программированием на скриптовом языке РНР. РНР (читается как пи-эйч-пи) появился на свет осенью 1994 года, и его создатель — Расмус Лердорф — использовал язык в своих целях, для того, чтобы иметь представление о тех людях, которые посещают его сайт и знакомятся с его резюме.
По словам автора, РНР был написан буквально за день в промежутках между деловыми встречами. Сначала это была просто невзрачная CGI-оболочка, написанная на языке Perl, и служила она исключительно для специфических целей. Для справки — СGI (Common Gateway Interface) — общий шлюзовой интерфейс, является стандартом, который предназначен для создания серверных приложений HTTP.
Такое приложение, которое называют шлюзом или CGI-программой, запускается сервером в реальном режиме времени. Сервер передает запросы пользователя CGI-программе, которая их обрабатывает и возвращает результат своей работы на экран пользователя.
Таким образом, посетитель получает динамическую информацию, которая может изменяться в результате влияния различных факторов. Сам шлюз (скрипт CGI) может быть написан на различных языках программирования — C/C++, Fortran, Perl, TCL, Unix Schell, Visual Basic, Apple Script и других подобных языках. Но в случае с РНР, для написания его в первоначальном варианте был выбран язык Perl, как наиболее простой и доступный.
В результате дальнейшей эксплуатации выяснилось, что оболочка обладает маленькой производительностью, и автор вынужден был переписать все заново, но уже на языке С, что позволило увеличить скорость работы РНР. Пользователи сервера, где располагался сайт с первой версией РНР, попросили себе такой же инструмент, и хоть автор не предполагал, что кто-то другой будет пользоваться этим языком, довольно быстро РНР перерос в самостоятельный проект, и в начале 1995 года вышла первая известная нам версия продукта. Имя этого первого пакета было Personal Home Page Tools (средства для персональной домашней страницы). На тот момент РНР обладал более чем скромными возможностями. Он имел простейший анализатор кода, который понимал несколько специальных команд, а также разные утилиты для использования на домашней странице, необходимые для построения таких полезных вещей, как гостевая книга, счетчик, чат, системы статистики и тому подобное. К середине 1995 года язык был основательно переработан, появилась обработка форм, были добавлены функции работы с базами данных, и в таком виде вышла вторая версия продукта.
Сегодня РНР — это мощный кроссплатформенный набор средств, который располагается на сервере и предназначен для обработки специального кода, встраиваемого в HTML-страницу. Благодаря этому, появляется возможность легко создавать динамические сайты. Файлы, созданные таким образом, хранятся и обрабатываются на сервере, и когда посетитель запрашивает документ с РНР, скрипт обрабатывается не браузером посетителя, как, например, Java Script, а сервером, и посетителю передаются уже только результаты работы. Точно так же работает CGI-программа, написанная на С или Perl. Но, в отличие от CGI, код РНР можно встраивать в любое место HTML-странички, что является основным преимуществом по отношению к CGI. А кроме того, сам язык РНР очень прост для изучения и не требует каких-либо специфических знаний. Например, лично мне вполне хватило опыта, приобретенного лет десять назад на уроках информатики в школе, где мы изучали язык Бейсик на очень модных и дорогих тогда Ямахах. Несмотря на столь радужную характеристику, есть у РНР и недостатки. Стоит отметить довольно медленную (по сравнению с программами на CGI) работу скриптов РНР, а также сложность написания больших и сложных программ. Так или иначе, РНР остается интерпретируемым языком, что непременно ведет к ухудшению производительности в случае очень больших и сложных программ, но, для выполнения несложных манипуляций на сайте, РНР — лучший выбор. Недаром к середине 2000 года РНР использовался более чем на 2,5 миллионах сайтов!
Особенности РНР
Как и у всякого языка программирования, у РНР есть свой синтаксис. Он очень похож на синтаксис языка С или Perl. Программисты, пишущие на этих языках, смогут освоить РНР буквально за несколько дней. Но даже если Вы никогда не программировали, РНР поддастся Вам легко и обеспечит базу для перехода на языки более сложного уровня. Все команды достаточно логичны, а правила просты. Синтаксис включает в себя операторы, разделенные между собой точкой с запятой. Одна из основных ошибок начинающих программистов — отсутствие точки с запятой между операторами. К счастью, ошибки в РНР по умолчанию выдаются на экран (в отличие от CGI, где все ошибки записываются в лог-файл), и найти их при определенной внимательности и опыте не составит большого труда. Тем более что умный интерпретатор подскажет номер строки, в которой произошла ошибка.
Для программирования на РНР Вам понадобится любой текстовый редактор, но для удобства он должен обеспечивать подсветку синтаксиса и нумерацию строк. Я использую CuteHTML, входящий в комплект поставки неплохого FTP-менеджера CuteFTP последних версий. Редактор очень удобный, без лишних ненужных функций, сам встраивается в контекстное меню, не требует инсталляции и имеет все необходимое для программирования. Еще нам понадобится комплект для работы с РНР. Как правило, используется Apache+PHP, хотя это и не обязательно, подходит любой сервер, например IIS Microsoft. Но первый вариант бесплатен и имеет большую поддержку документацией (в том числе на русском языке) и форумами, где можно выяснить любой вопрос.
Теперь давайте перейдем к делу — напишем наш первый скрипт. Для того чтобы сервер знал, в каком из файлов есть код РНР, его расширение (файла) нужно сделать либо phtml, либо php3, либо php. Строго говоря, может быть назначено любое расширение, но я рекомендую Вам в целях совместимости всегда использовать phtml. Каждая команда в РНР как правило начинается с "<?php" и заканчивается "?>" (здесь и далее без кавычек). Как Вы помните, несколько команд разделяются точкой с запятой. В любом месте скрипта РНР можно поставить комментарий, начинается он с "/*", а заканчивается — "*/" Если комментарий маленький и занимает только одну строчку, можно поставить "//" и таким образом легко закомментировать любую строчку до ее конца. Как обычно, пробелы, символы табуляции и перевод строки просто игнорируются и могут применяться для улучшения читабельности кода РНР.
Вывод на экран и переменные в РНР
РНР очень легко позволяет организовать вывод текста на экран. Рассмотрим пример скрипта:
<?php
echo "Привет, мир!";
?>
Этот скрипт может быть расположен в любом месте HTML-документа, и сам по себе он не несет ничего полезного, так как только выводит на экран фразу "Привет, мир!". Но таким образом мы знакомимся с одной из наиболее распространенных команд РНР — вывод информации на экран пользователя. Для того чтобы придать нашему скрипту полезные функции, давайте познакомимся с переменными. Переменная характеризуется именем, типом и значением. Имя может быть любым и включать в себя цифры, буквы английского алфавита и разрешенные символы (например, символ подчеркивания или тире). По типу переменные делятся на целые, с плавающей запятой и символьные. Значение в соответствии с типом может быть практически любым. Например, переменная a=5. Это говорит нам о том, что имя у переменной — а, тип — целочисленный, значение — 5. Вот еще примеры имен и значений:
<?php
$name = 6;
$h12 = 4.89;
$file_type = "path/index.phtml";
$os = "РНР для всех!";
?>
Как видите, все переменные в РНР (но не только в нем) должны начинаться с символа $, что позволяет интерпретатору безошибочно отличать их от команд РНР. В первой строчке нашего скрипта переменной $name присваивается значение 6, и эта переменная автоматически становится целочисленной. Кстати говоря, заранее описывать тип переменной не требуется, как в языках Pascal или Visual Basic, но хотя разделение на типы чисто условное, каждая переменная автоматически стремится использовать правильный тип, соответственно значению. Вторая строчка кода присваивает переменной $h12 значение 4.89, которое является значением с плавающей запятой. Третья и четвертая строчки кода присваивают своим переменным значения, являющиеся символьными строками. Все, что заключено в кавычки (включая цифры), будет интерпретировано как символьная строка. Если переменные не определены ранее, но используются, их значение принимается равным либо нулю, либо пустой строке в зависимости от типа.
Как и в любом языке, над переменными можно совершать любые арифметические действия, и это не требует присутствия специального оператора, достаточно указать переменную для результата, знак равенства и перечислить в естественном порядке переменные или значения с необходимыми арифметическими знаками. Пример:
<?php
$a = 5;
$b = 3;
$c = 4;
$d = $a+$b-$c;
echo $d;
?>
Результат работы скрипта — вывод на экран цифры 4. Поддерживаются все арифметические операции и функции, многоуровневые скобки, логические операции, операции увеличения или уменьшения на единицу и многое другое. Кроме того, очень просто и естественно организуется сравнение если — то — иначе. Для этого в РНР применяется конструкция if ( ) { } else { }. Есть различные варианты синтаксиса этого оператора, но этот — основной, и самый логичный из всех. (если) if (условие) (то) { выполняется то, что заключено в кавычки } (иначе) else { выполняется то, что заключено в кавычки }. После кавычек ставить точку с запятой, как обычно между операторами, не обязательно. Но внутри кавычек — разделение операторов между собой проводится только через точку с запятой. Допускается вложение нескольких операторов проверки один в один. В этом случае надо быть очень внимательным к количеству закрывающих кавычек, так как при отсутствии даже одной интерпретатор выдаст ошибку кода. Рассмотрим несложный пример:
<?php
$a = 5;
$b = 9;
if ( $a == $b ) { echo $b-$a; } else { echo $b.$a; }
?>
При сравнении на истину применяется два знака равенства для того, чтобы интерпретатор мог без труда отличить сравнение от присваивания. Результат работы скрипта — 95, т.к $a не равно $b, а команда echo $b.$a; (между переменными стоит точка, а не знак арифметической операции) выводит подряд указанные переменные. Неравенство (ложь) обозначается символами !=, допустимы все остальные арифметические и логические символы и операторы (например, or, and, >, <= и т.д.).
В РНР есть средства быстрого изменения переменной на единицу в сторону увеличения или уменьшения. Для этого нужно указать имя переменной и за ним, без знака равенства, — подряд два плюса или минуса соответственно. Например, $a++; — переменная $a будет увеличена на единицу. Поддерживается одновременное присваивание одного значения нескольким переменным — $a = $b = 4;. Обе переменных будут равны четырем. Вот еще несколько примеров нестандартых арифметических операций в РНР:
<?php
$b = $a = 5; // присваиваем значения переменым $a и $b
$c = $a++; // последующее увеличение, присваиваем $c
// начальное значение $a (5)
$e = $d = ++$b; // предварительное увеличение, присваиваем $d и $e
// увеличенное значение$b (6) тут и $d и$e равны 6
$f = double($d++); // присвоить удвоенное значение $d до его увеличения,
// то есть 2*6 = 12, переменной $f
$g = double(++$e); // присвоить удвоенное значение $e после его увеличения,
// то есть 2*7 = 14, переменной g
$h = $g += 10; // сначала увеличить значение $g на 10, что дает в
// результате 24, а затем присвоить это значение
// переменной $h, что также дает 24
?>
Вложения файлов в РНР
Каждый, кто сделал хоть одну страничку в сети, сталкивался с проблемой изменения тех или иных данных на ней. Конечно, это не сложно когда страничек несколько или она одна :-), но если Вы сделали большой сайт, маленькое дополнение (например, в меню) в сотни файлов может превратиться в настоящий кошмар! РНР решает эту проблему в раз, позволяя вкладывать одну страницу в другую. Достигается это с помощью операторов REQUIRE и INCLUDE. После этих операторов в круглых скобках должен стоять путь к вкладываемому файлу. Например, INCLUDE ("text.phtml"). Различие между указанными операторами заключается в том, что REQUIRE подменяется содержимым указанного файла и может быть использован только один раз, а INCLUDE — вставляет и выполняет содержимое указанного файла, что позволяет применить его несколько раз, например в цикле. В любом случае, при исполнении файла интерпретатор РНР (правильно говорить — парсер) пребывает в состоянии HTML, и для его включения код надо заключить в конструкцию <?php … ?>. Вложения файлов могут происходить только внутри серверного пространства, доступного РНР. Другими словами, Вы не можете использовать в имени файла http://
Достаточно часто встречаются сайты, ссылки которых включают в себя специальные символы — &, ?, %. Все это может быть и результатом работы РНР. Дело в том, что если в конце ссылки добавить ?имя=значение, это значение будет доступно под этим же именем в файле, куда указывает ссылка. Если необходимо добавить несколько имен, они могут быть разделены знаком &. Теперь мы можем сделать сайт, который будет доступен с помощью только одной странички. А всю остальную информацию эта страничка будет выводить на основании полученных по ссылке данных. Вид такой ссылки будет примерно таким: http://имя.ru/index.phtml?link=1. Единица в конце ссылки и есть наш параметр, который будет подставляться в файле index.phtml. Например, вот так:
<html>
…начало файла …
<?php
$url = "";
if ($link == 1) { $url = "name1.phtml"; }
if ($link == 2) { $url = "name2.phtml"; }
if ($link == 3) { $url = "name3.phtml"; }
if ($link == 4) { $url = "name4.phtml"; }
if ($url == "") { $url = "error.phtml"; }
INCLUDE ($url);
?>
… конец файла …
</html>
Обратите внимание, написанный нами код учитывает ситуацию, когда посетитель по разным причинам указал неправильный параметр. В этом случае выводится заранее заготовленная страничка с сообщением об ошибке. Если же параметр соответствует какому-либо из файлов сайта, он в код файла index.phtml вкладывается и исполняется. Таким образом, начало и конец остаются одинаковыми, а изменяется только середина. И какие-либо изменения уже не кажутся такими страшными, как раньше. Ведь сделать их надо только в одном файле, а отразится это на всем сайте.
Есть и другой путь. Его суть заключается в том, что у РНР есть доступ к так называемым переменным окружения сервера. Одна из этих переменных — запрашиваемый посетителем путь относительно адреса сайта. И этот путь становится нам доступен для использования. В этом случае ссылки у нас будут такого вида: http://имя.ru/index.phtml?patch/name.phtml. Вторая часть ссылки — patch/name.phtml — будет нам доступна, если мы считаем параметр $QUERY_STRING. Например, так: $add = $QUERY_STRING. Теперь изменим наш головной файл index.phtml, чтобы все работало автоматически. А если запрашиваемый параметр не будет указан (правильно говоря — будет равен пустой строке), чтобы что-то открыть, присвоим переменной $add имя файла, который должен быть открыт как главная страничка. Пусть это будет файл main.phtml. Тогда код будет выглядеть следующим образом:
<html>
…начало файла …
<?php
$add = $QUERY_STRING;
if ($add == "") { $add = "main.phtml"; }
INCLUDE ($url);
?>
… конец файла …
</html>
Как видите, еще проще. Но сразу хочу предостеречь Вас, что этот метод хоть и проще первого, но открывает путь к получению информации о сервере, где расположен сайт с такой организацией структуры. Злоумышленник или просто любопытный человек при наличии определенных обстоятельств и знаний сможет много узнать о Вашем сервере, а это открывает прямой путь к взлому. Так что будьте осторожны и не станьте причиной больших неприятностей. Защититься от подобных проблем можно, но это уже совсем другая история.
Пример счетчика посещений на РНР
Если Вы все внимательно прочитали, Вы без труда получите стартовый капитал знаний, и он даст Вам возможность самостоятельно в дальнейшем изучать язык РНР. И в конце я предлагаю Вам познакомиться с кодом скрипта, который позволит организовать на любой из страниц Вашего сайта счетчик посещений. Этот счетчик не будет полнофункциональным, так как имеет достаточно много недостатков, но как пример применения РНР вполне годится. В любом месте Вашей странички (но только там, где это нужно) вставьте следующий код:
<p>Посетителей странички —
<?php
$filename = "counter.dat";
$fp = @fopen($filename,"r");
if ($fp) { $counter=fgets($fp,10); fclose($fp); } else { $counter=0; }
$counter++;
echo $counter;
$fp = @fopen($filename,"w");
if ($fp) { $counter=fputs($fp,$counter); fclose($fp); }
?></p>
В том же каталоге, что и Ваша страничка, создайте файл counter.dat, закачайте его на сервер и с помощью своего FTP-менеджера измените атрибуты этого файла таким образом, чтобы он был доступен для записи. Обычно нужно установить галочки на всех атрибутах файла. Если Вы этого не сделаете, скрипт будет постоянно выдавать ошибку при попытке записи в файл. Кстати, для того чтобы этого не происходило, стоит поставить перед командой записи и открытия файла символ @, он отменит вывод сообщения о возникшей ошибке на экран посетителя. Когда атрибуты изменены, обновите Вашу страницу на сервере и обратитесь к ней по ее адресу в браузере. Вы увидите, что там, где Вы вставили код РНР, появляется строка: "Посетителей странички — " и далее число, соответствующее количеству посещений. И никакого следа кода! Он был обработан на сервере в Интернете, а браузеру просто передан результат этого исполнения.
Алгоритм этого скрипта очень прост. В первой строке мы присваиваем выбранной переменной имя файла, где будет храниться число посещений. Во второй — открываем соединение с этим файлом для чтения. Дальше проверяем успешность соединения, и если файл существует и он доступен для чтения, считываем из него строку из 10 байт, чего более чем достаточно для счетчика, и закрываем соединение с файлом. Увеличиваем показание счетчика на единицу и выводим его новое значение на экран. На следующем этапе нам нужно записать новое значение счетчика, и для этого мы снова открываем соединение (дескриптор) с файлом, но уже на запись с очисткой содержимого файла. Если оно успешно — записываем туда новое значение счетчика и закрываем дескриптор файла. Вот и все.
На этом наше первое знакомство с языком РНР будем считать законченным. Конечно, осталось еще очень много полезного и интересного, но, к сожалению, все сразу охватить невозможно. Впрочем, для того и придумали умные люди Сеть, чтобы сделать доступным недоступное. В Интернете есть очень много полезных ресурсов на тему РНР, и при минимальных навыках работы в поисковиках Вы без труда отыщите их. А я посоветую Вам подписаться на рассылку РНР для всех!, в которой регулярно раз в неделю появляются новые статьи, примеры скриптов, есть раздел вопросов-ответов, а также много другой полезной информации. Найти форму для подписки и подписаться можно по адресу в сети http://virtual.bresttelecom.by/php.
Там же выложены все предыдущие выпуски рассылки. А по адресу http://virtual.bresttelecom.by Вы сможете посмотреть, как все это работает в действии. Все, что Вы встретите на этом сайте, — сделано на РНР. Чат, форум, частные объявления, система статистики — это только малая часть тех возможностей, что дает нам программирование на языке РНР.
PHP и Web. Кэширование
Для оптимизации работы с сетью используется механизм сохранения однажды полученных по HTTP документов в кеше с целью их повторного использования без обращения к серверу-источнику. Документ, сохраненный в кеше будет доступен при следующем обращении к нему, без выгрузки с сервера-источника, что призвано повысить скорость доступа клиента к нему и уменьшить расход трафика сети.
Сами кэши бываю двух видов - локальные и общие. Локальный это кеш, хранимый непосредственно на диске у клиента, создаваемый и управляемый его браузером. Общий - кэш прокси-сервера организации или провайдера и может состоять из одного или нескольких прокси-серверов. Локальный кеш присутствует, наверное в каждом браузере, общими пользуется значительная часть людей использующих Internet. И если малую часть сайтов сейчас оценивают по расходу трафика, то скорость загрузки - важный критерий, который должен учитываться при разработке Вашего web-проекта.
Для динамических страниц, создаваемых в результате работы PHP-программы, казалось бы, кэширование вредно. Содержание страницы формируются по запросу пользователя на основе какого-либо источника данных. Однако, кэширование может быть полезным. Управляя им Вы можете сделать работу с Вашим сервером комфортнее для пользователя, разрешая загрузку из кэш определенных страниц, предотвращая тем самым их повторную выгрузку с Вашего сервера и экономя пользователю время и трафик.
Кэшировать или нет?
Возможность сохранения в кэш страницы определяется динамичностью информации в источнике данных. Таким образом необходимость использования кэша определяется Вами, исходя из планируемого времени жизни страницы.
Если речь идет о формировании выборки по базе (например, поиск введенного пользователем слова), то такую страница обязательно следует запрашивать с сервера при каждом вызове без использования кэш, так как количество вариантов запрашиваемых слов огромно, а если мы к тому же имеем дело с меняющимся массивом данных, то кэширование бессмысленно. Или речь идет о формировании допустим графика приходящих посетителей (который изменяется с каждым визитом, то есть практически с каждым вызовом), то кеширование уже просто вредно.
Однако, если мы говорим о том же графике но за вчерашний день, то кэширование рекомендуется, так как данные изменяться уже не будут и мы можем экономить себе и пользователю ресурсы и время на загрузку таких страниц помещением их в локальный или общий кэш. Как продолжение этой ситуации формирование графика не в реальном масштабе времени, а ежечасно. Тут Вы можете заранее предсказать дату окончания "срока годности" сформированных данных.
Общие принципы сохранения страниц в кэш.
PHP-программа может управлять кэшированием результатов ее работы формируя дополнительные поля в заголовке HTTP ответа вызовом функции Header().
Несколько общих утверждений характерных не только для PHP-программ:
- Страницы передаваемые по POST никогда не сохраняются в кэш.
- Страницы запрашиваемые по GET и содержащие параметры (в URL присутствует '?') не сохраняются в кэш, если не указано обратное.
Таким образом в большинстве ситуаций дополнительных инструкций в программу добавлять не надо. Основные моменты на которые следует обратить внимание можно свести к двум:
- запрет кэширования документов, кэшируемых по умолчанию
- кэширование документов, не подлежащих кэшированию по умолчанию.
- запрет кэширования документов, кэшируемых по умолчанию
Эта задача возникает для PHP-скриптов вызываемых без параметров или являющимися индексами директорий, однако формирующих данные персонально под пользователя (например на основе cookies или user agent) или работающих на основе быстро изменяющихся данных. По спецификации HTTP/1.1 мы можем управлять следующими полями:
- Expires - Задает дату истечения срока годности документа. Задание ее в прошлом определяет запрет кэш для данной страницы.
- Cache-control: no-cache - Управление кэш. Значение no-cache определяет запрет кэш данной страницы. Для версии протокола HTTP/1.0 действует "Pragma: no-cache".
- Last-Modified - Дата послднего изменения содержимого. Поле актуально только для статических страниц. Apache заменяет это поле значением поля Date для динамически генерируемых страниц, в том числе для страниц содержащих SSI.
На сайте www.php.net дается следующий код для запрета кеширования.
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Pragma: no-cache"); // HTTP/1.0
Однако, я считаю, что данный заголовок избыточен. В большинстве случаев достаточно:
header("Expires: Thu, 01 Jan 1970 00:00:01 GMT");
Чтобы пометить документ как "уже устаревший" следует установить Expires равным полю Date.
header("Expires: " . gmdate("D, d M Y H:i:s") . " GMT");
Ну и не следует забывать, что формы, запрошенные по POST также не подлежат кэшированию.
Кэширование документов, не подлежащих кэшированию по умолчанию
Обратная задача, может показаться на первый взгляд абсурдной. Однако и в этом существует потребность. Кроме простой минимизации трафика при разработке web-программы следует учитывать комфортность работы с ней пользователя. Например, некоторые страницы Вашего сервера формируются на основе статических данных большого объема. Возможность включения их в кэш существенно улучшит скорость работы сервера для пользователя и частично освободит Ваш от многочисленных повторных генераций такой страницы. Заголовок разрешающий сохранение на прокси-серверах:
header("Cache-control: public");
Если страница учитывает информацию сохраненную в браузере пользователя (тип и версию браузера, ключи, авторизацию и т.д.) такую страницу нельзя сохранить на прокси, однако возможно ее сохранение в локальном кэш браузера:
header("Cache-control: private");
Кэширование до истечения корректности
Описанные выше решения довольно прямолинейны, хотя и подходят для большинства задач. Но протокол HTTP/1.1 имеет средства для более тонкого управления кэш страниц, и существуют задачи требующие применения этих механизмов. Как пример - web-приложения работающие с данными большого объема и прогнозируемой динамичностью. Корректность данных может устанавливаться как по дате прогнозируемого обновления, так и по изменению содержания. Для этих случаев используются разные заголовки управления кэш.
Кэширование с прогнозируемым обновлением
Рассмотрим пример - прайс лист обновляемый по понедельникам. Вы заранее знаете, что содержание страницы можно хранить в кэш до наступления новой недели, что и следует указать в заголовке ответа обеспечивая нужное поведение страницы в кэш.
Основная задача - получить дату следующего понедельника в формате RFC-1123
$dt_tmp=getdate(date("U"));
header("Expires: " . gmdate("D, d M Y H:i:s",
date("U")-(86400*($dt_tmp["wday"]-8))) . " GMT");
header("Cache-control: public");
Этим методом можно эффективно управлять поведением страницы в кэш и пременим но для большого числа страниц - так или иначе можно выделить временные интервалы в течении которых содержание страницы остается постоянным. Реальное положение вещей таково, что страницы большинства динамических сайтов имеют определенное время жизни исходя из которго разработчик может сераер более приятным для работы.
Другой подход, применяемый при более оперативном обновлении информации и одновременной высокой посещаемости сервера (иначе кэширование не будет эффективным) состоит в использовании заголовка Cache-control: max-age=секунды, определяющий время по истечении которого документ считается устаревшим и имеющий больший приоритет при вычислении "свежести" документа.
Если Вы публикуете новости с интервалом в 30 минут:
header("Cache-control: public");
header("Cache-control: max-age=1800");
Кэширование по содержанию
Еще более интеллектуальный вид управления предоставляет HTTP/1.1 на основе содержимого с помощью директив Vary. Я очень рекомендую применять его при формировании изображений или текстов большого объема, которые как показывает практика изменяются крайне редко. При этом у пользователя в случае возврата не будет происходить их повторной выгрузки, если содержание осталось прежним, и страница будет взята с Вашего сервера, если ее содержание изменилось.
Рассмотрим пример выдачи изображения из базы данных индентифицируемых по ID. Вызов страницы выглядит следующим образом:
http://www.your.server/viewpic.php3?id=23123
а значит по правилам страница не будет сохраняться в кэш (присутствуют параметры), но через заголовок можно управлять этим.
mysql_connect("host", "user", "passwd");
$image=mysql("db", "select pics,type from pictures where id=$id");
Header("Cache-Control: public, must-revalidate");
Header("Vary: Content-ID");
Header("Content-ID: ".md5(mysql_result($image, 0, "pics")));
Header("Content-type: ".mysql_result($image, 0, "type"));
echo mysql_result($image, 0, "pics");
mysql_freeResult($image);
mysql_close();
Для управления используется MD5 сумма содержимого изображения. Пока содержание не изменилось, сумма будет постояной. В случае изменения содержания в базе на сервере клиент выполнит запрос для повторного формирования содержания. Пока изображение постоянно содержимое будет отображаться из кэш.
Примечания для Russian Apache
И приятное (или неприятное) сообщение для пользователей Russian Apache. Так как сервер выдает старину по пользовательской кодировке он автоматически снабжает ВСЕ страницы (не только динамические) заголовками запрета кэширования.
Expires: Thu, 01 Jan 1970 00:00:01 GMT
Так что все страницы не кэшируемые. Формирование в скрипте заголовка Expires эффекта не имеет. Зачем это сделано и некоторые методы борьбы описаны на apache.lexa.ru и нет необходимости воспроизводить эти советы здесь. Рассматривая работу PHP+Russian Apache вот как можно повлиять на кэшируемость.
Для скриптов выводящих изображения ситуация простая - Russian Apache не перекодирует (а значит не устанавливаетсрок истечения годности) документы имеющие MIME тип image/*. Для использования кэш текстовых документов видимо следует использовать "Cache-control: private, max-age=" для разрешения кэширования страниц в браузере. Хотя это теоретическое предположение, не проверенное на практике.
Данный документ пока нельзя считать законченым. Остались не рассмотренными некоторые заголовки влияющие на правила вычисления "свежести" документа. Так же принимаются любые комментарии, дополнения или сообщения о замеченных ошибках.
Классы в php3
Когда достаём из базы данных много подобной
информации мы её конечно же в массив оформляем.
Если нам надо блок разнотипной информации как
одно целое хранить и обрабатывать - создаём
объект, используя описание класса как трафарет.
Ну а если подобных объектов несколько - массив
обектов. Как такую штуку запустить попробую
показать.
Например стоит старинная задача: из пункта
"А" доехать в пункт "В" на автобусе.
Всего автобусных маршрутов в городке - 34,
остановок по городку - 99, в некоторые дни
отдельные маршруты не ходят. Но вся информация
имеется в базе данных и организована следующим
образом:
table common
id int(4) | name varchar(100) |
1 | Academic I |
2 | Anthony Seeger |
3 | Ashby Crossing |
4 | Blue Ridge Dr. @ Madison Manor |
5 | Blue Ridge Hall |
... | ... |
98 | Waterman Square |
99 | Zane Showker Hall |
В этой таблице перечисленны ID остановок ( int ) и
их названия ( string )
table common_2
id_stop int(4) | routes_string (varchar(250) |
1 | ;9;10;11;12; |
2 | ;5;6;6a;7;7a;9; |
3 | ;4w;7;7a;10;12; |
4 | ;2; |
... | ... |
98 | ;3; |
99 | ;1;2;4;4w;5;6;6a;7;7a;9;7a;10;12; |
Здесь показано на какой остановке - её ID ( int )
какие маршруты останавливаются ( string )
table routes
progr_name varchar(10) | real_name varchar(10) | sunday varchar(10) | monday varchar(10) | tuesday varchar(10) | wednesday varchar(10) | thursday varchar(10) | friday varchar(10) | saturday varchar(10) |
01 | 1 | n | y | y | y | y | y | y |
02 | 1w | y | y | y | y | y | y | y |
03 | 2 | n | y | y | y | y | y | y |
04 | 2w | y | y | y | y | y | y | y |
... | ... | ... | ... | ... | ... | ... | ... | ... |
33 | 12_1 | y | y | y | y | y | y | y |
34 | 12_2 | y | y | y | y | y | y | y |
Показывает програмное имя маршрута ( string ), его
реальное имя ( string ) и в какой день недели этот
автобус ходит, а в какой нет ( начинаем с
воскресенья )
и по каждому маршруту у нас имеется таблица
rt01, rt02, ..., rt34 ( приведу пример лишь для первого
маршрута )
table rt01
curent int(4) | stop_name int(4) | time_string varchar(250) |
1 | 26 | ;7:00;8:00;9:00;10:00;11:00;12:00;13:00;14:00; |
2 | 91 | ;7:03;8:03;9:03;10:03;11:03;12:03;13:03;14:03; |
3 | 54 | ;7:07;8:07;9:07;10:07;11:07;12:07;13:07;14:07; |
4 | 69 | ;7:11;8:11;9:11;10:11;11:11;12:11;13:11;14:11;15: |
... | ... | ... |
24 | 99 | ;7:49;8:49;9:49;10:49;11:49;12:49;13:49;14:49; |
25 | 26 | ;7:51;8:51;9:51;10:51;11:51;12:51;13:51;14:51; |
где показан порядковый номер остановки ( int ) , ID
этой остановки ( int ) и строка ( string ) в которой
перечисленно время в которое данный автобус
останавливается на каждой остановке.
Необходимо определить на каком автобусе можно
доехать до нужной останвки, а если невозможно на
одном, то на какой остановке пересесть на другой
маршрут. Возможность двух пересадок
рассматривать не будем, так как на скрипте такие
вычисления гонять неразумно - долго будет. Вся
необходимая информация - названия, времена
выдаётся пользователю.
Вытаскиваем ( из базы данных ) все автобусы
останавливаюшиеся на остановке start. Смотрим, а
можем ли добраться на каком нибудь из них до
остановки finish? Выдаём информацию, далее смотрим
все автобусы останавливающиеся на остановке finish
( не совпадающие с первым запросом ) и смотрим, а
где же можем пересесть на эти автобусы с тех, что
идут с остановки start. Выдаём результат. Вроде всё
просто, но запутаешься во всех этих переменных. И
другое дело если есть объект, который хранит всю
информацию о маршруте, и другой объект, хранящий
всю информацию о автобусной остановке, который
содержит массив объектов первого типа - маршруты.
Классы, по которым будем создавать эти объекты
назовём Route - маршрут, Bus_stop - остановка.
Получается что нам надо создать две переменных
типа Bus_stop - одна на остановку start и одна на
остановку finish. Ну а колличество объектов типа Route
в каждом объекте типа Bus_stop будет определяться
колличеством останавливающихся на этой
остановке маршрутов. При данном подходе
оперируем на уровне объектов, то есть переменных
немного и всё наглядно. При необходимости
вытаскиваем нужную информацию из объекта.
Ну а как это работает:
Файл choice.php3
<form action="find.php3" method=post >
<?php
$db = mysql_connect("localhost", "root");
mysql_select_db("BUS_STOP",$db);
$sql="select name from common ";
echo "<table><tr><td>Enter where are you</td>";
echo "<td>Enter Destination</td></tr>";
echo "<tr><td>";
$result = mysql_query($sql,$db); //выбрали имена остановок из ДБ
echo "<select name=\"place_is\" size=10>";
while($myrow=mysql_fetch_row($result)) {
echo "<option>$myrow[0]"; }
echo "</select>";
echo "</td><td>";
$result = mysql_query($sql,$db); //выбрали имена остановок из ДБ
echo "<select name=\"place_targ\" size=10>";
while($myrow=mysql_fetch_row($result)) {
echo "<option>$myrow[0]"; }
echo "</select>";
echo "<td></tr></table>";
mysql_close();
?>
<br><input type="submit" value="find all available routes">
</form>
генерит картинку
и позволяет пользователю задать пункт
отправления и пункт назначения. Данные, понятное
дело, выбираются из базы данных ( ну не писать же
дважды по сотне остановок в html форме ),
оформляются в виде стандартной формы, ну а запрос
пользователя пересылаем в файл
find.php3
<?php
include "functions.php3";
echo "<br>Need from: $place_is To: $place_targ ";
$day=get_day();
echo" DAY - $day";
get_result($place_is, $place_targ);
?>
с которым то же всё просто - выдать на экран
некоторую контрольную информацию - день недели и
запустить собственно программу get_result(...).
Основной код - в файле functions.php3.
Функция get_result($start, $finish)
открывает базу данных и создаёт две переменных
типа Bus_stop.
$db = mysql_connect("localhost", "root");
mysql_select_db("BUS_STOP",$db);
$routes_start=new Bus_stop($start, $db);
$routes_finish=new Bus_stop($finish, $db);
mysql_close();
При создании каждой переменной этого типа
запускается constructor, извлекаеся нужная информация
об этой автобусной остановке из базы данных, с
помощью вспомогательной функции cut_str(...)
строка,где перечисленны все останавливающиеся
здесь автобусы, разделяется на составляющие и в
результате получаем массив названий
автобусов,которые останавливаются на этой
остановке.
function Bus_stop($name, $db) {
$this->name_busstop=$name;
$sql="select id_stop, routes_string";
$sql="$sql from common_2, common";
$sql="$sql where name='$name' and id_stop=id";
if ($result = mysql_query($sql,$db)) {
$que=mysql_fetch_row($result);
$que[]=mysql_num_rows($result); //last element for control
}
$this->id_busstop=(int)$que[0];
$real_n=cut_str($que[1]); //get all real names for routes stoped
После чего контрольный вывод этих самых имён.
Теперь в цикле создаём объекты типа Route и
упаковываем их в массив
for($ll=0; $ll<$k;$ll++) {
$this->route[$ll]=new
Route($real_n[$ll],$db);
$this->number_routes++;
}
Tак же constructor класса Route получает имя
маршрута и по нему, извлекая нужную информацию из
базы данных, создаёт объект - переменную этого
типа.
function Route($name, $db) {
global $day_now;
$sql="select prog_name, $day_now";
$sql="$sql from routes";
$sql="$sql where real_name='$name'";
if($result = mysql_query($sql,$db)) {
$que=mysql_fetch_row($result);
$que[]=mysql_num_rows($result); //last element for control
}
$this->name_route=(string)$name;
$this->id_route=$que[0];
$this->rout_today=(string)$que[1];
if($que[1]=="y") { //do you have this rout today ?
$sql="select stop_name, time_string";
$sql="$sql from rt$que[0]";
if($result = mysql_query($sql,$db)) {
$k=0;
while ($myrow = mysql_fetch_row($result)) {
$r_stop[$k]=(int)$myrow[0];
$r_times[$k]=$myrow[1];
$k++;
}
$this->rout_stop=$r_stop;
$this->rout_times=$r_times;
$this->number_stop=$k;
}
}
}
В итоге имеем две переменных - $routes_start содержит
всё о остановке start, другая $routes_finish о остановке finish.
Теперь, для поиска автобусов, на которых можем
добраться от start до finish, вызываем функцию
find_direct(...) принадлежащую объекту остановки start,
передав в неё ID остановки finish. Ну и смотрим, есть
ли у маршрутов, на которые мы можем сесть на
остановке start остановка на которую нужно попасть,
но важно чтоб она была после того как мы сядем,
поскольку марщруты в основном кольцевые и
возвращаютсядругой дорогой. Если есть такая, то
нужная информация извлекается из объекта и
выдаётся пользователю.
function find_direct($id_stop_finish) {
$find=0;
for($i_rout=0; $i_rout<$this->number_routes; $i_rout++) {
$this_rout=$this->route[$i_rout];
if($this_rout->rout_today == "y") {
$numb_stop=$this_rout->number_stop; // for this route
$arr_id=$this_rout->rout_stop;
$i_stop_start=0;
do {
$id_bst=$arr_id[$i_stop_start];
if($id_bst == $this->id_busstop) break;
$i_stop_start++;
} while($i_stop_start<$numb_stop);
$i_stop_finish=$i_stop_start;
do {
$id_bst=$arr_id[$i_stop_finish];
if($id_bst == $id_stop_finish) {
$id_first=$this->id_busstop;
$id_second=$id_stop_finish;
$name_rout=$this_rout->name_route;
$all_time=$this_rout->rout_times;
$time_start=$all_time[$i_stop_start];
$time_finish=$all_time[$i_stop_finish];
echo"<p> YOU CAN USE ONE ROUT AND GET YOUR DESTINATION:";
echo"<br>Rout $name_rout will be on the start at time: $time_start";
echo"<br>And on the destination will be at time: $time_finish";
$find++;
break;
}
$i_stop_finish++;
} while($i_stop_finish<$numb_stop);
}
}
return $find;
}
А вот вторым шагом мы в объект типа Route,
созданный для остановки start передаём такой же
объект созданный для остановки finish как параметр
функции find_one_change(...), для поиска возможных
пересадок с маршрута который останавливается на
остановке start, на маршрут, останавливающийся на
остановке finish.
function find_one_change($routes_finish) {
$db = mysql_connect("localhost", "root");
mysql_select_db("BUS_STOP",$db);
$find=0;
$numb_startr=$this->number_routes;
$numb_finishr=$routes_finish->number_routes;
$all_startr=$this->route; // array of objects
$all_finishr=$routes_finish->route; // array of objects
$id_stop_finish=$routes_finish->id_busstop;
$id_stop_start=$this->id_busstop;
for($i=0; $i<$numb_startr; $i++) {
for($j=0; $j<$numb_finishr; $j++) {
$id_strout=$all_startr[$i]->id_route;
$id_fnrout=$all_finishr[$j]->id_route;
$today_finish=$all_finishr[$j]->rout_today;
$today_start=$all_startr[$i]->rout_today;
if($id_strout != $id_fnrout & $today_start == "y" & $today_finish == "y") {
$res= find_able($all_startr[$i], $all_finishr[$j]);
if($res[3] != 0) {
echo"<p>we can reach destination use bus with change:";
$name_start=$all_startr[$i]->name_route;
$all_timestart=$all_startr[$i]->rout_times;
$prog_name=$all_startr[$i]->id_route;
$timestart_sit=$all_timestart[$res[0]];
$timestart_chg=$all_timestart[$res[1]];
$sql="select name";
$sql="$sql from common, rt$prog_name";
$sql="$sql where curent='$res[1]' and stop_name=id";
if($result = mysql_query($sql,$db)) {
$que=mysql_fetch_row($result);
$que[]=mysql_num_rows($result); //last element for control
}
$name_finish=$all_finishr[$j]->name_route;
$all_timefinish=$all_finishr[$j]->rout_times;
$timefinish_sit=$all_timefinish[$res[2]];
$timefinish_out=$all_timefinish[$res[3]];
echo"<br>On the start bus-stop sit on the $name_start rout which stop here:";
echo"<br>$timestart_sit";
echo"<br>name BUS-STOP for change:$que[0]";
echo"<br>And Bus $name_start will be here at time:";
echo"<br>$timestart_chg";
echo"<br>on the Bus-stop $que[0] you have to sit on the";
echo"<br>next bus:$name_finish, which will be here at time:";
echo"<br>$timefinish_sit";
echo"<br>And you get your destination at time:";
echo"<br>$timefinish_out";
$find++;
}
}
}
}
mysql_close();
return $find; // number finded routes
}
}
Для работы этой функции привлекается
вспомогательная функция find_able(...) В которую
как параметр передаём объект - маршрут отходящий
от остановки start и объект - маршрут который
останавливантся на остановке finis. Проверям
возможность пересадки с первого на второй. При
этом для каждого автобуса отходящего от
остановки start сканируем по его становкам от
остановки посадки и смотрим, а есть ли такая же у
автобуса, который останавливается на остановке
finish. Если есть, то в возвращаемую переменную
$ret ( array ) записываем индексы для этой
остановки для первого и для второго $ret[1]=$i;
$ret[2]=$j; и потом смотрим, а есть ли у
финиширующего автобуса остановка назначения
после этой самой остановки пересадки. Если есть -
записываем $ret[3]=$j;
Отработав, функция возвращает $ret[] и если 4-й
элемент $ret ( $ret[3] ) равен 0, то
значит мы не можем пересесть с первого на второй
и достичь остановки назначения. Найдя искомую
остановку ( то есть ту, на которой
останавливаются и тот и другой маршруты ) по её ID
из базы данных получим имя, вся остальная
информация находиться в объектах и остаётся
только выдать её пользователю.
В данном случае не найдено прямого маршрута, то
есть на который мы можем сесть на остановке start и
добраться до остановки finish, но есть возможность
добраться до остановки назначения с пересадкой -
указан маршрут на который нужно сесть и строка
времён, кагда он останавливается здесь. Указана
остановка на которой необходимо сойти для
пересадки на другой маршрут и строка времён,
когда вы прибудете на неё. Далее такая же
информация про маршрут, на который следует
пересесть: строка времён, когда он бывает на
данной остановке и когда он останавливается на
остановке finish. В данном примере, несмотря на то
что маршрут 1 останавливается и на остановке start и
на остановке finish как возможный он не показан -
дело в том, что проходит он их в обратном для нас
порядке.
В фолдере create_tables найдёте всё необходдимое. На
Linux просто "руками" создайте базу данных
BUS_STOP, войдите в фолдер create_tables, файл createTables.sh
сделайте исполняемым и запустите его. Все
необходимые таблицы в BUS_STOP будут созданы и
заполнены. Скрипт createTables.sh использует файлы,
находящиеся там же, и если не получается - можно
руками, создать таблицы, заглядывая в файл
create_all_tables.dump и заполнить их используя файлы common,
common_2, routes и rt01, rt02, ..., rt34. Пользователям Windows видимо
так и придётся... :-( .
После установки базы данных BUS_STOP файлы choice.php3,
find.php3 и functions.php3 положить, как обычно, под Apache и
запустить choice.php3.
Триада PHP & MySQL & gd library - Сервис счетчиков обращений
Php - (Hypertext Preprocessor) - платформо-независимый язык для
динамического наполнения WEB-сайта. Тем неменее, он позволяет динамически
генерировать не только HTML-документы, но и графические изображения в
формате gif. В статье рассмотрены вопросы обработки параметров get-запроса,
доступа к базе MySQL и генерации gif-рисунков на практическом примере.
Когда у вас будет свой сайт вы безусловно захотите вести статистику посещений. Если вы предоставляете место и траффик для бесплатного размешения страниц и хотите предоставить каждому пользователю неограниченное количество счетчиков, но без права исполнения скриптов, то эта статья для Вас.
Как это работает? Web-мастер помещает на свою страницу ссылку на рисунок. Но не gif или jpeg, а на этот php скрипт. Положим, ссылка на счетчик страницы 'Об авторе', расположенной на моем сайте будет выглядеть так:
<img src="/scripts/counter.php3?SiteID=Vlad&PageID=About">
Здесь SiteID и PageID - так называемые get-параметры, а Vlad и About - их значения. Эти get-параметры идентифицируют счетчик. Скрипт найдет соответсвующую запись в базе, увеличит значение счетчика на еденицу и построит рисунок. Пример картинки, как выглядит счетчик:
Внешний вид счетчика
Мы еще не представляем как будет выглядеть все остальное но уже можем обработать get-параметры
$locSiteID = $HTTP_GET_VARS["SiteID"];
$locPageID = $HTTP_GET_VARS["PageID"];
Здесь следует остановиться на важном моменте. Дело в том, что по правилам php имена переменных и get-параметров чувствительны к регистру. Это значит, что если человек напишет ссылку и get-параметры прописными буквами, то наш скрипт не сможет распознать такой запрос. Поэтому я предлагаю не такой изящный, но очень надежный способ интерпреции get-параметров. Состоит он вот в чем: последоватьным перебором всех get-параметров попытаемся найти интересующие нас SiteID и PageID. Причем при поиске используем нечувствительное к регистру сравнение строк:
$locSiteID = "_";
$locPageID = "_";
while (list($key, $val) = each($HTTP_GET_VARS))
{
if (strcmp(strtoupper($key),"SITEID")==0) $locSiteID = "_" . $val;
if (strcmp(strtoupper($key),"PAGEID")==0) $locPageID = "_" . $val;
}
Здесь префикс "_" необходим для MySQL-движка. Далее установим базу счетчиков Counters
mysqladmin create Counters
и создадим в ней таблицу Counters. Таблица как вы наверно уже догадались, состоит из трех полей: SiteID, PageID, CountValue:
Create Table Counters (
SiteID char(120) not null,
PageID char(120) not null,
CountValue int,
index CounterIndex (SiteID,PageID));
Обратите внимание на индекс CounterIndex. Поля, входящие в индекс, должны иметь атрибут NOT NULL, размер ключей (читай полей, входящих в индекс) не должен превышать 256 символов. И последнее - порядок полей в индексе должен совпадать с порядком в секции Where SQL запроса. Если же Вы планируете небольшую базу и колчество записей невелико, то индекс можно и не создавать.
Для работы с базой нам потребуется всего три запроса:
- Создать новую запись в базе при первом обращении к счетчику:
INSERT INTO counters (SiteID,PageID,CountValue)
VALUES ('$locSiteID','$locPageID',1)
- Найти значение счетчика:
SELECT * FROM counters WHERE SiteID='$locSiteID' AND PageID='$locPageID'
- Увеличить значение счетчика на еденицу:
UPDATE counters SET CountValue = CountValue+1
WHERE SiteID = '$locSiteID' AND PageID = '$locPageID'
А теперь самое время вспомнить про префикс "_" значений get-параметров. На самом деле все очень просто. Посмотрите на структуру таблицы. Поля SiteID, PageID помечены аттрибутом NOT NULL, и префикс "_" позволяет пропустить один или даже оба параметра в get-запросе:
// Нечувствительные к регистру get-параметры
$locSiteID = "_";
$locPageID = "_";
while (list($key, $val) = each($HTTP_GET_VARS))
{
if (strcmp(strtoupper($key),"SITEID")==0) $locSiteID = "_" . $val;
if (strcmp(strtoupper($key),"PAGEID")==0) $locPageID = "_" . $val;
}
// Соеденяемся с базой Counters и запрашиваем значение счетчика
mysql_connect("localhost:3306","root","");
if (!($result = mysql_db_query("Counters","select * from counters
where SiteID='$locSiteID' AND PageID='$locPageID'")))
{
// База Counters не доступна - катапультируемся
echo "Cannot query database Counters\n";
echo "Query Error " . mysql_errno() . " " . mysql_error();
exit;
}
$FirstVisit=1;
while($row = mysql_fetch_object($result))
{
$FirstVisit=0;
$locCountValue = $row->CountValue;
}
mysql_free_result($result);
if ($FirstVisit==1)
{ // Первое обращение. Создаем запись в базе
$result = mysql_db_query("Counters","Insert Into Counters
(SiteID,PageID,CountValue) Values ('$locSiteID','$locPageID',1)");
$locCountValue = 1;
}
else
{ // Увеличиваем значение счетчика на еденицу
$locCountValue = $locCountValue + 1;
$result = mysql_db_query("Counters","Update Counters Set
CountValue = CountValue+1 Where SiteID = '$locSiteID'
AND PageID = '$locPageID'");
}
Преобразовываем число в строку и форматируем в шестизначное число.
$strCounterValue = sprintf("%d",$locCountValue);
while(strlen($strCounterValue)<6) $strCounterValue = "0" . $strCounterValue;
$txtlen = strlen($strCounterValue);
Далее показано как пользоваться gd library в php для динамического рисования счетчика. Строго говоря, это не самый иллюстративный пример. Шрифты, входящие в библиотеку gd library могут различаться по дистрибутивам, поэтому для этого проекта я использовал спецальный формат представления шрифта и процедуру его отображения. Тем неменее код достаточно документирован и, я надеюсь, поможет вам сделать очередной шаг.
Header("Content-type: image/gif");
$DeskWidth=24; $DeskHeight=48; $DeskSpace=5;
/* Создаем рисунок imgWidth * imgHeight pixels. */
$imgWidth= $txtlen * ($DeskWidth + $DeskSpace) - $DeskSpace;
$imgHeight = $DeskHeight;
$im_out = ImageCreate($imgWidth, $imgHeight);
/* Резервируем цвета в палитре*/
$white = ImageColorAllocate($im_out, 255, 255, 255);
$grey = ImageColorAllocate($im_out, 0, 0, 77);
$blue = ImageColorAllocate($im_out, 40, 5, 250);
$trans = ImageColorAllocate($im_out, 1, 1, 1);
$red = ImageColorAllocate($im_out, 40, 33, 155);
/* Устанавливаем прозрачный цвет и рисуем фон*/
ImageColorTransparent($im_out, $trans);
for ($dy=0; $dy < $imgHeight; $dy++)
ImageLine($im_out, 0, $dy, $imgWidth-1, $dy, $trans);
for ($dy=2; $dy < $imgHeight; $dy=$dy+4)
ImageLine($im_out,0,$dy,$imgWidth-1,$dy,$red);
// Далее выводим цифры по пикселам,
// сначала "тень" со смещением в один пиксел а поверх и "лицо"
$CurColor=$white;
for ($txtcur=0;$txtcur<$txtlen;$txtcur++)
{
$bx=$txtcur*($DeskWidth+$DeskSpace);
for ($dy=0;$dy<$imgHeight;$dy++)
ImageLine($im_out,$bx,$dy,$bx+$DeskWidth-1,$dy,$blue);
$bx=$bx+($DeskWidth-22)/2;
$c=0+$strCounterValue[$txtcur];
if ($c!=0) $CurColor=$white;
if (($c>=0) && ($c<=9))
for ($id=1;$id>=0;$id--)
{
if ($id==1) $CurColor=$grey; else $CurColor=$white;
for ($dy=0;$dy<48;$dy++)
for ($dx=0;$dx<22;$dx++)
{
$my_pos = $c*48*3 + $dy*3 + ($dx>>3);
$my_byte = $CounterDigitData[$my_pos];
if ((($my_byte >> (7-($dx & 7))) & 1) == 1)
ImageSetPixel($im_out,$bx+$dx+$id-1, $dy+$id, $CurColor);
}
}
}
// Отправляем обозревателю картинку ...
ImageGif($im_out);
// ... и освобождаем память
ImageDestroy($im_out);
Работа с Cookies на PHP
Откуда возник термин "cookie" никто достоверно не знает, хотя считается, что во времена зарождения Unix-систем где-то использовалось словосочетание Magic Cookies. Имелись в виду "квитанции" (token, ticket), которыми обменивались программы.
Cookie является решением одной из наследственных проблем HTTP протокола (HyperText Transfer Protocol). Эта проблема заключается в непостоянстве соединения между клиентом и сервером, как при FTP или Telnet сессии, т.е. для каждого документа (или файла) при передаче по HTTP протоколу посылается отдельный запрос. Включение cookie в HTTP протокол дало частичное решение этой проблемы. Иначе говоря, транзакция завершается после того, как браузер сделал запрос, а сервер выдал соответствующий ответ. Сразу после этого сервер "забывает" о пользователе и каждый следующий запрос того же пользователя считает новым пользователем.
Используя cookie, можно эмулировать сессию по HTTP протоколу. Коротко принцип эмуляции сессии таков: на первом запросе выдается соотвествующее значение cookie, а при каждом последующем запросе это значение читается из переменной окружения HTTP_COOKIE и соответствующим образом обрабатывается.
Простой пример: есть форма, где пользователю предлагается указать свое имя, из нее вызывается скрипт, прописывающий значение cookie в браузер пользователя. При каждом последующем заходе на основе анализа значения cookie из браузера пользователя на странице появляется либо именное приветствие (если есть установленное значение cookie), либо первоначальная форма с запросом имени пользователя (если значение cookie не установлено).
Итак,приступим к практике:
1.Задание cookie с помощью Php
Для задания этой финкции в языке php есть оператор:
setcookie() . Самое приятное,сто функция
setcookie() воспринимает до шести аргументов,
в зависимости от того, как вы собираетесь управлять значениями cookie
и кто будет считывать ее значения.
Простейший способ установить cookie таков:
setcookie('name', 'bret');
Затем, для каждой последующей страницы на Вашем сайте, просматриваемой
в течение данной сессии (пока пользователь не покинет сайт) переменная
$name будет иметь значение 'bret' и его
можно легко прочитать средствами PHP. Этот тип cookie известен как
cookie-сессия, поскольку значение сохраняется в течение пользовательской
сессии.
Если Вы хотите, чтобы значение cookie запоминалось браузером после того,
как пользователь закончит сессию, Вы должны передать функции
setcookie() третий параметр - дату истечения
срока действия cookie. Поскольку PHP сформировался в основном в среде Unix,
Вы должны представить время истечения срока действия cookie как число секунд,
прошедших с 1 января 1970 г. Если Вы имеете опыт программирования для
Unix, это не покажется Вам удивительным. Но, если Вы программировали только
в среде Windows или Macintosh, Вы, может быть, удивитесь, что за чокнутый народ
эти Unix-оиды.
Но не бойтесь. PHP имеет очень удобную функцию, mktime() .
Вы указываете ей в качестве параметров (в указанном порядке) час, минуту,
секунду, месяц, день и год, задающие тот момент времени, который Вы
хотите представить в воспринимаемом UNIX формате, и
mktime() возвращает Вам число секунд, прошедших
с 1 января 1970 г. до указанного момента времени. Например, если Вы хотите, чтобы
срок действия cookie истек 1 января 2000 г., Вы записываете:
<?php
$y2k = mktime(0,0,0,1,1,2000);
setcookie('name', 'bret', $y2k);
?>
Если Вы хотите изменить значение cookie на новое, Вы можете просто
переписать его (ее?) значение. Таким образом, даже если браузер уже
посылал значение cookie серверу на одной из предыдущих страниц, вполне возможно
сообщить серверу, что в действительности Вас зовут "jeff."
<?php
$y2k = mktime(0,0,0,1,1,2000);
setcookie('name', 'jeff', $y2k);
?>
Обратите внимание на то, что при этом не меняется значение переменной
$name. Оно устанавливается при загрузке
страницы. Если Вы хотите чтобы значение переменной изменялось синхронно
с изменением значения cookie, Вы должны изменить код следующим образом:
<?php
$name = 'jeff';
$y2k = mktime(0,0,0,1,1,2000);
setcookie('name', $name, $y2k);
?>
Следующие два параметра функции setcookie()
позволяют Вам задать путь и имя домена того, кто может прочитать значение
Вашего cookie. По умолчанию только страницы, расположенные в том же каталоге
или ниже в структуре подкаталогов того сервера, который установил cookie,
могут прочитать его (ее??) значение. Это делается из соображений безопасности.
Однако, если у Вашего сервера два доменных имени: "www.domain.com" и "other.domain.com",
и Ваш экаунт позволяет Вам обслуживать страницы из каталога ~/myhome,
Вы должны вызывать функцию setcookie()
следующим образом:
<?php
setcookie('name', 'jeff', $y2k, '~/myhome', '.domain.com');
?>
Последний параметр функции setcookie() ,
который мы никогда не использовали, требует, чтобы значение cookie
передавалось только на те Web-сервера, которые испольуют безопасный протокол
соединения, такой как SSL. Если Вам это нужно, то задайте для шестого параметра
значение 1.
Удалить cookie тоже очень просто, достаточно передать функции
setcookie() имя cookie и PHP
сделает все остальное:
<?php setcookie('name'); ?>
В заключение нужно сделать еще одно замечание, касающееся использования cookie.
В силу того, как организована обработка cookies в протоколе HTTP, необходимо
установить значения всех cookie до вывода какого-либо текста. Если сделать
наоборот, PHP выдаст Вам предупреждение и значение cookie не будет послано.
Вот так правильно:
<?php
setcookie('name', 'jeff');
echo "Hello Everyone!";
?>
А так - нет:
<?php
echo "Hello Everyone!";
setcookie('name', 'jeff');
?>
2. Задание cookie с помощью JavaScript
Можно задавать значение cookie, используя язык JavaScript. Единственный недостаток этого способа заключается в том, что не все браузеры его поддерживают. Ниже приведены примеры функций JavaScript, написанные Алексеем Александровым для скрипта "Органайзер". Пример. Функция установки значения cookie
// name - имя cookie
// value - значение cookie
// [expires] - дата окончания действия cookie (по умолчанию - до конца сессии)
// [path] - путь, для которого cookie действительно (по умолчанию - документ, в котором значение было установлено)
// [domain] - домен, для которого cookie действительно (по умолчанию - домен, в котором значение было установлено)
// [secure] - логическое значение, показывающее требуется ли защищенная передача значения cookie
function setCookie(name, value, expires, path, domain, secure) {
var curCookie = name + "=" + escape(value) +
((expires) ? "; expires=" + expires.toGMTString() : "") +
((path) ? "; path=" + path : "") +
((domain) ? "; domain=" + domain : "") +
((secure) ? "; secure" : "")
if (!caution || (name + "=" + escape(value)).length <= 4000)
document.cookie = curCookie
else
if (confirm("Cookie превышает 4KB и будет вырезан !"))
document.cookie = curCookie
}
Пример. Функция чтения значения cookie
Возвращает установленное значение или пустую строку, если cookie не существует.
// name - имя считываемого cookie
function getCookie(name) {
var prefix = name + "="
var cookieStartIndex = document.cookie.indexOf(prefix)
if (cookieStartIndex == -1)
return null
var cookieEndIndex = document.cookie.indexOf (";", cookieStartIndex + prefix.length)
if (cookieEndIndex == -1)
cookieEndIndex = document.cookie.length
return unescape(document.cookie.substring (cookieStartIndex + prefix.length, cookieEndIndex))
}
Пример. Функция удаления значения cookie
Принцип работы этой функции заключается в том, что cookie устанавливается с заведомо устаревшим параметром expires, в данном случае 1 января 1970 года.
// name - имя cookie
// [path] - путь, для которого cookie действительно
// [domain] - домен, для которого cookie действительно
function deleteCookie(name, path, domain) {
if (getCookie(name)) {
document.cookie = name + "=" +
((path) ? "; path=" + path : "") +
((domain) ? "; domain=" + domain : "") +
"; expires=Thu, 01-Jan-70 00:00:01 GMT"
}
3. Задание cookie с помощью Perl
Самый мощный и гибкий способ управления документами с использованием механизма cookie - с помощью CGI-скриптов. Задание значения cookie на Perl будет выглядеть следующим образом:
print "Content-type: text/htmln";
print "Set-Cookie: username=aaa13; expires=Friday, 31-Dec-99 23:59:59 GMT; path=/; domain=www.citforum.ru;nn";
Скрипт при выдаче результатов работы генерирует HTTP заголовок:
Content-type: text/html
Set-Cookie: "username=aaa13; expires=Friday, 31-Dec-99 23:59:59 GMT; path=/; domain=www.webscript.ru;"
Чтобы прочитать в скрипте ранее заданное значение cookie, используется переменная окружения HTTP_COOKIE.
$cookie = $ENV{'HTTP_COOKIE'};
Далее можно анализировать полученную строку и, в зависимости от считанных значений, выполнять соответствующие действия.
А теперь о грусном...
Ограничения:
Клиент (браузер) имеет следующие ограничения для cookies:
всего может храниться до 300 значений cookies
каждый cookie не может превышать 4Кбайт
с одного сервера или домена может храниться до 20 значений cookie
Если ограничение 300 или 20 превышается, то удаляется первая по времени запись. При превышении лимита объема в 4Кбайт корректность значения cookie страдает - отрезается кусок записи (с начала этой записи) равный превышению объема.
В случае кэширования документов, например, proxy-сервером, поле Set-cookie HTTP заголовка никогда не кэшируется.
Если proxy-сервер принимает ответ, содержащий поле Set-cookie в заголовке, предполагается, что поле доходит до клиента вне зависимости от кода возврата 304 (Not Modified) или 200 (OK). Соответственно, если клиентский запрос содержит в заголовке Cookie, то он должен дойти до сервера, даже если жестко установлен параметр If-modified-since.
Вот и все, удачи!
|