InfoCity
InfoCity - виртуальный город компьютерной документации
Реклама на сайте







Размещение сквозной ссылки

 

Доступ к базам данных средствами PHP


PHPLib - основная библиотека PHP
    Историческая справка
    Преимущества и недостатки
    Файлы local.inc и prepend.php3
    Настройка PHPLib

Уровень абстрактного представления базы данных
    Переносимость
    Режим отладки
    Обработка ошибок
    Пример использования класса DB_Sql
    Сеанс работы пользователя
    Запасной режим повторной генерации идентификатора сеанса
    Буферизация страниц
    Сериализация
    Работа с сеансами
    Функция page_open()
    Функции purl(), url() и pself()

Аутентификация
    Преимущества аутентификации средствами PHP
    Пример использования класса Auth
    Внутренние переменные класса Auth
    Управление уровнями прав доступа
    Поразрядные операции

Заключение

 


    База данных является для разработчика программного обеспечения для Web привычным инструментом. Программист должен знать язык SQL по крайней мере не хуже, чем PHP. В большинстве Web-приложений используются реляционные базы данных. В то время как новички пытаются по возможности избежать операций, связанных с запросами SQL и реляционными системами управления базами данных (РСУБД), опытный разработчик ценит те возможности, которые они предоставляют. Любая мало-мальски нетривиальная задача, например организация параллельного доступа, поиск и сортировка, обработка отношений между различными множествами данных, мгновенно становится серьезной проблемой при использовании методики хранения данных в файлах или массивах. Базы данных специально предназначены для эффективной организации и поиска информации, и нет никакой необходимости имитировать этот механизм в виде "псевдобаз".
    Эта статья представляет собой введение в теорию доступа к базам данных средствами библиотеки PHPLib и демонстрирует две из множества возможностей этой библиотеки: аутентификацию пользователя и управление правами доступа.

PHPLib - основная библиотека PHP


    Как уже отмечалось, библиотека PHPLib может значительно облегчить те задачи, с которыми программист сталкивается каждый день. Мы еще раз поднимем вопрос об основных понятиях, связанных с разработкой Web-приложений (управление сеансами работы пользователей, аутентификация, разделение кода и разметки). Библиотека PHPLib содержит целый ряд объектов, которые позволяют решать подобные задачи.
    На многих программистов, когда они впервые видят документацию и примеры, библиотека PHPLib действует устрашающе. И в самом деле, это сложная система классов, где различные объекты соотносятся и взаимодействуют отнюдь не тривиальным образом. Однако если вы уже овладели методикой разработки и написания собственных классов, то вы наверняка согласитесь с тем, что данная библиотека чрезвычайно удобна, а примеры достаточно просты для понимания.
    В настоящее время вы можете найти библиотеку PHPLib по адресу http://phplib.netuse.de/. В документации к библиотеке изложены подробные инструкции по ее установке, которые мы не будем здесь повторять. Мы будем исходить из того, что на вашем компьютере библиотека PHPLib была установлена правильно, а сценарий prepend.php3 автоматически подключается к обработке всех ваших сценариев (именно так рекомендовано в документации). Мы не стали включать в книгу описание всех функций и свойств, как это сделано в документации. Вместо этого мы попытаемся представить вам более общую картину и объяснить принципы работы с библиотекой PHPLib своими словами.

Историческая справка


    Работу над библиотекой PHPLib начали (в 1998 г.) Борис Эрдман (Boris Erdmann) и Кристиан Кёнтопп (Christian Koehntopp). Работая над неким большим проектом, они поняли, что вынуждены постоянно вновь и вновь кодировать одни и те же процедуры, и решили оформить соответствующие функции в виде библиотеки. Однако предложенное ими программное решение оказалось не слишком удачным.
    Так, им была нужна процедура входа пользователя в систему, которая не была бы основана на методике базовой аутентификации HTTP (HTTP Basic Authentication), так как указанная методика не обеспечивает должного уровня безопасности и не поддерживает дружественного пользовательского интерфейса. Для корректной аутентификации авторам проекта потребовалось реализовать процедуры управления сеансами (эту тему мы обсудили в главе 4). Поэтому они попытались создать объектно-ориентированную библиотеку управления сеансами работы пользователей и аутентификацией на основе идеи, сформулированной Карлом-Хайнцем Вильдом (Karl-Heinz Wild). Как это обычно бывает с интересными проектами сообщества Open Source, этой библиотекой постепенно стало пользоваться все большее число разработчиков, и сама библиотека быстро росла. Сегодня библиотека PHPLib содержит модули, обеспечивающие различные аспекты аутентификации и управления сеансами, а также методы создания форм ввода HTML, таблиц и деревьев.

Преимущества и недостатки


    Мы не устаем повторять, что оружие для боя всегда надо выбирать самым тщательным образом. Библиотека PHPLib предназначена для использования в проектах, работа над которыми занимает больше двух дней. Первый опыт применения этой библиотеки, разумеется, будет сопряжен для вас с дополнительным объемом работы - необходимо прочитать документацию, уяснить сущность основных концепций, еще раз удостовериться, что вы все правильно поняли.
    Библиотека PHPLib, по всей видимости, идеально подходит для проектов, в которых участвует больше одного разработчика. Она вынуждает программистов использовать одни и те же интерфейсы и ориентироваться на работу с объектами, что, по крайней мере, позволяет хорошо структурировать приложение. Кроме того, поскольку библиотека PHPLib требует от разработчиков знания современных концепций PHP и Web-приложений, программисты должны позаботиться о том, чтобы их познания в соответствующих областях оказались на должном уровне.
    Библиотека написана на стандартном языке PHP, поэтому она работает медленнее, чем модули расширения PHP для языка Си. С другой стороны, это делает ее более гибкой. Поскольку библиотеки PHP пишутся в форме классов, в них легко вносить изменения, подстраивая функции библиотеки под свои потребности. Более того, вам придется вносить изменения, так как библиотека PHPLib не является коммерческим продуктом, готовым к употреблению. Имейте в виду, что вам поневоле придется добавить к ней ряд функций.
    Характер зависимостей между различными классами библиотеки PHPLib довольно сложен. Так, вы не можете использовать функции управления сеансами, не обращаясь к уровню абстрактного представления баз данных. Если вам нужны только функции управления сеансами, то, может быть, лучше использовать встроенные функции работы с сеансами языка PHP 4.0 или последовать нашим рекомендациям, ориентированным на пользователей пакета PHP в версии 3.0. Тем не менее, даже если это ваш случай, обязательно прочитайте данную главу до конца: библиотека PHPLib располагает возможностями, которые наверняка облегчат вашу жизнь.

Файлы local.inc и prepend.php3


    В дистрибутиве PHPLib имеются два файла: local.inc и prepend.php3, - содержимое которых вам, вероятно, придется изменить.
    Файл сценария prepend.php3 загружает файлы, которые должны быть доступны для всех страниц, где используются средства библиотеки PHPLib. По умолчанию он обеспечивает загрузку интерфейса внутренних компонентов базы данных MySQL и использует для управления сеансом контейнер данных SQL. Для перехода от MySQL, скажем, к Postgres требуется изменить следующую строку таким образом, чтобы вместо имени файла db_mysql.inc в ней оказалась ссылка на файл db_pgsql.inc.

  require($_PHPLIB["libdir"] . "db_mysql.inc"); /*измените эту строку, 
           чтобы она соответствовала вашей базе данных */ 
      

    Если вы используете какие-то другие классы из библиотеки PHPlib, например Template, рекомендуется включать их в файл prepend.php3.
    Файл local.inc находится там, где осуществляется настройка PHPLib.

Настройка PHPLib


    В большинстве случаев базовые классы библиотеки PHPLib не используются непосредственно, но на их основе вы можете определять собственные производные классы, отвечающие вашим потребностям. Наряду с этим в библиотеке PHPLib существуют классы, для которых определена реализация по умолчанию. С некоторыми из них мы будем сталкиваться в примерах. Реализация по умолчанию всегда базируется на определенных допущениях, касающихся характеристик вашей системы. В частности, предполагается, что вы используете базы данных в формате MySQL. Если это не соответствует действительности, вам необходимо внести соответствующие изменения в файл local.inc; мы рекомендуем создать новый объект как расширение DB_Sql. Последовав нашему совету, вы избежите необходимости вновь и вновь задавать значения соответствующих параметров настройки для каждого создаваемого приложения: вы четко установите их в своем классе раз и навсегда.
    Кроме того, мы рекомендуем вам приспособить файл local.inc к своим потребностям, - по крайней мере, измените имя класса Example_Session. Это имя используется при управлении сеансами работы пользователей для обозначения файла персональных Интернет-настроек и включается в строку адреса URL в режиме выполнения запроса GET. Если в вашем адресе URL появится подстрока Example_Session, это будет выглядеть в высшей степени непрофессионально.

Уровень абстрактного представления базы данных


    Уровень абстракции, или абстрактного представления, базы данных - это интерфейс, который предоставляет ряд функций для работы с различными базами данных независимо от способа их реализации. Изменив внутренний интерфейс уровня абстракции базы данных, можно легко перейти, например, с формата MySQL на формат Oracle. В языке Perl таким уровнем является DBI (DataBase Interface), а в библиотеке PHPLib - уровень абстрактного представления базы данных, организованный в классе DB_Sql.

Переносимость


    Для профессионального программиста, создающего Web-приложения, возможность абстрактного представления базы данных может быть очень полезной и важной. Основой каждого приложения является некая модель данных - множество структур данных, ориентированных на решение наиболее общих задач. Модель данных во многих случаях реализуется непосредственно в базе данных. Пакет PHP поддерживает множество различных баз данных; каждая из них располагает своим интерфейсом прикладных программ. На основе такого программного интерфейса невозможно обеспечить разработку программ, независимых от конкретной базы данных и операционной системы. Если вы не будете использовать некий уровень абстракции, примером которого может служить библиотека PHPLib, то при переводе приложения с MySQL на Oracle вам придется проделать тяжелую работу. В табл. 6.1 представлены различия между программными интерфейсами баз данных в различных системах.
    Конечно, вы не меняете СУБД каждую неделю, поэтому данный вопрос может иметь для вас не очень большое значение. Даже если вы работаете с библиотекой PHPLib, но изначально не предусмотрели возможность переноса базы данных в другую систему, переносимость останется для вас несбыточной мечтой. Как показывает опыт, проблема заключается не в переносе программного интерфейса, а в переносе специфических функций работы с базами данных. Вы можете написать по-настоящему переносимый код для работы с базой данных, только если полностью откажетесь от применения специфических функций реляционных СУБД, но в этом случае вы кончите тем, что самостоятельно напишете аналогичные функции, иначе ваша программа окажется невыносимо медлительной и из рук вон плохо управляемой.

Таблица 6.1. Интерфейсы прикладных программ, реализованные в СУБД MySQL и Oracle

Описание

MySQL

Oracle 7

Соединить (connect) mysql_connect() ora_logon()
Запрос (Query) mysql_query() или mysql_db_query() ora_parse(), затем ora_exec()
Получить следующую строку в результирующем наборе записей mysql_fetch_array() Работает со смещениями: ora_columnname(), ora_getcolumn()
Получить количество строк в результирующем наборе записей mysql_num_rows() Невозможно, потому что Oracle возвращать строки до того, как узнает общее количество строк в результирующем файле
Получить ключевой идентификатор последней вставленной строки mysql_insert_id() Соответствующая функция в пакете PHP отсутствует

    Если вашей целью является создание переносимого кода, вы должны за километр обходить все специфические черты конкретных баз данных. Библиотека PHPLib может несколько упростить вашу задачу: в ней, к примеру, предусмотрены механизмы встроенной последовательной обработки записей и блокировки таблиц, которые не зависят от базы данных. Недавно разработчики библиотеки PHPLib добавили к ней класс Query, который предназначен для абстрагирования простых запросов (вставки, обновления, инструкций WHERE и некоторых других; такие запросы обычно составляют примерно 80 % общего числа запросов, адресованных базе данных), что позволяет сделать их независимыми от базы. В настоящее время данный класс работает только с СУБД MySQL и Oracle, начиная с версии 7.
    Уровень абстракции базы данных библиотеки PHPLib предоставляет еще две возможности, которые не менее важны, чем экономия времени при выполнении повседневных операций и переносимость приложений. Эти возможности рассматриваются ниже.

Режим отладки


    В классе DB_Sql предусмотрен отладочный режим, который позволяет просматривать запросы, адресуемые базе данных. Для включения отладочного режима вставьте в программу инструкцию $db->Debug = true;, которая должна выполняться после создания экземпляра класса. Мы обычно определяем глобальную константу DEBUG, которую присваиваем классу DB_Sql, как показано ниже.

  define("DEBUG", true); 
          $db->Debug = DEBUG; 
      

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

Обработка ошибок


    Библиотека PHPLib обрабатывает все ошибки, которые могут возникнуть при работе с функциями, имеющими отношение к работе с базами данных. К тому же код на основе DB_Sql обычно более компактен, так как вам не надо беспокоиться об обработке ошибок вручную.
    По умолчанию при возникновении какой-либо ошибки сценарий немедленно прекращает работу. Однако данное правило умолчания можно изменить, установив соответствующее значение переменной класса $Halt_On_Error (по умолчанию она имеет значение yes). Если задать ей значение report, библиотека будет сообщать об ошибках, не выходя из сценария. Присваивание значения no приведет к игнорированию всех ошибок. Это может вызвать нежелательные побочные эффекты, например противоречивость данных, если будет проигнорирован неверный запрос к базе; поэтому данным вариантом следует пользоваться с особой осторожностью. В промышленных приложениях сообщения об ошибках должны быть информативными и выдаваться в общепринятом формате, соответствующем корпоративным требованиям к сайту. Для использования собственного формата сообщений об ошибках вы можете создать новый класс, расширив DB_Sql и заменив в нем функцию haltmsg(). Поскольку эта функция управляет выводом всех сообщений об ошибках, то проще всего установить режим выдачи сообщений следующим образом.

  class test_db extends BD_Sql 
          { 
                function haltmsg($msg) 
                { 
            print("Ошибка базы данных: $msg
"); printf("Сообщение MySQL: %s
", $this->error);      } }

    За вывод сообщений об ошибках отвечает функция haltmsg(); фактически принятие решения о том, что следует сделать: прекратить выполнение сценария или попытаться преодолеть последствия ошибки, - эта функция оставляет на усмотрение библиотеки PHPLib. Данная функция выполняется только в том случае, когда переменная $Halt_On_Error имеет значения yes или report. Переменная $msg, передаваемая в качестве аргумента функции haltmsg(), содержит словесное описание ошибки. Следует также заметить, что вы можете использовать инструкции $this->Error и $this->Errno для получения сообщений об ошибках, генерируемых ядром базы данных.

Пример использования класса DB_Sql


    Использование класса DB_Sql лучше всего проиллюстрировать небольшим примером. Исходный код, приведенный в листинге 6.1, обращается к базе данных и выводит на экран все строки соответствующей таблицы. В этом примере используется класс Example_Db; он определяется в файле local.inc, который расширяет класс DB_Sql и показывает, каким образом программист может создавать собственные классы. Для простоты мы воспользуемся примером, позаимствованным из дистрибутива библиотеки PHPLib.

Листинг 6.1. Первый пример использования DB_Sql

   //создать экземпляр класса DB_Sql 
           $db = new Example_DB; 
             
           //подключиться к реляционной СУБД 
           $db->connect("test", "localhost", "root", ""); 
            
           //создать инструкцию SQL 
           $sql = "SELECT * FROM test"; 
            
           //выполнить запрос 
           $db->query($sql); 
            
           //просмотреть результат 
           while($db->next_record()) 
           { 
                //просмотреть хэш-таблицу $db->Records 
                while(list($key, $value) = each($db->Record)) 
                { 
            //выводить только нечисловые индексы 
            print(is_string($key) ? "$key: $value
":"");      }      print(" "); }

    В первой строке создается новый экземпляр класса DB_Sql. По умолчанию этот класс определяется в файле db_mysql.inc (загружаемом в сценарии prepend.php3); в качестве СУБД используется MySQL.
    Следующим шагом является подключение к базе данных. Разумеется, вы должны заменить константы, указанные в листинге, теми, которые вам нужны. В этом примере мы устанавливаем связь с базой данных test, находящейся на хосте localhost; имя пользователя - root, пароль отсутствует.
    Значения этих свойств можно задать и явным образом, через соответствующие переменные класса.

	   $db = new Example_Db; 
           $db->Database = 'test'; 
           $db->Host = 'localhost'; 
           $db->User = 'root'; 
           $db->Password = ''; 
      

    При вызове функции $db->query() библиотека PHPLib заметит, что соединение еще не установлено, и откроет его автоматически, используя заданные значения перечисленных переменных класса.
    Наш пример продолжает вызов функции $db->query(), которая осуществляет все необходимые операции, чтобы послать запрос базе данных. Эта функция устанавливает соединение с выбранной базой данных (если оно еще не установлено) и обрабатывает возникающие ошибки. Если мы установили значение переменной $db->Debug равным true, данная функция будет выводить строку запроса SQL перед передачей его в базу данных.
    Затем в цикле while вызывается функция $db->next_record(). Она получает очередную строку результирующего набора записей и сохраняет ее в массиве $db_Record. Если строк больше нет, то функция возвращает false, и цикл заканчивается.
    Второй цикл просматривает массив $db_Record и выводит имена полей и их содержимое. Поскольку в этом массиве в качестве ключа может выступает как числовой индекс (аналогично массивам, возвращаемым по умолчанию функцией mysql_fetch_array()), так и имя поля, мы должны удостовериться в том, что будет осуществляться вывод только тех элементов, для которых в качестве индекса используется имя поля.
    Сравните пример использования библиотеки PHPLib, содержащийся в листинге 6.1, с образцом традиционного стиля программирования, приведенным в листинге 6.2. Оба текста примерно одинаковы по размеру, но листинг 6.1 наглядно демонстрирует все преимущества использования PHPLib, о которых мы говорили выше. Изменив один файл, вы можете подключить иной уровень базы данных. Средства обработки ошибок здесь более мощные, чем во втором примере. При возникновении ошибки, изменив всего одну переменную класса, можно прервать работу библиотеки PHPLib, сообщить об этой ошибке или проигнорировать ее, в то время как традиционные методы программирования позволяют только прекратить работу приложения. Наконец, в вашем распоряжении имеется полная встроенная поддержка отладки.

Листинг 6.2. Образец реализации традиционного подхода

    //подключиться к реляционной СУБД 
    $link =mysql_connect('localhost', 'root', '') or die(mysql_error()); 
           
          //выбрать базу данных 
          $db = mysql_select_db('test') or die(mysql_error()); 
           
          //создать оператор SQL 
          $sql = "SELECT * FROM test" 
           
          //выполнить запрос 
          $res = mysql_query($sql) or die(mysql_error()); 
           
          //просмотреть множество результатов 
          while($row = mysql_fetch_array($res)) 
          { 
               //просмотреть хэш-таблицу $db->Records 
               while(list($key, $value) = each($row)) 
               { 
        
//выводить только нечисловые индексы
print(is_string($key) ? "$key: $value
" : "");      }      print("

"); }

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

 
  $res_one = mysql_connect("localhost", "root", "") or die(mysql_error()); 
  $res_two = mysql_connect("localhost", "root", "") or die(mysql_error()); 
      

    Вы могли бы подумать, что здесь два разных идентификатора соединения? Это не так, поскольку при втором вызове функции mysql_connect() интерпретатор PHP использует открытое соединение повторно. Если вы выведете значения идентификаторов соединений $res_one и $res_two, то увидите один и тот же идентификатор ресурса для обеих переменных. В результате использование функции mysql_select_db() для одного соединения меняет контекст для другого соединения. Аналогичная ситуация возникает и при использовании одной базы данных для объекта DB_Sql, а другой - для данных сеанса. К сожалению, приемлемого решения этой проблемы в настоящее время не существует.

Сеанс работы пользователя


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

Запасной режим повторной генерации идентификатора сеанса



    По умолчанию все средства управления сеансами работают через файлы персональных Интернет-настроек (cookies). Как подчеркивалось в главе 4, этот метод является предпочтительным (если он поддерживается клиентом); кроме того, это самый простой способ повторной генерации идентификатора сеанса. Но вы можете вместо него использовать методику, основанную на запросах GET/POST, изменив значение всего одной переменной - $mode. Переменная $mode определяет метод, который следует использовать в качестве основного метода повторной генерации идентификатора сеанса. Эта переменная может принимать значения cookie (установленное по умолчанию) и get.
    Библиотека PHPLib обеспечивает автоматический переход к запасному методу повторной генерации идентификатора сеанса в ходе выполнения сценария. Если значение переменной $fallback_mode установлено равным get, а режим, заданный переменной $mode (обычно это cookie), не поддерживается клиентом, то будет использоваться метод GET/POST. Наилучшим вариантом является установка значения переменной $mode равным cookie, а переменной $fallback_mode - равным get. В этом случае библиотека PHPLib попытается использовать методику на основе персональных настроек, но если это не удастся, она перейдет в запасной режим GET/POST. Библиотека PHPLib проверяет, поддерживается ли на клиентском компьютере механизм персональных настроек, следующим образом.

  1. При первом запросе к странице, поддерживаемой библиотекой PHPLib, библиотека пытается создать файл персональных настроек сеанса, одноименный с соответствующим экземпляром класса Session.

  2. Затем она повторно отправляет пользователя на ту же страницу, добавляя в качестве строки запроса идентификатор сеанса:
        header("Location: ".$PROTOCOL. "://" .HTTP_HOST.$this->self_url());

  3. Библиотека PHPLib проверяет, существует ли данный идентификатор сеанса в массиве $HTTP_COOKIE_VARS. Если да, то сеанс проходит в режиме cookie. В противном случае сеанс переключается в режим get.

Буферизация страниц


    Класс Session предоставляет возможность управлять механизмом буферизации страниц. Переменная этого класса $allow_cache принимает значения no, private и public. Правила умолчания в данном случае зависят от конкретной версии библиотеки PHPLib. Скажем, в версии 7.2 значение этой переменной по умолчанию равно private, а в последующих версиях - no. Методика кэширования страниц, предлагаемая библиотекой PHPLib, очень похожа на механизм буферизации, предоставляемый функциями сеанса в PHP 4.0

Сериализация


    В системе PHP 3.0 сериализовать объекты сложно. Функция serialize() неправильно сохраняет методы класса, а вручную это сделать невозможно. В механизме поддержки объектов в программе PHP 3.0 отсутствовало одно важное средство - интроспекция. Нельзя было получить имя класса и имя его родительского класса. Поэтому при разработке библиотеки PHPLib надо было использовать обходные маневры. В классах библиотеки были предусмотрены два дополнительных поля - $classname и $persistent_slots, - которые соответственно содержали имя класса и переменных класса, подлежащих сериализации. Зная имя класса, библиотека PHPLib имела возможность генерировать код PHP, создающий экземпляр этого класса ($class = new class), и сохранить этот код в банке данных сеанса. При повторном обращении к данным сеанса указанная инструкция выполнялась с помощью функции eval(). Помните пример с самомодифицирующимся счетчиком из главы 2? В библиотека PHPLib используется тот же самый принцип.
    При работе с пакетом PHP 4.0 подобные обходные маневры не нужны. В нем предусмотрены функции get_class() и get_parent_class(), которые обеспечивают адекватный механизм интроспекции. А функция serialize() работает с объектами значительно лучше.

Работа с сеансами


    Применение объекта сеанса библиотеки PHPLib, как правило, нисколько не сложнее, чем использование библиотеки управления сеансами пакета PHP 4.0. Это демонстрирует образец сценария, приведенный в листинге 6.3. В нем выполняются те же действия, что и в примере, который мы рассматривали в главе 4.

Листинг 6.3. Простейший пример использования класса Session библиотеки PHPLib

 
          //явным образом создать экземпляр объекта 
          $sess = new Example_Session; 
           
          //начать сеанс 
          $sess->start(); 
           
          //зарегистрировать переменную сеанса 
          $sess->register("counter"); 
           
          //инициализировать счетчик 
          if(!isset($counter)) 
          { 
            $counter = 0; 
          } 
           
          //вывести идентификатор сеанса и значение счетчик 
          printf("Идентификатор сенса: %s
", $sess->id); print("Значение счетчика: $counter"); //увеличить значение счетчика $counter++; //сохранить информацию о состоянии сеанса $sess->freeze();

    Единственным существенным различием между этим примером и примером из главы 4 является то, что в библиотеке PHPLib используется объектно-ориентированный подход.
    Так же как и библиотека управления сеансами PHP 4.0, библиотека PHPLib применяет для хранения данных сеанса модули хранения (которые в библиотеке PHPLib называются контейнерами). Имена классов контейнеров начинаются с префикса CT_. Самым распространенным способом хранения данных сеанса являются базы данных в формате SQL, но в библиотеке PHPLib предусмотрена возможность работы и с другими классами контейнеров.
    Библиотека PHPLib 7.2 содержит следующие классы контейнеров.

  • Класс CT_Sql является контейнером, используемым по умолчанию, и сохраняет данные сеанса в базе данных. В этом классе имеются следующие элементы данных.

Имя

Описание

$database_class Имя класса DB_Sql, которое следует использовать для связи с базой данных
$database_table Имя таблицы для хранения данных сеанса
$encoding_mode Эта переменная определяет способ хранения данных сеанса. Она может принимать два значения: base64 и slashes. Без особой необходимости не следует изменять значение, установленное по умолчанию (base64), - предусматривающее необходимость представления данных сеанса перед их записью в базу данных в кодировке Base64. При отладке можно использовать альтернативный метод (константа slashes), который позволяет хранить данные в таблице как обычный текст
  • С точки зрения реализованных в классе возможностей, контейнеры CT_ Split_Sql идентичны контейнерам CT_Sql. Данный класс следует использовать, если база данных не может сохранить все данные сеанса в одном поле, особенно в тех случаях, когда в базе возникают проблемы с обработкой больших двоичных объектов (BLOBs). Класс CT_Split_Sql несовместим с таблицами CT_Sql.

    Чтобы изменить размер частей, на которые класс разбивает данные сеанса, можно использовать переменную $split_length, по умолчанию ее значение равно 4096 (4 Кбайт).

  • Контейнер CT_Shm записывает данные сеанса в совместно используемую память. Чтобы с этим классом можно было работать, требуется, чтобы пакет РHP был скомпилирован с включением режима поддержки совместно используемой памяти. Контейнеры CT_Shm работают быстрее, чем другие виды контейнеров, так как позволяют осуществлять прямой доступ к хранимым данным. Однако у таких контейнеров есть один существенный недостаток: если вам по какой-либо причине потребуется перезагрузить сервер, все данные сеанса будут потеряны. Кроме того, из-за большого расхода памяти ограничено число параллельных сеансов. Каждый сеанс получает определенное количество памяти (оно зависит от количества и размера переменных сеанса), и когда ресурсы памяти будут исчерпаны, новый сеанс создать не удастся.

    Переменные этого класса отличаются от переменных класса CT_Sql.

Параметр

Описание

$max_sesions Максимальное количество одновременных активных сеансов. По умолчанию - 500
$shm_key Уникальный ключ сегмента совместно используемой памяти. Важно, чтобы он был уникальным для каждого приложения
$shm_size Размер сегмента совместно используемой памяти (в байтах). Приблизительно его можно рассчитать по формуле shn_size = = max_sessions * session_size, где размер сеанса в среднем имеет значение примерно 600 байт. Значение по умолчанию равно 64 000 (64 Кб)
  • Контейнер CT_Dbm использует для хранения данных сеанса файл в формате DBM операционной системы UNIX. В базах данных такого типа информация сохраняется в виде пар "ключ - значение" средствами обычной файловой системы. Единственной переменной, которой надо присвоить соответствующее значение (имя вашего файла DBV), является $dbm_file. Указанный файл обязан существовать; к нему должен быть установлен соответствующий режим доступа; серверу требуется доступ для записи к этому файлу.
  • Контейнер CT_Ldap позволяет хранить данные на сервере LDAP (Light Directory Access Protocol, облегченный протокол доступа к каталогам). Чтобы получить возможность использовать контейнеры подобного типа, скомпилируйте пакет PHP с включенной поддержкой протокола LDAP. Класс CT_Ldap располагает следующими свойствами.

Свойство

Описание

$ldap_host, $ldap_port Имя хоста и номер порта сервера LDAP
$rootdn, $rootpw Имя и пароль, используемые для связи с сервером LDAP
$basedn Под этим именем будут храниться данные сеанса
$objclass Имя объектного класса (его можно сопоставлять с именем соответствующей таблицы SQL)

    Если вы заглянете в файл local.inc, то увидите ряд определений классов. Три из них имеют непосредственное отношение к нашему примеру.

 
          class DB_Example extends DB_Sql { 
         var $Host = "localhost"; 
         var $Database= "phplib"; 
         var $User = "tobias"; 
         var $Password = "justdoit"; 
          } 
           
        class Example_CT_Sql extends CT_Sql{ 
         var $database_class = "DB_Example"; ##к какой базе данных 
         ##подключаться 
         var $database_table = "active_sessions"; ##здесь следует 
         ##искать наши данные 
          } 
           
          class Example_Session extends Sesion{ 
         var $classname = "Example_Session"; 
         var cookiename = ""; ##по умолчанию будет использоваться имя 
         ##класса 
         var $magic = "Hocuspocus"; ##исходное значение 
         ##идентификатора 
         var $mode = "cookie"; ##повторная генерация идентификатора 
         ## сеанса производится через файл 
         ##персональных настроек 
         var $fallback_mode = "get"; 
         var $lifetime = 0; ##0-работа с персональными 
         ##настройками, иначе - время (в минутах), 
         ##по истечении которого информация о сеансе 
         ##считается устаревшей 
         var $that_class = "Example_CT_Sql"; ##имя контейнера 
         ##для хранения данных 
         var $gc_probability = 5; 
          } 
      

    Как нетрудно видеть, между классами возникают определенные отношения: в классе Example_Session переменная $that_class получает имя класса Example_CT_Sql, а переменная $database класса Example_CT_Sql указывает на класс DB_Sql. Рис. 6.1 иллюстрирует эти отношения.


Рис. 6.1. Модель отношений между классами DB_Sql, CT_Sql и Session

    В нашем примере использовались не сами базовые классы, но их расширенные версии, определенные в local.inc. Соответствующие зависимости представлены на рис. 6.2.


Рис. 6.2. Зависимости между классами и их отношения в нашем примере

Функция page_open()


    Предположим, вы разрабатываете большое приложение, в котором используются механизмы управления сеансами и абстрактного представления базы данных, а также функции аутентификации и определения прав доступа библиотеки PHPLib. Для этого вам требуется создать экземпляры объектов сеанса, аутентификации и разрешения на доступ. Ваш локальный файл local.inc будет выглядеть примерно так:

 
          $sess = new Session_Example; 
          $sess->start(); 
           
          $auth = new Auth_Example; 
          $auth->start(); 
           
          $perm = new Perm_Example; 
          $user = new User_Example; 
          $user->start(); 
      

    Классы не являются независимыми друг от друга, поэтому их нужно инициализировать в правильном порядке; нельзя создать экземпляр объекта User_Example, если нет экземпляров объектов сеанса и аутентификации. И это еще не все. Способ завершения программы зависит от того, какой класс вы открыли первым; порядок, в котором вызываются программы освобождения ресурсов, занятых классами, также имеет значение.
    Вам помогут имеющиеся в библиотеке PHPLib функции page_open() и page_ close(). В документации они называются функциями управления страницами (page-management functions). Действительно, они могут выполнить всю работу, относящуюся к инициализации и закрытию библиотеки PHPLib. Используя эти функции, мы можем значительно сократить размер нашего примера.

 
          page_open(array("sess" => "Session_Example", 
         "auth" => "Auth_Example", 
         "perm" => "Perm_Example")); 
          [?] 
          page_close(); 
      

    В этом примере функция page_open() создает экземпляры классов Session_ Example, Auth_Example и Perm_Example с именами $sess, $auth и $perm соответственно. Теперь эти экземпляры можно использовать напрямую, например, при обращении к функции $sess->register().
    Функция page_open() должна вызываться до первой операции вывода, так как она определяет файлы персональных настроек и другие данные заголовков HTTP.

Функции purl(), url() и pself()


    Если в качестве основного режима повторной генерации идентификатора сеанса ваше приложение использует cookie, а в качестве запасного режима - get, надо ко всем ссылкам добавить идентификатор сеанса. Если вы хотите использовать другие способы распространения идентификатора сеанса, описанные в главе 4, необходимо расширить библиотеку PHPLib, чтобы она могла работать с другими значениями переменной $mode.
    Но даже задачу ручного замещения адреса URL библиотека PHPLib делает намного проще. В библиотеке предусмотрена функция url(), которая добавляет идентификатор сеанса к ссылкам, когда работа происходит в режиме get.
$link = $sess->url("script.php3");
    Если ваш сеанс находится в режиме get, то результирующая переменная $link будет выглядеть примерно так:
script.php3?Example_Session=2e4c3670ce9a143fee398aec282f960c
    Функция правильно обрабатывает каждую строку запроса; она будет работать корректно, даже если вы вызовете ее для адреса URL, в котором содержится параметр, к примеру, script.php3?foo=bar.
    Для сокращения в тексте программы представления процесса вывода окончательной ссылки можно использовать функцию purl(), которая работает так же, как функция url(), но, кроме того, выводит сгенерированный адрес URL. Сходным образом функции self_url() и pself_url() позволяют сгенерируют и вывести (последняя функция) ссылку на текущий файл.

Аутентификация


    В соответствующем разделе главы 4 мы отмечали, что методика базовой аутентификации HTTP имеет ряд недостатков, которые отсутствуют в механизме аутентификации, предусмотренном в системе PHP. В следующем разделе этот вопрос рассматривается более подробно.

Преимущества аутентификации средствами PHP


    В данном разделе мы продолжим разговор об аутентификации с того места, где остановились в главе 4. Мы уже рассмотрели недостатки метода базовой аутентификации HTTP (HTTP Basic Authentication) и отметили тот факт, что аутентификация на основе PHP этих недостатков лишена. Библиотека PHPLib содержит сложные классы для обработки данных аутентификации пользователей и управления правами доступа.
    Библиотека PHPLib аутентифицирует сеансы, а следовательно, зависит от класса Session. Для тех страниц, где требуется аутентификация, необходимо произвести следующий вызов функции page_open() для инстанцирования объекта сеанса и аутентификации.
page_open(array("sess" => "Session_Example", "auth" => "Auth_Example"));
    Поскольку данный механизм аутентификации базируется на концепции сеансов работы пользователей, он имеет ряд преимуществ, перечисленных ниже.

  • Имя пользователя и элемент аутентификации пересылаются только один раз, в начале сеанса. После успешной аутентификации сервер хранит дату аутентификации внутри сеанса и больше не передает имя пользователя и элемент аутентификации. Для сравнения: при базовой аутентификации HTTP имя пользователя и пароль передаются в заголовках HTTP каждого запроса. Однако если вы теряете сеанс в PHP, то вы теряете аутентификацию.
  • Процедура аутентификации на сервере может быть сложной. В ней может использоваться база данных или другой аналогичный механизм. Аутентификация выполняется неопределенной функцией auth_validatelogin() класса Auth. Реализация этой функции за вами.
  • Данная методика не ограничена уровнем доступа к папкам; механизм аутентификации может быть различным для отдельных файлов приложения и даже для разных уровней приложения внутри одного сценария. Какие-то части сценария можно скрыть от не имеющих к ним доступа пользователей.
  • Пользователи, не известные системе, могут зарегистрироваться перед началом процедуры входа в систему. Пользователю предлагается соответствующая форма, и библиотека PHPLib автоматически создает для нового пользователя стандартную запись в базе данных о пользователях.
  • Аутентификация работает даже в версии программы PHP на основе CGI.
  • Вы можете организовать для пользователей явный выход из системы. Это означает, что пользователи имеют возможность явным образом завершить текущий сеанс (предусмотрена кнопка выхода из системы).
  • Сеанс работы пользователя может быть завершен автоматически по истечении заданного времени ожидания. Это обеспечивает дополнительный уровень защиты данных приложения, поскольку вы можете по истечении времени ожидания предотвратить несанкционированный доступ к данным сеанса.

Пример использования класса Auth


    В примере, приведенном в листинге 6.4, вы встретитесь с заданным по умолчанию в библиотеке PHPLib видом экрана регистрации, или входа пользователя в систему. Именно так будет выглядеть экран регистрации при первом обращении к нему. Войдите в систему с заданными в библиотеке PHPLib по умолчанию именем пользователя и паролем (kris/test). По завершении аутентификации вы увидите идентификатор сеанса, ваше пользовательское имя и права доступа.

Листинг 6.4. Простейший пример работы с экраном аутентификации

 
  page_open(array("sess" => "Session_Example", "auth" => "Auth_Example")); 
           
      printf("Идентификатор сеанса: %s 
      \n", $sess->id); 
      printf("Идентификатор пользователя: %s
\n", $auth->auth["uid"]); printf("Ваше входное имя: %s
\n", $auth->auth["uname"]); printf("Ваши права доступа: %s
\n", $auth->auth["perm"]); page_close()

    Все страницы, использующие механизм аутентификации, предусмотренный в библиотеке PHPLib, придерживаются этой общей структуры. Сначала вызывается функция page_open(); остальная часть сценария будет выполняться только после входа пользователя в систему и завершения его аутентификации. Вы вполне можете быть уверены в том, что ни один пользователь, если он не прошел регистрацию, не увидит ничего, расположенного в тексте сценария после вызова функции page_open(). Написав всего одну строку кода, вы обеспечиваете в своем сценарии полную аутентификацию пользователя. После того как вы решили, какие классы вы хотите включить в приложение, библиотеку PHPLib использовать действительно просто. До сих пор во всех примерах мы применяли готовые классы, поставляемые в составе библиотеки PHPLib. Однако вам придется создавать свои собственные классы (производные от базовых), соответствующие вашим задачам. Для этого необходимо лучше разобраться в принципах работы библиотеки PHPLib.

Внутренние переменные класса Auth


    Если вы пользуетесь ядром СУБД MySQL, таблица пользователей будет строиться по приведенной ниже схеме.

 
          CREATE TABLE auth_user{ 
         user_id varchar(32) NOT NULL, 
         username varchar(32) NOT NULL, 
         password varchar(32) NOT NULL, 
         perms varchar(255), 
         PRIMARY KEY (user_id), 
         UNIQUE k_username (username) 
          }; 
      

    Первичным ключом является поле user_id, потому что внутренние процедуры библиотеки PHPLib работают именно с идентификационным номером пользователя, а не с парой значений "имя пользователя - пароль" (username/password). Пользовательский идентификатор (который в библиотеке PHPLib называется uid) представляет собой уникальную строку, аналогичную идентификатору сеанса, которая создается функциями uniqid() и md5(), как показано ниже.
$uid = md5(uniqid($hash_secret));
    Почему в библиотеке PHPLib не используется составной первичный ключ, включающий в себя поля username и password? Ведь в этом случае нам удалось бы сэкономить место за счет того, что отпадает необходимость в дополнительном поле user_id. Причина заключается в том, что основными задачами библиотеки PHPLib являются обеспечение возможности применения произвольной процедуры аутентификации и упрощение интерфейса. Наличие у каждого пользователя отдельного уникального идентификатора фиксированной длины упрощает создание дополнительных таблиц, привязанных в модели связей базы данных к таблице auth_user.
    В последнем примере мы просто использовали заданную по умолчанию реализацию класса Auth, которая предлагается дистрибутивом библиотеки PHPLib в сценарии Example_Default_Auth. Вам почти всегда придется создавать собственные классы, расширяя базовый класс Auth. В исходном виде класс Auth не используется, потому что в нем нет двух функций, необходимых для аутентификации. Класс Auth не знает, какой вы хотите использовать экран регистрации и как именно вы хотите осуществлять аутентификацию. Вы должны сами определять соответствующие функции в производных классах. В листинге 6.5 представлен пример такого производного класса; этот класс мало чем отличается от образца реализации, приведенного в файле local.inc.

Листинг 6.5. Расширение базового класса Auth

 
          require("EasyTemplate.inc.php3); 
          class My_Auth extends Auth 
          { 
               var $classname = "My_Auth"; 
               var $database_class = "DB_Example"; 
               var $database_table = "auth_user"; 
           
         function auth_lpginform() 
         { 
             //создать экземпляр шаблона 
             $tpl = new EasyTemplate("loginform.inc.html"); 
           
             //задано ли имя пользователя? Если да, это 
          значит, 
             //что первая попытка аутентификации не удалась 
             if(isset($this->auth["uname"])) 
             { 
                  $tpl->assign("USERNAME", 
          $this->auth["uname"]); 
                 $tpl->assign("MESSAGE", 
          "Либо имя, либо пароль заданы 
                 неверно. <br> 
          Попробуйте еще раз!"); 
             } 
             else 
             { 
                 $tpl->assign("USERNAME", 
          ""); 
                 $tpl->assign("MESSAGE", 
          "Пожалуйста, введите свое имя и пароль:"); 
             } 
           
             $tpl->assign("ACTION", $this->url()); 
           
             //вывести результаты синтаксического анализа 
          шаблона 
             $tpl->easy_print(); 
         } 
           
         function auth_validatelogin() 
         { 
             //глобальные переменные формы 
             global $username, $password; 
           
         //если существует значение $username, запомнить его 
         if(isset($username)) 
         { 
             $this->auth["uname"] = $username; 
         } 
           
         //установить значение $uid равным false по умолчанию 
         $uid = false; 
           
         //выбрать строки, соответствующие введенным 
         //имени пользователя и паролю 
         $query = " 
             SELECT 
             * 
             FROM 
                 $this->database_table 
             WHERE 
                 username = '$username' 
                 AND password = '$password' 
             "; 
           
         //выполнить запрос 
         $this->db->query($query); 
           
         //если возвращена одна строка, пользователь 
         //считается прошедшим аутентификацию 
         if($this->db->num_rows() == 1) 
         { 
             $this->db->next_record(); 
           
             //задать $uid и массив $this->auth 
             $uid = $this->db->Record["user_id"]; 
             $this->auth["uid"] = $uid; 
             $this->auth["uname"] = $this->db->Record["username"]; 
         } 
           
         return($uid); 
           
               } 
          } 
      

    Две переменные класса Auth: $database_class и $database_table - используются классом как внутренние для хранения информации о сеансе и данных аутентификации. Они не влияют на процесс аутентификации. Процедура регистрации обеспечивается двумя методами класса, которые необходимо определить, - auth_ loginform() и auth_validatelogin().
    Если пользователь запрашивает защищенную страницу и еще не вошел в систему, класс Auth запускает функцию auth_loginform(). Эта функция должна вывести экран регистрации; в случае неудачной аутентификации она будет вызвана повторно. Следовательно, в ней должен быть предусмотрен механизм обработки неудачных попыток аутентификации. В нашем примере при неудачной аутентификации на экран выводится соответствующее сообщение, а поле формы, предназначенное для ввода имени пользователя, заполняется ранее введенными данными.
    Вторая функция, auth_validatelogin(), фактически представляет собой сердце класса: именно здесь выполняется процедура аутентификации. Данная функция будет вызываться тогда, когда пользователь подтверждает правильность введенной им информации в форме, предъявленной пользователю функцией auth_loginform(). Переменные формы - это глобальные переменные, они должны быть объявлены в функции в качестве глобальных до того, как программа к ним обратится. Конкретная методика аутентификации полностью зависит от вас. В приведенном примере аутентификация проводится с помощью стандартной таблицы auth_user, предложенной в дистрибутиве библиотеки PHPLib, но вы вполне можете использовать файлы типа .htaccess, сервер LDAP и т. д.
    Если аутентификация прошла успешно, функция должна вернуть правильный идентификатор пользователя и создать массив $this->array. Этот ассоциативный массив должен содержать по крайней мере два элемента: uid (уникальный идентификатор пользователя) и uname (имя пользователя, введенное им самим).
    Если вы хотите использовать механизм уровней доступа класса Perm (подробнее об этом ниже), надо задать дополнительный элемент $this->auth["perm"]. Этот элемент должен содержать права доступа пользователя в виде списка имен, разделенных запятыми без пробелов, например admin или author,editor. Обычно этот список находится в той среде данных, из которой вы получаете информацию о пользователе, - в нашем примере это база данных MySQL.
    Если в ходе аутентификации возникает ошибка, функция должна возвратить false; в этом случае функция auth_loginform() вызывается повторно. Обратите внимание, что мы устанавливаем значение $this->auth["uname"] независимо от того, насколько успешной была аутентификация. Массив $this->auth является переменной сеанса и сохраняется на протяжении всего процесса входа в систему независимо от количества попыток. В функции auth_loginform() мы проверяем, было ли введено имя пользователя, и заносим полученное имя в форму регистрации.
    Теперь, когда пользователь правильно зарегистрировался в приложении, вы точно знаете, с кем имеете дело. Для управления различными уровнями прав доступа, ассоциированных с пользователями, вы можете использовать другой класс библиотеки PHPLib - Perm.

Управление уровнями прав доступа


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

  • Супервизор может изменять в системе все, что требуется, может модифицировать пользовательскую систему и т. д.
  • Редактор может редактировать статьи и другую информацию, а также утверждать тексты, предлагаемые авторами.
  • Автор может создавать статьи и другие виды текстов, вносить их на рассмотрение редакторов, но не может сам утверждать тексты;
  • Пользователь располагает лишь правами доступа для чтения.

    Поскольку вы знаете текущего зарегистрированного пользователя и можете идентифицировать его по уникальной строке ($uid), то несложно написать функции, которые будут включать пользователя в определенную группу и предоставлять ему те или иные права доступа в зависимости от того, что это за группа. Библиотека PHPLib располагает встроенными функциями для выполнения этих действий.
    Для того чтобы можно было работать с классом Perm, вы должны добавить еще один элемент к вызову функции page_open(). Библиотека PHPLib по умолчанию предоставляет реализацию класса Perm с именем Example_Perm. Но вы уже знаете, как следует поступить, чтобы можно было использовать все возможные функции, как привести систему в соответствие со своими потребностями, - необходимо создать собственный производный клаcс в файле local.inc.
    В листинге 6.6 приведен пример использования класса Perm. Здесь реализовано несколько больше функциональных возможностей, чем действительно необходимо: наш класс позволяет выходить из системы и повторно входить в нее с другим именем пользователя (это позволяет более наглядно продемонстрировать механизм уровней прав доступа в действии); эталонный пользователь PHPLib (имя пользователя kris, пароль test) получает привилегии администратора (admin), и если вы войдете в систему под этим именем, сценарий выведет сообщение "Добро пожаловать, администратор". Поскольку в дистрибутиве библиотеки PHPLib задан единственный пользователь - администратор, - нужно создать и других пользователей, чтобы увидеть, как выглядит соответствующая страница для посетителей с иным уровнем доступа.

Листинг 6.6. Работа с классом Perm

 
   page_open(array("sess" => " Example_Session", "auth" => "Example_Auth 
          ", "perm" => "Example_Perm")); 
          if(isset($mode) && $mode == "reload") 
          { 
               $auth->unauth(); 
               print("Вы успешно вышли из системы.
");      printf('Если хотите, вы можете <a href=%s"> войти повторно.',      е$sess->url(basename($PHP_SELF))); } else {      if($perm->have_perm("admin"))      { print("<b>Добро пожаловать, администратор.</b><br>"); print('Вы вошли в систему с уровнем доступа "admin".<br>');      }      else      { printf(Вы вошли в систему с уровнем доступа "%s".<br>', е$auth->auth["perm"]);      }      printf("Ваше имя: $s<br>", $auth->auth["name"]);      printf('<a href=%s">Log out</a>',      е$sess->url(basename($PHP_SELF)."?mode=reload")); } page_close();

Поразрядные операции


    Поразрядные вычисления часто вызывают большие затруднения у программистов-новичков, и даже опытные разработчики периодически испытывают трудности при работе с ними.
    Однако представление значений флагов с помощью отдельных битов часто бывает очень удобным и полезным способом организации информации. Так, в библиотеке PHPLib этот прием используется для уровней прав доступа. Часто для хранения значений флагов используется целочисленное поле (INT) базы данных. Рассмотрим приложение, которое должно обеспечивать возможность обработки и сохранения неопределенного количества данных, например обо всех увлечениях пользователя. Вместо того чтобы создавать в базе данных отдельные поля для каждого определенного в программе вида хобби и устанавливать их значения равными false или true, можно применить единое поле, состоящее из битовых флагов. В зависимости от того, входит ли некоторое хобби в число увлечений пользователя, соответствующий бит устанавливается или сбрасывается - "включается" или "выключается".
    Поразрядные операции - это операции, которые выполняются над одним или несколькими битами. Вы знаете, что в двоичной системе счисления используется понятие октета битов - последовательности из восьми двоичных цифр (нулей или единиц). Десятичное число 42 в двоичной системе представляется как 00101010:
Позиция бита: 7 6 5 4 3 2 1 0
Значение бита: 0 0 1 0 1 0 1 0
    Самый правый бит, нулевой, называется младшим значащим битом (разрядом). Бит № 7 называется старшим значащим битом (разрядом). Чтобы преобразовать число из двоичной системы в десятичную и наоборот, можно использовать функции языка PHP BinDec() и DecBin() соответственно.
    Двоичные операторы устанавливают и снимают определенные биты в октетах.

Установка значения бита


    Чтобы установить определенный бит, используется операция включающей дизъюнкции (значение | значение). Допустим, текущее значение флага $value равно 3 (это означает, что установлен нулевой и первый биты), а вы хотите дополнительно установить второй бит (не забывайте, что нумерация начинается с нуля). Тогда можно воспользоваться операцией включающей дизъюнкции текущего значения флага с константой 4 (2 во второй степени):
$value = 3;
$value |= 4;
    Теперь значение стало равно 7. В двоичном виде операция выглядит следующим образом:
0 0 0 0 0 1 1
| 0 0 0 0 1 0 0
= 0 0 0 0 1 1 1

Переключение бита


    Переключение бита - установка его значения равным 1, если он был равен 0, и наоборот, - осуществляется операцией исключающей дизъюнкции (value ^ value). Если значение равно 3, а мы хотим переключить первый бит (который равен 1, поскольку в двоичной системе счисления число 3 выглядит как 0000011), то следует написать:
$value = 3;
$value ^= 2;
    В результате получится 1. В двоичном виде:
0 0 0 0 0 1 0
^ 0 0 0 0 0 1 0
= 0 0 0 0 0 0 1

Сброс бита


    Сброс бита осуществляется очень просто: сначала нужно убедиться, что он включен, а затем обратить его. Это требует двух поразрядных операций (value & ~value). Чтобы сбросить первый бит в значении 3, можно использовать следующие строки:
$value = 3;
$value $= ~2;

Проверка значения бита


    Чтобы проверить, установлен ли бит, используется операция конъюнкции. Эта операция сравнивает два значения и возвращает 1 для тех позиций, где биты в обоих числах оказались равны 1. Скажем, чтобы проверить, установлен ли первый бит в константе 3, можно написать:
if(2 & 3)
//соответствующий код

Поразрядный сдвиг


    Для сдвига битов вправо или влево можно использовать операторы сдвига >> и << (например, значение << количество_позиций). Так, сдвиг значения 1 влево на одну позицию даст в результате двоичное значение 10:
0 0 0 0 0 0 1
<< 0 0 0 0 0 1 0
= 0 0 0 0 0 1 0
    Поразрядный сдвиг может использоваться для установки значений битов (см. пример).

Приоритет операторов


    Помните, что приоритет поразрядных операторов ниже, чем приоритет арифметических операторов. Операторы в выражении 1 + 2 | 3 будут выполняться в следующем порядке: (1 + 2) | 3. Кроме того, необходимо помнить, что поразрядные операторы имеют более низкий приоритет, чем операторы сравнения. Будьте внимательны и избегайте условий вроде if(2 & 3 != 0). Вместо проверки на то, установлен ли второй бит в значении 3 (а он установлен), в данном выражении сначала проверяется значение условия 3 != 0, а это, конечно, правда (true). В результате мы получаем выражение 2 & 1, возвращающее 0, а это совсем не то, что мы хотели.

Пример


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

  • чтение - 0
  • программирование - 1
  • сочинительство - 2
  • путешествия - 3

    В двоичном виде эти значения представлены следующим образом:

 
          Чтение: 0 0 0 0 0 0 1 
          Программирование: 0 0 0 0 0 1 0 
          Сочинительство: 0 0 0 0 1 0 0 
          Путешествия: 0 0 0 1 0 0 0 
      

    Определить виды увлечений в тексте сценария можно обычным поразрядным сдвигом:

 
          define("HOBBY_READING":, 1 << 0); 
          define("HOBBY_PROGRAMMING", 1 << 1); 
          define("HOBBY_WRITING", 1 << 2); 
          define("HOBBY_HIKING", 1 << 3); 
      

    Если пользователь выбирает в качестве хобби сочинительство и программирование, можно создать комбинацию битов при помощи оператора | (включающая дизъюнкция):
$pattern = HOBBY_WRITING | HOBBY_PROGRAMMING;
    После этого вы можете проверить, действительно ли пользователь выбрал сочинительство в качестве своего хобби, обратившись к значению соответствующего бита:
printf ("Сочинительство %s выбрано.", (HOBBY_WRITING & $pattern) ? "": "не");
    Для сброса бита, связанного с сочинительством, можно использовать следующую строку:
$pattern &= -HOBBY_WRITING;
    Проверка уровня прав доступа осуществляется с помощью функции $auth-> have_perm("admin"). В качестве параметра ей передается уровень прав доступа; в нашем примере это $auth->have_perm("admin"). Функция проверяет, имеет ли пользователь привилегию admin. Если пользователь имеет необходимые права доступа, то функция возвращает true, иначе - false.
    У каждого пользователя есть соответствующие привилегии. В реализации класса Example_Auth информация о привилегиях записывается в таблицу auth_user базы данных MySQL. Вы уже видели это поле в текстах сценариев:
perms varchar (255)
    Список допустимых уровней доступа определяется в классе, производном от Perm, в переменной класса $permissions. Для класса Example_Perm установлены следующие разрешения на доступ:

 
           var $permissions = array( 
           "user" =>1 
           "author" =>2 
           "editor" =>4 
           "supervisor" =>8 
           "admin" =>16 
           ); 
      

    Права доступа переводятся в двоичные значения внутренними функциями класса; при соответствующих вычислениях используются конъюнкция и дизъюнкция. Значения в определенном выше ассоциативном массиве определяют свою комбинацию битов для каждого уровня прав доступа. Хотя все это выглядит достаточно сложно, использование комбинации битов очень эффективно для представления подобных данных. В частности, этот механизм дает возможность работать с уровнями, наследующими права доступа более низких уровней. Если вы хорошо продумали соответствующие комбинации битов, уровень admin может автоматически получить все привилегии уровня user.
    У значений, заданных по умолчанию, подобная модель поведения отсутствует: уровень admin отличается от уровня user, и пользователь, принадлежащий к группе admin, не получит доступа к функциональным возможностям, установленным в элементе $auth->have_perm("user"). Чтобы было понятнее, посмотрим, как библиотека PHPLib вычисляет комбинации битов.

  • Допустим, определенные возможности доступны только для уровня user. Этому уровню соответствует константа 1.
  • Пусть пользователю соответствует уровень доступа admin, которому соответствует число 16.
  • Указанные операнды соединяются конъюнкцией, в результате получается 0 (проверьте сами: print(16 & 1);). Результат (0) не совпадает с константой, определенной для уровня user (требуется 1), поэтому доступ невозможен.

    Суть этих вычислений заключается в том, что библиотека PHPLib проверяет, установлен ли в комбинации разрядов, заданной в качестве аргумента функции $perm-> have_perms(), бит, соответствующий правам доступа данного пользователя. Такой механизм дает возможность определять сложные комбинации прав доступа.
    Рассмотрим другой пример. Предположим, что имеется четыре уровня прав доступа: admin, editor_in_chief, editor и author (администратор, главный редактор, редактор, автор). Вы хотите, чтобы редактор (editor) не мог вносить на рассмотрение ответственных лиц никаких материалов (это прерогатива автора - author), но все прочие группы должны наследовать права доступа, установленные для более низких уровней. Окончательно система полномочий выглядит следующим образом:

  • admin : наследует editor_in_chief, editor, author
  • editor_in_chief: наследует editor, author
  • editor
  • author

    Чтобы вычислить комбинацию битов для каждой группы пользователей, следует начать с нижнего уровня, author, с комбинации 1 (означающей, что установлен самый правый, нулевой бит). Если бы мы хотели, чтобы редактор наследовал уровень автора, мы бы использовали его комбинацию разрядов и установили следующий бит (1 | 2). Однако в данном примере мы хотим разделить права этих двух групп, поэтому мы задаем уровень editor как комбинацию разрядов, дающую значение 2 (двоичное 10).
    Теперь, если редактор запрашивает страницу, доступную только для группы author, он не получает к ней доступа:

  • Требуемый уровень (author): 1.
  • Действительный уровень (editor): 2.
  • Конъюнкция 1 и 2 (1 & 2) равна 0, а это не соответствует требуемому уровню.

    Следующий, более высокий уровень (второй бит, то есть третий справа) - это editor_in_chief, наследующий уровням editor и author. Это означает, что в комбинации разрядов уровня главного редактора должны быть установлены нулевой и первый биты, а мы дополнительно устанавливаем второй бит (2 во второй степени, или 4): 1 | 2 | 4. В результате получаем 7.
    Проверим правильность полученного результата:

  • Требуемый уровень (editor): 2.
  • Текущий уровень (editor_in_chief): 7.
  • Конъюнкция 7 и 2 (7 & 2) дает 2, то есть требуемый уровень.

    Осталось рассмотреть уровень администратора, который также наследует всем низшим уровням. Следует использовать выражение 7 | 8 (константа 8, или 2 в третьей степени, используется для установления третьего бита). Результат равен 15. Проверяем:

  • Требуемый уровень (editor_in_chief): 7.
  • Текущий уровень (admin): 15.
  • Конъюнкция 15 и 7 (15 & 7) дает нам 7, что соответствует требуемому уровню.

    Таким образом, наши права доступа определяются следующим образом:

  define("PHPLIB_PERM_AUTHOR", 1 | 0); 
        define("PHPLIB_PERM_EDITOR", 1 | 0); 
        define("PHPLIB_PERM_EDITOR_IN_CHIEF" 1 | 2 | 4); 
        define("PHPLIB_PERM_ADMIN", 1 | 2 | 4 | 8); 
         
        var $permissions = array( 
       "author" => PHPLIB_PERM_AUTHOR, 
       "editor" => PHPLIB_PERM_EDITOR, 
       "editor_in_chief" =>PHPLIB_PERM_EDITOR_IN_CHIEF, 
       "admin" => PHPLIB_PERM_ADMIN 
      
);

Заключение


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


Реклама на InfoCity

Яндекс цитирования



Финансы: форекс для тебя








1999-2009 © InfoCity.kiev.ua