special

Всё о PHP

Самые основы. Как работает PHP.
Сессии. Подробное описание работы и объяснение механизма.
Хочу изучать PHP и Mysql. С чего начать?
\"Кавычки \". Cоставление запросов, слеши, SQL Injection
Регулярные выражения.
Документация по PHP. Мануал по PHP, книги.
Ничего не работает! Что делать??? Поиск ошибок и отладка.
Решение проблемы "Cannot add header information - headers already sent"
Не передаются переменные! Проблема Undefined variable
Различие между абсолютными и относительными путями. В файловой системе и на сайте.
Как разбить вывод из mysql постранично
Определение IP адреса
Проблемы с кодировкой в MySQL версий 4.1+
Пример кода, работающего с MySQL
Как писать музыку на PHP
Пример системы управления сайтом
Mail injection. Работа с e-mail средствами PHP.
Безопасность PHP скриптов
Базовые понятия MySQL и отличия от текстовых файлов.
Рисование календаря
Обработка ошибок, часть 1. Общие принципы.
Обработка ошибок, часть 2. Разбор примера. Исключения.
Что такое PHP?
Как делать UPLOAD файлов на сервер
Как сделать уменьшенную копию картинки?
Шаблоны


Самые основы. Как работает PHP.

Отличие веб-приложения от обычной программы
Как работает РНР, где он выполняется?
Как передать переменную из PHP в JavaScript и обратно?
Способы общения браузера с сервером.
Просмотр обмена HTTP заголовками
ОЧЕНЬ ВАЖНОЕ ЗАМЕЧАНИЕ
Комментарии

Отличие веб-приложения от обычной программы
Начиная писать программы для веба, многие начинающие программисты сталкиваются с такой ошибкой. Они рассматривают систему браузер-сервер, как обычное приложение. Интерактивное. Нажал кнопку - система среагировала. Провел мышкой - среагировала. Вся информация, которая доступна клиенту - доступна и программе, программа все время находится в памяти.
Так вот, в веб-программировании это не так!.
В момент, когда пользователь видит перед собой страницу и начинает совершать какие-то действия с ней, PHP уже завершил работу! И пользователь взаимодействует не с PHP скриптом, а со своей страницей HTML, которую он получил в браузер. Результатом работы скрипта на PHP в большинстве случаев является обычный текст. Текст HTML страницы. Которая отдается браузеру и показывается им, как обычный HTML. Вы сами можете в этом убедиться, написав в скрипте
<? echo "Привет, <b>Вася!</b>"?>;
А потом просмотрев в браузере исходный текст полученной страницы. Никаких тегов PHP там нет! Только
Привет, <b>Вася!</b>
Потому, что PHP исполняется на сервере!


Сервер и браузер общаются, посылая друг другу запросы по особому протоколу - HTTP. Соединение может инициировать только браузер. Он посылает серверу запрос - показать такой-то файл. Сервер клиенту файл посылает.
Только так и происходит. Клиент запросил - сервер отдал. И забыл сразу о клиенте. Отсюда становится понятным ответ на вопрос, можно ли точно узнать, сколько юзеров сечас на сайте. Нельзя. потому, что "на сайте" нету ни одного. Они соединяются, запрашивают страницу, и отсоединяются. Не имеют постоянного cоединения с сервером, как, например, игроки в Кваку. Узнать можно только примерно, записывая время каждого соединения и выбирая записи за определенный промежуток времени.

Так же, отсюда становится ясно, что сервер может узнать о клиенте очень мало. Только то, что клиент пришлет в HTTP-запросе. Разрешения экрана там нет ;-)
Все, что сервер может знать о клиенте, можно посмотреть командой phpinfo()

Пример общения браузера с сервером:
Пользователь нажимает на ссылку, браузер посылает запрос серверу и ждет ответа:
Браузер -> PHP

PHP выполняет скрипт, отдает результат в браузер и завершает работу:
PHP -> браузер

Браузер отображает страницу, "просматривая" её на предмет ссылок, которые надо запросить у сервера (теги <img src>, <script src> и так далее) и посылает соответствующие запросы. Их можно увидеть, просматривая обмен заголовками, о чем речь будет чуть ниже:
Браузер -> сервер, Браузер -> сервер, Браузер -> сервер...

Пользователь заполняет форму и нажимает на кнопку:
Браузер -> PHP

PHP обрабатывает форму, записывает данные в базу и посылает браузеру заголовок
Location:
PHP -> браузер

Браузер, получив этот заголовок, запрашивает указанную страницу
Браузер -> PHP

PHP выполняет ее... и так далее.

Как работает РНР, где он выполняется?
РНР выполняется на сервере. Браузер посылает серверу запрос на страницу с php кодом. Сервер отдает эту страницу на исполнение интерпретатору PHP, интерпретатор генерирует HTML код, отдает серверу, а сервер посылает клиенту. Никакого РНР кода в браузер не попадает (это важно! Это значит, что увидеть исходный код PHP скрипта невозможно!). Единственный способ отправить что-то скрипту - это кликнуть по ссылке или нажать на кнопку в форме. Так, чтобы РНР обрабатывал какие-то действия пользователя в браузере - невозможно. РНР остался на сервере, ждать новых запросов с данными для обработки. PHP, но не скрипт! Скрипт, который выполнялся, отдавая пользователю страницу, завершил работу. Все данные, которые были в нем - пропали. Именно поэтому, если какая-то переменная нужна при последующих вызовах скрипта, ее надо этому скрипту передать снова.

Очень качественная и подробная статья об основах веб-программирования находится на сайте PHPWIKI.RU. Обязательно прочитайте её.

Как передать переменную из PHP в JavaScript и обратно?
Начнем с того, что никакую переменную передать, конечно же, невозможно. Поскольку переменная - это часть программы. И из одной в другую передать её нальзя. Передать можно только значение переменной. Т.е. текст. То есть, различия между "передачей переменной в яваскрипт" и формированием html таблицы НЕТ НИКАКОГО!
Отсюда вывод - "Передать переменную" в Javascript очень легко. Особенно, повторюсь, если учесть, что никакой "передачи" не происходит. PHP просто напросто генерирует яваскрипт точно так же, как и всю остальную страницу, вместе со всеми переменными.
Точно так же, как вы выводите в браузер строку "Hello World, это Вася Пупкин!", выводится и любой яваскрипт, со всеми своими переменными.
Единственное условие - вы должны представлять себе тот яваскрипт, который хотите получить.
К примеру в PHP есть переменная $name="Вася", значение которой надо передать в яваскрипт, чтобы получить
<script>name="Вася";</script>
Мы просто пишем
<?
$name
="Вася";
?>
<script>name="<? echo $name?>"</script>

То есть, фактически, мы просто сформировали нашим PHP скриптом некий текст, который выглядит, как нужный нам код на яваскрипте. Или, с другой стороны, мы писали свой яваскрипт, в нужных местах вставляя вывод переменных из PHP.
Чтобы не сойти с ума от разнообразных кавычек, настоятельно рекомендуется яваскрипт выводить не весь с помощью echo, а именно так, как написано здесь - закрыв тег PHP и открывая их только там, где нужно вывести переменную.

Как передать переменную из яваскрипта в PHP?
Точно так же, как и любые другие данные - послав запрос на сервер.
Но надо четко понимать, что во время выполнения php скрипта получить что-либо из яваскрипта, разумеется, невозможно. Передать можно будет только при следующем запросе. И обрабатывать его будет уже другой PHP скрипт.
Если надо но событию onClick рбратиться к базе данных, то следует помнить, что она находится на сервере. То есть, надо запрашивать сервер, который запустит PHP скрипт, который обратится к базе, получит от неё ответ и передаст его в браузер.

Все вышеизложенное не противоречит, разумеется, модной технологии асинхронного общения браузера с сервером. Все методы остались те же, просто общение с сервером выполняет не сам браузер, а программа на яваскрипте.
Подробнее можно почитать на ресурсах, посвященных яваскрипту. С точки зрения PHP запросы по технологии AJAX ничем не отличаются от обычных.

Способы общения браузера с сервером.
Способов, предоставляемых протоколом HTTP, немного. Это важная информация. Никаких других способов нет. На практике используются два:
GET - это когда данные передаются в адресной строке, например, когда пользователь жмет ссылку.
POST - когда он нажимает кнопку в форме.
Сформировали страницу со ссылкой или с формой методом GET - запрос придет GET-ом. Сформировали с формой, в которой указан метод POST - придет POST-ом.
Определить, какой способ следует применять, очень просто. Если форма служит для запроса некой информации, например - при поиске, то ее следует отправлять методом GET. Чтобы можно было обновлять страницу, можно было поставить закладку и или послать ссылку другу.
Если же в результате отправки формы данные записываются или изменяются на сервере, то следует их отправлять методом POST, причем обязательно после обработки формы надо перенаправить браузер методом GET. Так же, POST может понадобиться, если на сервер надо передать большой объём данных (у GET он сильно ограничен), а так же, если не следует "светить" передаваемые данные в адресной строке (при вводе логина и пароля, например). Но в любом случае, после обработки POST надо всегда перенаправлять браузер на какую-нибудь страницу, пусть ту же самую, но уже без данных формы, чтобы при обновлении страницы они не записывались повторно. Например:
header("Location: http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']); 
exit;


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

Полезная информация может содержаться в различных НТТР заголовках.
Cookie - если сервер поставил куку, и она не устарела, то браузер отсылает ее вместе с каждым запросом.
HTTP authentication - если сервер запрашивал HTTP авторизацию, то браузер при каждом обращении шлет введенные логин и пароль.

РНР может посылать HTTP заголовки двумя командами - header() и setcookie().

Просмотр обмена HTTP заголовками
Я очень рекомендую попрактиковаться с HTTP заголовками, посмотреть, как ими обмениваются сервер и клиент.
Для этого есть множество разных способов. Если у вас стоит популярный download manager FlashGet, то можно использовать его. Так же заголовки показывает популярная программа Proxomitron, можно скачать какие-нибудь специальные утилиты.
Для IE можно предложить плагин http://blunck.se/iehttpheaders/iehttpheaders.html
Для браузера Mozilla есть удобный плагин http://livehttpheaders.mozdev.org/
Так же, существует много других утилит, легко находимых в сети по запросу HTTP sniffer.
Обязательно воспользуйтесь любым способом посмотреть HTTP заголовки, которыми обменивается браузер с сервером. Это очень хорошая практика, а так же проверка - что шлет твой скрипт. Удобно при отладке установки кук или проблемах с сессиями.
Примерное представление о пришедших заголовках можно также получить, воспользовавшись функцией getallheaders(). Но следует учитывать, что работает она только если PHP собран, как модуль.

ОЧЕНЬ ВАЖНОЕ ЗАМЕЧАНИЕ
Из того факта, что PHP исполняется на сервере, и посылает результат своей работы браузеру, следует один простой, но очень важный вывод. Что PHP в принципе НЕ МОЖЕТ отобразить в браузере ничего такого, что невозможно было бы сделать средствами html.
ПРЕЖДЕ, чем что-то писать на PHP - попробуйте это сделать чистым HTML.
"Нажатие на Энтер" не переводит строку? А в html вы не пробовали таким образом строки переводить? Не получилось? Какая досада. Прочитайте, как в html сделать перевод строки и приходите снова.

PHP в результате своей работы формирует не картинку с текстами, как вы ее видите на экране монитора! PHP формирует HTML код! И этот код ЗНАЧИТЕЛЬНО отличается от того изображения, которое вы видите на экране. Если у вас что-то не получается, то надо всегда смотреть именно ИСХОДНЫЙ код страницы, а не то, как вам ее рисует браузер. В браузере Internet Explorer исходный код можно посмотреть, выбрав в меню Вид - Просмотр HTML-кода.
Если у вас не работает яваскрипт, сформированный PHP скриптом, или html показывает не то, что вы хотите, то исправить эту проблему очень просто.
1. Сначала пишете нужный яваскрипт или html руками. Если у вас с этим проблемы - обратитесь в соотвествующий форум - по яваскрипту или html. PHP тут не при чём.
2. Сравниваете с тем, что получено из PHP
3. Вносите исправления в PHP скрипт, чтобы текст, отдаваемый им, не отличался от написанного руками.

Браузер не умеет показывать файлы, в которые напихан одновременно и html картинки. Браузер умеет показывать только известные ему типы данных. В частности, это ИЛИ html ИЛИ картинка. Но не вместе. Если картинка - то ОДНА. Несколько картинок подряд браузер показывать не умеет. Браузер умеет показывать HTML, в котором прописаны ССЫЛКИ на несколько картинок.
Пожалуйста, прежде, чем изучать PHP - изучите хотя бы основы HTML! Прежде, чем что-то требовать от PHP - попробуйте сделать это на html.



В начало раздела   Наверх


Сессии. Подробное описание работы и объяснение механизма.

Введение
Как устроены, и как работают сессии?
Область применения.
Возможные проблемы и их устранение.
Безопасность
Дополнительная информация:
Пример авторизации с помощью сессий
ОПС! Очень Полезные Ссылки:
Комментарии

Введение
Сессии - это на самом деле очень просто.
Надо только понимать, для чего они нужны и как устроены.
Ответим сначала на первый вопрос.
Как показано в соответствующем разделе этого FAQ, веб-сервер не поддерживает постоянного соединения с клиентом, и каждый запрос обрабатывается, как новый, безо всякой связи с предыдущими.
То есть, нельзя ни отследить запросы от одного и того же посетителя, ни сохранить для него переменные между просмотрами отдельных страниц. Вот для решения этих двух задач и были изобретены сессии.
Собственно, сессии, если в двух словах - это механизм, позволяющий однозначно идентифицировать браузер и создающий для этого браузера файл на сервере, в котором хранятся переменные сеанса.

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

В принципе, довольно несложно сделать собственный аналог сессий, не такой функциональный, как встроенный в PHP, но похожий по сути. На куках и базе данных.
При запросе скрипта смотрим, пришла ли кука с определенным именем. Если куки нет, то ставим ее и записываем в базу новую строку с данными пользователя. Если кука есть, то читаем из базы данные. Еще одним запросом удаляем из базы старые записи и вот у нас готов механизм сессий. Совсем несложно. Но есть некоторые нюансы, которые делают предпочтительным использование именно встроенного механизма сессий.

Как устроены, и как работают сессии?
Для начала надо как-то идентифицировать браузер. Для этого надо выдать ему уникальный идентификатор и попросить передавать его с каждым запросом. Стыдно признаться, но когда я впервые узнал о сессиях, я думал, что это какой-то особый механизм, некий новый способ общения браузера с сервером - "сессии". Что идентификатор сессии передается каким-то особым образом. Разочарование было жестоким.
Сессии используют стандартные, хорошо известные способы передачи данных. Собственно, других-то просто и нет.
Идентификатор - это обычная переменная. По умолчанию ее имя - PHPSESSID.
Задача PHP отправить ее браузеру, чтобы тот вернул ее со следующим запросом. Из уже упоминавшегося раздела FAQ ясно, что переменную можно передать только двумя способами: в куках или POST/GET запросом.
PHP использует оба варианта.
За это отвечают две настройки в php.ini:
session.use_cookies - если равно 1, то PHP передает идентификатор в куках, если 0 - то нет.
session.use_trans_sid если равно 1, то PHP передает его, добавляя к URL и формам, если 0 - то нет.
Менять эти и другие параметры сессий можно так же, как и другие настройки PHP - в файле php.ini, а так же с помощью команды ini_set() или в файлах настройки веб-сервера

Если включена только первая, то при старте сессии (при каждом вызове session_start()) клиенту устанавливается кука. Браузер исправно при каждом следующем запросе эту куку возвращает и PHP имеет идентификатор сессии. Проблемы начинаются, если браузер куки не возвращает. В этом случае, не получая куки с идентификатором, PHP будет все время стартовать новую сессию, и механизм работать не будет.

Если включена только вторая, то кука не выставляется. А происходит то, ради чего, в основном, собственно, и стоит использовать встроенный механизм сессий. После того, как скрипт выполняет свою работу, и страница полностью сформирована, PHP просматривает ее всю и дописывает к каждой ссылке и к каждой форме передачу идентификатора сессии. Это выглядит примерно так:
<a href="/index.php">Index</a> превращается в
<a href="/index.php?PHPSESSID=9ebca8bd62c830d3e79272b4f585ff8f">Index</a>
а к формам добавляется скрытое поле
<input type="hidden" name="PHPSESSID" value="00196c1c1a02e4c37ac04f921f4a5eec" />
И браузер при клике на любую ссылку, или при нажатии на кнопку в форме, пошлет в запросе нужную нам переменную - идентификатор сессии!
По очевидным причинам идентификатор добавляется только к относительным ссылкам.

Теоретически, в наших с вами самодельных сессиях на куках и базе, можно самому, руками приписать ко всем ссылками передачу ид - и тогда наши собственные сессии будут работать независимо от кук. Но, согласитесь - приятнее, когда эту работу делает кто-то другой? ;-)

По умолчанию в последних версиях PHP включены обе опции. Как PHP поступает в этом случае? Кука выставляется всегда. А ссылки автодополняются только если РНР не обнаружил куку с идентификатором сессии. Когда пользователь в првый раз за этот сеанс заходит на сайт, ему ставится кука, и дополняются ссылки. При следующем запросе, если куки поддерживаются, PHP видит куку и перестает дополнять ссылки. Если куки не работают, то PHP продолжает исправно добавлять ид к ссылкам, и сессия не теряется.
Пользователи, у которых работают куки, увидят длинную ссылку с ид только один раз.

Фух. С передачей идентификатора закончили.
Теперь осталось привязать к нему файл с данными на стороне сервера.
PHP это сделает за нас. Достаточно просто написать
session_start();
$_SESSION['test']='Hello world!';

И PHP запишет в файл, связанный с этой сессией, переменную test.
Здесь очень важное замечание.
Массив $_SESSION - особенный.
В нем, собственно, и находятся переменные, которые мы ходим сделать доступными в различных скриптах.
Чтобы поместить переменную в сессию, достаточно присвоить ее элементу массива $_SESSION.
Чтобы получить ее значение - достаточно обратиться к тому же элементу. Пример будет чуть ниже.

Cборкой мусора - удалением устаревших файлов PHP тоже занимается сам. Как и кодированием данных и кучей всяких других нужных вещей. В результате этой заботы работа с сессиями оказывается очень простой.
Вот мы, собственно, и подошли к примеру работы сессий.
Пример очень маленький:
<? 
session_start
(); 
if (!isset(
$_SESSION['counter'])) $_SESSION['counter']=0;
echo 
"Вы обновили эту страницу ".$_SESSION['counter']++." раз. ";
echo 
"<br><a href=".$_SERVER['PHP_SELF'].">обновить"
?>

Мы проверяем, есть ли у нас в сессии переменная counter, если нет, то создаем ее со значением 0, а дальше выводим ее значение и увеличиваем на единицу. Увеличенное значение запишется в сессию, и при следующем вызове скрипта переменная будет иметь значение 1, и так далее.
Все очень просто.

Для того, чтобы иметь доступ к переменным сессии на любых страницах сайта, надо написать ТОЛЬКО ОДНУ(!) строчку в самом начале КАЖДОГО файла, в котором нам нужны сессии:
session_start();
И далее обращаться к элементам массива $_SESSION. Например, проверка авторизации будет выглядеть примерно так:
session_start();
if (
$_SESSION['authorized']<>1) {
header("Location: /auth.php");
exit;
}


Удаление переменных из сессии.
Если у вас register_globals=off, то достаточно написать
unset($_SESSION['var']);
Если же нет, то тогда рядом с ней надо написать
session_unregister('var');

Область применения.
Очень важно понимать, для чего сессии стоит использовать, а для чего - нет.

Во-первых, помните, что сессии можно применять только тогда, когда они нужны самому пользователю, а не для того, чтобы чинить ему препятствия. Ведь он в любой момент может избавиться от идентификатора!
Скажем, при проверке на то, что заполняет форму человек, а не скрипт, пользователь сам заинтересован в том, чтобы сессия работала - иначе он не сможет отправить форму! А вот для ограничения количества запросов к скрипту сессия уже не годится - злонамеренный скрипт просто не будет возвращать идентификатор.

Во-вторых. Важно четко себе представлять тот факт, что сессия - это сеанс работы с сайтом, так как его понимает человек. Пришел, поработал, закрыл браузер - сессия завершилась. Как сеанс в кино. Хочешь посмотреть еще один – покупай новый билет. Стартуй новый сеанс. Этому есть и техническое объяснение. Гарантированно механизм сессий работает только именно до закрытия браузера. Ведь у клиента могут не работать куки, а в этом случае, естественно, все дополненные идентификатором ссылки пропадут с его закрытием.
Правда, сессия может пропасть и без закрытия браузера. В силу ограничений, рассмотренных в самом главном разделе этого FAQ, механизм сессий не может определить тот момент, когда пользователь закрыл браузер. Для этого используется таймаут – заранее определенное время, по истечении которого мы считаем, что пользователь ушел с сайта. По умолчанию этот параметр равен 24 минутам.
Если вы хотите сохранять пользовательскую информацию на более длительный срок, то используйте куки и, если надо - базу данных на сервере. В частности, именно так работают все популярные системы авторизации:
- по факту идентификации пользователя стартует сессия и признак авторизованности передается в ней.
- Если надо "запомнить" пользователя, то ему ставится кука, его идентифицирующая.
- При следующем заходе пользователя на сайт, для того, чтобы авторизоваться, он должен либо ввести пароль, либо система сама его опознает по поставленной ранее куке, и стартует сессию. Новую сессию, а не продолжая старую.

В-третьих, не стоит стартовать сессии без разбору, каждому входящему на сайт. Это создаст совершенно лишнюю нагрузку. Не используйте сессии по пустякам – к примеру, в счетчиках. То, что спайлог называет сессиями, считается, конечно же, на основе статистики заходов, а не с помощью механизма сессий, аналогичного пхп-шному.
К тому же, возьмем поисковик, который индексирует ваш сайт. Если поисковый робот не поддерживает куки, то пхп по умолчанию будет поставлять к ссылкам PHPSESSID, что - согласистесь - может не сильно понравится поисковику, который, по слухам, и так-то динамические ссылки не жалует, а тут вообще при каждом заходе - новый адрес!
Если сессии используются для ограничения доступа к закрытому разделу сайта, то все просто поисковик и не должен его индексировать.
Если же приходится показывать одну и ту же страницу как авторизованным, так и не авторизованным пользователям, то тут поможет такой трюк – стартовать сессию только тем, кто ввел пароль, или тем, у кого уже стартовала сессия.
Для этого в начало каждой страницы вместо просто session_start() пишем
if (isset($_REQUEST[session_name()])) session_start();
таким образом, Мы стартуем сессию только тем, кто прислал идентификатор.
Соответственно, надо еще в первый раз отправить его пользователю – в момент авторизации.
Если имя и проль верные – пишем session_start()!

Возможные проблемы и их устранение.

Самыми распространенными ошибками, которые выдает РНР при попытке работать с сессиями, являются такие:
Две из них,
Warning: Cannot send session cookie - headers already sent
Warning: Cannot send session cache limiter - headers already sent

вызваны одной и той же причиной, решение описано в этом факе здесь
Третья,
Warning: open(/tmp\sess_SID, O_RDWR) failed: No such file or directory (2) in full_script_path on line number (ранее она выглядела, как Warning: Failed to write session data (files). Please verify that the current setting of session.save_path is correct (/tmp)),
если перевести ее с английского, подробно объясняет проблему: недоступен указанный в php.ini путь к каталогу, в который пишутся файлы сессий. Эту ошибку исправить проще всего. Просто прописать каталог, который существует, и доступен на запись, например,
session.save_path = c:\windows\temp
И не забыть перезагрузить апач после этого.

Как выясняется, сообразительность людская не имеет пределов, и поэтому я вынужден пояснить:
сообщение о третьей ошибке (невозможно найти каталог) НЕИЗБЕЖНО приведет к появлению первых двух, поскольку сообщение об ошибке - это вывод в браузер и после него заголовками пользоваться нельзя. Поэтому не спешите искать преждевременный вывод, а сначала пропишите правильный путь!


Следующей по распространенности проблемой при работе с сессиями является тяжелое наследие register_globals. НЕ давайте переменным скрипта имена, совпадающие с индексами массива $_SESSION!
При register_globals=on значения будут перезаписывать друг друга, и вы запутаетесь.
А при register_globals=off появится другая ошибка: "Your script possibly relies on a session side-effect which existed until PHP 4.2.3.", в случае, если в скрипте есть переменная сессии не имеющая значения, и глобальная переменная с тем же именем. Чтобы от неё избавиться, надо всегда инициализировать переменные перед использованием (или хотя бы проверять на существование) и не давать глобальным переменным имена, совпадающие с индексами массива $_SESSION.

Если не работает, но и никаких сообщений не выводится, то добавьте в самое начало скрипта две строчки, отвечающие за вывод ВСЕХ ошибок на экран - вполне возможно, что ошибки есть, но вы их просто не видите.
ini_set('display_errors',1);
error_reporting(E_ALL);

или смотрите ошибки в error_log. Вообще, тема отображения сообщений об ошибках выходит за рамки данной статьи, поэтому просто убедитесь хотя бы, что вы можете их видеть. Чуть продробнее о поиске ошибок можно прочитать в <? 
session_start
(); 
if (!isset(
$_SESSION['counter'])) $_SESSION['counter']=0;
echo 
"Вы обновили эту страницу ".$_SESSION['counter']++." раз.<br>
<a href="
.$_SERVER['PHP_SELF'].'?'.session_name().'='.session_id().">обновить</a>"
?>

При этом следует убедиться, что не включена директива session.use_only_cookies, которая запрещает PHP принимать идентификатор сессии, если он был передан через URL

Если этот пример не заработает, то проблема либо в банальных опечатках (половина "проблем" с сессиями происходит от неправильно написанного имени переменной), либо в слишком старой версии PHP: поддержка сессий появилась в версии 4.0, а массив $_SESSION - в 4.1 (До этого использовался $HTTP_SESSION_VARS).
Если же заработает - то проблема в куках. Отслеживайте - что за куку ставит сервер браузеру, возвращает ли браузер ее. Искать очень полезно, просматривая
просматривая обмен HTTP-заголовками между браузером и сервером.
Объяснение принципа работы кук выходит за рамки этого и так уж слишком большого текста, но хотя бы убедитесь, что сервер куку с идентификатором посылает, а браузер - возвращает. И при этом идентификаторы совпадают друг с другом =)
Установка куки должна выглядеть, как
Set-Cookie: PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6;
или как
Set-Cookie: PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6; path=/
(если вы запрашиваете скрипт не из корневого каталога)
Ответ сервера должен выглядеть, как
Cookie: PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6
либо
Cookie: PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6; b=b
если браузер возвращает другие куки, кроме идентификатора сессии.

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

Если пример отсюда работает, а ваш собственный код - нет, то проблема, очевидно, не в сессиях, а в алгоритме. Ищите, где потеряли переменную, по шагам переносите пример отсюда, <a href=>, но не делает этого для header-ов, яваскрипта, мета-тегов.
Поэтому надо добавлять идентификатор руками, например, так:
header("Location: /script.php?".session_name().'='.session_id());

Следует помнить, что пхп лочит файл сессии. То есть, если один ваш скрипт стартует сессию и долго выполняется, а другой пытается в это время стартовать её с тем же идентификатором, то он зависнет. Поэтому в долго выполняющихся скриптах следует стартовать сессию только тогда, когда она нужна, и тут же закрывать её, с помощью
session_write_close()

Так же, весьма редкая, и совершенно непонятно, откуда появляющаяся, проблема бывает в том, что настройка session.save_handler имеет значение, отличное от files. Если это не так - исправляйте.

Безопасность
Безопасность сессий - тема обширная. Поэтому остановлюсь на нескольких основных моментах.
Самый хрестоматийный - не передавать идентификатор через адресную строку. Об этом написано даже в php.ini, но это ограничивает функциональность сессий. Если вы решите последовать этому совету, то кроме session.use_trans_sid = 0 не забудьте session.use_only_cookies = 1
Желательно привязывать сессию к IP адресу: таким образом, если идентификатор будет украден, то злодей все равно не сможет им воспользоваться в большинстве случаев.
Рекомендуется пользоваться директивой session.save_path, с помощью которой задать собственный каталог для сохранения файлов сессий. Это более безопасно, чем когда они хранятся в общем временном каталоге сервера по умолчанию.

Дополнительная информация:
  • Кроме кук, механизм сессий посылает еще и заголовки, запрещающие кэширование страниц (тот самый cache limiter). Для html это правильно и необходимо. Но вот когда вы пытаетесь скриптом, проверяющим авторизацию, отдать файл, то интернет эксплорер отказывается его скачивать. Именно из-за этого заголовка. Вызов
    session_cache_limiter("private");
    перед стартом сессии должен решить проблему.
  • Как это ни кажется странным, но в массиве $_SESSION нельзя использовать числовые индексы - $_SESSION[1], $_SESSION['10'] - cессии работать не будут.
  • Где-то между версиями 4.2 и 5.0 невозможно было установить session.use_trans_sid с помощью ini_set(). Начиная с 5.0 уже можно снова.
  • До версии 4.3.3 куку PHP отправлял куку только если при старте сессии в запросе отсутстввал идентификатор. Теперь же кука посылается при каждом вызове session_start()

    Пример авторизации с помощью сессий
    Проиллюстрируем все вышенаписанное небольшим примером:
    создадим файл auth.php:
    <?
    if (isset($_POST['auth_name'])) {
      
    $name=mysql_real_escape_string($_POST['auth_name']);
      
    $pass=mysql_real_escape_string($_POST['auth_pass']);
      
    $query "SELECT * FROM users WHERE name='$name' AND pass='$pass'";
      
    $res mysql_query($query) or trigger_error(mysql_error().$query);
      if (
    $row mysql_fetch_assoc($res)) {
        
    session_start();
        
    $_SESSION['user_id'] = $row['id'];
        
    $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
      }
      
    header("Location: http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
      exit;
    }
    if (isset(
    $_GET['action']) AND $_GET['action']=="logout") {
      
    session_start();
      
    session_destroy();
      
    header("Location: http://".$_SERVER['HTTP_HOST']."/");
      exit;
    }
    if (isset(
    $_REQUEST[session_name()])) session_start();
    if (isset(
    $_SESSION['user_id']) AND $_SESSION['ip'] == $_SERVER['REMOTE_ADDR']) return;
    else {
    ?>
    <form method="POST">
    <input type="text" name="auth_name"><br>
    <input type="password" name="auth_pass"><br>
    <input type="submit"><br>
    </form>
    <? 
    }
    exit;
    ?>


    теперь достаточно написать во всех защищаемых скриптах строчку
    require "auth.php";

    ОПС! Очень Полезные Ссылки:
    http://www.php.net/manual/ru/ref.session.php - самая последняя и свежая информация о поддержке сессий в PHP в официальной документации, плюс многочисленные комментарии пользователей. Настоятельно рекомендуется к прочтению.
    http://phpclub.ru/manrus/f/ref.session.html - ВЕСЬМА устаревший перевод этой главы на русский, из документации в переводе Александра Пирамидина.
    http://phpclub.ru/detail/article/sessions
    Статья с пафосным названием "Правда о сессиях". Двойственное впечатление оставляет. Вначале автор ОЧЕНЬ доступно рассказывает о механизме сессий, но методы, которые он предлагает к концу статьи - совершенно мутные.

    Хрестоматийная статья Дмитрия Бородина с сайта
    http://php.spb.ru/ настоятельно НЕ рекомендуется.
    Ребята, она страшно устарела. Мало того, что в ней есть фактические неточности, так с сессиями в PHP уже давно просто не работают.
    Огромное Диме спасибо за нее, это была первая статья по сессиям на русском языке, я сам по ней учился, но сейчас надо ее отправить на заслуженный отдых.
    Так же, устарели к сожалению, и многие другие статьи, лежащие в интернете и не обновлявшиеся годами.


    В начало раздела   Наверх


    Хочу изучать PHP и Mysql. С чего начать?

    Информация для начинающих изучать PHP и MySQL
    Программное обеспечение.
    Форумы.
    Комментарии

    Информация для начинающих изучать PHP и MySQL
    Для тех, кто хочет изучать PHP, можно посоветовать великолепный "Самоучитель PHP" с сайта PHP5.RU
    Курс находится в процессе написания, но уже сейчас ссылки на отдельные уроки из него стоят в различных разделах этого FAQ. И, поверьте - оно стоит того.
    Не могу не порекомендовать замечательный материал Вадима Ткаченко АКА Bizon-а "Вступление в PHP и MySQL". Он даже издавался отдельной книгой, а сейчас - исправленный и дополненный - размещается на сайте
    "PHP в деталях". Этот ресурс стоит особняком. В отличие от предыдущих, рекомендовать прочесть его целиком может только садист - там слишком много информации. но в этом и его прелесть. Это неисчерпаемый ресурс информации по PHP. Единственное замечание - обращайте внимание на дату написания статьи. Не стоит особо доверять тем, что написаны до 2003 года.
    Ну, и, конечно же - этот сайт, http://phpfaq.ru
    Если вы еще не прочли его целиком - обязательно сделайте это. Здесь перечислены проблемы, с которыми рано или поздно столкнется КАЖДЫЙ, кто пишет на PHP.
    Неплохой курс изучения MySQL:
    http://www.intuit.ru/department/database/mysql/

    Программное обеспечение.
    Для работы с РНР под Windows, надо установить следующие программы:
    - web-сервер Apache (5Mb)
    - сам PHP (10Mb)
    - по желанию - MySQL (23Mb).
    Настройка очень простая. Апач устанавливается программой установки. Там, где он запрашивает имя вашего сервера и емейл администратора, надо 2 раза написать localhost и свой e-mail.
    PHP распаковывается из зипа в любой каталог по желанию (стандартно - C:\PHP) и настраивается обязательно как модуль Апача. Для этого надо выполнить три действия:
    - переписать файл php5ts.dll в каталог WINDOWS
    - в файл httpd.conf (C:\Program Files\Apache Group\Apache\conf\httpd.conf), в самом низу, добавить две строчки
    LoadModule php5_module c:/php/php5apache2_2.dll
    AddType application/x-httpd-php .php .php3 .phtml

    - перезапустить Апач (ярлыком Restart в группе Apache HTTP Server/Configure Apache Server)
    Выполнив все эти действия, можно положить тестовый php скрипт (допустим, он называется test.php и состоит из строчки
    <?php phpinfo(); ?>
    ) в каталог, который является корневым для веб-сервера (по умолчанию это C:\Program Files\Apache Group\Apache\htdocs\) и обратиться к нему, написав в браузере адрес
    http://127.0.0.1/test.php

    MySQL после скачивания следует распаковать из зипа, запустить setup.exe и установить. Во избежание проблем, ЛУЧШЕ установить в папку по умолчанию - c:\mysql. Если вы этого не сделали, то внимательно читайте документацию.
    После установки в командной строке (Пуск - Выполнить - cmd.exe) выполните следующие команды:
    C:\mysql\bin\mysqld --install
    net start mysql

    Все! MySQL установлена! Для проверки наберите
    C:\mysql\bin\mysql -uroot
    если консоль запустилась - все работает. Наберите exit для выхода и приступайте к конфигурированию поддержки mysql в PHP.
    Для этого, если вы не сделали этого раньше, возьмите файл c:\php\php.ini-recommended и скопируйте под именем php.ini в каталог windows. Затем отредактируйте его, убрав точку с запятой в начале строки
    ;extension=php_mysql.dll
    а затем перепишите файлы c:\php\libmysql.dll и c:\php\ext\php_mysql.dll в каталог WINDOWS и не забудьте после этого перезапустить Апач, как это было описано выше.
    Теперь вы можете использовать mysql в своих php-скриптах.

    Те, для кого эта инструкция слишком сложна, могут попробовать установить готовый комплект Денвер-2.
    В него входит сразу все, что нужно, и еще много ненужного. А главное - работает все само.
    Еще одно достоинство Денвера в том, что объем базового комплекта в 10 раз меньше полных версий - всего 4 мегабайта. А так же то, что его автор пишет интересные книжки по PHP.

    Так же, всем любознательным рекомендуется ВЕСЬМА толковая статья Установка и настройка Apache+PHP
    с сайта PHP5.RU. И, конечно же - разделы официальной документации, посвященные установке соответствующих программ.

    Форумы.
    При изучении любого дела обязательно появятся вопросы.
    Вопросы удобно задавать на форумах.
    http://phpclub.ru/talk/forumdisplay.php?s=&forumid=12
    Форум PHPклуба. Самый посещаемый и известный. К сожалению, известность служит ему дурную службу. Очень часто на вопрос новичка отвечает еще более зеленый новичок, давая совершенно неправильный ответ. Однако профессионалов там тоже предостаточно, готовых объяснить ошибки и первому и второму.

    PHP представлен и в русскоязычном сегменте Livejournal
    В сообществах ru_php и [info]ru_mysql всегда найдутся профессионалы, кототорые помогут с любой проблемой. Только не забудьте сначала прочитать правила сообщества!

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

    Сайты для начинающих.
    Ранее здесь были размещены ссылки на различные сайты от начинающих для начинающих.
    К сожалению, и так-то не блиставшие качеством материала, они давно заброшены своими авторами и окончательно потеряли актуальность.
    Все, что есть лучшего по теме PHP, перечислено вверху страницы.
    Если вы знаете хороший сайт - напишите о нем в разделе "Обратная связь".

    В начало раздела   Наверх


    \"Кавычки \". Cоставление запросов, слеши, SQL Injection

    Быстрые рекомендации.
    Правила составления запросов MySQL
    Динамическое составление запросов
    Правильная работа со спецсимволами при составлении запросов
    Подготовленные выражения
    SQL Injection
    Особенности работы с оператором LIKE
    О слешах. Как от них избавиться
    Замечания
    ОПС: очень полезные ссылки:
    Примечание: формы
    Комментарии

    Быстрые рекомендации.
    Для предотвращения SQL инъекций следует соблюдать два простых правила:
    1. Не помещать в БД данные без обработки.
    Это можно сделать либо с помощью подготовленных выражений, либо обрабатывая параметры вручную.
    Если запрос оставляется вручную, то
    - все числовые параметры должны быть приведены к нужному типу
    - все остальные параметры должны быть обработаны функцией mysql_real_escape_string() и заключены в кавычки.

    2. Не помещать в запрос управляющие структуры и идентификаторы, введенные пользователем.
    А заранее прописывать в скрипте список возможных вариантов, и выбирать только из них.

    Почитать об этом подробнее - почему надо делать именно так и как это все работает, можно ниже. И настоятельно рекомендуется.

    Правила составления запросов MySQL
    Для начала - немного о том, почему вообще нужны эти слеши.
    Если мы подставляем в запрос какие-либо данные, то, чтобы отличить эти данные от команд SQL, их надо брать в кавычки.
    К примеру, если написать
    SELECT * FROM table WHERE name = Bill
    то база решит, что Bill - это имя другого поля, не найдёт его, и выдаст ошибку. Поэтому подставляемые данные (в данном случае имя Bill) надо заключать в кавычки - тогда база сочтет его строкой, значение которой надо присвоить полю name:
    SELECT * FROM table WHERE name = 'Bill'
    Однако, и в самих данных могут встречаться кавычки тоже. К примеру,
    SELECT * FROM table WHERE name = 'Д'Артаньян'
    Здесь база данных решит, что 'Д' - это данные, а Артаньян - команда, которую она не знает, и тоже выдаст ошибку. Поэтому и надо прослешивать все данные, чтобы объяснить базе, что встречающиеся в них кавычки (и некоторые другие спецсимволы) относятся к данным.
    В результате мы получим правильный запрос, который ошибок не вызовет:
    SELECT * FROM table WHERE name = 'Д\'Артаньян'

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

    Следует специально отметить: добавленные слеши НЕ идут в базу. Они нужны только в запросе. При попадании в базу слеши отбрасываются. Соответственно, распространенной ошибкой является применение stripslashes при получении данных из базы.

    На самом деле, всё вышесказанное относится к данным строкового типа и датам. Числа можно вставлять не прослешивая и не окружaя кавычками. Если вы так делаете, то ОБЯЗАТЕЛЬНО! насильно приводите данные к нужному типу перед вставкой в запрос, например:
    $id=intval($id);
    Однако для простоты (и надёжности) можно и с числами работать, как со строками (проскольку mysql всё равно преобразует их к нужному типу). Соответственно, мы будем любые данные, вставляемые в запрос, прослешивать и заключать в кавычки.

    Так же, есть ещё одно правило - необязательное, но его следует придерживаться во избежание появления ошибок:
    Имена полей и таблиц следует заключать в обратные одинарные кавычки - "`" (клавиша с этим символом находится на стандартной клавиатуре слева от клавиши "1") Ведь имя поля может совпадать с ключевыми словами mysql, но если мы используем обратную кавычку, то MySQL поймёт всё правильно:
    SELECT * FROM `table` WHERE `date` = '2006-04-04'
    Следует различать эти кавычки и не путать одни с другими. Следует также помнить, что обратные кавычки слешами не экранируются.

    Динамическое составление запросов
    Если SQL запрос в скрипте написан целиком, и никак не меняется, например
    SELECT * FROM `table`
    то никаких проблем с ним и не будет.
    Но вся сила наших скриптов именно в динамическом составлении запросов!
    Вместо того, чтобы писать готовые запросы на все случаи жизни, мы составляем их на основании поступающих в скрипт данных.
    И вот здесь нас подстерегает опасность.
    Допустим, мы составляем запрос с использованием переменной:
    SELECT * FROM table WHERE name = '$name'
    Вроде бы - всё нормально?
    А если $name у нас будет Д'Артаньян? Запрос выдаст ошибку!
    То есть, переменную перед подстановкой в запрос надо прослешить.
    Это можно сделать несколькими путями.
    Самый простой (и неправильный) - положиться на волшебные кавычки. Как вы уже догадались, именно для этого случая они и были придуманы. Ради того, чтобы уберечь SQL запросы забывчивых программистов от ошибок, ВСЕ поступающие в скрипт данные прослешиваются без разбору.
    Если вы используете чужой код, то лучше воспользоваться волшебными кавычками. Это может создавать некоторые неудобства и не гарантирует вас от ошибок или взлома (поскольку прослешиванием правила составления запросов не исчерпываются) но хотя бы снижает риск. Поэтому, при использовании чужого кода, обязательно убедитесь, что волшебные кавычки включены.
    Если же вы пишете весь код самостоятельно, то следует научиться правильному составлению запросов.

    Правильная работа со спецсимволами при составлении запросов
    Итак. Как мы уже узнали выше, чтобы правильно составить запрос, надо заключать данные в кавычки и прослешивать их.
    С первым всё понятно. При составлении динамических запросов мы никогда не забываем все данные заключить в кавычки:
    $query="INSERT INTO `table` VALUES(NULL,'$name','$date','$price')";
    Если переменная $price должна быть типа int и мы приведём её к этому типу, то можно её не заключать в кавычки. Однако, если заключим, то беды особой не будет, но зато можно будет сделать работу с данными единообразной.

    Второе же - прослешивание - и является тем, ради чего, собственно, по большей части, и написан весь этот текст. Поскольку вызывает больше всего вопросов и затруднений.

    Сначала отключим волшебные кавычки. Так, как это описано в самом начале.
    Почему это следует сделать?
    По многим причинам. Самая очевидная - логическая. "Волшебные кавычки" добавляют слеши не там, где они нужны - при составлении запроса, а еще до попадания в скрипт! Но ведь данные совсем не обязательно после этого будут вставляться в запрос. Может быть, их придётся выводить пользователю, и слеши будут только мешать. Плюс к тому, добавленные слеши помешают, к примеру, правильно проверить длину введённой строки. К тому же, прослешивать нам надо не только пришедшие от пользователя данные, а вообще любые, вставляемые в запрос - многим этот очевидный факт даже не приходил в голову! Список можно продолжать, но вывод один: добавлять слеши надо не автоматом, без разбору, до начала выполнения скрипта, а только там, где действительно надо – при составлении запроса.
    Есть и ещё одна причина: при использовании кодировки Unicode, которая приобретает всё большую популярность, а со временем займёт доминирующее положение в веб, волшебные кавычки могут попросту испортить текст, приняв часть мультибайтной строки за спецсимвол.

    Теперь займёмся добавлением слешей самостоятельно.
    Во-первых, для прослешивания мы воспользуемся функцией mysql_real_escape_string()
    Следует помнить, что применять её можно только после установления соединения с базой.
    Эта функция делает гораздо больше, чем устаревшие addslashes и mysql_escape_string. Во-первых, она облегчает ведение и чтение логов mysql, заменяя, например, символ перевода строки на "\n" и некоторые другие символы на escape-последовательности. Во-вторых, и самое главное - она корректно работает с многобайтными кодировками, принимая во внимание текущую кодировку MySQL и не портит, таким образом, тексты в кодировке Unicode.
    Во-вторых, не забываем, что прослешить надо все те данные, которые мы заключили в запросе в кавычки:

    $name
    =mysql_real_escape_string($name);
    $age=mysql_real_escape_string($age);
    $query="INSERT INTO table (name,age,class) VALUES ('$name','$age',11)";

    или:
    $query="SELECT * FROM table WHERE name LIKE '".mysql_real_escape_string($_GET['name'])."%'";

    Видно, что код получается довольно громоздким. Для облегчения составления запросов можно пуститься на разные ухищнения - сделать функцию для составления запросов из массива (очень удобно для запросов типа INSERT и UPDATE), прослешивать массив данных в цикле, и так далее.
    Вы можете написать и свою библиотеку или функцию для составления запросов.
    Главное - помнить, что только неукоснительное соблюдение правил составления запросов гарантирует вас от взлома БД, а так же сознавать, что применение "волшебных кавычек", при видимой лёгкости составления запросов, не даёт такой гарантии, а только мешает нормальной работе приложения.

    Подготовленные выражения
    Есть еще один способ отправлять запросы в БД, называемый "подготовленными выражениями" (prepared statements).
    Суть его заключается в том, что подготавливается шаблон запроса, со специальными маркерами, на место которых будут подставлены динамические компоненты. Пример такого шаблона:
    SELECT * FROM table WHERE name=?
    Знак вопроса здесь - это тот самый маркер. По-другому он называетсй плейсхолдером (placeholder). Весь секрет в том, что данные на его место подставляет специальная функция, которая "привязывает" переменную к запросу.
    Вот как выглядит код в таком случае:
    $stmt $mysqli->prepare("SELECT District FROM City WHERE Name=?");
    $stmt->bind_param("s"$city);
    $stmt->execute();

    В первой строчке мы подготавливаем шаблон запроса.
    Во второй - привязываем к маркеру значение переменной $city.
    В третьей строчке выполняем подготовленный таким образом запрос.
    При этом запрос и данные идут в базу не вместе, а по отдельности, исключая возможность какой-либо ошибки или злонамеренной манипуляции.

    Понятно, что возникает много вопросов. Но ни объём, ни тематика данной статьи не позволяют остановиться на них более подробно. Рекомендую обратиться к документации по библиотекам mysqli и PDO, реализующим данный принцип.
    Так же, можно использовать библиотеку DbSimple Дмитрия Котерова или PEAR::DB. Основное отличие этих двух состоит в том, что они реализуют механизм подготовленных выражений только внешне. А внутри работают по-старинке - составляя запрос и отправляя его в базу, беря на себя работу по обработке переменных. А PDO и mysqli работают, как было описано выше - то есть, шаблон запроса и данные уходят в базу по отдельности.

    SQL Injection

    Итак, мы научились правильно подставлять в запрос данные.
    НО! Динамическое составление запросов не исчерпывается подстановкой данных. Часто нам приходится подставлять в запрос команды SQL и имена полей. И здесь мы уже переходим к теме безопасности:

    SQL Injection - это способ хакерской атаки, когда передаваемые скрипту данные модифицируются таким образом, что запрос, формируемый в этом скрипте, начинает выполнять совсем не то, для чего он предназначался.
    Правила защиты от таких атак можно разделить на два пункта:
    1. Работа с данными.
    2. Работа с управляющими элементами запроса.

    Первый пункт мы подробно рассматривали выше. Он, можно сказать, и не является, собственно, защитой. Соблюдение правил добавления занных в запрос продиктовано, в первую очередь, требованиями СИНТАКСИСА SQL. А как побочный эффект мы имеем и защиту от взлома.

    Второй пункт гораздо сложнее, поскольку не существует такого же единого универсального правила, как для данных - обратная кавычка никак не защитит имя поля от модификации хакером. Невозможно кавычками защитить имя таблицы, операторы SQL, параметры команды LIMIT, и другие операторы.
    Поэтому основное правило при подстановке управляющих элементов в запрос такое:
    Если требуется динамически подставлять в запрос операторы SQL или имена полей, баз данных, таблиц, то ни под каким видом не вставлять их в запрос напрямую.
    Все варианты таких добавлений должны быть ЗАРАНЕЕ прописаны в вашем скрипте и выбираться на основании того, что ввёл пользователь.
    К примеру, если надо передать имя поля в оператор order by, то ни в коем случае нельзя подставлять его напрямую. Надо сначала проверить его. К примеру, сделать массив допустимых значений, и подставлять в запрос только если переданный параметр в этом массиве присутствует:
    $orders=array("name","price","qty");
    $key=array_search($_GET['sort'],$orders));
    $orderby=$orders[$key];
    $query="SELECT * FROM `table` ORDER BY $orderby";

    Мы ищем в массиве заранее описанных вариантов введённое пользователем слово, и, если находим, то выбираем соответствующий элемент массива. Если совпадения не будет найдено, то будет выбран первый элемент массива.
    Таким образом, в запрос подставляется не то, что ввёл пользователь, а то, что было прописано у нас в скрипте.
    Точно так же надо поступать и во всех остальных случаях
    К примеру, если динамически формируется оператор WHERE:
    if (!empty($_GET['price'])) $where.="price='".mysql_real_escape_string($_GET['price'])."'";
    $query="SELECT * FROM `table` WHERE $where";

    Мне сложно представить себе случай, когда имя таблицы может подставляться в запрос динамически, но если такое случится, то имя тоже надо вставлять только из заранее прописанного в скрипте набора.
    Параметры оператора LIMIT следует принудительно приводить к целочисленному типу с помощью арифметических операций или функции intval().
    Не следует думать, что перечисленными здесь примерами исчерпываются все варианты динамического составления запросов. Нужно просто понять принцип, и применять его во всех подобных случаях.

    Особенности работы с оператором LIKE
    Совершенно отдельный случай - оператор LIKE.
    Во-первых, помимо обычного прослешивания, в переменных, которые подставляются в LIKE, надо удваивать слеши. То есть, если в переменной содержится символ \, то его надо удвоить, а после этого выполнить обычное прослешивание, через mysql_real_escape_string.
    К примеру, если мы ищем строку
    символ \ называется "backslash"
    и нам нужно точное совпадение, то мы просто применяем mysql_real_escape_string и запрос получается стандартный:
    SELECT * FROM test WHERE field = 'символ \\ называется \"backslash\"'
    Если же мы хотим подставить эту строку в LIKE, то сначала надо заменить каждый слеш на два, а потом применить mysql_real_escape_string. В результате получится
    SELECT * FROM table WHERE field LIKE '%символ \\\\ называется \"backslash\"%'

    Во-вторых, следует обратить внимание на то, что ни одна из функций, добавляющих слеши, не добавляет их к метасимволам поиска "%" и "_", используемым в операторе LIKE. Поэтому, если вы используете этот оператор, и не хотите, чтобы символы _ и % использовались, как маски, то добавляйте слеши вручную. Это можно сделать командой
    $data addCslashes($data'%_');
    Внимание - это не addslashes! В имени этой функции есть дополнительная буква "c".

    Таким образом получается, что переменные, используемые в операторе LIKE мы должны обрабатывать отдельно.
    сначала заменять один слеш на два, с помощью такого, к примеру, кода:
    $var=str_replace('\\','\\\\',$var);
    затем (можно наравне со всеми другими данными, идущими в запрос) прослешиваем:
    $var=mysql_real_escape_string($var);
    а затем, если хотим, чтобы _ и % соответствовали точно самим себе, делаем
    $var=addCslashes($var'_%');

    В результате, если мы будем искать, к примеру, такую строку
    символ \ называется "backslash", а символ _ называется "underscore"
    то после обработки, в запросе она должна выглядеть так:
    '%символ \\\\ называется \"backslash\", а символ \_ называется \"underscore\"
    То есть, слеш, который был в строке изначально - учетверился. Остальные символы прослешились, как обычно. Плюс - прослешился символ подчёркивания.

    О слешах. Как от них избавиться
    Слеш, или бэкслеш, от английского back slash - обратная косая черта ("\"), которая непонятным образом вдруг сама собой появляется в ваших переменных. Добавляется он к некоторым спецсимволам, но в основном его замечают из-за кавычек.
    Происходит это из-за специальных настроек PHP, обычно включённых на хостинге по умолчанию. Теоретически, эти настройки могут повысить безопасность скриптов, работаюющих с БД. Практически же, от автоматического добавления слешей часто получается путаница и неудобство, как при работе с БД, так и при её отсутствии.
    Ниже мы подробно разберём оба этих случая.

    За автоматическое добавление слешей отвечают директивы php.ini, которые носят общее название "волшебные кавычки":
    magic_quotes_gpc и magic_quotes_runtime
    Если включена первая, то PHP автоматически добавляет слеши к данным, пришедшим от пользователя - из POST, GET запросов и кук (а так же - к логину и паролю, полученным через HTTP Authorisation).
    Если вторая, то слеши добавляются к данным, полученым во время исполнения скрипта - например, из файла или базы данных.

    Если вы работаете без базы данных, или же работаете с БД правильно (о чём будет написано ниже), лишние слеши вам только мешают, и от них надо избавляться. Проще и правильнее всего отключить автоматическое добавление, в настройках PHP.
    Это можно сделать либо поправив соответствующие директивы в php.ini, если у вас есть к нему доступ, либо создав в коневом каталоге сайта файл .htaccess, и добавив в него строчки
    php_flag magic_quotes_gpc 0
    php_flag magic_quotes_runtime 0


    Если отключить таким образом не получается, то придётся писать код разной степени сложности, чтобы очистить от слешей входящие данные. (Впрочем, если вы хотите написать переносимое приложение, не зависящее от настроек PHP, то написать его всё равно придётся. И включать отдельным блоком в начале ваших скриптов).

    С данными, получаемыми во время работы, разобраться проще всего: достаточно в начале скрипта написать
    set_magic_quotes_runtime(0);
    Для данных, полученных от пользователя, всё гораздо сложнее. Для этого кода нам потребуется две функции:
  • проверить, добавил ли PHP, можно с помощью функции get_magic_quotes_gpc.
  • удаляет слеши функция stripslashes.
    Соответственно, с помощью первой надо проверить, и, если PHP добавил, то перебрать все входящие переменные и очистить с помощью второй.
    Если вы работаете правильно, при register_globals = off, то достаточно применить stripslashes ко всем массивам, содержащим данные, приходящие из браузера.
    к примеру, можно включить во все скрипты сайта вот такой код:
    function strips(&$el) { 
      if (
    is_array($el)) 
        foreach(
    $el as $k=>$v
          
    strips($el[$k]); 
      else 
    $el stripslashes($el); 

    if (
    get_magic_quotes_gpc()) { 
      
    strips($_GET);
      
    strips($_POST);
      
    strips($_COOKIE); 
      
    strips($_REQUEST);
      if (isset(
    $_SERVER['PHP_AUTH_USER'])) strips($_SERVER['PHP_AUTH_USER']); 
      if (isset(
    $_SERVER['PHP_AUTH_PW']))   strips($_SERVER['PHP_AUTH_PW']);
    }

    В случае же неправильных настроек register_globals приемлемое решение и вовсе будет найти затруднительно, поэтому лучше - повторюсь - сразу работать при правильных настройках.

    Замечания
    • Среди причин, по которым не стоит полагаться на "волшебные кавычки", есть ещё одна. Весьма маловероятная, но всё же. К "волшебным кавычкам" относится на самом деле не две директивы, а три. Третья - magic_quotes_sybase. Мало того, что она вместо слеша добавляет кавычку - так она ещё и отменяет действие magic_quotes_gpc. Если каким-то чудом обе эти директивы имеют статус 'on', то последняя не сработает! То есть, полагаясь на "волшебные кавычки", мы в этом случае получим все прелести неправильно составленных запросов. Вообще, чисто теоретически, надо учитывать наличие этой директивы, поскольку она преподносит ещё и такой сюрприз, как... изменение поведения функций addslashes и stripslashes! Если magic_quotes_sybase = on, то эти функции начинают вместо слеша добавлять и удалять одинарную кавычку соответственно.
    • Все приведенные примеры касаются только БД Mysql. Конкретные правила составления запросов могут отличаться для других СУБД, но общий принцип остается прежним:
      • если API для работы с БД или сторонняя библиотека предоставляет специальные функции для составления запросов, и есть возможность их использования, то пользоваться в первую очередь надо ими.

      • если таких функций нет, то следует искать в документации функции экранирования спецсимволов для этой СУБД.


    ОПС: очень полезные ссылки:
    Про волшебные кавычки на сайте PHP.NET
    Про SQL-инъекции на сайте PHP.NET

    Несколько материалов по SQL Injection:
    http://www.securitylab.ru/45438.html
    http://www.securitylab.ru/49424.html
    http://www.nextgenss.com/papers/advanced_sql_injection.pdf

    Примечание: формы
    При выводе value в тегах input форм, слеши не помогают.
    Чтобы текст в таком поле выводился целиком, value надо заключать в кавычки, а к выводимым данным применять функцию htmlspecialchars()
    Пример:
    <input type="text" name="name" value="<? echo htmlspecialchars($name,ENT_QUOTES)?>">
    <textarea><? echo htmlspecialchars($text,ENT_QUOTES)?></textarea>

    Необходимо так же отметить (хоть это уже совсем не имеет отношения к кавычкам и слешам), что функцию htmlspecialchars следует применять при выводе в браузер вообще ко всем данным, которые получены от непроверенного пользователя. Почему это следует делать, можно почитать в гугле по запросу что такое XSS уязвимость

    В начало раздела   Наверх


    Регулярные выражения.


    Что такое регулярные выражения?
    Это замечательный инструмент для работы с текстом.
    Такой же революционный, по сравнению с обычными строковыми функциями, как БД - по сравнению с текстовыми файлами. Это специальный язык для работы с текстом. Причём подчас одна строчка с использованием регулярных выражений может заменить страницу другую обычного кода!

    С помощью регулярных выражений можно эффективно искать фрагменты текста любой сложности, заменять одни вхождения на другие.
    Основа регулярного выражения - шаблон. С его помощью мы описываем формат нужного нам фрагмента текста, а затем либо проверяем, подходит ли текст под шаблон, либо выразаем одно или несколько вхождений шаблона, либо заменяем на какой-либо текст.

    В данном топике собраны все ссылки, которые помогут вам быстро освоить и эффективно использовать регулярные выражения.
    Во-первых, это прекрасные статьи для начинающих Сергея Колесниченко (Yukko)
    Регулярные выражения, Часть I
    Регулярные выражения, Часть II
    Материал изложен в лёгкой и доступной форме, снабжён примерами решения часто встречающихся задач. Служит для первичного знакомства с предметом.

    При практическом же использовании не обойтись без справочных материалов официальной документации.
    В PHP поддерживается два стандарта регулярных выражений - POSIX и PCRE.
    Первый считается более устаревшим и медленным, вы не найдёте по нему хороших примеров и даже документация не переведена на русский.
    По PCRE (функции preg_*) документация очень солидная.
    Во-первых, это подробнейшее изложение синтаксиса и пояснение значения всех спецсимволов - Синтаксис регулярных выражений
    Во-вторых, - Общее описание, список функций и полезные комментарии
    В-третьих, пояснение довольно важного элемента - Модификаторов шаблонов
    Ну, и документация на все функции, разумеется. Основными из которых являются preg_match, preg_match_all и preg_replace с незаменимыми комментариями пользователей.
    Всё (кроме комментариев) - на русском языке.

    Нельзя, так же, обойти вниманием фундаментальный труд столпа российского PHP Дмитрия Бородина,
    Регулярные выражения в PHP (сравнение Perl и PHP)

    Так же можно скачать знаменитую книгу Дж.Фридла "Регулярные выражения" в формате DjVu.
    Книга написана с примерами на Perl, но разобраться там несложно.

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

    чтобы данный текст не выглядел сухой теорией, приведем пример решения наиболее распространённой задачи - "подсветка ссылок" или превращение http://phpfaq.ru в ссылку

    $text=preg_replace("#(https?|ftp)://\S+[^\s.,>)\];'\"!?]#",'<a href="\\0">\\0</a>',$text);

    Им можно пользоваться, не понимая ни слова. А узнать, что означают эти закорючки, можно из ссылок выше :-)

    В начало раздела   Наверх


    Документация по PHP. Мануал по PHP, книги.

    Документация на русском языке:
    Документация на английском языке.
    Книги, журналы
    Документация и книги по MySQL
    Комментарии

    Документация на русском языке:
    Перевод официальной документации - http://www.php.net/manual/ru/
    Далеко не все еще переведено, но, тем не менее, эту ссылку можно рекомендовать, как замену официального англоязычного мана. Что переведено - будет по-русски. Не переведенное же будет точной копией английского.
    Скачать полную версию документации в различных форматах (в том числе - в очень удобном .chm) можно здесь: http://www.php.net/download-docs.php
    Перевод делается руками добровольцев, которых, как всегда, не хватает. Если вы хотите помочь этому благородному делу и чувствуете себя в силах, то пишите (in English please), в лист рассылки по адресу doc-ru@lists.php.net, а так же по этому адресу с благодарностью будут приняты сообщения о неточностях в переводе.

    Удобно: Быстрый доступ к описанию функции из онлайн документации можно получить введя в браузере адрес www.php.net/имя_функции

    Существует полный русский перевод документации от версии 4.2 от некоего Александра Пирамидина.
    Перевод машинный, но тем не менее - вполне читабельный. ЧУДОВИЩНО устарел.
    пользоваться этим переводом можно только в самом КРАЙНЕМ случае!
    Только если нужный вам участок официальной документации не переведен, а в английском, даже с переводчиком - ни в зуб ногой.
    Онлайн, на PHPClub-e, без баннеров: http://phpclub.ru/manrus/
    chm-версия: http://web.php.net.ua/download?what=php4

    Документация на английском языке.
    Самая актуальная и правильная:
    Онлайн, с комментариями пользователей: http://www.php.net/manual/en/
    Очень удобно иметь всю документацию в одном файле с быстрым поиском. Такая документация есть, в формате windows help (.chm):
    http://www.php.net/distributions/manual/php_manual_en.chm
    Расширенный вариант документации в формате .chm, с очень ценными комментариями пользователей постоянно обновляется на этом сайте
    http://weblabor.hu/php-doc-chm

    Книги, журналы

    PHP. Сборник рецептов. Пeревод самой лучшей книги по PHP.

    Уникальная книга. Собрание конкретных ответов на конкретные вопросы. Как дату сложить, отнять, отформатировать. Как со строками работать, с файлами, с БД. Если сравнивать книги по количеству воды в них, то это будет пустыня Сахара. Книга разбита на 20 глав, каждая глава состоит из разделов вида: постановка проблемы - решение - объяснение.
    Немного устарела, в плане того, в чем пхп сильно ушел вперед - XML, обработка ошибок. Однако в базовых основах языка остается непревзойдённой. Может использоваться как в виде справочника для решения конкретных проблем, так и в виде учебника.


    Котеров Д., Костарев А., "PHP5 в подлиннике".

    Второе издание знаменитой книги Д.Котерова. Не имеет ничего общего с первым. Это не переработанная, это совершенно новая книга. Уникальна тем, что подходит как начинающему, так и профессионалу - в ней изложены все аспекты программирования на PHP. В отличие от всех прочих скороспелок по "PHP5", вышедших ДО выхода самой пятой версии, книга действительно написана на материале релиза 5 версии.

    Если совсем нету денег на книги, то можно почитать очень старый учебник Д.Гилмора "PHP - учебный курс": http://phpfaq.ru/txt/gilmor

    Первый русскоязычный онлайн журнал по PHP - PHPinside.RU

    Сборник черезвычайно полезных сведений от авторa РНР: www.lerdorf.com/tips.pdf (на английском языке)

    Документация и книги по MySQL
    Самую свежую версию официальной документации для своей версии MySQL можно посмотреть на сайте http://dev.mysql.com/doc/
    Раньше там был и русский перевод к версии 4.0, но поскольку он устарел и не поддерживается, то его с сайта убрали. Посмотреть его по можно по адресу http://www.mysql.ru/docs/man
    Книга М.Грабера Введение в SQL http://www.mysql.ru/docs/gruber/



    П.Дюбуа. MySQL. Сборник рецептов
    Книга из суперсерии "cookbook" (книга рецептов) от автора "Библии MySQL" Поля Дюбуа. книга, необходимая каждому разработчику, использующему MySQL. Собрание ответов на любые практические вопросы. По ссылке можно ознакомиться с одной из глав книги.


    П.Дюбуа. MySQL. 2-е издание
    . Второе издание "Библии MySQL", на которой училось не одно поколение девелоперов и администраторов. Объясняет секреты MySQL от "а" до "я", но при этом очень простым и понятным языком.



    В начало раздела   Наверх


    Ничего не работает! Что делать??? Поиск ошибок и отладка.

    Быстрые рекомендации.
    Введение. Очень важное.
    Сообщения об ошибках PHP.
    Отладка и поиск ошибок в своем алгоритме.
    Пример отладки.
    Самое важное - знать, что ты хочешь получить.
    Заключение.
    Комментарии

    Быстрые рекомендации.
    1. Убедитесь, что вы видите сообщения об ошибках, если они возникают.
    Для этого надо добавить в начало скрипта 2 строчки
    ini_set('display_errors',1);
    error_reporting(E_ALL);

    Хотя в некоторых случаях это всё равно не поможет. Тогда смотрите ошибки в логах веб-сервера.
    Ещё можно добавить в файл .htaccess строчку
    php_flag display_errors 1
    Обязательно убрать всех собак (@) из кода!
    Если апач выдаёт ошибку 500 - значит надо смотреть текст ошибки в логе ошибок веб-сервера.

    2. При проблемах с MySQL (supplied argument is not a valid MySQL result resource) под строкой, где произошла ошибка, обязательно надо вывести на экран mysql_error() и сам запрос - для визуального контроля и копирования на форум. повторяю - вывести надо ЗАПРОС! А не PHP-код, который его формирует.

    3. При работе с изображениями, чтобы увидеть сообщение об ошибке, обязательно надо догадаться отключить вывод заголовка, говорящего браузеру, что дальше идет картинка.
    И, естественно, обращаться к скрипту напрямую, а не через тег <img>!

    4. При проблемах в аплоаде в первую очередь смотрите массив $_FILES (print_r($_FILES);). Описания ошибок из $_FILES['filename']['error'] есть в мануале.

    5. При проблемах во взаимодействии сервера и клиента (куки, сессии, запросы)- в обязательном порядке смотреть обмен HTTP заголовками

    6. И САМОЕ ВАЖНОЕ: запуская скрипт, смотрите не то, что показывает браузер, а ИСХОДНЫЙ HTML код!.

    Получив сообщение об ошибке, вы можете его прочитать и исправить.
    Если не справились - пишите на форум. При этом КОПИРУЙТЕ сообщение об ошибке, и КОПИРУЙТЕ небольшой - 3-5 строк - кусок кода, на который указывает ошибка. Повторяю - КОПИРУЙТЕ! никакой отсебятины!

    Если вы всё равно не нашли ошибку - читайте дальше:

    Введение. Очень важное.
    Ты написал программу, а она не работает.
    Вариантов ты видишь немного - либо сидеть и пытаться умственным усилием обнаружить ошибку, в сотый раз просматривая код, либо пойти на форум и попросить, чтобы там тебе нашли ошибку.
    Самое интересное, что есть третий, в сто раз лучше первых двух.
    Этот способ называется "Отладка программы". По-английски - debug.
    Заключается он в том, чтобы заставить программу саму показать, где в ней ошибка.
    Это мало того, что получится быстрее, чем спрашивать на стороне - так зачастую это единственный способ решить проблему. Единственный.
    Я тебе сейчас открою страшный секрет. В мире НЕТ программистов, которые пишут код, как художники на Арбате - сел, наваял, отдал. Нету. И не будет.
    Процесс написания программы - циклический: Написал кусок кода - посмотрел, как работает. Если не работает - ищем ошибки. Работает - пишем дальше.
    Только так. Других вариантов нет.
    Больше того. В большинстве случаев совершенно бесполезно вываливать на форум свой код, и спрашивать - "В чём ошибка?". На форуме не сидят волшебники вперемешку с телепатами. И гадалок с прорицателями - тоже нет. Поэтому отгадывать, в чём, теоретически, может быть ошибка, никто не будет. Ошибку найти может только хозяин программы. На своём сервере. Со своими настройками и опечатками. Поэтому локализовать ошибку - найти место, где она происходит, определить тип ошибки - можно только самостоятельно. А вот исправить её на форуме помогут. Если не получится самому.

    Те, кто приходит к веб-программированию от дизайна, или от игр, или от нечего делать, просто не знают этой страшной тайны: Основное время программиста уходит не на написание кода. Основное время программиста уходит на поиск ошибок и отладку. Это не шутка. Это правда. И если вы решили заняться программированием, то вам придётся искать ошибки точно так же, как это делают все остальные.
    К сожалению, очень много людей приходит к PHP вообще без опыта программирования и, как следствие - никогда не слышали об отладке.
    А это и есть самое главное в программировании - умение искать ошибки.
    И мы с тобой сейчас будем учиться их искать.

    Программа не работает. Что можно сделать в этом случае?

    Сообщения об ошибках PHP.
    Самый твой большой помощник в деле отладки - это сам PHP. При возникновении любых проблем он сообщит тебе о них. То есть, в первую очередь ты должен убедиться в том, что если сообщение об ошибке есть - ты его увидишь.

    Пояснение: Очень многие люди не понимают, что такое сообщение об ошибке. В основном эти люди делятся на две категории. Первая считает, что сообщения об ошибках - это блажь разработчиков языка, надоедливый сервис, сделанный для того, чтобы программисту было не скучно. И избавляться от сообщений надо любыми доступными способами. Вторая категория не выросла из школьного возраста и воспринимает не смысл ошибки, а лишь сам факт. Принимают пхп за стервозного завуча, который ругает не по делу, а абстрактно, за то, что оболтус. То есть, факт появления ошибки вызывает у этих людей только негативные эмоции, а в смысл они даже не пытаются вчитываться.

    Это чудовищные заблуждения. Сообщения об ошибках - это ПОМОЩЬ! Это громадная помощь программисту. Как ей воспользоваться, мы рассмотрим ниже.


    Даже самый рабочий код, которому ты на 100% доверяешь, и который на соседней машине работает, как часы, может выдавать сообщение об ошибке. Причин тому может быть бесконечное множество. Это и такой распространенный случай, как отсутствие прав доступа к файлам, и такие экзотические, как запрещение провайдером выполнения самых распространенных и безобидных функций.

    Твоя задача - обеспечить интерпретатору возможность сообщить тебе об ошибке при возникновении оной.
    Ты должен быть уверен, что если выполнение программы вызывает ошибку, то ты эту ошибку увидишь.
    Что интерпретатору не запрещено сообщать тебе об ошибке.
    Что ты запросил все варианты вывода сообщения об ошибке.

    Во-первых, надо выяснить, выводятся ошибки на экран или пишутся в лог. Обычно, домашний, или тестовый сервер настраивается так, чтобы ошибки выводились на экран. Рабочий же сервер, с сайтом в публичном доступе ОБЯЗАТЕЛЬНО должен быть настроен так, чтобы ошибки не выводились на экран (поскольку посетителю они все равно ничего не скажут, а программист их не увидит), а писались в лог, где программист их увидит.
    Если ты не уверен, и не знаешь, где посмотреть, а ошибку найти надо срочно, то напиши в самом начале скрипта две строчки
    ini_set('display_errors',1);
    error_reporting(E_ALL ^E_NOTICE);

    Эти две строки заставят выводить сообщения обо всех критических ошибках на экран.
    Если никаких ошибок не выведется, надо написать
    error_reporting(E_ALL);
    Это очень сильно поможет, показав несуществующие переменные и другие мелкие ошибки, которые обычно игнорируются, но от которых может зависеть работоспособность программы.
    ВАЖНО! В случае ошибки синтаксиса, по очевидным причинам, установка с помощью ini_set не сработает.
    Поэтому лучше на неё не надеяться, а либо исправить в php.ini (или в .haccess добавить строчку php_flag display_errors 1), либо искать ошибку в логе.

    Во-вторых, убедись, что в коде отсутствуют символы '@' перед именами функций. Этот запрещает вывод сообщения об ошибке. Хорошенькое дело! Ты ошибку ищешь-ищещь, а сам же своей программе рот заткнул.

    Если ты уверен, что ошибка есть, но на экран она всё равно не выводится - найди лог ошибок веб-сервера. Обычно, это файл с названием error_log. Где он находится - надо посмотреть в документации или спросить в службе поддержки провайдера.

    При возникновении проблем с функциями mysql (supplied argument is not a valid MySQL result resource) под строкой, где произошла ошибка, обязательно надо вывести на экран mysql_error() и сам запрос - для визуального контроля и копирования на форум.

    При работе с изображениями, чтобы увидеть сообщение об ошибке, обязательно надо догадаться отключить вывод заголовка, говорящего браузеру, что дальше идет картинка.
    При аплоаде в первую очередь смотрите массив $_FILES.
    При проблемах во взаимодействии сервера и клиента - в обязательном порядке смотреть обмен HTTP заголовками
    И всегда смотрите не то, что показывает браузер, а ИСХОДНЫЙ HTML код!

    Допустим, сообщение об ошибке появляется, и ты его получил. Что делать дальше? Очень просто - прочесть и исправить. Если не хватает знания английского языка, то стоит либо воспользоваться переводчиком, либо взять значащую часть этого сообщения и запросить Google. 90% вероятности, что кто-то с такой ошибкой уже сталкивался, и ты тут же прочтешь ответ.
    Если же не нашел, то задай вопрос в форуме, точно скопировав небольшой (3-5 строк) кусок кода, в котором произошла ошибка, точно указав строку, о которой говорится в сообщении об ошибке, а так же - самое главное! - само сообщение об ошибке.
    Согласись, что с такой информацией тебе на форуме помогут гораздо скорее и качественней?

    Отладка и поиск ошибок в своем алгоритме.
    Но бывает так, что программа не вызывает ошибок, но все равно не работает, или работает не так, как надо.
    Тут уже виноват или алгоритм или какие-то внешние факторы.
    Однако и тут можно найти место, где происходит ошибка.
    Но только при одном условии.
    что ты четко представляешь, что делает твоя программа, каждая функция, каждая строка в ней. Потому, что если ты представляешь, то можешь предсказать, какие значения должны иметь переменные на каждом этапе выполнения.
    А дальше все ОЧЕНЬ просто!
    Во-первых, надо разделить программу на логические блоки.
    Допустим, скрипт выводит форму, получает ее, и записывает данные в базу. ТРИ шага! И в любом из них может быть ошибка, приводящая к тому, что данные в базу не записываются.
    Надо проконтролировать на каждом из участков - все ли переменные имеют то значение, которое ожидается.
    Программа ведь работает с переменными.
    Как проверить?
    Выводить все используемые переменные на экран! И визуально контролировать их содержимое.
    Всего-то лишь написать проблемных местах var_dump($var) и выяснится, что переменная-то пустая!
    И уже можешь пойти на форум не с вопросом "у меня вот код на 100 строк, где ошибка?", а "я написал функцию, но почему-то, когда обращаюсь в ней к переменным, они все пустые". или "из формы не передаются переменные".
    Между этими двумя способами задания вопросов - пропасть.
    Первый не может тебе помочь никак. Ты, собственно, и сам не знаешь, что у тебя за проблема. А при втором ты уже знаешь проблему, и, если сам не справился с ее решением, то можешь задать на форуме конкретный вопрос.

    Еще очень поможет избежать ошибок в программе выставление error_reporting в E_ALL с самого начала работы скрипта.
    Если при отлове критических ошибок сообщения о потенциальных ошибках могут нам помешать увидеть главную, то при разработке нам желательно видеть все - и потенциальные в том числе. Скажем, при E_ALL, при обращении к несуществующей переменной, PHP выдаст предупреждение. То есть, тебе не придется самому выводить переменную, чтобы выяснить, что ты не присвоил ей никакого значения - РНР тебя сам предупредит.

    Пример отладки.
    Из html формы передаются чекбоксы с именами c_1, c_1, c_3... c_10
    В скрипте мы пытаемся в цикле вывести
    for ($i=1$i<11$i++) {
      echo 
    $_POST['с_$i'];
    }

    скрипт ничего не выводит.
    Начинаем отлаживать.
    Сначала смотрим в исходный код html страницы. соответствует ли она стандартам?
    Допустим, соответствует. Значит, проблема не в форме.
    Далее, проверяем в скрипте - а есть ли такая переменная, к которой мы обращаемся - массив $_POST?
    пишем

    echo '<pre>';
    var_dump($_POST);

    Убеждаемся в том, что массив есть и все элементы на месте. Значит, проблема не в передаче.
    Значит, мы как-то неправильно обращаемся к массиву.
    обращаемся мы к нему так: $_POST['с_$i']
    Надо проверить - а во что превращается 'с_$i'?
    делаем echo 'с_$i'; и видим... совсем не то, что ожидали увидеть.
    И вот теперь уже идем либо читать документацию про строки в пхп (что предпочтительнее), либо - на форум, с вопросом "почему у меня переменная не заместилась своим значением". Каковой вопрос будет гораздо лучше звучать, чем "у меня форма не работает".
    Понятно?

    Следует понимать, что здесь приведён пример, Нереальный. Показан алгоритм действий.
    В реальности, при error_reporting(E_ALL); PHP сразу же показал бы, что индекс массива у вас неправильный.

    Самое важное - знать, что ты хочешь получить.
    Примерно половина вопросов на форумах вызвана тем, что человек делает что-то...НЕ ЗНАЯ, что именно!
    Самый гениальный вопрос всех времён и народов: "у меня база съела все переводы строк из текстарии".
    Человек просто не дал себе труд посмотреть, как будет выглядеть HTML, который он хочет получить, и решил, что переводы строк съела база.
    И так во всём.
    Непризнанный гений строит сложный SQL запрос, а когда его спрашивают, как запрос должен выглядеть - он только хлопает глазами. ВСЕГДА СНАЧАЛА составьте запрос руками и выполните в консоли или phpmyadmin! А после того, как получили нужный запрос и отладили - милости просим, составляйте его на пхп.
    Работа с удалённым хостом через сокет по протоколу HTTP - то же самое! Сначала научитесь выполнять все команды руками, посмотрите ответы сервера глазами, а потом моделируйте этот диалог в пхп.
    Работа с яваскриптом по тому же методу описана в факе "на танке"

    Запомните - на пхп вы работаете только со СТРОКАМИ! HTML страница, которую вы создаёте скриптом - это для пхп всего лишь набор строк! Ему без разницы, что в этом наборе - теги img, script или frame. Пхп за вас не сделает переводы строк, не нарисует яваскрипт. Если вы не знаете яваскрипта - то не пытайтесь создавать программу на нём с помощью PHP.
    Открывая соединение с удалённым хостом, вы посылаете строку в сокет, вы получаете строку из сокета. Пхп ничего не понимает в этих строках и за вас диалог вести не будет! Это ВЫ должны чётко понимать, что вы хотите послать в сокет, и что получить! Поэтому возьмите программу telnet, соединитесь с нужным хостом и пробуйте сначала САМИ сделать то, что хотите заставить сделать пхп.
    Если у вас не работает скрипт с сокетами - бегом в телнет смотреть, что происходит!
    SQL запрос - это СТРОКА. Вы должны себе чётко представлять, какой запрос получится в результате вашего хитроумного пхп-кода! Сервер БД не понимает конструкций intval, date, mktime и так далее! Это всё пхп-код. Результатом которого будет являться строка корректного SQL запроса. прежде, чем писать пхп код, вы должны ЧЁТКО СЕБЕ ПРЕДСТАВЛЯТЬ, КАК ДОЛЖЕН ВЫГЛЯДЕТЬ SQL ЗАПРОС В РЕЗУЛЬТАТЕ!
    Если у вас не выполняется SQL запрос 0 выводите его на экран и смотрите - что нагородили своим скриптом!

    Заключение.
    Отладка - главное занятие программиста.
    Отладка - единственный и самый мощный способ найти ошибку в программе.
    Отладка состоит из двух основных компонентов:
    1. Максимально упрощать пример. Если у вас не работает программа, которая рисует форму,получает данные, записывает данные формы в базу и выводит их снова, то разбейте программу на составляющие и выполняйте по очереди.
    Если у вас не работает сложная подпрограмма определения работоспособности кук - напишите сначала тест в две строчки чтобы убедиться, что вы хотя бы можете выставлять и читать куку.
    2. Вывод отладочной информации.
    Проверяйте значение КАЖДОЙ переменной! Каждого значения, возвращаемого функцией!
    Не работает локейшен? Выведите его на экран и скопируйте в браузер!
    В файл записывается пустая строка? проверяйте составляющие этой строки на каждом этапе ее создания и выводите на экран!
    Убедились, что на экран выводится? Тренируйтесь писать в файл, на тестовой строке! Забитой прямо в скрипт! Уменьшайте количество неизвестных!
    И всегда смотрите не то, что показывает браузер, а ИСХОДНЫЙ HTML код!

    Надеюсь, что я смог хотя бы немного объяснить принципы этого занятия.
    Удачной отладки.


    В начало раздела   Наверх


    Решение проблемы "Cannot add header information - headers already sent"


    Ошибку эту исправить несложно.
    Часто такое же сообщение появляется при старте сессий, в немного другой формулировке:
    Warning: Cannot send session cookie - headers already sent
    Warning: Cannot send session cache limiter - headers already sent

    Для начала узнаем, как вообще общается броузер с сервером. Происходит это по специальному протоколу HTTP. К примеру, когда ты набраешь адрес, или нажимаешь на ссылку, броузер посылает HTTP запрос серверу. Сервер отвечает. Первыми в ответе ВСЕГДА идут HTTP заголовки. Хоть один. И только потом уже сервер посылает, а броузер принимает, текст, или картинку, или файл - в общем, что было запрошено. Cобственно, из-за этого правила - сначала заголовок, а потом информация, и происходит данная ошибка. РНР, для твоего удобства, посылает заголовки автоматически, как только скрипт начинает выдавать бровзеру информацию. Соответственно, если хоть один пробел был уже передан пользователю, заголовки уже ушли, и снова их послать уже никак не можно. А, как ты уже, наверное, догадался, команды header(), setcookie, session_start(), посылают HTTP заголовки.

    Разберем теперь это предупреждение.
    Warning: Cannot add header information - headers already sent by (output started at /www/script.php:5) on line 20

    Cannot add header information - headers already sent. Все ясно написано. "Не могу послать заголовок, поезд уже ушел" - пишет нам РНР. Дальше РНР сообщает, в каком скрипте и в какой его строке (output started at /www/script.php:5) произошел вывод информации, вызвавший автоматическую посылку заголовков. Очень легко найти и исправить. Может быть, там html теги, может быть, echo, а может и просто незамеченая пустая строка или пробел перед первым тегом <?.
    Очень часто такую ошибку вызывает файл, подключаемый через include, в котором либо есть какой-то вывод, либо пустая строка после закрывающего PHP тега - обнаружить ее очень трудно.

    Для решения этой проблемы нужно функцию header() (или session_start(), setcookie) и всю логику, которая ее вызывает, поместить ДО любого вывода в броузер. Просто перенести повыше в скрипте.
    Ведь вы всё равно перенаправляете браузер. То есть, никакой текст всё равно не будет выведен! Значит, и выводить что-то одновременно с заголовком Location нет смысла. Правильно планируйте структуру своего скрипта: блок, который обрабатывает POST, не должен ничего выводить в браузер.

    Иногда вы проверили ВСЁ - нигде ничего нет. Смените редактор. Посмотрите свой файл в другой программе. К примеру, Windows Блокнот при использовании кодировки Unicode добавляет в начало вашего файла служебный символ Byte Order Mark, никак при этом не ставя вас в известность. Откройте скрипт в другом редакторе и удалите посторонние символы. И смените Блокнот на другой редактор.

    Многочисленные вопросы на форуме заставляют меня сделать здесь важное замечание:
    Эта ошибка появляется не от того, что у вас в скрипте "что то написано выше". А от того, что РНР выводит что-то в браузер. Это не обязательно код. Это может быть сообщение об ошибке. может быть пробел или хтмл тег. Да-да. Для самых талантливых: речь идет о любом символе, отправленном в браузер, а не только о тех, которые браузер отображает неискушенному пользователю. У HTML страниц есть исходный текст. И именно он является результатом работы PHP скрипта, а не красивые буковки с картиночками, как думает очень большое количество людей.

    В начало раздела   Наверх


    Не передаются переменные! Проблема Undefined variable


    Во всех старых руководствах по РНР написано, что даные, полученные из формы, или переданные по ссылке, вот так: script.php?peremennaya=znachenie&variable=value
    автоматически становятся переменными PHP, $peremennaya и $variable
    Эта информация устарела.
    Дело в том, что в целях безопасности, начиная с версии 4.1, РНР настраивается по умолчанию так, чтобы переданные значения не назначались переменным.
    Получить же переданное значение можно обратившись к соответствующему массиву.
    Например:
    Если передаем методом GET, обратившись к скрипту по ссылке вида
    script.php?var=value
    или отправив форму, указав в ней method="GET",
    то все переменные содержатся в массиве $_GET.
    echo $_GET['var']; напечатает "value".
    Если получаем данные из формы, отправленной методом POST, то все поля этой формы содержатся в массиве $_POST. Допустим, в форме был элемент
    <input type="hidden" name="var" value="1">,
    то в скрипте, который указан в action формы, можно написать echo $_POST['var']; и будет выведена 1.

    Поэтому, если вы уверены, что переменная есть, но вы не можете ее найти - ищите ее в суперглобальных массивах.
    Подробнее о них можно почитать на русском языке в официальной документации
    http://ru2.php.net/variables.predefined

    То же касается и серверных переменных, таких, как $REMOTE_ADDR$PHP_SELF. Получить их можно, обратившись к массивам $_SERVER$_ENV или функцией getenv 
    getenv('HTTP_REFERER')
    ;

    Переменные, зарегистрированные в сессии, следует искать в массиве $_SESSION.
    Данные cookie содержатся в массиве $_COOKIE, сведения о закачанных файлах - в $_FILES
    Переменные окружения - в $_ENV, а так же, существует массив $_REQUEST, в котором собраны данные из GET, POST и cookie.

    Важно! Весьма в поисках переменных помогает одна их главных функций PHP - phpinfo()
    ее следует применять всякий раз, когда вы "потеряли" переменную, вызовите phpinfo(32); в скрипте, в котором не работает авторизация, в скрипте, который принимает файл при аплоаде - и все найдется!


    Все то же самое можно прочесть и в документации: http://www.php.net/manual/ru/security.globals.php

    В начало раздела   Наверх


    Различие между абсолютными и относительными путями. В файловой системе и на сайте.


    твой сайт существует в как бы в двух измерениях.
    Реальном и виртуальном.

    Для всех посетителей - это виртуальный веб-сервер. Который отличается, в числе прочего, тем, что на нем НЕ СУЩЕСТВУЕТ ФАЙЛОВ. если ты пишешь http://site.ru/file.html - это не файл. Это URI, виртуальный адрес. Никакого файла с именем file.html на сервере может вообще не быть. Это все виртуальные адреса, а не файлы.
    И браузер работает именно с адресами.

    Для разработчика же сайт - это программа, выполняющаяся на совершенно конкретном реальном компьютере. С совершенно конкретным жестким диском, каталогами и файлами. И скрипт, работая со своими данными, подгружая другие скрипты, работает именно с реальными ФАЙЛАМИ, на физическом ДИСКЕ.

    Вот в этом различии и кроются трудности, с которыми часто сталкиваются новички.
    Теряют файлы, путают ссылки с файлами, обращаются к локальным файлам по протоколу HTTP, или инклюдят файлы от корня веб-сервера.

    А всего-то надо четко понимать две вещи:
    1. Различать корень веб-сервера, как его видит браузер, и корень файловой системы на диске.
    2. Отличие относительных путей от абсолютных.

    Начнем со второго.
    Это очень просто. Если путь указывается от корня системы, то это путь абсолютный. Это как почтовый адрес в реальной жизни - откуда бы ты не шел, но по точному адресу ты всегда точно найдешь нужное место.
    примеры абсолютных путей:
    /var/www/site/forum/index.php
    /img/frame.gif
    с:\windows\command.com

    В юникс-системах и на веб сайтах корень обозначается косой чертой - "/".
    Это важно. Это не просто палочка, а самостоятельный АДРЕС, путь.
    В адресе http://www.site.ru/ последняя косая черта - не для красоты! Она обозначает вполне конкретный адрес - начало сайта.
    На диске в юникс системах так же можно набрать "cd /" и ты попадешь в корневой каталог.
    В виндоус системах файловая система разбивается по дискам, поэтому, в абсолютном адресе надо указывать имя диска. Абсолютного корня всей файловой системы в виндоус нет, у каждого диска - свой. Например, C:\ E:\
    поэтому, даже если путь в виндоус начинается с косой черты, то это не абсолютный путь, а относительный. Относительно текущего диска. А абсолютный начинается с буквы.

    Если в начале пути корень не указать, то этот путь будет относительным, и он достаивается от текущего положения. В реальной жизни это напоминает дорогу к винному магазину - "два квартала налево и там все время прямо". Дойти по такому пути можно только из конкретной точки. Из другой ты попадешь уже в совсем другое место.
    Самый простой пример относительного пути - это просто имя файла.
    Если файл находится в том же каталоге, с которым работает программа - она его найдет, добавив текущий путь к имени файла.
    примеры относительных путей:
    file.php (фал лежит в той же папке)
    ./file.php (фал лежит в той же папке. такая запись иногда требуется в некоторых юникс системах)
    images/picture.jpg (файл лежит в капке images, которая находится в текущей)
    ../file.php (файл лежит в папке, которая расположена на один уровень выше от текущей)
    ../../file.php (файл лежит в папке, которая расположена на два уровня выше от текущей)

    И операционная система, и браузер, встретив относительный путь, достраивают его до абсолютного. но каждый - по-своему.

    Теперь перейдём к первому пункту.
    Различие корня веб-сервера, как его видит браузер, и корень файловой системы на диске.
    В общем-то, из предыдущих объяснений уже все должно быть понятно.
    На диске путь к файлу скрипта может быть таким:
    /var/www/site/forum/index.php
    В то же время, виртуальный адрес этого скрипта при просмотре через браузер, будет:
    http://www.site.ru/forum/index.php
    На этом примере легко увидеть, где пересекаются два измерения: у этих двух адресов есть общая часть - /forum/index.php - и она-то и служит причиной путаницы.
    Для браузера это самый полный путь, который только может быть. Он начинается от корня сайта.
    Для скрипта же, исполняющегося на сервере - это всего лишь ЧАСТЬ пути.
    для скрипта путь /forum/index.php окажется несуществующим - в корне диска нет каталога forum!
    чтобы получить полный путь для того, что на сайте выглядит, как /forum/index.php, надо приставить слева к нему путь к папке, которая считается корневаой для всего веб сервера.
    в нашем примере - это
    /var/www/site
    Этот путь задается в кофигурации веб-сервера и именно он содержится в системной переменной PHP $_SERVER['DOCUMENT_ROOT']

    В виртуальном же сервере - том, который видит пользователь - наоборот, нет никакого диска. Есть корень сайта. То есть, для того, чтобы любая ссылка гарантированно работала, независимо от того, из какого места сайта она вызывается, она должна быть абсолютной.
    Если у вас на сайте есть, допустим, два раздела:
    http://www.site.ru/about/info.php
    и
    http://www.site.ru/job/vacancy.php
    то, если в файле info.php сделать ссылку просто на vacancy.php, то браузер ее не найдет - он будет искать адрес http://www.site.ru/about/vacancy.php, достраивая путь от текущего каталога.
    Поэтому надо писать полный путь от корня сайта - /job/vacancy.php
    Всё это касается, естественно, не только тегов <a> но и <img> и любых других, где используются ссылки на другие файлы.

    Ссылки на локальные адреса следует писать без указания протокола и домена - только путь от корня сайта - /job/vacancy.php. Ссылки же на другие сайты следует писать полностью - http://www.site1.ru/job/vacancy.php.


    PHP предоставляет множество средств для работы с файлами, каталогами и URL-ами.

    Во-первых, это многочисленные предопределённые переменные, которые описаны в документации и значения которых в своём скрипте пможно посмотрев с помощью phpinfo():

    Константа __FILE__ содержит имя текущего исполняемого файла.
    В отличие от PHP_SELF она содержит имя файла, исполяющегося в данный момент.
    очень полезной представляется конструкция dirname(__FILE__), на которую желательно заменить все вызовы файлов, лежащих в том же каталоге, что и вызывающий скрипт. Например:
    require dirname(__FILE__)."/init.php"
    функция dirname(), наряду с basename() является одними из наиболее употребительных для работы с файлами и каталогами.




    В начало раздела   Наверх


    Как разбить вывод из mysql постранично


    Как сделать постраничный вывод из mysql "как в яндексе"?
    по 10 записей на страницу, внизу - ссылки на остальные страницы?

    Сначала научимся получать из базы нужные записи.
    Их получение в mysql обеспечивается оператором LIMIT, который вызывается с двумя параметрами - с какой записи начинать, и сколько выводить (внимание! не по какую, а сколько!)
    SELECT * FROM table LIMIT 0,10
    этот запрос вернет записи с первой по 10, поскольку нумерация начинается с 0
    соответственно, запрос для третьей страницы будет выглядеть, как
    SELECT * FROM table LIMIT 20,10
    получается, что нам всего лишь надо передать в скрипт число, которое потом подставить в запрос.
    Этим будет заниматься код, который выводит ссылки на страницы.
    Естественно, в цикле.
    Для цикла нам понадобится количество записей, которое возвращает запрос без лимита.
    Это число можно получить двумя путями. Либо отдельным запросом, в котором отсутствует оператор LIMIT, а вместо перечисления полей после оператора SELECT запрашивается только count(*):
    $q="SELECT count(*) FROM table";
    $res=mysql_query($q);
    $row=mysql_fetch_row($res);
    $total_rows=$row[0];


    Либо, если версия mysql больше 4.0, то общее количество строк можно запросить в том же запросе. См. документацию mysql по функции FOUND_ROWS()
    Однако, первый сособ представляется более удобным, хотя и немного более медленным.

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

    Для начала определим, сколько всего получится страниц. Для этого надо поделить общее число записей на количество оных на одной странице и округлить результат в большую сторону. Таким округлением занимается в пхп функция ceil()
    $num_pages=ceil($total_rows/$per_page);
    В этом выражении участвует переменная $per_page, в которую мы положим количество выводимых на странице записей.
    Ведь, если это количество изменится, мы же не хотим ползать по всему коду и исправлять цифры? проще сделать это один раз в начале скрипта при объявлении переменной. В запрос, вторым параметром LIMIT, подставлять нужно, конечно же, тоже ее.

    Ну, а дальше, собственно, вывод ссылок.
    for($i=1;$i<=$num_pages;$i++) {
      echo 
    '<a href="'.$_SERVER['PHP_SELF'].'?num='.$i*$per_page.'">'.$i."</a>\n";
    }

    в цикле от 1 до $num_pages выводим ссылку с параметром num, равным числу, которое надо передать в LIMIT, а в тексте ссылки пишем номер страницы, поскольку людям понятнее видеть номер страницы, а не записи. На код это не влияет, а людям приятно.

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

    что у нас в результате получилось?

    // количество записей, выводимых на странице
    $per_page=10;
    // получаем номер страницы
    if (isset($_GET['page'])) $page=($_GET['page']-1); else $page=0;
    // вычисляем первый оператор для LIMIT
    $start=abs($page*$per_page);
    // составляем запрос и выводим записи
    // переменную $start используем, как нумератор записей.
    $q="SELECT * FROM `table` ORDER BY field LIMIT $start,$per_page";
    $res=mysql_query($q);
    while(
    $row=mysql_fetch_array($res)) {
      echo ++
    $start.". ".$row['field']."<br>\n";
    }

    // дальше выводим ссылки на страницы:
    $q="SELECT count(*) FROM `table`";
    $res=mysql_query($q);
    $row=mysql_fetch_row($res);
    $total_rows=$row[0];

    $num_pages=ceil($total_rows/$per_page);

    for(
    $i=1;$i<=$num_pages;$i++) {
      if (
    $i-== $page) {
        echo 
    $i." ";
      } else {
        echo 
    '<a href="'.$_SERVER['PHP_SELF'].'?page='.$i.'">'.$i."</a> ";
      }
    }


    Разумеется, вышеприведённый код подходит только как учебное пособие. С его помощью становится понятным принцип, но в реальных условиях мы сразу же столкнемся, как минимум, с двумя проблемами:
    Во-первых, кроме переменной $page нашему крипту явно будут переданы и другие переменные, да и адрес может совсем не совпадать с именем скрипта. А мы это при формировании ссылок не учитываем.
    Во-вторых, нормальный современный сайт немыслим без шаблонов. И такая ужасная лапша из SQL запросов, PHP кода и HTML тегов никуда не годится.

    Займемся решением этих проблем.
    Первая решается просто:
    $uri strtok($_SERVER['REQUEST_URI'],"?")."?"
    if (
    count($_GET)) foreach ($_GET as $k => $v) if ($k != "page"$uri.=urlencode($k)."=".urlencode($v)."&";

    и полученную переменную $uri подставляем в код вместо $_SERVER['PHP_SELF']

    Вторая - тоже несложно. Шаблонизаторов много, но мы воспользуемся самым универсальным - PHP.
    Что же у нас получилось? А получился у нас - рефакторинг! Переделка старого кода в соответствии с требованиями современности, плюс мелкое причесывание:

    <?
    //определим фрагмент запроса, который отвечает за то, какие записи мы запрашиваем
    $from_where="FROM table WHERE filter=1";
    // и получим общее количество записей
    $res=mysql_query("SELECT count(id) ".$from_where);
    $row=mysql_fetch_row($res);
    $total_rows=$row[0];

    //дальше получаем номер страницы и значение для лимита 
    if (isset($_GET['page'])) $CUR_PAGE=($_GET['page']); else $CUR_PAGE=1;
    $start=abs(($CUR_PAGE-1)*$per_page);

    //выполняем запрос и получаем данные для вывода
    $query="SELECT * $from_where ORDER BY date DESC LIMIT $start,$per_page";
    $res=mysql_query($query);
    while (
    $row=mysql_fetch_array($res)) $DATA[++$start]=$row;

    //определяем адрес страницы без переменной page
    $uri=strtok($_SERVER['REQUEST_URI'],"?")."?";
    if (
    count($_GET)) {
      foreach (
    $_GET as $k => $v) {
        if (
    $k != "page"$uri.=urlencode($k)."=".urlencode($v)."&";
      }
    }
      
    //узнаем общее количество страниц и заполняем массив со ссылками
    $num_pages=ceil($total_rows/$per_page);
    for(
    $i=1;$i<=$num_pages;$i++) $PAGES[$i]=$uri.'page='.$i;

    //а дальше выводим в шаблоне днные и навигацию:
    ?>
    Найдено сообщений: <b><?=$total_rows?></b><br><br>
    <? foreach ($DATA as $i => $row): ?>
    <?=$i?>
    . <a href="?id=<?=$row['id']?>"><?=$row['title'])?></a><br>
    <? endforeach ?> 

    <br>
    Страницы: 
    <? foreach ($PAGES as $i => $link): ?>
    <? 
    if ($i == $CUR_PAGE): ?>
    <b><?=$i?></b>
    <? else: ?> 
    <a href="<?=$link?>"><?=$i?></a>
    <? endif ?> 
    <? endforeach ?> 



    В начало раздела   Наверх


    Определение IP адреса

    Беллетристика
    Теория
    Практика.
    Примечания.
    Комментарии

    Беллетристика
    Один из самых дремучих вопросов в околопхпешном вебе - это определение IP адреса.
    Такого количества неправильного кода не написано, наверное, ни для какой другой операции.

    Каждый, кто в один прекрасный день узнаёт о существовании переменной HTTP_X_FORWARDED_FOR, тут же воображает себя мегагуру, и заменяет ей REMOTE_ADDR. Потом приходит знание о других переменных (X_REAL_IP, VIA, и ещё вагон и маленькая тележка), изобретаются многослойные мегаконструкции, изобретатели хвастаются друг перед другом их многоэтажностью и сравнивают свои творения с "кодом из PHPbb!".

    При этом спроси любого из них - "какой именно адрес они хотят определить?" - ни один не ответит: понимание основ функционирования сети TCP/IP среди пхп-программистов традиционно слабое.
    А вот стремление к нахождению Идеального и Единственно Правильного Решения - традиционно сильное.
    В результате вместо IP адреса в логи пишется не пойми что.

    К примеру, возьмем, казалось бы, простой вопрос "какой именно IP адрес (из цепочки хостов, через которые идет запрос от компьютера клиента к серверу) мы хотим записать в лог?". После того, как выяснилось, что большинство пхп-программистов затрудняются на него ответить, я и решил написать эту заметку.

    А это, между прочим, очень важный вопрос. Не ответив на него, наряду с вопросом "Зачем нам нужен IP адрес?", приступать к самому определению бессмысленно.
    При том, что большинству читателей этого текста вопросы покажутся бессмысленными.
    Ну что ж, попробуем разобраться.

    Теория
    Во-первых. Самые азы. Для тех, кто не знает.
    Все элементы массива $_SERVER, начинающиеся со слова "HTTP_" - это HTTP-заголовки.
    Как уже знают вдумчивые читатели фака на танке, HTTP заголовки присылает клиент. И прислать может любые.
    К примеру, заголовок X-All-Your-Base-Belongs-To-Us: Surrender!
    Или, как вы уже, наверное, догадались, заголовок X-Forwarded-For: admin durak
    Мне кажется, что записывать столь глубокомысленную строку вместо IP адреса - не самая лучшая идея.
    Как и вообще доверять любым переменным, начинающимся с HTTP. Это первое правило, которое надо запомнить с молоком матери: Любые элементы массива $_SERVER, начинающиеся с "HTTP_", можно использовать только в справочных целях! К примеру, HTTP_REFERER записываем, чтобы потом посмотреть. Но ни в коем случае не делаем на него Location.

    Во-вторых, определимся с тем, ДЛЯ ЧЕГО нам нужен IP адрес. Если мы хотим записать в лог, то пишем однозначно только REMOTE_ADDR. В этой переменной содержится реальный IP адрес реального хоста в интернете, который произвел соединение с нашим сервером. Единственный реальный адрес. Никаких других сервер не знает.
    Апач пишет в логи именно REMOTE_ADDR. Не надо считать авторов веб-сервера дурнее себя.

    Что значит - реальный IP адрес? А то и значит. Адрес хоста, который произвел соединение с нашим сервером. Этот адрес по определению может быть только один. Один, а не 5 по цепочке. Рассмотрим типичный пример:
    Есть пользовательский компьютер, который, который находится в офисной сети. IP компьютера 192.168.0.22
    Офисная сеть включена через роутер в сеть здания. IP роутера - 10.10.0.3
    Сеть здания, в свою очередь, подключена к интернету, через роутер. IP роутера - 77.88.22.11
    Пользователь заходит на сайт, через НТТР прокси. IP прокси - 212.121.0.8
    Так вот, сеть TCP/IP так устроена, что каждый следующий узел ничего не знает о предыдущих. Есть только пара хостов, которые соединяются друг с другом. В самих TCP/IP пакетах никакой информации о предыдущих хостах не предусмотрено.
    Поэтому, как это ни обидно, но реальным адресом мы можем считать только последний в цепочке - адрес HTTP прокси.

    Больше того. Ну допустим, узнали мы адрес компьютера пользователя (чем кичатся многие определители с помощью activeX и ява-апплетов). Этот адрес - 192.168.0.22. Он из приватной сети. Компьютеров с такими адресами в мире - миллионы. Найти компьютер по такому адресу - невозможно. Пользы от него - практически никакой. Практически, но не совсем. Почему? Слушаем дальше:

    Поскольку в протоколе HTTP текстовые заголовки, то в них можно добавить свой. Что некоторые хосты и делают. В те самые X-Forwarded-For, Via и прочие.
    Можем мы их использовать? Можем. Если правильно понимать - для чего.
    Для определения "реального IP адреса", как мы уже убедились - нельзя. А для чего же можно? Например - справки. просто записать, на всякий случай. НО! Только в том случае, если мы откажемся от дурацкой идеи найти один идеальный IP адрес. Если мы не будем писать вместо реального всякую лабуду, а будем записывать все похожее на IP адрес с реальным наряду, то почему нет?
    Итак, можно записать всё, похожее на IP адрес. Понадобится выявить злостного вредителя - возможно, какой-то интересный айпишник среди заголовков и проскочит.
    Для небольшого повышения надежности сессий - тоже можно. Писать в сессию не только реальный, но и все похожие. И все сверять. Хоть один не совпал - сессию рубим.
    И в других подобных случаях.
    Не забывая: реальный - отдельно, все похожее на IP адрес - отдельно.
    Не забывая: особо полагаться на все эти заголовки не стоит.

    Практика.
    Итак. Из всего вышеизложенного делается простой вывод.
    IP адрес в скрипте может быть только один. Лежит он в переменной REMOTE_ADDR.
    Следовательно, вожделенный код получания "идеального IP адреса" выглядит, как
    $ip=$_SERVER['REMOTE_ADDR']
    Точка.

    Далее. Если мы хотим воспользоваться "заголовками, похожими на IP адрес" (лучше всего, во избежание недоразумений, совсем не считать их адресами хостов, а HTTP заголовками особого формата. Тем более, что никакого стандарта на содержимое заголовков X-Forwarded-For, Via и прочих - нет. Там могут оказаться IP адреса чарез запятую, или доменные имена или не через запятую. Не говоря уже о подделках!), то нет смысла судорожно искать все имена заголовков, где может встретиться адрес. Проще искать сами адреса.
    Берем, пишем простой код, который в цикле перебирает массив $_SERVER, и регулярным выражением выцепляет все заголовки, в которых встречается подхдящая под шаблон IP адреса строка. Если встретилась, то весь заголовок - с именем и всем содержимым - добавляем в массив или в строку. Которая хранится отдельно от IP адреса, в текстовом виде.
    Соответственно, в нужном месте повторяем операцию, и сверяем. IP адрес с IP адресом, строку похожих заголовков - со строкой похожих заголовков.
    function get_all_ip() {
      
    $ip_pattern="#(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)#";
      
    $ret="";
      foreach (
    $_SERVER as $k => $v) {
        if (
    substr($k,0,5)=="HTTP_" AND preg_match($ip_pattern,$v)) $ret.=$k.": ".$v."\n";
      }
      return 
    $ret;
    }

    Такой вот, несложный код.
    Правда, нужда в нем, если задуматься, очень невелика. Разве что, для тех же сессий. А для справки, про запас... не проще ли писать вообще все HTTP заголовки, пришедшие в скрипт? И это поинформативнее будет, чем выцеплять какой-то один адрес из HTTP_X_REAL_IP.
    Да и для сессий следует применять с осторожностью - IP адрес может оказаться, к примеру, в реферере...

    Примечания.
    Недавно я выяснил удивительную вещь. Оказывается, на свете существуют криворукие хостеры, у которых на сервере нет REMOTE_ADDR (а точнее есть, но в нем лежит... адрес самого сервера!). И пихают они адрес удаленного хоста кому куда бог на душу положит. Некоторые - вы будете смеяться - в HTTP_X_FORWARDED_FOR. Говорят, в некоторых больших программных продуктах есть даже специальная настройка для таких случаев - "Получать IP-адреса из заголовка X_FORWARDED_FOR".
    Разумеется, этот курьёз не опровергает сказанного выше, и не стоит кидаться писать автоматические определители IP с его учетом. Все подобные случаи должны разбираться только в ручном режиме, самим программистом. Который сначала убедится - где именно в HTTP_X_FORWARDED_FOR лежит нужный адрес - в начале цепочки запятых или в конце, напишет правильный рег, и только потом в настройки сайта добавит код
    $_SERVER['REMOTE_ADDR']=get_ip_from_xff();

    Примечание для хостеров: mod_realip или mod_rpaf
    Примечание для пользователей: разумеется, таких хостеров надо избегать, как калёного железа. Наверняка ведь это не единственная их криворукость?

    В начало раздела   Наверх


    Проблемы с кодировкой в MySQL версий 4.1+

    Быстрые рекомендации
    Подробные объяснения.
    Перекодировка
    Исправление БД, в таблицах которой неверно указана кодировка
    Если все равно ничего не получается
    Примечания
    ОПС
    Комментарии

    Быстрые рекомендации
    Если из базы выводятся вопросики, то после соединения с сервером выполняем волшебный запрос
    set names кодировка
    Параметр "кодировка" должен соответствовать кодировке, в которой выводятся страницы на сайте.
    например:
    set names utf8
    Если это не помогло, и всё равно идут вопросики или крокозябры - значит, криво настроена кодировка таблиц. В этом случае см. рис.1 п. "Исправление БД" ниже.
    Если выводится нормально, но сортировка хромает - см. туда же.

    Подробные объяснения.
    До версии 4.1 кодировку данных в MySQL можно было задать только одну на всех. Теоретически, ничто не мешало в одной таблице хранить данные в юникоде, а в другой - в KOI-8. Так все и поступали: в конце концов, для БД любые данные - это всего лишь набор цифр. Что в неё положил - то она тебе и вернёт. Но правильный поиск и сортировка работали только для данных, которые были в кодировке, совпадавшей с настройками MySQL.

    Начиная с версии 4.1 стало можно хранить данные в любой кодировке, и задавать свой порядок сортировки хоть для каждого поля в таблице. Но для этого понадобилось ввести некоторые правила.

    Во-первых, для каждого поля в таблице были введены два параметра: кодировка (CHARACTER SET) и правила сравнения (COLLATION).
    Кодировка - это понятно. Говорим базе, в какой кодировке лежат наши данные.
    Правила сравнения задают порядок сортировки и сравнения данных при поиске.
    COLLATION жестко привязан к CHARACTER SET-у и может быть задан только из поддерживаемых кодировкой. Проще говоря, начало названия COLLATION должно совпадать с CHARACTER SET. К примеру, для кодировки utf8 можно задать правила сравнения utf8_bin, но нельзя cp1251_bin.
    Обычно у каждой кодировки есть, как минимум, два набора правил сравнения - имякодировки_bin и имякодировки_general_ci. Первый сравнивает в лоб по кодам символов, а второй - регистронезависимо, учитывая совпадающие символы. COLLATION имякодировки_general_cs сравнивает регистрозависимо, отличаясь от _bin тем, что учитывает совпадающие символы ("е" и "ё" в русском языке), а также, при сортировке, ставит на место те символы, которые идут в кодировке не по порядку (например "ё" в 1251).
    Если для поля не указан COLLATION, то он берется по умолчанию. К примеру, для utf8 - utf8_general_ci. В большинстве случаев COLLATION по умолчанию устраивает пользователя, а это значит, что его задавать не нужно. То есть, достаточно указать только кодировку.
    Кодировка может быть задана для поля, таблицы, database и для всего сервера. Установки имеют характер умолчаний, и могут быть изменены на любом уровне:
    Кодировку (и сортировку) можно указывать для каждого отдельного поля. Если они не указаны, то при создании таблицы берутся из кодировки, указанной для этой таблицы. Если при создании таблицы кодировка не указывается, то она берется из параметров database. Так же и при создании database - либо задаются явно, либо берутся из параметров сервера.

    Во-вторых, появилась необходимость говорить базе данных, в какой кодировке мы записываем или хотим получить свои данные. То есть, появилось такое понятие, как кодировка клиента. Здесь и кроется ответ на вопрос - "откуда берутся "вопросики"? Они появляются, если кодировка таблицы не совпадает c кодировкой клиента.
    Соответственно, в MySQL появились две новые команды
    set character_set_client
    и
    set character_set_results

    Первая указывает, в какой кодировке приходят данные в базу, а вторая - в какой их выдавать. Поскольку чаще всего эти кодировки совпадают, то можно писать короче - один запрос "SET NAMES кодировка", который и устанавливает оба эти параметра.

    Из приведенных объяснений должно быть ясно, что для беспроблемной работы нам надо сделать всего две вещи:
    1. Указывать правильную кодировку клиента.
    Это можно сделать либо в настройках сервера в my.ini, либо тем самым запросом SET NAMES.
    2. Создавая таблицы, не забывать указывать правильную кодировку для них.
    Это можно сделать несколькими способами.
    Самое простое - это указывать кодировку и правила сравнения прямо в коде CREATE TABLE. Пример:

    CREATE TABLE `chartest` (
      `name` varchar(10) default NULL
    ) ENGINE=MyISAM CHARACTER SET=utf8


    Но что, если у нас огромный дамп на сотни таблиц, сделанный в прошлой версии MySQL? Дописывать к каждой таблице вручную? Возможно, это и придется делать. Но сначала надо попробовать установить параметры по умолчанию.
    Как мы помним, при создании таблиц, если для них не указывается collation и charset, эти параметры берутся из настроек database.
    Следовательно, надо попытаться изменить эти настройки.
    сначала смотрим, какие они сейчас: заходим в консоль и пишем

    use `mydb`
    show variables like "character_set_database";

    Этот запрос выведет кодировку базы mydb по умолчанию.
    Если она нас не устраивает, то пытаемся переопределить настройки самостоятельно

    alter database `mydb` character set utf8;

    Если запрос прошел успешно, то проверяем ещё раз, и, если все нормально, то начинаем создавать таблицы или заливать дамп.
    Если таким образом сделать не удалось (не хватает прав), то варианта только два - или обращаться к провайдеру, чтобы он сам поменял настройки, или дописывать COLLATION И CHARACTER SET ко всем создаваемым таблицам вручную.

    Перекодировка
    Как следует предыдущих объяснений, кодировка клиента должна соответствовать реальной кодировке поступающих данных. В этом случае, даже если данные лежат в другой, то всё равно никаких проблем не будет - MySQL автоматом перекодрует туда и обратно.
    Проведем эксперимент. Для него нам потребуется MySQL, установленная под Windows. Для тех, у кого другая ОС, я думаю, поменять кодировки в терминале проблемы не составит.
    Для демонстрации возможностей перекодировки воспользуемся тем фактом, что по умолчанию консоль windows настроена на старую кодировку DOS - 866. То есть, сначала мы создадим таблицу в этой кодировке и запишем в неё данные, а потом попробуем общаться с базой в другой кодировке.

    Сначала запустим командный интерпретатор cmd.exe и установим в свойствах окна шрифт Lucida Console.
    затем вызываем консоль mysql:
    C:\MySQL\bin\mysql.exe -uroot test
    В консоли пишем:

    set names cp866;
    CREATE TABLE ct (`name` varchar(10) default NULL)CHARACTER SET=cp866;
    insert into ct values ('Вова');
    select * from ct;


    Если мы все сделали правильно, то вывод будет таким:
    +------+
    | name |
    +------+
    | Вова |
    +------+


    дальше пишем exit, выходим из консоли, и пишем команду
    chcp 1251
    которая сменит кодировку окна консоли windows на 1251
    затем снова запускаем консоль mysql и пишем:

    set names cp1251;
    select * from ct;
    insert into ct values ('Вова');
    select * from ct;


    То же самое можно повторить и для кодировки utf8 (chcp 65001).
    В результате мы видим, что даже тогда, когда данные поступают не в той кодировке, в которой они хранятся в базе, работа с ними происходит совершенно корректно. При этом они продолжают лежать в базе в той же самой кодировке, в которой они были с самого начала - 866.

    Возможности перекодировки ограничиваются, разумеется, одним и тем же языком. То есть, из 1251 можно перекодировать в 866, в koi8r, в UTF8. В latin1 из 1251 перекодировать нельзя - появятся вопросики.

    Исправление БД, в таблицах которой неверно указана кодировка
    Или что делать, если буквы нормальные, а поиск и сортировка работают странно.

    Итак, у нас есть проблемы, которые не решаются запросом SET NAMES. Это значит, что в таблицах лежат данные в одной кодировке, а указана для этих таблиц - другая. В принципе, быстрое решение этой проблемы можно вывести из предыдущих объяснений - сделать запрос SET NAMES с кодировкой, которая указана в таблице. Посмотреть её можно запросом show create table `table`.
    Если там в последней строчке написано DEFAULT CHARSET=latin1, то выполняем запрос SET NAMES latin1
    В таблице не будет работать толком сортировка и поиск, но хотя бы сами данные будут отдаваться и записываться нормально (если кодировка html страницы соответствует фактической кодировке лежащих в базе данных). Но это, конечно, ненормальная ситуация, тем более, что исправить её совсем несложно.
    Что мы сейчас и проделаем.

    Для исправления существует два способа, которые описаны по ссылкам внизу.
    Я воспользуюсь вариантом из FAQ сайта linux.by, приведя только его содержательную часть. Но настоятельно рекомендую прочитать подробный вариант, с объяснением опций и исправлением возможных ошибок.

  • Узнаём кодировку таблиц (show create table `table`).
  • Делаем дамп БД с помощью mysqldump
    Допустим, мы выяснили, что таблицы были созданы по умолчанию в кодировке latin1, а фактически в них содержатся данные в utf8. В этом случае используем команду:
    mysqldump -uUSERNAME -pPASSWORD DB_NAME --allow-keywords --create-options --complete-insert --default-character-set=latin1 --add-drop-table > dump.sql
    Распространненая ошибка в таких случаях - когда в --default-character-set указывают фактическую кодировку данных, в данном случае - utf8. В дампе будет мусор. Указывать надо ту, которая установлена в таблицах. В результате MySQL не будет пытаться данные перекодировать, и отдаст как есть.
  • Просматриваем файл с дампом, чтобы убедиться, что в файле нормальные данные в кодировке utf8, а не мусор.
  • Целый и проверенный дамп копируем в сторону. Далеко в сторону.
  • В файле дампа поправляем операторы CREATE DATABASE и/или CREATE TABLE для создания таблиц в правильной кодировке. Либо меняем настройки database, как это было описано выше.
  • заливаем дамп обратно:
    mysql -uUSERNAME -pPASSWORD DB_NAME --default-character-set=utf8 < dump.sql
  • в код сайта, после функций mysql_connect и mysql_select_db добавляем строчку
    mysql_query("SET NAMES utf8");
  • Всё, можно работать!

    Если все равно ничего не получается
    Большое количество вопросов по кодировкам, не имеющих отношения к БД, побудило меня составить небольшое сводное руководство. Итак, кодировка нашего сайта складывается из 4 пунктов:
    1. Кодировка базы данных.
    • Задается при создании таблиц.
    • Может быть любая. Должна отражать реальную кодировку данных в таблице.
    • Например, если данные у нас будут лежать в кодировке Windows-1251, то создавая таблицу, пишем CREATE TABLE chartest (string text) DEFAULT CHARSET=cp1251;
    • Проверить текущую кодировку таблицы можно запросом SHOW CREATE TABLE tablename
    2. Кодировка клиента БД (клиентом в данном случае является скрипт, работающий с БД).
    • Задается сразу после соединения с БД, запросом SET NAMES кодировка
    • Должна совпадать с кодировкой выводимой HTML страницы.
    • Например, если страница у нас в utf-8, то в PHP пишем mysql_query("SET NAMES utf8");
    • Проверить можно запросом show variables like '%char%'; (переменные character_set_client, character_set_connection и character_set_results должны иметь установленное нами значение)
    3. Кодировка сайта.
    • Задается HTTP заголовком Content-type.
    • Должна соответствовать кодировке данных на странице.
    • Например, если страница у нас в utf-8, то в PHP напишем header("Content-Type: text/html; charset=UTF-8");
    • Проверить можно, выбрав в браузере пункт меню Вид - кодировка. Так же полезно посмотреть, какую кодировку шлет сервер в заголовке.
    4. Кодировка данных на странице.
    • Задается в редакторе.
    • Должна соответствовать той кодировке, которую мы хотим.
    • Особых рекомендаций здесь дать невозможно, но хотя бы минимальная компьютерная грамотность, чтобы выбрать в редакторе желаемую кодировку при сохранении, автору сайта необходима.

    N.B.: Обозначения кодировок в mysql могут не совпадать с общепринятыми. Внимательно смотрите примеры

    Примечания
  • Решая проблемы с кодировкой БД, не забывайте указывать правильную кодировку САЙТА!
    Во-первых, убедитесь, что сервер отдает правильную кодировку в HTTP заголовке и в meta теге.
    Во-вторых, если в коде присутствуют перекодировки, то убедитесь, что они работают, и работают правильно. А ещё лучше - вообще уберите их, и пользуйтесь средствами БД.
    И вообще - старайтесь отличать проблемы браузера от проблем с БД.

    ОПС
    Если бы я нашел этот текст чуть раньше, то эта статья не была бы написана. Все очень толково и подробно: http://www.linux.by/wiki/index.php/FAQ_PHP_MySQL_charset
    Еще один неплохой текст по кодировкам: http://mysqlfaq.wikispaces.com/Encoding
    Официальная документация, разумеется. Я, конечно, знаю, что ни один из читателей этого фака не пойдет читать документацию на английском... Но сам я брал информацию именно оттуда. http://dev.mysql.com/doc/refman/5.0/en/charset.html

    В начало раздела   Наверх


    Пример кода, работающего с MySQL


    Самая классическая задача при работе с базой данных - это приложение вида форма-таблица.
    Таблица отображает записи, лежащие в БД, а форма служит для их добавледния/редактирования.

    Вот код скрипта, который и реализует упрощенную схему такого приложения.
    Кроме уникального идентификатора строки id, в таблице имеется только одно текстовое поле name
    Удаление в данном примере не предусмотрено, но при желании, его нетрудно добавить. Особенно учитывая тот факт, что записи желательно не удалять, а помечать, как непоказываемые. то есть, удаление сведется к апдейту.


    <? 
    mysql_connect
    ();
    mysql_select_db("new");
    $table="test";
    if(
    $_SERVER['REQUEST_METHOD']=='POST') { 
      if (
    $id=intval($_POST['id'])) {
        
    $query="UPDATE $table SET name='".mysql_real_escape_string($_POST['name'])."' WHERE id=$id";
      } else {
        
    $query="INSERT INTO $table SET name='".mysql_real_escape_string($_POST['name'])."'";
      }
      
    mysql_query($query);
      
    header("Location: http://".$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF']); 
      exit; 

    if (!isset(
    $_GET['id'])) { 
      
    $LIST=array();
      
    $query="SELECT * FROM $table"
      
    $res=mysql_query($query);
      while(
    $row=mysql_fetch_assoc($res)) $LIST[]=$row;
      include 
    'list.php';
    } else { 
      if (
    $id=intval($_GET['id'])) {
        
    $query="SELECT * FROM $table WHERE id=$id"
        
    $res=mysql_query($query);
        
    $row=mysql_fetch_assoc($res);
        foreach (
    $row as $k => $v$row[$k]=htmlspecialchars($v);
      } else {
        
    $row['name']='';
        
    $row['id']=0;
      }
      include 
    'form.php';

    ?>

    form.php
    <form method="POST">
    <input type="text" name="name" value="<?=$row['name']?>"><br>
    <input type="hidden" name="id" value="<?=$row['id']?>">
    <input type="submit"><br>
    <a href="?">Return to the list</a>
    </form>

    list.php
    <a href="?id=0">Add item</a>
    <? foreach ($LIST as $row): ?>
    <li><a href="?id=<?=$row['id']?>"><?=$row['name']?></a>
    <? endforeach ?>


    В начало раздела   Наверх


    Как писать музыку на PHP


    Часто на форумах задают вопросы, как с помощью PHP выполнить ту или иную операцию. При этом скромному языку для домашних страничек приписывают чудодейственные свойства - по ожиданию авторов, в PHP должны быть встроенные функции, которые будут показывать кино, писать музыку, делать скриншоты с сайтов и многое другое.

    Нижеследующий текст призван ответить на можеcтво подобных вопросов разом.

    Ответ: Для большинства перечисленных задач в PHP встроенных функций нет. РНР - это язык обработки гипертекста. Или просто текста. Вот с текстом РНР работает великолепно. А если вы хотите работать с бинарными данными, то следует посмотреть в сторону специально предназначенных для этого утилит.
    Вся работа РНР сведется в этом случае к вызову внешней программы с помощью команды system() или её аналогов.

    Определить, существуют ли у РНР встроенные функции для решения той или иной задачи, можно с помощью документации. Просто посмотрев список функций. Если лень, то можно использовать тот же самый критерий: текстовый или бинарный у нас протокол. К примеру, HTTP, FTP, POP3, SMTP - это все текстовые протоколы. И РНР, в силу своей текстовой сущности, прекрасно с ними работает.
    Всякие картинки, видео, музыка - это бинарные данные. И для работы с ними стоит использовать внешние утилиты. Мне сейчас возразят, что с картинками PHP работать умеет. Ага, умеет. Полтора формата и imagestring. Граждане. Давно пора забыть про эту убогую библиотечку - в мире есть множество гораздо более продвинутых средств для работы с графикой. И все, что нужно для их освоения - это научиться работать с ними из командной строки, а потом аккуратно перенести эту строку в функцию system().

    Для справки:
    Для работы с видео - ffmpeg
    Для работы с графикой - ImageMagick

    Так же к этому разделу можно отнести многочисленные вопросы, посвященные скачиванию. Чего угодно и откуда угодно. Файлов с русскими буквами, файлов с пробелами в имени, файлов с зарегистрированными в браузере расшинениями (например html). Как заставить скачиваться или наоборот - как заставить открыть.

    Ответ на все эти многочисленные вопросы один. И очень простой. Решается в два счёта:
    1. Находим сайт, который реализует нужный нам функционал. Заодно проверяем - а возможно ли это в принципе. Если не нашли, то, скорее всего, нельзя. Если нашли, то
    2. Берем любую программу просмотра НТТР заголовков, выполняем требуемую операцию на найденном сайте и смотрим, какие заголовки он формирует. После этого формируем у себя такие же.
    Задача решена.



    В начало раздела   Наверх


    Пример системы управления сайтом

    См. статью http://phpfaq.ru/easy/


    В начало раздела   Наверх


    Mail injection. Работа с e-mail средствами PHP.

    Что это такое?
    Формат e-mail
    Защита
    Комментарии

    Что это такое?
    Это когда форма обратной связи на сайте используется для рассылки спама.
    Как? Cамый дурной вариант - когда скрипт позволяет пользователю подставить адрес получателя. Это уж совсем ни в какие ворота не лезет. И даже словом injection не назовешь - а просто головотяпство.
    Собственно injection - это когда адреса получателей подставляются в поля формы так, чтобы попасть в заголовки письма. К примеру, в заголовок.

    Говоря о mail injection, невозможно обойти вниманием сам принцип работы почтовых отправлений. Даже больше скажу: человек, который знает, как устроено электронное письмо, сразу понимает, в чем смысл таких инъекций, и как от них защищаться. Вообще, это та проблема, о которой я все время говорю: Давая новичку полное понимание предмета, ты отвечаешь ему на сто вопросов разом. А пичкать ответами на каждый отдельный вопрос - ну это же жутко непроизводительно! Не по-программистски!
    Что проще - посмотреть ответ в таблице умножения, или постоянно бегать к соседу с вопросами - сколько будет пятью пять, трижды семь?
    Что проще - один раз объяснить (а ещё лучше - отправить почитать) про формат письма или отвечать на сто вопросов про инъекции, кодировки, вложения файлов и так далее?

    Формат e-mail
    Итак, что собой представляет почтовое сообщение?
    Во-первых, это обычный текст. просто набор строк определенного формата. Которые можно читать и, при некотором навыке - понимать.
    Во-вторых, вы будете смеяться, но устроено оно точно по тому же принципу, что и наши веб-страницы.
    При запросе страницы по протоколу HTTP сначала идут заголовки, каждый на своей отдельной строке, а затем - тело ответа. Соответственно, заголовки от тела отделяются пустой строкой. Такой вот нехитрый формат. Сначала каждая строка воспринимается как заголовок, а как только встречается пустая - значит, дальше идет тело, которое надо показывать.

    В почтовом сообщении все устроено точно так же.
    Вообще, я считаю, что один раз посмотреть - это лучше, чем сто раз прочесть. А при работе с почтой исходный текст письма так же важен, как и исходник HTML страницы при разработке сайта на пхп.
    Здесь ещё одна проблема. Очень многие программисты просто не знают, что они хотят сделать. Я, говорит, хочу отправить письмо. Но ведь письмо - это ТЕКСТ! Казалось бы, чего проще - составил сначала текст, который хочешь получить, убедился, что этот текст работает - и генери точно такой же на пхп! Но нет. поскольку человек не знает, из каких элементов состоит исходный текст письма, и какой за что отвечает - то и тычется с вопросами: а почему у меня письмо крокозябрами? А почему у меня письмо нормальное, а заголовок крокозябрами - я ведь кодировку указал? А почему заголовок не "Вам письмо" а лабуда какая-то - =?koi8-r?B?98HNINDJ09jNzw==?= ???
    С SQL запросами и HTML текстом то же самое. Я, говорит, хочу меню на пхп. Милый - объясняешь ему - меню на пхп не бывает! Нарисуй какое хочешь меню на HTML, а потом пиши скрипт на пхп, который рисует такое же. Мне не надо - орет - на HTML! Подожду ответа более грамотного специалиста!

    Так же и здесь. Ну разберись один раз - что какой заголовок значит и как кодируется.
    В бате и в Outlook Express посмотреть исходник письма можно. В MS Outlook - только заголовки. В других клиентах не знаю. Но Outlook Express есть на любой виндовой машине, а пользователям других систем, я надеюсь, не нужно объяснять формат почтовых сообщений. Поэтому рекомендую создать письмо в OE, зайти в отправленные, нажать Свойства - Подробно - Исходное сообщение.
    Да, очень рекомендую перед этим в настройках выставить формат отправления, как "просто текст". иначе исходник будет гораздо сложнее для понимания. Что мы там увидим?
    From: "phorror" <phorror@phorror.ru>
    To: =?koi8-r?B?98HNINDJ09jNzw==?= <phorror@phorror.ru>
    Subject: =?koi8-r?B?98HNINDJ09jNzw==?=
    Date: Wed, 13 Jun 2007 10:08:32 +0400
    MIME-Version: 1.0
    Content-Type: text/plain;
        charset="koi8-r";
    Content-Transfer-Encoding: 7bit
    X-Priority: 3
    X-MSMail-Priority: Normal
    X-Mailer: Microsoft Outlook Express 6.00.2900.3028
    X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.3028

    test
    test
    test


    на заголовки, начинающиеся с X, обращать внимание не надо - это необязательные.
    главное, что мы видим:
    1. Все, как я и рассказывал - сначала заголовки, потом пустая строка, потом текст
    2. Формат заголовка: начинается с новой строки, дальше идет ключевое слово, дальше двоеточие, пробел и значение (опять очень похоже на НТТР заголовки!)
    3. Формат указания e-mail адресов. Кроме имени, понятно без перевода. про имя ниже.
    4. формат указания заголовка. Ничё непонятно. Хотя, если не ужасаться, а сесть и немножко подумать, то можно сообразить, что, во-первых, такая лабуда применяется для форматирования строк в кодировках, отличных от latin1, а, во-вторых, формат-то у них совсем простой. Вопросительные знаки разделяют разные поля (точь-в точь как палочки | или пять троеточий в твоей первой гостевой книге). Первым полем идет явно кодировка языка. Вторым - можно догадаться - формат кодирования текста. Там может быть B или Q. (base64 и Q-encoding соответственно). А дальше - сам текст заголовка.
    Следовательно, понятен и алгоритм раскодирования: разбиваем по вопросам, раскодируем текст (функции есть в пхп, для Q подойдет от quoted-printable, как я понимаю), перекодируем язык если надо. Всё. Для сборки своего заголовка - обратная операция.
    Если бы сабжект был английский, то он выглядел бы просто:
    Subject: This is a test letter
    5. Поле Content-Type показывает нам, что заголовок может состоять не из одной строки, а может продолжаться на следующих - для этого дополнительные строки должны начинаться с пробельного символа. А так же, что она отвечает за кодировку текста письма, указывает формат - текст или html, и ведает ещё одной очень важной функцией: отвечает за структуру сложных, multipart сообщений, которые мы рассматривать здесь не будем.

    Из всего вышесказанного видно, что научиться отправлять собственные письма (те, которые формирует скрипт, а не из заполненной формы) очень просто:
    1. Пишем в аутлуке ровно такое же письмо, которое хотим отправлять скриптом.
    2. Смотрим заголовки.
    3. Пишем скрипт, который формирует точно такие же.
    4. Подсталяем эти заголовки в нужные поля функции mail
    5. Готово!
    И никаких проблем с кодировками самого письма, заголовоков, имени отправителя! слово "крокозяблы" пропадает из нашего лексикона!
    И с SQL запросами все ровно то же самое. новички часто пытаются сразу писать скрипт, который формирует сложный запрос, даже не представляя себе в точности, как этот запрос должен выглядеть! Запрос сначала надо написать и отладить в консоли или графическом клиенте. А потом писать пхп скрипт, который формирует точно такой же текст запроса.

    Всё. Теперь можно переходить к инъекциям.

    Защита
    Исходя из структуры письма можно сделать простой вывод: используя переводы строк, можно добавить любое количество новых заголовоков, в том числе - и адресов получателей.
    то есть, если мы напишем в своем коде
    $subject=$_POST['subject'], а в этом поле будет написано сто адресов получателей, разделенных переводами строк, то письмо отправится сотне жертв. просто, как два байта переслать.

    Отсюда следует, что и защита форм обратной связи такая же простая.
    Самое надёжное - помещать введенную пользователем информацию только в текст письма, и никуда больше. Сам я использую именно этот метод. Мне он нравится своей простотой и надежностью. А чем решение проще - тем оно мне милее.

    Если же ну прямо так уж хочется эмулировать отправку "настоящего" емейла - с заголовком, адресом отправителя и так далее, то все данные из формы, которые вставляются не в текст сообщения, следует обязательно проверить на наличие символов "\r" и "\n"!
    И при наличии таких символов письмо не отправлять.
    Адрес получателя, разумеется, дожен быть жестко прописан в скрипте (видал я некоторых уникумов, писавших адрес в скрытое поле в форме).

    Всё.

    В начало раздела   Наверх


    Безопасность PHP скриптов

    Файлы
    SQL Injection
    Eval
    Mail Injection
    XSS
    CSRF, Cross-site Request Forging
    HTTP Injection
    Инициализация переменных
    Заливка файлов на сервер
    ОПС
    Комментарии

    В принципе, я не большой специалист по безопасности. Да и тема настолько обширная и многогранная, что одной заметкой её охватить невозможно. Поэтому я сначала писать не собирался. Но со временем выяснилось, что даже очевидные вещи неизвестны многим программистам. Поэтому я решил собрать здесь несколько рекомендаций самого базового уровня.

    Основной принцип написания безопасных программ - это контроль данных, приходящих от пользователя.
    Есть несколько основных типов атак, которым может подвергнуться сайт, и мы их сейчас рассмотрим.

    Файлы
    В хорошем современном сайте уже редко можно встретить передачу имени файла в скрипт - данные хранятся в базе, а модули подключаются по более сложным алгоритмам, чем просто передача имени файла в адресной строке. Однако начинающие программисты просто без этого не могут. Доходит до анекдота - "хочу, чтобы было index.php?module=news потому что news.php - не солидно, сайт не выглядит крутым!". Ну, на что не пойдешь ради того, чтобы выглядеть круто. Надо только не забывать о безопасности. О том, что подсунуть для инклюда можно через адресную строку что угодно - от файла на другом сервере (с любым пхп-кодом!), до файла на своем - с настройками или паролями.

    Для борьбы с такими подстановками есть несколько методов. Можно проверять имя файла регулярками, но мне ближе всего функция basename(), которая отрезает от имени файла все лишнее.
    Если надо передавать файл вместе с путем к нему, к примеру, themes/green/balloons.php, то проверка будет сложнее. Поэтому я бы рекомендовал, все-таки, передавать только имя файла. Или не передавать ничего.

    SQL Injection
    Следующий тип атак довольно подробно рассмотрен в уже имеющейся статье на этом сайте, \"Кавычки \". Cоставление запросов, слеши, SQL Injection. Хочется лишь добавить, что защита от инъекций - не главное в соблюдении правильного синтаксиса, а побочный продукт, и синтаксис надо соблюдать всегда. Особенно - учитывая инъекции второго порядка, когда данные в запрос подставляются не от пользователя, а уже лежащие на сервере.

    Eval
    Удивительно, но многие программисты не осознают того факта, что применять eval к пользовательским данным - это все равно, что лично вручать вору ключ от сейфа. Этой функцией и так-то следует пользоваться очень осторожно, а на обработку ей введенных пользователем данных и вовсе должен быть жесткий запрет.
    При этом следует понимать, что пользовательские данные остаются таковыми и после того, как пользователь непосредственно отправил их в скрипт. Полученные из базы данных при последующих исполнениях скрипта, они не становятся менее опасными.

    Mail Injection
    Получившая распространение в последнее время атака так же уже рассмотрена на этом сайте, Mail injection. Работа с e-mail средствами PHP.. Коротко можно сказать, что нельзя допускать наличие переводов строки в данных, которые попадут в заголовки письма.

    XSS
    Тип атаки, так же получивший распространение сравнительно недавно. В отличие от всех предыдущих, которые атакуют непосредственно код скрипта, XSS атакует его функциональность: заставляет выполнять пользователя действия, нужные хакеру, или ворует его, пользователя, данные.
    Методы весьма разнообразны и многочисленны, однако наиболее распространенный и опасный - это внедрение javascript инструкций в безобидные теги - <img>, <a> и так далее.
    Наиболее действенным методом борьбы с этим является полный запрет на ввод пользователем какого бы то ни было HTML кода. Методов борьбы с этим достаточно много, но следует помнить, что функция strip_tags() не убирает опасные вставки из разрешенных тегов. поэтому лично я предпочитаю пользоваться функцией htmlspecialchars(), при выводе любых пользовательских данных.
    Но главная проблема при защите от XSS - не сам метод, а последовательность его применения. практически все известные атаки были произведены в тех частях сайта, где никто и подумать не мог об обработке пользовательских данных. А зря. Обрабатывать их надо везде и всегда.

    Так же к этому разделу можно отнести атаки на сессии. Связанные с кражей или подстановкой идентификатора. подробнее можно почитать в разделе Сессии. Подробное описание работы и объяснение механизма.

    CSRF, Cross-site Request Forging
    Формирование на сайте хакера запроса, который будет выполнен браузером пользователя (и, как следствие - со всеми правами пользователя!). То есть, просто авторизация от этой атаки не спасает. Для защиты от этого типа атак следвет взять за правило добавлять ко всем важным формам скрытое поле, содержащее уникальную строку (сгенерированную случайным образом только для этой формы), записывать её в сессию и сравнивать при обработке формы. Хакер добавить такую строку в свою форму не сможет, и атака не сработает.

    HTTP Injection
    XSS уязвимость, похожая на mail injection, только объектом атаки являются не заголовки письма, а HTTP-заголовки. Атака экзотическая, но знать про неё надо. Хакеру ничего не стоит дописать к урлу код с яваскриптом, который благополучно через переменную PHP_SELF или REQUEST_URI программист сам вставит в свою страницу.

    Инициализация переменных
    Со времени появления PHP в нем есть одна очень неприятная функциональность: получить пользовательские данные в своем скрипте можно совсем того не ожидая. Да-да, речь идет о register_globals. Если эта директива включена, пользователь может создать в скрипте любую переменную с любым содержимым, если только она не была создана программистом. А программисты часто это делать забывают, к примеру, если мы имеем код
    if ($login=="login" AND $password=="password"$admin=1;
    и в дальнейшем проверяем переменную $admin, то любой может получить права админа, просто дописав в адресной строке index.php?admin=1.
    Лекарством от этого служит принудительная инициализация всех переменных перед использованием. В строгих языках, таких, как Паскаль, невозможно использовать переменную, предварительно не объявив. В PHP это возможно, но злоупотреблять этим не стоит - следует объявлять все переменные перед использованием. В качестве же временной заплатки можно порекомендовать register_globals=off.
    то же самое можно почитать на офсайте PHP, http://ru.php.net/manual/ru/security.globals.php

    Отдельной строкой следует упомянуть борьбу с register_globals. Попытки эмулировать её включение (если отключена) или нейтрализовать (если включена) приводят к обратным результатам. Многочисленные циклы с назначением или удалением переменных приводят к проблемам более худшим, если бы директива просто была установлена в желаемое значение. Единственный гарантированный способ избежать её влияния - инициализация переменных.

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

    ОПС
    Статья Сергея Круглова, http://www.captcha.ru/articles/antihack/


    В начало раздела   Наверх


    Базовые понятия MySQL и отличия от текстовых файлов.

    FAQ. Порядок записей, первая и последняя.
    FAQ. ID.
    Язык SQL.
    FAQ. Перенос данных между разными серверами.
    Практическое использование.
    Комментарии

    FAQ. Порядок записей, первая и последняя.

    Список - это текстовый файл. Или, скажем, список учеников в классном журнале.
    чтобы получить упорядоченный список, мы должны, при добавлении любой записи, список переупорядочивать. При добавлени любой! А если мы хотим иметь два и больше видов упорядочивания? К примеру, список учеников у нас составляется по алфавиту, но нам интересно посмотреть его по успеваемости. Заводить два списка? НЕТ! Завести базу данных.
    И понять первый, самый основополагающий принцип. Порядок у данных бывает только при выборке их из БД ! Это очень важный факт. Данные в БД хранятся не как в классном журнале - по порядку. А как в песочных часах - кучей. Не задашь порядок вывода - будут выводиться от балды. Сама база не запоминает, какая запись была первой, а какая - последней. Но зато она умеет сортировать по признакам, указанным пользователем при создании таблицы.

    Первое, что надо запомнить. В базе не бывает первой и последней записи. О порядке можно говорить только при выводе, если пользователь сам задал этот порядок. к примеру, отсортировав записи по алфавиту. Вот в такой выборке есть и первая и последняя.
    Если нам нужно знать порядок занесения записей в базу - добавьте поле, с помощью которого это можно определить.

    Следующее, чем отличается БД от файла - это произвольный доступ. В файле, для того, чтобы обратиться к строке в середине, надо перебрать по очереди все идущие до неё. При обновлении - еще хуже. Для того, чтобы изменить строчку в строковом файле - надо переписать его ВЕСЬ! Целиком. С БД же - все просто. Хотим выбрать одну строчку - выбираем! Хотим изменить одну строку - изменяем. Хотим удалить две - удаляем!
    Примечание: На самом деле, БД - это не волшебный ящик, и хранит она данные не в нематериальном эфире, а в тех же самых файлах. И точно так же их переписывает. Но делает это она совершенно прозрачно для пользователя.

    При произвольном доступе и отсутствии внутренней нумерации «первый-второй-третий», перед нами встает проблема идентификации строк. Допустим, мы вывели строки из базы на экран, и теперь, кликнув по ссылке, хотим отредактировать одну из них. Как её запросить? По содержимому одного из полей? А вдруг это содержимое совпадает с еще несколькими записями? Нам нужен уникальный идентификатор! В MySQL применено остроумное решение. Одно из полей, обычно называемое `id` является автоинкрементным, то есть, автоматически увеличивающимся. При добавлении каждой новой записи, если id не указать или значение равно нулю, база присвоит полю id значение, на единицу большее, чем у предыдущего. Так мы получили уникальный идентификатор – ни у одной строки в этой таблице id не будет совпадать. Теперь очень просто отредактировать или вывести любую запись – достаточно указать её id.
    Кстати, у автоинкрементного id есть еще одно побочное свойство. Отсортировав таблицу по этому полю, можно получить ту самую пресловутую первую или последнюю запись :-)

    Однако, с этим полем связано множество ошибок и непониманий.

    FAQ. ID.
    Во-первых, это поле ошибочно принимают за нумератор. Как в классном журнале – 1,2,3-й ученик... А если ученика исключили из школы? А если мы выбираем не всех учеников, а только мальчиков?
    Правило первое: id к нумерации не имеет ни малейшего отношения! Во-первых, потому, что id могут идти не по порядку, а во-вторых, что все равно у нас порядок бывает только при ВЫБОРКЕ. Которая может быть какой угодно, id в ней могут идти совершенно вразнобой! Если мы хотим пронумеровать результаты – пожалуйста, при выводе добавим код на PHP, который будет это делать. В ЭТОЙ выборке. Нумеровать надо при выводе. Именно потому, что вариантов выборки может быть бесконечное количество.
    Посмотрим, для чего еще новичку может потребоваться нумерация? Для определения количества выбранных записей. Для этого есть функция – mysql_num_rows(). Она нам поможет при ЛЮБОЙ выборке. В то время, как нумерация не поможет нам вовсе, по причинам указанным выше.
    Правило второе: Менять id записи нельзя никогда, ни под каким видом. Во-первых, это просто никогда не нужно. Во-вторых, это же у нас УНИКАЛЬНЫЙ идентификатор. Поле id лишь внешне напоминает цифры. На самом деле – это способ ОДНОЗНАЧНО, и в любое время идентифицировать запись. Допустим, у нас есть сайт с новостями. Кто-то поставил ссылку на новость с id=1. Потом мы этот id сменили. В результате человек придет по ссылке не туда. Если же вам необходимо id перенумеровать – значит, вам просто не нужно автоинкрементное поле.

    Язык SQL.
    Теперь немного о языке SQL. Это, как я уже говорил – гениальное изобретение человечества. Практически искусственный интеллект. Посудите сами.
    Длял того, чтобы выбрать имена всех учеников школы, учащихся в классе 7А, отсортировав их по алфавиту, надо написать такой запрос:
    SELECT lastname FROM school WHERE klass=’7A’ ORDER BY lastname
    Это практически осмысленное предложение на английском языке!
    Попробуем сделать подстрочный перевод:
    ВЫБРАТЬ фамилии ИЗ школы [такие,] ГДЕ класс равняется 7А, ОТСОРТИРОВАВ ФАМИЛИИ ПО АЛФАВИТУ
    Ну как - неплохо? Мы практически натуральным языком говорим базе, что нам надо – и она выдает нам нужные строки в нужном порядке! И все это ОДНОЙ СТРОЧКОЙ! При этом сложность выборки может быть любой. Если бы мы выбирали из текстового файла – нам бы пришлось написать программу не на один лист. А тут – одна строчка!
    Ну как? Вы еще хотите колупаться с файлами?

    FAQ. Перенос данных между разными серверами.
    Перенос данных посредством дампа - файлов, содержащих запросы INSERT.
    То есть, на том сервере, с которого мы хотим перенести базу, мы генерируем файл с запросами INSERT для каждой строки каждой таблицы базы данных. А на удалённом сервере просто исполняем эти запросы.

    Наиболее правильный и прямой путь получить дамп - это воспользоваться утилитой командной строки mysqldump
    заходим в DOS или в shell и пишем
    mysqldump -u<логин> -p<пароль> база > dump.sql
    получаем файл dump.sql, который следует перенести на удалённый сервер и исполнить там.
    для этого надо вызвать mysql shell таким образом:
    mysql -u<логин> -p<пароль> база < dump.sql
    всё, база перенесена.
    при отсутствии доступа к шеллу можно воспользоваться PHP скриптом Sypex Dumper

    Практическое использование.
    Очень хорошая подборка материалов, обучающих работе с БД есть на сайте "PHP в деталях".
    Это уже упоминавшаяся здесь статья Вадима Ткаченко
  • "Вступление в PHP и MySQL".

  • А так же статьи Дмитрия Лебедева:
  • Работа с базами данных. Начало.

  • Работа с MySQL: Подробнее

  • Работа с MySQL. Новостная лента для странички

  • Так же, при работе с MySQL необходимо учитывать требования, изложенные в разделе этого FAQ, посвященном составлению запросов и использованию кавычек

    Остальные материалы, посвященные MySQL:


    В начало раздела   Наверх


    Рисование календаря


    Функция выводит календарь на месяц и принимает аргументом массив с датами в формате ГГГГ-ММ-ДД.
    Если дата совпадает в выводимой в календаре, она оформляется ссылкой
    Пример использования:
    <? 
    if (isset($_GET['date'])) echo "выбрана дата ".$_GET['date'];
    my_calendar(array('2004-09-01')); 
    ?>

    Код:
    <? 
    function my_calendar($fill='') { 
      
    $month_names=array("январь","февраль","март","апрель","май","июнь",
      
    "июль","август","сентябрь","октябрь","ноябрь","декабрь"); 
      if (isset(
    $_GET['y'])) $y=$_GET['y'];
      if (isset(
    $_GET['m'])) $m=$_GET['m']; 
      if (isset(
    $_GET['date']) AND strstr($_GET['date'],"-")) list($y,$m)=explode("-",$_GET['date']);
      if (!isset(
    $y) OR $y 1970 OR $y 2037$y=date("Y");
      if (!isset(
    $m) OR $m OR $m 12$m=date("m");

      
    $month_stamp=mktime(0,0,0,$m,1,$y);
      
    $day_count=date("t",$month_stamp);
      
    $weekday=date("w",$month_stamp);
      if (
    $weekday==0$weekday=7;
      
    $start=-($weekday-2);
      
    $last=($day_count+$weekday-1) % 7;
      if (
    $last==0$end=$day_count; else $end=$day_count+7-$last;
      
    $today=date("Y-m-d");
      
    $prev=date('?\m=m&\y=Y',mktime (0,0,0,$m-1,1,$y));  
      
    $next=date('?\m=m&\y=Y',mktime (0,0,0,$m+1,1,$y));
      
    $i=0;
    ?> 
    <table border=1 cellspacing=0 cellpadding=2> 
     <tr>
      <td colspan=7> 
       <table width="100%" border=0 cellspacing=0 cellpadding=0> 
        <tr> 
         <td align="left"><a href="<? echo $prev ?>">&lt;&lt;&lt;</a></td> 
         <td align="center"><? echo $month_names[$m-1]," ",$y ?></td> 
         <td align="right"><a href="<? echo $next ?>">&gt;&gt;&gt;</a></td> 
        </tr> 
       </table> 
      </td> 
     </tr> 
     <tr><td>Пн</td><td>Вт</td><td>Ср</td><td>Чт</td><td>Пт</td><td>Сб</td><td>Вс</td><tr>
    <? 
      
    for($d=$start;$d<=$end;$d++) { 
        if (!(
    $i++ % 7)) echo " <tr>\n";
        echo 
    '  <td align="center">';
        if (
    $d OR $d $day_count) {
          echo 
    "&nbsp";
        } else {
          
    $now="$y-$m-".sprintf("%02d",$d);
          if (
    is_array($fill) AND in_array($now,$fill)) {
            echo 
    '<b><a href="'.$_SERVER['PHP_SELF'].'?date='.$now.'">'.$d.'</a></b>'
          } else {
            echo 
    $d;
          }
        } 
        echo 
    "</td>\n";
        if (!(
    $i 7))  echo " </tr>\n";
      } 
    ?>
    </table> 
    <? ?>


    В начало раздела   Наверх


    Обработка ошибок, часть 1. Общие принципы.


    Большинство начинающих пхп-программистов путаются в обработке ошибок.
    Причём путаница происходит оттого, что они смешивают несколько понятий. А именно:
    1. Факт ошибки.
    2. Сообщение системы об ошибке.
    3. Обработка ошибки
    4. Информирование пользователя об ошибке.

    Чаще всего путают второй и четвёртый пункты, принимая одно за другое.
    Так же, ради неправильного понимания четвёртого, кладут на предыдущие три.
    Ну и - коронный номер - борьба с ошибками путём подавления сообщений о них.

    Далее следует несколько простых и очевидных рекомендаций.

    Системное сообщение об ошибке - не твой враг, а твой друг. Избавляться от него не надо! Наоборот - надо стремиться получить его всеми силами - оно поможет исправить тебе ошибку.
    Не надо просто путать программиста с пользователем.
    Если ты разрабатываешь сайт, и пользователь - ты сам, то удобнее смотреть ошибки на экране.
    поэтому делаем в настройках сервера
    display_errors=on

    Если сайт уже работает, и на него заходит куча пользователей, то ситуация меняется в корне.
    Во-первых, системные сообщения об ошибках пользователь видеть не должен.
    Во-вторых, их как-то должен видеть программист, причём не только когда он сам обращается к сайту, но и те ошибки, которые происходят у других пользователей.
    Первая задача решается уже знакомой нам директивой
    display_errors=off
    вторая - настройкой, которая заставит пхп все ошибки писать в лог, где их потом может увидеть программист.
    log_errors=on

    С самописными функциями всё просто.
    главное - никаких die(mysql_error())!!!
    это хорошо на этапе обучения, но никуда не годится на посещаемом сайте!
    во-первых, ПОЛЬЗОВАТЕЛЮ эта mysql_error() ничего не скажет.
    во-вторых, программист её не увидит.
    В-третьих, негоже вообще обрывать вывод сайта на середине.
    Поэтому делаем проверку вместо die надо использовать trigger_error()
    В результате у нас должно получиться
    $query="Select * FROM table";
    $res=mysql_query($query) or trigger_error(mysql_error().$query)
    ;
    Таким образом, сообщение об ошибке выведется туда же, куда выводятся все остальные ошибки, в зависимости от установок, рассмотренных выше.

    Из написанного выше становится ясно, что собака не бывает нужна в принципе никогда.
    Во-первых, расставить собак во всех местах вероятного появления ошибки просто нереально.
    Во-вторых, и самое главное - собака делает НЕ ТО, ЧТО ВАМ НУЖНО! Вы просто путаете вывод сообщения пользователю и информирование программиста об ошибке.
    Вам нужно запретить вывод ошибок пользователю? Отлично! ОДИН раз написать display_errors гораздо проще, чем лазить по коду, расставляя собак.
    Надо посмотреть сообщение об ошибке? Отлично! Лезем в лог или включаем display_errors, вместо того, чтобы сидеть и гадать на кофейной гуще - где ошибка. Вывод же сообщений мы собакой подавили!

    Всё. С программистом закончили.
    Теперь осталось проинформировать пользователя об ошибке - ведь до сих пор мы заботились только о том, чтобы пользователь не увидел ошибку.
    Теперь подумаем, как сделать так, чтобы пользователь увидел дружественное сообщение об ошибке, да ещё и желательно не в разорванном дизайне.
    Проще всего это делается с использованием шаблонов.
    Ведь при их использовании сначала исполняется весь нужный код, по завершении которого можно проконтролировать успешность его выполнения, а потом выводится шаблон, который, в случае ошибки, можно заменить шаблоном стандартного сообщения об ошибке.
    Подробнее об этом можно прочитать в следующей главе: Обработка ошибок, часть 2. Разбор примера. Исключения.

    В начало раздела   Наверх


    Обработка ошибок, часть 2. Разбор примера. Исключения.

    Пример из жизни.
    Использование исключений.
    Комментарии

    Пример из жизни.
    Вот яркий образчик "обработки ошибок", который можно встретить практически в любом скрипте
    if (is_writable($file) {
      
    $handle fopen($file,'w') || die('error opening');        
      
    fwrite($handle$text) || die('error writing');
      
    fclose($handle);
    } else die(
    "not writable");

    Почему это неправильно, было рассказано в первой части.
    Попробуем переписать этот код по-другому.

    $handle fopen($file,'w');        
    $written=fwrite($handle$text);
    fclose($handle);
    if (
    $written===FALSE) {
     
    $error="Извините, произошла ошибка. Попробуйте повторить позднее"
    }


    Функция is_writable имеет смысл только в том случае, если планируется как-то реагировать на невозможность записи. А если для пользователя, как это часто бывает, существует только два состояния - "операция прошла успешно" и "произошла ошибка", то и дополнительная проверка ничем не поможет. А вот запись в лог конкретной причины невозможности записи - очень поможет программисту.
    поэтому мы убираем проверку is_writable, чтобы ошибки при открытии и записи файла пошли в лог, а для сообщения пользователю проверяем только самый конечный результат - запись в файл.

    однако это решение подходит не для всех случаев. иногда от наличия ошибки зависят очень большие куски кода, которые гарантированно будут выдавать ошибки, которые ничего не добавят к самой первой. К примеру, если на месте fopen будет mysql_connect, за которым идет десяток запросов, то после нужной программисту ошибки соединения в логе будет еще куча ошибок запросов.
    В таких случаях нужно обрабатывать эти "ключевые точки":
    if ($handle fopen($file,'w')) {
      
    $written=fwrite($handle$text);
      
    fclose($handle);
    }
    if (!
    $handle OR $written===FALSE) {
      
    show_error_page();
    }


    Смысл здесь в чем?
    Как описано в первой части, мы должны разделять саму ошибку, сообщение об ошибке, информирование о ней пользователя и программиста.
    саму ошибку обрабатывает первый if - если файл не открылся, то записи не будет.
    сообщение об ошибке, как положено, не пойдет на экран, а пойдет в лог.
    программист будет проинформирован из лога же.
    А пользователь, которому неважно, какие у нас там были ошибки, а интересует только одно: записалось-не записалось - получает свое сообщение. Для этого мы проверяем то, что интересует пользователя - произошла ли, собственно, запись в файл.

    Использование исключений.
    В PHP5 появился стандартный для большинства языков механизм исключений.
    почитать про него можно в документации, а здесь мы просто посмотрим, как будет выглядеть код с применением этого механизма:
    try {
      
    $handle=fopen($file,"a");
      if (!
    $handle) throw new Exception("open");
      
    fwrite($handle$text);
      
    fclose($handle);

    catch (
    Exception $e) {
      
    show_error_page();
    }


    Можно провести такую аналогию, что throw является аналогом die, но не для всего скрипта, а только для канарейки - кода, заключенного между try {}. Что позволяет, с одной стороны, не выполнять код, который все равно не сработает, а с другой - не обрывать работу всего скрипта, а завершить его корректно.

    Вообще, механизм исключений предоставляет просто неограниченные возможности по управлению ошибками.
    Взять, например, вот такой код:
    function exceptions_error_handler($severity$message$filename$lineno) { 
        throw new 
    ErrorException($message0$severity$filename$lineno); 
    }
    set_error_handler('exceptions_error_handler');


    при его использовании наш тестовый пример становится совсем удивительным:
    try {
      
    $handle fopen($file,'w');
      
    fwrite($handle$text);
      
    fclose($handle);

    catch (
    Exception $e) {
      
    show_error_page();
    }

    Видишь обработку ошибок? И я нет. А она есть!
    fwrite($handle, $text); не выполнится, если fopen отработала с ошибкой.

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

    В начало раздела   Наверх


    Что такое PHP?


    Как ни странно, но все 5 лет, которые существует этот сайт, здесь не было такого раздела :-)
    попробуем восполнить этот недостаток.
    Данный раздел предназначается тем, кто собирается изучать PHP или просто интересуется - что это такое.

    PHP - это язык программирования.
    Язак программирования, предназначенный для создания сайтов. Или, другими словами, PHP позволяет автоматизировать работу с сайтом.

    Для чего вообще может понадобиться язык программирования при создании сайта? Кому он пригодится, а кому - не очень? Давайте посмотрим.

    Условно говоря, применение PHP можно разделить на три группы:

    1. Самое простое (но очень мощное) применение - это использование PHP, как аналога SSI. Записать все неизменяемые части сайта в отдельные файлы (меню, шапку, подвал) и вместо того, чтобы писать одно и то же на каждой странице, просто вызывать
    include "menu.php";
    Это уже значительно облегчит работу с сайтом. К примеру, если добавится новый пункт в меню...

    2. Следующий этап - создание отдельных небольших программок. Это может быть голосование, гостевая книга, вывод текущей даты, рисование календаря... Хотя последние две задачи, в отличие от первых, не обязательно делать на PHP - можно и на Яваскрипте. PHP применяется тогда, когда нужно какую-то информацию хранить на сервере. К примеру, гостевую книгу на Яваскрипте написать множно, но вот... увидит её сообщения только тот, кто их добавлял :-)

    3. Победное шествие PHP па планете началось в тот миг, когда кому-то пришла в голову замечательная идея: а почему бы не добавлять материалы на сайт не обычным способом, закачивая HTML файлы по FTP, а забивая текст в форму, как сообщение в гостевую книгу?
    Таким образом, пишется две программы, одна из которых позволет администратору добавить информацию на сайт, а вторая - показывает эту информацию посетителям. Таким образом устроены практически все современные сайты.

    С чем чаще всего путают PHP? С готовыми программами, написанными на PHP. Если вам нужна гостевая книга, портал или электронный магазин - вам не нужно учить PHP. Вам нужно найти готовую программу.

    Если же вы решили изучать этот язык, то следует знать, что ожидает впереди. Для того, чтобы стать даже начинающим веб-программистом, надо знать:
    - три языка программирования - HTML, PHP и SQL.
    - Иметь очень хорошее представление о протоколе передачи гипертекста HTTP и о том, как взаимодействует компьютер пользователя с веб-сервером.
    - иметь базовые представления об операционных системах (в частности понития файл, каталог, знать основные отличия windows от unix)
    - иметь базовые представления о сетевом протоколе TCP/IP (как соединяются компьютеры, что такое IP адрес, DNS и прочее)
    - обязательно придется познакомиться с понятием отладки своих программ

    Если весь этот объем не пугает, то добро пожаловать! Начните с раздела "Хочу изучать PHP"

    В начало раздела   Наверх


    Как делать UPLOAD файлов на сервер

    См. статью http://phpclub.ru/detail/article/upload


    В начало раздела   Наверх


    Как сделать уменьшенную копию картинки?

    См. статью http://phpfaq.ru/links#resize


    В начало раздела   Наверх


    Шаблоны


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

    Самое простое и очевидное заблуждение состоит в том, что новички называют шаблоном вынесенный в отдельный файл "дизайн" - обший html для всех страниц сайта. И на этом успокаиваются. Динамическую информацию, ничтоже сумняшеся, выводя старым добрым echo :-)
    На самом же деле, шаблонизатор в основном занимается выводом изменяющегося содержимого страниц сайта. А вывод "дизайна" - задача второстепенная.

    Фантазий главных две:
    1. Шаблоны нужны "дизайнеру", чтобы он мог их править, не разбираясь в PHP.
    2. Следовательно, шаблоны служат для отделения PHP от HTML.

    Давайте попробуем задуматься над первым утверждением. Кто такой дизайнер? Это человек, который работает в фотошопе. HTML он чаще всего не знает вообще. А над шаблоном работает либо специальный верстальщик или - чаще всего... сам программист! Смешно, правда?
    Теперь следствие, про отделение PHP от HTML. Отлично. Перед нами стоит святая цель отделить. Поэтому мы придумываем Смарти и пишем:
    {foreach key=cid item=con from=$contacts}
        <a href="contact.php?contact_id={$cid}">{$con.name} - {$con.nick}</a><br />
    {/foreach}

    Ещё смешнее.
    "Дизайнер", ради которого все затевалось, падает в обморок от счастья.

    Получается, наши причины, по которым мы решили пользоваться шаблонами, гроша выеденного не стоят. И что, не нужны, выходит, шабоны вообще? Нужны. Но сначала надо ответить себе на вопрос - зачем. Для чего нужны шаблоны. И проверить ответ практикой. Я много раз задавал людям этот вопрос. Но почти никто не может на него ответить. Зачем ему нужны шаблоны. Получается, люди делают что-то, не зная зачем.
    Это - самое смешное.

    За время своей деятельности в качестве веб-программиста я сформулировал для себя три причины, по которым нужны шаблоны лично мне. По сути, их две. А сводятся, в конечном счете, к одной:

    Один код - несколько представлений.

    Часто бывает так, что вместо одной информации надо показать другую. Например, код получения текста новости из базы получает вместо него сообщение об ошибке. Вместо страницы с текстом новости нужно показать совсем другую - с извинениями и просбой зайти позже. С помощью шаблонов это делается элементарно.

    Часто одну и ту же информацию надо показывать в нескольких видах. К примеру - обычная страница и страница для печати. Информация та же самая, код её получения один и тот же, а код вывода - разный. Столкнувшись с такой ситуацией, очень быстро разделишь свой код на две части, одна из которых отвечает за вывод, а вторая - не отвечает.

    В чем-то похожая ситуация: допустим, наш скрипт стоит на двух сайтах. И у нас дома. И вот дома мы нашли крупную багу. Заделали её. Теперь надо обновить код на сайтах. И вот он - момент истины: если шаблоны были использованы правильно, то мы просто заливаем код на оба сайта, и все продолжает работать, как ни в чем не бывало! Такая ситуация, как я считаю - идеальная проверка выбранного подхода к шаблонизации.

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

    В начало раздела   Наверх



    Created/Updated: 25.05.2018

    ';