Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Очень просто о PHP, от элементарных понятий до ООП. Сессии и куки


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

Начали:


Повестка дня

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

Ни-фи-га! Большая ошибка.

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

На вечеринке

Возможно, вы слышали на последней посещённой вечеринке такое утверждение: "HTTP - обезличенный протокол, а интернет - среда разработки без хранения состояния". Нет? Ну, значит вы ходите на неправильные вечеринки!

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

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

Другой распространенный подход заключается в использовании для хранения данных сессии. При этом данные сеанса сохраняются на сервере во время визита и уничтожаются по окончании. Работают сессии, связывая каждую сессию с ID сессии (уникальный идентификатор сессии), которые автоматически генерируются PHP. Этот идентификатор сессии хранится в двух местах: у клиента, используя временные куки, и на сервере в обычном файле или базе данных. С помощью идентификатора сессии разработчик может определять какой клиент инциировал запрос, а также отслеживать и поддерживать специфическую для этого клиента информацию в переменных сессии(пары переменная-значение, которые остаются определёнными в течение сессии и которые могут хранить текстовые или числовые данные).

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

PHP поддерживает куки с версии 3.0 и встроенную поддержку управления сессиями с версии 4.0. Обе эти функции включены по умолчанию, так что вам не нужно делать ничего специального для их активации. Вместо этого гляньте вниз и посмотрите на вашу первую сессию.

Первая сессия

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

Посмотрим на код:

<?

// начнём сессию
session_start(); 

// увеличим значение счётчика
$_SESSION['counter']++; 

// напечатаем его значение
echo "Вы просматривали эту страницу " . $_SESSION['counter'] . " раз."; 

?>

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

Каждая сессия в PHP начинается вызовом функции session_start(). Эта функция проверяет, существовала-ли сессия ранее, и затем либо восстанавливает её (если существовала), либо создаёт новую (если сессии раньше не существовало). После этого могут быть зарегистрированы переменные сессии - добавлением ключа и его значения в суперглобальный массив $_SESSION, и могут быть извлечены в течение сессии в любое время с использованием стандартной нотации массивов. В примере выше ключ с именем counter был добавлем в массив $_SESSION array. Во время создания сессии этот ключ имеет значение 0. При каждом последующем запросе, в течение той-же сессии, скриптом будет получено предыдущее значение счётчика и увеличено на единицу.

Если у вас пример выше не работает как описано, проверьте , указывает-ли переменная session.save_path в вашем файле php.ini file на верную папку в вашей системе. Её значение указано как /tmp по умолчанию, так-что если вы попробуете этот пример на системе Windows, вам придётся изменить его на C:\Windows\temp (или на другую временную папку).

Запомни меня!

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

<?

// начнём сессию 
session_start(); 

?>

<html> 
<head></head>
<body> <? if (!isset($_SESSION['name']) && !isset($_POST['name'])) { // если нет данных, напечатаем форму ?> <form action="<?php echo $_SERVER['PHP_SELF']?>" method="post">
<input type="text" name="name">
<input type="submit" name="submit" value="Введите имя">
</form> <? } else if (!isset($_SESSION['name']) && isset($_POST['name'])) { // если нет сессии, но форма была отправлена, // то проверим, имела-ли форма все нужные нам значения // создадим новую сессию if (!empty($_POST['name'])) { $_SESSION['name'] = $_POST['name']; $_SESSION['start'] = time(); echo "Добро пожаловать, " . $_POST['name'] .". Для вас была создана новая сессия. Щёлкните <a href=" . $_SERVER['PHP_SELF'] . ">здесь</a> чтобы обновить страницу."; } else { echo "ОШИБКА: Введите имя!"; } } else if (isset($_SESSION['name'])) { // если существует предыдущая сессия // вычислим прошедшее время между началом сессии и текущим echo "Добро пожаловать снова, " . $_SESSION['name'] . " . Эта сессия началась " . round((time() - $_SESSION['start']) / 60) . " минут назад. Щёлкните <a href=" . $_SERVER['PHP_SELF'] . ">здесь</a> чтобы обновить страницу."; } ?> </body>
</html>

В этом примере наличие или отсутствие переменной используется для определения трёх возможных экранов, которые надо показать. Время начала сесси записывается в $_SESSION['start'] функцией time(), которая возвращает общее число секунд, прошедшее с первого января 1970 года до текущего времени. При следующем действии это сохранённое в $_SESSION['start'] значение сравнивается с текущим значанием time() для вычисления и показа (примерного) прошедшего времени.

Важно отметить, что вызов функции session_start() должен быть первым, перед любым выводом вашего скрипта (если вы не используете функции буферизации, о которых можно прочитать на http://www.php.net/manual/en/ref.outcontrol.php). Причина этого - в том, что обработчик сессий в PHP использует куки для хранения данных сессии, а они должны передаваться в браузер клиента до какого-либо вывода. Если вы когда-нибудь увидите в своём скрипте работы с сессиями такую ошибку:

Warning: Cannot send session cache limiter - headers already sent (output started at ...)

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

Как отмечалось ранее, каждая сессия имеет уникальный идентификатор сессии, которая используется PHP для отслеживания различных клиентов. Этот идентификатор сессии - длинная буквенно-цифровую строка, которая автоматически передается PHP от страницы к странице, так что сохраняется преемственность сессии. Чтобы увидеть, как он выглядит, используйте функцию session_id(), как в этом простом примере:

<?

// начнём сессию 
session_start(); 

// напечатаем идентификатор сессии
echo "Я слежу за тобой, номер " . session_id() . ", >:-E !";

?>

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

<?

// начнём сессию
session_start(); 

// затем уничтожим её
session_destroy();

?>

В случае, если вы подумали - правильно-ли вы прочитали этот пример - таки да, перед вызовом функции session_destroy() необходимо вызвать функцию session_start() чтобы воссоздать сессию.

Запомните, что $_SESSION - суперглобальная переменная, так-что вы можете использовать её внутри или вне функций без обьявления её глобальной. Следующий пример иллюстрирует это:

<?
// начнём сессию 
session_start(); 

// эта функция проверяет значение переменной сессии
// и возвращает итсину или ложь
function isAdmin() { 
    if ($_SESSION['name'] == 'admin') { 
        return true; 
    } 
    else { 
        return false; 
    } 
} 

// установим значение для $_SESSION['name'] 
$_SESSION['name'] = "guessme"; 
// вызовем функцию, использующую переменную сессии
// здесь она возвращает ложь 
echo isAdmin()."<br />"; 

// установим новое значение для $_SESSION['name'] 
$_SESSION['name'] = "admin"; 
// вызовем функцию, использующую переменную сессии
// здесь она возвращает истину 
echo isAdmin()."<br />";

?>

Вы можете узнать больше о сессиях и функциях для работы с ними по адресу http://www.php.net/manual/en/ref.session.php.

Правила игры

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

Прежде чем мы начнём использовать куки, предупрежу вас о нескольких вещах:

  1. Поскольку куки используются для записи вашей активности в определённом домене, они могут быть прочитаны только доменом, создавшим их.
  2. Один домен не может установить более 12 куки, максимального размера по 4 Кб.
  3. Куки обычно обладает шестью атрибутами, из которых только первый является обязательным:
    • имя: имя куки
    • значение: значение куки
    • истекает: дата и время, когда истекает срок куки
    • путь: каталог верхнего уровня домена, с которого доступны данные куки
    • домен: домен, для которого действительны куки
    • безопасность: булево значение, показывающее что куки передаётся только по безопасному HTTP-соединению

Больше информации о куки вы можете получить на сайте Netscape - компании, придумавшей их. Посетите http://www.netscape.com/newsref/std/cookie_spec.html

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

Имея в виду это предостережение, давайте посмотрим на простые методы работы с куки в PHP.

Встречаем старых друзей

PHP предлагает всего одну функцию для работы с куки: setcookie(). Эта функция позволяет вам записывать файлы куки, как показано в следующем примере:

<?

if (!isset($_COOKIE['visited'])) { 
    // если куки не существует,
    // то установим
    setcookie("visited", "1", mktime()+86400, "/") or die("Не могу установить куки"); 
    echo "Это ваш первый визит за сегодня."; 
} 
else { 
    // если куки уже есть
    echo "Рады видеть вас снова!"; 
} 

?>

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

Функция setcookie() принимает шесть аргументов: имя куки, значение, дату истечения, домен, допустимый путь и булево значение состояния безопасности. Как было сказано раньше, важны только имя и значение, несмотря на то, что приведённый выше пример указывает ещё и каталог верхнего уровня и дату истечения срок жизни куки (1 день, вычисленный функцией mktime()).

Значения куки автоматически отправляются PHP клиенту и преобразовываются в пары величина-значение в переменной $_COOKIE, суперглобальный массив, схожий с $_SESSION. Значения могут быть получены применением стандартной нотации массивов, как мы уже видели в примере выше. Заметьте, что как и в случае с сессиями, вызовы setcookie() должны располагаться перед любым выводом данных скрипта, или вы увидите что-то наподобие:

Warning: Cannot add header information - headers already sent by (output started at ... )

Форма и функция

Вот ещё один, чуть более сложный пример:

<?
if (!isset($_POST['email'])) { 
// если форма ещё не была отправлена
// покажем её
// если уже есть куки, заполним предварительно форму значениями из куки ?> <html> <head></head> <body> <form action="<?php echo $_SERVER['PHP_SELF']?>" method="post"> Ваш адрес email: <input type="text" name="email" value="<?php echo $_COOKIE['email']; ?>"&t; <input type="submit" name="submit" value="Отправить"> <?php // также вычислим время от последней отправки формы if ($_COOKIE['lastsave']) { $days = round((time() - $_COOKIE['lastsave']) / 86400); echo "<br /> $days дней с последнего посещения"; } ?> </form> </body> </html> <?php } else { // если форма была отправлена // установим куки в значение из формы // срок жизни куки - 30 дней if (!empty($_POST['email'])) { setcookie("email", $_POST['email'], mktime()+(86400*30), "/"); setcookie("lastsave", time(), mktime()+(86400*30), "/"); echo "Ваш адрес email был записан."; } else { echo "ОШИБКА: Введите адрес email!"; } } ?> </body> </html>

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

Этот пример также показывает как вы можете установить несколько куки для домена, вызывая setcookie() множество раз. В нём время заполнения формы сохраняется как второе куки, а затем используется для вычисления времени между успешными вводами адреса.

Для удаления куки у клиента просто вызовите функцию setcookie() с таким-же синтаксисом, как и при установке куки, но со временем истечения в прошлом. Это заставит куки удалиться из клиентской системы. Пример:

<?

// удалим куки
setcookie("lastsave", NULL, mktime() - 3600, "/");

?>

Узнайте больше о куки и функции setcookie() на страничках http://www.php.net/manual/en/features.cookies.php и http://www.php.net/manual/en/function.setcookie.php.

Доступ разрешён

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

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

Допустим, что таблица базы данных выглядит следующим образом:

+-------+-----------------------------------------------+
| name  | pass                                          |
+-------+-----------------------------------------------+
| sue   | 9565d44fd0fe4db59f073eea1db70f3ea258e10b      |
| harry | 6e74234b8b552685113b53c7bff0f386c8cef8cf      |
| louis | 6817dda51b64b8190029490d2811a4d9cb9cd432      |
| sam   | bd17f8243e771a57cfbb06aa9a82bbf09fd2d90b      |
| james | 792ec9b44d432c947ac6775b2b52326e9d08512f      |
+-------+-----------------------------------------------+

здесь имеются уникальные имена пользователей и пароли, созданные функцией SHA1(), а вот и сам скрипт, который выполняет всю работу:

<?

if (isset($_POST['name']) || isset($_POST['pass'])) { 
    // формы была отправлена
    // проверим данные
    if (empty($_POST['name'])) { 
        die ("ОШИБКА: Введите имя!"); 
    } 
    if (empty($_POST['pass'])) { 
        die ("Ошибка: Введите пароль!"); 
    } 

    // установим данные для доступа к серверу БД MySQL
    $host = "localhost"; 
    $user = "test"; 
    $pass = "test"; 
    $db = "db2"; 
     
    // откроем соединение
    $connection = mysql_connect($host, $user, $pass) or die ("Не могу подключиться к БД!"); 
     
    // выберем базу данных
    mysql_select_db($db) or die ("Не могу выбрать БД!"); 
     
    // создадим запрос
    $query = "SELECT * FROM users WHERE name = '" . $_POST['name'] . "' AND pass = SHA1('" . $_POST['pass'] . "')"; 
     
    // выполним запрос
    $result = mysql_query($query) or die ("Ошибка в запросе: $query. " . mysql_error()); 
     
    // проверим, был-ли возвращёны строки
    if (mysql_num_rows($result) == 1) { 
        // если была возвращена одна строка,
        // значит авторизация успешна
        // создадим сессию и установим куки с именем пользователя
        session_start(); 
        $_SESSION['auth'] = 1; 
        setcookie("username", $_POST['name'], time()+(84600*30)); 
        echo "Доступ разрешён!"; 
    } 
    else { 
        // не результатов
        // авторизация не прошла
        echo "ОШИБКА: Неверные имя или пароль!"; 
    } 
     
    // освободим память от результата запроса
    mysql_free_result($result); 
     
    // запроем подключение
    mysql_close($connection); 
} 
else { 
    // форма не была отправлена
    // покажем её

?>

 <html>
 <head></head> 
<body>
<center>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
Имя пользователя <input type="text" name="name" value="<?php echo $_COOKIE['username']; ?>">
<p />
Пароль <input type="password" name="pass">
<p />
<input type="submit" name="submit" value="Вход">
</center>
</body>
</html>
<?php
}

 

?>

Здесь значения, введённые в поля формы, соединяются с запросом MySQL SELECT, который выполняется с таблицей. Если оба поля - имя и пароль - подходят, то будет возвращена строка, показывающая, что авторизация успешна; если нет - ни одной записи возвращено не будет, что показывает что авторизация провалилась.

Предположим чтоавторизация прошла успешно, тогда будет инициализирована сессия, будет создан ключ $_SESSION['auth'] и ему будет присвоено булево значение "истина", а имя пользователя будет сохранено в куки до следующего раза. Куки останутся действительными 30 дней и будут использованы при предварительном заполнении имени пользователя в форме при следующей попытке авторизации.

Конечно, этого само по себе недостаточно. В то время, когда скрипт выше проверяет авторизацию и инициализирует сессию и куки с полномочиями пользователя, проверка должна выполняться на каждой страничке ограниченного доступа. Без этой проверки любой пользователь может пропустить экран авторизации и просто ввести адрес URL к любой странице для её просмотра.

Как это видно из предыдущего сценария, переменная сессии $_SESSION['AUTH'] может существовать только в том случае, если учетные данные пользователя были подтверждены, то достаточно проверки наличия переменной $_SESSION['AUTH'] в верхней части каждой страницы ограниченного доступа, и предоставить доступ, если наша проверка вернёт истину. Вот так:

<?

// start session 
session_start(); 
if (!$_SESSION['auth'] == 1) { 
    // проверим, проведена-ли авторизация
    // если ошибка - выходим
    die ("ОШИБКА: нет доступа!"); 
} 
else { 
?> 
    <html>
    <head></head>
    <body>
    Это секретная страница. Вы можете видеть её, если $_SESSION['auth'] = 1 
    </body>
    </html>
<?php 
}
?>

Довольно просто, да? Только прошедшие проверку подлинности пользователи будут видеть эту страницу, потому что только их клиенты будут иметь сессию с переменной $_SESSION['AUTH'] в нем. Всем остальным будут просто показаны сообщения об ошибке.

С этой лекцией всё. В одиннадцатой мы узнаем всё о SimpleXML, новом инструментарии обработки XML, который входит в PHP 5.

Не пропускаем лекции, не пропускаем!

Оригинал перевода: http://www.figli-migli.org.ua/books/1941-php101-10


В избранное