Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Предмет Web-программирования.
Язык HTML. За последние годы разработки для Интернета эволюционировали от статических страниц до динамических информационных систем. Некоторое время назад создание современных Web-страниц не требовало практически ничего, кроме совершенного владения языком разметки гипертекста (Hypertext Markup Language, HTML). HTML представляет собой простой язык обработки текстов; на этом языке при помощи набора тегов (tags) создается документ, который можно просматривать специальной программой просмотра Web (browser). Так, HTML-код из листинга 1.1 создает простую Web-страницу.
Листинг 1.1. Исходный код простой Web-страницы.
<HTML>
<HEAD><TITLE>My First Web Page</TITLE></HEAD>
<BODY BGCOLOR="WHITE">
<H2><CENTER>Добро пожаловать на мою первую Web-страничку! </CENTER></H2>
</BODY></HTML>
HTML не язык программирования в том смысле, как C++ или Visual Basic; он больше напоминает средства форматирования документов с использованием управляющих последовательностей. Кодирование на HTML часто сравнивают с созданием документа в формате Microsoft Word путем набивки кодов форматирования прямо в Notepad. Очевидно, что функциональность этого крайне мала.
По многим причинам HTML бедный язык с точки зрения программирования. Во-первых, возьмем гиперссылки (hyperlinks) эти подчеркнутые и выделенные голубым цветом слова, которые Вы щелкаете, чтобы перейти к другой странице. Гиперссылка это, по сути, облагороженный оператор перехода GOTO, обеспечивающий переход к жестко указанному месту приложения. Об операторе GOTO и его недостатках написана масса статей. Жестко закодированные ссылки порождают код, который очень трудно сопровождать, и если вы когда-либо писали HTML-код, то знаете, как трудно его повторно использовать и модифицировать.
Во-вторых, HTML не предоставляет никакой реальной возможности сохранять данные в процессе работы приложения. Да и вообще, в этом случае трудно даже говорить о приложении в Web. Когда каждая страница представляет собой лишенную состояния транзакцию с сервером, как вообще можно определить, где приложение начинается и где заканчивается? Сравните это с типичным клиент-серверным приложением, о начале работы которого сигнализирует двойной щелчок значка на рабочем столе, а о конце выбор пункта Exit в меню File.
В-третьих, у HTML очень ограниченные возможности для взаимодействия. Стандартный HTML довольствуется статическими Web-страницами с текстом, рисунками и ссылками на другие страницы. Подобные узлы называют Желтыми Страницами WWW (World Wide Yellow Pages), так как их формат очень напоминает страницы телефонной книги.
Разумеется, HTML обеспечивает некоторую интерактивность при помощи встроенных элементов управления (intrinsic controls) тех самых полей ввода, которые обычно присутствуют в HTML-формах. Простые формы можно создать, например, при помощи тегов <INPUT>. Тег <INPUT> допускает применение текстовых полей (text boxes), флажков (check boxes), переключателей (radio buttons) и кнопок (buttons). Листинг 1.2 определяет HTML-форму, которая содержит текстовые поля ввода для имени, номера телефона и адреса электронной почты.
Листинг 1.2. Код для HTML-формы.
<HTML><HEAD><TITLE>Simple HTML Form</TITLE></HEAD>
<B0DY BGCOLOR="WHITE">
<FORM>
<INPUT TYPE="TEXT" NAME="txtName">Имя<P>
<INPUT TYPE="TEXT" NAME="txtPhone">Телефон<Р>
<INPUT TYPE="TEXT" NAME="txtEMail">Адрес электронной почты<Р>
</FORM></BODY></HTML>
Формы представляют собой простейшее средство взаимодействия с HTML. Пользователь заполняет ряд форм, которые затем отсылаются серверу. В процессе пересылки данные преобразуются в некий заранее определенный формат и отсылаются в текстовом виде исполняемому файлу сервера. После этого процесс на сервере может использовать полученные данные, например, для доступа к базам данных, посылки почтового сообщения или выполнения иных функций.
HTML представляет собой обычный текст, поэтому первоначально большинство разработчиков писали свои программы непосредственно в текстовых редакторах, таких как Notepad. Co временем ряд фирм предложили графические средства разработки, например, Microsoft FrontPage, дающие возможность создавать Web-страницы, не зная в явном виде HTML. Эти графические редакторы позволяют непосредственно макетировать Web-страницы без трудоемкой возни с тегами. К сожалению, мощь подобных графических редакторов оборачивается и серьезным их недостатком: у разработчиков создается впечатление, что им ни к чему изучать синтаксис и теги HTML, а между тем трудно придумать что-нибудь более далекое от истины, чем это утверждение. Если вы даже ничего больше не вынесете из этого краткого введения в HTML, то запомните хотя бы это: чтобы быть настоящим Web-разработчиком, вы должны знать HTML. Навыки редактирования страницы непосредственно в виде исходного текста позволят вам добиться желаемого эффекта независимо от того, поддерживает ли его ваш любимый графический редактор.
Программирование на стороне клиента.
В своих первых попытках повысить интерактивность HTML Web-страниц разработчики обратились к сценариям (scripting), добавляя функциональность путем комбинирования языка программирования с HTML. В результате зачастую получается странный гибрид кода и тегов, что вынуждает разработчиков вернуться к текстовым редакторам. Был введен специальный тег <SCRIPT>, который определяет раздел кода на Web-странице. Код из листинга 1.3 при помощи VBScript создает пример, выводящий приветствие «Hello, World!».
Листинг 1.3. Код на VBScript, выводящий «Hello, World!».
<HTML><HEAD>
<META HTTP-EQUIV="Content-Type content=text/html, charset=windows-1251">
<TITLE>Yet Another Hello, World! Example</TITLE>
<SCRIPT LANGUAGE="VBScript”> <!--
Sub cmdClickMe_OnClick()
MsgBox "Hello, World!"
End Sub
-->
</SCRIPT>
</HEAD>
<BODY BGCOLOR= WHITE >
<FORM>
<INPUT TYPE= BUTTON NAME= cmdClickMe VALUE="Click Me!”>
</FORM></BODY></HTML>
VBScript представляет собой язык описания сценариев, в основе которого лежит Visual Basic for Applications (VBA), популярный язык, применяемый, например, в Microsoft Office 97. VBScript это не полная версия VBA, а скорее его подмножество, которое сохраняет многие ключевые возможности VBA, но в то же время не реализует те, которые сделали бы его чересчур громоздким и небезопасным. Так, VBScript не поддерживает типы данных: все переменные объявляются как Variant.
Как и его старший брат, VBA, язык VBScript управляется событиями. Это означает, что написанный Вами код выполняется в ответ на событие (event), возникшее в результате взаимодействия пользователя с графическим интерфейсом (graphical user interface, GUI). В нашем случае GUI представляет собой Web-страницу. Так, в приведенном выше примере, когда пользователь взаимодействует с GUI, нажимая кнопку «Click Me!», это действие вызывает событие OnClick. Это событие, в свою очередь, обрабатывается кодом на VBScript, организованным в виде процедуры обработки события. Имена таких процедур имеют вид ИмяЭлементаУправления_ИмяСобытия, представляя собой произвольные комбинации из имен элементов управления и событий.
Хотя сценарии и представляют собой шаг вперед в развитии интерактивности, у них есть и определенные ограничения. Например, не все программы просмотра распознают и обрабатывают сценарии, а те, которые это делают, используют разные языки. Главным образом это касается Netscape Navigator, который не распознает VBScript, однако работает с JavaScript языком описания сценариев, первоначально разработанным для Netscape Navigator. По функциональности JavaScript очень похож на VBScript, но по синтаксису эти языки сильно различаются. В отличие от VBScript, JavaScript не поддерживает концепцию процедур обработки событий. Все процедуры в JavaScript это функции, вызываемые при помощи атрибутов событий, расположенных в HTML-теге. JavaScript-версия предыдущего примера на VBScript представлена в листинге 1.4.
Листинг 1.4. Код на JavaScript, выводящий «Hello, World!».
<HTML><HEAD>
<META HTTP-EQUIV="Content-Type" content="text/html;charset=windows-1251">
<TITLE>JavaScript Hello, World! Example</TITLE>
<SCRIPT LANGUAGE="JavaScript"> <!--
function clickme() {
alert("Hello, World!");
return true; }
-->
</SCRIPT>
</HEAD><BODY BGCOLOR="WHITE">
<FORM>
<INPUT TYPE="BUTTON" NAME="cmdClickMe"
VALUE="ClickMe!" OnClick="var rtn=clickme();">
</FORM> </BODY> </HTML>
Плохо не только то, что поддержка сценариев различается в разных программах просмотра, но и то, что сценарии не обеспечивают всей развитой функциональности, которой ожидают от языка программисты. Сценарии предоставляют подмножество тех языковых возможностей, которые обычно используют разработчики: базовые структуры и операторы циклы и ветвления. По сути, сценарии годятся только для проверки корректности введенных данных перед отсылкой формы на сервер.
Как только к возможностям программ просмотра добавляются сценарии, возрастает сложность клиентской платформы. Очевидно также, что раз отсутствует универсальный язык описания сценариев, то теряются все разрекламированные преимущества платформенной независимости Web. Для многих Web-мастеров и разработчиков постоянная война между программами просмотра за преобладание на рынке создает необходимость поддерживать две версии Web-узла: для Microsoft Internet Explorer и для Netscape Navigator.
Компоненты ActiveX. По мере совершенствования технологии программ просмотра зависимость от платформы усилилась она была привнесена компонентами ActiveX, технологией, основанной на СОМ модели многокомпонентных объектов Microsoft (Component Object Model). Компоненты ActiveX варьируются от причудливых элементов управления, таких как движки (spinners), до невизуальных компонентов, обеспечивающих доступ к базам данных или электронной почте. Подобные компоненты делают страницы в Internet Explorer более функциональными и привлекательными, но практически бесполезными в среде, не поддерживающей ActiveX, например, в Netscape Navigator.
Компонент ActiveX добавляется в Web-страницу при помощи тега <OBJECT>, однозначно определяющего компонент для программы просмотра. Приведенный ниже код, используя тег <OBJECT>, помещает на Web-страницу элемент управления ActiveX метку (label).
<OBJECT ID="Label1" WIDTH=291 HEIGHT=41 CLASSID="CLSID:978C9E23-D4B0-11CE-BF2D-00AA003F40D0"
CODEBASE="http://www.microsoft.com/activex/controls/FM20.DLL">
<PARAM NAME="ForeColor" VALUE="65408">
<PARAM NAME="VariousPropertyBits" VALUE="276824091">
<PARAM NAME="Caption" VALUE="Щелкни меня!">
<PARAM NAME="Size" VALUE="7691;1094">
<PARAM NAME="SpecialEffect" VALUE="1">
<PARAM NAME="FontEffects" VALUE="1073741827">
<PARAM NAME="FontHeight" VALUE="480">
<PARAM NAME="FontCharSet" VALUE="204">
<PARAM NAME="ParagraphAlign" VALUE="3">
<PARAM NAME="FontWeight" VALUE="700">
</OBJECT>
Значения GUID в любой операционной системе хранятся в реестре централизованной базе данных, которая отвечает за поддержание информации о программных объектах, используемых приложениями. Когда Internet Explorer обнаруживает тег <OBJECT>, он обращается к реестру и ищет там GUID, совпадающий со значением атрибута CLASSID. Когда такой GUID найден, из реестра выбирается дополнительная информация, позволяющая отыскать файл, который соответствует данному элементу управления ActiveX.
В теге <OBJECT> можно выделить несколько ключевых составных частей, которые определяют, как именно компонент ActiveX будет размещен на странице. Атрибут ID задает имя элемента управления, посредством которого ко всем его свойствам, методам и событиям можно будет получить доступ из текста сценария.
CLASSID представляет собой буквенно-цифровой код, который однозначно идентифицирует данный компонент ActiveX среди всех остальных. Этот код, известный как глобально уникальный идентификатор (Globally Unique Identifier, GUID), не использует больше ни один компонент ActiveX. При помощи GUID Internet Explorer однозначно определяет требуемый компонент и создает его на странице. Если нужный элемент управления ActiveX на клиентской машине отсутствует, Internet Explorer обращается к атрибуту CODEBASE за информацией о том, где находится этот элемент на сервере. Следуя этой информации, файлы данного элемента управления загружаются с сервера, и элемент устанавливается на клиентской машине. Теперь Internet Explorer может свободно работать с ним.
Доступ к компонентам ActiveX посредством тега <OBJECT> не ограничивается элементами управления. Этот тег может активизировать произвольный компонент ActiveX, в том числе и те компоненты, которые можно написать на языках Visual Basic, C++ и Microsoft FoxPro. В сущности, вы легко можете расширить функциональность, доступную клиенту, написав свои собственные компоненты ActiveX и загрузив их в программу просмотра. Следует, правда, помнить, что Internet Explorer по умолчанию не загружает и не выполняет компоненты без цифровой подписи разработчика.
Окончательное обеспечение компонента данными происходит через тег <PARAM>, имеющий атрибуты NAME и VALUE, при помощи которых задаются начальные значения свойств данного компонента, когда он впервые создается на Web-странице. После того, как начальные значения установлены, значения свойств легко изменить во время выполнения из текста сценария.
<SCRIPT LANGUAGE="VBScript"><!--
Sub Label1_DblClick(Cancel)
Label1.Font.Weight=24
Label1.Caption="Щелкни снова!"
end sub
Sub Label1_Click()
Label1.AutoSize = false
Label1.Font.Weight = 30
Label1.Caption="Еще два раза!!!!!"
Label1.SpecialEffect=1
end sub
-->
</SCRIPT>
Документы ActiveX. Visual Basic, начиная с версии 5.0, позволяет, помимо элементов управления ActiveX, создавать документы ActiveX. Документы ActiveX представляют собой программные объекты, которые могут загружаться и работать внутри ActiveX-контейнера, такого как Internet Explorer. Документы ActiveX позволяют разработчикам на Visual Basic немедленно применить свой опыт работы на Visual Basic для создания приложений для Интернета. Что самое существенное, документы ActiveX предоставляют доступ к большей части ключевых возможностей Visual Basic в загружаемом формате.
Java. Не будем забывать и о Java! Этот язык очень быстро приобрел популярность, и его поддерживают как Internet Explorer, так и Netscape Navigator. Апплеты, разработанные на Java при помощи таких средств, как Microsoft J++, во многом напоминают компоненты ActiveX: это самодостаточные, загружаемые фрагменты Web-страницы. Так же, как и у компонентов ActiveX, у апплетов имеется свой особый тег <APPLET>, который дает программе просмотра указание загрузить код на Java и выполнить его. Нижеследующий код исполняет апплет на Web-странице:
<APPLET CODE="DBLBULB.CLASS" HEIGHT=35 WIDTH=26> </APPLET>
Атрибут CODE тега <APPLET> идентифицирует исходный код апплета Java практически так же, как атрибут CODEBASE определяет источник для компонента ActiveX. У апплетов могут также быть теги <PARAM>, задающие начальные значения. Во многих случаях апплеты представляют собой функциональные эквиваленты элементов управления ActiveX. Во всяком случае, языки описания сценариев могут обращаться к открытым функциям апплетов точно так же, как они обращаются к методам компонентов ActiveX.
Dynamic HTML. Да, различия между платформами не облегчают жизнь Web-разработчикам. В версии Internet Explorer 4.0 Microsoft добавила к клиентской функциональности еще одну особенность Dynamic (динамический) HTML, который позволяет посредством сценариев программно изменять теги. Это необычайно мощное средство. Код из листинга 1.5 при помощи VBScript определяет, когда указатель мыши находится поверх какого-то участка текста Web-страницы, и изменяет размер и цвет текста.
Листинг 1.5. Dynamic HTML.
<HTML><HEAD>
<META HTTP-EQUIV="Content-Type"content="text/html;charset=windows-1251">
<TITLE>Dynamic HTML</TITLE>
<SCRIPT LANGUAGE="VBScript">
Function MyFont_OnMouseOver()
MyFont.Color = "Red"
MyFont.Size = "5"
End Function
Function MyFont_OnMouseOut()
MyFont.Color = "Blue"
MyFont.Size = "4"
End Function
</SCRIPT> </HEAD>
<BODY BGCOLOR="WHITE">
<FONT ID="MyFont" FACE="ARIAL" SIZE="4" COLOR="BLUE">
Эй, укажи-ка сюда мышкой!
</BODY></HTML>
В Dynamic HTML определяется набор событий, которые можно ассоциировать с тегами HTML. Это расширяет парадигму VBScript управляемости событиями на все элементы Web-страницы теги HTML, элементы управления ActiveX; даже программа просмотра сама по себе обладает определенными событиями. Если у вас еще сохранились сомнения в необходимости глубокого владения HTML для эффективного создания Web-страниц, то предыдущий пример должен вас убедить. В нем текст на VBScript динамически изменяет атрибуты COLOR и SIZE тега <FONT> при обнаружении определенных действий с мышью. Подобный код нельзя написать, не зная в точности, что представляет собой тег <FONT>, и не понимая смысла атрибутов COLOR и SIZE. Так что прощайте, графические редакторы!
Dynamic HTML заметно увеличивает мощность Web-клиента и его интерактивность, причем не только за счет динамического стиля манипулирования, но и другими средствами. Так, он умеет располагать элементы на Web-странице. Вы можете, например, изменить изображение, просто изменив атрибуты тега <IMG>. Для изменения содержимого страницы вы можете также добавлять или удалять теги. И наконец, Internet Explorer 4.0 поддерживает привязку данных (data binding) к полям формы. Это означает, что данные из базы данных на сервере могут быть напрямую связаны с полем формы в программе просмотра Web, и тем самым будут мгновенно редактироваться и обновляться. Все это делает Dynamic HTML мощным орудием, достойным вашего внимания. Но не забывайте одну важную вещь: в настоящее время Dynamic HTML доступен только для Internet Explorer.
Программирование на стороне сервера.
Еще одна эпохальная технология это технология серверных сценариев, примером которой является Active Server Page (ASP). Эта технология позволяет создавать великолепные, не зависящие от платформ Web-страницы, которые можно просматривать любой программой просмотра. Или, если вы хотите максимально воспользоваться преимуществами зависящих от платформы технологий, таких как Dynamic HTML, то можете создавать ASP-страницы, которые могут общаться напрямую с Internet Explorer.
В своей основе ASP это сценарий, исполняемый на сервере IIS. Этот код динамически выполняется при запросе страницы, а получившийся HTML-текст отправляется программе просмотра. Посмотрите, как код в листинге 1.6 использует ASP для создания шести последовательных строк текста, набранных все увеличивающимся шрифтом.
Листинг 1.6. Web-страница с ASP.
<SCRIPT LANGUAGE="VBSCRIPT"></SCRIPT>
<HTML><HEAD>
<META HTTP-EQUIV="Content-Type" content="text/html;charset=windows-1251">
<TITLE> ASP Example</TITLE>
</HEAD>
<BODY BGCOLOR="WHITE">
<%For x = 1 to 6%>
<FONT FACE="ARIAL" SIZE=<%=X%>>ActiveX - это круто!</FONT><P>
<%Next%>
</BODY> </HTML>
В тексте примера присутствует тег <SCRIPT>, хоть он и не обязателен, но обратите внимание, что внутри скобок появился знак процента. Этот синтаксис означает, что код должен быть выполнен на сервере, перед тем как страница будет отправлена клиенту. Обратите также внимание, что знаки процента окружают все фрагменты кода на данной странице, т. е. весь он выполняется до того, как программа просмотра получит страницу. Полученный HTML-текст выглядит так:
<HTML><HEAD>
<META HTTP-EQUIV="Content-Type" content="text/html;charset=windows-1251">
<TITLE>ASP Example</TITLE></HEAD>
<BODY BGCOLOR="WHITE">
<FONT FACE="ARIAL" SIZE=1>ActiveX - это круто!</FONT>
<P><FONT FACE="ARIAL" SIZE=2>ActiveX - это круто!</FONT>
<P><FONT FACE="ARIAL" SIZE=3>ActiveX - это круто!</FONT>
<P><FONT FACE="ARIAL" SIZE=4>ActiveX - это круто!</FONT>
<P><FONT FACE="ARIAL" SIZE=5>ActiveX - это круто!</FONT>
<P><FONT FACE="ARIAL" SIZE=6>ActiveX - это круто!</FONT>
<P></BODY></HTML>
В получившемся HTML и скрывается красота ASP. Результирующая страница может содержать чистый HTML, понятный любой программе просмотра! Это делает ASP идеальным для приложений, которые должны выполняться в Интернете, где со страницей может работать любая программа просмотра. Однако ASP не ограничивается минимальным общим знаменателем, и вы можете добавлять в результирующую страницу клиентский сценарий, элементы управления ActiveX и Dynamic HTML. Таким образом, ASP гибки настолько, насколько Вы этого сами захотите.
К серверным языкам сценариев относятся также широко известные языки Perl и PHP. В отличие от ASP, они совместимы практически с любым Web-сервером, включая и IIS (PWS), которые по умолчанию поддерживают только ASP (IIS - сразу после установки, PWS - после установки свободно распространяемого модуля ASP.EXE). Существует, правда, Java-пакет Instant ASP фирмы ChillySoft, позволяющий программировать на ASP и под другими Web-серверами, но он коммерческий. В то же время весьма популярный свободно распространяемый кросс-платформенный web-сервер Apache имеет в комплекте интерпретаторы Perl и PHP. Эти интерпретаторы существуют в версиях для Windows и UNIX-совместимых ОС, поэтому также могут считаться платформно-независимыми. В общем же случае выбор серверного языка сценариев обусловлен конфигурацией web-сервера заказчика, поскольку, даже имея навыки конфигурирования web-серверов, порой невозможно добраться до сервера, на котором расположен сайт заказчика.
В рамках данного курса будут рассматриваться серверные языки сценариев ASP, Perl, PHP.
Инструменты и технологии программирования.
Минимальным набором инструментов web-программиста является текстовый редактор и браузер, под который оптимизируется сайт. При использовании серверных сценариев требуется и web-сервер, желательно такой же, как и у заказчика. Избегайте отладки сценариев на сервере клиента - ваши ошибки могут привести к его зависанию, и не всегда у вас есть права и возможность его перезагрузки! Лучше всего установить web-сервер на рабочей станции или домашнем компьютере и подключить к нему требуемый интерпретатор. Следует, правда, иметь в виду, что функциональные возможности web-серверов и интерпретаторов под разными ОС (Windows и UNIX) зачастую различны.
Что касается редактора, то можно использовать как стандартный «Блокнот» или встроенный редактор файлового менеджера FAR (желательно с плагином Colorer для подсветки тегов и операторов), так и какой-либо специализированный WYSIWYG (What You See Is What You Get) HTML-редактор. Однако среди их многообразия практически отсутствует такой, который поддерживает ОДНОВРЕМЕННО ВСЕ серверные языки сценариев, хотя поддержка клиентских языков, как правило, присутствует. Кроме того, зачастую эти редакторы преобразуют русские буквы в их коды, что существенно затрудняет последующее редактирование. Приведем небольшой перечень.
Несомненно, самым мощным, хоть и громоздким средством программирования на ASP является Microsoft Visual InterDev, входящий в комплект Microsoft Visual Studio. Будучи интегрирован со справочной системой MSDN, он позволяет быстро получить справку по любому оператору, функции или объекту. Возможен также предварительный просмотр как в окне редактора, так и в браузере по умолчанию, а также пошаговая отладка.
Для программирования на Perl, пожалуй, лучше всего подходит NetObject Fusion, обладающий обширной справкой по этому языку. Поддержкой PHP может похвастаться довольно большое количество редакторов: как небольшие EditPlus, HTML-Kit, PHPEd и UltraEdit, так и достаточно громоздкий HomeSite. Здесь не ставится цель делать их детальный обзор, желающие могут посетить раздел HTML-редакторов на http://tucows.com.
Что касается технологий программирования, то все современные языки сценариев поддерживают как классическую процедурную, так и объектно-ориентированную, хотя и в различной степени. В любом из перечисленных языков вы можете использовать встроенные и внешние объекты, их методы и свойства, но создать полноценный собственный класс можно лишь на С-подобных языках: Perl и PHP.
В рамках данного курса невозможно детально изучить все вышеперечисленные языки, поэтому необходимо привести список как печатных, так и онлайновых источников дополнительной информации, сгруппировав их по языкам:
Основные задачи.
Как и большинство акронимов, Common Gateway Interface (CGI - общий шлюзовой интерфейс) мало что говорит по сути. Интерфейс с чем? Где этот шлюз? О какой общности речь? Чтобы ответить на эти вопросы, вернемся назад и бросим взгляд на WWW в целом.
Тим Бернерс-Ли, физик, работавший в CERN, придумал Web в 1990 году, хотя план возник еще в 1988. Идея состояла в том, чтобы дать возможность легко и быстро обмениваться мультимедийными данными - текстом, изображениями и звуком - через Интернет. WWW состояла из трех основных частей: HTML, URL и HTTP. HTML - язык форматирования, используемый для представления содержания в Web. URL - это адрес, используемый для получения содержимого в формате HTML (или каком-либо ином) с веб-сервера. HTTP - это язык, который понятен веб-серверу и позволяет клиентам запрашивать у сервера документы.
Протокол HTTP.
Работа по протоколу HTTP происходит следующим образом: программа-клиент устанавливает TCP-соединение с сервером (стандартный номер порта-80) и выдает ему HTTP-запрос. Сервер обрабатывает этот запрос и выдает HTTP-ответ клиенту.
Структура HTTP-запроса. HTTP-запрос состоит из заголовка запроса и тела запроса, разделенных пустой строкой. Тело запроса может отсутствовать. Заголовок запроса состоит из главной (первой) строки запроса и последующих строк, уточняющих запрос в главной строке. Последующие строки также могут отсутствовать. Запрос в главной строке состоит из трех частей, разделенных пробелами:
Таким образом, простейший HTTP-запрос может выглядеть следующим образом:
GET / HTTP/1.0 - запрашивается корневой файл из корневой директории web-сервера.
Строки после главной строки запроса имеют следующий формат: Параметр: значение.
Таким образом задаются параметры запроса. Это является необязательным, все строки после главной строки запроса могут отсутствовать; в этом случае сервер принимает их значение по умолчанию или по результатам предыдущего запроса (при работе в режиме Keep-Alive).
Перечислим некоторые наиболее употребительные параметры HTTP-запроса:
Формат HTTP-ответа. Формат ответа очень похож на формат запроса: он также имеет заголовок и тело, разделенное пустой строкой. Заголовок также состоит из основной строки и строк параметров, но формат основной строки отличается от таковой в заголовке запроса. Основная строка запроса состоит из 3-х полей, разделенных пробелами:
Наиболее употребительные параметры http-ответа:
Некоторые типы содержимого:
Возможность пересылки через Интернет информации всех типов явилась революцией, но вскоре была обнаружена и другая возможность. Если можно переслать через Web любой текст, то почему нельзя переслать текст, созданный программой, а не взятый из готового файла? При этом открывается море возможностей. Простой пример: можно использовать программу, выводящую текущее время, так, чтобы читатель видел правильное время при каждом просмотре страницы. Несколько умных голов в National Center for Supercomputing Applications (Национальный центр разработки приложений для суперкомпьютеров - NCSA), которые создавали веб-сервер, такую возможность увидели, и вскоре появился CGI.
CGI - это набор правил, согласно которым программы на сервере могут через веб-сервер посылать данные клиентам. Спецификация CGI сопровождалась изменениями в HTML и HTTP, вводившими новую характеристику, известную как формы.
Если CGI позволяет программам посылать данные клиенту, то формы расширяют эту возможность, позволяя клиенту посылать данные для этой CGI-программы. Распространенные приложения CGI включают в себя:
Все они дают возможность соединения CGI с базой данных, что нас особенно интересует.
Спецификация CGI
Итак, что в точности представляет собой «набор правил», позволяющий CGI-программе, скажем, в Батавии, штат Иллинойс, обмениваться данными с веб-броузером во Внешней Монголии? Официальную спецификацию CGI наряду с массой других сведений о CGI можно найти на сервере NCSA по адресу http://hoohoo.ncsa.uiuc.edu/cgi/.
Есть четыре способа, которыми CGI передает данные между CGI-программой и веб-сервером, а следовательно, и клиентом Web:
С помощью этих четырех методов сервер пересылает все данные, переданные клиентом, CGI-программе. Затем CGI-программа делает свое волшебное дело и пересылает выходные данные обратно серверу, который переправляет их клиенту.
Эти данные приводятся с прикидкой на сервер HTTP Apache. Apache - наиболее распространенный веб-сервер, работающий практически на любой платформе, включая Windows 9х и Windows NT. Однако они могут быть применимы ко всем HTTP-серверам, поддерживающим CGI. Некоторые патентованные серверы, например, от Microsoft и Netscape, могут иметь дополнительные функции или работать несколько иначе. Поскольку лицо Web продолжает изменяться с невероятной скоростью, стандарты все еще развиваются, и в будущем, несомненно, произойдут изменения. Однако, технология CGI представляется устоявшейся - расплачиваться за это приходится тем, что другие технологии, такие как апплеты, ее потеснили. Все CGI-программы, которые вы напишете, используя эти сведения, почти наверное смогут работать еще долгие годы на большинстве веб-серверов.
Когда CGI-программа вызывается посредством формы - наиболее распространенного интерфейса, броузер передает серверу длинную строку, в начале которой стоит путь к CGI-программе и ее имя. Затем следуют различные другие данные, которые называются информацией пути и передаются CGI-программе через переменную окружения PATH_INFO (табл. 2-1). После информации пути следует символ «?», а за ним - данные формы, которые посылаются серверу с помощью метода HTTP GET. Эти данные становятся доступными CGI-программе через переменную окружения QUERY_STRING. Любые данные, которые страница посылает с использованием метода HTTP POST, который используется чаще всего, будут переданы CGI-программе через стандартное устройство ввода. Типичная строка, которую может получить сервер от броузера, показана в табл. 3-1. Программа с именем formread в каталоге cgi-bin вызывается сервером с дополнительной информацией пути extra/information и данными запроса choice=help - по-видимому, как часть исходного URL. Наконец, данные самой формы (текст «CGI programming» в поле «keywords») пересылаются через метод HTTP POST.
Таблица 2-1. Части строки, переданной броузером серверу
http://www.myserver.com/cgi-bin |
/formread |
/extra/information |
?choice=help |
название программы |
информация о пути |
строка запроса |
Переменные окружения
Когда сервер выполняет CGI-программу, то прежде всего передает ей некоторые данные для работы в виде переменных окружения. В спецификации официально определены семнадцать переменных, но неофициально используется значительно больше - с помощью описываемого ниже механизма, называемого HTTP_mechanism. CGI-программа имеет доступ к этим переменным так же, как и к любым переменным среды командного процессора при запуске из командной строки. В сценарии командного процессора, например, к переменной окружения FOO можно обращаться как $FOO; в Perl это обращение выглядит, как $ENV{'FOO'}; в С - getenv("FOO"); и т. д. В таблице 2-2 перечислены переменные, которые всегда устанавливаются сервером - хотя бы и в значение null. Помимо этих переменных данные, возвращаемые клиентом в заголовке запроса, присваиваются переменным вида HTTP_FOO, где FOO - имя заголовка. Например, большинство веб-броузеров включает данные о версии в заголовок с именем USER_AGENT . Ваша CGI-программа может получить эти данные из переменной HTTP_USER_AGENT .
Таблица 2-2. Переменные окружения CGI
Переменная окружения |
Описание |
CONTENT LENGTH |
Длина данных, переданных методами POST или PUT, в байтах |
CONTENT_TYPE |
Тип MIME данных, присоединенных с помощью методов POST или PUT. |
GATEWAY_INTERFACE |
Номер версии спецификации CGI, поддерживаемой сервером. |
PATH_INFO |
Дополнительная информация пути, переданная клиентом. Например, для запроса http://www.myserver.com/test.cgi/this/is/a/ path?field=green значением переменной РАTH_INFO будет /this/is/a/path. |
PATH_TRANSLATED |
То же, что PATH_INFO, но сервер производит всю возможную трансляцию, например, расширение имен типа «~account». |
QUERY_STRING |
Все данные, следующие за символом «?» в URL. Это также данные, передаваемые, когда REQUEST_METOD формы есть GEТ. |
REMOTE_ADDR |
IP-адрес клиента, делающего запрос. |
REMOTE_HOST |
Имя узла машины клиента, если оно доступно. |
REMOTE_IDENT |
Если веб-сервер и клиент поддерживают идентификацию типа identd, то это имя пользователя учетной записи, которая делает запрос. |
REQUEST_METHOD |
Метод, используемый клиентом для запроса. Для CGI-программ, которые мы собираемся создавать, это обычно будет POST или GET. |
SCRIPT_NAME |
Путь к выполняемому сценарию, указанный клиентом. Может использоваться при ссылке URL на самого себя, и для того, чтобы сценарии, ссылки на которые существуют в разных местах, могли выполняться по-разному в зависимости от места. |
SERVER_NAME |
Имя узла - или IP-адрес, если имя недоступно, машины, на которой выполняется веб-сервер. |
SERVER_PORT |
Номер порта, используемого веб-сервером. |
SERVER_PROTOCOL |
Протокол, используемый клиентом для связи с сервером. В нашем случае этот протокол почти всегда HTTP. |
SERVER_SOFTWARE |
Данные о версии веб-сервера, выполняющего CGI-программу. |
Приведем пример сценария CGI на Perl, который выводит все переменные окружения, установленные сервером, а также все унаследованные переменные, установленные командным процессором, запустившим сервер.
Листинг 2.1. Вывод значений переменных окружения.
print "Content-Type: text/html\n\n
<HTML><HEAD><TITLE></title></head><BODY>\n
<p>Переменные окружения:<p>\n";
foreach (keys %ENV) {print "$_: $ENV{$_}<br>\n" }
print "</body></html>";
Все эти переменные могут быть использованы и даже изменены вашей CGI-программой. Однако эти изменения не затрагивают веб-сервер, запустивший программу.
Передача параметров серверу.
Командная строка. CGI допускает передачу CGI-программе аргументов в качестве параметров командной строки, которая редко используется. Редко используется она потому, что практические применения ее немногочисленны, и мы не будем останавливаться на ней подробно. Суть в том, что если переменная окружения QUERY_STRING не содержит символа « = », то CGI-программа будет выполняться с параметрами командной строки, взятыми из OUERY_STRING . Например, http://www.myseruer.com/cgi-bin/finger?root запустит finger root на www.myserver.com.
Параметры командной строки чаще всего используются вместе с тегом HTML <ISINDEX> . Тег <ISINDEX> обозначает миниформу, содержащуюся в одном теге. Обнаружив тег <ISINDEX> , броузер выводит окно, в которое пользователь может ввести текст запроса. При подаче запроса (нажатии пользователем клавиши «Enter»), броузер извлекает URL из тега <ISINDEX> и обращается к нему, передавая текст запроса в качестве командной строки.
Предшествующий finger можно написать так, что при вызове без аргументов он выведет HTML-страницу с тегом <ISINDEX> . После ввода пользователем адреса finger исполнится так же, как описано.
Стандартное устройство ввода. Как сказано выше, если клиент использует для передачи информации HTTP-методы PUT или POST, длина и тип MIME этих данных помещаются в переменные CONTENT_LENGTH и CONTENT_TYPE соответственно. Передаваемые данные посылаются на стандартное устройство ввода CGI-программы. Признак конца данных может не посылаться программе, поэтому она должна взять значение переменной CONTENT_LENGTH и прочесть столько байтов, сколько в ней указано. Это основной метод передачи данных из форм, и в наших примерах мы будем почти исключительно использовать только его.
Существуют многочисленные библиотеки почти для всех языков, которые выполняют важные задачи настройки CGI-программ, в том числе определяют, каким методом - GET или POST переданы данные, и, соответственно, разбирают переменную окружения QUERY_STRING или читают с устройства стандартного ввода. Затем эти библиотеки помещают данные в легко доступные переменные. Обширный список ресурсов CGI для разных языков есть на Yahoo по адресу: http://www.yahoo.com/Computers_and_Internet/Internet/ World_Wide_Web/CGI_Common_Gateway_Interface/
Стандартное устройство вывода. Данные, посылаемые CGI-программой на стандартное устройство вывода, читаются веб-сервером и отправляются клиенту. Если имя сценария начинается с nph-, то данные посылаются прямо клиенту без вмешательства со стороны веб-сервера. В этом случае CGI-программа должна сформировать правильный заголовок HTTP, который будет понятен клиенту. В противном случае предоставьте веб-серверу сформировать HTTP-заголовок.
Даже если вы не используете nph-сценарий, серверу нужно дать одну директиву, которая сообщит ему сведения о вашей выдаче. Обычно это HTTP-заголовок Content-Type , но может быть и заголовок Location . За заголовком должна следовать пустая строка, то есть перевод строки или комбинация CR/LF.
Заголовок Content-Type сообщает серверу, какого типа данные выдает ваша CGI-программа. Если это страница HTML, то строка должна быть Content-Type: text/html. Заголовок Location сообщает серверу другой URL или другой путь на том же сервере, куда нужно направить клиента. Заголовок должен иметь следующий вид: Location: http:// www.myserver.com/another/place/.
После заголовков HTTP и пустой строки можно посылать собственно данные, выдаваемые вашей программой - страницу HTML, изображение, текст или что-либо еще. Среди CGI-программ, поставляемых с сервером Apache, есть nph-test-cgi и test-cgi, которые хорошо демонстрируют разницу между заголовками в стилях nph и не-nph, соответственно.
Важные особенности сценариев CGI. Вы уже знаете, в основном, как работает CGI. Клиент посылает данные, обычно с помощью формы, веб-серверу. Сервер выполняет CGI-программу, передавая ей данные. CGI-программа осуществляет свою обработку и возвращает свои выходные данные серверу, который передает их клиенту. Теперь от понимания того, как работают CGI-программа, нужно перейти к пониманию того, почему они так широко используются.
Нужно разобрать еще несколько важных вопросов, прежде чем создавать реально работающие программы. Во-первых, нужно научиться работать с несколькими формами. Затем нужно освоить некоторые меры безопасности, которые помешают злоумышленникам получить незаконный доступ к файлам вашего сервера или уничтожить их.
Запоминание состояния
Запоминание состояния является жизненно важным средством предоставления хорошего обслуживания вашим пользователям, а не только служит для борьбы с закоренелыми преступниками. Проблема вызвана тем, что HTTP является так называемым протоколом «без памяти». Это значит, что клиент посылает данные серверу, сервер возвращает данные клиенту, и дальше каждый идет своей дорогой. Сервер не сохраняет о клиенте данных, которые могут понадобиться в последующих операциях. Аналогично, нет уверенности, что клиент сохранит о совершенной операции какие-либо данные, которые можно будет использовать позднее. Это накладывает существенное ограничение на использование World Wide Web.
Рис. 2-1. Множественные запросы форм
Составление сценариев CGI при таком протоколе аналогично неспособности запоминать разговор. Всякий раз, разговаривая с кем-либо, независимо от того, как часто вы общались с ним раньше, вам приходится представляться и искать общую тему для разговора. Рисунок 3-1 показывает, что всякий раз, когда запрос достигает программы CGI, это совершенно новый экземпляр программы, не имеющий связи с предыдущим.
В части клиента с появлением Netscape Navigator появилось выглядящее наспех сделанным решение под названием cookies. Оно состоит в создании нового HTTP-заголовка, который можно пересылать туда-сюда между клиентом и сервером, похожего на заголовки Content-Type и Location. Броузер клиента, получив заголовок cookie, должен сохранить в cookie данные, а также имя домена, в котором действует этот соokie. После этого всякий раз при посещении URL в пределах указанного домена заголовок cookie должен возвращаться серверу для использования в CGI-программах на этом сервере.
Метод cookie используется в основном для хранения идентификатора пользователя. Сведения о посетителе можно сохранить в файле на сервере. Уникальный ID этого пользователя можно послать в качестве cookie броузеру пользователя, после чего при каждом посещении сайта пользователем броузер автоматически посылает серверу этот ID. Сервер передает ID программе CGI, которая открывает соответствующий файл и получает доступ ко всем данным о пользователе. Все это происходит незаметно для пользователя.
Несмотря на всю полезность этого метода, большинство больших сайтов не использует его в качестве единственного средства запоминания состояния. Для этого есть ряд причин. Во-первых, не все броузеры поддерживают cookie. До недавнего времени основной броузер для людей с недостаточным зрением (не говоря уже о людях с недостаточной скоростью подключения к сети) - Lynx - не поддерживал cookie. «Официально» он до сих пор их не поддерживает, хотя это делают некоторые его широко доступные «боковые ветви». Во-вторых, что более важно, cookie привязывают пользователя к определенной машине. Одним из великих достоинств Web является то, что она доступна из любой точки света. Независимо от того, где была создана или где хранится ваша веб-страница, ее можно показать с любой подключенной к Интернет машины. Однако если вы попытаетесь получить доступ к поддерживающему cookie сайту с чужой машины, все ваши персональные данные, поддерживавшиеся с помощью cookie, будут утрачены.
Многие сайты по-прежнему используют cookie для персонализации страниц пользователей, но большинство дополняет их традиционным интерфейсом в стиле «имя регистрации/пароль». Если доступ к сайту осуществляется из броузера, не поддерживающего cookie, то страница содержит форму, в которую пользователь вводит имя регистрации и пароль, присвоенные ему при первом посещении сайта. Обычно эта форма маленькая и скромная, чтобы не отпугивать большинство пользователей, не заинтересованных ни в какой персонализации, а просто желающих пройти дальше. После ввода пользователем в форму имени регистрации и пароля CGI находит файл с данными об этом пользователе, как если бы имя посылалось с cookie. Используя этот метод, пользователь может регистрироваться на персонализированном веб-сайте из любой точки света.
Помимо задач учета предпочтений пользователя и длительного хранения сведений о нем можно привести более тонкий пример запоминания состояния, который дают популярные поисковые машины. Осуществляя поиск с помощью таких служб, как AltaVista или Yahoo, вы обычно получаете значительно больше результатов, чем можно отобразить в удобном для чтения виде. Эта проблема решается тем, что показывается небольшое количество результатов - обычно 10 или 20 и дается какое-либо средство перемещения для просмотра следующей группы результатов. Хотя обычному путешественнику по Web такое поведение кажется обычным и ожидаемым, действительная его реализация нетривиальна и требует запоминания состояния. Когда пользователь впервые делает запрос поисковому механизму, тот собирает все результаты, возможно, ограничиваясь некоторым предустановленным предельным количеством. Фокус состоит в том, чтобы выдавать эти результаты одновременно в небольшом количестве, запомнив при этом, что за пользователь запрашивал эти результаты и какую порцию он ожидает следующей. Оставляя в стороне сложности самого поискового механизма, мы встаем перед проблемой последовательного предоставления пользователю некоторой информации по одной странице.
Однако если вам требуется нечто большее, чем возможность просто листать файл, то полагаться на URL бывает обременительно. Облегчить эту трудность можно через использование формы HTML и включение данных о состоянии в теги <INPUT> типа HIDDEN . Этот метод с успехом используется на многих сайтах, позволяя делать ссылки между взаимосвязанными CGI-программами или расширяя возможности использования одной CGI-программы. Вместо ссылки на определенный объект, такой как начальная страница, данные URL могут указывать на автоматически генерируемый ID пользователя.
Так работают AltaVista и другие поисковые машины. При первом поиске генерируется ID пользователя, который скрыто включается в последующие URL. С этим ID связаны один или несколько файлов, содержащих результаты запроса. В URL включаются еще две величины: текущее положение в файле результатов и направление, в котором вы хотите перемещаться в нем дальше. Эти три значения - все, что нужно для работы мощных систем навигации больших поисковых машин.
Меры безопасности
При работе серверов Интернет, будь они серверами HTTP или другого рода, соблюдение мер безопасности является важнейшей заботой. Обмен данными между клиентом и сервером, совершаемый в рамках CGI, выдвигает ряд важных проблем, связанных с защитой данных. Сам протокол CGI достаточно защищен. CGI-программа получает данные от сервера через стандартное устройство ввода или переменные окружения, и оба эти метода являются безопасными. Но как только CGI-программа получает управление данными, ее действия ничем не ограничены. Плохо написанная CGI-программа может позволить злоумышленнику получить доступ к системе сервера. Рассмотрим следующий пример CGI-программы:
Листинг 2.2. Действующий CGI-интерфейс к команде finger.
#!/usr/bin/perl -w
use CGI;
my $output = new CGI;
my $username = $output->param('username');
print $output->header, $output->start_html('Finger Output'), "<pre>", `finger $username`, "</pre>", $output->end_html;
Если запустить программу просто как finger.cgi, она выведет список всех текущих пользователей на сервере. Если запустить ее как finger.cgi?username=fred, то она выведет информацию о пользователе «fred» на сервере. Можно даже запустить ее как finger. cgi?username=bob@foo.com для вывода информации об удаленном пользователе. Однако если запустить ее как finger.cgi?username=fred;mail hacker@bar.com</etc/passwd, могут произойти нежелательные вещи. Оператор обратный штрих «`` » в Perl порождает процесс оболочки и выполняет команду, возвращающую результат. В данной программе "finger $username" используется как простой способ выполнить команду finger и получить ее результат. Однако большинство командных процессоров позволяет объединять в одной строке несколько команд. Например, любой процессор, подобный процессору Борна, делает это с помощью символа «;». Поэтому `finger fred;mail hacker@bar.com</ etc/ passwd` запустит сначала команду finger, а затем команду mail hacker@bar.com</etc/passwd, которая может послать целиком файл паролей сервера нежелательному пользователю.
Одно из решений состоит в синтаксическом анализе поступивших от формы данных с целью поиска злонамеренного содержания. Можно, скажем, искать символ «;» и удалять все следующие за ним символы. Можно сделать такую атаку невозможной, используя альтернативные методы.
Другое важное соображение, касающееся безопасности, связано с правами пользователя. По умолчанию веб-сервер запускает программу CGI с правами того пользователя, который запустил сам сервер. Обычно это псевдопользователь, такой как «nobody», имеющий ограниченные права, поэтому у CGI-программы тоже мало прав. Обычно это хорошо, ибо, если злоумышленник сможет получить доступ к серверу через CGI-программу, ему не удастся причинить много вреда. Пример программы, крадущей пароли, показывает, что можно сделать, но фактический ущерб для системы, как правило, ограничен.
Однако работа в качестве пользователя с ограниченными правами ограничивает и возможности CGI. Если программе CGI нужно читать или записывать файлы, она может делать это только там, где у нее есть такое разрешение. CGI-программа должна иметь разрешение на чтение и запись в нужном ей каталоге, не говоря уже о самих файлах. Это можно сделать, создав каталог в качестве того же пользователя, что и сервер, с правами чтения и записи только для этого пользователя. Однако для такого пользователя, как «nobody», только root имеет подобную возможность. Если вы не суперпользователь, то вам придется общаться с администратором системы при каждом изменении в CGI.
Другой способ - сделать каталог свободным для чтения и записи, фактически сняв с него всякую защиту. Поскольку из внешнего мира получить доступ к этим файлам можно только через вашу программу, опасность не так велика, как может показаться. Однако если в программе обнаружится прореха, удаленный пользователь получит полный доступ ко всем файлам, в том числе возможность уничтожить их. Кроме того, законные пользователи, работающие на сервере, также получат возможность изменять эти файлы. Если вы собираетесь воспользоваться этим методом, то все пользователи сервера должны заслуживать доверия. Кроме того, используйте открытый каталог только для файлов, которые необходимы CGI-программе; иными словами, не подвергайте риску лишние файлы.
Что еще можно почитать. «CGI Programming on the World Wide Web» издательства O'Reilly and Associates охватывает материал от простых сценариев на разных языках до действительно поразительных трюков и ухищрений. Общедоступная информация имеется также в изобилии в WWW. Неплохо начать с CGI Made Really Easy (Действительно просто о CGI) по адресу http://www.jmarshall.com/easy/cgi/.
CGI и базы данных
С начала эпохи Интернет базы данных взаимодействовали с разработкой World Wide Web. На практике многие рассматривают Web просто как одну гигантскую базу данных мультимедийной информации.
Поисковые машины дают повседневный пример преимуществ баз данных. Поисковая машина не отправляется бродить по всему Интернету в поисках ключевых слов в тот момент, когда вы их запросили. Вместо этого разработчики сайта с помощью других программ создают гигантский указатель, который служит базой данных, откуда поисковый механизм извлекает записи. Базы данных хранят информацию в таком виде, который допускает быструю выборку с произвольным доступом.
Благодаря своей изменчивости базы данных придают Web еще большую силу: они превращают ее в потенциальный интерфейс для чего угодно. Например, системное администрирование можно производить удаленно через веб-интерфейс вместо требования регистрации администратора в нужной системе. Подключение баз данных к Web лежит в основе нового уровня интерактивности в Интернет.
Одна из причин подключения баз данных к Web регулярно дает о себе знать: значительная часть мировой информации уже находится в базах данных. Базы данных, существовавшие до возникновения Web, называются унаследованными (legacy) базами данных (в противоположность неподключенным к Web базам данных, созданным в недавнее время и которые следует назвать «дурной идеей»). Многие корпорации (и даже частные лица) стоят сейчас перед задачей обеспечения доступа к этим унаследованным базам данных через Web.
Как сказано выше, только ваше воображение может ограничить возможности связи между базами данных и Web. В настоящее время существуют тысячи уникальных и полезных баз данных, имеющие доступ из Web. Типы баз данных, действующих за пределами этих приложений, весьма различны. Некоторые из них используют CGI-программы в качестве интерфейса с сервером баз данных, таким как MySQL или используют коммерческие приложения для взаимодействия с популярными настольными базами данных, такими как Microsoft Access. А другие просто работают с плоскими текстовыми файлами, являющимися самыми простыми базами данных изо всех возможных.
С помощью этих трех типов баз данных можно разрабатывать полезные веб-сайты любого размера и степени сложности.
Доступ к базам данных.
Выше мы обсуждали, зачем Web-программисту требуется обеспечить доступ к базам данных. Далее необходимо выяснить, к каким типам баз данных и какими средствами этот доступ можно обеспечить.
Самыми простыми базами данных изо всех возможных являются плоские текстовые файлы. Для доступа к ним можно использовать средства DHTML либо файловые операции, включенные в состав серверных языков сценариев. Для взаимодействия с популярными настольными базами данных, такими как хорошо известная вам СУБД Microsoft Access, используются свои технологии. Поскольку самыми популярными настольными операционными системами по-прежнему остаются представители семейства Microsoft Windows, средства доступа к настольным СУБД будут рассмотрены в теме, посвященной серверному языку сценариев Active Server Pages (ASP). Самыми же продвинутыми средствами доступа располагают серверы баз данных, такие как MS SQL или MySQL, особенностям которого и посвящена данная тема.
СУБД MySQL.
MySQL является, возможно, самым ярким программным проектом после выхода Linux. Сейчас она серьезный конкурент большим СУБД в области разработки баз данных малого и среднего масштаба. Особыми целями проектирования MySQL были скорость, надежность и простота использования. Чтобы достичь такой производительности, ее разработчик - шведская фирма ТсХ приняла решение сделать многопоточным внутренний механизм MySQL. Многопоточное приложение одновременно выполняет несколько задач - так, как если бы одновременно выполнялось несколько экземпляров приложения.
Сделав MySQL многопоточной, ТсХ дала пользователям много выгод. Каждое входящее соединение обрабатывается отдельным потоком, при этом еще один всегда выполняющийся поток управляет соединениями, поэтому клиентам не приходится ждать завершения выполнения запросов других клиентов. Одновременно может выполняться любое количество запросов. Пока какой-либо поток записывает данные в таблицу, все другие запросы, требующие доступа к этой таблице, просто ждут, пока она освободится. Клиент может выполнять все допустимые операции, не обращая внимания на другие одновременные соединения. Управляющий поток предотвращает одновременную запись какими-либо двумя потоками в одну и ту же таблицу. Такая архитектура более сложна, чем однопоточная. Однако выигрыш в скорости благодаря одновременному выполнению нескольких запросов значительно превосходит потери скорости, вызванные увеличением сложности.
Рис. 3.1. Клиент-серверная архитектура MySQL
Другое преимущество многопоточной обработки присуще всем многопоточным приложениям. Несмотря на то, что потоки совместно используют память процесса, они выполняются раздельно. Благодаря этому разделению выполнение потоков на многопроцессорных машинах может быть распределено по нескольким ЦП. На рис. 3-1 показана эта многопоточная природа сервера MySQL.
Помимо выигрыша в производительности, полученного благодаря многопоточности, MySQL поддерживает большое подмножество языка запросов SQL. MySQL поддерживает более десятка типов данных, а также функции SQL. Ваше приложение может получить доступ к этим функциям через команды ANSI SQL. MySQL фактически расширяет ANSI SQL несколькими новыми возможностями. В их числе новые функции (ENCRYPT, WEEKDAY, IF и другие), возможность инкрементирования полей (AUTO_INCREMENT и LAST_INSERT ID), а также возможность различать верхний и нижний регистры.
ТсХ намеренно опустила некоторые возможности SQL, встречающиеся в больших базах данных. Наиболее заметно отсутствие транзакций и встроенных процедур. ТсХ решила, что реализация этих возможностей нанесет слишком сильный удар по производительности. Однако ТсХ продолжает работу в этом направлении, но так, чтобы от потери производительности страдали только те пользователи, которым такие возможности действительно необходимы.
С 1996 года ТсХ использует MySQL в среде, где имеется более 40 баз данных, содержащих 10 000 таблиц. Из этих 10 000 более 500 таблиц имеют, в свою очередь, более 7 миллионов записей - около 100 Гбайт данных.
Система безопасности MySQL
Вам не только нужно иметь надежный доступ к своим данным, но и быть уверенным, что у других нет никакого доступа к ним. MySQL использует собственный сервер баз данных для обеспечения безопасности. При первоначальной установке MySQL создается база данных под названием «mysql». В этой базе есть пять таблиц: db, host, user, tables_priv, и columns_priv. Более новые версии MySQL создают также базу данных с названием func, но она не имеет отношения к безопасности. MySQL использует эти таблицы для определения того, кому что позволено делать. Таблица user содержит данные по безопасности, относящиеся к серверу в целом. Таблица host содержит права доступа к серверу для удаленных компьютеров. И наконец, db, tables_priv и соlumns_priv управляют доступом к отдельным базам данных, таблицам и колонкам.
Мы кратко рассмотрим все таблицы, поддерживающие безопасность в MySQL, а затем рассмотрим технологию их использования при обеспечении защиты ядром MySQL.
Таблица user. Таблица user имеет вид, показанный в Таблице 3.1:
Таблица 3.1. Таблица user
Поле |
Тип |
Null |
Ключ |
Значение по умолчанию |
Host |
char(60) |
PRI |
||
User |
char(16) |
PRI |
||
Password |
char(16) |
|||
Select_priv |
enum('N','Y') |
N |
||
Insert_priv |
enum('N','Y') |
N |
||
Update_priv |
enum('N','Y') |
N |
||
Delete_priv |
enum('N','Y') |
N |
||
Create_priv |
enum('N','Y') |
N |
||
Drop_priv |
enum('N','Y') |
N |
||
Reload_priv |
enum('N','Y') |
N |
||
Shutdown_priv |
enum('N','Y') |
N |
||
Process_priv |
enum('N','Y') |
N |
||
File_priv |
enum('N','Y') |
N |
||
Grant_priv |
enum('N','Y') |
N |
||
References_priv |
enum('N','Y') |
N |
||
Index_priv |
enum('N','Y') |
N |
||
Alter_priv |
enum('N','Y') |
N |
В колонках Host и User можно использовать символ «%», заменяющий произвольную последовательность символов. Например, имя узла «chem%lab» включает в себя «chembiolab», «chemtestlab» и т. д. Специальное имя пользователя «nobody» действует как одиночный «%», то есть охватывает всех пользователей, не упомянутых где-либо в другом месте. Ниже разъясняется смысл различных прав доступа:
Select_priv - Возможность выполнять команды SELECT.
Insert_priv - Возможность выполнять команды INSERT.
Update_priv - Возможность выполнять команды UPDATE.
Delete_priv - Возможность выполнять команды DELETE.
Create_priv - Возможность выполнять команды CREATE или создавать базы данных.
Drop_priv - Возможность выполнять команды DROP для удаления баз данных.
Reload_priv - Возможность обновлять информацию о доступе с помощью mysqladmin reload.
Shutdown_priv - Возможность останавливать сервер через mysqladmin shutdown.
Process_priv - Возможность управлять процессами сервера.
File_priv - Возможность читать и записывать файлы с помощью команд типа SELECT INTO OUTFILE и LOAD DATA INFILE.
Grant_priv - Возможность давать привилегии другим пользователям.
Index_priv - Возможность создавать и уничтожать индексы.
Alter_priv - Возможность выполнять команду ALTER TABLE.
В MySQL есть специальная функция, позволяющая скрыть пароли от любопытных глаз. Функция password() зашифровывает пароль. Ниже показано, как использовать функцию password() в процессе добавления пользователей в систему.
INSERT INTO user (Host, User, Password, Select_priv, Insert_priv, Update_priv, Delete_priv)
VALUES ('%', 'bob', password('mypass'), 'Y', 'Y', 'Y', 'Y')
INSERT INTO user (Host, User, Password, Select_priv)
VALUES ('athens.imaginary.com', 'jane', '', 'Y')
INSERT INTO user(Host, User, Password)
VALUES ('%', 'nobody', '')
INSERT INTO user (Host, User, Password, Select_priv, Insert_priv, Update_priv, Delete_priv)
VALUES ('athens.imaginary.com', 'nobody', password('thispass'), 'Y', 'Y', 'Y', 'Y')
И
мена пользователей MySQL обычно не связаны с именами пользователей операционной системы. По умолчанию клиентские средства MySQL используют при регистрации имена пользователей операционной системы, однако, обязательного соответствия не требуется. В большинстве клиентских приложений MySQL можно с помощью параметра -u подключиться к MySQL, используя любое имя. Ваше имя пользователя операционной системы не появится в таблице user, если не будет специально включено в нее с присвоением прав.
Первый созданный нами пользователь, «bob», может подключаться к базе данных с любого компьютера и выполнять команды SELECT, INSERT, UPDATE и DELETE. Второй пользователь, «jane», может подключаться с «athens.imaginary.com», не имеет пароля и может выполнять только SELECT. Третий пользователь - «nobody» - с любой машины. Этот пользователь вообще ничего не может делать. Последний пользователь - «nobody» - с машины «athens.imaginary.com», он может выполнять SELECT, INSERT, UPDATE и DELETE, как и пользователь «bob».
Как MySQL производит сопоставление? Некоторое имя может соответствовать на деле нескольким записям. Например, «nobody @athens.imaginary.com» соответствует и «nobody@%», и «nobody@athens.imaginary.com». Прежде чем осуществлять поиск в таблице user, MySQL сортирует данные следующим образом:
В предыдущем примере пользователь сначала будет сравниваться с «nobody» из «athens.imaginary.com», поскольку «athens.imaginary.com» в порядке сортировки стоит выше «%». Поскольку имена компьютеров сортируются раньше имен пользователей, значения привилегий для компьютера, с которого вы подключаетесь, имеют приоритет перед любыми конкретными правами, которые у вас могут быть. Если таблица user содержит записи:
Host |
User |
% |
jane |
athens.imaginary.com |
и jane подключается с «athens.imaginary.com», то MySQL будет использовать привилегии, данные «athens.imaginary.com».
Таблица db. В таблице user не упоминаются конкретные базы данных и таблицы. Таблица user управляет сервером в целом. Однако на сервере обычно находится несколько баз данных, которые служат различным целям и, соответственно, обслуживают разные группы пользователей. Права доступа к отдельным базам данных хранятся в таблице db:
Таблица 3.2. Таблица db
Поле |
Тип |
Null |
Ключ |
Значение по умолчанию |
Host |
char(60) |
PRI |
||
Db |
char(32) |
PRI |
||
User |
char(16) |
PRI |
||
Select_priv |
enum('N','Y') |
N |
||
Insert_priv |
enum('N','Y') |
N |
||
Update_priv |
enum('N','Y') |
N |
||
Delete_priv |
enum('N','Y') |
N |
||
Create_priv |
enum('N','Y') |
N |
||
Drop_priv |
enum('N','Y') |
N |
||
References_priv |
enum('N','Y') |
N |
||
Index_priv |
enum('N','Y') |
N |
||
Alter_priv |
enum('N','Y') |
N |
Эта таблица во многом похожа на таблицу user. Основное отличие в том, что вместо колонки Password имеется колонка Db. Таблица управляет правами пользователей в отношении определенных баз данных. Поскольку привилегии, указанные в таблице user, относятся ко всему серверу в целом, права, присвоенные пользователю в таблице user, перекрывают права, присвоенные тому же пользователю в таблице. Например, если пользователю в таблице user разрешают доступ типа INSERT, это право действует в отношении всех баз данных, вне зависимости от того, что указано в таблице db.
Наиболее эффективно создание в таблице user записей для всех пользователей, в которых не даны никакие права. В этом случае пользователь может лишь подключиться к серверу, не выполняя никаких действий. Исключение делается только для пользователя, назначенного администратором сервера. Все остальные должны получить права доступа через таблицу db. Каждый пользователь должен присутствовать в таблице user, иначе он не сможет подключаться к базам данных.
Те же правила, которые действуют в отношении колонок User и Host в таблице user, действуют и в таблице db, но с некоторой особенностью. Пустое поле Host вынуждает MySQL найти запись, соответствующую имени узла пользователя, в таблице host. Если такой записи не найдено, MySQL отказывает в доступе. Если соответствие найдено, MySQL определяет права как пересечение прав, определяемых таблицами host и db. Иными словами, в обеих записях разрешение должно иметь значение «Y», иначе в доступе отказывается.
Таблица host. Таблица host служит особой цели. Ее структура показана в таблице 3.3:
Таблица 3.3. Таблица Host
Поле |
Тип |
Null |
Ключ |
Значение по умолчанию |
Host |
char(60) |
PRI |
||
Db |
char(32) |
PRI |
||
Select_priv |
enum('N','Y') |
N |
||
Insert_priv |
enum('N','Y') |
N |
||
Update_priv |
enum('N','Y') |
N |
||
Delete_priv |
enum('N','Y') |
N |
||
Create_priv |
enum('N','Y') |
N |
||
Drop_priv |
enum('N','Y') |
N |
||
Grant_priv |
enum('N','Y') |
N |
||
References_priv |
enum('N','Y') |
N |
||
Index_priv |
enum('N','Y') |
N |
||
Alter_priv |
enum('N','Y') |
N |
Таблица host позволяет задать основные разрешения на межкомпьютерном уровне. При проверке прав доступа MySQL ищет в таблице db соответствие имени пользователя и его машине. Если он находит запись, соответствующую имени пользователя, поле host которой пусто, MySQL обращается к таблице host и использует пересечение обоих прав для определения окончательного права доступа. Если у вас есть группа серверов, которые вы считаете менее защищенными, то вы можете запретить для них все права записи. Если «bob» заходит с одной из таких машин, и его запись в таблице db содержит пустое поле host, ему будет запрещена операция записи, даже если она разрешена ему согласно таблице db.
Таблицы tables_priv и columns_priv. Эти две таблицы, по сути, уточняют данные, имеющиеся в таблице db. Именно, право на всякую операцию сначала проверяется по таблице db, затем по таблице tables_priv , затем по таблице columns_priv. Операция разрешается, если одна из них дает разрешение. С помощью этих таблиц можно сузить область действия разрешений до уровня таблиц и колонок. Управлять этими таблицами можно через команды SQL GRANT и REVOKE.
Последовательность контроля доступа. Соединим элементы системы защиты MySQL вместе и покажем, как можно ими пользоваться в реальных ситуациях. MySQL осуществляет контроль доступа в два этапа. Первый этап - подключение. Необходимо подключиться к серверу, прежде чем пытаться что-либо сделать.
При подключении проводятся две проверки. Сначала MySQL проверяет, есть ли в таблице user запись, соответствующая имени пользователя и машины, с которой он подключается. Поиск соответствия основывается на правилах, которые мы обсудили раньше. Если соответствие не найдено, в доступе отказывается. В случае, когда соответствующая запись найдена и имеет непустое поле Password , необходимо ввести правильный пароль. Неправильный пароль приводит к отклонению запроса на подключение.
Если соединение установлено, MySQL переходит к этапу верификации запроса. При этом сделанные вами запросы сопоставляются с вашими правами. Эти права MySQL проверяет по таблицам user, db, host, tables_pnv и columns_priv. Как только найдено соответствие в таблице user с положительным разрешением, команда немедленно выполняется. В противном случае MySQL продолжает поиск в следующих таблицах в указанном порядке:
Если таблица db содержит разрешение, дальнейшая проверка прекращается и выполняется команда. Если нет, то MySQL ищет соответствие в таблице tables_priv . Если это команда SELECT, объединяющая две таблицы, то пользователь должен иметь разрешения для обеих этих таблиц. Если хотя бы одна из записей отказывает в доступе или отсутствует, MySQL точно таким же способом проверяет все колонки в таблице columns_priv.
Изменение прав доступа. MySQL загружает таблицы доступа при запуске сервера. Преимуществом такого подхода по сравнению с динамическим обращением к таблицам является скорость. Отрицательная сторона состоит в том, что изменения, производимые в таблицах доступа MySQL, не сразу начинают действовать. Для того чтобы сервер увидел эти изменения, необходимо выполнить команду mysqladmin reload. Если таблицы изменяются с помощью SQL-команд GRANT или REVOKE, явно перегружать таблицы не требуется.
Утилиты MySQL
ТсХ распространяет MySQL с большим набором вспомогательных утилит, однако набор утилит, предлагаемых сторонними разработчиками, еще богаче.
Утилиты командной строки (Command Line Tools):
Isamchk - Производит проверку файлов, содержащих данные базы. Эти файлы называются ISAM-файлами (ISAM метод индексированного последовательного доступа). Эта утилита может устранить большую часть повреждений ISAM-файлов.
Isamlog - Читает создаваемые MySQL журналы, относящиеся к ISAM-файлам. Эти журналы можно использовать для воссоздания таблиц или воспроизведения изменений, внесенных в таблицы в течение некоторого промежутка времени.
mysql - Создает прямое подключение к серверу баз данных и позволяет вводить запросы непосредственно из приглашения MySQL.
mysqlaccess - Модифицирует таблицы прав доступа MySQL и отображает их в удобном для чтения виде. Использование этой утилиты - хороший способ изучения структуры таблиц доступа MySQL.
Mysqladmin - Осуществляет административные функции. С помощью этой утилиты можно добавлять и удалять целые базы данных, а также завершать работу сервера.
Mysqlbug - Составляет для ТсХ отчет о возникшей в MySQL неполадке. Отчет будет также послан в почтовый список рассылки MySQL, и армия добровольцев MySQL будет исследовать проблему.
Mysqldump - Записывает все содержимое таблицы, включая ее структуру, в файл в виде SQL-команд, которыми можно воссоздать таблицу. Выходные данные этой утилиты можно использовать для воссоздания таблицы в другой базе или на другом сервере. Синтаксис ее применения: mysqldump -u user -p dbname --tab=path, где path - путь для сохранения файлов.
Mysqlimport - Считывает данные из файла и вводит их в таблицу базы данных. Это должен быть файл с разделителями, где разделитель может быть любого обычного вида, например, запятая или кавычки.
Mysqlshow - Выводит на экран структуру баз данных, имеющихся на сервере, и таблицы, из которых они состоят.
Утилиты сторонних разработчиков. Ни один поставщик или разработчик не может самостоятельно предоставить все необходимые для программного продукта средства поддержки. За самым свежим списком обратитесь на домашнюю страницу MySQL: http://www.mysql.com/Contrib.
Утилиты преобразования баз данных:
access_to_mysql - Преобразует базы данных Microsoft Access в таблицы MySQL. Включается в Access в виде функции, позволяющей сохранять таблицы в формате, позволяющем экспортировать их в MySQL.
dbf2mysql - Конвертирует файлы dBASE (DBF) в таблицы MySQL. Хотя dBASE утратил популярность, формат DBF установился как наиболее распространенный для передачи данных между различными приложениями баз данных. Все главные настольные приложения баз данных могут читать и писать DBF-файлы. Это приложение полезно для экспорта/импорта данных в коммерческие настольные базы данных.
Exportsql/Importsql - Конвертирует базы данных Microsoft Access в MySQL и обратно. Эти утилиты являются функциями Access, которые можно использовать для экспорта таблиц Access в формате, пригодном для чтения MySQL. С их помощью можно также преобразовывать SQL-выход MySQL в вид, пригодный для чтения Access.
Интерфейсы CGI:
PHP - Создает HTML-страницы с использованием специальных тегов, распознаваемых анализатором РНР. РНР имеет интерфейсы к большинству основных баз данных, включая MySQL и mSQL.
Mysql-webadmin - Осуществляет веб-администрирование баз данных MySQL. Используя это средство, можно просматривать таблицы и изменять их содержимое с помощью HTML-форм.
Mysqladm - Осуществляет веб-администрирование баз данных MySQL. Эта CGI-программа позволяет просматривать таблицы через WWW, добавлять таблицы и изменять их содержимое.
www-sql - Создает HTML-страницы из таблиц баз данных MySQL. Эта программа осуществляет разбор HTML-страниц в поисках специальных тегов и использует извлеченные данные для выполнения команд SQL на сервере MySQL.
Клиентские приложения:
Mysqlwinadmn - Позволяет администрировать MySQL из Windows. С помощью этого средства можно выполнять функции mysqladmin из графического интерфейса.
Xmysql - Обеспечивает полный доступ к таблицам баз данных MySQL для клиента X Window System. Поддерживает групповые вставки и удаления.
Xmysqladmin - Позволяет осуществлять администрирование MySQL из X Window System. Это инструмент для графического интерфейса, позволяющий создавать и удалять базы данных и управлять таблицами. С его помощью можно также проверять, запущен ли сервер, перегружать таблицы доступа и управлять потоками.
Интерфейсы программирования:
MyODBC - Реализует ODBC API к MySQL в Windows.
mm.mysql.jdbc - Реализует стандартный API JDBC (Java Database Connectivity -доступ к базам данных из Java).
TwzJdbcForMysql - Реализация JDBC API для Java.
Язык SQL.
Для чтения и записи в базах данных MySQL используется структурированный язык запросов (SQL). Используя SQL, можно осуществлять поиск, вводить новые данные или удалять данные. SQL является просто основополагающим инструментом, необходимым для взаимодействия с MySQL. Даже если для доступа к базе данных вы пользуетесь каким-то приложением или графическим интерфейсом пользователя, где-то в глубине это приложение генерирует SQL-команды.
SQL является разновидностью «естественного языка». Иными словами, команда SQL должна читаться, по крайней мере на первый взгляд, как предложение английского языка. У такого подхода есть как преимущества, так и недостатки, но факт заключается в том, что этот язык очень непохож на традиционные языки программирования, такие как С, Java или Perl. Здесь мы рассмотрим язык SQL, как он реализован в MySQL.
Основы SQL. SQL «структурирован» в том отношении, что он следует определенному набору правил. Компьютерной программе легко разобрать на части сформулированный запрос SQL. Действительно, в книге издательства O'Reilly «lex & уасс», написанной Дж. Ливайном, Т. Мэйсоном и Д. Брауном (John Levine, Tony Mason, Doug Brown), реализована грамматика SQL для демонстрации процесса создания программы, интерпретирующей язык! Запрос (query) - это полностью заданная команда, посылаемая серверу баз данных, который выполняет запрошенное действие. Ниже приведен пример SQL-запроса:
SELECT name FROM people WHERE name LIKE Stac%'
Как можно видеть, это предложение выглядит почти как фраза на ломаном английском языке: «Выбрать имена из список люди, где имена похожи на Stac». SQL в очень незначительной мере использует форматирование и специальные символы, обычно ассоциируемые с компьютерными языками. Сравните, к примеру, «$++;($*++/$!);$&$",,;$!» в Perl и «SELECT value FROM table» в SQL.
История SQL. В IBM изобрели SQL в начале 1970-х, вскоре после введения д-ром Е. Ф. Коддом (Е. F. Codd) понятия реляционной базы данных. С самого начала SQL был легким в изучении, но мощным языком. Он напоминает естественный язык, такой как английский, и поэтому не утомляет тех, кто не является техническим специалистом.
SQL действительно был настолько популярен среди пользователей, для которых предназначался, что в 1980-х компания Oracle выпустила первую в мире общедоступную коммерческую SQL-систему. Oracle SQL был хитом сезона и породил вокруг SQL целую индустрию. Sybase, Informix, Microsoft и ряд других компаний вышли на рынок с собственными разработками реляционных систем управления базами данных (РСУБД), основанных на SQL.
В то время когда Oracle и ее конкуренты вышли на сцену, SQL был новинкой, и для него не существовало стандартов. Лишь в 1989 году комиссия по стандартам ANSI выпустила первый общедоступный стандарт SQL. Сегодня его называют SQL89. К несчастью, этот новый стандарт не слишком углублялся в определение технической структуры языка. Поэтому, хотя различные коммерческие реализации языка SQL сближались, различия в синтаксисе делали задачу перехода с одной реализации языка на другую нетривиальной. Только в 1992 году стандарт ANSI SQL вступил в свои права.
Стандарт 1992 года обозначают как SQL92 или SQL2. Стандарт SQL2 включил в себя максимально возможное количество расширений, добавленных в коммерческих реализациях языка. Большинство инструментов, работающих с различными базами данных, основывается на SQL2 как на способе взаимодействия с реляционными базами данных. Однако, из-за очень большой широты стандарта SQL2, реляционные базы, реализующие полный стандарт, очень сложные и ресурсоемкие.
SQL2 - не последнее слово в стандартах SQL. В связи с ростом популярности объектно-ориентированных СУБД (ООСУБД) и объектно-реляционных СУБД (ОРСУБД) возрастает давление с целью принятия объектно-ориентированного доступа к базам данных в качестве стандарта SQL. Ответом на эту проблему должен послужить SQL3. Он не является пока официальным стандартом, но в настоящее время вполне определился и может стать официальным стандартом.
С появлением MySQL проявился новый подход к разработке серверов баз данных. Вместо создания очередной гигантской РСУБД с риском не предложить ничего нового в сравнении с «большими парнями», были предложены небольшие и быстрые реализации наиболее часто используемых функций SQL.
Архитектура SQL. SQL больше напоминает естественный человеческий, а не компьютерный язык. SQL добивается этого сходства благодаря четкой императивной структуре. Во многом походя на предложение английского языка, отдельные команды SQL, называемые запросами, могут быть разбиты на части речи. Рассмотрим примеры:
CREATE |
TABLE |
people (name CHAR(10)) |
|
глагол |
дополнение |
расширенное определение |
|
INSERT |
INTO people |
VALUES('me') |
|
глагол |
косвенное дополнение |
прямое дополнение |
|
SELECT |
name |
FROM people |
WHERE name LIKE '%e' |
глагол |
прямое дополнение |
косвенное дополнение |
придаточное предложение |
Большинство реализаций SQL, включая MySQL, нечувствительны к регистру: неважно, в каком регистре вы вводите ключевые слова SQL, если орфография верна. Например, CREATE из верхнего примера можно записать и так:
cREatE ТАblЕ people (name cHaR(10))
Нечувствительность к регистру относится только к ключевым словам SQL. В MySQL имена баз данных, таблиц и колонок к регистру чувствительны. Но это характерно не для всех СУБД. Поэтому, если вы пишете приложение, которое должно работать с любыми СУБД, не следует использовать имена, различающиеся одним только регистром.
Первый элемент SQL-запроса - всегда глагол. Глагол выражает действие, которое должно выполнить ядро базы данных. Хотя остальная часть команды зависит от глагола, она всегда следует общему формату: указывается имя объекта, над которым осуществляется действие, а затем описываются используемые при действии данные. Например, в запросе CREATE TABLE people (char(10)) используется глагол CREATE, за которым следует дополнение (объект) TABLE. Оставшаяся часть запроса описывает таблицу, которую нужно создать.
SQL-запрос исходит от клиента - приложения, с помощью которого пользователь взаимодействует с базой данных. Клиент составляет запрос, основываясь на действиях пользователя, и посылает его серверу SQL. После этого сервер должен обработать запрос и выполнить указанные действия. Сделав свою работу, сервер возвращает клиенту одно или несколько значений.
Поскольку основная задача SQL - сообщить серверу баз данных о том, какие действия необходимо выполнить, он не обладает гибкостью языка общего назначения. Большинство функций SQL связано с вводом и выводом из базы: добавление, изменение, удаление и чтение данных. SQL предоставляет и другие возможности, но всегда с оглядкой на то, как они могут использоваться для манипулирования данными в базе.
Создание и удаление таблиц. Успешно установив MySQL, вы можете приступить к созданию своей первой таблицы. Таблица, структурированное вместилище данных, является основным понятием реляционных баз. Прежде чем начать вводить данные в таблицу, вы должны определить ее структуру. Рассмотрим следующую раскладку:
people |
|
name |
char(10) not null |
address |
text(100) |
id |
int |
Таблица содержит не только имена колонок, но и тип каждого поля, а также возможные дополнительные сведения о полях. Тип данных поля определяет, какого рода данные могут в нем содержаться. Типы данных SQL сходны с типами данных в других языках программирования. Полный стандарт SQL допускает большое разнообразие типов данных. MySQL реализует большую их часть.
Общий синтаксис для создания таблиц следующий:
CREATE TABLE table_name (column_name1 type [modifiers]
[, column_name2 type [modifiers]] )
Какие идентификаторы - имена таблиц и колонок - являются допустимыми, зависит от конкретной СУБД. В MySQL длина идентификатора может быть до 64 символов, допустим символ «$», и первым символом может быть цифра. Более важно, однако, что MySQL допускает использование любых символов из установленного в системе локального набора. Для хорошей переносимости SQL избегайте имен, начинающихся не с допустимой буквы.
Колонка - это отдельная единица данных в таблице. В таблице может содержаться произвольное число колонок, но использование больших таблиц бывает неэффективным. Создав правильно нормализованные таблицы, можно объединять их («join») для осуществления поиска в данных, размещенных в нескольких таблицах. Механику объединения таблиц мы обсудим позднее.
Как и бывает в жизни, разрушить легче, чем создать. Следующая команда удаляет таблицу:
DROP TABLE table_name
MySQL уничтожит все данные удаленной таблицы. Если у вас не осталось резервной копии, нет абсолютно никакого способа отменить действие данной операции. Поэтому всегда храните резервные копии и будьте очень внимательны при удалении таблиц. В один «прекрасный» день это вам пригодится.
В MySQL можно одной командой удалить несколько таблиц, разделяя их имена запятыми. Например, DROP TABLE people, animals, plants удалит эти три таблицы. Можно также использовать модификатор IF EXISTS для подавления ошибки в случае отсутствия удаляемой таблицы. Этот модификатор полезен в больших сценариях, предназначенных для создания базы данных и всех ее таблиц. Прежде чем создавать таблицу, выполните команду DROP TABLE table_name IF EXISTS.
Типы данных в SQL. Каждая колонка таблицы имеет тип. Типы данных SQL сходны с типами данных традиционных языков программирования. В то время как во многих языках определен самый минимум типов, необходимых для работы, в SQL для удобства пользователей определены дополнительные типы, такие как MONEY и DATE. Данные типа MONEY можно было бы хранить и как один из основных числовых типов данных, однако использование типа, специально учитывающего особенности денежных расчетов, повышает легкость использования SQL.
Таблица 3.4. Наиболее употребительные типы данных, поддерживаемые MySQL
Тип данных |
Описание |
INT |
Целое число, может быть со знаком или без знака. |
REAL |
Число с плавающей запятой. Этот тип допускает больший диапазон значений, чем INT, но не обладает его точностью. |
CHAR(length) |
Символьная величина фиксированной длины. Поля типа CHAR не могут содержать строки длины большей, чем указанное значение. Поля меньшей длины дополняются пробелами. |
TEXT(length) |
Символьная величина переменной длины. TEXT - лишь один из нескольких типов данных переменного размера. |
DATE |
Стандартное значение даты. |
TIME |
Стандартное значение времени. Этот тип используется для хранения времени дня безотносительно какой-либо даты. При использовании вместе с датой позволяет хранить конкретную дату и время. Есть дополнительный тип DATETIME для совместного хранения даты и времени в одном поле. |
MySQL поддерживает атрибут UNSIGNED для всех числовых типов. Этот модификатор позволяет вводить в колонку только положительные (беззнаковые) числа. Беззнаковые поля имеют верхний предел значений вдвое больший, чем у соответствующих знаковых типов. Беззнаковый TINYINT - однобайтовый числовой тип MySQL - имеет диапазон от 0 до 255, а не от -127 до 127, как у своего знакового аналога.
MySQL имеет больше типов, чем перечислено выше. Однако на практике в основном используются перечисленные типы. Размер данных, которые вы собираетесь хранить, играет гораздо большую роль при разработке таблиц MySQL.
Числовые типы данных. Прежде чем создавать таблицу, вы должны хорошо представить себе, какого рода данные вы будете в ней хранить. Помимо очевидного решения о том, будут это числовые или символьные данные, следует выяснить примерный размер хранимых данных. Если это числовое поле, то каким окажется максимальное значение? Может ли оно измениться в будущем? Если минимальное значение всегда положительно, следует рассмотреть использование беззнакового типа. Всегда следует выбирать самый маленький числовой тип, способный хранить самое большое мыслимое значение. Если бы требовалось хранить в поле численность населения штата, следовало бы выбрать беззнаковый INT. В штате не может быть отрицательной численности населения, а чтобы беззнаковое поле типа INT не могло вместить число, представляющее его население, численность населения штата должна примерно равняться численности населения всей Земли.
Символьные типы. С символьными типами работать немного труднее. Вы должны подумать не только о максимальной и минимальной длине строки, но также о среднем размере, частоте отклонения от него и необходимости в индексировании. В данном контексте мы называем индексом поле или группу полей, в которых вы собираетесь осуществлять поиск в основном, в предложении WHERE. Индексирование, однако, значительно сложнее, чем такое упрощенное определение, и мы займемся им далее. Здесь важно лишь отметить, что индексирование по символьным полям происходит значительно быстрее, если они имеют фиксированную длину. Если длина строк не слишком колеблется или, что еще лучше, постоянна, то, вероятно, лучше выбрать для поля тип CHAR. Хороший кандидат на тип CHAR - код страны. Стандартом ISO определены двух символьные коды для всех стран. CHAR(2) будет правильным выбором для данного поля.
Чтобы подходить для типа CHAR, поле необязательно должно быть фиксированной длины, но длина не должна сильно колебаться. Телефонные номера, к примеру, можно смело хранить в поле CHAR(13), хотя длина номеров различна в разных странах. Просто различие не столь велико, поэтому нет смысла делать поле для номера телефона переменным по длине. В отношении поля типа CHAR важно помнить, что, вне зависимости от реальной длины хранимой строки, в поле будет ровно столько символов, сколько указано в его размере - не больше и не меньше. Разность в длине между размером сохраняемого текста и размером поля заполняется пробелами. Не стоит беспокоиться по поводу нескольких лишних символов при хранении телефонных номеров, но не хотелось бы тратить много места в некоторых других случаях. Для этого существуют текстовые поля переменной длины.
Хороший пример поля, для которого требуется тип данных с переменной длиной, дает URL Интернет. По большей части адреса Web занимают сравнительно немного места - http://www.ora.com, http://www.hughes.com.au, http://www.mysql.com - и не представляют проблемы. Иногда, однако, можно наткнуться на адреса подобного вида:
http://www.winespectator.com/Wine/Spectator/notes\5527293926834323221480431354?XvlI=&Xr5=&Xvl =&type-region-search-code=&Xa14=flora+springs&Xv4=.
Если создать поле типа CHAR длины, достаточной для хранения этого URL, то почти для каждого другого хранимого URL будет напрасно тратиться весьма значительное пространство. Поля переменной длины позволяют задать такую длину, что оказывается возможным хранение необычно длинных значений, и в то же время не расходуется напрасно место при хранении обычных коротких величин.
Поля переменной длины. Преимуществом текстовых полей переменной длины является то, что они используют ровно столько места, сколько необходимо для хранения отдельной величины. Например, поле типа VARCHAR(255) , в котором хранится строка «hello, world», занимает только двенадцать байтов (по одному байту на каждый символ плюс еще один байт для хранения длины). В отличие от стандарта ANSI, в MySQL поля типа VARCHAR не дополняются пробелами. Перед записью из строки удаляются лишние пробелы.
Сохранить строки, длина которых больше, чем заданный размер поля, нельзя. В поле VARCHAR(4) можно сохранить строку не длиннее 4 символов. Если вы попытаетесь сохранить строку «happy birthday», MySQL сократит ее до «happ». Недостатком подхода MySQL к хранению полей переменной длины является то, что не существует способа сохранить необычную строку, длина которой превосходит заданное вами значение. В таблице 3.5 показан размер пространства, необходимого для хранения 144-символьного URL, продемонстрированного выше, и обычного, 30-символьного URL.
Таблица 3.5. Пространство памяти, необходимое для различных символьных типов MySQL
Тип данных |
Пространство для хранения строки из 144 символов |
Пространство для хранения строки из 30 символов |
Максимальная длина строки |
СНАR(150) |
150 |
150 |
255 |
VARCHAR(150) |
145 |
31 |
255 |
TINYTEXT(150) |
145 |
31 |
255 |
ТЕХТ(150) |
146 |
32 |
65535 |
MEDIUMTEXT(150) |
147 |
33 |
16777215 |
LONGTEXT(150) |
148 |
34 |
4294967295 |
Если через годы работы со своей базой данных вы обнаружите, что мир изменился, и поле, уютно чувствовавшее себя в типе VARCHAR(25), должно теперь вмещать строки длиной 30 символов, не все потеряно. В MySQL есть команда ALTER TABLE , позволяющая переопределить размер поля без потери данных.
ALTER TABLE mytable MODIFY mycolumn LONGTEXT
Двоичные типы данных. В MySQL есть целый ряд двоичных типов данных, соответствующих своим символьным аналогам. Двоичными типами, поддерживаемыми MySQL, являются CHAR BINARY, VARCHAR BINARY, TINYBLOB, BLOB, MEDIUMBLOB и LONGBLOB. Практическое отличие между символьными типами и их двоичными аналогами основано на принципе кодировки. Двоичные данные просто являются куском данных, которые MySQL не пытается интерпретировать. Напротив, символьные данные предполагаются представляющими текстовые данные из используемых человеком алфавитов. Поэтому они кодируются и сортируются, основываясь на правилах, соответствующих рассматриваемому набору символов. Двоичные же данные MySQL сортирует в порядке ASCII без учета регистра.
Перечисления и множества. MySQL предоставляет еще два особых типа данных. Тип ENUM позволяет при создании таблицы указать список возможных значений некоторого поля. Например, если бы у вас была колонка с именем «фрукт», в которую вы разрешили бы помещать только значения «яблоко», «апельсин», «киви» и «банан», ей следовало бы присвоить тип ENUM:
CREATE TABLE meal(meal_id INT NOT NULL PRIMARY KEY,
фрукт ENUM('яблоко', 'апельсин', 'киви', 'банан'))
При записи значения в эту колонку оно должно быть одним из перечисленных фруктов. Поскольку MySQL заранее знает, какие значения допустимы для этой колонки, она может абстрагировать их каким-либо числовым типом. Иными словами, вместо того, чтобы хранить в колонке «яблоко» в виде строки, MySQL заменяет его однобайтовым числом, а «яблоко» вы видите, когда обращаетесь к таблице или выводите из нее результаты.
Тип MySQL SET позволяет одновременно хранить в поле несколько значений.
Другие типы данных. Любые мыслимые данные можно хранить с помощью числовых или символьных типов. В принципе, даже числа можно хранить в символьном виде. Однако то, что это можно сделать, не означает, что это нужно делать. Рассмотрим, к примеру, как хранить в базе данных денежные суммы. Можно делать это, используя INT или REAL. Хотя интуитивно REAL может показаться более подходящим - в конце концов, в денежных суммах нужны десятичные знаки, - на самом деле более правильно использовать INT. В полях, содержащих значения с плавающей запятой, таких как REAL, часто невозможно найти число с точным десятичным значением. Например, если вы вводите число 0.43, которое должно представлять сумму $0.43, MySQL может записать его как 0.42999998. Это небольшое отличие может вызвать проблемы при совершении большого числа математических операций.
MySQL предоставляет специальные типы данных для таких денежных сумм. Одним из них является тип MONEY, другим - DATE.
Индексы. Хотя MySQL обеспечивает более высокую производительность, чем любые большие серверы баз данных, некоторые задачи все же требуют осторожности при проектировании базы данных. Например, если таблица содержит миллионы строк, поиск нужной строки в ней наверняка потребует длительного времени. В большинстве баз данных поиск облегчается благодаря средству, называемому индексом.
Индексы способствуют хранению данных в базе, таким образом, который позволяет осуществлять быстрый поиск. К несчастью, ради скорости поиска приходится жертвовать дисковым пространством и скоростью изменения данных. Наиболее эффективно создавать индексы для тех колонок, в которых вы чаще всего собираетесь осуществлять поиск. MySQL поддерживает следующий синтаксис для создания индексов:
CREATE INDEX index_name ON tablename (column1, column2, columnN)
MySQL позволяет также создавать индекс одновременно с созданием таблицы, используя следующий синтаксис:
CREATE TABLE materials (id INT NOT NULL,
name CHAR(50) NOT NULL,
resistance INT,
melting_pt REAL,
INDEX index1 (id, name),
UNIQUE INDEX index2 (name))
В этом примере для таблицы создается два индекса. Первый индекс index1 состоит из полей id и name. Второй индекс включает в себя только поле name и указывает, что значения поля name должны быть уникальными. Если вы попытаетесь вставить в поле name значение, которое уже есть в этом поле в какой-либо строке, операция не будет осуществлена. Все поля, указанные в уникальном индексе, должны быть объявлены как NOT NULL .
Хотя мы создали отдельный индекс для поля name, отдельно для поля id мы не создавали индекса. Если такой индекс нам понадобится, создавать его не нужно - он уже есть. Когда индекс содержит более одной колонки (например, name, rank, и serial_number), MySQL читает колонки в порядке слева направо. Благодаря используемой MySQL структуре индекса всякое подмножество колонок с левого края автоматически становится индексом внутри «главного» индекса. Например, когда вы создаете индекс name, rank, serial_number, создаются также «свободные» индексы name и name вместе с rank. Однако индексы rank или name и serial_number не создаются, если не потребовать этого явно.
MySQL поддерживает также семантику ANSI SQL для особого индекса, называемого первичным ключом. В MySQL первичный ключ - это уникальный индекс с именем PRIMARY. Назначив при создании таблицы колонку первичным ключом, вы делаете ее уникальным индексом, который будет поддерживать объединения таблиц. В следующем примере создается таблица cities с первичным ключом id.
CREATE TABLE cities (id INT NOT NULL PRIMARY KEY,
name VARCHAR(100),
pop MEDIUMINT,
founded DATE)
Прежде чем создавать таблицу, нужно решить, какие поля будут ключами (и будут ли вообще ключи). Как уже говорилось, любые поля, которые будут участвовать в объединении таблиц, являются хорошими кандидатами на роль первичного ключа.
Последовательности и автоинкрементирование. Лучше всего, когда первичный ключ не имеет в таблице никакого иного значения, кроме значения первичного ключа. Для достижения этого лучшим способом является создание числового первичного ключа, значение которого увеличивается при добавлении в таблицу новой строки. Если вернуться к примеру с таблицей cities, то первый введенный вами город должен иметь id, равный 1, второй - 2, третий - 3, и т. д. Чтобы успешно управлять такой последовательностью первичных ключей, нужно иметь какое-то средство, гарантирующее, что в данный конкретный момент только один клиент может прочесть число и увеличить его на единицу. В базе данных с транзакциями можно создать таблицу, скажем, с именем sequence, содержащую число, представляющее очередной id. Когда необходимо добавить новую строку в таблицу, вы читаете число из этой таблицы и вставляете число на единицу большее. Чтобы эта схема работала, нужно быть уверенным, что никто другой не сможет произвести чтение из таблицы, пока вы не ввели новое число. В противном случае два клиента могут прочесть одно и то же значение и попытаться использовать его в качестве значения первичного ключа в одной и той же таблице.
MySQL не поддерживает транзакции, поэтому описанный механизм нельзя использовать для генерации уникальных чисел. Использовать для этих целей команду MySQL LOCK TABLE обременительно. Тем не менее СУБД предоставляет свой вариант понятия последовательности, позволяющий генерировать уникальные идентификаторы, не беспокоясь о транзакциях.
Последовательности. При создании таблицы в MySQL можно одну из колонок специфицировать как AUTO_INCREMENT. В этом случае, при добавлении новой строки, имеющей значение NULL или 0 в данной колонке, автоматически будет происходить замена на значение на единицу больше, чем наибольшее текущее значение в колонке. Колонка с модификатором AUTO_INCREMENT должна быть индексирована. Ниже приведен пример использования поля типа AUTO_INCREMENT:
CREATE TABLE cities (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
pop MEDIUMINT,
founded DATE)
Когда вы первый раз добавляете строку, поле id получает значение 1, если в команде INSERT для него используется значение NULL или 0. Например, следующая команда использует возможность AUTO_INCREMENT:
INSERT INTO cities (id, name, pop)
VALUES (NULL, 'Houston', 3000000)
Если вы выполните эту команду, когда в таблице нет строк, поле id получит значение 1, а не NULL. В случае, когда в таблице уже есть строки, полю будет присвоено значение на 1 большее, чем наибольшее значение id в данный момент.
Другим способом реализации последовательностей является использование значения, возвращаемого функцией LAST_INSERT_ID:
UPDATE table SET id=LAST_INSERT_ID (id+1);
Управление данными. Первое, что вы делаете, создав таблицу, это добавляете в нее данные. Если данные уже есть, может возникнуть необходимость изменить или удалить их.
Добавление данных. Добавление данных в таблицу является одной из наиболее простых операций SQL. Несколько примеров этого вы уже видели. MySQL поддерживает стандартный синтаксис INSERT:
INSERT INTO table_name (column1, column2, ..., columnN)
VALUES (value1, value2, ..., valueN)
Данные для числовых полей вводятся, как они есть. Для всех других полей вводимые данные заключаются в одиночные кавычки. Например, для ввода данных в таблицу адресов можно выполнить следующую команду:
INSERT INTO addresses (name, address, city, state, phone, age)
VALUES('Irving Forbush', '123 Mockingbird Lane', 'Corbin', 'KY', '(800) 555-1234', 26)
Кроме того, управляющий символ - по умолчанию '\' - позволяет вводить в литералы одиночные кавычки и сам символ '\':
# Ввести данные в каталог Stacie's Directory, который находится
# в c:\Personal\Stacie
INSERT INTO files (description, location)
VALUES ('Stacie\'s Directory', 'C:\\Personal\\Stacie')
MySQL позволяет опустить названия колонок, если значения задаются для всех колонок и в том порядке, в котором они были указаны при создании таблицы командой CREATE. Однако если вы хотите использовать значения по умолчанию, нужно задать имена тех колонок, в которые вы вводите значения, отличные от установленных по умолчанию. Если для колонки не установлено значение по умолчанию, и она определена как NOT NULL, необходимо включить эту колонку в команду INSERT со значением, отличным от NULL. MySQL позволяет указать значение по умолчанию при создании таблицы в команде CREATE.
Новые версии MySQL поддерживают INSERT для одновременной вставки нескольких строк:
INSERT INTO foods VALUES (NULL, 'Oranges', 133, 0, 2, 39),
(NULL, 'Bananas', 122, 0, 4, 29),
(NULL, 'Liver', 232, 3, 15. 10)
Хотя поддерживаемый MySQL нестандартный синтаксис удобно использовать для быстрого выполнения задач администрирования, не следует без крайней нужды пользоваться им при написании приложений. Как правило, следует придерживаться стандарта ANSI SQL2 настолько близко, насколько MySQL это позволяет. Благодаря этому вы получаете возможность перейти в будущем на какую-нибудь другую базу данных. Переносимость особенно важна для тех, у кого потребности среднего масштаба, поскольку такие пользователи предполагают когда-нибудь перейти на полномасштабную базу данных.
MySQL поддерживает синтаксис SQL2, позволяющий вводить в таблицу результаты запроса:
INSERT INTO foods (name, fat)
SELECT food_name, fat_grams FROM recipes
Обратите внимание, что число колонок в INSERT соответствует числу колонок в SELECT. Кроме того, типы данных колонок в INSERT должны совпадать с типами данных в соответствующих колонках SELECT. И, наконец, предложение SELECT внутри команды INSERT не должно содержать модификатора ORDER BY и не может производить выборку из той же таблицы, в которую вставляются данные командой INSERT.
Изменение данных. Если ваша база не является базой данных «только для чтения», вам, вероятно, понадобится периодически изменять данные. Стандартная команда SQL для изменения данных выглядит так:
UPDATE table_name
SET column1=value1, column2=value2, ..., columnN=valueN
[WHERE clause]
MySQL позволяет вычислять присваиваемое значение. Можно даже вычислять значение, используя значение другой колонки:
UPDATE years
SET end_year = begin_year+5
В этой команде значение колонки end_year устанавливается равным значению колонки begin_year плюс 5 для каждой строки таблицы.
Предложение WHERE. Возможно, вы уже обратили внимание на предложение WHERE. В SQL предложение WHERE позволяет отобрать строки таблицы с заданным значением в указанной колонке, например:
UPDATE bands
SET lead_singer = 'Ian Anderson'
WHERE band_name = 'Jethro Tull'
Эта команда - UPDATE - указывает, что нужно изменить значение в колонке lead_singer для тех строк, в которых band_name совпадает с «Jethro Tull». Если рассматриваемая колонка не является уникальным индексом, предложение WHERE может соответствовать нескольким строкам. Многие команды SQL используют предложение WHERE, чтобы отобрать строки, над которыми нужно совершить операции. Поскольку по колонкам, участвующим в предложении WHERE, осуществляется поиск, следует иметь индексы по тем их комбинациям, которые обычно используются.
Удаление. Для удаления данных вы просто указываете таблицу, из которой нужно удалить строки, и в предложении WHERE задавая строки, которые хотите удалить:
DELETE FROM table_name [WHERE clause]
Как и в других командах, допускающих использование предложения WHERE, его использование является необязательным. Если предложение WHERE опущено, то из таблицы будут удалены все записи!
Запросы. Самая часто используемая команда SQL - та, которая позволяет просматривать данные в базе: SELECT. Ввод и изменение данных производятся лишь от случая к случаю, и большинство баз данных в основном занято тем, что предоставляет данные для чтения. Общий вид команды SELECT следующий:
SELECT column1, column2, ..., columnN
FROM table1, table2, ..., tableN
[WHERE clause]
Этот синтаксис чаще всего используется для извлечения данных из базы, поддерживающей SQL. Cуществуют разные варианты для выполнения сложных и мощных запросов.
В первой части команды SELECT перечисляются колонки, которые вы хотите извлечь. Можно задать «*», чтобы указать, что вы хотите извлечь все колонки. В предложении FROM указываются таблицы, в которых находятся эти колонки. Предложение WHERE указывает, какие именно строки должны использоваться, и позволяет определить, каким образом должны объединяться две таблицы.
Объединения. Объединения вносят «реляционность» в реляционные базы данных. Именно объединение позволяет сопоставить строке одной таблицы строку другой. Основным видом объединения является то, что иногда называют внутренним объединением. Объединение таблиц заключается в приравнивании колонок двух таблиц:
SELECT book, title, author. name
FROM author, book
WHERE book.author = author.id
Рассмотрим базу данных, в которой таблица book имеет вид, как в таблице 3.6.
Таблица 3.6. Таблица книг
ID |
Title |
Author |
Pages |
1 |
The Green Mile |
4 |
894 |
2 |
Guards, Guards! |
2 |
302 |
3 |
Imzadi |
3 |
354 |
4 |
Gold |
1 |
405 |
5 |
Howling Mad |
3 |
294 |
А таблица авторов author имеет вид таблицы 3.7.
Таблица 3.7. Таблица авторов
ID |
Name |
Citizen |
1 |
Isaac Asimov |
US |
2 |
Terry Pratchet |
UK |
3 |
Peter David |
US |
4 |
Stephen King |
US |
5 |
Neil Gaiman |
UK |
В результате внутреннего объединения создается таблица, в которой объединяются поля обеих таблиц для строк, удовлетворяющих запросу в обеих таблицах. В нашем примере запрос указывает, что поле author в таблице book должно совпадать с полем id таблицы author. Результат выполнения этого запроса представлен в таблице 3.8.
Таблица 3.8. Результаты запроса с внутренним объединением
Book Title |
Author Name |
The Green Mile |
Stephen King |
Guards, Guards! |
Terry Pratchet |
Imzadi |
Peter David |
Gold |
Isaac Asimov |
Howling Mad |
Peter David |
В этих результатах нет автора с именем Neil Gaiman, поскольку его author.id не найден в таблице book.author. Внутреннее объединение содержит только те строки, которые точно соответствуют запросу. Ниже мы обсудим понятие внешнего объединения, которое оказывается полезным в случае, когда в базу данных внесен писатель, у которого нет в этой базе книг.
Псевдонимы. Полные имена, содержащие имена таблиц и колонок, зачастую весьма громоздки. Кроме того, при использовании функций SQL, о которых мы будем говорить ниже, может оказаться затруднительным ссылаться на одну и ту же функцию более одного раза в пределах одной команды. Псевдонимы, которые обычно короче и более выразительны, могут использоваться вместо длинных имен внутри одной команды SQL, например:
# Псевдоним колонки
SELECT long_field_names_are_annoying AS myfield
FROM table_name
WHERE myfield = 'Joe'
# Псевдоним таблицы в MySQL
SELECT people.names, tests.score
FROM tests really_long_people_table_name AS people
Группировка и упорядочение. По умолчанию порядок, в котором появляются результаты выборки, не определен. К счастью, SQL предоставляет некоторые средства наведения порядка в этой случайной последовательности. Первое средство - упорядочение. Вы можете потребовать от базы данных, чтобы выводимые результаты были упорядочены по некоторой колонке. Например, если вы укажете, что запрос должен упорядочить результаты по полю last_name, то результаты будут выведены в алфавитном порядке по значению поля last_name. Упорядочение осуществляется с помощью предложения ORDER BY:
SELECT last_name, first_name, age
FROM people
ORDER BY last_name, first_name
В данном случае упорядочение производится по двум колонкам. Можно проводить упорядочение по любому числу колонок, но все они должны быть указаны в предложении SELECT. Если бы в предыдущем примере мы не выбрали поле last_name, то не смогли бы упорядочить по нему.
Группировка - это средство ANSI SQL, реализованное в MySQL. Как и предполагает название, группировка позволяет объединять в одну строки с аналогичными значениями с целью их совместной обработки. Обычно это делается для применения к результатам агрегатных функций. О функциях мы поговорим несколько позднее.
Рассмотрим пример:
mysql> SELECT name, rank, salary FROM people\g
name |
rank |
salary |
Jack Smith |
Private |
23000 |
Jane Walker |
General |
125000 |
June Sanders |
Private |
22000 |
John Barker |
Sergeant |
45000 |
Jim Castle |
Sergeant |
38000 |
5 rows in set (0.01 sec)
После группировки по званию (rank) выдача изменяется:
mysql> SELECT rank FROM people GROUP BY rank\g
rank |
General |
Private |
Sergeant |
3 rows in set (0.01 sec)
После применения группировки можно, наконец, найти среднюю зарплату (salary) для каждого звания. О функциях, используемых в этом примере, мы поговорим позднее.
mysql> SELECT rank, AVG(salary) AS income FROM people GROUP BY rank\g
rank |
income |
General |
125000 |
Private |
22500 |
Sergeant |
41500 |
3 rows in set (0 04 sec)
Упорядочение и группировка в сочетании с использованием функций SQL позволяет производить большой объем обработки данных на сервере до их извлечения. Но этой мощью нужно пользоваться с осторожностью. Хотя может показаться, что перенос максимального объема обработки на сервер базы данных дает выигрыш в производительности, на самом деле это не так. Ваше приложение-клиент обслуживает потребности отдельного клиента, в то время как сервер используется многими клиентами. Из-за большого объема работы на сервере, почти всегда более эффективно возложить на сервер минимально возможную нагрузку. MySQL, возможно, наиболее быстрая из имеющихся баз данных, но не нужно использовать эту скорость для той работы, к которой лучше приспособлено клиентское приложение.
Если вам известно, что много клиентов будет запрашивать одни и те же итоговые данные (например, данные по некоторому званию в нашем предыдущем примере), создайте новую таблицу с этими данными и обновляйте ее при изменении данных в исходной таблице. Эта операция аналогична буферизации и является распространенным приемом в программировании баз данных.
Функции в MySQL. MySQL предоставляет возможность работы с функциями. Функции в SQL аналогичны функциям в других языках программирования, таких как С и Perl. Функция может принимать аргументы и возвращает некоторое значение. В MySQL в команде SELECT функции могут использоваться в двух местах:
Как извлекаемая величина
В этом случае функция включается в список извлекаемых колонок. Возвращаемое функцией значение, вычисляемое для каждой выбранной строки, включается в возвращаемое результирующее множество, как если бы это была колонка базы данных. Вот пример:
# Функция FROM_UnixTIME()
# преобразует стандартное значение времени Unix в читаемый вид.
SELECT name, FROM_UnixTIME(date)
FROM events
# Функция LENGTH() возвращает длину заданной строки в символах.
SELECT title, text, LENGTH(text)
FROM papers
WHERE author = 'Stacie Sheldon'
Как часть предложения WHERE
В этом виде функция заменяет место константы при вычислении в предложении WHERE. Значение функции используется при сравнении в каждой строке таблицы. Приведем пример.
# Функция RAND() генерирует случайное число
# между 0 и 1 (умножается на 34, чтобы сделать его между О
# и 34, и увеличивается на 1 , чтобы сделать его между 1 и
# 35) Функция ROUND() возвращает данное число округленным
# до ближайшего целого, что приводит к целому числу
# между 1 и 35 которое должно соответствовать одному из чисел ID
SELECT name
FROM entries
WHERE id = ROUND((RAND()*34) + 1 )
# Можно использовать функции в списке значений и предложении WHERE
# Функция UNIX_TIMESTAMP()без аргументов возвращает текущее время # в формате Unix.
SELECT name, FROM_UnixTIME(date)
FROM events
WHERE time > (Unix_TIMESTAMP() - (60 * 60 * 24))
# Функция ENCRYPT() # возвращает зашифрованную в стиле пароля Unix
# заданную строку, используя 2-символьный ключ.
# Функция LEFT() возвращает n левых символов переданной строки.
SELECT name
FROM people
WHERE password = ENCRYPT(name, LEFT(name, 2))
Некоторые из функций могут вернуть значение как число или как строку, в зависимости от того, какой формат необходим пользователю. Эта возможность называется «контекстом» функции. Когда выбранные значения выводятся на дисплей, используется только текстовой контекст, но при вводе выбранных данных в поля таблиц или при использовании их в качестве аргументов других функций контекст зависит от того, что ожидается получателем данных. В частности, когда данные выбраны для их последующего ввода в поля числового типа, контекст функции будет числовым.
Кроме того, имеются агрегатные функции, выполняемые над набором данных. Обычно этот метод используется для выполнения некоторого действия над всем набором возвращаемых данных. Например, функция SELECT AVG(height) FROM kids возвращала бы среднее от значений поля height в таблице kids.
Таблица 3.9. Агрегатные функции MySQL
AVG(expression ) |
Возвращает среднее значение из значений в expression (например, SELECT AVG(score) FROM tests). |
BIT_AND( expression ) |
Возвращает результат побитового И, агрегирующего все значения в expression (например, SELECT BIT_AND(flags) FROM options). |
BIT_OR( expression ) |
Возвращает побитовое ИЛИ, агрегирующее все значения в expression (например, SELECT BIT_OR(flags) FROM options). |
COUNT(expression ) |
Возвращает количество раз, когда значение expression было не нулевым. COUNT(*) вернет число записей с какими-либо данными во всей таблице (например, SELECT COUNT( *) FROM folders). |
MAX(expression ) |
Возвращает наибольшее из значений в expression (например, SELECT MAX (elevation) FROM mountains ). |
MIN( expression ) |
Возвращает наименьшее из значений в expression (например, SELECT MIN(level) FROM toxic_waste ). |
STD(expression )/STDDEV( expression ) |
Возвращает среднеквадратичное отклонение значения в expression (например, SELECT STDDEV(points) FROM data ). |
SUM( expression ) |
Возвращает сумму значений в expression (например, SELECT SUM(calories) FROM daily_diet ). |
Объединения в MySQL. MySQL поддерживает более сильный тип объединения, чем простое внутреннее объединение, которое мы до сих пор использовали. Именно, MySQL поддерживает так называемое левое внешнее объединение (известное также просто как внешнее объединение). Объединение этого типа похоже на внутреннее объединение, за исключением того, что в него включаются данные из левой колонки, которым нет соответствия в правой колонке. Если вы обратитесь к нашим таблицам с авторами и книгами, то вспомните, что в наше объединение не вошли авторы, у которых в базе данных не было книг. Часто вы можете пожелать вывести записи из одной таблицы, для которых нет соответствия в другой таблице, с которой производится объединение. Это можно сделать с помощью внешнего объединения:
SELECT book.title, author.name
FROM author
LEFT JOIN book ON book.author = author.id
Обратите внимание, что во внешнем объединении вместо WHERE используется ключевое слово ON. Результат нашего запроса будет выглядеть так:
book title |
author name |
The Green Mile |
Stephen King |
Guards, Guards! |
Terry Pratchet |
Imzadi |
Peter David |
Gold |
Isaac Asimov |
Howling Mad |
Peter David |
NULL |
Neil Gaiman |
MySQL делает следующий шаг, позволяя использовать естественное внешнее объединение (natural outer join). Естественное внешнее объединение соединяет строки двух таблиц, в которых две колонки имеют одинаковые имена и тип, и значения в этих колонках совпадают:
SELECT my_prod name
FROM my_prod
NATURAL LEFT JOIN their_prod
Основы ASP.
Dynamic HTML представляет собой основное средство программирования клиента для Microsoft Internet Explorer 4.0 и выше, но такие программы просмотра Web, как Netscape Navigator, не поддерживают Dynamic HTML. На самом деле очень малая часть функциональности клиентской части, поддерживаемой различными программами просмотра, может рассматриваться как действительно кросс-платформенная.
Если Вы хотите разработать Интернет-узел, открытый для доступа самым различным программам просмотра, то должны перенести программирование с клиента на сервер. Такую возможность предоставляют Microsoft ASP (Active Server Pages активные серверные страницы). По сути ASP не что иное, как сценарий на VBScript, который исполняется на сервере. Когда запрашивается страница, этот сценарий порождает HTML-текст. Это ключевая особенность ASP клиент никогда не видит вашего кода, а только результирующий HTML, который воспринимает любая программа просмотра.
Листинг 4.1 демонстрирует простую ASP-страницу, которая создает приветствие в соответствии со временем суток. В нем текущий час определяется при помощи кода Hour(Now), где Now функция VBScript, возвращающая текущий момент времени и дату. Если текущий час меньше 12, то приветствие задается в форме «Доброе утро!» От полудня до шести вечера сообщение имеет вид «Добрый день!», а после шести «Добрый вечер!»
Листинг 4.1. Простой пример ASP.
<%@ LANGUAGE="VBSCRIPT" %>
<HTML> <head>
<MEТА HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=windows-1251">
<TITLE>Simple ASP Example</TITLE> </HEAD><BODY BGCOLOR="#FFFFFF">
<%
Dim strGreeting
If Hour(Now) < 12 Then
strGreeting = "Доброе утро!"
ElseIf Hour(Now) > 11 And Hour(Now) < 18 Then
StrGreeting = "Добрый день"
ElseIf Hour(Now) > 17 Then
strGreeting = "Добрый вечер!"
End If
%>
<CENTER><H1><%=strGreeting%> </Н1>
</BODY></HTML>
Обратите внимание на код в листинге, окруженный специальными символами: угловыми скобками и знаками процента (<%...%>). Такие символы означают, что это серверный код, который выполняется перед тем, как страница будет на самом деле послана программе просмотра. Если бы Вы посмотрели в Internet Explorer на результирующий HTML-текст, то увидели бы следующее (в предположении, что сейчас еще не вечер, но уже не утро):
<%@ LANGUAGE="VBSCRIPT" %>
<HTML><HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=windows-1251">
<TITLE>Simple ASP Example</TITLE> </HEAD>
<BODY BGCOLOR="#FFFFFF"> <Н1>Добрый день!</Н1> </BODY> </HTML>
В этом-то и состоит суть ASP. Результат исполнения кода обыкновенный HTML! Эту страницу можно просматривать любой программой просмотра: не только Internet Explorer, но и, например, Netscape Navigator. Иными словами, ASP предоставляет разработчику подлинную платформенную независимость
У представленного кода есть еще несколько особенностей. Обратите внимание на самую верхнюю строчку она определяет, на каком языке написан сценарий на данной странице:
<%@ LANGUAGE="VBSCRIPT" %>
Скорее всего, Вы будете писать свои сценарии на VBScript, но ASP поддерживает и JavaScript. В отличие от сценариев на клиентской стороне, здесь выбор языка абсолютно не создает проблем совместимости с различными программами просмотра, поскольку весь код исполняется на сервере.
А теперь обратите внимание на ту строку, где и происходит генерация HTML-текста. Здесь для вывода приветствия используется переменная:
<H1><%=strGreeting%> </H1>
Переменная strGreeting заключена в угловые скобки со знаками процента, как и весь остальной серверный код, но, кроме того, ей предшествует знак равенства. Такая конструкция играет важную роль в ASP, указывая, что в данное место HTML-страницы нужно вставить текущее значение этой переменной.
Объекты и компоненты. На самом простом уровне создание ASP-страницы это ни что иное, как написание серверного кода для получения ожидаемого результата. Но VBScript не является полнофункциональным языком и, как только Вы приступаете к построению более сложных страниц, его выразительных средств начинает не хватать. Так, в VBScript нет встроенных функций доступа к данным; не умеет он и открывать текстовые файлы. Собственно говоря, в VBScript отсутствуют какие-либо встроенные средства доступа к каким бы то ни было внешним источникам данных. Так как же в таком случае при помощи ASP выполняются такие сложные действия, как доступ к данным? Ответ будет таким: нужно дополнить VBScript объектами и компонентами ASP.
ASP-объекты и компоненты это не что иное, как компоненты ActiveX, подобные обычным DLL ActiveX, с которыми Вы наверняка работали в Microsoft Visual Basic. Различие между объектами и компонентами ASP состоит в том, каким образом они появляются в программе. ASP-объекты это элементы управления ActiveX, которые в коде на VBScript доступны всегда: их не нужно создавать явно. В число объектов ASP входят Application, Session, Request, Response и Server. А вот ASP-компоненты представляют собой DLL, существующие вне структуры ASP. Эти компоненты могут быть написаны на любом языке, а некоторые полезные ASP-компоненты просто поставляются в комплекте с Visual InterDev. ASP-компоненты нужно явно создавать в коде. ASP поддерживает компоненты Database Access, File Access, Browser Capabilities, Ad Rotator и Content Linking.
Файл GLOBAL.ASA. Одна из главных трудностей разработчика для Интернета, независимо от того, какую технологию он использует, состоит в том, как сложно создать в Интернете настоящее приложение. Взаимодействие программы просмотра и Web-сервера представляет собой по сути лишенную состояния транзакцию, в ходе которой сервер посылает клиенту Web-страницу и затем забывает о его существовании. Когда клиент запрашивает другую Web-страницу, сервер ничего не помнит о предыдущем запросе. Коренная проблема для всех Web-приложений такова: как показать, что это именно приложение?
Определить приложение в среде Microsoft Windows довольно просто. Приложение запускается двойным щелчком значка и завершается, когда в меню File выбран пункт Exit. В промежутке между двумя этими событиями данные хранятся в переменных. Но для Интернет-приложений это не так. Как определить, когда приложение начинается, а когда заканчивается? Можно сказать, что приложение начало работу, если пользователь зашел на узел и просматривает одну из его страниц. Но что если он переходит к другому узлу, а через пять минут возвращается? Приложение все еще активно? А если пользователь отсутствовал час или два?
Проблема определения моментов запуска и завершения приложения оказывает серьезное влияние на правильное управление переменными и последовательностью выполнения. К счастью, ASP предлагает решение. Оно состоит в том, что для определения начала и завершения как всего приложения, так и отдельных пользовательских сессий используется специальный файл под названием GLOBAL.ASA. На этот файл возложено реагирование на четыре ключевых события узла: Application_OnStart (запуск приложения), Application_OnEnd (завершение приложения), Session_OnStart (начало сессии) и Session_OnEnd (завершение сессии). В листинге 4.2 приведен типичный файл GLOBAL.ASA.
Листинг 4.2. Файл GLOBAL.ASA.
<SCRIPT LANGUAGE="VBSCRIPT" RUNAT="Server">
' В этот файл можно добавить обработчики событий ASP.
' Для создания обработчиков внесите в файл подпрограмму с именем,
' соответствующим событию, на которое Вы бы хотели среагировать.
'Название события Описание
'SessionOnStart Происходит, когда пользователь в первый раз вы-
' полняет любую страницу Вашего приложения
SessionOnEnd Происходит, когда превышен лимит времени,
' в течение которого пользователь не обращается
' к страницам Вашего приложения, или если имел
' место явный выход
'Application_OnStart Происходит один раз, когда любой
' пользователь впервые выполняет первую страницу
' Вашего приложения
'Application_OnEnd Происходит один раз при остановке Web-сервера
Sub SessionOnStart
End Sub
Sub Session_OnEnd
End Sub
Sub ApplicationOnStart
End Sub
Sub Application On_End
End Sub
</SCRIPT>
Для обозначения разделов сценария GLOBAL.ASA содержит теги <SCRIPT>. Эти теги имеют особый атрибут RUNAT=Server, который означает, что содержащийся в теге код на VBScript должен исполняться на сервере, а не на клиенте. Функционально RUNAT=Server означает то же, что и сочетания угловых скобок и знака процента, используемые для обозначения серверного сценария на Web-страницах. Обработка стандартных событий на сервере записывается в GLOBAL.ASA в стандартном синтаксисе. Например, обработку запуска приложения выполняет следующий фрагмент кода:
<SCRIPT LANGUAGE=VBScript RUNAT=Server>
Sub Application_OnStart
' Конкретный код приложения
End Sub </SCRIPT>
Хотя GLOBAL.ASA отмечает начало и завершение приложения при помощи событий, остается неясным, что же все-таки составляет собственно приложение. Одна из рабочих формулировок, предложенная Microsoft, определяет Интернет-приложение как виртуальный каталог со всеми его файлами. Если пользователь запрашивает Web-страницу из виртуального каталога под названием Bookstore, то тем самым он запускает приложение Bookstore, и в GLOBAL.ASA возбуждаются события Application_OnStart и Session_OnStart.
Согласно этому определению с приложением одновременно могут работать несколько программ просмотра. Но событие Application_OnStart происходит только один раз: когда первый пользователь запрашивает Web-страницу из виртуального каталога. Когда затем страницы из этого каталога запрашивают другие пользователи, возбуждается только событие Session_OnStart.
В то время как приложение может относиться к нескольким программам просмотра, обращающимся к одному и тому же множеству Web-страниц, сессия касается какой-то одной программы просмотра, обращающейся к тем же Web-страницам. Для конкретной программы просмотра сессия длится, пока программа продолжает запрашивать страницы виртуального каталога. Если же пользователь не запрашивает Web-страницы (из данного виртуального каталога) на протяжении 20 минут (по умолчанию), сессия завершается, и возбуждается событие Session_OnEnd. Когда в данном виртуальном каталоге завершаются все сессии, возбуждается событие Application_OnEnd.
В качестве примера рассмотрим следующий сценарий. Два пользователя намереваются посетить на Web-узле приложение Magazine. Пользователь 1 оказывается проворнее и быстренько запрашивает Web-страницу DEFAULT.ASP. Тут же возбуждаются события Application_OnStart и Session_OnStart. Буквально пятью минутами позже к приложению обращается пользователь 2. Поскольку пользователь 1 как-то проявлял себя в течение последних 20 минут, приложение Magazine активно. Следовательно, возбуждается только событие Session_OnStart, сигнализируя о начале новой сессии. Кроме того, теперь для завершения приложения необходимо, чтобы завершились обе сессии.
В течение следующих 15 минут пользователь 1 не запрашивает никаких страниц приложения Magazine. Поскольку он не проявлял активности на протяжении 20 минут, ASP приходит к выводу, что пользователь 1 закончил свою работу с приложением, и возбуждает событие Session_OnEnd. Но приложение все еще активно, поскольку в течение последних 20 минут к нему обращался пользователь 2.
Пользователь 2 работает с приложением еще час, то и дело запрашивая новые Web-страницы. Но в конце концов он отключается, а через 20 минут после того, как он покинул узел (точнее, в последний раз запросил Web-страницу приложения), возбуждается событие Session_OnEnd. Поскольку пользователь 2 был последним пользователем данного приложения, оно завершается, и возбуждается событие Application_OnEnd.
Объекты.
В ASP есть несколько встроенных объектов, которые доступны разработчику. Эти объекты помогают управлять многими вещами: от переменных, до передачи форм. Работать с ними легко, они вызываются из кода напрямую без какого-то особого синтаксиса.
Объект Application. Объект Application (приложение) позволяет создавать переменные приложения (application variables) - переменные, доступные всем пользователям данного приложения. Все, кто обращается к Web-страницам данного виртуального каталога, могут совместно использовать любую переменную приложения определенную для этого каталога.
В листинге 4.3 приведен пример программы, которая использует Объект Application. В нем переменная приложения служит для отслеживания времени последнего обращения к страницам приложения.
Листинг 4.3. Объект Application.
<%@ LANGUAGE ="VBScript"%>
<html><head><title>Application Variables</TITLE>
</HEAD><BODY><CENTER>
Эта страница последний раз посещалась: <%=Application("Time")%>
<% Application.Lock
Application("Time")=Now
Application.UnLock %>
</BODY></HTML>
Создание переменной приложения сводится к адресации объекта Application именем новой переменной, которую вы хотите создать. Например, следующий код создает новую переменную приложения с именем Company и присваивает ей значение NewTech.
Application("Company")="NewTech"
Имя может быть произвольным, а переменная может содержать любую информацию, будь то число или текст.
Поскольку такие переменные доступны нескольким пользователям одновременно, вы не сможете гарантировать, что два пользователя не попытаются одновременно присвоить одной и той же переменной разные значения. Для разрешения подобных коллизий объект Application поддерживает методы Lock и UnLock. Метод Lock блокирует весь объект Application, а не только переменную, вы хотите изменить, поэтому сразу же после изменения значения переменной разблокируйте объект:
Application.Lock
Application("Company")="NewTech"
Application.UnLock
Несмотря на то, что переменные приложения пригодны для временного хранения данных, их нельзя использовать для долговременного хранения. Все данные в переменных приложения уничтожаются, когда происходит событие Application_OnEnd. Так что, если вы хотите, чтобы они сохранились после завершения приложения, позаботьтесь о их переносе в файл или базу данных.
Объект Session. Зачастую разработчиков меньше интересуют данные, совместно используемые несколькими пользователями, зато гораздо больше - данные, связанные с конкретным пользователем. ASP поддерживает переменные для индивидуального пользователя при помощи объекта Session (сессия), который позволяет создавать переменные сессии (session variables).
Листинг 4.4 демонстрирует, как определить несколько переменных сессии в файле GLOBAL.ASA. Само по себе их определение так же просто, как и в случае переменных приложения. Все, что нужно сделать - это адресовать объект Session именем переменной, которую вы хотите создать. Основное различие между переменными этих объектов - их области видимости. Переменные сессии предназначаются для одного пользователя и живут, пока пользователь поддерживает сессию. Как только в течение 20 минут (по умолчанию) пользователь не обращается к страницам данного виртуального каталога, данные пропадают.
Листинг 4-.. Создание переменных сессии.
<SCRIPT LANGUAGE="VBSCRIPT" RUNAT="Server">
Sub Session_OnStart
Session("Company")="NewTech"
Session("Email")="info@newtech.com"
End Sub
</SCRIPT>
Переменные сессии можно создавать на любой Web-странице или в файле GLOBAL.ASA, а доступны они на любой Web-странице приложения, в котором эти переменные были первоначально созданы. Получить значения переменных сессии можно, считывая их из объекта Session. Следующий фрагмент кода считывает переменные сессии, созданные в листинге 4.4, и выводит их в полях ввода:
<FORM>
<P><INPUT TYPE="TEXT" VALUE=<%=Session("Company")%>Компания</P>
<P><INPUT TYPE="TEXT" VALUE=<%=Session("Email")%>Эл. Почта</P>
</FORM>
Ранее мы определили Интернет-приложение как лишенные статуса транзакции между Web-сервером и программой просмотра. Как же тогда ASP запоминает переменные сессии для каждого пользователя приложения? Ответ будет таким: эти переменные сохраняются на сервере для каждого клиента. Программа просмотра получает от сервера уникальный идентификатор, позволяющий определить, какой набор переменных кому принадлежит. Клиент этот идентификатор (Globally Unique Identifier, GUID) сохраняет, а впоследствии посылает серверу и получает именно ему предназначенные данные. Таким образом каждый клиент может иметь свой набор данных в каждом Интернет-приложении.
Осталось сказать, что для установки или считывания впемени жизни сессии (в минутах) применяется свойство Timeout объекта Session:
Session.Timeout=30
Объект Request. Для передачи данные клиенту создается Web-страница, а для передачи данных в обратном направлении программа просмотра использует отправку формы (form submission). В форме содержатся текстовые поля, переключатели и т.п. Клиент размещает введенные данные в этих полях и пересылает пакет серверу. Процессом передачи формы управляют два атрибута тега <FORM>: METHOD и ACTION. Первый атрибут - METHOD - определяет, каким именно образом данные пересылаются серверу. Атрибут может иметь два значения: POST и GET. POST диктует программе просмотра, что данные нужно поместить внутрь формы, а GET пересылает данные как составную часть URL целевой страницы. Второй атрибут - ACTION - задает целевую страницу для обработки отправленных данных. Следующий код посылает все данные формы сценарию DATA.ASP методом POST:
<FORM METHOD="POST" ACTION="/l5/data.asp">
<P><INPUT TYPE="TEXT" NAME="Name"></P>
<P><INPUT TYPE="TEXT" NAME="EMail"></P>
<P><INPUT TYPE="SUBMIT"></P>
</FORM>
Элемент формы с типом SUBMIT - это кнопка, нажатие которой пользователем заставляет программу просмотра упаковать данные формы и отправить их. Формат пересылки данных определен строго и сервер знает, чего ожидать от клиента. Данные имеют вид пар Поле=Значение, отсылаемых серверу в формате открытого текста. Если в предыдущем примере ввести в поле Name NewTech и info@newtech.com в поле Email, то сценарию DATA.ASP будет послан следующий текст:
Name=NewTech&Email=info@newtech.com
На сервере эти данные можно вновь разобрать по полям и использовать в любых целях. Вот тут-то и нужен объект Request. Он используется в ASP для разбора полученных от клиента данных. Для работы с объектом Request просто сообщите ему имя поля, значение которого хотите получить, и объект вернет вам это значение. Например, следующий код вернет вам значение поля Name:
<%=Request.Form("Name")%>
Request.Form применяется, когда данные были отправлены методом POST и именно этому сценарию. Если для отправки данных используется метод GET или сценарий вызывается с передачей параметров прямо в гиперссылке
<A HREF=/l5/data.asp?Name=NewTech&Email=info@newtech.com> Чтобы отправить данные, щелкните здесь!</A>
то для разбота данных применяют свойство Request.QueryString, который работает так же, как Request.Form. Следующий фрагмент кода вернет значение поля Name из гиперссылки:
<%=Request.QueryString("Name")%>
Другое свойство - Request.Cookies используются для извлечения информации из кукисов (cookies), отосланных вместе с запросом строке пользовательского агента программы просмотра. А листинг 4.5 демонстрирует применение свойства ServerVariables для определения имени компьютера, с которого клиент вызвал сценарий.
Листинг 4.5. Определение компьютера пользователя.
<%@ LANGUAGE="VBSCRIPT"%>
<html><head><title>Server Variables</TITLE>
</HEAD><BODY><CENTER>Вы вошли с компьютера
<%=Request.ServerVariables("Remote_Host")%>
</BODY></HTML>
Переменные сервера представляют широкий круг информации о клиенте и Web-сервере. Доступ к каждой конкретной переменной сводится к чтению соответствующей переменной.
Объект Response. Этот объект управляет содержимым страницы, которую ASP возвращает программе просмотра. Фактически в комбинации <%=переменная%> знак равенства представляет собой сокращенное обозначение метода Write объекта Response. Так что следующие две строки кода эквивалентны:
<%="NewTech"%>
<%Response.Write "NewTech"%>
Поскольку объект Response используется очень часто, такое сокращение оправдано.
Полезное свойство объекта Response - Expires. Оно задает время (в минутах) за которое страница устаревает. Если установить его в нуль, то страница будет устаревать в момент загрузки и Internet Explorer не будет ее кэшировать. Кэширование сильно влияет на разработку и может привести к тому, что приложение будет функционировать неправильно. IE кэширует страницы двумя способами: на диске и в памяти. Рассмотрим такой фрагмент кода, показывающий текущее время и дату:
<CENTER>Сейчас <%=Now%>
Когда IE запрашивают страницу с этим кодом, на сервере выполняется сценарий, и на странице появляется текущее время. Однако, если программа просмотра переходит к другой странице, а затем возвращается к этой, со временем, то время не изменится, поскольку IE не запрашивает ее повторно. В листинге 4.6 приведена исправленная версия примера, устаревающая уже в момент загрузки.
Листинг 4.6. Страница, устаревающая уже в момент загрузки.
<%@ LANGUAGE="VBSCRIPT"%>
<%Response.Expires=-1%>
<HTML><HEAD><TITLE>Forcing a Page to Expire</TITLE></HEAD><BODY>
<Н1>Сейчас <%Response.Write Now%>
</BODY> </HTML>
Еще один полезный метод объекта Response - Redirect, перенаправляющий программу просмотра на указанный URL:
<% Response.Redirect "enter.asp"%>
Объект Server. Объект Server (сервер) представляет собой в некотором роде свалку в том смысле, что предоставляемые им функции никак не связаны между собой, за тем исключением, что все они полезны разработчику для Интернета. Пожалуй, самая важная из всех функций объекта Server это метод CreateObject, который создает экземпляр компонента ActiveX. Причем это может быть как встроенный компонент, входящий в комплект поставки, так и тот, который написали Вы сами на любом языке. В любом случае использование компонента ActiveX на сервере требует вызова метода CreateObject.
Аргументом метода CreateObject служит ProgID (программный идентификатор) требуемого компонента ActiveX. ProgID это содержательное имя компонента, такое как Excel.Sheet или Word.Basic. Следующая строчка показывает, как при помощи CreateObject создать экземпляр компонента с ProgID Excel.Sheet.
Set MyObject = Server.CreateObject("Excel.Sheet")
Другим полезным методом объекта Server является MapPath, возвращающий фактический путь, соответствующий заданному виртуальному каталогу.
Компоненты.
Компоненты ASP это на самом деле просто компоненты ActiveX, наподобие тех, что Вы можете сами создать на Visual Basic или Visual C++. Но эти компоненты написаны Microsoft и поставляются вместе с Visual InterDev. Они предназначены для выполнения полезных для Web-узлов задач общего характера, включая доступ к данным. Создать их на своей странице Вы можете при помощи метода CreateObject объекта Server, а как только они созданы, смело обращайтесь к их свойствам и методам для выполнения нужных Вам задач.
Компонент ActiveX Data Objects. Самым полезным изо всех компонентов ASP следует признать компонент доступа к базам данных, называемый также ActiveX Data Objects, или сокращенно ADO. Он и содержащиеся в нем объекты применяются для чтения и записи данных в источники данных ODBC при публикации в Web информации из баз данных.
Объект Connection (подсоединение) создается методом CreateObject объекта Server, и ссылка на него помещается в переменную. Когда объект создан, его можно использовать для открытия подсоединения к любому источнику данных ODBC. Следующий фрагмент кода устанавливает подсоединение к источнику данных ODBC с названием Publications:
<%
' Объявляем переменную
Dim objConnection
' Создаем объект Connection
Set objConnection = Server.CreateObject("ADODB.Connection")
' Открываем подсоединение к источнику данных
objConnection.Open "Publications", "sa", "" %>
Здесь objConnection переменная для объектной ссылки на экземпляр объекта Connection. Метод Open устанавливает подсоединение, принимая в качестве аргументов имя источника данных, идентификатор пользователя и пароль.
Когда подсоединение установлено, получать информацию из источника данных можно при помощи объекта Recordset (набор записей). Этот объект умеет выполнять оператор SELECT языка SQL и возвращать набор записей, удовлетворяющих этому запросу. Как и объект Connection, Recordset создается методом CreateObject. В следующем примере программа выполняет оператор SELECT над источником данных, представленным переменной objConnection:
<%
' Объявляем переменную
Dim objRecordset
' Создаем объект
Recordset Set objRecordset =
Server.CreateObject("ADODB.Recordset")
' Выполняем запрос SQL
objRecordset.Open "SELECT *", objConnection
%>
После того, как записи получены, для передвижения по ним можно обращаться к методам MoveFirst, MoveLast, MoveNext и MovePrevious. Затем метод Write объекта Response помещает данные на Web-страницу, которая и посылается программе просмотра. В листинге 4.7 приведен полный пример ASP-страницы, которая строит список пользователей, содержащихся в источнике данных Data.
Листинг 4.7. Построение списка при помощи ADO.
<HTML><HEAD>
<META HTTP-EQUIV="Content-Type" content="text/html; charset=windows-1251">
<TITLE>Using ADO</TITLE></HEAD><BODY>
<%
'Объявляем переменные
Dim objConnection
Dim objRecordset
' cоздаем объекты
Set objConnection = Server.CreateObject("ADODB.Connection")
Set objRecordset = Server.CreateObject("ADODB.Recordset")
' Устанавливаем подсоединение и выполняем запрос
objConnection.Open "Data", "", ""
objRecordset.Open "SELECT Name FROM Users", objConnection
%>
<!-- Строим список SELECT по набору данных -->
<SELECT SIZE=8>
<% Do While Not ObjRecordset.EOF %>
<!-- Создаем очередной элемент списка -->
<OPTION><%=objRecordset("Name")%>
</OPTION>
<% objRecordset.MoveNext
Loop %>
</SELECT></BODY></HTML>
Одна из основных задач в управляемом данными Web-приложении управление информацией в объекте Recordset. Очень часто простой запрос возвращает гораздо больше данных, чем имеет смысл показывать. Давайте, например, посмотрим, что происходит, когда Вы обращаетесь к какой-либо поисковой системе в Интернете. Поисковая машина, получив ключевое слово, возвращает ссылки на узлы, где это ключевое слово встречается. Но зачастую обнаруживаются тысячи узлов, содержащих данное ключевое слово. Совершенно очевидно, что показать все эти узлы на одной Web-странице совершенно невозможно.
Выход состоит в разбиении на страницы (paging). Этот механизм реализован во всех поисковых системах для передачи за один раз некоей порции результатов запроса, скажем, из 10 записей. Теперь пользователь может эффективно работать с полученной информацией. Поддерживает разбиение на страницы и ADO посредством нескольких свойств объекта Recordset: PageSize, PageCount и AbsolutePage.
При получении набора данных можно указать, что записи следует разбить на страницы. Количество строк набора данных, составляющих страницу, задается значением свойства PageSize. Затем можно определить общее количество страниц в наборе данных посредством свойства PageCount. А доступ к заданной странице обеспечивает свойство AbsolutePage.
В листинге 4.8 приведен полный пример, в котором пользователь может просматривать 10 записей за раз. Переменная сессии CurrentPage отслеживает текущую страницу. Пользователь может перейти к предыдущей порции данных или к следующему набору из 10 записей, выбрав одну из двух гипертекстовых ссылок на странице.
Листинг 4.8. Разбиение набора данных на страницы средствами ADO.
<%@LANGUAGE="VBScript" %>
<%Response.Expires=-1 %>
<HTML><HEAD>
<META HTTP-EQUIV="Content-Type" content="text/html; charset=windows-1251">
<TITLE>Paging Records</TITLE> </HEAD> <BODY>
<%
' На какой мы странице?
Select Case Request.QueryString("Direction")
Case "" Session("CurrentPage") = 1
Case "Next" Session("CurrentPage") = Session("CurrentPage") + 1
Case "Prev" Session("CurrentPage") = Session("CurrentPage") - 1
End Select
' Константы
Const adOpenKeyset = 1
' Объявляем переменные
Dim objConnection
Dim objRecordset
' Открываем базу данных
Set objConnection = Server.CreateObject("ADODB.Connection")
objConnection.Open "Data", "", ""
' Конструируем оператор SQL
Dim strSQL
strSQL ="SELECT Name, About FROM Users"
' Создаем набор данных
Set objRecordset = Server.CreateObject("ADODB.Recordset")
objRecordset.PageSize = 10
objRecordset.Open strSQL, objConnection, adOpenKeyset
objRecordset.AbsolutePage = CLng(Session("CurrentPage"))
' Выводим результаты
%>
<P>Page <%=Session("CurrentPage")%> of
<%=objRecordset.PageCount%></P>
<TABLE BORDER>
<TR><TH>Пользователь</TH><TH>Сведения</TH>
</TR><%
Dim i
For i = 1 To objRecordset.PageSize
if NOT objRecordset.EOF Then
%>
<TR><TD><%=objRecordset("Name")%></TD>
<TD><%=objRecordset("About")%></TD>
</TR> <%
objRecordset.MoveNext
end if
Next %>
</TABLE>
<!-- Ссылка на СЛЕДУЮЩУЮ страницу -->
<% If CLng(Session("CurrentPage")) < objRecordset. PageCount Then %>
<P><A HREF="e8.asp?Direction=Next">Следующая страница</A></P>
<%End If%>
<!-- Ссылка на ПРЕДЫДУЩУЮ страницу -->
<% If CLng(Session("CurrentPage")) > 1 Then %>
<P><A HREF="e8.asp?Direction=Prev">Предыдущая страница</A></P>
<%End If%>
<%
' Закрываем базу данных
objRecordset.Close
objConnection.Close
Set objRecordset = Nothing
Set ObjConnection = Nothing %>
</BODY></HTML>
В этом примере применяются определенные приемы, которые стоит обсудить подробнее. Обратите внимание, что весь процесс разбиения на страницы обеспечивается одним ASP-файлом. Для каждой страницы данных вновь вызывается тот же самый ASP-файл. Обычно при вызове страницы Internet Explorer получает ее из памяти клиентского компьютера. В нашем примере страница всегда будет находиться в памяти, поскольку вызывается постоянно. Налицо проблема: ведь запрос выполняется только тогда, когда выполняется код на сервере. Поэтому мы должны предотвратить использование ASP-файлов из памяти. Для этого следует установить свойство Expires объекта Response в нуль. Тогда файл будет обновляться при каждом новом обращении к странице, что нам и требуется.
Обратите также внимание, что при каждом обращении к странице выполняется один и тот же запрос, меняется только свойство Absolute-Page. Такая стратегия кажется достаточно расточительной, но требует меньше ресурсов, чем запоминание объектов Recordset большого объема в переменных сессии и хранение их в период между обращениями к странице. Представьте себе узел с тысячами пользователей, у каждого из которых в переменной сессии хранится объект Recordset. Ресурсы сервера будут моментально истощены.
При помощи ADO можно выполнять доступ к данным на сервере на основе любого оператора SQL. Запросы можно закодировать как хранимые процедуры SQL-сервера или непосредственно в виде SQL-операторов SELECT. Модифицировать данные можно при помощи SQL-операторов UPDATE и метода Execute объекта Connection. ADO может применяться и на клиентской стороне в сочетании с Advanced Data Control (ADC). По сути ADC очень мало отличается от просто ActiveX-оболочки вокруг большей части функциональности ADO. Основную роль в доступе к данным посредством ADC играет его свойство Recordset.
Компонент File Access. Компонент File Access (компонент доступа к файлам) предоставляет доступ к текстовым файлам на Web-узле. На самом деле он состоит из двух отдельных объектов: FileSystem, который служит для открытия и закрытия файлов, и TextStream, предназначенного для чтения и записи.
Итак, чтобы получить доступ к файлу, сначала создайте (методом CreateObject объекта Server) объект FileSystem. Затем для создания нового файла следует вызвать метод CreateTextFile только что созданного объекта, а для открытия существующего файла метод OpenTextFile. В любом случае оба метода вернут объект TextStream. Ниже показано, как получить доступ к файлу DATA.TXT:
Set objFile = Server. CreateObject("Scripting. FileSystemObject") Set objStream = objFile.OpenTextFile("DATA.TXT")
После создания объекта TextStream можно свободно вызывать любые его методы для чтения или записи информации. Не забывайте, однако, что обычно файл открывается только для чтения или только для записи, но не для того и другого одновременно. Этот факт требует определенной организации кода ASP-страниц: если над одним и тем же файлом необходимо выполнить разные операции, его следует открыть, закрыть и вновь открыть.
Помимо просто чтения и записи компонент File Access служит для создания динамического информационного наполнения. Давайте попробуем реализовать генератор «дежурных советов» (tip-of-the-day), выводящий подсказки или сообщения, которые обновляются при каждой новой загрузке страницы. Полный текст примера, посвященного советам по программированию на Visual Basic, приведен в листинге 4.9. Главное в этой задаче написать текстовый файл, каждая строчка которого содержит одну подсказку. Затем эти строчки считываются по одной в случайном порядке методом ReadLine объекта TextStream и помещаются на страницу методом Write объекта Response.
Листинг 4.9. Генерация «дежурного совета».
<HEAD>
<META HTTP-EQUIV="Content-Type" content="text/html; charset=windows-1251">
<TITLE>VB Tips</TITLE> </HEAD>
<BODY BGCOLOR="#FFFFFF">
<%
' Объявляем переменные
Dim objFile
Dim objStream
' Открываем файл
Set objFile =Server.CreateObject("Scripting.FileSystemObject")
Set objStream =objFile.OpenTextFile(Server.MapPath("/scripts") &_ "\web\l5\tips.txt")
Randomize Timer
intLine = Int(Rnd * 19)
For i = 0 to intLine
objStream.SkipLine
Next
strText = objStream.ReadLine
objStream.Close
Set objStream = Nothing
Set objFile = Nothing
%>
<CENTER><H1>Дежурный совет по VB</H1></CENTER>
<%=strText%>
</BODY></HTML>
Компонент Browser Capabilities. Компонент Browser Capabilities (компонент характеристик программы просмотра) идентифицирует программу просмотра Web, которая в данный момент осуществляет доступ к узлу, а также предоставляет программный доступ к ряду поддерживаемых этой программой возможностей. Это огромная ценность для Web-разработчиков, которые должны поддерживать как Internet Explorer, так и Netscape Navigator. При помощи этого компонента можно приспособить Web-страницу к определенному типу программы просмотра.
Действие компонента Browser Capabilities основано на строке пользовательского агента программы просмотра, в которой указан тип последней. Эту строку программа просмотра передает серверу каждый раз при запросе Web-страницы. Для IE 4.0 строка выглядит так:
Mozilla/4.0 (compatible; MSIE 4.0; Windows 95)
Чтобы определить, какие возможности поддерживает данная программа просмотра, компонент пытается найти соответствие полученной строке среди образцов в специальном файле инициализации BROWSCAP.INI. Как только такой образец найден, все возможности, свойственные этой программе просмотра, немедленно становятся доступны как свойства компонента характеристик. В листинге 4.10 приведен фрагмент файла BROWSCAP.INI, соответствующий IE 4.0.
Листинг 4.10. Фрагмент файла BROWSCAP.INI для IE 4.0.
Mozilla/4.0 (compatible; MSIE 4.0; Windows 95)
browser=IE
Version=4.0
majorver=#4
minorver=#0
frames=TRUE
tables=TRUE
cookies=TRUE
backgroundsounds=TRUE
vbscript=TRUE
javascript=TRUE
javaapplets=TRUE
ActiveXControls=TRUE
Win16=False
beta=False
Использование компонента Browser Capabilities заменяет в ASP-страницах операторы условной компиляции. Можно построить такие простые операторы If...Then, которые очень заметно влияют на конечный результат. Так, код из листинга 4.11 посылает тег <OBJECT> программе просмотра, поддерживающей элементы управления ActiveX, тег <APPLET> программе просмотра с поддержкой Java и текстовое сообщение всем остальным.
Листинг 4.11. Определение характеристик программы просмотра Web.
<%@LANGUAGE="VBScript"%>
<%Response.Expires=-1%>
<HTML><HEAD>
<HETA HTTP-EQUIV="Content-Type" content="text/html; charset=windows-1251">
<TITLE>Browser Capabilities</TITLE> </HEAD> <BODY> <CENTER>
<%
' Создаем компонент Browser Capabilities
Dim objBrowser
Set objBrowser = Server.CreateObject("MSWC.BrowserType")
' Выясняем, какие возможности поддерживаются
If objBrowser.ActiveXControls Then
%>
<H1>Элементы управления ActiveX</H1>
<OBJECT ID="mylabel" WIDTH="300" HEIGHT="51"
CLASSID="CLSID:978C9E23-D4B0-11CE-BF2D-00AA003F40D0"
CODEBASE="http://www.microsoft.com/activex/controls/FM20.DLL">
<PARAM NAME="ForeColor" VALUE="98776">
<PARAM NAME="VariousPropertyBits" VALUE="276824091">
<PARAM NAME="Caption" VALUE="Щелкни меня!">
<PARAM NAME="Size" VALUE="7691;1094">
<PARAM NAME="SpecialEffect" VALUE="1">
<PARAM NAME="FontEffects" VALUE="1073741827">
<PARAM NAME="FontHeight" VALUE="480">
<PARAM NAME="FontCharSet" VALUE="204">
<PARAM NAME="ParagraphAlign" VALUE="3">
<PARAM NAME="FontWeight" VALUE="700">
</OBJECT>
<%ElseIf objBrowser.JavaApplets Then%>
<H1>Апплет Java</H1>
<applet code="marquee.class" codebase="http://inna/scripts/web/l5" align="baseline" HEIGHT=40 WIDTH=400>
<PARAM NAME="CAPTION" VALUE="Java is Cool!">
</APPLET>
<%Else%>
<!-- Чисто текстовая программа просмотра -->
<H1>Никакие компоненты не поддерживаются!</Н1>
<%End If%>
</CENTER> </BODY> </HTML>
Компонент Ad Rotator. Компонент Ad Rotator (ротация рекламных объявлений) специально предназначен для узлов, продающих рекламную площадь. Он позволяет управлять ротацией рекламных картинок на узле. Ad Rotator считывает информацию о рекламных объявлениях из специального текстового файла и указывает, какое объявление следует показывать и как долго. Использование данного компонента сводится к его созданию и чтению текстового файла, как в следующем фрагменте кода:
<%
Dim Ad
Set Ad = Server.CreateObject("MSWC.AdRotator")
Response.Write Ad.GetAdvertisement("ADS.TXT")
%>
Файл, считываемый компонентом Ad Rotator, имеет четко заданную структуру, которая определяет, какое изображение выводить и какую часть времени его показывать, а также обеспечивает гипертекстовую ссылку, активизируемую при щелчке данного объявления. От Вас требуется только составить текстовый файл определенного формата, а компонент сделает все остальное.
Компонент Content Linking. Компонент Content Linking (компонент связывания содержания) предназначен для публикаций электронных журналов и газет. Он связывает вместе несколько Web-страниц, позволяя их прокручивать. Как и Ad Rotator, компонент Content Linking для создания публикации использует текстовый файл. Этот файл, известный как список информационных ссылок (Content Linking List), содержит список связанных Web-страниц и их описаний. Использование данного компонента сводится к его созданию и последующему считыванию ассоциированного текстового файла, как в следующем фрагменте кода:
<%Set objLinker = Server.CreateObject("MSWC.NextLink")%>
Когда публикация скомпонована, для перемещения по страницам используются методы GetNextURL и GetPreviousURL, а описания каждой конкретной страницы можно получить, вызвав методы GetNextDescription и GetPreviousDescription. Полученные значения служат для генерации ссылок на другие страницы публикации. Пример такой ссылки:
<А HREF="<%=objLinker.GetNextURL%>">
<%=objLinker.GetNextDescriptiion%> </A>
Использование других компонентов ActiveX. Кроме использования компонентов, поставляемых с Visual InterDev, Вы можете создавать свои собственные компоненты ActiveX для ASP. Когда такой компонент разработан, работать с ним можно посредством метода CreateObject объекта Server. Все, что нужно - это указать его ProgID.
Написание собственных компонентов позволяет расширить возможности ASP.
Доступ к базам данных.
Хотя доступ к базам данных средствами ADO уже обсуждался, уделим еще некоторое время этому вопросу. Дело в том, что приведенные ранее примеры работоспособны, только если на сервере создан источник данных ODBC. Его создание обычно производится апплетом Панели управления Windows, который при удаленной работе с сервером оказывается недоступен. Для выхода из этой ситуации применяют файловый DSN (Data Source Name), создав который, можно скопировать файл с расширением .dsn в тот же каталог, что и сценарий, обращающийся к базе данных. В листинге 4.12 приведен пример файла для доступа к базе данных MS Access, а в листинге 4.13 - для доступа к БД MySQL.
Листинг 4.12. Файл для доступа к БД MS Access'97.
[ODBC]
DRIVER=Microsoft Access Driver (*.mdb)
UID=admin
UserCommitSync=Yes
Threads=3
SafeTransactions=0
ReadOnly=0
PageTimeout=5
MaxScanRows=8
MaxBufferSize=512
ImplicitCommitSync=Yes
FIL=MS Access
DriverId=281
DefaultDir=C:\Inetpub\scripts\bookcd
DBQ=C:\Inetpub\scripts\bookcd\bakal1.mdb
Сценарий, использующий этот файл, приведен в листинге 4.14.
Листинг 4.13. Файл для доступа к БД MySQL.
[ODBC]
DRIVER=MySQL ODBC 3.51 Driver
UID=gun
STMT=
OPTION=3
PORT=
PASSWORD=
SERVER=inna
DATABASE=test
Листинг 4.14. Сценарий формирования DSN.
<%
Dim sDB, sPath, sDSNFil, sDSN, sScriptDir
'This is the entire DB path
sDB = "C:\Inetpub\scripts\bookcd\bakal1.mdb"
' Retrieve the script directory
sScriptDir = Request.ServerVariables("SCRIPT_NAME")
sScriptDir = StrReverse(sScriptDir)
sScriptDir = Mid(sScriptDir, InStr(1, sScriptDir, "/"))
sScriptDir = StrReverse(sScriptDir)
' Set the virtual Directory
sPath = Server.MapPath(sScriptDir) & "\"
' This is the DSN file Name for Access database
sDSNFil = "Access.dsn" ' This is the resulting DSN string
sDSN="filedsn="&sPath&sDSNFil&";DefaultDir="&sPath&";DBQ="&sPath&sDB&";"
%>
Чтобы не вставлять данный код в каждый сценарий, в котором требуется обращение к базе данных, применяют файлы включения (подобно include-файлам в языке С):
<!-- #include file ="dsn.asp" -->
<!-- #include file="adovbs.inc" -->
Заметим, что файл adovbs.inc не может быть вызван, как сценарий, хотя и содержит код. Как правило, в файлы с расширением .inc помещают часто употребляемые константы и функции, как это показано в листинге 4.15:
Листинг 4.15. Файл включения.
<%
' ADO constants include file for VBScript
'---- CursorTypeEnum Values ----
Const adOpenForwardOnly = 0
Const adOpenKeyset = 1
Const adOpenDynamic = 2
Const adOpenStatic = 3
%>
<FONT FACE="Verdana, Arial" SIZE=2>
<FORM ACTION="<% =sScript%>" METHOD=GET>
<SELECT NAME=YEAR>
<OPTION VALUE=1998 SELECTED>1998
<OPTION VALUE=1999>1999
<OPTION VALUE=2000>2000
<OPTION VALUE=2001>2001
<OPTION VALUE=2002>2002
</SELECT></FONT>
<P><INPUT TYPE="Image" SRC="images/go.gif" BORDER="0" WIDTH="35" HEIGHT="20"> </P>
</FORM>
В заключение приведем в листинге 4.16 законченный пример сценария авторизации посетителя с извлечением регистрационного имени и пароля из базы данных.
Листинг 4.16. Сценарий авторизации посетителя.
<!-- #include file ="dsn.asp" -->
<!-- #include file="adovbs.inc" -->
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<title>Идентификация пользователя</title></head>
<body BGCOLOR="#CCCCFF" TEXT="#0000FF" LEFTMARGIN="3">
<%
If not isEmpty(Session("userlevel")) then
Session("userlevel") = Empty
Session("userid") = Empty
end if
user = Request.Form.Item("T1")
pswd = Request.Form.Item("T2")
b1 = Request.Form.Item("B1")
If not isEmpty(user) and not isEmpty(pswd) then
Set Rs = Server.CreateObject("ADODB.RecordSet")
sSQL = "SELECT Users.Pswd, Users.Access_ID, Users.User_ID FROM Users WHERE Users.Login = '"&user&"'"
Rs.Open sSQL, sDSN
if not RS.EOF then
if RS.fields(0).value = pswd then
Session("userlevel") = RS.fields(1).value
Session("userid") = RS.fields(2).value
Response.Redirect("menu.asp")
end if
end if
Session("userlevel") = 3
Response.Redirect("menu.asp")
end if
%>
<form method="POST" action="enter.asp">
<center>
<table border="0" width="50%" cellspacing="1" cellpadding="4">
<tr><td width="100%" colspan="2" bgcolor="#000080">
<b><font size="4">Идентификация пользователя </font> </b></td> </tr>
<tr><td width="50%" align="right">Имя:</td>
<td width="50%"><input type="text" name="T1" size="20"></td>
</tr>
<tr><td width="50%" align="right">Пароль:</td>
<td width="50%"><input type="password" name="T2" size="20"></td>
</tr>
<tr><td width="100%" align="right" colspan="2">
<center><input type="submit" value="Войти" name="B1" ></td>
</tr>
</table></center>
</form></body></html>
Доступ к базам данных MySQL осуществляется аналогично, с использованием файлового источника данных (листинг 4.13) и модификации сценария доступа (листинг 4.14), где последняя строка имеет вид:
sDSN = "filedsn=" & sPath & sDSNFil & ";"
Ниже приводится аналог листинга 4.10, но обращающегося к базе данных MySQL (из примера 3.14).
Листинг 4.17. Сценарий доступа к БД MySQL.
<%@ LANGUAGE="VBScript"%>
<!-- #include file ="e14a.asp" -->
<HTML><HEAD>
<META HTTP-EQUIV="Content-Type" content="text/html; charset=windows-1251">
<TITLE>Using MySQL</TITLE></HEAD><BODY>
<%
Dim objRecordset
Set objRecordset = Server.CreateObject("ADODB.Recordset")
' Устанавливаем подсоединение и выполняем запрос
objRecordset.Open "SELECT name FROM people", sDSN
%><!-- Строим список SELECT по набору данных -->
<SELECT SIZE=3>
<% Do While Not ObjRecordset.EOF %>
<!-- Создаем очередной элемент списка -->
<OPTION><%=objRecordset("name")%></OPTION>
<% objRecordset.MoveNext
Loop %>
</SELECT></BODY></HTML>
Операторы, функции и подпрограммы. Модули. CGI-программирование.
Примеры приложений. Доступ к базам данных
Прежде чем приступить к последовательному ознакомлению с новым для вас языком, должен оговориться и сказать, что все примеры да и сам язык, описание которого следует ниже - это Perl версии 5.6.0 для операционной системы Linux Red Hat версии 7.1 и ActivePerl 5.6.1 для Windows 9x/NT/2000. Существуют реализации этого языка для операционных систем OS/2 , MS-DOS, но они отстают по возможностям от оригинала, рожденного в Unix.
На пятнадцатый год своего существования Практический Язык для Извлечения текстов и Генерации отчетов (Practical Extraction and Reporting Language) по-прежнему популярен не только среди линуксоидов, но и среди Web-программистов.
Начнем с самого простого. Введите в файл test1.pl следующие строки:
#!/usr/local/bin/perl
# Содержимое файла test1.pl
print "Наше Вам с кисточкой!\n";
А теперь подробно разберем каждую строку.
#!/usr/local/bin/perl
Данная строка должна быть первой в любой Perl-программе. Она указывает системному интерпретатору, что данный файл - это Perl-программа.
# Содержимое файла test1.pl
Эта строка комментария. Она всегда начинается символом '#'.
print "Наше Вам с кисточкой!\n";
Самая последняя строка просто выводит на экран надпись "Наше Вам с кисточкой!".
Здесь слово print - это команда "вывести". Все что в кавычках - это символы, \n - перевод строки и ';' - признак конца команды. Он обязателен. В одной строке может быть несколько команд и все они должны завершаться символом ';'. После него может быть символ '#' - тогда остаток строки считается комментарием.
Чтобы этот пример заработал, вам надо иметь установленный Perl и набрать в командной строке: perl test1.pl (в Windows) или ./test.pl (в *nix).
Синтаксис Perl.
Perl программа (скрипт) состоит из последовательности деклараций и предложений. Что должно быть обязательно декларировано, так это форматы отчетов и подпрограммы (функции). Все необъявленные переменные, массивы имеют значение 0 или null.
Декларации (объявления). Perl имеет свободный формат. Комментарии начинаются с символа '#' и продолжаются до конца строки. Декларации могут использоваться в любом месте программы так же как и предложения (statements), но действуют они только в фазе компиляции программы. Обычно их помещают или в начале или в конце программы. Декларация подпрограмм позволяет использовать имя подпрограммы как списковый оператор, начиная с момента декларирования:
sub test; # Декларация подпрограммы test
$var1 = test $0; # Использование как оператора списка.
Декларации подпрограмм могут быть загружены из отдельного файла предложением require или загружены и импортированы в текущую область имен предложением use.
Простое предложение. Простое предложение обязательно заканчивается символом ';', если только это не последнее предложение в блоке, где ';' можно опустить. Существуют операторы, такие как eval{} и do{}, которые выглядят как сложные предложения, но на самом деле это термы и требуют обязательного указания конца предложения.
Любое простое предложение может содержать модификатор перед ';'. Существуют следующие модификаторы:
if EXPR, unless EXPR, while EXPR, until EXPR
где EXPR - выражение, возвращающее логическое значение true или false. Модификаторы while и until вычисляются в начале предложения, кроме do, который выполняется первым.
if EXPR- Модификатор "если". Предложение выполняется, если EXPR возвращает true.
$var = 1;
if $var > 0 $var2 = 3; # Результат: $var2 = 3
while EXPR - Модификатор "пока". Предложение выполняется пока EXPR = true
$var = 1;
print $var++ while $var < 5; # Результат: 1234
until EXPR- Модификатор "до ". Предложение выполняется до тех пор пока EXPR = false
$var = 1;
print $var++ until $var > 5; # Результат: 12345
unless EXPR - Модификатор "если не". Обратный к if. Выражение выполняется, если EXPR = false.
$var = 1;
print $var++ unless $var > 5; # Результат: 1
Сложные предложения. Последовательность простых предложений, ограниченная функциональными ограничителями, называется блоком. В Perl это может быть целый файл, последовательность предложений в операторе eval{} или чаще всего это множество простых предложений, ограниченных круглыми скобками '{}'.
Существуют следующие виды сложных предложений:
if (EXPR) BLOCK, if (EXPR) BLOCK else BLOCK, if (EXPR) BLOCK elsif (EXPR) BLOCK ... else BLOCK
LABEL while (EXPR) BLOCK, LABEL while (EXPR) BLOCK continue BLOCK
LABEL for (EXPR; EXPR; EXPR) BLOCK, LABEL foreach VAR (LIST) BLOCK
LABEL BLOCK continue BLOCK
Обратите внимание, что сложные предложения описаны в термах блоков, а не предложений, как в языке C. Поэтому необходимо использовать круглые скобки для обозначения блока.
if (EXPR) BLOCK - Вычисляется логическое выражение EXPR и если true блок выполняется.
$var =1;
if ($var == 1)
{ print $var,"\n";
}
Результат: 1
if (EXPR) BLOCK else BLOCK2 - Если EXPR=true выполняется BLOCK иначе BLOCK2.
$var =2;
if ($var == 1)
{ print "\$var = 1\n";
}
else
{ print "\$var не равно 1\n";
}
Результат: $var не равно 1
if (EXPR1) BLOCK1 elsif (EXPR2) BLOCK2 ... else BLOCK - Если EXPR1=true выполняется BLOCK1 иначе если EXPR2=true выполняется BLOCK2 иначе ... иначе BLOCK.
$var = 1;
if ($var == 0)
{ print "\$var = 0\n";
}
elsif ($var == 1)
{ print "\$var = 1\n";
}
else
{ print "Не известное \$var\n";
}
Результат: $var = 1
Цикл While. Выполняет BLOCK до тех пор пока EXPR = true. Метка LABEL не обязательна и состоит из идентификатора, завершающегося символом ':'. Метка необходима при использовании внутри блока цикла управляющих операторов next, last и redo. Если метка отсутствует, то эти операторы ссылаются к началу ближайшего цикла. Блок после continue выполняется всегда перед тем, как вычисляется логическое выражение EXPR. Это подобно EXPR3 в предложении for, поэтому в этом блоке удобно изменять счетчики и флаги цикла, даже если применяется оператор next.
Операторы управления циклом. next - подобен continue в С. Переходит к началу текущего цикла, т.е. повторяет итерацию.
M1:
while ($i < 6)
{
++$i; # Увеличиваем счетчик на 1
next M1 if $i < 3; # Переходим в начало если $i < 3
++$i; # иначе увеличиваем счетчик еще раз на 1
}
continue
{
print "$i "; # Результат: 1 2 4 6
}
last - подобен оператору break в языке С. Немедленно прерывает цикл. Блок continue пропускается.
M1:
while ($i < 6)
{
++$i; # Увеличиваем счетчик на 1
last M1 if $i > 3; # Выход из цикла если $i > 3
++$i; # иначе увеличиваем счетчик еще раз на 1
}
continue {
print "$i "; # Результат: 2 4
}
redo - начать новый цикл, не вычисляя EXPR и не выполняя continue блок.
M1:
while ($i < 6)
{
++$i; # Увеличиваем счетчик на 1
redo M1 if $i == 3; # Далее пропустить для $i = 3
++$i; # иначе увеличиваем счетчик еще раз на 1
}
continue {
print "$i "; # Результат: 2 5 7
}
Цикл for. LABEL for (EXPR1; EXPR2; EXPR3) BLOCK. Оператор for полностью аналогичен оператору for в С. Перед началом цикла выполняется EXPR1, если EXPR2 = true выполняется блок, затем выполняется EXPR3.
for ($i = 2; $i < 5; ++$i){
print $i, " "; # Результат: 2 3 4
}
print "\nПосле цикла i = $i\n"; # После цикла i = 5
Цикл foreach. LABEL foreach VAR (LIST) BLOCK. Переменной VAR присваивается поочередно каждый элемент списка LIST и выполняется блок. Если VAR опущено, то элементы присваиваются встроенной переменной $_. Если в теле блока изменять значение VAR то это вызовет изменение и элементов списка т.к. VAR фактически указывает на текущий элемент списка. Вместо слова foreach можно писать просто for - это слова синонимы.
@month = ("январь","февраль","март"); # Создали массив
foreach $i (@month)
{ print $i," "; # Результат: январь февраль март
}
foreach $i (@month)
{ $i = uc($i); # Перевели в верхний регистр
}
print @ month; # Результат: ЯНВАРЬФЕВРАЛЬМАРТ
for $i (3,5,7)
{ print "$i "; # Результат: 3 5 7
}
Блоки и оператор switch. Блок не зависимо от того имеет он метку или нет семантически представляет собой цикл который выполняется один раз. Поэтому действие операторов цикла next, last, redo - аналогично описанному выше. Блоки удобны для построения switch (переключатель) структур. В Perl нет специального оператора switch подобного языку С поэтому вы сами можете создавать удобные для вас конструкции. Опыт автора показывает что для простоты написания лучше всего подходит конструкция вида if ... elsif ... else ... хотя можно сочинить и нечто подобное:
SWITCH:
{
if ($i ==1 ) { .....; last SWITCH; }
if ($i ==2 ) { .....; last SWITCH; }
if ($i ==3 ) { .....; last SWITCH; }
$default = 13;
}
Выбирайте сами по своему вкусу.
Оператор goto. В Perl существует оператор goto. При создании больших производственных задач на последнем этапе, особенно при отработке ошибочных ситуаций конечно goto нужен.
В Perl реализовано три формы goto. goto - метка, goto - выражение и goto - подпрограмма.
goto метка - выполняет непосредственный переход на указанную метку.
goto - выражение - Вычисляет имя метки и делает соответствующий переход. Например, если мы хотим сделать переход на одну из трех меток "M1:", "M2:" или "M3:" в зависимости от значений переменной $i равной 0, 1 или 2 то это лучше сделать следующим образом:
goto ("M1", "M2", "M3")[$i];
здесь $i используется как индекс массива указанного непосредственно в выражении.
goto подпрограмма - довольно редкий случай т.к. всегда проще и надежней вызвать подпрограмму "естественным" образом.
POD операторы. Документирование программ. В Perl реализован очень удобный механизм для написания документации в момент создания программы. Для этого применяются специальные POD операторы. Если в теле программы интерпретатор встречает оператор начинающийся с символа '=' например:
= head Набор стандартных процедур
то пропускается все до слова '=cut'. Это удобно для включения длинных на несколько строк или страниц комментариев. Затем с помощью специальной программы pod можно отделить текст документации от текста программы.
Переменные.
В Perl существует три типа структур данных: скаляры, массивы скаляров и хеши (hashes) - ассоциативные массивы скаляров. Обычно элементы массивов индексируются целыми числами, первый элемент - нулевой. Отрицательное значение индекса обозначает номер позиции элемента с конца. Хеши индексируются строками символов.
Имена скаляров всегда начинаются с символа '$' - даже когда обозначают элемент массива:
$var1 # Простой скаляр 'var1'
$var1[0] # Первый элемент массива 'var1'
$var1{'first'} # Элемент с индексом 'first'
В случае использования имени массива "целиком" или его "среза" перед именем массива ставится символ '@':
@var1 #Все элементы массива var1 ($var1[0],$var1[1],..., $var1[n])
@var1[1,3,10] # Элементы $var1[1], $var1[3], $var1[10]
@var1{'first','last'} #то же что и ($var1{'first'}, $var1{'last'})
Хеш "целиком" начинается с символа '%':
%var, %key, %years
Имена подпрограмм начинаются символом '&', если из контекста не видно, что это подпрограмма:
&sub1, &test_prog, test(12)
Имена таблиц символов всегда начинаются символом '*'.
Каждый тип переменных имеет свою область памяти поэтому $var1 и $var1[0] совершенно разные переменные, хотя $var1[0] часть массива @var1. Так же @var1 и %var1 - разные массивы переменных.
Имена переменных могут содержать любые буквенно-цифровые символы за исключением пробела и табуляции. Эти символы используются в качестве разделителей. Большие и малые буквы различаются поэтому $var1 и $Var1 - разные переменные. В Perl по умолчанию имена меток и указателей файлов пишут большими буквами.
Контекст. Большое значение для правильного употребления встроенных функций имеет контекст использования результата этих функций, т.к. в противном случае они возвращают совершенно "непонятный" результат. В Perl имеется два главных контекста: скалярный и список (list). Если в левой части выражения имеется ввиду одно единственное значение, то это скалярный контекст. Если множество значений - то список:
$var1 = <>; # Прочитать одну строку файла
@var1 = <>; # Прочитать все строки файла в массив @var1
$var1 = (1,2,3); # $var = 3 - количество элементов
@var1 = (1,2,3); # Создание массива @var1 с элементами 1,2,3
Скалярные значения. Все данные в Perl это скаляры, массивы скаляров и хеши скаляров. Скалярные переменные могут содержать числа, строки и ссылки. Преобразование числа - строки происходит автоматически по умолчанию. Скаляр может иметь только одно единственное значение, хотя это может быть ссылка на массив скаляров. Так как Perl сам преобразовывает числа в строки и наоборот, то программисту нет необходимости думать о том, что возвращает функция.
В Perl не существует типов "строка" или "число" или "файл" или что-то еще. Это контекстно зависимый полиморфный язык для работы с текстами. Скаляр имеет логическое значение "TRUE" (истина), если это не нулевая строка или число не равное 0.
В Perl существует два типа нулевых (null) скаляров - определенные (defined) и неопределенные (undefined). Неопределенное значение возвращается, когда что-то не существует. Например, неизвестная переменная, конец файла или ошибка. С помощью функции defined() вы можете заранее обнаружить подобное состояние.
Количество элементов массива так же является скаляром и начинается символами $#. Фактически $#var1 - это индекс последнего элемента массива. Нужно помнить, что первый элемент имеет индекс 0, поэтому количество элементов определяется как $#var1+1 . Присвоение значения $#var1 изменит длину массива и разрушит "оставленные" значения. Присвоение значения элементу массива с индексом больше чем $#var1 увеличит размер массива, а присвоение ему нулевого списка - обнулит.
В скалярном контексте имя массива возвращает его длину (для списка возвращается последний элемент):
@var1 = (4, 3, 2, 1);# Присвоение значения элементам массива
$i = @var1; # Использование скалярного контекста
print $i; # Печать результата 4 - кол-во элементов
print @var1; # Списковый контекст, печать всех элементов.
Для принудительного получения скалярного значения удобно применять функцию scalar():
print scalar(@var1);# Вывод длины массива а не его значений
Хеш в скалярном контексте возвращает "true", если существует хотя бы одна пара "ключ-значение". Фактически возвращается строка типа 2/8 где 8 - количество выделенных "ячеек" памяти, а 2 - количество использованных.
Конструкторы скаляров. Числа пишутся стандартно:
123
123.123
0.12
.12E-10
0xABCD # Шестнадцатиричная запись
0377 # Если 0 в начале - восьмеричная
123_456_123 # Так тоже можно для удобства чтения.
Строки ограничиваются одинарными (') или двойными (") кавычками:
'Равняйсь, смирно!' или "Построимся и спасемся."
В хеше можно опускать кавычки, если индекс не содержит пробелов:
$var1{first} то же что и $var1{'first'}
Обратите внимание на то, что перед первой одинарной кавычкой должен стоять пробел, иначе строка воспримется как имя переменной, т. к. в именах разрешено использование одинарных кавычек. Запрещается в кавычках применять зарезервированные литералы __LINE__ (номер текущей строки программы), __FILE__ (текущий файл). Для обозначения конца программы можно применять литерал __END__. Весь последующий текст игнорируется, но его можно прочитать, используя указатель файла DATA.
Слова в программе, не поддающиеся никакой интерпретации, воспринимаются как строки в кавычках, поэтому рекомендуется имена меток и указателей файлов писать большими буквами во избежание возможного "конфликта" с зарезервированными словами.
В Perl есть возможность вставлять текст документа прямо в программу, используя "here-doc" (здесь текст) метод. Обозначается символами <<, за которыми идет слово-ограничитель:
print <<EOF; # Все строки до EOF - текст для печати.
Эй вы трое, идите оба сюда!
Что стоишь! Я тебе говорю!!
Полковник Савонькин.
EOF
Конструкторы списков. Список - множество значений, перечисленных через запятую и заключенных в круглые скобки. В списковом контексте список возвращает последний элемент списка:
@var1 = (1, 2, 'привет', 1.2); # Присвоить значение элементам.где
$var1[0] = 1,
$var1[1] = 2,
$var1[2] = 'привет'
$var1[3] = 1.2
$var1 = (1, 2, 'привет', 1.2);
а здесь $var1 = 1.2 т.е. последнее значение списка.
Допускается применять в списке другие списки, но в полученном списке уже невозможно различить начало и конец включенных списков:
@s1 = (1, 2, 3); # Первый список
@s2 = (6, 7, 8); # Второй
@s = (0, @s1, 4, 5, @s2, 9, 10); # Включаем списки @s1 и @s2
print @s; # Результат: 012345678910 - значения без пробелов.
Список без элементов обозначается, как (), и называется нуль-списком. Списковое выражение можно употреблять как имя массива, но при этом брать в круглые скобки:
print ('январь','февраль','март')[1];
Результат: февраль
Список может быть присвоен списку, только если каждый элемент в списке в левой части выражения допустим по типу списку в правой части:
($a, $b, $c) = (1, 2, 3); # $a = 1, $b
Встроенные переменные Perl.
Описанные ниже переменные имеют в Perl специальные значения. Они обозначаются несколько непривычно для "глаза" программистов, т.к. состоят обычно только из двух символов, причем первый это '$' символ, с которого начинаются имена всех переменных, и произвольный часто не буквенно-цифровой символ. Если вы хотите пользоваться их "нормальными" буквенными синонимами, то вам нужно указать в начале программы:
use English;
Ниже приводятся имена встроенных переменных как в короткой, так и в длинной (словесной) форме. Некоторые из них имеют доступ только на чтение, поэтому изменить их значение невозможно.
$_ ($ARG) - переменная - по умолчанию для операторов ввода и поиска. То есть если в качестве аргумента не указана никакая переменная, то используется именно эта.
$цифра - содержит найденную в последнем поиске подстроку, когда шаблон содержит метасимволы в круглых скобках. Цифра - это номер скобок. Первая подстрока - номер 1.
$& ($MATCH) - найденная подстрока в последнем поиске по шаблону.
$` - подстрока, предшествующая найденной подстроке.
$' ($POSTMATCH) - подстрока, последующая за найденной подстрокой.
$+ ($LAST_PAREN_MATCH) - подстрока, найденная в поиске с выбором по "или".
$* ($MULTILINE_MATCHING) - если ее значение установить равным 1, то переменная, в которой осуществляется поиск, будет считаться многострочной, т.е. содержащей символы '\n' - перевод строки. Если значение равно 0, то переменная считается однострочной.
$. ($INPUT_LINE_NUMBER), ($NR) - номер прочитанной строки последнего оператора ввода. Закрытие файла вызывает очистку значения этой переменной.
$/ ($RS), ($INPUT_RECORD_SEPARATOR) - символ - признак конца входной строки. По умолчанию это '\n'
$| ($OUTPUT_AUTOFLUSH) - если присвоить этой переменной ненулевое значение, то будет сброс буфера вывода после каждой операции вывода. Значение по умолчанию - 0
$, ($OFS), ($OUTPUT_FIELD_SEPARATOR) - символ, добавляемый оператором print после каждого элемента из списка параметров.
$\ ($ORS), ($OUTPUT_RECORD_SEPARATOR) - символ, добавляемый print после вывода всех параметров.
$" ($LIST_SEPARATOR) - аналогичен "$,", но добавляется после каждого элемента массива, указанного в "....".
$# ($OFMT) - формат по умолчанию для вывода чисел.
$% ($FORMAT_PAGE_NUMBER) - формат по умолчанию для вывода номеров страниц.
$= ($FORMAT_LINES_PER_PAGE) - длина одной страницы. По умолчанию 60 строк.
$- ($FORMAT_LINES_LEFT) - количество оставшихся строк на странице.
$~ ($FORMAT_NAME) - имя формата текущего вывода. По умолчанию имя указателя.
$^ ($FORMAT_TOP_NAME) - имя текущего формата для заголовка страницы.
$: ($FORMAT_LINE_BREAK_CHARACTERS) - символы переноса строки для многострочных полей. В строке формата такие поля начинаются с '^'. По умолчанию '\n-'.
$^L ($FORMAT_FORMFEED) - символ перевода формата (листа). По умолчанию '\f'.
$^A ($ACCUMULATOR) - текущее значение аккумулятора функции write() для format(). Значение этой переменной можно увидеть только при использовании функции formline(), т.к. write() очищает ее после каждого вывода.
$? ($CHILD_ERROR) - данная переменная содержит статус завершения таких процессов как: закрытие pipe, завершение функций system(), wait() и `...`.
$! ($ERRNO $OS_ERROR) - в числовом контексте возвращает код ошибки errno. В строковом - строку сообщения об ошибке. Можно принудительно присвоить этой переменной код ошибки, что бы получить системное сообщение для данного кода или установить код завершения для функции die().
$@ ($EVAL_ERROR) - сообщение об ошибке последней команды eval().
$$ ($PID), ($PROCESS_ID) - номер текущего процесса.
$O ($PROGRAM_NAME) - имя файла программы.
$[ - номер первого элемента массива или символа строки. Значение по умолчанию - 0.
$] ($PERL_VERSION) - строка - сообщение версии Perl. Печатается по команде perl -v В числовом контексте это номер версии плюс номер модификации / 1000.
$^T ($BASETIME ) - Время в секундах с начала 1970 года старта текущей программы.
$^X ($EXECUTABLE_NAME) - команда запуска Perl. Аналогично argv[0] в С.
$ARGV - имя текущего файла, читаемого оператором '<>'.
@ARGV - массив параметров строки запуска программы. Внимание! @#ARGV - меньше количества параметров на 1, т.к. $ARGV[0] это первый параметр (не имя программы).
@INC - список директорий диска, которые просматривает Perl для выполнения команд do, require или use.
%INC - этот хеш содержит имена директорий для имен использованных файлов командами do или require. Ключ - имя файла, а значение - директория.
$ENV{выражение} - хеш %ENV содержит значения переменных окружения. Изменение этих значений вызывает изменение окружения для процессов потомков.
$SIG{выражение} - хеш %SIG содержит имена подпрограмм для таких системных сигналов как INT, QUIT, PIPE, ... Значение 'DEFAULT' - для системной обработки. 'IGNORE' - игнорировать данный сигнал.
Регулярные выражения (шаблоны).
Регулярные выражения в Perl чаще всего используются в операторах поиска и замены таких как s/, m/, операторах связки =~ или != и т.д. Все эти операторы имеют схожие опции:
i |
- не различать строчные и заглавные буквы. |
m |
- считать строку многострочной. |
s |
- однострочная строка. |
x |
- расширенный синтаксис (использование пробелов и комментариев) |
Эти опции, обозначаемые как '/x', можно использовать внутри шаблонов, используя конструкцию (?...). В шаблонах используются следующие метасимволы (символы, обозначающие группы других символов):
\ |
- считать следующий метасимвол как обычный символ. |
^ |
- начало строки |
. |
- один произвольный символ. Кроме '\n' - конец строки. |
$ |
- конец строки |
| |
- альтернатива (или) |
() |
- группировка |
[] |
- класс символов |
Метасимволы имеют модификаторы (пишутся после метасимвола):
* |
- повторяется 0 или большее число раз |
+ |
- -//- 1 или большее число раз |
? |
- 1 или 0 раз |
{n} |
- точно n раз |
{n,} |
- по меньшей мере раз |
{n,m} |
- не меньше n, но и не больше m |
Во все других случаях фигурные скобки считаются обычными (регулярными) символами. Таким образом '*' эквивалентна {0,} , '+' - {1,} и '?' - {0,1}. n и m не могут быть больше 65536.
По умолчанию действие метасимволов "жадно" (greedy). Совпадение распространяется столько раз, сколько возможно, не учитывая результат действия следующих метасимволов. Если вы хотите "уменьшить их аппетит", то используйте символ '?'. Это не изменяет значение метасимволов, просто уменьшает распространение. Таким образом:
*? |
- станет 0 и более |
+? |
- 1 и более |
?? |
- 0 или 1 раз |
{n}? |
- точно n раз |
{n,}? |
- не меньше n раз |
{n,m}? |
- больше или равно n и меньше m раз |
Шаблоны работают так же, как и двойные кавычки, поэтому в них можно использовать `\` - символы (бэкслэш-символы):
\t |
- символ табуляции |
\n |
- новая строка |
\r |
- перевод каретки |
\а |
- перевод формата |
\v |
- вертикальная табуляция |
\a |
- звонок |
\e |
- escape |
\033 |
- восьмеричная запись символа |
\x1A |
- шестнадцатеричная |
\c[ |
- control символ |
\l |
- нижний регистр следующего символа |
\u |
- верхний регистр -//- |
\L |
- все символы в нижнем регистре до \E |
\U |
- в верхнем -//- |
\E |
- ограничитель смены регистра |
\Q |
- отмена действия как метасимвола |
Дополнительно в Perl добавлены следующие метасимволы:
\w |
- алфавитно-цифровой или '_' символ |
\W |
- не -//- |
\s |
- один пробел |
\S |
- один не пробел |
\d |
- одна цифра |
\D |
- одна не цифра |
Обратите внимание, что все это "один" символ. Для обозначения последовательности применяйте модификаторы. Так:
\w+ |
- слово |
\d+ |
- целое число |
[+-]?\d+ |
- целое со знаком |
[+-]?\d+\.?\d* |
- число с точкой |
Существуют мнимые метасимволы, обозначающие места смены значения:
\b |
- граница слова |
\B |
- не граница слова |
\A |
- начало строки |
\Z |
- конец строки |
\G |
- конец действия m//g |
Граница слова (\b) - это мнимая точка между символами \w и \W. Внутри класса символов '\b' обозначает символ backspace (стирания). Метасимволы \A и \Z - аналогичны '^' и '$', но если начало строки '^' и конец строки '$' действуют для каждой строки в многострочной строке, то \A и \Z обозначают начало и конец всей многострочной строки.
Если внутри шаблона применяется группировка (круглые скобки), то номер подстроки группы обозначается как '\цифра'. Заметьте, что за шаблоном в пределах выражения или блока эти группы обозначаются как '$цифра'. Существуют и дополнительные переменные:
$+ |
- обозначает последнее совпадение |
$& |
- все совпадение |
$` |
- все до совпадения |
$' |
- все после совпадения |
Листинг 5.1. Переменные совпадений.
$s = "Один 1 два 2 и три 3";
if ($s =~ /(\d+)\D+(\d+)/) {
print "$1\n"; # Результат '1'
print "$2\n"; # '2'
print "$+\n"; # '2'
print "$&\n"; # '1 два 2'
print "$`\n"; # 'Один '
print "$'\n"; # ' и три 3'
}
Perl версии 5 содержит дополнительные конструкции шаблонов:
(?#комментарий) |
- комментарий в теле шаблона. |
(?:шаблон) |
- группировка как и '( )', но без обратной ссылки |
(?=шаблон) |
- "заглядывание" вперед. Например /\w+(?=\t)/ соответствует слову, за которым идет табуляция, но символ '\t' не включается в результат. |
(?!шаблон) |
- "заглядывание" вперед по отрицанию. |
Листинг 5.2. Конструкции шаблонов.
$s = "1+2-3*4";
if ($s =~ /(\d)(?=-)/) # Найти цифру за которой стоит '-'
{ print "$1\n"; # Результат '2'
}
else { print "ошибка поиска\n";}
$s = "1+2-3*4";
if ($s =~ /(\d)(?!\+)/) # Найти цифру за которой не стоит '+'
{ print "$1\n"; # Результат '2'
}
else { print "ошибка поиска\n";}
Правила регулярного выражения. (regex)
Любой символ обозначает себя самого, если это не метасимвол. Если вам нужно отменить действие метасимвола, то поставьте перед ним '\'.
Строка символов обозначает строку этих символов.
Множество возможных символов (класс) заключается в квадратные скобки '[]', это значит, что в данном месте может стоять один из указанных в скобках символов. Если первый символ в скобках это '^' - значит, ни один из указанных символов не может стоять в данном месте выражения. Внутри класса можно употреблять символ '-', обозначающий диапазон символов. Например, a-z - одна из малых букв латинского алфавита, 0-9 - цифра.
Все символы, включая специальные, можно обозначать с помощью '\' как в языке С.
Альтернативные последовательности разделяются символом '|' Заметьте что внутри квадратных скобок это обычный символ.
Внутри регулярного выражения можно указывать "подшаблоны" заключая их в круглые скобки и ссылаться на них как '\номер' Первая скобка обозначается как '\1'.
Операторы и приоритеты.
В Perl ассоциативность и приоритетность операторов аналогична языку С. Ниже перечислены все операторы в порядке уменьшения приоритета с указанием ассоциативности.
ассоц. |
операторы |
левая |
термы и левосторонние списковые операторы |
левая |
-> |
- |
++ -- |
правая |
** |
правая |
! ~ \ унарные + и - |
левая |
=~ !~ |
левая |
* / % x |
левая |
+ - . |
левая |
<< >> |
- |
именованные унарные операторы |
- |
< > <= >= lt gt le ge |
- |
== != <=> eq ne cmp |
левая |
& |
левая |
| ^ |
левая |
&& |
левая |
|| |
- |
.. |
правая |
?: |
правая |
= += -= *= и т.д. |
левая |
, => |
- |
правосторонние списковые операторы |
левая |
not |
левая |
and |
левая |
or xor |
Термы и левосторонние списковые операторы. Любой терм имеет самый высокий приоритет. К терму относятся переменные, кавычки и их операторы, арифметические и логические выражения в скобках, любые функции с параметрами в скобках. Фактически таких функций нет, так как это просто унарные и списковые операторы. Просто они ведут себя подобно функциям с параметрами в скобках. Если после любого спискового оператора (print(), и т.д.) или унарного оператора (chdir(), и т.д.) следует левая круглая скобка, то операторы внутри скобок имеют наивысший приоритет. Так же как и обычные функции.
Если скобки отсутствуют, то приоритет списковых операторов или наивысший или наименьший в отношении операторов справа или слева от него:
@i = ('a ','b ', print 'c ', 'd ');
print "\n",@i,"\n"; #Результат: c d и a b 1
Здесь мы имеем списковый оператор print. Для запятых слева от него он имеет наименьший приоритет, но повышает приоритет правой запятой. Поэтому правая запятая воспринимается как параметр для print и печатается 'c d', а левая просто записывает код завершения операции в массив @i и последний print показывает это.
Оператор '->' Как и в С - это инфиксный оператор переадресации. Если справа от него стоит [...] или {...} выражение, то правая часть может быть непосредственной или символической ссылкой на массив или хеш. В противном случае правая сторона это метод или простой скаляр, содержащий имя метода, а правая - или объект, или имя класса.
Операторы ++ (инкремент) и -- (декремент). Работают, как и в С. Если оператор - перед переменной, то значение переменной изменяется на 1 и полученное значение используется. Если после - то ее величина изменяется после применения. Употребление инкремента к строковым переменным в Perl имеет особенность. Каждый символ остается в своем классе (большие, малые, цифры) и учитывается перенос предыдущего символа. Таким образом, строковые переменные с цифрами работают как числовые переменные:
print ++($i = "09"); # Результат "10"
print ++($i = "a9"); # "b0"
print ++($i = "az"); # "ba"
print ++($i = "aZ"); # "bA"
Оператор ** (возведение в степень):
print 4**2 # Результат 16
print -4**2 # Результат -16 т.е. -(4**2)
Унарные операторы.
'!' |
- логическое отрицание |
'-' |
- арифметический минус |
'~' |
- побитная инверсия (дополнение до 1) |
'+' |
- арифметический плюс |
'\' |
- получение ссылки на переменную (как & в С) |
Операторы "привязки" =~ и !=. Эти оригинальные операторы имеют очень широкое применение в Perl. Оператор =~ логически связывает левую часть выражения с патерном (pattern - образец, шаблон) в правой. По умолчанию поиск или изменение по патерну выполняется в переменной $_. Операторы привязки позволяют делать это с любой переменной, указанной в левой части. Логическим результатом будет успех операции. Если в правой части вместо патерна присутствует выражение, то результат этого выражения воспринимается как патерн. Однако это не очень эффективно, т.к. патерн будет компилироваться во время исполнения программы, что заметно снизит быстродействие. Оператор != аналогичен =~, только результат совпадения инвертируется (логическое "нет").
Мультипликативные операторы.
'*' |
- арифметическое умножение |
'/' |
- арифметическое деление |
'%' |
- арифметический модуль |
'x' |
- оператор повторения |
'x' в скалярном контексте возвращает строку левой части, повторенную величиной, указанной в правой части. В списковом контексте, если в левой части список, то в круглых скобках - повторенный список:
print '*' x 5; # Результат '*****'
print (1,2) x 3; # Результат 121212
Аддитивные операторы.
'+' |
- арифметический плюс |
'-' |
- арифметический минус |
'.' |
- конкатенация (объединение) строк |
Операторы сдвига.
'<<' |
- Сдвигает побитно влево значение выражения в левой части на количество бит, указанное в правой. |
'>>' |
- Сдвигает побитно вправо значение выражения в левой части на количество бит указанное в правой |
Именованные унарные операторы. Фактически это функции с одним аргументом. Круглые скобки можно опускать.
Операторы отношений.
'<' |
- арифметическое меньше |
'>' |
- арифметическое больше |
'<=' |
- арифметическое меньше или равно |
'>=' |
- арифметическое больше или равно |
'lt' |
- строковое меньше |
'gt' |
- строковое больше |
'le' |
- строковое меньше или равно |
'ge' |
- строковое больше или равно |
Операторы равенства.
'==' |
результат true если левая часть равна правой (равно) |
'!=' |
- не равно |
'<=>' |
- -1 если левая часть меньше правой, 0 если равна, |
1 если больше. |
|
'eq' |
- строковое равно |
'ne' |
- строковое не равно |
'cmp' |
- как и '<=>' применительно к строкам |
Операторы работы с битами.
'&' |
- побитное AND |
'|' |
- побитное OR |
'^' |
- побитное XOR |
Логические операторы && (AND) и || (OR).
'&&' |
- если левое выражение возвращает false, правое не выполняется. |
'||' |
- если левое выражение возвращает true, правое не выполняется. |
Отличие от подобных операторов в С заключается в том, что в С возвращаемое значение либо 0, либо 1, тогда как в Perl возвращается результат выражения.
Оператор диапазона '..' Результат его работы зависит от контекста. В списковом контексте результат есть список с элементами, первый элемент которого это левое выражение и последнее - правое. Значение каждого элемента внутри списка увеличивается на 1. Данный оператор удобен для небольших циклов, т.к. память отводится для всего списка целиком. Поэтому будьте внимательны и не задавайте слишком большой диапазон.
for $i (1..4)
{ print "$i "; # Результат: 1 2 3 4
}
В скалярном контексте результат - логическое значение. Каждая '..' операция устанавливает свое собственное состояние. Это false до тех пор, пока левый операнд false. Как только он стал true, результат - true до тех пока правый true, после чего опять - false. Если вы не хотите проверять правый операнд, то используйте оператор '...'.
Правый операнд не вычисляется, пока результат false и левый операнд не вычисляется, пока результат true. Приоритетность оператора '..' немного ниже чем '&&' и '||'. Возвращаемое значение если flase - нулевая строка, если true - порядковый номер, начиная с 1. Порядковый номер обнуляется для каждого нового диапазона.
@abc = ('a'..'z'); # Массив малых букв латинского алфавита
@digits = (0..9); # Массив цифр
Условный оператор '?:' Этот оператор работает так же как и в С. Если выражение перед '?' истинно, то выполняется аргумент перед ':' - иначе после ':'.
$i = 1;
$i > 1 ? print "больше" : print "меньше"; #Результат: меньше
Операторы присваивания. '=' - обычный оператор "присвоить" правое значение переменной слева. Вся эта группа операторов подобна операторам С, т.е.
$i += 2; #эквивалентно $i = $i + 2;
Остальные операторы этой группы работают аналогично. Допустимы следующие операторы:
**=, +=, -=, .=, *=, /=, %=, x=, &=, |=, ^=, <<=, >>=, &&=, ||=
Приоритет всей этой группы операторов равен приоритету '='.
Оператор ',' (запятая) В скалярном контексте выполняется левый аргумент, результат игнорируется, затем правый, и его результат есть результат действия оператора. В списковом контексте это разделитель элементов списка, включает указанные элементы в список.
Операторы not, and, or, xor Оператор логическое not (отрицание). - унарный not возвращает противоположное значение, полученное выражением справа. Он эквивалентен '!'.
Оператор логическое and (И). Выполняет логическую конъюнкцию двух выражений. Эквивалентен '&&', но имеет очень низкий приоритет и "краткость" действия, т. е. если левое выражение равно false - правое не выполняется.
Логическое or (ИЛИ). Выполняет логическую дизъюнкцию двух выражений. Эквивалентен '||', но имеет очень низкий приоритет и "краткость" действия, т. е. если левое выражение равно true - левое не выполняется.
Логическое xor (исключающее ИЛИ). Выполняет логическое исключающие или. Всегда выполняются оба правое и левое выражение.
В Perl отсутствуют операторы языка С, такие как:
унарное & |
- получить адрес. Для этого применяется '\'. |
унарный * |
- переадресация. |
(TYPE) |
- совмещение типов. |
Операторы ограничители строк. Обычно ограничителями строк мы считаем литералы, но в Perl это операторы, выполняющие разного рода интерполяцию и поиск по шаблону. Вы можете сами задавать удобные для вас ограничители. В следующей таблице приведен полный перечень вариантов. Фигурные скобки '{}' обозначают любой символ, используемый для ограничителя. В случае использования скобок (круглых '( )', квадратных '[ ]', фигурных '{ }', угловых '< >') в начале ставится открывающаяся скобка, а в конце закрывающая.
По умолчанию |
Полное |
Функция |
Интерполяция |
'' |
q{} |
Literal |
нет |
"" |
qq{} |
Литерал |
да |
`` |
qx{} |
Команда |
да |
qw{} |
Список слов |
нет |
|
// |
m{} |
Шаблон |
да |
s{}{} |
Подстановка |
да |
|
tr{}{} |
Трансляция |
нет |
В строках, допускающих интерполяцию, имена переменных, начинающиеся с символов '$' или '@' - интерполируются, т.е. в строку вставляется значение строки или массива. Данные последовательности символов имеют специальное значение:
\t |
символ табуляции |
\n |
символ новой строки |
\r |
возврат |
\f |
перевод формата |
\v |
вертикальная табуляция |
\b |
backspace (забой) |
\a |
звонок |
\e |
escape |
\034 |
восьмеричный символ |
\x1a |
шестнадцатеричный символ |
\c[ |
символ управления |
\l |
нижний регистр следующего символа |
\u |
верхний регистр следующего символа |
\L |
нижний регистр для всех символов до \E |
\U |
верхний регистр для всех символов до \E |
\E |
ограничитель смены регистра |
\Q |
отмена действия метасимволов до \E |
Шаблоны интерполируются как регулярные выражения. Это выполняется вторым проходом после интерполяции переменных, поэтому в шаблоны можно вставлять переменные. Для отмены интерполяции используйте '\Q'. Если вы применяете вложенные ограничители, то внутренние ограничители работать не будут.
?PATERN? Действие этого оператора аналогично /шаблон/, но выполняется до первого совпадения. Это удобно для поиска наличия какой-нибудь строки в одном или множестве файлов. Это не очень удачный оператор.
m/PATERN/gimosx, /PATERN/gimosx Поиск в строке по патерну (шаблону). В скалярном контексте возвращает логическое значение true (1) или false (''). Если строка не указана с помощью операторов '=~' или '!~', поиск ведется в строке $_. Опции:
g |
- Глобальный поиск. Поиск всех вхождений. |
i |
- Сравнение не зависит от регистра (верхний или нижний) |
m |
- Строка многострочная. |
o |
- однопроходная компиляция |
s |
- однострочная строка |
x |
- используются расширенные регулярные выражения. |
Если '/' - ограничитель, то начальное 'm' можно опустить. С помощью него в качестве ограничителя может быть любой символ кроме пробела.
PATTERN может содержать переменные, которые будут интерполироваться (перекомпилироваться) каждый раз в момент вычисления. Переменные $) и $| не интерполируются. Если вы хотите, что бы такой шаблон интерполировался один раз - добавьте /o. Это необходимо делать в циклах поиска для увеличения быстродействия. Если PATERN - нулевая строка, то используется последнее регулярное выражение.
В скалярном контексте возвращается список, элементы которого - результаты выполнения выражений в скобках патерна ($1, $2, $3...). Обратите внимание, что первый элемент $1.
$a = "/usr/local/perl/perl.bin"; # Анализируемая строка
Цель: Создать массив @dirs с именами директорий.
Решение: Самый простой способ - воспользоваться split('\/'), но мы используем скобки.
@dirs =($a=~ m[/(\w*)/(\w*)/(\w*)/(\w*)]);
Здесь 'm[' - использовать квадратные скобки как ограничители, (\w*)- шаблон алфавитно-цифровой последовательности.
В результате @dirs равен ('usr', 'local', 'perl')
q/строка/, 'строка' Строка литералов. Не интерполируется. Внутри строки разрешается использовать \' или \\ для обозначения символов ' и \ :
print q#Привет.#; # Результат Привет.
print 'O\'K'; # O'K
qq/строка/, "строка" Интерполируемая строка:
$var = 13;
print "\$var = $var"; # Результат: $var = 13
qx/строка/, `строка` Строка интерполируется, а потом выполняется как системная команда:
print `date`; #Результат: Thu Nov 14 13:36:49 MSK 1996
qw/строка/ Возвращает список, элементы которого - слова строки, разделенные пробелами:
print qw/Построимся и спасемся!/; # ('Построимся','и','спасемся!')
s/шаблон/подстрока/egimosx Поиск по шаблону и в случае успеха замена подстрокой. Возвращает количество произведенных подстановок, иначе false (0). Если строка в которой ведется поиск не указана (операторы =~ или != ), то используется переменная $_ . Если в качестве разделителя '/' использовать одинарную кавычку ('), то интерполяции не будет, иначе можно применять переменные в шаблоне или подстроке. Опции:
e |
- Рассматривать правую часть как выражение. |
g |
- Глобальный поиск. |
i |
- Без различия регистра букв |
m |
- многострочная переменная |
o |
- компилировать шаблон один раз |
s |
- однострочная переменная |
x |
- расширенное регулярное выражение |
Разделитель '/' можно заменить на любой алфавитно-цифровой символ кроме пробела:
Листинг 6-3. Регулярные выражения.
$var = "12345"; # исходная строка
$var =~ s/1/0/; print $var; # Заменить '1' на '0'. Результат 02345
$var =~ s(5)(.); print $var; # Заменить '5' на '.' Результат 0234.
Здесь в качестве разделителя применены скобки, поэтому подстрока взята в две скобки.
$var =~ s/\d*/каламбур/; Заменить все цифры. Результат 'каламбур.'
$var =~ s/а/о/g; print $var; # Заменить все 'а' на 'о'.
$var = "12 34"; # Новое значение
$var =~s/(\d\d)\s(\d\d)/$2 $1/; print $var;#Поменять местами числа
tr/таблица1/таблица2/cds, y/таблица1/таблица2/cds Замена всех символов из "таблица1" на соответствующий символ из "таблица2". Результат - количество замен или стираний. Без оператора =~ или != операция выполняется со строкой $_. Для совместимости с программой sed вместо tr можно писать 'y'. Опции:
c |
- дополнение "таблица1" |
d |
- стереть найденные, но не замененные символы. |
s |
- "сжать" повторяющиеся замененные символы. |
Если указана опция /d, таблица2 всегда интерпретируется как положено: если таблица2 короче, чем таблица1, то символ из таблицы1 интерпретируется всегда. Если таблица2 - null, то все символы строки остаются неизменными. Это удобно для подсчета количества символов в строке определенного класса или сжатия повторяющихся символов:
Листинг 5.4. Регулярные выражения.
$s = "hello"; # Исходная строка
$s =~ tr/a-z/A-Z/; # Заменить малые буквы на большие.
print $s; # Результат 'HELLO'
$s = 'Hel....lo';
$s =~ tr/a-zA-z/_/c; # Заменить все не буквы на '_'
print $s; # Результат 'Hel____lo'
$s =~ tr/_/ /s; # Заменить '_' на ' ' и сжать.
print $s; # Результат 'Hel lo'
$s =~ tr/a-zA-Z /a-zA-Z/d; # Удалить все не буквы.
print $s; # Результат 'Hello'
Если один символ несколько раз указан в таблице1, то применяется только первая замена.
Операторы ввода-вывода. В Perl существует несколько операторов ввода-вывода. Первый это скобки из символа '`' - акцента. Строка в этих скобках воспринимается, как системная команда и результат ее действия возвращается как "псевдо" литерал. В скалярном контексте это строка, содержащая весь результат, а в списковом - список, элементы которого - строки результата. Статус выполненной команды хранится в переменной $?.
Следующая команда ввода вывода выглядит как '<файл>'. Вычисление <файл> приводит к чтению строки из файла. Обратите внимание, что 'файл' здесь не имя файла, а указатель файла, который создается функцией open(). В скалярном контексте читается одна строка вместе с символом '\n' - перевода строки, а в списковом - весь файл читается в список, элементы которого суть строки файла. В случае обнаружения конца файла результат оператора не определен и воспринимается как false. Если не указана переменная результата, то по умолчанию это $_. Указатель файла по умолчанию STDIN - стандартный ввод:
while(<>) { print; }; # Прочитать и вывести весь файл STDIN
У оператора '<>' есть одна отличительная особенность. Если в командной строке нет никаких аргументов, то читается стандартный ввод, если есть аргументы, то они считаются именами файлов, которые последовательно читаются.
Если в угловых скобках записана переменная, то содержимое этой переменной считается именем указателя файла или ссылкой на указатель файла. Если такого указателя не существует, то содержимое переменной воспринимается как шаблон имен файлов и результат - имена файлов на диске, подходящих по шаблону:
while(<*.pl>) { print;}; # То же что и ls *.pl
@files = <*>; # Массив @files содержит имена файлов в директории
но лучше сделать: @files = glob("*"); т.к. внутри скобок можно использовать переменные.
Слияние констант. Как и С Perl выполняет возможные вычисления в период компиляции. Так подстановка символов после '\', операция конкатенации строк, арифметические выражения, содержащие только одни константы, все это делается в момент компиляции, что существенно увеличивает скорость выполнения программы.
Целочисленная арифметика. По умолчанию Perl выполняет арифметику с плавающей запятой, но если вы укажете:
use integer;
то компилятор будет использовать целочисленную арифметику до конца текущего блока, хотя вложенный блок может это и отменить в своих пределах с помощью:
no integer;
Встроенные функции.
Встроенные функции используются как термы выражений и подразделяются на две категории: списковые операторы и унарные операторы. Это влияет на их приоритет по отношению к оператору ',' - запятая. Списковые операторы могут иметь множество (список) аргументов, а унарные только один. Таким образом, запятая завершает аргументы унарного оператора и разделяет аргументы спискового. Аргумент унарного оператора воспринимается обычно в скалярном контексте, а спискового - как в скалярном, так и списковом, причем скалярные аргументы идут первыми. Аргументы функций можно заключать в круглые скобки и таким образом обозначать, что "это функция" и приоритет не имеет значения, иначе это списковый или унарный оператор с определенным фиксированным приоритетом. Пробел после имени функции и скобкой значения не имеет:
print 1 + 2 + 3; # результат 6
print(1+2)+3; # результат 3
print (1+2)+3; # опять 3
print (1+2+3); # 6
Если функция возвращает результат, как в скалярном, так и в списковом контексте, то код выхода по ошибке - скаляр с неопределенным значением или пустой список.
Запомните правило: Не существует общего правила преобразования списка в скаляр!
Перечень встроенных функций (без описания системных вызовов) приведен в приложении.
Подпрограммы.
Для применения подпрограммы ее необходимо определить либо в текущем модуле (файле), либо во внешнем модуле (файле). Подпрограммы определяются и декларируются следующим образом:
sub имя; - Только декларация. Определение ниже.
sub имя (прототипы); - То же но с декларацией параметров.
sub имя блок; - Декларация и определение.
sub имя (прототипы) блок; - То же, но с параметрами.
Для определения динамической анонимной подпрограммы можно указать:
$переменная = sub блок;
Для импортирования подпрограмм из других модулей используйте:
use модуль qw(подпрограмма1 подпрограмма2 );
Вызов подпрограммы:
имя(список параметров); # символ '&' можно не указывать.
имя список; # Если подпрограмма уже декларирована.
&имя; # Параметры в @_
Все параметры передаются подпрограмме как массив @_. Соответственно $_[0] - первый параметр, $_[1] - второй и т.д. Массив @_ - локальный, но он содержит адреса параметров, поэтому можно изменять значение параметров. Возвращаемое значение подпрограммы - результат последнего оператора. Это может быть как скаляр, так и массив. Можно принудительно возвращать результат, используя функцию return().
Подпрограмму можно вызвать, используя префикс '&' перед именем подпрограммы. Если подпрограмма предварительно продекларирована, то префикс и скобки можно опустить.
Private переменные. Для применения переменных доступных только внутри блока или подпрограммы необходимо определить их с помощью функции my(список). Если переменная одна, то скобки можно опустить. my() декларирует private переменные в пределах текущей подпрограммы, блока, функции eval() или do/require/use файлов. Private переменные аналогичны auto переменным в С:
Листинг 5.5. Подпрограммы.
# Программа вычисления факториала.
print fact(3); # вычислить факториал 3*2*1
sub fact # Определяем подпрограмму.
{ my $m; # private переменная но не local !
$m = $_[0];
return 1 if $m <= 1;
return($m * fact($m -1));
}
Можно указывать начальные значения private переменных как:
my(список) = выражение;
Так для вышеприведенного примера лучше было написать:
my($m) = $_[0];
Переменные типа local. В общем лучше использовать private переменные, т. к. это надежней и быстрее. private переменные обеспечивают лексическую область применения (видимости), а local - динамическую. Обычно это переменные форматов, значение которых должно быть видимо из вызываемых подпрограмм. Применение функции local() нецелесообразно в циклах, так как она вызывается каждый раз и таким образом заметно увеличивает время выполнения цикла.
Прототипы (prototypes). Для краткого описания типа передаваемых подпрограмме параметров можно применять прототипы. В Perl существуют следующие прототипы:
Декларация |
Пример вызова |
sub mylink($$) |
mylink $old, $new |
sub myvec($$$) |
myvec $var, $offset, 1 |
sub myindex($$;$) |
myindex &getstring, "substr" |
sub myreverse(@) |
myreverse $a, $b, $c |
sub myjoin($@) |
myjoin ":",$a,$b,$c |
sub mypop(\@) |
mypop @array |
sub mysplice(\@$$@) |
mysplice @array, @array, 0, @pushme |
sub mykeys(\%) |
mykeys %{$hashref} |
sub myopen(*;$) |
myopen HANDLE, $name |
sub mypipe(**) |
mypipe READHANDLE, WRITEHANDLE |
sub mygrep(&@) |
mygrep { /foo/ } $a, $b, $c |
sub myrand($) |
myrand 42 |
sub mytime() |
mytime |
Здесь:
\'символ' - параметр с типом 'символ'
'@' или '%' - все оставшиеся параметры как список
'$' - скаляр
'&' - безымянная подпрограмма
'*' - ссылка на таблицу имен
';' - разграничитель обязательных и не обязательных параметров.
Ссылка как параметр. Иногда нужно в качестве параметра передать подпрограмме не значение элемента массива, а ссылку на него, чтобы подпрограмма могла изменить значение элемента. Для этого в Perl к имени переменной добавляется символ '*' Подобное выражение называют 'type glob' так же как в Unix символом '*' обозначают "все возможные значения". Поэтому '*' для массива означает "все элементы массива". Для скаляров употреблять '*' не имеет смысла, т.к. они и так передаются ссылкой и вы можете изменять значение параметра, изменяя, например, переменную $_[0].
Переопределение встроенных функций. Большинство встроенных функций Perl можно переопределить своими собственными. Обычно это делают для удобства совместимости Perl для разных платформ систем. Для этого нужно перечислить имена этих функций в виде:
use subs 'функция1', 'функция2' ....;
и далее в модуле определить сами функции.
Автозагрузка. Если вы попытаетесь вызвать несуществующую функцию, то Perl выдаст сообщение об ошибке. Но если вы определите подпрограмму с именем 'AUTOLOAD', то она будет вызвана с теми же параметрами, а переменная $AUTOLOAD будет содержать имя несуществующей подпрограммы. Данный механизм очень удобен для средств отладки.
Модули (packages).
В Perl реализован механизм модулей. Модуль это группа подпрограмм и переменных, обычно включенных в один файл. Внутри одного модуля можно определить другой модуль. Начало модуля определяется директивой:
packages имя_модуля;
Конец модуля это конец блока или файла. Головной модуль имеет по умолчанию имя main. На имя внутри модуля можно ссылаться, добавляя '::' после имени модуля:
$main::var1 - переменная в головном модуле.
::var1 - то же самое. Имя main можно опускать.
$модуль1::var1 - переменная в модуле 'модуль1'
$модуль1::модуль2::var1 - Модуль2 содержится в модуле 1.
Только идентификаторы, начинающиеся с буквы или символа '_', хранятся в пространстве имен текущего модуля. Остальные хранятся в пространстве головного модуля main. Кроме этого имена STDIN, STDOUT, STDERR, ARGV, ARGVOUT, ENV, INC и SIG так же хранятся в головном модуле.
Таблицы имен. Все имена модуля хранятся в ассоциативном массиве (хеше) с именем модуля, к которому добавлены символы "::". Таким образом, имена головного модуля хранятся в %main:: , модуля 'mod1' в %mod1:: и т.д. Выражение вида *имя указывает значение элемента хеша 'имя', это удобно для определения констант.
*pi = \3.14159;
Здесь переменная $pi - это константа пи, которую уже нельзя изменить.
Конструкторы и деструкторы. Конструктор - это подпрограмма, которая выполняется в момент создания объекта, а деструктор - удаления объекта. Для модуля это подпрограммы с именами BEGIN и END. При определении этих подпрограмм слово sub можно опускать.
Конструктор BEGIN выполняется сразу, как только возможно, т.е. как только он определен, даже не завершая дальнейший разбор программы. Можно указать несколько блоков BEGIN. Они будут выполняться один за другим в порядке определения.
Деструктор END выполняется последним как только возможно, т.е. при завершении работы интерпретатора. Можно указать несколько блоков END, при этом они будут выполняться в обратном определению порядке.
Классы. В Perl нет специального синтаксиса для классов. Но функционально полноценными классами могут быть модули. При этом подпрограммы модуля становятся методами, а с помощью массива @ISA можно реализовать механизм наследования в классах. Более подробно классы рассматривать не будем.
Создание библиотеки. Если вы хотите создать модуль отдельным файлом и использовать как библиотеку подпрограмм, при этом вызывать подпрограммы библиотеки, не указывая имени модуля, вам необходимо оформить модуль следующим образом:
package имя_модуля; #Такое же, как имя файла без расширения '.pm'
require Exporter; # Обязательная строка для экспорта имен
@ISA = qw(Exporter); # -//-
@EXPORT = qw(func1 func2) #Перечисляем имена функций. Нет запятой!
@EXPORT_OK = qw($переменная @массив); #Указать публичные #переменные, массивы и т.д. если необходимо
{ # Начало блока модуля
.....
sub func1
........
sub func2
........
1;
}
Данный файл с расширением ".pm" должен храниться в одной из библиотечных директорий Perl. Они перечислены в массиве @INC, одна из них обычно "/usr/local/lib/perl/".
В головной программе вы указываете:
use имя_модуля;
и вам становятся доступны имена подпрограмм данного модуля.
Perl библиотеки. Стандартный набор библиотек обычно поставляется с дистрибутивом Perl, они разделяются на pragma библиотеки (работают как директивы компилятору) и стандартные библиотеки.
Pragma библиотеки. Данные библиотеки используют как:
use имя;
когда хотят включить действие и
no имя;
когда хотят выключить.
В стандартный набор входят следующие pragma:
diagnostics- Включить режим расширенной диагностики.
integer- Использовать целочисленную арифметику.
less- Режим минимальной загрузки компилятора.
overload- Режим переопределения операторов.
sigtrap- Режим слежения за прерываниями.
strict- Режим ограниченного использования "опасных" операторов.
subs- Режим обязательного декларирования подпрограмм.
Стандартные библиотеки. С точки зрения web-программирования наибольший интерес представляют библиотеки для работы с тегами HTML и для доступа к базам данных. Их мы рассмотрим чуть позднее и более подробно. Подробное описание каждой библиотеки записано в самом файле.
CPAN. Программисты всего мира, работающие с Perl, создали общедоступную библиотеку модулей CPAN . Она доступна через Интернет и содержит огромное количество различных по назначению модулей. К ним относятся документаторы, системные интерфейсы, интерфейсы работы с базами данных, работа в сети, с файлами, Интернет-броузеры, системы поиска, огромное количество CGI скриптов для Web серверов и многое-многое другое.
Форматы. В Perl реализован удобный метод создания форматированных отчетов. С помощью оператора format вы описываете заголовки, размеры полей, указываете положение данных на листе в удобной текстовой форме. Затем выполняете команду write(файл), которая выводит отформатированные данные в указанный файл.
Оператор format имеет следующий синтаксис:
format имя = FORMLIST.
Обратите внимание на то, что описание формата идет после строки format и заканчивается символом '.' в начале строки. Здесь 'имя' - это имя формата, такое же как и имя указателя выходного файла. Если 'имя' отсутствует то значение по умолчанию - STDOUT.
FORMLIST - это строки формата. Они бывают трех типов:
Комментарий. Строка начинается символом '#'.
Описатель полей данных (picture).
Строка аргументов используемых описателем.
Описатель - это строка, которая выводится в виде "как есть" за исключением специально обозначенных форматов полей данных. Каждое поле начинается либо символом '@', либо '^'. В описательной строке указывается только положение и вид выводимых данных, но не имена полей и переменных. Для этого предназначена следующая строка аргументов которая следует всегда после описателя и содержит имена переменных или целые выражения в порядке указанном описателем. Размер и вид поля в описателе обозначается символами:
"<<<<" |
- выравнить значение по правому краю. |
">>>>" |
- -//- по левому. |
"||||" |
- -//- по центру. |
"####.###" |
- формат числа с точкой. |
"@*" |
- многострочная строка. Данные выводятся в колонку. |
Размер поля равен количеству указанных символов. Символ '^' в начале поля имеет специальное значение. Так:
"^####" - пусто если переменная не определена.
для строчного скаляра:
"^<<<<<" - Выводится сколько возможно символов, а значение переменной меняется на остаток, вывод которого можно продолжить на следующих строках, которые могут иметь свои поля.
Специальные переменные:
$~ - построчный формат содержимого.
$^ - формат заголовка листа.
$% - номер листа.
$= - строк в листе.
Если вы хотите использовать одни форматы для разных файлов, то самый простой путь:
use FileHandle; # Указать в начале программы
format_name файл имя_формата; # Формат содержимого листа.
format_top_name файл имя_формата; # Формат заголовка листа.
write(файл); # вывод данных.
Здесь 'файл' имеется в виду указатель файла, полученный командой open();
Если вам нужно в теле листа выводить разного рода форматы (например, заголовки групп или отбивку листа), то применяйте format_name.
CGI-программирование.
Использование CGI.pm. Итак, как coздается CGI сценарий? Теоретически это очень просто - программа CGI, как и любая другая программа на Perl, выполняет обычные команды Perl, когда она вызывается броузером (то есть когда броузеру в качестве URL задается CGI-сценарий). Все, что вы направляете в стандартный вывод, передается броузеру. Так, если CGI-сценарий выполняет команду print "Hello!", этот текст будет возвращен броузеру, и на странице появится надпись "Hello'" Но это рудиментарный способ. Требуется ли прочитать данные, введенные пользователем с помощью элементов управления, расположенных на странице? Или вы захотите создать эти элементы управления из сценария? Чтобы сделать все это и многое другое, используется прилагающийся к Perl пакет CGI.pm. (Далее мы рассмотрим другой популярный пакет cgi-lib.pl.) С одной стороны, это стандартный способ работы с CGI средствами Perl, с другой отличное введение в CGI.pm.
Итак, интерпретатор Perl содержит, среди других модулей, стандартный модуль CGI.pm. Поэтому если у вас установлен Perl, то, скорее всего, есть и CGI.pm. Начиная с пятой версии Perl CGI.pm стал объектно-ориентированным, хотя упрощенный функционально-ориентированный интерфейс все еще существует. В наших примерах мы будем использовать объектно-ориентированное программирование. Создавая с помощью CGI.pm объекты CGI, мы будем вызывать различные методы этого объекта. Существуют методы, соответствующие практически всем основным тегам HTML, и при их вызове создается нужный тег с указанными атрибутами. Все они могут получать именованные параметры (за исключением методов, требующих один аргумент), иными словами, требуется указать не только значение атрибута HTML, но и его имя. В следующем примере объект CGI создает Web-страницу посредством встроенных методов. Обратите внимание на именованные параметры метода textarea, задающие имя области редактирования текста ('textarea'), значение по умолчанию и размеры:
Листинг 5.6. Создание Web-страницы.
#!/usr/local/bin/perl
use CGI;
$co = new CGI;
print $co->header, $co->start_html(-title=>'CGI Example'),
$co->center($co->h1('Welcome to CGI!')),
$co->textarea (
-name => 'textarea',
-default => 'No opinion',
-rows => 10,
-columns => 60 ),
$co->end_html;
Если возможности объектно-ориентированного интерфейса не требуются, пакет CGI.pm также поддерживает простой функционально-ориентированный интерфейс. Мы рассмотрим пример применения функционально-ориентированного интерфейса CGI.pm ниже.
Создание и использование элементов управления HTML. Изучать программирование лучше всего на примерах. Потому ниже приводятся два CGI-сценария: один создает Web-страницу с элементами управления полями ввода текста, переключателями, кнопками, включая Submit, а второй читает данные, введенные пользователем на этой странице. Оба сценария вариации с небольшими дополнениями на тему оператора print, который собственно и создает страницу.
Первый сценарий хранится в файле cgil.pl, и в справочных целях полностью приводится в листинге 6-7. Когда пользователь открывает сценарий в броузере (переходя по его адресу например, http://www.yourserver.com/user/cgi/cgil.pl), сценарий возвращает страницу с элементами управления HTML и текстом. В данном случае это страница анкеты.
Страница содержит приветствие и сообщение о том, что посетители, не желающие заполнять анкету, могут перейти по ссылке на сервер CPAN (Comprehensive Perl Archive Network всеобъемлющий архив, посвященный языку Perl).
Затем вы увидите запрос имени пользователя с текстовым полем и вопрос о его мнении с областью редактирования текста (многострочное текстовое поле).
Просматривая анкету дальше, вы увидите еще несколько элементов управления кнопки с зависимой и независимой фиксацией, списки, а также кнопка подтверждения и очистки анкеты. Позже мы рассмотрим, как создать все эти элементы управления на примере предложенных сценариев. Когда пользователь нажимает на кнопку Submit, расположенную в конце анкеты, броузер собирает данные, введенные на странице, и передает их другому CGI-сценарию, cgi2.pl. В справочных целях он приведен в листинге 6-8.
В общем, нет никакой необходимости создавать CGI-сценарий, генерирующий анкету, можно просто написать страницу HTML, которая будет вызывать cgi2.pl по нажатию пользователем кнопки Submit. Подход, требующий генерации кода, выбран лишь затем, чтобы продемонстрировать обе стороны процесса как создать элементы управления HTML из CGI-сценария и как прочесть данные из этих элементов управления.
Листинг 5.7. cgi1.pl
#!/usr/local/bin/perl
use CGI; $co = new CGI;
$labels{'1'} = 'Sunday'; $labels{'2'} = 'Monday';
$labels{'3'} = 'Tuesday'; $labels{'4'} ='Wednesday';
$labels{'5'} = 'Thursday'; $labels{'6'} = 'Friday';
$labels{'7'} = 'Saturday';
print $co->header(-charset=>'windows-1251', -lang=>'ru'),
$co->start_html(
-title => 'CGI Example',
-author => 'Andrew',
-meta => {'keywords' => 'CGI Perl'},
-BGCOLOR => 'white',
-LINK => 'red' ),
$co->center($co->h1('Here is the Survey!')),
$co->h2('Please fill out survey...'),
"Reasons for filling out our survey:", $co->p,
$co->ul( $co->li('Fame'), $co->li('Fortune'), $co->li('Fun' ) ),
"If you would rather not fill out our survey, ",
"you might be interested in ",
$co->a({href=>"http://www.cpan.org/"},"CPAN"),".",
$co->hr, $co->startform(
-method=>'POST',
-action=>'http://inna/scripts/web/l6/e8.pl'),
"Please enter your name: ", $co->textfield('text'), $co->p,
"Please enter your opinion: ", $co->p,
$co->textarea( -name => 'textarea',
-default => 'No opinion',
-rows => 10,
-columns => 60 ),
$ co->p, "Please indicate what products you use: ", $co->p,
$co->checkbox_group(
-name => 'checkboxes',
-values => ['Shampoo','Toophpaste','Bread', 'Cruise missiles' ],
-defaults => ['Bread','Cruise missiles'] ), $co->p,
"Please indicate your income level: ", $co->p,
$co->scrolling_list('list', ['Highest','High','Medium','Low' ], 'High'), $co->p,
"Please indicate your day of a week: ", $co->p,
$co->radio_group(
-name => 'radios',
-values =>['1','2','3', '4' ,'5' ,'6','7'],
-default => '1',
-labels => \%labels), $co->p,
"Thank you for filling out our survey. Please indicate ",
"How much unsolicited mail you like to get: ", $co->popup_menu(
-name => 'popupmenu',
-values => ['Very much','A lot','Not so much','None'] ), $co->p,
$co->hidden(-name=>'hiddendata', -default=>'Rosebud'),
$co->center( $co->submit, $co->reset, ),
$co->hr, $co->endform,$co->end_html;
Листинг 5.8. cgi2.pl
#!/usr/local/bin/perl
use CGI; $co = new CGI;
print $co->header(-charset=>'windows-1251', -lang=>'ru'),
$co->start_html(
-title => 'CGI Example',
-author => 'Andrew',
-meta => {'keywords'=>'CGI Perl'},
-BGCOLOR => 'white',
-LINK => 'red' ),
$co->center($co->h1('Thanks for filling out our survey.')),
$co->h3('Here is your responses...'), $co->hr;
if ($co->param()) { print "Your name is: ",
$co->em($co->param('text')), ".", $co->p, "Your opinions are: ",
$co->em($co->param('textarea')), ".",$co->p,
"You use these products: " ,
$co->em(join(", ", $co->param('checkboxes'))), ".",
$co->p, "Your income level is:", $co->em($co->param('list')),".",
$co->p, "Today is day ",
$co->em($co->param('radios')), "</em> of the week.", $co->p,
"How much unsolicited mail you like: ",
$co->em($co->param('popupmenu')),".", $co->p,
"The hidden data is ", $co->em(join(", ",
$co->param('hiddendata')))}
print $co->hr; print $co->end_html;
Начинаем HTML-документ. Начало работы над документом HTML строится следующим образом. Вначале вы создаете CGI объект, затем с помощью метода header этого объекта HTTP-заголовок (в данном примере создается простая шапка документа, но допустимы сколь угодно сложные шапки с любыми атрибутами, например, -charset=>'windows-1251'). Заголовок можно сформировать и самостоятельно, напимер, командой print. Метод start_html начинает сам документ HTML. Этот метод создает секцию <HEAD>, а также позволяет указать некоторые атрибуты <BODY>, как-то: цвет для изображения фона и ссылок. Ниже приведен фрагмент кода cgil.pl, открывающий страницу. Обратите внимание: чтобы результаты работы методов header и start_html попали на страницу, необходимо использовать функцию print:
#! /usr/local/bin/perl
$со = new CGI,
print $co->header,
$co->start_html(
-title => 'CGI Example',
-author => 'Andrew',
-meta => {'keywords'=>'CGI Perl'},
-BGCOLOR => 'white',
-LINK => 'red' )
Создаем заголовки HTML. После создания шапки CGI-методы типа h1, h2, h3 и др. помогут создать заголовки, соответствующие тегам <Н1>, <Н2>, <НЗ> и т. д. Ниже приведен фрагмент кода, генерирующий заголовки <Н1> и <Н2> в начале Web-страницы с анкетой. В данном случае это простое приглашение пользователю.
#!/usr/local/bin/perl
$со = new CGI; print
$co->h1('Here is the Survey!,'),
$co->h2('Please fill out survey...')
Центрируем элементы. Чтобы центрировать текст с помощью тегов <CENTER>, используется CGI-метод center. В следующем примере центрируется заголовок, созданный в предыдущем примере:
#'/usr/local/bin/perl
$со = new CGI; print
$co->center($co->h1('Here is the Survey!')),
$co->h2('Please fill out survey...'),
Создаем маркированный список. CGI-методы ul и li создают несортированный маркированный список (теги <UL> и <LI> соответственно). Ниже приведен фрагмент кода, представляющий пользователю несколько аргументов, побуждающих заполнить анкету:
#!/usr/local/bin/perl
$со = new CGI; print "Reasons for filling out our survey:",
$co->p, $co->ul(
$co->li('Fame'), $co->li('Fortune '), $co->li('Fun'), )
Создаем гиперссылку. Гиперссылки помещаются на страницу CGI-методом а, как в примере ниже, где выводится URL для перехода (на случай, если пользователь не заинтересован в заполнении анкеты, созданной сценарием cgil.pl):
#!/usr/local/bin/perl
$со = new CGI; print
"If you would rather not fill out our survey, ",
"you might be interesed in",
$co->a({href=>"http://www. cpan.org/"}, "CPAN"), ". "
Создаем горизонтальную полосу. Для создания горизонтальной линии (метка <HR>) используется CGI-метод hr:
#!/usr/local/bin/perl
$со = new CGI; print
$co->hr
Создаем HTML-форму. Элементы управления HTML группируются в формы. В примере с анкетой для создания формы использовался GGI-метод startform. После нажатия кнопки Submit данные из элементов управления должны быть прочитаны и переданы сценарию, формирующему сводку данных, то есть cgi2.pl. URL этого сценария указывается в атрибуте action формы:
#!/usr/local/bin/perl $со = new CGI; print
$co->startform(
-method=>'POST',
-action=>'http://www.yourself.com/user/cgi/cgi2.pl')
#$co->startform()
Все последующие элементы управления будут включены в форму, потому что метод startform генерирует тег <FORM>.
Замечание. Если startform вызывается без аргументов, кнопка Submit возвращает введенные данные той же форме. Ниже рассказывается, как использовать такую возможность.
Работаем с текстовыми полями. Для создания текстового поля, позволяющего вводить текст, используется CGI-метод textfleld. В примере ниже создается текстовое поле, предназначенное для хранения имени пользователя.
#!/usr/local/bin/perl
"Please enter your name: ' ,
$co->textfield('text')
Чтение данных из элементов управления HTML. Элементы управления созданы (точнее, пока только текстовое поле), но как считать из них данные? Когда пользователь нажмет на кнопку Submit, броузер отправит данные формы сценарию cgi2.pl; CGI-метод param в нем как раз и предназначен для чтения данных. Ему достаточно передать имя, присвоенное текстовому полю, в данном случае 'text' (см. предыдущий раздел), а вывод выполняется следующим образом:
#!/usr/local/bin/perl
$co = new CGI;
print "Your name is: ", $co->em($co->param( 'text')), ".";
Метод em создает метку <ЕМ>, которая большинством броузеров воспринимается как указание на переход к курсивному начертанию.
Работаем с текстовыми областями. Текстовая область может содержать несколько строк текста. Вот как в cgil.pl создается текстовая область, предназначенная для ввода любого мнения пользователя (задается как описание самой области в 10 строк по 60 символов в каждой, так и некоторого текста по умолчанию, а также имени области, 'textarea'):
#!/usr/local/bin/perl
$со = new CGI; print "Please enter your opinion; ", $co->p,
$co->' textarea' (
-name => 'textarea',
-default => 'No opinion',
-rows => 10,
-columns => 60 )
А во фрагменте ниже, CGI-метод param считывает текст и выводит данные анкеты:
print "Your opinions are: ", $co->em($co->param('textarea')), ".";
Работаем с кнопками с независимой фиксацией. Кнопки с независимой фиксацией (checkbuttons) обычно объединяются в группу, что позволяет возвращать имена выбранных элементов управления в одном списке. Во фрагменте кода, приведенном ниже, с помощью CGI-метода , checkbox_group как раз и создается такая группа. Ей присваивается имя, кнопки получают подписи и задаются пункты, выбранные по умолчанию при выводе Web-страницы:
#!/usr/local/bin/perl $со = new CGI; print
"Please indicate what products you use. ",$co->p,
$co->checkbox_group(
-name => 'checkboxes',
-values => ['Shampoo', 'Toothpaste', 'Bread', 'Cruise missiles'],
-defaults => ['Bread', 'Cruise missiles'] )
Код ниже предназначен для проверки и вывода выбора пользователя. В данном случае param возвращает список имен помеченных кнопок, поэтому потребовался вызов функции join, объединяющей элементы списка в строку:
print "You use these products: ", $co->em(join(", ", $co->param('checkboxes'))),
Работаем со списками. Список с готовыми значениями можно прокрутить в случае, когда невозможно одновременно вывести па экран все его строки. Этот элемент управления создается CGI-методом scrolling_list. В сценарии cgil.pl список позволяет выбрать уровень доходов. Он называется 'list' и включает строки 'Highest', 'High', 'Medium' и 'Low', причем по умолчанию выбрано 'High':
#! /usr/local/bin/perl
$со = new CGI; print "Please indicate your income level: ",$co->p,
$co->scrolling_list('list',
['Highest', 'High', 'Medium', 'Low'], 'High', )
Ниже приведен пример чтения и вывода выбранной строки:
print "Your income level is: ", $co->em($co->param('list')), ".";
Работаем с кнопками с зависимой фиксацией. Кнопки с зависимой фиксацией (radiobuttons) позволяют сделать однозначный выбор из нескольких значений. Например, в cgil.pl создается семь таких кнопок. Они объединяются в группу 'radios' и получают значения от '1' до '7', а метки прикрепляются к ним с помощью хэша %labels:
#!/usr/local/bin/perl
$со = new CGI;
$labels{'1'} = 'Sunday'; $labels{'2'} = 'Monday':
$labels{'3'} = 'Tuesday'; $labels{'4'} = 'Wednesday';
$labels{'5' } = 'Thursday', $labels{'6'} = 'Friday';
$labels{'7'} = 'Saturday';
print "Please indicate your day of a week: ", $co->p,
$co->radio_group (
-name->' radios',
-values=>['1', '2', '3', '4', '5', '6', '7'],
-default=>'1',
-labels=>\%labels )
Ниже приведен пример чтения и печати выбранного элемента, взятый из сценария cgi2.pl:
print "Today is day ', co->param('radios'), " of the week.";
Работаем с раскрывающимся списком. В HTML раскрывающийся список представляет собой набор элементов, который пользователь может открыть нажатием кнопки, обычно сопровождающийся изображением стрелки вниз. Пользователь может выбрать элемент списка, а вы - определить, на чем он остановился. Вот как делается выбор количества непрошеной почты, которое пользователь согласен получать (пример взят из нашей анкеты). Элементы задаются при помощи метода popup_menu:
#!/usr/local/bin/perl
$со = new CGI; print
$co->popup_menu (
-name => 'popupmenu',
-values => ['Very much', 'A lot','Not so much', 'None'] )
Далее приведен пример чтения и печати выбора пользователя, взятый из сценария cgi2.pl:
print "How much unsolicited mail you like",
$co->param('popupmenu');
Работаем со скрытыми полями данных. Данные, хранящиеся в скрытом поле на Web-странице, невидимы для пользователя. (Это удобно, когда сценарий ожидает получить некоторые неизменные сведения о странице.) Такие поля создаются следующим образом:
#!/usr/local/bin/perl
$со = new CGI; print
$co->hidden(-name=>'hiddendata', -default=>'Rosebud'),
И вот как вывести эти данные из cgi2.pl:
print "The hidden data is: ",
join(", ", $co->param('hiddendata'));
Создаем кнопки отмены и подтверждения. Чтобы отправить на сервер данные формы, пользователь должен нажать кнопку Submit. Она создается CGI-методом submit. Аналогично, кнопка Reset, которая очищает данные формы, создается методом reset. Ниже приведен пример кода, создающий кнопки Submit и Reset на Web-странице:
#!/usr/local/bin/perl
$со = new CGI; print
$co->center(
$co-> submit(-value=>'Отправить'),
$co->reset(-value=>'Отмена'))
После нажатия на кнопку Submit данные отправляются сценарию cgi2.pl.
Закрываем HTML-форму. Все элементы управления, описанные ранее, являются частью одной формы анкеты, созданной в cgil.pl. Для открытия формы использовался метод startform, а для ее закрытия endform:
#!/usr/local/bin/perl
$со = new CGI; print
$co->endform,
Закрываем HTML-документ. Чтобы завершить работу с HTML-документом, используйте метод end_html, который выводит теги </BODY></HTML>. Вот как заканчивается страница с анкетой в сценарии cgil.pl:
#!/usr/local/bin/perl $со = new CGI;
$co->end_html;
На этом cgil.pl кончается. Когда пользователь введет данные и нажмет кнопку Submit, будет вызван сценарий cgi2.pl, который выведет сводку анкеты.
Функционально-ориентированное CGI-программирование. До сих пор мы использовали объектно-ориентированные методы. Однако пакет CGI имеет и функционально-ориентированный интерфейс (Впрочем, при обращении к нему некоторые возможности объектно-ориентированною интерфейса становятся недоступными). В примере ниже используется функционально ориентированный интерфейс пакета CGI. Код генерирует текстовое поле с предложением ввести имя пользователя. После нажатия на кнопку Submit данные возвращаются к тому же CGI-сценарию, который с помощью функции рагаm выводит введенное имя в нижней части Web-страницы:
Листинг 5.9. Функционально ориентированный интерфейс пакета CGI.
#!/usr/local/bin/perl
use CGI qw/:standard/;
print header(-charset=>'windows-1251', -lang=>'ru'),
start_html(' CGI Functions Example' ),h1('CGI Functions Example'),
start_form, "Please enter your name: ", textfield('text'), p,
submit(-value=>'Отправить'), reset(-value=>'Отмена'), end_form,hr;
if(param()) {print "Your name is: ",em(param('text')), hr;}
print end_html;
Использование cgi-lib.pl. Ранее рассказывалось о CGI-программировании на базе методов стандартного модуля CGI.pm. He менее популярен среди программистов пакет cgi-lib.pl. Поскольку многие CGI-сценарии на Perl написаны с его помощью, далее рассказывается именно о нем. Авторские права на этот пакет принадлежат его создателю Стивену Е. Бреннеру, на домашней странице которого (http://cgi-lib.stanford.edu/cgi-lib) можно получить копию cgi-lib.pl. Вам разрешается работать с cgi-lib.pl и даже изменять его до тех пор, пока ваши действия не будут ущемлять описанные в начале файла авторские права. Особая процедура установки не требуется файл cgi-lib.pl копируется в каталог, где хранятся CGI-сценарии, и с помощью команды require подключается к ним:
require 'cgi-lib.pl';
Если у вас нет этого пакета - просто используйте модуль CGI.pm в таком контексте:
use CGI qw/:ReadParse, PrintHeader, HtmlTop, HtmlBot, SplitParam/;
Далее создаются два сценария, генерирующие те же страницы, что и в предыдущей главе, но вместо CGI.pm на сей раз будет использован пакет cgi-lib.pl. Предыдущие сценарии назывались cgil.pl и cgi2.pl, а в этой речь пойдет о libl.pl и lib2.pl.
Пакет cgi-lib.pl подключается к сценарию с помощью команды require. (He запрещено использовать также use, но вряд ли вам встретится такой вариант.) В отличие от модуля CGI.pm, количество функций, генерирующих теги HTML в cgi-lib.pl, крайне ограничено. Обычно эти теги приходится выводить вручную. (Пакет cgi-lib.pl предназначался в первую очередь для разбора посланных сценарию данных.) Впрочем, некоторые теги HTML все же генерируются автоматически: подпрограмма PrintHeader создает шапку HTML, необходимую для страницы, раздел HtmlTop (метки <HEAD> и <BODY>). Также с ее помощью можно создать заголовок страницы, как показано в следующем примере, задающем страницу с заголовком "My Web Page":
#!/usr/local/bin/perl
require 'cgi-lib. рl';
print &PrintHeader;
print &HtmlTop ("My Web Page");
После описания начала страницы остальная разметка HTML (в том числе формы) создается путем непосредственного вывода тегов. Например, вот так задается заголовок <Н1>:
print "<CENTER><H1>Hello!</H1></CENTER>";
Чтобы прочитать данные, переданные CGI-сценарию, используется подпрограмма ReadParse. Она создает хэш (обычно называемый %in) и записывает в него значения элементов данных, переданных сценарию. Элементы данных адресуются по именам, присвоенным соответствующим элементам HTML. Например, следующий код создает хэш %in и, читая данные текстового поля 'text', выводит их:
&ReadParse(*in);
print "Here is the text: <EM>", $in('text'), "</EM>.";
Чтобы завершить Web-страницу метками HTML </BODY> и </HTML>, можно использовать подпрограмму HtmlBot (она просто возвращает строку "</BODY>\n</HTML>\n"):
print &HtmlBot;
Вот так, вкратце, и работает cgi-lib.pl.
Листинг 5.10. lib1.pl
#!/usr/local/bin/perl
#require 'cgi-lib.pl';
use CGI qw/:ReadParse, PrintHeader, HtmlTop, HtmlBot, SplitParam/;
print &PrintHeader;
print &HtmlTop ("CGI Example Using cgi-lib.pl");
print "<BODY BGCOLOR=\ white\ LINK=\ red\><p>
<CENTER><H1>Here is the Survey!</H1></CENTER>
<H2>Please fill out our survey</H2>
Reasons for filling out our survey: <P><UL>
<LI>Fame</LI><LI>Fortune</LI><LI>Fun</LI></UL>
If you would rather not fill out our survey, you might be interested in <A HREF=http://www.cpan.org>CPAN</A>
<HR><FORM METHOD=\"POST\"
ACTION=\"http://inna/scripts/web/l6/lib2.pl\"
ENCTYPE=\"application/x-www-form-urlencoded\">
Please enter your name.
<INPUT TYPE=\"text\" NAME=\"text\" VALUE=\"\"><P>
Please enter your opinion:<P><TEXTAREA NAME=\"textarea\"
ROWS=10 COLS=60>No opinion</TEXTAREA><P>
Please indicate what products you use <P>
<INPUT TYPE=\"checkbox\" NAME=\"checkboxes\"VALUE=\"Shampoo\"> Shampoo
<INPUT TYPE=\"checkbox\" NAME=\"checkboxes\"VALUE=\"Toophpaste\"> Toothpaste
<INPUT TYPE=\"checkbox\" NAME=\"checkboxes\" VALUE=\"Bread\" CHECKED> Bread
<INPUT TYPE=\"checkbox\" NAME=\"checkboxes\" VALUE=\"Cruise missiles\" CHECKED>Cruise missiles </P>
Please indicate your income level:<P><SELECT NAME=\"list\" SIZE=4>
<OPTION VALUE=\"Highest\">Highest
<OPTION SELECTED VALUE=\"High\">High
<OPTION VALUE=\"Medium\">Medium
<OPTION VALUE=\"Low\">Low
</SELECT><P>
Please indicate your day of the week:<P>
<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"1\" CHECKED>Sunday
<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"2\">Monday
<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"3\">Tuesday
<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"4\">Wednesday
<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"5\">Thursday
<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"6\">Friday
<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"7\">Saturday <P>
Thank you for filling out our Survey. Please indicate how
much unsolicited mail you like to get.
<SELECT NAME=\"popupmenu\">
<OPTION VALUE=\"Very much\">Very much
<OPTION VALUE=\"A lot\">A lot
<OPTION VAlUE=\"Not so much\">Not so much
<OPTION VALUE=\"None\">None</SELECT><P>
<INPUT TYPE=\"hidden\" NAME=\"hiddendata\" VALUE=\"Rosebud\">
<CENTER><INPUT TYPE=\"submit\" NAME=\"submit\">
<INPUT TYPE=\"reset\"></CENTER><HR></FORM>";
print &HtmlBot;
Листинг 5.11. lib2.pl
#!/usr/local/bin/perl
#require 'cgi-lib.pl';
use CGI qw/:ReadParse, PrintHeader, HtmlTop, HtmlBot, SplitParam/;
print &PrintHeader;
print &HtmlTop ("CGI Example Using cgi-lib.pl");
print "<BODY BGCOLOR=\white\ LINK=\ red\><p>
<CENTER><H1>Thank you for filling out our survey.</H1></CENTER>
<H3>Here are your responses...</H3>";
if (&ReadParse(*in)) {print "Your name is: <EM>", $in{'text'},
"</EM>.", "<p>","Your opinions are. <EM>", $in{'textarea'},
"</EM>.","<p>", "You use these products. <EM>",
join(", ", &SplitParam($in{'checkboxes'})), "</EM>.<p>",
"Your income level is: <EM>",$in{'list'},
"</EM>.<p>Today is day <EM>", $in{'radios'},
"</EM> of the week.<p>","How much uncolicited mail you like<EM>",
$in{'popupmenu'}, "</EM>.", "<p>",
"The hidden data is <EM>", $in{'hiddendata'}, "</EM>."; }
print &HtmlBot;
Какие подпрограммы входят в состав cgi-lib.pl? Вот их список:
CgiDie как и CgiError, печатает сообщение об ошибке и, кроме того, останавливает программу.
CgiError печатает сообщение об ошибке, используя стандартные заголовки и HTML-код.
HtmlBot возвращает строку"</BODY>\n</HTML>\n".
HtmlTop возвращает раздел <HEAD> документа HTML и открывает раздел <BODY>. Необязательный строковый параметр используется в качестве названия Web-страницы: добавляется тег HTML <H1> с этим названием.
MethGet возвращает значение истина, если текущий вызов CGI сделан при помощи метода GET. В противном случае возвращается значение ложь.
MethPost возвращает значение истина, если текущий вызов CGI сделан при помощи метода POST. В противном случае возвращается значение ложь.
MyBaseUrl возвращает базовый адрес (base URL) CGI-сценария, без дополнительного пути или строк запроса.
MyFullUrl возвращает базовый адрес (base URL) CGI-сценария, включая дополнительный путь и строки запроса.
PrintEnv форматирует и печатает переменные среды, доступные сценарию.
PrintHeader возвращает строку "Content-type: text/html\n\n". С нее должны начинаться все Web-страницы, создаваемые cgi-lib.pl.
PrintVariables форматирует и печатает значения данных. Ей передается хэш или запись таблицы символов (для вывода элементов соответствующего массива). Без аргументов PrintVariables выводит содержимое хэша %in.
ReadParse основная подпрограмма библиотеки cgi-lib.pl. Она читает и разбирает данные, переданные CGI-сценарию методами GET или POST. Обычно она используется для создания хэша %in: ей передается запись таблицы символов *in. Хэш содержит данные, переданные сценарию, упорядоченные по именам соответствующих элементов управления. Необязательные второй, третий и четвертый параметры указывают на то, что надо заполнить соответствующие хэши данными из принятых файлов.
SplitParam разбивает параметр, содержащий несколько значений, на список из единичных параметров. Эта подпрограмма предназначена для работы с элементами HTML, способными хранить несколько значений, например, группой кнопок.
Начинаем документ HTML. Прежде всего, необходимо подключить пакет cgi-lib.pl. HTTP-заголовок ("Content-type: text/html\n\n") обычно генерируется функцией PrintHeader. Секции <HEAD> и <BODY> создает подпрограмма HtmlTop. Ее необязательный строковый аргумент используется в качестве названия Web-страницы и вставляется в начало страницы как заголовок первого уровня. Пример (lib1.pl):
#!/usr/local/bin/perl
require 'cgi-lib.pl';
print &PrintHeader;
print &HtmlTop ("CGI Example Using cgi-lib.pl");
Если для тега <BODY> требуется задать дополнительные атрибуты, их выводят вручную:
print "<BODY BGCOLOR=\ white\ LINK=\ red\><p>";
Подсказка. Конечно же, можно пропустить HtmlTop и создать собственные <HEAD> и <BODY>, выводя теги HTML со всеми необходимыми атрибутами.
Файл cgi-lib.pl не имеет специальных подпрограмм для создания разметки HTML, поэтому почти все, что требуется, приходится выводить вручную.
Читаем данные из элементов управления HTML. Для чтения данных из различных элементов управления в cgi-lib.pl предназначена подпрограмма ReadParse. Обычно создается хэш %in, с данными, полученными от элементов управления. Он адресуется по именам элементов. Например, в листинге 6-8 мы задавали текстовое поле с именем text. Вот как создается хэш %in и выводятся данные, полученные от элемента управления:
require 'cgi-lib.pl';
if (&ReadParse(*in)) {print "Your name is: ", $in{'text'}, "<p>";}
Обратите внимание: значение ReadParse проверяется до обращения к хэшу %in. Если ReadParse возвращает значение ложь, значит, получить данные не удалось.
Работаем с текстовыми областями. Содержимое текстовой области выводится так:
if (&ReadParse(*in)) {print "Your opinions are: ",
$in{'textarea'};};
Работаем с кнопками с независимой фиксацией. Когда данные группы кнопок будут переданы сценарию lib2.pl, $in('checkboxes') возвратит строку с несколькими значениями. Подпрограмма SplitParam пакета cgi-lib.pl делает из этой строки список:
if (&ReadParse(*in)) { print 'You use these products: ",
join(", ", &SplitParam($in{'checkboxes'}));}
Работаем со списками. Вот как lib2.pl читает и выводит на экран данные, выбранные пользователем из списка:
if (&ReadParse(*in)) {print "Your income level is: ",$in{'list'};}
Работаем с кнопками с зависимой фиксацией. Сценарий lib2.pl проверяет, какая кнопка была выбрана, и выводит ее значение:
if (&ReadParse(*in)) {print "Today is day", $in{'radios'};}
Работаем с раскрывающимися списками. Сценарий lib2.pl проверяет выбор пользователя и выводит его на экран:
if (&ReadParse(*in)) {print 'How much unsolicited mail you like:",
$in{ 'popupmenu'}, "<p>";}
Работаем со скрытыми полями данных. Вот часть кода lib2.pl, читающая текст из скрытого элемента управления и выводящая его на экран:
if (&ReadParse(*in)) {print "The hidden data is ",
$in{'hiddendata'};}
Завершаем документ HTML. Подпрограмма HtmlBot пакета cgi-lib.pl закрывает Web-страницу. Она возвращает строку "</BODY>\n</HTML>\n":
print &HtmlBot;
Часто эта строка оказывается последней в сценарии, как и в lib1.pl с lib2.pl.
Выводим все переменные. В этих примерах значения данных, переданных элементами управления HTML, выводились непосредственно оператором print. Но это можно сделать и проще: то же самое делает подпрограмма PrintVariables, но только в фиксированном формате. Например, можно заменить lib2.pl кодом, приведенным ниже:
Листинг 5.12. Вывод всех переменных.
#!/usr/local/bin/perl
#require 'cgi-lib рl';
use CGI qw/:ReadParse, PrintHeader, HtmlTop, HtmlBot, Vars/;
print &PrintHeader;
print &HtmlTop ("CGI Example Using cgi-lib.pl");
if (&ReadParse(*in)) { print &Vars; } print &HtmlBot;
Все данные, переданные lib2.pl, действительно есть, но не сопровождаются пояснительным текстом. PrintVariables полезна при отладке, но вряд ли нужна в завершенном проекте.
Примеры приложений.
Огромное количество CGI-сценариев на языке Perl уже доступно в Интернете и готово к использованию. Вот список нескольких полезных источников (проверяйте каждый такой сценарий на защищенность, а также на предмет наличия любых других проблем):
Когда вы начнете писать сценарии, которые делают больше, чем простенькие предыдущие, проблема защиты станет актуальной. Это одна из тем, которая будет обсуждаться далее.
Защита CGI. Обеспечение безопасности всегда было серьезной проблемой. В наши дни она еще более актуальна, так как но мере развития операционных систем становится все сложнее и сложнее затыкать бреши в защите. Поэтому на Unix-системах CGI-сценарии обычно запускаются от имени идентификатора пользователя "nobody" («никто»). Такой процесс имеет минимум привилегий. Считалось, что процесс, имеющий минимум привилегий, принесет меньше вреда. Однако и по сей день могут возникать проблемы в частности, из-за неаккуратности в CGI-сценариях. Ниже рассказывается, как обойти некоторые наиболее вероятные неприятности.
Вот несколько Web-страниц, посвященных безопасности CGI, которые я рекомендовал бы прочитать до того, как вы начнете создавать для широкого использования что-либо серьезнее простейших CGI-сценариев:
страница WWW-консорциума, посвященная безопасности CGI (The World Wide Web Consortium's CGI security page), www.w3.org/Security/Faq/ www-security-faq.html;
часть сборника вопросов и ответов (FAQ) по CGI-программированию на Perl, посвященная проблемам безопасности, - www.perl.com/CPAN-local/doc/ FAQs/cgi/perl-cgi-faq.html;
страничка Селены Сол (Selena Sol), рассказывающая, как вы рискуете при установке чужих сценариев Stars.com/Authoring/Scripting/Sequrity;
Следующий шаг непосредственное изучение кода. В примерах вы найдете информацию о безопасности и о том, как писать CGI-сценарии для счетчиков и гостевых книг.
Серьезно беремся за защиту. CGI-сценарии могут порождать множество потенциальных брешей в безопасности. В качестве предельного случая рассмотрим сценарий, запускающий программы, имена которых передаются ему в качестве аргумента. Данные форм HTML посылаются в виде строк, причем в качестве разделителя аргументов используется вопросительный знак. Строка данных записывается в конце URL, что означает, что если вы хотите просто запустить сценарий Perl, URL должен выглядеть, например, так:
http://www yourserver.com/user/perl.exe?script.pl
Но если хакер увидит, что вы используете технику вроде этой, он может послать собственную строку такого вида:
http://www.yourserver.com/user/perl.exe?-e+ nasty commands
В результате он сможет выполнить любые команды Perl, что вряд ли вас порадует. Этот пример указывает на одну из самых больших опасностей CGI-сценариев, написанных на Perl, вызовы внешних программ без проверки кода, передаваемого в конце строки.
В Perl внешние программы вызываются многими способами, например с помощью строки, заключенной в обратные апострофы (backtics), вызовов system или ехес. Даже операторы eval требуют осторожного обращения. Очень важно настроить CGI так, чтобы нельзя было легко сделать ничего опасного. Хакеры собаку съели на использовании этого класса ошибок и CGI-сценариев для выполнения кода, нужного им.
На самом деле в Perl существует прекрасный механизм безопасности, предназначенный для латания дырок подобного типа, меченые данные. Если разрешено отслеживание данных, Perl не позволяет передавать пришедшие извне данные функциям system, ехес и т. д. Простое правило, позволяющее обеспечить безопасность, никогда не передавать непроверенные данные внешней программе и всегда стараться обойтись без запуска командной оболочки.
Если же это невозможно, следует всегда проверять аргументы на предмет наличия метасимволов командной оболочки и, по крайней мере, удаления их. Вот метасимволы командной оболочки Unix:
&;''\"*?`<>^(){}$\n\r
Еще одно важное замечание: не позволяйте другим перезаписывать ваши сценарии или файлы данных, неважно случайно или намеренно. Другими словами будьте особенно внимательны к правам доступа к файлам, чтобы их нельзя было заместить.
И, конечно же, обычные ограничения: не посылайте пароли по электронной почте, не набирайте их при работе с бесплатными утилитами. He оставляйте ваш счет в системе (account) на долгое время неиспользуемым хакеры следят за такими вещами, чтобы получить контроль над ними. Не позволяйте CGI-сценариям получать слишком много системной информации. И так далее, и тому подобное большинство хакеров пролезут там, где вы и не думали.
Работаем с мечеными данными. Одной из самых больших дыр в защите CGI-сценариев является передача непроверенных данных командному интерпретатору. В Perl для предотвращения таких ситуаций можно использовать механизм меченых данных (tainted data). В этом случае любые переменные, связанные с данными, полученными извне (включая переменные среды, стандартный поток ввода и командную строку), считаются мечеными. Пока они остаются таковыми, их нельзя использовать для чего бы то ни было за пределами вашей программы. Если меченая переменная используется для установки другой переменной, последняя также становится меченой, что означает, что помеченные (или «запачканные») данные могут распространяться по программе сколь угодно далеко и сколь угодно сложными путями, но они все равно будут аккуратно помечены.
Подсказка. Этот механизм работает только для скалярных значений. Некоторые элементы массива могут быть мечеными, в то время как остальные нет.
В общем, меченые данные не могут быть использованы при помощи вызовов eval, system, exec. Perl следит за тем, чтобы они не попали в команды, вызывающие оболочку, в команды, модифицирующие файлы, каталоги или процессы. Однако есть одно важное исключение: если вызовам system или eval передается список аргументов, он не проверяется на наличие меченых элементов. Если вы попробуете произвести какую-либо операцию с мечеными данными за пределами программы, Perl остановится с предупреждающим сообщением. В режиме меченых данных Perl прекращает работу также в случае вызова внешней программы без предварительной установки переменной среды PATH. В Perl версии 4 для включения отслеживания меченых данных используется специальная версия интерпретатора, называемая taintperl:
#!/usr/local/bin/taintperl
Однако в версии 5 проверка меченых данных включена в состав Perl, и вы можете включить ее, передав интерпретатору Perl ключ -T:
#!/usr/local/bin/perl -T
В следующем примере включается отслеживание меченых данных, но программа не делает ничего опасного соответственно, проблем нет:
#!/usr/local/bin/perl -T
print "Hello!\n";
Однако при выполнении потенциально опасных операторов типа system при включенной проверке меченых данных Perl сообщит о возможной бреши в защите, обусловленной использованием данных окружения. Даже если вы не используете PATH при вызове внешней программы, не исключено, что его использует вызываемая программа. Вот сообщение об ошибке, которое вы увидите:
#!/usr/local/bin/perl -Т
print system( 'date');
Insecure $ENV(PATH) while running with -T switch at taint.pl
line 5, <> chunk 1
Чтобы исправить это, вы можете при включенной проверке меченых данных самостоятельно установить $ENV{'PATH'}:
#!/usr/local/bin/perl -T
$ENV{'PATH'} = '/bin:/usr/bin:/usr/1ocal/bin';
print system('date'),
Thu Nov 12 19-55 53 NSK
Вот еще пример, в котором делается попытка передать системному вызову меченые данные. Даже если $ENV{'PATH'} устанавливается в программе, сценарий все равно прекращает работу, так как пытается передать меченые данные оператору system:
#!/usr/local/bin/perl -T
$ENV{'PATH'} = /bin:/usr/bin:/usr/local/bin';
while(<>) {
$command = $_;
system($command); }
Insecure dependency in system while running with -T switch at taint.pl line 5, <> chunk 1
Данные, даже будучи переданными в $command из $_, все равно считаются мечеными. Как очистить данные, если вы уверены в них?
Очистка данных. Единственный способ очистить меченую переменную использовать шаблоны, по которым из нее выбираются подстроки. В следующем примере предполагается, что меченая переменная $tainted содержит электронный адрес. Мы можем извлечь его и сохранить как не меченый в другой переменной следующим образом:
$tainted =~/(^[\w]+)\@(^[w]+)/
$username = $1;
$domain = $2;
print "$username\n",
print "$userdornain\n';
Таким образом, мы извлекли безопасные данные. То есть способ создания «чистых» данных заключается в извлечении из меченых данных подстрок, которые априори безопасны (и, конечно же, не содержат метасимволы командного интерпретатора).
Создаем счетчик посещений. Создание счетчика посещений достаточно простая задача: вы просто должны хранить текущее значения счетчика в файле и показывать его при необходимости. Я приведу пример создания счетчика counter.pl.
Подсказка. Заметьте, что этот счетчик просто выводит текущее значение как текстовую строку, но в принципе можно сделать более интересные вещи, например, создать графический счетчик, имея набор файлов с изображениями цифр и выводя их один за другим на Web-странице. Можно также воспроизводить цифры с помощью тега HTML <IMG>, если установить атрибут SRC в соответствии с URL сценария, выводящего цифры.
Сценарий называется counter.pl, приведен он в листинге 6-12. Чтобы он работал, в том же каталоге, что и counter.pl, должен находиться файл counter.dat. Для начала отсчета запишите в counter.dat 0 (ноль) с помощью любого текстового
Листинг 5.13. counter.pl
#!/usr/bin/perl
use CGI;
$co = new CGI;
open (COUNT, "<counter.dat")
or die "Could not open counter data file!";
$count = <COUNT>;
close COUNT;
$count++;
open (COUNT, ">counter.dat");
print COUNT $count;
close COUNT;
$co->header,
$co->start_html(
-title=>'Counter Example' ,
-author=>'Andrew',
-BGCOLOR=>'white'),
$co->center($co->h1('Counter Example')), $co->p,
$co->center($co->h3("Current count ", $count)), $co->p,
$co->center($co->h3("Reload the page to update the count")),
$co->end_html;
Этот сценарий очень прост: все, что он делает, это читает число, хранящееся в counter.dat, увеличивает его на единицу, записывает обратно в counter.dat и затем показывает увеличенный счетчик
Создаем гостевую книгу. Создание гостевой книги шаг вперед по сравнению со счетчиком. Гостевая книга собирает комментарии пользователей и сохраняет их в файле, обычно имеющем формат HTML, чтобы затем выводить их на странице. Наша гостевая книга использует три файла, хранящихся в одном каталоге: guestbook.htm (листинг 6-14), guestbook.pl (листинг 6-15) и book.htm (листинг 6-16). Первый является лицом гостевой книги, то есть именно эта страница указывает пользователю, что он может добавить запись в книгу посетителей. Она получает имя пользователя и комментарий. Когда пользователь нажимает на кнопку подтверждения, данные посылаются сценарию guestbook.pl; иными словами, если вы используете этот сценарий, вам следует сменить указанный URL в guestbook.htm на реальный URL guestbook.pl:
<BODY><H1>Please add to my guestbook</H1>
<FORM METHOD=POST ACTION=
"http://www.yourself.com/cgi/guestbook.pl">
В guestbook.pl (см. листинг 6-15) мы открываем собственно гостевую книгу, хранящуюся в файле book.htm. Основная идея добавить в нее имя пользователя и его комментарий, но book.htm заканчивается тегами </BODY></HTML>. Поэтому сначала надо установить указатель файла перед этими словами с помощью следующего кода:
open (BOOK, ">>book.htm") or die "Could not open guestbook!";
seek (BOOK, -lenght($co->end_html), 2);
Поскольку строки </BODY></HTML> в данном случае создаются с помощью CGI-метода end_html, мы откатываемся назад на длину генерируемой строки, что позволяет нам не зависеть от того, что метод end_html будет выводить в следующих версиях модуля CGI.pm.
После этого код записывает вместо тегов </BODY></HTML> новые данные, добавляя в конце те же теги вызовом CGI-метода end_html. Затем guestbook.pl создает страницу, на которой располагается благодарность пользователю за комментарии и гиперссылка, позволяющая просмотреть содержимое гостевой книги. Иными словами, если вы используете этот сценарий, вам следует сменить URL, приведенный в листинге, на реальный URL book.htm (убедившись, что права доступа для этого файла достаточно низки, чтобы guestbook.cgi мог записывать в него данные):
"If you whant to take a look at the guest book, "
$co->a({href=> "http://www.yourserver.com/cgi/book.htm"},
"click here"),".",
Если пользователь щелкает на гиперссылке, открывается гостевая книга, а ссылки на нее можно расположить на любой другой Web-странице вашего раздела. Имя пользователя и комментарии отображаются в гостевой книге вместе со временем добавления записи. Файл guestbook.pl приводит в безопасное состояние любой код HTML, который пользователь может попытаться ввести в гостевую книгу, замещая любые символы < HTML-кодом < (это делается так: $username =~ s/</< и $text =~ s/</<), который выводит "<", чтобы не позволять броузеру пытаться разобрать комментарии пользователя как HTML. Это означает, что любой код HTML, который пользователь попытается ввести в гостевую книгу, будет выведен как текст и не будет исполняться.
Заметьте, что вы можете настроить guestbook.pl так, чтобы он принимал электронные адреса посетителей (впрочем, все больше и больше пользователей не желают оставлять свои адреса не столько из соображений секретности, сколько из-за программ, которые сканируют сеть в поисках адресов электронной почты, а затем продают полученные списки распространителям рекламы). Вы также можете видоизменить файл гостевой книги book.htm, добавив графику с по мощью тега HTML <IMG>, установив фоновое изображение и т. д. Просто следите, чтобы последним, что вы выводите в book.htm был текст </BODY></HTML> (или вывод текущей версии CGI-метода end_html, поскольку в новой версии пакета CGI.pm он может смениться), чтобы guestbook.pl мог откатиться на необходимое число символов и заменить эти теги новым комментарием.
Подсказка. Если вы не хотите зависеть от версии модуля CGI.pl, записывайте в отдельный файл длину текста, выведенного методом end_html при последней записи в гостевую книгу и используйте для установки указателя это значение, а не длину строки, выводимой end_html.
Листинг 5.14. guestbook.htm
<HTML><HEAD><TITLE>Add to the guest book</TITLE></HEAD><BODY>
<Н1>Р1еаsе add to my guestbook</H1>
<FORM METHOD = POST ACTION =
http://www.yourself.com/cgi/guestbook.pl >
<BR><CENTER>Please enter your name<P>
<INPUT TYPE = "TEXT" NAME ="username"></INPUT>
<BR>Please enter your comments
<TEXTAREA ROWS = 8 COLS = 40 NAME = "comments">
</TEXTAREA><BR><BR>
<INPUT TYPE =SUBMIT VALUE =Send><INPUT type = RESET VALUE =Reset>
</CENTER></FORM>
</BODY></HTML>
Листинг 5.15. guestbook.pl
#!/usr/bin/perl
use CGI; $co = new CGI;
open (BOOK ">>book.htm") or die "Could not open guestBOOK";
seek (BOOK -lenght($co->end_html), 2)
$date = date; chop($date);
$username = $co->param("username"); $username =" s/<l</;
print BOOK,
$co->h3("New comments by :, $username, "on ",
$date,$co->p,$text,),
$co->hr, $co->end_html;
close BOOK;
print $co->header, $co->start_html(
-title=> "Guest Book Example" ,
-author=> "Andrew",
-BGCOLOR=> "white",
-LINK=> "red" );
$co->center($co->h1('Thanks for adding to the guest book!')),
"If you whant to take a look at the guest book, ",
$co->a({href=> http://www.yourserver.com/cgi/book.htm"},
" click here"),".",
$co->hr, $co->end_html;
Листинг 5.16. book.htm
<HTML><HEAD><TITLE>The Guest Book</TITLE></HEAD>
<BODY><CENTER>
<H1>Here is the guest book </H1><HR>
</BODY></HTML>
Теневые посылки (cookies). Создание и чтение теневой посылки (английское cookie, на современном новоязе именуемая также "кука", "кукис") стало популярным в Интернете - по крайней мере, среди Web-программистов. Под этим именем подразумевается использование протокола HTTP для хранения информации, полученной от сервера, на машине клиента и обмен этой информацией между компьютерами и программами-броузерами незаметно от пользователя, то есть в теневом режиме.
Теневые посылки и любимы и ненавидимы. Некоторые пользователи протестуют против получения и обработки теневых посылок на своих компьютерах и запрещают этот режим для своих броузеров. Поэтому приведенный ниже пример не активизирует их до тех пор, пока пользователь сам не введет данные для работы теневой посылки. Этот сценарий хранит данные в хеше, поэтому его легко подстроить для использования в других сценариях.
Использование теневых посылок. Многие пользователи терпеть не могут, когда на их компьютерах сохраняются мегабайты информации подобного рода. Мне приходилось видеть Web-страницу, на которой было более 70 теневых посылок. Это не столь безобидно как кажется. В большинстве броузеров верхний предел теневых посылок число порядка 200. Поскольку теневые посылки позволяют отслеживать передвижение пользователей по разделам, а также хранить настройки пользователя, то теплые чувства к теневым посылкам иногда все же преобладают над раздражением.
Сценарий, приведенный в листинге 6-17, позволяет посетителю изменить страницу так, чтобы при следующих визитах она бы приветствовала ею по имени, а в день рождения еще и поздравляла бы. Этот сценарий вполне корректен он не устанавливает никаких теневых посылок до тех пор, пока пользователь сам не предоставит или не обновит необходимую информацию. Сценарий проверяет данные, полученные от пользователя чтобы убедиться, что день рождения введен в формате месяц/день (mm/dd), содержит лишь цифры, а единственный символ / находится в нужном месте, и удаляет теги HTML, которые он мог ввести в строку для имени. Когда пользователь впервые открывает сценарий hellocookie.pl, он может настроить эту страницу, введя имя и цату рождения в формате mm/dd. После нажатия на кнопку подтверждения сценарий записывает информацию под именем greetings, сохраняя имя и день рождения, на компьютере клиента.
Когда пользователь снова открывает hellocookie.pl, сценарий проверяет, нет ли у посетителя теневой посылки greetings, и, если она есть, выводит приветствие, включая при необходимости поздравление с днем рождения. Вот и все.
Как записать теневую посылку. Записать теневую посылку с помощью CGI.pm несложно. В нашем примере она будет называться "greetings" и хранить информацию в хэше %greetings, уничтожая ее по истечении года:
$со = new CGI:
$greetingcookie = $co->cookie(
-name=>'greetings',
-value=>\%greetings,
-expires=>'+365d' );
print $co->header(-cookie=>$greetingcookie);
Заметьте, что для создания теневой посылки вы передаете ее в качестве именованного параметра CGI-методу header.
Как прочитать теневую посылку. Для чтения теневой посылки используется обычный CGI-метод, получающий в качестве параметра имя посылки. После этой операции можно использовать данные хэша %greetings:
$со = new CGI;
%greetings = $co->cookie('greeting');
print $greetings{'name'}
Вот и вся работа с теневыми посылками. Но имейте в виду, что многие пользователи не желают, чтобы программы хранили какие бы то ни было данные на их машинах.
Листинг 5.17. Hellocookie.pl
#!/usr/bin/perl
use CGI;
$co = new CGI;
%greetings = $co->cookie('greeting');
if ($co->param('name')) {$greetings{'name'} =$co->param('name')}
print $greetings{'name'};
if ($co->param('birthday') )
=~ m/\d\d\/\d\d/)
{$greetings{'birthday'} = $co->param('birthday');}
($date, $month, $year) = (lockaltime)[3, 4, 5];
$date = join ("/", $month + 1, $day);
if(exists($greetings{'name'})) {
$greetingstring = "Hello ". $greetings{'name'};
$greetingstring .= ", happy birthday!" if ($date eq $greetings{'birthday'});
$greetingstring =~ s/</</;
$promt = "If you want to change this page's settings,
just enter new data below.";
} else { $promt = "To have this page greet you next time,
enter your data below "; }
$greetingcookie = $co->cookie(
-name=>'greetings',
-value=>\%greetings,
-expires=>'+365d' );
if ($co->param('name') || $co->param('birthday')) {
print $co->header(-cookie=>$greetingcookie);
} else { print $co->header;}
$co->start_html(-title=>"Cookie Example",), $co->center(
$co->h1("Cookie Example"), $co->p, $co->h1("$greetingstring"),
$promt, $co->startform, "Your name: ",
$co->textfield(
-name=>'name',
-default=>'',
-override=>1 ), $co->p,
"Your birthday (mm/dd): ", $co->textfield(
-name=>'birthday',
-default=>'',
-override=>1
),
$co->p, $co->submit, $co->reset,
$co->endform), $co->end_html;
Доступ к базам данных.
Язык программирования Perl превратился из инструмента, используемого преимущественно администраторами Unix-систем, в наиболее распространенную платформу разработки для World Wide Web. Perl не предназначался изначально для Web, но простота его использования и мощные функции для работы с текстом сделали естественным его применение для CGI-программирования. Сходным образом, MySQL со своей высокой скоростью и широкми возможностями стала очень привлекательным средством для веб-разработчиков. Естественно поэтому, что был разработан интерфейс Perl к MySQL, объединив, таким образом их достоинства.
В настоящий момент существуют два интерфейса между Perl и MySQL. Более ранний состоит из специализированного интерфейса Mysql.pm. Другой, более новый интерфейс является подключаемым модулем в комплекте DBI (DataBase Independent) - независимых от базы данных модулей. DBI является попыткой обеспечить общий Perl API для доступа к любым базам данных и предоставления более высокой переносимости. Интерфейс DBI стал наиболее надежным и стандартным, и разработчики MySQL рекомендуют пользоваться только DBI, поскольку дальнейшая разработка модуля Mysql.pm прекращена. Однако многие унаследованные системы все еще используют их, поэтому мы расскажем здесь и о них.
DBI. Рекомендуемым методом доступа к базам данных MySQL из Perl является интерфейс DBD/DBI. DBD/DBI означает DataBase Dependent/DataBase Independent (Зависимый от базы данных/Независимый от базы данных). Название связано с двухъярусной реализацией интерфейса. В нижнем ярусе находится зависимый от базы данных уровень. На нем существуют свои модули для каждого типа базы данных, доступного из Perl. Поверх этого уровня находится независимый от базы данных уровень. Это тот интерфейс, которым вы пользуетесь при доступе к базе данных. Выгода такой схемы в том, что программисту нужно знать только один API уровня независимости от базы данных. Когда появляется новая база данных, кому-нибудь нужно лишь написать для нее модуль DBD (зависимый), и она станет доступна всем программистам, использующим DBD/DBI.
Как и в любом модуле Perl, для получения доступа нужно указать DBI в директиве use:
#!/usr/bin/perl -w
use strict;
use CGI qw(:standard);
use DBI;
При запуске программ Perl для MySQL/mSQL следует всегда задавать аргумент командной строки -w. Благодаря этому DBI будет перенаправлять все специфические для MySQL сообщения об ошибках на STDERR, и вы сможете увидеть ошибки, вызванные работой с базой данных, не прибегая к явной проверке их в программе.
Всякое взаимодействие между Perl, с одной стороны, и MySQL с другой, производится с помощью объекта, известного как описатель базы данных (handle). Описатель базы данных (database handle) - это объект, представленный в Perl как скалярная ссылка и реализующий все методы, используемые для связи с базой данных. Одновременно можно открыть любое число описателей базы данных, ограничение накладывают только ресурсы системы. Метод connect() использует для создания описателя формат соединения DBI:servertype:database:hostname:port (имя узла и порта необязательны), дополнительными аргументами служат имя пользователя и пароль:
my $dbh= DBI->connect( 'DBI:mysql:mydata ', undef, undef);
my $dbh = DBI->connect('DBI:mysql:mydata', 'me', 'mypass"};
Атрибут servertype является именем специфического для базы данных DBD-модуля, в нашем случае «mysql» (обратите внимание на точное использование регистра). В первом варианте создается соединение с сервером MySQL на локальной машине через сокет Unix. Это наиболее эффективный способ связи с базой данных, который должен использоваться при соединении на локальном сервере. Если указано имя узла, оно используется для соединения с сервером на этом узле через стандартный порт, если только не задан и номер порта. Если при соединении с сервером MySQL вы не указываете имя пользователя и пароль, то пользователь, выполняющий программу, должен обладать достаточными привилегиями в базе данных MySQL.
В Perl 5 используются два соглашения по вызову модулей. В объектно-ориентированном синтаксисе для ссылки на метод определенного класса используется символ стрелки «->» (как в DBI->connect). Другой метод - использование непрямого синтаксиса, в котором за именем метода следует имя класса, а затем - аргументы. В последнем примере метод connect следовало бы записать как connect DBI 'DBI:mysql:mydata', 'me', 'mypass'.
После соединения с сервером MySQL описатель базы данных - во всех примерах этого раздела $dbh - становится шлюзом к базе данных. Например, так готовится запрос SQL:
$dbh->prepare($query);
При работе с MySQL можно включать в запрос другие базы данных, явно указывая их имена. Кроме того, в MySQL, при необходимости одновременного доступа к нескольким базам данных можно создать несколько описателей базы данных и использовать их совместно.
Для иллюстрации использования DBI рассмотрим следующие простые программы. В листинге 5.18 datashow.pl принимает в качестве параметра имя узла; при отсутствии параметра принимается имя «localhost». Затем программа выводит список всех баз данных, имеющихся на этом узле.
Листинг 5.18. Скрипт datashow.pl показывает базы данных, имеющиеся на сервере MySQL
#!/usr/bin/perl -w
use strict;
use CGI qw( standard);
use CGI Carp;
# Использовать модуль DBI use DBI CGI use_named_parameters(1),
my ($server, $sock, $host);
my $output = new CGI;
$server = param('server') or $server ="";
# Подготовить DBD-драйвер для MySQL
my $driver = DBI->install_driver('mysql');
my @databases = $driver->func($server '_ListDBs');
# Если параметр §databases не определен, # предполагаем, что на
# этом узле не запущен сервер MySQL Однако это может быть вызвано
# другими причинами Полный текст сообщения об ошибке
# можно получить проверив $DBI errmsg
if (not @databases) {
print header, start_html( title => Данные по $server , BGCOLOR => white ),
print <<END_OF_HTML, <H1>$server</h1>
Ha $server , по-видимому не запущен сервер mSQL </body></html> END_OF_HTML
exit(0)}
print header, start_html(title =>"Данные по $host", BGCOLOR => white),
print <<END_OF_HTML, <H1>$host</h1>
<P>Соединение с $host на сокете $sock<P>Базы данных <br><UL>
END_OF_HTML
foreach(@databases) {
print "<LI>$_\n";}
print << END_OF_HTML
</ul></body></html>
exit(0)
В листинге 5.19 tableshow.pl принимает в качестве параметров имя сервера базы данных (по умолчанию «localhost») и имя базы данных на этом сервере. Затем программа показывает все таблицы, имеющиеся в этой базе данных.
Листинг 5.19. Скрипт tableshow.pl выводит список всех таблиц в базе данных
#!/usr/bin/perl -w
use strict;
use CGI qw( standard);
use CGI Carp;
# Использовать модуль Msql.pm
use DBI;
CGI use_named_parameters(1);
my ($db);
my $output = new CGI;
$db = param( db ) or die( He указана база данных' );
# Connect to the requested server
my $dbh = DBI->connect( DBI mysql $db $server, undef, undef);
# Если не существует $dbh значит, попытка соединения с сервером
# базы данных не удалась Возможно, сервер не запущен,
# или не существует указанной базы данных
if (not $dbh) {
print header, start_html( title => Данные по $host => $db ,
BGCOLOR => white )
print <<END_OF_HTML <H1>$host</h1> <H2>$db</h2>
Попытка соединения не удалась по следующей причине <BR>
$DBI errstr </body></html> END_OF_HTML
exit(0) }
print header, start_html( title => Данные по $host => $db ,
BGCOLOR => white ), print <<END_OF_HTML,
<H1>$host</h1> <H2>$db</h2><р>
Таблицы:<br><UL>
END_OF_HTML
# $dbh->listtable возвращает массив таблиц,
# имеющихся в текущей базе данных.
my @tables = $dbh->func('_ListTables');
foreach (@tables) {
print "<LI>$_\n"; }
print <<END_OF_HTML; </ul>
</body></html> END_OF_HTML
exit(0);
И наконец, листинг 5.20 показывает, как вывести все сведения о некоторой таблице.
Листинг 5.20. Скрипт tabledump.pl выводит сведения об указанной таблице
#!/usr/bin/perl -w
use strict;
use CGI qw(:standard);
use CGI::Carp;
# Использовать модуль DBI
use DBI;
CGI::use_named_parameters(1);
my ($db,$table);
my $output = new CGI;
$server = param('server') or $server = '';
$db = param('db') or die("He указана база данных !");
# Соединиться с указанным сервером.
my $dbh = DBI->connect("DBI:mysql:$db:$server", undef, undef);
# Готовим запрос к серверу, требующий все данные таблицы.
my $table_data = $dbh->prepare("select * from $table");
# Посылаем запрос серверу.
$table_data->execute;
# Если возвращаемое значение не определено, таблица не существует # или пуста; мы не проверяем, что из двух верно,
if (not $table_data) {
print header, start_html('title'=>
"Данные no $host => $db => $table", 'BGCOLOR'=>'white');
print <<END_OF_HTML;
<H1>$host</h1><H2>$db</h2>
Таблицы'$table' нет в $db на $host.
</body></html>
END_OF_HTML
exit(0); }
# Теперь мы знаем, что есть данные для выдачи. Сначала выведем
# структуру таблицы.
print header, start_html('title'=>"Данные по $host => $db =>
$table",'BGCOLOR'=>'white');
print <<END_OF_HTML; <H1>$host</h1> <H2>$db</h2>
<H3>$table</h3><p><TABLE BORDER> <CAPTION>Поля</caption> <TR>
<TH>Поле<TH>Тип<TH>Pa3Mep<TH>NOT NULL </tr> <UL>
END_OF_HTML
# $table_data->name возвращает ссылку
# на массив полей таблицы.
my @fields = @{$table_data->NAME};
# $table_data->type возвращает ссылку на массив типов полей.
# Возвращаемые типы имеют стандартные обозначения SQL,
# а не специфические для MySQL.
my @types = @{$table_data->TYPE};
# $table_data->is_not_null возвращает ссылку на массив типа Boolean,
# указывающий, в каких полях установлен флаг 'NOT NULL'.
my @not_null = @{$table_data->is_not_null};
# $table_data->length возвращает ссылку на массив длин полей. Они
фиксированные для типов INT и REAL, но переменные (
# заданные при создании таблицы) для CHAR.
my @length = @{$table_data->length};
# Все перечисленные выше массивы возвращаются в одном и том же
# порядке, поэтому $fields[0],$types[0], $not_null[0] and
# $length[0] относятся к одному полю.
foreach $field (0. .$#fields) {
print "<TR>\n"; print "<TD>$fields[$field]<TD>$types[$field]<TD>";
print $length[$field] if $types[$field] eq 'SQL_CHAR';
print "<TD>";
print 'Y' if ($not_null[$field]);
print "</tr>\n"; }
print <<END_OF_HTML; </table>
<P><B>Data</b><br><OL>
END_OF_HTML
#построчно перемещаемся по данным: DBI::fetchrow_array().
# Мы сохраним данные в массиве в таком же порядке, как и в
# массивах (@fields, @types, etc.), которые мы создали раньше.
while(my((@data)=$table_data->fetchrow_array) {
print "<LI>\n<UL>";
for (0..$#data) {
print "<LI>$fields[$_] => $data[$_]</li>\n";
}
print "</ul></li>"; }
print <<END_OF_HTML;
</ol>
</body></html>
END_OF_HTML
Пример приложения, использующего DBI. DBI допускает любые SQL-запросы, поддерживаемые MySQL. Например, рассмотрим базу данных, используемую в школе для ведения учета учащихся, состава классов, результатов экзаменов и т. д. База данных должна содержать несколько таблиц: одну для данных о предметах, другую для данных об учащихся, таблицу для списка экзаменов и по одной таблице для каждого экзамена. Возможность MySQL выбирать данные из нескольких таблиц, используя объединение таблиц, позволяет совместно использовать таблицы как согласованное целое для создания приложения, облегчающего работу учителя.
Для начала мы хотим учесть данные об экзаменах по разным предметам. Для этого нам нужна таблица, содержащая названия и числовые идентификаторы для экзаменов. Нам потребуется также отдельная таблица для каждого экзамена. В этой таблице будут содержаться результаты испытаний для всех учащихся, а также высший балл для проведения сравнения. Таблица test имеет следующую структуру:
CREATE TABLE test (
id INT NOT NULL AUTO_INCREMENT, name CHAR(100),
subject INT, num INT
Для каждого отдельного экзамена структура таблицы такая:
CREATE TABLE t1 (
id INT NOT NULL, q1 INT, q2 INT, q3 INT, q4 INT, total INT )
К имени таблицы t присоединен идентификатор экзамена из таблицы test. При создании таблицы пользователь определяет количество вопросов. Поле total содержит сумму баллов по всем вопросам.
Программа, обрабатывающая данные экзаменов, называется test.pl. Эта программа, текст которой следует ниже, позволяет только добавлять новые экзамены. Просмотр экзаменов и их изменение не реализованы и оставлены в качестве упражнения.
Этот сценарий хорошо показывает возможности DBI:
Листинг 5.21. Скрипт test.pl добавляет новые экзамены
#!/usr/bin/perl -w
use strict;
require my_end;
use CGI qw(:standard);
my $output = new CGI;
use_named_parameters(1);
# Использовать модуль DBI.
use DBI;
# DBI::connect() использует формат 'DBI:driver:database'
# используется драйвер MySQL и открывается база данных 'teach',
my $dbh = DBI->connect('DBI:mysql:teach');
# Операция добавления распределена между тремя функциями. Add -
# выводит пользователю форму шаблона для создания нового экзамена, sub add {
$subject = param('subject') if (param('subjects')); $subject = "" if $subject eq 'all';
print header, start_html('title'=>'Create a New Test', 'BGCOLOR'=>'white');
print <<END_OF_HTML; <Н1>Создание нового экзамена</п1>
<FORM ACTION="test.pl" METHOD=POST>
<INPUT TYPE=HIDDEN NAME="action" VALUE="add2"> Предмет: END_OF_HTML
my @ids = ();
my %subjects = ();
my $out2 = $dbh->prepare("select id,name from subject order by name");
$out2->execute;
while(my($id,$subject)=$out2->fetchrow_array) {push(@ids,$id);
$subjects{"$id"}'= $subject; }
print popup_menu(
'name'=>'subjects',
'values'=>[@ids],
'default'=>$subject,
'labels'=>\%subjects);
print <<END_OF_HTML; <br>
Число вопросов: <INPUT NAME="num" SIZE=5><br>
Название или идентификатор (например, дата) экзамена:
<INPUT NAME="name" SIZE=20> <Р>
<INPUT TYPE=SUBMIT VALUE=" Следующая страница ">
<INPUT TYPE=RESET> </form></body></html> END_OF_HTML }
Эта функция выводит форму, позволяющую пользователю выбрать предмет для экзамена, а также количество вопросов и название. Для вывода списка имеющихся предметов выполняется запрос к таблице предметов. При выполнении в DBI запроса SELECT он должен быть сначала подготовлен, а затем выполнен. Функция DBI::prepare полезна при работе с некоторыми серверами баз данных, позволяющими осуществить операции над подготовленными запросами, прежде чем выполнить их. Для MySQL это означает лишь запоминание запроса до вызова функции DBI:: execute .
Результаты работы этой функции посылаются функции add2, как показано ниже:
Листинг 5.21. Продолжение скрипта test.pl
sub add2 {
my $subject = param('subjects');
my $num = param('num');
$name = param('name') if param('name');
my $out = $dbh->prepare("select name from subject where
id=$subject");
$out->execute;
my ($subname) = $out->fetchrow_array;
print header, start_html('title'=>"Создание экзамена по предмету $subname", 'BGCOLOR'=>'white');
print <<END_OF_HTML;
<H1> Создание экзамена по предмету $subname</h1> <h2>$name</h2>
<P><FORM ACTION="test.pl" METHOD=POST>
<INPUT TYPE=HIDDEN NAME="action" VALUE="add3">
<INPUT TYPE=HIDDEN NAME="subjects" VALUE="$subject">
<INPUT TYPE=HIDDEN NAME="num" VALUE="$num">
<INPUT TYPE=HIDDEN NAME="name" VALUE="$name">
Введите количество баллов за каждый правильный ответ.
Сумма баллов не обязательно должна равняться 100.
<Р> END_OF_HTML
for (1.,$num) {
print qq%$_: <INPUT NAME="q$_" SIZE=3> %;
if (not $_ % 5) { print "<br>\n", }
}
print <<END_OF_HTML;
<P>Введите текст экзамена:<br>
<TEXTAREA NAME="test" ROWS=20 COLS=60>
</textarea><P>
<INPUT TYPE=SUBMIT VALUE="Ввести экзамен ">
<INPUT TYPE=RESET>
</form></body></html>
END_OF_HTML
}
Эта функция динамически генерирует форму для экзамена, основываясь на параметрах, введенных в предыдущей форме. Пользователь может ввести количество баллов для каждого вопроса экзамена и полный текст самого экзамена. Выходные данные этой функции посылаются завершающей функции add3, как показано ниже:
Листинг 5.21. Продолжение скрипта test.pl
sub add3 {
my $subject = param('subjects');
my $num = param('num');
$name = param('name') if param('name');
my $qname;
($qname = $name) =" s/'/\\'/g;
my $q1 = "insert into test (id, name, subject, num)
values ("'', $qname', $subject, $num)";
my $in = $dbh->prepare($q1);
$in->execute;
# Извлечем значение ID , которое MySQL создал для нас
my $id = $in->insertid,
my $query = "create table t$id (id INT NOT NULL, ";
my $def = "insert into t$id values ( 0, ";
my $total = 0;
my @qs = grep(/"q\d+$/,param);
foreach (@qs) {
$query = $_ " INT,\n',
my $value = 0,
$value = param($_) if param($_);
$def = '$value, ";
$total += $value; }
$query = "total INT\n)"; $def = "$total)',
my $in2 = $dbh->prepare($query);
$in2->execute;
my $in3 = $dbh->prepare($def);
$in3->execute,
open(TEST,">teach/tests/$id") or die('A: $id $' ),
print TEST param('test'), \n",
close TEST;
print header, start_html('title =>'Экзамен создан', ;'BGCOLOR'=>'white');
print <<END_OF_HTML; <Н1>Экзамен создан</H1> <p>Экзамен создан<P>
<A HREF='..'>Перейти</а> на домашнюю страницу 'В помощь учителю'.<br>
<А HREF='test.pl'>перейти</a> на главную страницу экзаменов.<br>
<А HREF= 'test.pl'?action=add'>Добавить</a> следующий экзамен
</body></html>
END_OF_HTML
}
MysqlPerl. Монти Видениус, автор MySQL, написал также и интерфейс Perl к MySQL, Mysql.pm. Mysql.pm входит составной частью в пакет msql-mysql-modules Йохена Видмана (Jochen Wiedmann).
Чтобы показать некоторые функции Mysql.pm, вернемся к примеру с экзаменами. Займемся таблицей предметов. Ее структура описана выше.
Чтобы продемонстрировать, как работает Mysql.pm, мы подробно изучим ту часть subject.pl, которая позволяет пользователю добавлять предметы. Операция «add» (добавление) была так же разбита на две отдельные функции. Первая функция - изменения, выводит форму, позволяющую ввести название предмета и фамилию преподавателя:
Листинг 5.22. Скрипт subject.pl
#!/usr/bin/perl -w
use strict;
use CGI qw(:standard);
my $output = new CGI;
use_named_parameters(1);
# Использовать модуль MySQL
use MySQL;
# используется localhost и открывается база данных 'teach',
my $dbh = Mysql->connect(undef,'teach', 'teacher',undef);
# Операция добавления распределена между двумя функциями. Add -
# выводит пользователю форму шаблона для создания нового предмета,
&add if not (param('action'));
foreach(param('action')){
/add2/ and do{&add2; last;};
}
sub add {
print header, start_html('title'=>'Добавить предмет','BGCOLOR'=>'white');
print <<END_OF_HTML;
<H1>Добавить предмет</h1> <form METHOD=POST ACTION="subject.pl"> <P>Название предмета:<input TYPE=TEXT size=40 name="name" ><br>
Фамилия учителя: <input TYPE=TEXT size=40 name="teacher" ><br><INPUT TYPE=HIDDEN NAME="action" VALUE="add2">
<INPUT TYPE=SUBMIT VALUE="Следующая страница "><INPUT TYPE=RESET> </form><p><A HREF="..">Перейти</a> к домашней странице Помощи учителю. </body></html>
END_OF_HTML
}
Функция проверяет, не имеют ли какие-либо поля предустановленные значения. Это придает функции дополнительную гибкость, позволяя использовать ее как шаблон для классов со значениями по умолчанию, возможно, генерируемыми какой-либо другой CGI-программой.
Значения, полученные в первой части процесса добавления, передаются обратно CGI-программе для использования в функции add2. Функция add2 сначала проверяет, существует ли уже предмет. Если существует, то пользователю посылается сообщение об ошибке, и он может ввести другой предмет.
Листинг 5.22. Продолжение скрипта students.pl
sub add2 {
my $name = param('name');
my $query_name = "'".$name."'";
# Строим запрос для проверки существования предмета,
# Если введена фамилию учителя, отдельно проверяем фамилию,
# поскольку могут быть два курса с одинаковым названием, но
# разными учителями,
my ($teacher, $query_teacher, $query);
$query ="select id, name ";# from subject where name=$query_name";
if (param('teacher')) {
$teacher = param('teacher'); $query.=", teacher";
$query_teacher = "'".$teacher."'"; }
$query .= " from subject where name=$query_name";
if (param('teacher')) {$query .= " and teacher=$query_teacher";}
# Теперь посылаем запрос серверу MySQL.
my $out = $dbh->query($query);
# Проверяем значение $out->numrows, чтобы узнать, были ли
# возвращены какие-либо строки. Если были, то мы выходим с
# сообщением, что предмет уже существует
if ($out->numrows)
{ # Печать страницы 'Предмет уже существует'.
print header, start_html('title' =>'Предмет уже существует', 'BGCOLOR'=>'white');
print <<END_OF_HTML;
<h1>Предмет уже существует</H1><A HREF='..'>Перейти</a> на домашнюю страницу 'В помощь учителю'.<br>
<A HREF= 'subject.pl'?action=add'>Добавить</a> следующий предмет
</body></html>
END_OF_HTML
} else {
$out = $dbh->query("select * from subject");
my ($id) = $out->fetchrow+1;
# Теперь вводим информацию в базу данных, используя
# полученное число в качестве ID
$query = "INSERT INTO subject (id, name, teacher) VALUES ($id, $query_name, $query_teacher)"; $dbh->query($query);
print header, start_html('title' =>'Предмет создан', 'BGCOLOR'=>'white');
print <<END_OF_HTML;
<h1>Предмет создан</H1><A HREF='..'>Перейти</a> на домашнюю страницу 'В помощь учителю'.<br>
<A HREF= 'subject.pl'?action=add'>Добавить</a> следующий предмет
</body></html>
END_OF_HTML
}
Технологии применения. Примеры программ
Что такое РНР?
РНР это серверный язык создания сценариев (или стороны сервера), разработанный специально для Web. В HTML-страницу можно внедрить код РНР, который будет выполняться при каждом ее посещении. Код РНР интерпретируется Web-сервером и генерирует HTML или иной вывод, наблюдаемый посетителем страницы.
Разработка РНР была начата в 1994 г. и вначале выполнялась одним человеком, Расмусом Лердорфом (Rasmus Lerdorf). Этот язык был принят рядом талантливых людей и претерпел три основных редакции, пока не стал широко используемым и зрелым продуктом, с которым мы имеем дело сегодня. К январю 2001 г. он использовался почти в пяти миллионах доменов во всем мире и их число продолжает быстро расти. Количество доменов, в которых в настоящее время используется РНР, можно выяснить на странице http://www.php.net/ usage.php. РНР это продукт с открытым исходным кодом (Open Source). У пользователя имеется доступ к исходному коду. Его можно использовать, изменять и свободно распространять другим пользователям или организациям.
Первоначально РНР являлось сокращением от Personal Home Page (Персональная начальная страница), но затем это название было изменено в соответствии с рекурсивным соглашением по наименованию GNU (GNU = Gnu's Not Unix) и теперь означает РНР Hypertext Preprocessor (Препроцессор гипертекста РНР).
В настоящее время основной версией РНР является четвертая. Адрес начальной страницы для РНР http://www.php.net
Что нового в РНР 4? Имеется ряд важных усовершенствований 4 версии:
Некоторые преимущества РНР. К числу конкурентов РНР относятся Perl, Active Server Pages (ASP) от Microsoft, Java Server Pages (JSP) и Allaire Cold Fusion.
РНР обладает множеством преимуществ по сравнению с этими продуктами:
• Высокая производительность
• Наличие интерфейсов ко многим различным системам баз данных
• Встроенные библиотеки для выполнения многих общих задач, связанных с Web
• Низкая стоимость
• Простота изучения и использования
• Переместимость
• Доступность исходного кода
Производительность. РНР исключительно эффективен. Используя единственный недорогой сервер, можно обслуживать миллионы обращений в день. Результаты тестирования, опубликованные компанией Zend Technologies (http://www.zend.com), подтверждают более высокую производительность РНР по сравнению с конкурирующими продуктами.
Интеграция с базами данных. РНР обладает встроенной связностью со многими системами баз данных. В дополнение к MySQL, в числе прочих можно непосредственно подключаться к базам данных PostgreSQL, mSQL, Oracle, dbm, Hyperware, Informix, InterBase и Sybase.
Используя Open Database Connectivity Standard (Стандарт открытого интерфейса связи с базами данных, ODBC), можно подключаться к любой базе данных, для которых существует ODBC-драйвер. Это распространяется на продукты Microsoft и многих других компаний.
Встроенные библиотеки. Поскольку РНР был разработан для использования в Web, он имеет множество строенных функций для выполнения широкого разнообразия полезных, связанных с Web, задач. С его помощью можно "на лету" генерировать GIF-изображения, подключаться к другим сетевым службам, отправлять сообщения электронной почты, работать с cookie-наборам и генерировать PDF-документы посредством нескольких строк кода.
Стоимость. Пакет РНР является бесплатным. Наиболее новую версию можно в любой момент совершенно бесплатно выгрузить из http://www.php.net
Изучение РНР. Синтаксис РНР основывается на других языках программирования, в первую очередь на С и Perl, Если вы уже знакомы с С, Perl или С-подобным языком, таким как C++ или Java, то почти сразу сможете эффективно использовать РНР.
Переносимость. Пакет РНР можно использовать под управлением многих различных операционных систем. Код РНР можно создавать в среде таких бесплатных Unix-подобных операционных систем, как Linux и FreeBSD, коммерческих версий Unix типа Solaris и IRIX или различных версий Microsoft Windows. Как правило, программы будут работать без каких-либо изменений в различных средах с установленным РНР.
Исходный код. Пользователь имеет доступ к исходному коду РНР. В отличие от коммерческих закрытых программных продуктов, если нужно что-либо изменить или добавить в этом языке, то это всегда можно сделать. Не следует дожидаться, пока фирма-изготовитель выпустит правки (патчи). Нет необходимости беспокоиться о том, что изготовитель собирается покинуть рынок или перестанет поддерживать продукт.
Синтаксис и грамматика. Синтаксис PHP заимствован непосредственно из C. Java и Perl также повлияли на синтаксис данного языка.
Переход из HTML. Есть три способа выхода из HTML и перехода в "режим PHP кода":
1. <? echo("простейший способ, инструкция обработки SGML\n"); ?>
2. <?php echo("при работе с XML документами делайте так\n"); ?>
3. <script language="php">
echo ("некоторые редакторы (подобные FrontPage) не любят обрабатывающие инструкции");
</script>;
4. <% echo("От PHP 3.0.4 можно факультативно применять ASP-тэги"); %>
Разделение инструкций. Инструкции (утверждения) разделяются также как в C или Perl - точкой с запятой. Закрывающий тэг (?>) тоже подразумевает конец утверждения, поэтому следующие записи эквивалентны:
<php
echo "Это тест";
?>
<php echo "Это тест" ?>
Типы переменных. PHP поддерживает переменные следующих типов:
integer - целое
double - число с дробной частью
string - строковая переменная
array - массив
object - объектная переменная
pdfdoc - PDF-документ (только при наличии поддержки PDF)
pdfinfo - PDF-инфо (только при наличии поддержки PDF)
Тип переменной обычно не устанавливается программистом; вместо этого, он определяется PHP во время выполнения программы, в зависимости от контекста, в котором она используется. Если вам нравится указывать тип переменной непосредственно, используйте для этого инструкцию cast либо функцию settype(), но учтите, что переменная может вести себя по-разному, в зависимости от того, какой тип определен для нее в данное время.
Инициализация переменной. Для инициализации переменной в PHP вы просто присваиваете ей значение. Для большинства переменных это именно так; для массивов и объектных переменных, однако, может использоваться несколько иной механизм.
Инициализация массивов. Массив может инициализироваться одним из двух способов: последовательным присвоением значений, или посредством конструкции array().
При последовательном добавлении значений в массив вы просто записываете значения элементов массива, используя пустой индекс. Каждое последующее значение будет добавляться в качестве последнего элемента массива.
$names[] = "Jill"; // $names[0] = "Jill"
$names[] = "Jack"; // $names[1] = "Jack"
Как в C и Perl, элементы массива нумеруются, начиная с 0, а не с 1.
Инициализация объектов. Для инициализации объектной переменной используйте новое предписание для сопоставления данного объекта объектной переменной.
class foo {
function do_foo () { echo "Doing foo."; }
}
$bar = new foo;
$bar -> do_foo ();
Область переменной. Областью переменной является контекст, внутри которого она определена. Обычно все переменные PHP имеют одну область. Однако, внутри функций определенных пользователем, представлена локальная область функции. Любая переменная, определенная внутри функции, по умолчанию ограничена локальной областью функции. Например:
$a = 1; /* глобальная область */
Function Test () {
echo $a; /* ссылка на переменную локальной области */
}
Test ();
Этот скрипт не выдаст что-либо на выходе, поскольку инструкция echo относится к локальной версии переменной $a, значение которой присваивается не внутри этой области. Вы можете заметить, что здесь имеется некоторое отличие от языка C, в том, что глобальные переменные в C автоматически действуют и внутри функций, если только они не переписываются локальными определениями. Это может вызвать некоторые проблемы, т.к. по неосторожности можно изменить глобальную переменную. В PHP глобальные переменные должны быть продекларированы глобально внутри функции, если предполагается их использование в данной функции. Например:
$a = 1;
$b = 2;
Function Sum () {
global $a, $b;
$b = $a + $b;
}
Sum ();
echo $b;
Этот скрипт выдаст значение "3". Поскольку $a и $b декларируются глобально внутри функции, ссылки на данные переменные трактуются как ссылки на их глобальные версии.
Вторым способом доступа к переменным из глобальной области является использование специального определяемого PHP массива $GLOBALS. При этом предыдущий пример может быть записан как:
$a = 1; $b = 2;
Function Sum () { $GLOBALS["b"] = $GLOBALS["a"] + $GLOBALS["b"]; }
Sum ();
echo $b;
Массив $GLOBALS является ассоциативным, в котором имя глобальной переменной является ключом, а значение этой переменной является значением элемента массива.
Другой важной характеристикой от области определения переменной является статическая переменная. Статическая переменная существует только в локальной области функции, но она не теряет своего значения, когда программа, при исполнении, покидает эту область. Рассмотрим следующий пример:
Function Test () {
$a = 0;
echo $a;
$a++;
}
Эта функция совершенно бесполезна практически, поскольку каждый раз при ее вызове она устанавливает $a в 0 и выводит "0". Выражение $a++ так же бесполезно, поскольку при выходе из функции переменная $a исчезает. Для придания дееспособности функции подсчета переменная $a декларируется как статическая:
Function Test () {
static $a = 0;
echo $a;
$a++;
}
Теперь при вызове функции Test() она будет выводить значение $a и увеличивать его.
Статические переменные также весьма существенны, когда функции вызываются рекурсивно. Рекурсивные функции - это те, которые вызывают сами себя. Составлять рекурсивную функцию нужно внимательно, т.к. при неправильном написании можно сделать рекурсию неопределенной. Вы должны быть уверены в адекватности способа прекращения рекурсии. Следующая простая функция рекурсивно считает до 10:
Function Test () {
static $count = 0;
$count++;
echo $count;
if ($count < 10) {
Test ();
}
$count--;
}
Изменяемые переменные. Иногда бывает удобно давать переменным изменяемые имена. Такие имена могут изменяться динамически. Обычная переменная устанавливается так:
$a = "hello";
Изменяемая переменная берет некое значение и обрабатывает его как имя переменной. В приведенном выше примере значение hello может быть использовано как имя переменной, посредством применения двух записанных подряд знаков доллара, т.е.:
$$a = "world";
С этой точки зрения, две переменных определены и сохранены в символьном дереве PHP: $a с содержимым "hello" и $hello с содержимым "world". Так, инструкция:
echo "$a ${$a}";
осуществляет то же самое, что и инструкция:
echo "$a $hello";
а именно, обе они выводят: hello world.
Чтобы использовать изменяемые переменные с массивами, решите проблему неоднозначности. Это означает, что если вы пишете $$a[1], то синтаксическому анализатору необходимо знать, имеете ли вы в виду использовать $a[1] как переменную, или вы предполагаете $$a как переменную, а [1] как индекс этой переменной. Синтаксис для разрешения неоднозначности такой: ${$a[1]} для первого случая и ${$a}[1] для второго.
Переменные вне PHP. HTML Формы (GET и POST)
Когда программой-обработчиком формы является PHP-скрипт, переменные этой формы автоматически доступны для данного скрипта PHP. Например, рассмотрим следующую форму:
<form action="foo.php" method="post">
Name: <input type="text" name="name"><br>
<input type="submit">
</form>
При активизации формы PHP создаст переменную $name, значением которой будет то содержимое, которое было введено в поле Name: данной формы.
PHP также воспринимает массивы в контексте переменных формы, но только одномерные. Вы можете, например, группировать взаимосвязанные переменные вместе или использовать это свойство для определения значений переменных при множественном выборе на входе:
<form action="array.php" method="post">
Name: <input type="text" name="personal[name]"><br>
Email: <input type="text" name="personal[email]"><br>
Beer: <br>
<select multiple name="beer[]">
<option value="warthog">Warthog
<option value="guinness">Guinness
</select>
<input type="submit">
</form>
Если PHP-атрибут track_vars включен, через установку конфигурации track_vars или директивой <?php_track_vars?>, тогда переменные, активизированные посредством методов POST или GET, будут также находиться в глобальных ассоциативных массивах $HTTP_POST_VARS и $HTTP_GET_VARS соответственно.
Имена переменных РИСУНКА АКТИВИЗАЦИИ. При активизации (запуске) формы можно использовать рисунок (изображение) вместо стандартной кнопки запуска:
<input type=image src="../files/60/image.gif" name="sub">
Когда пользователь нажимает кнопку мыши где-либо над таким рисунком, сопровождающая форма передается на сервер с двумя дополнительными переменными, sub_x и sub_y. Они содержат координаты места нажатия кнопки мыши пользователем внутри данного рисунка. Можно отметить, что практически, реальные имена переменных, передаваемые браузером, содержат точку вместо символа подчеркивания, но PHP конвертирует точку в элемент подчеркивания (underscore) автоматически.
Переменные окружения. PHP автоматически создает переменные окружения, как и обычные переменные.
echo $HOME; /* Показывает переменную HOME, если она установлена.*/
Хотя при поступлении информации механизмы GET, POST и Cookie также автоматически создают переменные PHP, иногда лучше явным образом прочитать переменную окружения, для того чтобы быть уверенным в получении ее правильной версии. Для этого может использоваться функция getenv(). Для установки значения переменной окружения пользуйтесь функцией putenv().
Изменение типа. PHP не требует явного определения типа при объявлении переменной, тип переменной определяется по контексту в котором она используется. То есть, если вы присваиваете строковое значение переменной var, var становится строкой. Если затем присвоить переменной var значение целого (числа), то она станет целым.
Примером автоматического преобразования типа в PHP может служить оператор сложения '+'. Если какой-либо из операндов является числом с дробной частью (тип double), то затем все операнды оцениваются как double и результат будет иметь тип double. Иначе, эти операнды будут интерпретированы как целые (integers) и результат будет так же иметь тип integer. Отметим, что при этом НЕ меняются типы операндов, меняется только их оценка.
$foo = "0"; // $foo является строкой (ASCII 48)
$foo++; // $foo является строкой "1" (ASCII 49)
$foo += 1; // $foo сейчас является целым (2)
$foo = $foo + 1.3; // $foo сейчас имеет тип double (3.3)
$foo = 5 + "10 Little Piggies"; // $foo является целым (15)
$foo = 5 + "10 Small Pigs"; // $foo является целым (15)
Если вы желаете изменить тип переменной, используйте settype().
Определение типов переменных. Поскольку PHP определяет типы переменных и преобразует их (в общем) по мере необходимости, не всегда очевидно какой тип данная переменная имеет в какой-то отдельный момент. Поэтому PHP включает несколько функций, которые позволяют определить текущий тип переменной. Это функции gettype(), is_long(), is_double(), is_string(), is_array(), и is_object().
Приведение типа. Приведение типа работает в PHP во многом так же как в C: название требуемого типа записывается в круглых скобках перед переменной, которая должна быть приведена к данному типу.
$foo = 10; // $foo is an integer
$bar = (double) $foo; // $bar is a double
Допускается следующее приведение типов:
(int), (integer) - приведение к целому
(real), (double), (float) - приведение к типу double
(string) - приведение к строке
(array) - приведение к массиву
(object) - приведение к объектной переменной
Заметим, что табуляция и пробелы допускаются внутри круглых скобок, поэтому следующее функционально эквивалентно:
$foo = (int) $bar;
$foo = ( int ) $bar;
Преобразование строк. Когда строковая переменная оценивается как числовая, результирующее значение и тип переменной определяются следующим образом.
Переменная string будет оценена как double, если она содержит любой из символов '.', 'e', или 'E'. Иначе она будет оценена как integer.
Данное значение задается начальной частью строковой переменной. Если строка начинается с допустимых цифровых данных, то это значение и будет использоваться. Иначе, будет значение 0 (ноль). Допустимые цифровые данные - это конструкция из факультативного символа, следующего за одной или несколькими цифрами (содержащими факультативно десятичную точку), обозначающего экспоненту. Экспонента может обозначаться символом 'e' или 'E', который может следовать за одной или несколькими цифрами.
$foo = 1 + "10.5"; // $foo тип double (11.5)
$foo = 1 + "-1.3e3"; // $foo тип double (-1299)
$foo = 1 + "bob-1.3e3"; // $foo тип integer (1)
$foo = 1 + "bob3"; // $foo тип integer (1)
$foo = 1 + "10 Small Pigs"; // $foo тип integer (11)
$foo = 1 + "10 Little Piggies"; // $foo тип integer (11);
Манипуляции с массивом. PHP поддерживает как скалярные, так и ассоциативные массивы. Фактически, между ними нет разницы. Вы можете создать массив, используя функции list() или array(), или можно явно задать значение каждого элемента массива.
$a[0] = "abc";
$a[1] = "def";
$b["foo"] = 13;
Можно также создать массив просто добавляя в него значения.
$a[] = "hello"; // $a[2] == "hello"
$a[] = "world"; // $a[3] == "world"
Массив может сортироваться функциями asort(), arsort(), ksort(), rsort(), sort(), uasort(), usort(), и uksort() в зависимости от типа желаемой сортировки. Подсчет количества элементов массива осуществляется функцией count(). Перемещаться по массиву позволяют функции next() и prev(). Другим типовым способом перемещения по массиву является использование функции each().
Элементы языка.
Любой скрипт PHP состоит из последовательности операторов. Оператор может быть присваиванием, вызовом функции, циклом, условным выражением или пустым выражением (ничего не делающим). Операторы обычно заканчиваются точкой с запятой. Также операторы могут быть объединены в группу заключением группы операторов в фигурные скобки. Группа операторов также является оператором.
Константа. PHP определяет несколько констант и предоставляет механизм для определения Ваших констант. Константы похожи на переменные, но они имеют иной синтаксис.
Предопределенные константы - это __FILE__ and __LINE__, которые соответствуют имени файла и номеру строки, которая выполняется в настоящий момент.
<?php
function report_error($file, $line, $message) {
echo "An error occured in $file on line $line: $message.";}
report_error(__FILE__,__LINE__, "Something went wrong!");
?>
Можно определить дополнительные константы с помощью функций define() и undefine().
<?php
define("CONSTANT", "Hello world.");
echo CONSTANT; // outputs "Hello world."
undefine ("CONSTANT");
?>
Выражения. Выражения - это краеугольный камень PHP. В PHP почти всё является выражениями. Простейший и наиболее точный способ определить выражение - это "что-то, имеющее значение". Простейший пример, приходящий на ум - это константы и переменные. Когда вы печатаете "$a = 5", вы присваиваете значение '5' переменной $a.
После этого присваивания вы считаете значением $a 5, также, если вы напишете $b = $a, вы будете ожидать того же как, если бы вы написали $b = 5. Другими словами, $a это также выражение со значением 5. Запись типа '$b = ($a = 5)' похожа на запись'$a = 5; $b = 5;' (точка с запятой отмечает конец выражения). Так как присваивания рассматриваются справа налево, вы также можете написать '$b = $a = 5'.
Более сложные примеры выражений - это функции:
function foo () {
return 5;
}
Функции - это выражения с тем значением, которое они возвращают. Так как foo() возвращает 5, значение выражение 'foo()' - 5.
PHP поддерживает 3 скалярных типа значений: целое, число с плавающей точкой и строки (скалярные выражения вы не можете "разбить" на более маленькие части, как, к примеру, массивы). PHP поддерживает 2 составных (нескалярных) типа: массивы и объекты. Каждое из таких значений может быть присвоено переменной или возвращено функцией.
PHP это язык, ориентированный на выражения, практически всё является выражениями.
Другой хороший пример выражения это префиксное и постфиксное увеличение и уменьшение. Пользователи С и многих других языков могут быть знакомы с записями variable++ and variable--. Это операторы увеличения и уменьшения. В PHP, подобно C, есть 2 типа инкремента - префиксный и постфиксный. Префиксное увеличение, которое записывается как '++$variable', приравнивается увеличенной переменной (PHP увеличивает переменную до того, как прочитать её значение).
Очень распространённый тип выражений - это выражения сравнения. Эти выражения имеют значение 0 или 1 (означает ложь или истину соответственно). PHP поддерживает > (больше, чем), >= ( больше или равно), = (равно), < (меньше, чем) и <= (меньше или равно). Эти выражения в основном используются внутри условий, например оператора IF.
Последний пример выражений, с которыми мы разберёмся, это совмещённые выражения оператор-присваивание. В PHP, также как и в ряде других языков типа C, вы можете писать '$a+=3'. Значение '$a+=3' как и значение обычного присваивания - это присвоенное значение. Любой бинарный оператор может быть записан таким методом, например : '$a=5' (вычесть 5 из значения $a), '$b*=7' (умножиить значение $a на 7) и так далее.
Есть ещё условный оператор с тремя операндами: $first ? $second : $third. Если значение первого выражения истинно (не равно 0), то исполняется второе выражение и это является результатом данного условного выражения. Иначе исполняется третий оператор.
Этот пример должен помочь Вам лучше понять выражения:
function double($i) /* функция удваивания переменной */
{ return $i*2; }
$b = $a = 5; /* присваиваем значения переменым $a и $b */
$c = $a++; /* постфиксное увеличение, присваиваем $c начальное значение $a (5)*/
$e = $d = ++$b; /* префиксное увеличение, $d и $e равны увеличенному значение $b */
$f = double($d++); /* $f = удвоенное значение $d до его увеличения*/
$g = double(++$e); /* $g = удвоенное значение $e после его увеличения */
$h = $g += 10; /* увеличить значение $g на 10, присвоить это значение переменной $h */
Не каждое выражения является оператором. Оператор имеет форму 'выражение' ';', то есть выражение, за которым следует точка с запятой. В '$b=$a=5;' $a=5 это правильное выражение, но оно не является оператором. А вот '$b=$a=5;' является оператором.
Ещё одна вещь, которую нужно упомянуть - это логические значения выражений. Во многих случаях, в основном в условных операторах и операторах циклов, вы не заинтересованы в конкретных значениях выражений, а только являются ли их значения TRUE или FALSE (в PHP нет специального типа boolean). Логические значения вычисляются примерно так же, как я в языке Perl. Любое не нулевое целое значение - это TRUE, ноль - это FALSE. Обратите внимание на то, что отрицательные значения - это не ноль и поэтому они считаются равными TRUE. Пустая строка и строка '0' это FALSE; все остальные строки - TRUE. И насчёт составных типов (массивы и объекты) - если значение такого типа не содержит элементов, то оно считается равным FALSE; иначе подразумевается TRUE.
Далее мы будем писать 'expr' для обозначения любого правильного выражения PHP.
IF. Возможности PHP по использованию выражения IF похожи на C:
if (expr) statement
Вычисляется логический результат "expr" . Если expr равно TRUE, то PHP выполнит "statement", а если FALSE - проигнорирует.
Следующий пример выведет фразу 'a is bigger than b' если $a больше $b:
if ($a > $b) print "a is bigger than b";
Зачастую Вам требуется исполнить больше чем одно выражение по условию. Конечно, не надо окружать каждое выражение конструкцией IF. Вместо этого вы можете сгруппировать несколько выражений в блок выражений. К примеру, следующий код не только выведет фразу, но и присвоит значение $a переменной $b:
if ($a > $b) { print "a is bigger than b"; $b = $a; }
Выражение IF может иметь неограниченную степень вложенности в другие выражения IF, что позволяет Вам использовать выполнение по условию различных частей программы.
ELSE. Зачастую Вам требуется исполнить одно выражение, если соблюдается какое-либо условие и другое выражение в противном случае. Вот для этого применяется ELSE. ELSE расширяет возможности IF по части обработки вариантов выражения, когда оно равно FALSE. Данный пример выведет фразу 'a is bigger than b' если $a больше $b, и 'a is NOT bigger than b' в противном случае:
if ($a > $b) { print "a is bigger than b"; }
else { print "a is NOT bigger than b"; }
Выражение ELSE выполняется только если выражение IF равно FALSE, а если есть конструкции ELSEIF - то если и они также равны FALSE (см. ниже).
ELSEIF. Является комбинацией IF и ELSE. ELSEIF, как и ELSE позволяет выполнить выражение, если значение IF равно FALSE, но в отличие от ELSE, оно выполнится только если выражение ELSEIF равно TRUE. К примеру, следующий код выведет 'a is bigger than b' если $a>$b, 'a is equal to b' если $a==$b, и 'a is smaller than b' если$a<$b:
if ($a > $b) { print "a is bigger than b";}
elseif ($a == $b) { print "a is equal to b";}
else { print "a is smaller than b"; }
Внутри одного выражения IF может быть несколько ELSEIF. Первое выражение ELSEIF (если таковые есть), которое будет равно TRUE, будет выполнено. В PHP вы можете написать 'else if' (два слова), что будет значить то же самое, что и 'elseif' (одно слово).
Выражение ELSEIF будет выполнено, только если выражение IF и все предыдущие ELSEIF равно FALSE, а данный ELSEIF равен TRUE.
Иной синтаксис для оператора IF: IF(): ... ENDIF; PHP предлагает иной путь для группирования операторов с оператором IF. Наиболее часто это используется, когда вы внедряете блоки HTML внутрь оператора IF, но вообще может использоваться где угодно. Вместо использования фигурных скобок за "IF(выражение)" должно следовать двоеточие, одно или несколько выражений и завершающий ENDIF. Рассмотрите следующий пример:
<?php if ($a==5): ?> A = 5 <?php endif; ?>
В этом примере блок "A = 5" внедрЈн внутрь выражения IF, используемого альтернативным способом. Блок HTML будет виден, только если $a равно 5.
Этот альтернативный синтаксис применим и к ELSE и ELSEIF (expr) .Вот пример подобной структуры :
if ($a == 5): print "a equals 5"; print "...";
elseif ($a == 6): print "a equals 6"; print "!!!";
else: print "a is neither 5 nor 6";
endif;
WHILE. Цикл WHILE - простейший тип цикла в PHP. Он действует, как и его аналог в C. Основная форма оператора WHILE:
WHILE(expr) statement
Он предписывает PHP выполнять вложенный(е) оператор(ы) до тех пор, пока expr равно TRUE. Значение выражения проверяется каждый раз при начале цикла, так что если значение выражения изменится внутри цикла, то он не прервётся до конца текущей итерации. Если значение expr равно FALSE с самого начала, цикл не выполняется ни разу.
Как и в IF, вы можете сгруппировать несколько операторов внутри фигурных скобок или использовать альтернативный синтаксис:
WHILE(expr): выражения ... ENDWHILE;
Следующие примеры идентичны - оба выводят номера с 1 по 10:
/* example 1 */
$i = 1;
while ($i <= 10) { print $i++; }
/* example 2 */
$i = 1;
while ($i <= 10): print $i; $i++;
endwhile;
DO..WHILE. Цикл DO..WHILE очень похож на WHILE за исключением того, что значение логического выражения проверяется не до, а после окончания итерации. DO..WHILE гарантировано выполнится хотя бы один раз, что в случае WHILE не обязательно.
Для циклов DO..WHILE существует только один вид синтаксиса:
$i = 0; do { print $i; } while ($i>0);
Этот цикл выполнится один раз, так как после окончания итерации будет проверено значение логического выражения, а оно равно FALSE ($i не больше 0).
Программисты на C знакомы с иным использованием DO..WHILE, позволяющим прекратить исполнение блока операторов в середине путём внедрения его в цикл DO..WHILE(0) и использования оператора BREAK. Следующий код демонстрирует такую возможность :
do {
if ($i < 5) {
print "i is not big enough";
break;
}
$i *= $factor;
if ($i < $minimum_limit) { break; }
print "i is ok";
} while(0);
FOR. Циклы FOR - наиболее мощные циклы в PHP. Они работают подобно их аналогам в C. Синтаксис цикла FOR :
FOR (expr1; expr2; expr3) statement
Первое выражение (expr1) безусловно вычисляется(выполняется) в начале цикла.
В начале каждой итерации вычисляется expr2. Если оно равно TRUE, то цикл продолжается и выполняются вложенный(е) оператор(ы). Если оно равно FALSE, то цикл заканчивается. В конце каждой итерации вычисляется(исполняется) expr3.
Каждое из этих выражений может быть пустым. Если expr2 пусто, то цикл продолжается бесконечно (PHP по умолчанию считает его равным TRUE, как и С). Это не так бесполезно, как могло бы показаться, так как зачастую вам требуется закончить выполнение цикла, используя оператор BREAK в сочетании с логическим условием вместо использования логического выражения в FOR.
Рассмотрим следующие примеры. Все они выводят номера с 1 по 10 :
/* пример 1 */
for ($i = 1; $i <= 10; $i++)
print $i;
}/* пример 2 */
for ($i = 1;;$i++) {
if ($i > 10) break;
print $i;
}/* пример 3 */
$i = 1;
for (;;) {
if ($i > 10) break;
print $i;
$i++;
}/* пример 4 */
for ($i = 1; $i <= 10; print $i, $i++) ;
PHP также поддерживает альтернативный синтаксис FOR :
FOR (expr1; expr2; expr3): выражение; ...; endfor;
Другие языки используют оператор foreach для того, чтобы обрабатывает массивы или списки. PHP использует для этого оператор while и функции list() и each() . Для примера смотрите документацию по этим функциям.
BREAK. Прерывает выполнение текущего цикла.
$i = 0;
while ($i < 10) {
if ($arr[$i] == "stop") { break; }
$i++;
}
CONTINUE. CONTINUE переходит на начало ближайшего цикла.
while (list($key,$value) = each($arr)) {
if ($key % 2) { // skip even members
continue; }
do_something_odd ($value);
}
SWITCH. Оператор SWITCH похож на группу операторов IF с одинаковым выражением. Во многих случаях вам нужно сравнить переменную (или выражение) со многими различными значениями и выполнить различные фрагменты кода в зависимости от того, чему будет равно значение выражения.
Следующие 2 примера - это 2 различных пути для достижения одной цели, но один использует серию операторов IF, а другой - оператор SWITCH.
/* пример 1 */
if ($i == 0) { print "i equals 0"; }
if ($i == 1) { print "i equals 1"; }
if ($i == 2) { print "i equals 2"; }
/* пример 2 */
switch ($i) {
case 0: print "i equals 0"; break;
case 1: print "i equals 1"; break;
case 2: print "i equals 2"; break;
}
SWITCH выполняет последовательно оператор за оператором. В начале код не исполняется. Только когда встречается оператор CASE с подходящим значением, PHP начинает выполнять программу. PHP продолжает выполнять операторы до конца блока SWITCH или пока не встретит оператор BREAK. Если вы не напишете BREAK в конце цикла операторов, то PHP продолжит выполнять операторы и следующего SWITCH'а. К примеру:
switch ($i) {
case 0: print "i equals 0";
case 1: print "i equals 1";
case 2: print "i equals 2";
}
В этом случае, если $i равно 0, то PHP выполнит все операторы print! Если $i равно 1, то PHP выполнит последние два print. И только если $i равно 2, вы получите ожидаемый результат и выведено будет только 'i equals 2'. Так что важно не забывать ставить BREAK. Специальный случай - это 'default case'. Этот оператор соответствует всем значениям, которые не удовлетворяют другим case'ам. К примеру :
switch ($i) {
case 0: print "i equals 0"; break;
case 1: print "i equals 1"; break;
case 2: print "i equals 2"; break;
default: print "i is not equal to 0, 1 or 2";
}
Выражения в CASE могут быть любого скалярного типа, то есть целые числа или числа с плавающей запятой, а так же строки. Массивы и объекты не будут ошибкой.
REQUIRE. Оператор REQUIRE заменяет себя содержимым указанного файла, похоже на то, как в препроцессоре C работает #include. Это означает, что вы не можете поместить require() внутрь цикла и ожидать, что он включит содержимое другого файла несколько раз в процессе каждой итерации. Для это используйте INCLUDE.
require ('header.inc');
INCLUDE. Оператор INCLUDE вставляет и выполняет содержимое указанного файла. Это случается каждый раз, когда встречается оператор INCLUDE, так что вы можете включить этот оператор внутрь цикла, чтобы включить несколько файлов :
$files = array ('first.inc', 'second.inc', 'third.inc');
for ($i = 0; $i < count($files); $i++) { include($files[$i]); }
include() отличается от require() тем, что оператор include выполняется каждый раз при его встрече, а require() заменяется на содержимое указанного файла безотносительно будет ли выполнено его содержимое или нет. Так как include() это специальный оператор, требуется заключать его в фигурные скобки при использовании внутри условного оператора.
/* Это неправильно и не будет работать, как хотелось бы. */
if ($condition) include($file);
else include($other);
/* А вот это - верно. */
if ($condition) { include($file);}
else { include($other); }
Когда файл исполняется, парсер пребывает в "режиме HTML", то есть будет выводить содержимое файла, пока не встретит первый стартовый тег PHP (<?).
FUNCTION. Функция может быть объявлена следующим образом:
function foo ($arg_1, $arg_2, ..., $arg_n) {
echo "Example function.\n";
return $retval; }
Внутри функции может быть любой верный код PHP, даже объявление другой функции или класса. Функции должны быть определены перед тем, как на них ссылаться.
Возвращение результатов. Результаты возвращаются через необязательный оператор return. Возвращаемый результат может быть любого типа, включая списки и объекты.
function my_sqrt ($num) {
return $num * $num; }
echo my_sqrt (4); // outputs '16'.
Множественные результаты не могут быть возвращены в качестве результата, но вы можете реализовать это путём возврата списка :
function foo() {
return array (0, 1, 2); }
list ($zero, $one, $two) = foo();
Аргументы. Информация может быть передана функции через список аргументов, которые являются разделенным запятыми списком переменных и/или констант.
PHP поддерживает передачу аргументов по значению (по умолчанию), по ссылке, и значения по умолчанию. Списки аргументов переменной длины не поддерживаются, но того же можно достичь, передавая массивы.
function takes_array($input) {
echo "$input[0] + $input[1] = ", $input[0]+$input[1];
}
Передача по ссылке. По умолчанию, аргументы функции передаются по значению. Если вы хотите в функции модифицировать аргументы, то можете передать их по ссылке.
Если вы хотите, чтобы аргумент всегда передавался по ссылке, то следует поставить амперсанд (&) перед именем аргумента в объявлении функции:
function foo( &$bar ) {
$bar .= ' and something extra.'; }
$str = 'This is a string, ';
foo ($str);
echo $str; // выведет :'This is a string, and something extra.'
Если вы хотите передать аргумент по ссылке в случае, когда по умолчанию такого не делается, то добавьте амперсанд перед именем аргумента в вызове функции :
function foo ($bar) {
$bar .= ' and something extra.';}
$str = 'This is a string, ';
foo ($str);
echo $str; // выведет 'This is a string, '
foo (&$str);
echo $str; // выведет 'This is a string, and something extra.'
Значения по умолчанию. Функции могут определять значения по умолчанию для скалярных аргументов в стиле C++ как показано :
function makecoffee ($type = "cappucino") {
echo "Making a cup of $type.\n";
}
echo makecoffee ();
echo makecoffee ("espresso");
Этот пример выведет следующее :
Making a cup of cappucino.
Making a cup of espresso.
Значение по умолчанию должно быть константой, а не переменной или, к примеру, членом класса. Учтите, что когда вы объявляете аргументы по умолчанию, они должны быть справа от всех "неумолчиваемых" аргументов, в противном случае это не будет работать, как задумано. К примеру :
function makeyogurt ($type = "acidophilus", $flavour) {
return "Making a bowl of $type $flavour.\n";
}
echo makeyogurt ("raspberry");
Этот пример выведет следующее :
Warning: Missing argument 2 in call to makeyogurt() in
/usr/local/etc/httpd/htdocs/php3test/functest.html on line 41
Making a bowl of raspberry .
А теперь сравните с этим :
function makeyogurt ($flavour, $type = "acidophilus") {
return "Making a bowl of $type $flavour.\n";
}
echo makeyogurt ("raspberry"); // а вот это работает
И выводит следующее :
Making a bowl of acidophilus raspberry.
Класс. Класс - это набор переменных и функций, работающих с этими переменными. Класс определяется следующим образом :
<?php
class Cart {
var $items; // Количество вещей в корзине покупателя
// Добавить $num наименований типа $artnr в корзину
function add_item ($artnr, $num) {
$this->items[$artnr] += $num; }
// Убрать $num наименований $artnr из корзины
function remove_item ($artnr, $num) {
if ($this->items[$artnr] > $num) {
$this->items[$artnr] -= $num;
return true; }
else { return false; }
}
}
?>
Это определения класса Cart, который состоит связного списка наименований товаров в корзине и двух функций для добавления и удаления вещей из корзины.
Классы это типы, то есть, заготовки для реальных переменных. Вы должны создавать переменные желаемого типа, используя оператор new :
$cart = new Cart;
$cart->add_item("10", 1);
Таким образом, мы создали объект $cart класса Cart. Функция add_item() этого объекта вызывается для добавления 1 товара номер 10 к корзине.
Классы могут быть расширениями других классов. Расширенный класс обладает всеми переменными и функциями базового класса и тем, что вы определите при расширении класса. Это делается, используя ключевое слово extends:
class Named_Cart extends Cart {
var $owner;
function set_owner ($name) {
$this->owner = $name; }
}
Это определяет класс Named_Cart, который имеет все переменные и функции класса Cart плюс дополнительную переменную $owner и дополнительную функцию set_owner(). Вы можете создать поименованную корзину обычным образом и установить или получить владельца корзины. Также вы можете использовать и нормальные функции корзины в поименованной корзине :
$ncart = new Named_Cart; // Создать корзину
$ncart->set_owner ("kris"); // Указать владельца
print $ncart->owner; // Распечатать имя владельца корзины
$ncart->add_item ("10", 1); // (унаследовано из обычной корзины)
Внутри функций класса переменная $this означает сам объект. Вы должны использовать $this->нечто для доступа к переменной или функции с именем 'нечто' внутри объекта.
Конструкторы это функции в классе, которые автоматически вызываются, когда вы создаёте новую переменную данного класса. Функция становится классом, когда она имеет такое же имя, как и сам класс.
class Auto_Cart extends Cart {
function Auto_Cart () {
$this->add_item ("10", 1); }
}
Мы определили класс Auto_Cart который является тем же классом Cart плюс имеет конструктор, который инициализирует корзину при создании, наполняя ее одним товаром типа "10". Конструкторы также могут иметь аргументы, и эти аргументы могут быть необязательными, что делает конструктор более полезным:
class Constructor_Cart {
function Constructor_Cart ($item = "10", $num = 1) {
$this->add_item ($item, $num);
}
}// Покупаем все одно и то же:
$default_cart = new Constructor_Cart;
// А тут что-то новое :
$different_cart = new Constructor_Cart ("20", 17);
Операторы
Арифметические операторы. Эти операторы работают подобным образом
Таблица 6.1. Арифметические операторы
пример |
название |
результат |
$a + $b |
Сложение |
Сумма $a и $b. |
$a - $b |
Вычитание |
Вычитает $b из $a. |
$a * $b |
Умножение |
Произведение $a и $b. |
$a / $b |
Деление |
Деление $a на $b. |
$a % $b |
Остаток деления |
Остаток от деления $a на $b. |
Оператор деления("/") возвращает целую величину(результат целочисленного деления) если оба оператора - целые (или строка преобразованная в целое). Если каждый операнд является величиной с плавающей запятой, выполнится деление с плавающей запятой.
Операторы строк. B действительности есть только один оператор - конкатенации (".").
$a = "Hello ";
$b = $a . "World!"; // теперь $b = "Hello World!"
Операторы присваивания. Основным оператором присваивания является "=". Это означает, что левый операнд получает значение выражения справа (собирательное присваивание). Значением выражения присваивания является присваиваемая величина. Так что если "$a = 3", то это 3.
$a = ($b = 4) + 5; // теперь $a равно 9, а $b стало равным 4.
В дополнение к основным операторам присваивания есть дополнительные "комбинационные операторы", для всех арифметических и строковых операторов, что позволяет Вам использовать значение в выражении и затем устанавливать свое значение в результате этого выражения. Например:
$a = 3; $a += 5; // теперь $a равно 8
$b = "Hello ";
$b .= "There!"; // теперь $b равно "Hello There
Бинарные Операторы (Побитовые Логические Операторы). Бинарные Операторы позволяют вам изменять биты в целых числах.
Table 6.2.Бинарные Операторы
пример |
название |
результат |
$a & $b |
И |
Будут установлены биты, которые были установлены и в $a и в $b. $a=5; /* 0101 */ $b=12; /* 1100 */ $c=$a & $b; /* $c будет равно 4 (0100) */ |
$a | $b |
Или |
Будут установлены биты, установленные в $a или $b . |
~ $a |
Не |
Будут установлены не_присутствующие в $a биты (реверс) |
Логические операторы.
Таблица 6.3. Логические операторы
пример |
название |
результат |
$a and $b |
И |
Истина, если истинны $a и $b. |
$a or $b |
Или |
Истина, если истинны $a или $b. |
$a xor $b |
Или |
Истина, если истинны $a или $b, но не оба. |
! $a |
Не |
Истина, если не истинно $a. |
$a && $b |
И |
Истина, если истинны и $a и $b. |
$a || $b |
Или |
Истина, если истинны $a или $b. |
Разница двух различных вариантов операторов "and" и "or" - в различии приоритетов.
Операторы Сравнения. Операторы Сравнения позволяют Вам сравнивать две величины.
Таблица 6.4. Операторы Сравнения
пример |
название |
результат |
$a == $b |
равно |
истина, если $a эквивалентно $b. |
$a != $b |
Не равно |
Истина, если $a не эквивалентно $b. |
$a < $b |
Меньше чем |
Истина если $a меньше чем $b. |
$a > $b |
Больше чем |
Истина если $a больше $b. |
$a <= $b |
Меньше или равно |
Истина, если $a меньше или равно $b. |
$a >= $b |
Больше или равно |
Истина, если $a больше или равно $b. |
Регулярные выражения. Регулярные выражения используются для сложного манипулирования строками в PHP. Функции, которые поддерживают регулярные выражения:
ereg()
ereg_replace()
eregi()
eregi_replace()
split()
Все эти функции принимают строку регулярного выражения как их первый параметр. Для полного описания регулярных выражений см. соответствующие разделы руководства (regex), в каталоге regex дистрибутива PHP.
Примеры регулярных выражений
ereg("abc",$string); /* Возвращает 'истина', если "abc" найдено в $string. */
ereg("^abc",$string);
/* Возвращает 'истина', если "abc" найдено в начале $string. */
ereg("abc$",$string);
/* Возвращает 'истина', если "abc" найдено в конце $string.*/
eregi("(ozilla.[23]|MSIE.3)",$HTTP_USER_AGENT);
/* Возвращает 'истина', если браузер клиента - Netscape 2, 3 или MSIE 3.*/
ereg("([[:alnum:]]+) ([[:alnum:]]+) ([[:alnum:]]+)",
$string,$regs);
/* Помещает три слова - $regs[1], $regs[2] и $regs[3], разделенные пробелом. */
ereg_replace("^","<BR>",$string);
/* Устанавливает тег <BR> в начало строки $string. */
ereg_replace("$","<BR>",$string);
/* Устанавливает тег <BR> в конец строки $string. */
ereg_replace("\n","",$string);
/* Отсекает символ "возврат каретки" в строке $string. */
Встроенные функции
Их очень много (более 1200!), и, чтобы не дублировать справочники, самые популярные рассматриваются в примерах.
Работа с файлами.
Существуют два основных способа хранения данных: в двумерных (обычных) файлах и в базах данных. Двумерный файл может иметь множество форматов, но в общем случае под двумерным (flat) файлом будем понимать простой текстовый файл. В рассматриваемом ниже примере заказы клиента записываются в текстовый файл, по одному заказу в каждой строке.
Этот способ столь же прост, сколь и ограничен, как будет показано далее в главе. Если приходится иметь дело с достаточно большим объемом информации, вероятно, лучше воспользоваться базами данных. Однако, двумерные файлы находят достаточно широкое применение, поэтому в ряде ситуаций необходимо владеть технологией их применения.
Расмотрим работу с файлами на примере сбора заказов. Форма заказа имеет вид:
Листинг 6.1. Form.htm - форма заказа.
<html><head><title> Auto Parts</title></head><body>
<h1> Auto Parts</h1><h2>Order Form</h2>
<form action="processorder.php" method=post><table border=0>
<tr><td width=150>Item</td><td width=15>Quantity</td></tr>
<tr><td>Tyres</td><td <input type="text" name="tyreqty" size=3 maxlength=3></td></tr>
<tr><td>Oil</td><td><input type="text" name="oilqty" size=3 maxlength=3></td></tr>
<tr><td colspan=2 align=center><input type=submit value="Submit Order"></td></tr>
</table></form></body></html>
Отображение переменных формы производит следующий сценарий:
Листинг 6.2. processorder.php - обработка формы.
<html><head><title> Auto Parts: Order Results</title></head><body>
<h1>Auto Parts</h1><h2>Order Results</h2>
<?
echo "<p>Order processed at "; // Start printing order
echo date("H:i, jS F");
echo "<br><p>Your order is as follows: <br>";
echo $tireqty." tires<br>";
echo $oilqty." bottles of oil<br>";
$totalqty = 0; $totalamount = 0.00;
define("TYREPRICE", 100); define("OILPRICE", 10);
$totalqty = $tyreqty + $oilqty;
$totalamount = $tyreqty * TYREPRICE + $oilqty * OILPRICE;
$totalamount = number_format($totalamount, 2);
echo "<br>\n Items ordered: ".$totalqty."<br>\n";
echo "Subtotal: $".$totalamount."<br>\n";
$taxrate = 0.10; // local sales tax is 10%
$totalamount = $totalamount * (1 + $taxrate);
$totalamount = number_format($totalamount, 2);
echo "Total including tax: $".$totalamount."<br>\n";
?></body></html>
Использование функции date(). Функция date() принимает два аргумента, один из которых является необязательным. Первый аргумент представляет собой строку формата, а второй, необязательный, метку времени UNIX. Если метка времени не указана, то функция date() обрабатывает текущую дату и время. Она возвращает отформатированную строку, содержащую дату. Типовой вызов функции выглядит так:
echo date("jS F Y");
Вывод этого выражения имеет вид "31th July 2001". Коды форматирования, используемые функцией date(), Перечислены в табл. 6.5.
Таблица 6.5 Коды форматирования РНР-функции date()
Код |
Описание |
а |
Утро или время после полудня, с двумя строчными символами, "am" или "pm," |
А |
Утро или время после полудня, с двумя прописными символами, "AM" или "PM". |
В |
Internet-время Swatch универсальная временная схема. Более подробно о ней можно узнать на сайте http://www.swatch.com. |
d |
День месяца в виде двузначного числа с ведущим нулем. Диапазон значений от "01" до "31". |
D |
День недели в виде трехбуквенной аббревиатуры. Диапазон значений от "Mon" (понедельник) до "Sun" (воскресенье). |
F |
Месяц в полнотекстовом формате. Диапазон значений от "January" до "December". |
g |
Часы в 12-часовом формате без ведущих нулей. Диапазон значений от "1" до "12". |
G |
Часы в 24-часовом формате без ведущих нулей. Диапазон значений от "0" до "23". |
h |
Часы в 12-часовом формате с ведущими нулями. Диапазон значений от "01" до "12" |
H |
Часы в 24-часовом формате с ведущими нулями. Диапазон значений от "00" до "23" |
i |
Минуты с ведущими нулями. Диапазон значений от "00" до "59". |
I |
Переход на летнее время, представленный значением логического типа. Если переход на летнее время установлен, функция возвращает значение "1", иначе "0". |
j |
День месяца в виде числа без ведущих нулей. Диапазон значений от "1" до "31". |
I |
День недели в полнотекстовом формате. Диапазон значений от "Monday" (понедельник) до "Sunday" (воскресенье). |
L |
Високосный год, представленный значением логического типа. Функция возвращает значение "1", если дата принадлежит високосному году, и "0" в противном случае. |
Таблица 6.5. Продолжение.
Код |
Описание |
L |
Високосный год, представленный значением логического типа. Функция возвращает значение "1", если дата принадлежит високосному году, и "0" в противном случае. |
m |
Месяц в двузначном числовом формате с ведущими нулями. Диапазон значений от "01" до "12". |
M |
Месяц в виде трехбуквенной аббревиатуры. Диапазон значений от "Jan" (январь) до "Dec" (декабрь). |
n |
Месяц в виде числа без ведущих нулей. Диапазон значений от "1" до "12". |
s |
Секунды с ведущими нулями. Диапазон значений от "00" до "59". |
S |
Порядковый суффикс для дат в двухбуквенном формате. Он может принимать значение "st", "nd", "rd" или "th" в зависимости от числа, за которым он следует. |
t |
Полное количество дней в месяце. Диапазон значений от "28" до "31". |
Т |
Временная зона сервера, заданная в трехбуквенном формате, например, "EST". |
U |
Число секунд с 1 января 1970 г. до текущего момента; его также называют меткой времени UNIX для текущей даты. |
w |
День недели в виде числа. Диапазон значений от "0" (воскресенье) до "6" (суббота). |
У |
Год в двузначном формате, например, "00". |
Y |
Год в четырехзначном формате, например, "2000". |
z |
День года в виде числа. Диапазон значений от "0" до "365". |
Z |
Смещение текущей временной зоны в секундах. Диапазон значений от "-43200" до "43200". |
Обзор обработки файлов. Запись данных в файл реализуется в три шага:
Аналогично, считывание данных из файла также связано с выполнением трех шагов:
При необходимости считывания данных из файла можно выбирать, какая часть файла "должна считываться за один раз. Чуть позже будут подробно рассматриваться все доступные возможности. Пока давайте исследуем шаг открытия файла.
Открытие файла. Для открытия файла в среде РНР используется функция fopen(). При открытии файла необходимо указать режим его использования.
Режимы файлов. Серверная операционная система должна знать, что нужно делать с открываемым файлом. Ей требуется знать, может ли файл быть открыт и обработан другим сценарием в то время, когда он является открытым, если владелец сценария имеет право на подобное его использование. По существу, режимы файла предоставляют операционной системе механизм для определения способа обработки запросов на доступ, поступающих от других пользователей или сценариев, а также метод проверки наличия доступа и прав для работы с конкретным файлом.
При открытии файла следует принять три решения:
Функция fopen() поддерживает любые имеющие смысл комбинации этих трех вариантов. Использование функции fopen() для открытия файла. Предположим, что требуется записать заказ клиента в файл. Этот файл можно открыть для записи так:
$fp = fopen("$DOCUMENT_ROOT/../orders/orders.txt", "w');
Функция fopen ожидает двух или трех входных параметров. Обычно используются два параметра, как показано в приведенной выше строке кода.
Первым параметром должен быть файл, который необходимо открыть. При этом можно указать путь к файлу, как было сделано в приведенной выше строке кода orders.txt находится в каталоге orders. Мы использовали встроенную переменную $DOCUMENT_ROOT PHP. Эта переменная указывает на основание дерева документов Web-сервера. Кроме того, мы использовали символ "..", означающий "родительский каталог каталога $DOCUMENT_ROOT. В целях повышения безопасности этот каталог находится вне дерева документов. Нежелательно, чтобы этот файл был доступен в Web помимо предоставляемого нами интерфейса. Этот путь называется относительным, поскольку он описывает позицию в файловой системе относительно $DOCUMENT_ROOT.
Можно было бы задать и абсолютный путь к файлу путь от корневого каталога (/ в системе UNIX и, как правило, С:\ в системе Windows). На сервере UNIX такой путь выглядит как /home/book/orders. Проблема, связанная с подобным указанием пути, особенно в случае размещения своего сайта на чужом сервере, заключается в том, что абсолютный путь может изменяться, когда системные администраторы без предупреждения "сочтут необходимым" изменить структуру каталогов. Если путь вообще не указан, файл будет создаваться или отыскиваться в том же каталоге, в котором находится собственно сценарий. Упомянутое поведение будет иным при запуске РНР через какую-то CGI-оболочку и зависит от конфигурации сервера.
В среде UNIX в качестве разделителя каталогов используется символ прямой (с уклоном вправо) косой черты (/). На платформах Windows можно применять символы прямой или обратной косой черты. При использовании символа обратной черты они должны быть помечены как специальные (т.е. отменены), чтобы функция fopen смогла их корректно интерпретировать. Для отмены перед символом следует просто поместить дополнительный символ обратной косой черты, как показано в следующей строке:
$fp = fopen("..\\..\\orders\\orders.txt", "w") ;
Второй параметр функции fopen() это режим файла, который должен иметь строковый тип. Этот параметр определяет, что необходимо делать с файлом. В данном случае в функцию fopen() передается параметр "w" это означает открытие файла для записи. Режимы файла перечислены в табл. 6.6.
Таблица 6.6. Режимы файла для функции fopen
Режим |
Значение |
г |
Режим чтения Открытие файла для чтения, начиная с начала файла. |
r+ |
Режим чтения Открытие файл для чтения и записи, начиная с начала файла. |
w |
Режим записи Открытие файла для записи, начиная с начала файла. Если файл уже существует, его содержимое удаляется. Если файл не существует, предпринимается попытка его открытия и в результате файл создается. |
w+ |
Режим записи Открытие файла для записи и чтения, начиная с начала файла. Если файл уже существует, его содержимое удаляется. Если файл не существует, предпринимается попытка его открытия и в результате файл создается. |
a |
Режим добавления Открытие файла только для добавления (записи), начиная с конца существующего содержимого, если оно имеется. Если файл не существует, предпринимается попытка его открытия и в результате файл создается. |
a+ |
Режим добавления Открытие файла для добавления (записи) и чтения, начиная с конца существующего содержимого, если оно имеется. Если файл не существует, предпринимается попытка его открытия и в результате файл создается. |
b |
Двоичный режим Используется в сочетании с одним из остальных режимов. Его указание требуется, если файловая система различает двоичные и текстовые файлы. Операционная система Windows различает эти файлы, а UNIX - нет. |
Режим файла, который необходимо использовать в рассматриваемом примере, зависит от того, как будет использоваться система. Параметр "w" позволяет сохранить в файл только один заказ. При приеме каждого нового заказа он будет перезаписывать ранее записанный заказ. Вероятно, это не очень разумно, поэтому лучше указать режим добавления:
$fp = fopen("../../orders/orders.txt", "a");
Третий параметр функции fopen() не является обязательным. Его можно использовать, если файл необходимо искать в пути include_path (определенном в конфигурации РНР). Если это требуется, установите параметр равным 1 и не задавайте имя каталога или путь:
$fp = fopen("orders.txt", "a", 1);
В случае успешного открытия файла функция fopen() возвращает указатель на файл и сохраняет его в переменой, в данном случае $fp. Эта переменная будет использоваться для доступа к файлу, когда потребуется выполнить считывание либо запись в него.
Открытие удаленных файлов через FTP или HTTP. Используя функцию fopen(), можно открывать для чтения или записи не только локальные файлы, но и удаленные с использованием протоколов FTP и HTTP. Если используемое имя файла начинается с ftp://, открывается FTP-соединение с указанным сервером в пассивном режиме и возвращается указатель на начало файла. Если используемое имя файла начинается с http://, открывается HTTP-соединение с указанным сервером и возвращается указатель на ответ от сервера. В случае применения режима HTTP обязательно следует указывать завершающие символы косой черты в именах каталогов, как показано в следующем примере:
http://www.server.com/
но не
http://www.server.com
При второй форме указания адреса (без завершающей косой черты) Web-сервер, как правило, будет использовать перенаправление HTTP с целью обращения по первому адресу (с косой чертой). Проверьте это в своем браузере.
Функция fopen() не поддерживает перенаправление HTTP, поэтому необходимо указывать URL-адреса (унифицированный локатор ресурсов), которые ссылаются на каталоги с завершающими символами косой черты. Помните, что имена доменов в URL-адресах не зависят от регистра, однако пути и имена файлов зависят.
Проблемы, возникающие при открытии файлов. Обычная ошибка, связанная с открытием файла попытка открыть файл, для которого отсутствуют права на чтение или запись. В этом случае РНР выводит соответствующее предупреждение.
В случае получения подобного сообщения об ошибке необходимо убедиться, что пользователь, выполняющий сценарий, обладает правами доступа к файлу, попытка использования которого предпринимается. В зависимости от того, как установлен данный сервер, сценарий может выполняться как пользователь Web-сервера или как владелец каталога, в котором размещается сценарий.
В большинстве систем сценарий будет выполняться как пользователь Web-сервера. Если бы сценарий находился в системе UNIX в каталоге ~/public_html/chapter2/, общедоступный для записи каталог для хранения заказов можно было бы создать, набрав следующие команды:
mkdir ~/orders chmod 777 -/orders
Имейте в виду, что каталоги, в которых любой пользователь может выполнить запись, представляют опасность. В системе не должно быть каталогов, которые доступны для записи непосредственно из Web. Именно поэтому наш каталог orders размещается на два подкаталога выше каталога public_html.
Некорректные настройки прав доступа вероятно, наиболее часто встречающаяся ошибка во время открытия файла. Если файл не может быть открыт, об этом действительно нужно знать, дабы не пытаться считывать или записывать в него данные.
Если обращение к функции fopen() оказывается безуспешным, функция возвращает значение false. Обработку ошибок можно сделать более удобной для пользователя, подавив сообщение об ошибке от РНР, и реализовав собственное сообщение:
@$fp = fopen("$DOCUMENT_ROOT/../orders/orders.txt", "a", 1);
if ('$fp) {
echo "<p><strong> Your order could not be processed at this time. ."Please try again later.</strong></p></body></html>";
exit;}
Символ @ перед обращением к функции fopen() указывает РНР на необходимость подавления любых сообщений об ошибках, генерируемых после вызова функции. Обычно важно только знать, когда что-то выполняется неправильно, но в данной ситуации в любом случае следует разобраться с подобного рода проблемой. Обратите внимание, что символ @ должен располагаться в самом начале строки.
Оператор if проверяет переменную $fp с целью выяснения, возвращался ли из функции fopen допустимый указатель файла, и если нет, выводится сообщение об ошибке и выполнение сценария завершается. Поскольку здесь завершается и страница, обратите внимание на генерацию закрывающего дескриптора </html>, что обеспечивает допустимость HTML-кода.
Запись в файл. Запись в файл в РНР выполняется сравнительно просто. Для этого можно воспользоваться любой из функций fwrite() (file write запись в файл) или fputs() (file put string запись строки в файл); fputs() это псевдоним функции fwrite(). Функцию fwrite() можно вызвать следующим образом:
fwrite($fp, $outputstring);
Это указывает РНР на необходимость записи строки из переменной $outputstring в файл, указанный $fp. Рассмотрим функцию fwrite() более подробно, прежде чем приступать к исследованию содержимого переменной $outputstring.
Параметры функции fwrite(). В действительности функция fwrite() принимает три параметра, однако третий из них не является обязательным. Прототип fwrite() имеет вид
int fputs(int fp, string str, int [length]);
Третий параметр length представляет собой максимальное количество байтов, которые требуется записать. При передаче этого параметра функция fwrite() будет записывать строку str в файл, указанный параметром fp, пока не встретит конец строки или не запишет length байтов, в зависимости от того, что произойдет раньше.
Форматы файлов. При создании файла данных, подобного используемому в рассматриваемом примере, формат хранения данных полностью зависит от программиста. (Однако, если планируется использование файла данных в другом приложении, возможно, придется учесть особенности интерпретации данных этого приложения.)
Давайте создадим строку, которая представляет одну запись в файле данных. Это можно сделать следующим образом:
$outputstring = $date."\t".$tireqty."tires \t".$oilqty." oil\t"
.$total ."\t\n";
В этом простом примере каждая запись заказа сохраняется в отдельной строке файла, Подобное решение обусловлено тем, что позволяет в качестве простого разделителя строк использовать символ новой строки. Поскольку символы новой строки невидимы, они представляются с помощью управляющей последовательности "\n". Поля данных будут записываться в одном и том же порядке, а в качестве разделителя полей будет использоваться символ табуляции. Опять-таки, поскольку этот символ невидим, он представляется управляющей последовательностью "\t". Разделителем должен быть любой символ, который наверняка не будет встречаться в исходных данных, иначе придется подвергнуть исходные данные дополнительной обработке с целью удаления или отмены всех вхождений ограничителя. Пока предположим, что никто не будет выводить символы табуляции в форму заказа. Помещение пользователем символов табуляции или новой строки в однострочное поле ввода HTML маловероятно, но не так уж невозможно. Использование специального разделителя полей упрощает разделение данных на отдельные переменные во время считывания. Пока каждый заказ будет обрабатываться как отдельная строка.
После обработки нескольких заказов содержимое файла будет выглядеть, как в листинге 6.3.
Листинг 6.3. orders.txt - пример содержимого файла заказов
15:42, 20th April 4 tires 1 oil $434.00
15:43, 20th April 1 tires 0 oil $100.00
15:43, 20th April 0 tires 1 oil $26
Закрытие файла. По завершении использования файла его следует закрыть при помощи функции fclose(), как показано ниже:
fclose($fp);
Эта функция возвращает значение true в случае успешного закрытия файла и false, если файл не был закрыт. Ошибка при этом значительно менее вероятна, чем при открытии файла, поэтому в данном случае проверка выполнения функции не выполняется.
Считывание из файла. Уже сейчас клиенты могут отправлять свои заказы через Web, однако если сотрудники компании захотят взглянуть на заказы, им придется открывать файлы самостоятельно. Давайте создадим Web-интерфейс, который позволит служащим компании легко читать файлы. Код этого интерфейса приведен в листинге 6.4.
Листинг 6.4. vieworders.php интерфейс для просмотра файла заказов
<html><head>
<title> Auto Parts - Customer Orders</title></head><body>
<hl> Auto Parts</hl> <h2>Customer Orders</h2>
<?
@$fp = fopen("$DOCUMENTROOT/../orders/orders.txt" , "r");
if (!$fp) {
echo "<p><strong>No orders pending."
."Please try again later.</strong></p></body></html>";
exit; }
while (!feof($fp)) {
$order= fgets($fp, 100);
echo $order."<br>"; }
fclose($fp); ?>
</body> </html>
В этом сценарии выполняется ранее описанная последовательность действий: открытие файла, считывание из файла, закрытие файла. Давайте подробнее рассмотрим функции, используемые в этом сценарии.
Открытие файла для чтения: fopen(). Как и ранее, мы открываем файл с помощью функции fopen(). На этот раз файл открывается только для чтения, поэтому используется режим файла "r":
$fp = fopen("$DOCUMENT_ROOT/../orders/orders.txt", "r");
Определение конца файла: feof(). В этом примере используется цикл while для считывания из файла до тех пор, пока не будет достигнут конец файла. Проверка на наличие конца файла осуществляется при помощи функции feof():
while (!feof($fp))
Функция feof() принимает в единственном параметре указатель файла. Она будет возвращать значение true, если указатель файла находится в конце файла. Имя функции легко запомнить, если знать, что feof означает File End Of File (Файл: конец файла).
В данном случае (и вообще при считывании) считывание из файла выполняется до тех пор, пока не будет достигнут EOF.
Построчное считывание: fgets(), fgetss() и fgetcsv(). В рассматриваемом примере для считывания из файла используется функция fgets(): $order= fgets($fp, 100);
Эта функция используется для считывания из файла по одной строке за один раз. В данном случае считывание будет выполняться до тех пор, пока не встретится символ новой строки (\n), EOF или из файла не будут прочитаны 99 байт. Максимальная длина считываемой строки равна указанной длине минус один байт.
Интересным вариантом функции fgets() является fgetss(), имеющая следующий прототип:
string fgetss (int fp, int length, string [allowable_tags] ) ;
Эта функция во многом подобна функции fgets() за исключением того, что она будет избавляться от любых дескрипторов РНР и HTML, найденных в строке. Если в файле необходимо оставить конкретные дескрипторы, они должны быть включены в строку allowable_tags. Функцию fgetss() следует использовать для обеспечения безопасности при считывании файла, записанного кем-либо другим или содержащего данные, введенные пользователем. Отсутствие ограничений на наличие в файле HTML-кода может привести к нарушению тщательно спланированного форматирования. Отсутствие ограничений на наличие в файле РНР-кода может предоставить злонамеренному пользователю почти полную свободу действий на сервере.
Функция fgetcsv() еще одна вариация функции fgets(). Она имеет следующий прототип:
array fgetcsv(int fp, int length, string [delimiter]);
Эта функция используется для разделения строк файлов при использовании в качестве разделительного символа табуляции, как предлагалось ранее, или запятой, которая обычно применяется в электронных таблицах и других приложениях. Если требуется восстановить переменные заказа отдельно одна от другой, а не в виде строки текста, следует прибегнуть к функции fgetcsv(). Она вызывается подобно функции fgets(), но ей необходимо передать разделитель, используемый для разделения полей. Например,
$order = fgetcsv($fp, 100, "\t");
получает строку из файла и разбивает ее при каждом обнаружении символа табуляции (\t). Результирующие данные помещаются в массив (в этом примере в $order).
Параметр length должен быть больше длины самой длинной строки считываемого файла, выраженной в символах.
Считывание всего файла: readfile(), fpassthru(), file(). Вместо считывания по одной строке из файла за один проход можно считывать весь файл. Существуют три различных способа.
Первый заключается в использовании функции readfile(). Весь ранее созданный сценарий можно заменить одной строкой:
readfile("$DOCUMENT_ROOT/../orders/orders.txt");
Функция readfilc() открывает файл, повторяет его содержимое в стандартном выводе (окне браузера), а затем закрывает файл. Прототип этой функции имеет вид
int readfile (string имя_файла, int [use_include_path]) ;
Необязательный второй параметр указывает, должен ли РНР искать файл в пути use_include_path, и действует так же, как в функции fopen(). Функция возвращает общее количество байтов, считанных из файла.
Во-вторых, можно использовать функцию fpassthru(). Вначале необходимо открыть файл с помощью функции fopen(). Затем указатель файла можно передать в функцию fpassthru(), которая загрузит содержимое файла, начиная с позиции, заданной указателем, в стандартный вывод. По завершении этого процесса функция закрывает файл.
Ранее приведенный сценарий можно заменить функцией fpassthru() следующим образом:
$fp = fopen("$DOCUMENT_ROOT/../orders/orders.txt", "r");
fpassthru($fp);
Функция fpassthru() возвращает значение true, если считывание было выполнено Успешно, и false в противном случае.
Третья возможность считывания всего файла использование функции file(). Эта Функция идентична функции readfile() за исключением того, что вместо повторения файла в стандартном выводе она преобразует его в массив. Ее вызов выглядит так:
$filearray = file($fp);
Эта строка приведет к считыванию всего файла в массив, названный $filearray. каждая строка файла сохраняется в отдельном элементе массива.
Считывание символа: fgetc(). Еще одна возможность обработки файлов считывание из файла по одному символу. Это выполняется с помощью функции fgetc(). В качестве своего единственного параметра она принимает указатель файла и возвращает следующий символ файла Цикл while в первоначальном сценарии можно заменить циклом, в котором используется функция fgetc():
while (!feof($fp))
{
$char = fgetc($fp); if (!feof($fp))
echo ($char=="\n" ? "<br>": $char);
}
Используя функцию fgetc(), этот код считывает из файла по одному символу за раз и сохраняет его в переменной $char, пока не будет достигнут конец файла. Затем выполняется небольшая дополнительная обработка с целью замещения текстовых символов конца строки \n HTML-разделителями строк <bг>. Это делается лишь для приведения в порядок форматирования. Поскольку без этого кода браузеры не распознают новые строки, весь файл был бы выведен в виде единой строки. (Попытайтесь сделать это и посмотрите, что получится.) Для выполнения этой задачи используется тернарная операция.
Побочный эффект использования функции fgetc() вместо функции fgets() заключается в том, что она будет возвращать символ EOF, в то время как fgets() не делает этого. После считывания символа приходится снова выполнять проверку с помощью функции feof(), поскольку символ EOF не должен отображаться в окне браузера.
В общем случае считывание файла символ за символом не находит особого применения, если только по какой-либо причине не требуется посимвольная обработка файла.
Считывание строк произвольной длины: fread(). Последний способ считывания из файла, который мы рассмотрим использование функции fread() для считывания из файла произвольного количества байтов. Эта функция имеет следующий прототип:
string fread(int fp, int length);
Функция считывает length байтов или все байты до конца файла, в зависимости от того, что произойдет раньше.
Другие полезные файловые функции. Существует ряд других файловых функций, которые временами могут оказаться полезными.
Проверка существования файла: file_exists(). Если необходимо проверить файл на предмет существования без его открытия, можно воспользоваться функцией file_exists(), как показано в следующем примере:
if (file_exists("$DOCUMENT_ROOT/../orders/orders.txt"))
echo "There are orders waiting to be processed."; else
echo "There are currently no orders.";
Выяснение размера файла: filesize(). Размер файла можно проверить с помощью функции filesize(). Она возвращает размер файла, выраженный в байтах:
echo filesize("$DOCUMENT_ROOT/../orders/orders.txt");
Эта функция может применяться в сочетании с функцией fread() для одновременного считывания всего файла (или определенной его части). Весь первоначальный сценарий можно заменить следующим кодом:
$fp = fopen("$DOCUMENT_ROOT/../orders/orders.txt", "r");
echo fread( $fp, filesize("$DOCUMENT_ROOT/../orders/orders.txt"));
fclose( $fp ) ;
Удаление файла unlink(). Если файл заказов необходимо удалить, это выполняется с помощью функции unlink(). (Нет ни одной функции с именем delete.) Например:
unlink("$DOCUMENT_ROOT/../orders/orders.txt");
Эта функция возвращает значение false, если файл не может быть удален. Это будет происходить при недостаточном уровне прав доступа к файлу или если файл не существует.
Перемещение внутри файла: rewind(), fseek и ftell(). Выяснять позицию указателя файла внутри файла и изменять ее можно с помощью функций rewind(), fseek() и ftell().
Функция rewind() переустанавливает указатель файла на начало файла. Функция ftell() сообщает в байтах позицию указателя относительно начала файла. В нижнюю часть первоначального сценария (перед командой fclose()) можно добавить следующие строки:
echo "Final position of the file pointer is ".(ftell($fp));
rewind($fp);
echo ""<br>After rewind, the position is ". (ftell ($fp) );
echo "<br>";
Функция fseek() может использоваться для установки указателя файла в некоторую точку внутри файла. Ее прототип имеет вид
int fseek(int fp, int offset);
В результате вызова функции fseek() указатель файла fp устанавливается в точку файла, имеющую смещение offset байтов относительно начала файла. Вызов функции rewind() эквивалентен вызову функции fseek() со смещением, равным нолю. Например, функцию fseek() можно использовать для нахождения средней записи в файле или для выполнения бинарного поиска. Часто, когда подобные задачи требуется решать применительно к достаточно сложному файлу данных, имеет смысл использовать базу данных.
Блокирование файлов. Представьте себе ситуацию, когда два клиента одновременно пытаются заказать товар. (Эта ситуация возникает не столь уж редко, особенно когда Web-сайт начинает обрабатывать значительные информационные потоки.) Что произойдет, если один клиент вызовет функцию fopen() и начнет запись, а затем второй клиент также вызовет функцию fopen() и тоже попытается выполнить запись? Ответ на этот вопрос зависит от используемой операционной системы, но часто точно ответить на них невозможно.
Во избежание подобных проблем используется блокирование файлов. В РНР блокирование реализуется с помощью функции flock(). Эта функция должна вызываться после открытия файла, но перед считыванием данных из файла или их записью в файл.
Прототип функции flock() выглядит так:
bool flock(int fp, int operation);
В функцию необходимо передать указатель на открытый файл и число, представляющее вид требуемой блокировки. Функция возвращает значение true, если блокировка была успешно выполнена, и false в противном случае.
Возможные значения параметра operation перечислены в табл. 6.7.
Таблица 6.7. Значения параметра operation функции flock()
Значение параметра operation |
Описание |
\ |
Блокировка чтения. Это означает, что файл может использоваться совместно с другими читающими приложениями. |
2 |
Блокировка записи. Это монопольный режим. Файл не доступен для совместного использования. |
3 |
Снятие существующей блокировки. |
+4 |
Добавление 4 к текущему значению параметра operation предотвращает другие попытки блокирования во время выполнения текущего блокирования. |
Если решено использовать функцию flock(), ее следует включить во все сценарии, в которых используется данный файл; в противном случае ее применение лишено смысла.
Для использования блокирования в рассматриваемом примере программу processorder.php необходимо изменить следующим образом:
$fp = fopen("$DOCUMENT_ROOT/../orders/orders.txt", "a", 1) ;
flock($fp, 2); // блокирование файла для записи
fwrite($fp, $outputstring);
flock($fp, 3); // снятие блокировки записи
fclose($fp) ;//Следует добавить блокировки в файл vieworders.php:
$fp = fopen("$DOCUMENT_ROOT/../orders/orders.txt" , "r");
flock($fp, 1); // блокирование файла для чтения
flock($fp, 3); // снятие блокировки записи
fclose($fp);
Теперь код более надежен, по все еще не идеален. Что произойдет, если два сценария попытаются одновременно выполнить блокирование? Это привело бы к конфликту, когда процессы соперничают за установку блокировки, но не известно, какому из них это удастся, что, в свою очередь, могло бы породить новые проблемы. Задачу можно решить значительно успешнее, используя СУБД.
Более рациональный способ обработки: системы управления базами данных. До сих пор во всех рассмотренных примерах использовались двумерные файлы. Далее будет рассматриваться применение MySQL системы управления реляционными базами данных. Для чего это нужно?
Проблемы, связанные с использованием двумерных файлов. При работе с двумерными файлами возникает ряд проблем:
Как эти проблемы решаются с помощью СУРБД. Системы управления реляционными базами данных (СУРБД) решают все эти проблемы:
СУРБД могут обеспечить более быстрый доступ к данным, чем двумерные файлы. А MySQL, система управления базами данных, обладает одними из самых высоких показателей производительности среди всех СУРБД.
Вероятно, главная побудительная причина использования СУРБД заключается в том, что все (или, по меньшей мере, большинство) функциональные возможности, требуемые от системы хранения данных, в ней уже реализованы. Конечно, можно было бы создать собственную библиотеку РНР-функций, но зачем же заново изобретать колесо?
Доступ к базам данных.
Подготовка базы. Перед организацией системы доступа к базам данных, необходимо иметь эти базы. Далее рассматривается создание учебной базы данных, используемой в примерах.
Запустив монитор mysql и зарегистрировавшись в системе, создадим базу данных books:
mysql> create database books;
После входа в систему сначала потребуется определить базу данных, с которой необходимо работать. Это можно сделать, набрав:
mysql> use dbname;
где dbname имя соответствующей базы данных.
Можно и не набирать команду use, но тогда следует указать базу данных при входе систему:
mysql dbname -h hostname -u username -p
В этом примере воспользуемся базой данных books:
mysql> use books;
Для соединения РНР-сценариев с MySQL потребуется настроить пользователя. В этом случае нужно применить принцип наименьших привилегий: зачем права сценариям?
В большинстве случаев сценариям понадобится проводить над строками таблиц только такие операции: SELECT, INSERT, DELETE и UPDATE. Можно поступить следующим образом:
mysql> grant select, insert, delete, update
-> on books.*
-> to bookorama identified by 'bookoramal23' ;
He забывайте о безопасности! Такой пароль, конечно, никуда не годится.
Следующий этап настройки базы данных создание таблиц. Это делается при помощи SQL-команды CREATE TABLE.
Листинг 7.5 содержит SQL-код для создания этих таблиц, при этом подразумевается, что база данных books уже создана.
SQL-код создания таблиц запускается следующим образом:
> mysql -h host -u bookorama books -p < books.sql
В данном случае очень удобно использовать переназначение файлов, поскольку предполагается, что до выполнения SQL-код редактируется в любом текстовом редакторе.
Листинг 6.5. books.sql - SQL-код создания таблицы для базы данных books
create table books
( isbn char(13) not null primary key,
author char(30),
title char(60),
price float(4,2)
);
Прежде чем приняться работать с базой данных, в ней необходимо сохранить какие-нибудь данные. Наиболее приемлемый способ предполагает использование оператора SQL INSERT.
Листинг 6.6. book_insert.sql SQL-код для заполнения данными таблицу Books
use books;
insert into books values
("0-672-31697-8", "Michael Morgan", "Java 2 for Professional Developers", 34.99) ,
("0-672-31745-1", "Thomas Down", "Installing Debian GNU/Linux", 24.99),
("0-672-31509-2", "Pruitt, et al.", "Sams Teach Yourself GIMP in 24 Hours", 24.99),
("0-672-31769-9", "Thomas Schenk", "Caldera OpenLinux System Administration Unleashed", 49.99);
Как работает архитектура Web-баз данных. Распишем ее по шагам:
У нас имеется база данных MySQL, поэтому можем подготовить код РНР, чтобы выполнялись предыдущие шаги. Начнем с поисковой формы. Это простая HTML-форма. Код для этой формы показан в листинге 6.7.
Листинг 6.7. search.html поисковая страница для базы данных Book
<html><head><title>Book Catalog Search</title> </head><body>
<hl>Book Catalog Search</hl>
<form action="results.php" method="post"> Choose Search Type:<br>
<select name="searchtype">
<option value="author">Author
<option value="title">Title
<option value="isbn">ISBN </select> <br>
Enter Search Term:<br> <input name="searchterm" type=text> <br>
<input type=submit value="Search"> </form>
</body> </html>
После того как пользователь нажмет на кнопке Search, вызывается сценарий results.php. Все это представлено в листинге 6.8. Далее мы рассмотрим, что делает упомянутый сценарий и как он работает.
Листинг 6.8. results.php - извлечение результатов запроса и форматирование их
<html><head><title>Book Search Results</title></head> <body>
<hl>Book Search Results</hl> <?
trim($searchterm);
if (!$searchtype || !$searchterm) {
echo "You have not entered search details. Please go back and try again.";
exit; }
$searchtype = addslashes($searchtype);
$searchterm = addslashes($searchterm);
@$db = mysql_pconnect("localhost", "bookorama", "bookorama");
if (!$db) {
echo "Error: Could not connect to database. Please try again later.";
exit; }
mysql_select_db("books");
$query = "select * from books where ".$searchtype." Like '%".$searchterm."%'";
$result = mysql_query($query);
$num_results = mysql_num_rows($result);
echo "<p>Number of books found: ".$num_results."</p>";
for ($i = 0; $i <$num_results; $i ++) {
$row = mysql_fetch_array($result);
echo "<p><strong>".($i+l).". Title:";
echo htmlspecialchars( stripslashes($row["titie"]));
echo "</strong><br>Author: ";
echo htmlspecialchars (stripslashes($row["author"]));
echo "<br>ISBN:
echo htmlspecialchars (stripslashes($row["isbn"]));
echo "<br>Price: " ;
echo htmlspecialchars (stripslashes($row["price"] ));
echo "</p>"; }
?>
</body> </html>
Основные шаги выполнения запросов к базе данных через Web. В любом сценарии, который обеспечивает доступ к базе данных из Web, имеется несколько базовых шагов:
То же самое делает и сценарий results.php, и сейчас мы исследуем каждый из этих этапов.
Проверка и фильтрация данных, исходящих от пользователя. Сначала необходимо убрать все лишние пробелы по краям слова, которые мог случайно набрать пользователь. Справиться с этим поможет функция trim(), применяемая к $searchterm (критерий поиска).
trim($searchterm);
Следующий этап убедиться, что пользователь указал критерий и тип поиска. Заметьте, это проверяется лишь тогда, когда критерий поиска не содержит лишние пробелы. Если поменять эти этапы местами, может возникнуть ситуация, когда критерий вроде и введен, сообщения об ошибке быть не должно, однако критерий содержит только пробелы, которые полностью удаляются функцией trim():
if (!$searchtype || !$searchterm) {
echo "You have not entered search details. Please go back and try again.";
exit; }
В этом случае выдается сообщение о том, что критерий поиска не введен.
Мы проверили переменную $searchtype даже в том случае, когда она поступает из оператора SELECT. Вас может заинтересовать, зачем проверять входные данные. Не забывайте, что база данных может иметь не один интерфейс. Например, Amazon располагает большим количеством филиалов, которые используют свои поисковые интерфейсы. Вследствие того, что пользователи могут заходить с разных рабочих станций, возникает и потенциальная угроза безопасности.
В случае задействования данных, которые вводят другие пользователи, необходимо тщательно фильтровать вводимые данные от управляющих символов. Если записывать данные, введенные пользователем, в базу данных типа MySQL, следует вызывать addslashes(), а при возврате пользователю выходных данных stripslashes().
В нашем случае к критерию поиска применяется функция addslashes():
$searchterm = addslashes ($searchterm);
К данным, исходящим из базы, применяется stripslashes(). Введенные данные не содержат косых линий, равно как и управляющих символов. То есть вызов stripslashes() не поможет. При построении Web-интерфейса для базы данных велики шансы того, что потребуется вносить данные о новых книгах, а детали, внесенные пользователем, могут содержать специальные символы. Сохраняя их в базе данных, мы обращаемся к addslashes(), а это означает, что при извлечении данных необходимо будет вызывать stripslashes().
Функцию htmlspecialchars() применяют для кодировки символов, которые в HTML имеют особое значение. В наших тестовых данных нет амперсандов (&), знаков "меньше" (<), "больше" (>), двойных кавычек ("), однако в названиях многих замечательных книг может повстречаться амперсанд. Использование этой функции страхует от грядущих ошибок.
Установка соединения. Для подключения к серверу MySQL в сценарии есть такая строка:
@ $db = mysql_pconnect("localhost", "bookorama", "bookorama");
Для подключения к базе данных используется функция mysql_pconnect() с прототипом:
int mysql_pconnect ([string host [:port] [:/socketpath]],
[string user], [string password]);
Потребуется указать имя узла (host), на котором размещен сервер MySQL, имя пользователя (user), чтобы войти в него, и пароль (password). Все это необязательно и если не указать все вышеперечисленное, функция воспользуется значениями по умолчанию локальная машина вместо узла, имя пользователя, под которым запущен РНР, и пустой пароль.
При успехе функция вернет идентификатор связи с базой данных (который следует сохранить для дальнейшего использования), а при неудаче значение false. Результат не стоит игнорировать, поскольку без соединения с базой данных работа невозможна. Это делает следующий код:
if (!$db) {
echo "Error: Could not connect to database. Please try again later."; exit; }
Как альтернативу, можно использовать другую функцию, которая делает практически то же самое mysql_connect(). Единственное отличие состоит в том, что mysql_connect() устанавливает постоянное соединение с базой данных.
Соединение с базой данных закрывается, когда сценарий завершает свое выполнение или когда обращается к функции mysql_close(). Постоянное соединение остается открытым и после того, как сценарий выполнен, а функцией mysql_close() его закрыть нельзя.
Может возникнуть вопрос, для чего это нужно. Ответ таков: соединение с базой данных предполагает некоторые непроизводительные затраты, что требует времени. Когда вызывается mysql_pconnect(), прежде чем она попытается подключиться к базе данных, она автоматически проверит, нет ли уже открытого постоянного соединения. Если есть, она не станет открывать новое. Это и время экономит, и предотвращает перегрузку сервера.
Однако если РНР выполняется как CGI, то постоянное соединение окажется не таким уж и постоянным. (Каждый вызов сценария РНР запускает новую копию механизма РНР и закрывает ее, когда сценарий завершает свою работу. Это, в свою очередь, также закрывает любое постоянное соединение.)
Количество соединений в MySQL, которые существуют одновременно, ограничено. Границу устанавливает параметр max_connections. Его задача (как и родственного ему параметра Apache MaxClients) заставить сервер отвергать новые запросы на соединение, когда ресурсы узла заняты или когда программное обеспечение не функционирует.
Значения этих параметров можно изменять, редактируя файл конфигурации. Чтобы настроить MaxClients в Apache, следует править файл httpd.conf. Настройка max_connections в MySQL осуществляется за счет редактирования файла my.conf.
Если вы пользуетесь постоянными соединениями, и практически каждой странице на вашем сайте требуется доступ к базе данных, вам, пожалуй, понадобится постоянное соединение для каждого процесса Apache. Если же используются значения параметров, принятые по умолчанию, могут возникнуть определенные сложности. По умолчанию Apache допускает до 150 соединений, а MySQL только 100. В особо напряженное время соединений может не хватить. Поэтому лучше всего настроить параметры так, чтобы у каждого процесса Web-сервера было свое соединение, конечно, с оглядкой на технические возможности применяемых аппаратных средств.
Выбор базы данных. Работая с MySQL, необходимо указывать, какая база данных нужна. Это может сделать РНР-функиия mysql_select_db():
mysql_select_db("books");
Прототип этой функции выглядит так:
int mysql_select_db(string database, [int database_connection]);
В результате будет использоваться база данных с именем database. Можно также использовать соединение с базой данных, для которого требуется выполнить эту операцию (в нашем случае $db), однако, если его не указать, будет использоваться последнее открытое соединение. Если открытое соединение не существует, оно открывается по умолчанию, как если бы вызывалась mysql_connect().
Выполнение запроса к базе данных. Чтобы осуществить запрос, можно воспользоваться функцией mysql_query(). Однако прежде запрос необходимо настроить:
$query = "select * from books where ".$searchtype." like '%".$searchterm."%'";
В этом случае будет отыскиваться значение, введенное пользователем ($searchterm), в поле, которое указал пользователь ($searchtype). Обратите внимание на то, что мы употребили like, отдав ему предпочтение перед equal толерантность никогда не бывает излишней. Не забывайте, что запрос, отправляемый вами в MySQL, не требует в конце точки с запятой, в отличие от запроса, который вводится в среде монитора MySQL.
Теперь можно выполнить запрос:
$result = mysql_query ($query);
Прототип функции mysql_query() таков:
int mysql_query(string query, [int database_connection]);
В функцию передается запрос, который должен быть выполнен; можно также передать еще и соединение с базой данных (в нашем случае $db). Если его не указать, будет использоваться последнее открытое соединение. Если такового нет, функция откроет соединение точно так же, как при выполнении mysql_connect().
Вместо этого можно воспользоваться функцией mysql_db_query() Рассмотрим ее прототип:
int mysql_db_query(string database, string query,
[int database_connection]);
Здесь можно указать базу данных, в которой требуется производить поиск. В каком-то смысле это комбинация функций mysql_select_db() и mysql_query(). Обе функции возвращают идентификатор результата (что позволяет получить результаты поиска) в случае успеха и значение false в случае неудачи. Идентификатор результата следует сохранить (так же, как в нашем случае с $result), чтобы извлечь из него некоторую пользу.
Получение результатов запроса. Разнообразие функций дает возможность получить результат различными способами. Идентификатор результата это ключ доступа к строкам, возвращенным запросом, которых может быть нуль, одна и более.
В нашем примере использовались две функции: mysql_numrows() и mysql_fetch_array().
Функция mysql_numrows() сообщает количество строк, которые возвращает запрос В нее следует передать идентификатор результата:
$num_results = mysql_num_rows($result);
Это полезно знать, если планируется обрабатывать или отображать результаты. Зная их количество, можно организовать цикл:
for ($i=0; $i <num_results; $i++)
{ // обработка результатов
}
На каждой итерации цикла происходит вызов mysql_fetch_array() Цикл не будет выполняться, если нет строк. Эта функция берет каждую строку из списка результата и возвращает ее в виде ассоциативного массива, с ключом как именем атрибута и значением как соответствующим значением массива.
$row = mysql_fetch_array($result);
Имея $row в ассоциативном массиве, можно пройти каждое поле и отобразить его:
echo "<br>ISBN: ";
echo stripslashes($row["isbn"]);
Как уже упоминалось, stripslashes() вызывают для того, чтобы "подчистить" значение, прежде чем отображать его пользователю.
Существуют несколько вариантов получения результата из идентификатора результата. Вместо ассоциативного массива можно воспользоваться нумерованным массивом, применив mysql_fetch_row():
$row = mysql_fetch_row($result);
Значения атрибутов будут храниться в каждом порядковом значении $row[0], $row[l] и т.д.
С помощью функции mysql_fetch_object() можно выбрать строку внутрь объекта:
$row = mysql_fetch_object ($result);
После этого к атрибутам можно получить доступ через $row->title, $row->author и т.д.
Каждый из этих вариантов подразумевает выборку строки за раз. Другой вариант получить доступ, используя mysql_result() Для этого потребуется указать номер строки (от 0 до количества строк минус 1) и название поля, например
$row = mysql_result($result, $i, "title");
Название поля можно задать в виде строки (либо в форме "title" либо в форме books.title") или номером (как в mysql_fetch_row()). He стоит смешивать mysql_result() с другими функциями выборки.
Строчно-ориентированные функции выборки намного более эффективны, нежели mysql_result(), так что лучше использовать одну из них
Отсоединение от базы данных. Для закрытия непостоянного соединения применяется функция:
mysql_close(database_connect±on);
Однако в этом нет особой необходимости, поскольку с завершением выполнения сценария соединение закроется автоматически.
Внесение новой информации в базу данных. Внесение новой информации очень похоже на получение существующей. Нужно пройти те же шаги установить соединение, отправить запрос и проверить результаты. Только в данном случае вместо SELECT будет использоваться INSERT. Хоть все и просто, но взглянуть на пример никогда не помешает.
Листинг 6.9. newbookhtml HTML-код страницы добавления новых книг
<html><head><title>Book - New Book Entry</title> </head> <body>
<hl>Book- New Book Entry</hl>
<form action="insert_book.php" method="post"> <table border=0>
<tr><td>ISBN</td><td><input type=text name=isbn maxlength=13 size=13><br></td> </tr>
<tr><td>Author</td><td> <input type=text name=author maxlength=30 size=30><br></td> </tr>
<tr><td>Title</td><td> <input type=text name=title maxlength=60 size=30><br></td> </tr>
<tr><td>Price $</td><td><input type=text name=price maxlength=7 size=7><br></td> </tr>
<tr><td colspan=2><input type=submit value="Register"></td></tr>
</table>
</form></body></html>
Результаты заполнения этой формы передаются в insert_book.php, а сценарий выполняет определенную аутентификацию и пытается записать данные в базу данных. Код для сценария представлен в листинге 6.10.
Листинг 6.10. insert_book.php сценарий записывает новые книги в базу данных
<html><head><title> Book Entry Results</title></head> <body>
<hl> Book Entry Results</hl>
<?
if (!$isbn || !$author || !$title || !$price)
echo "You have not entered all the required details .<br>"
."Please go back and try again."; exit ; }
$isbn = addslashes($isbn);
$author = addslashes($author);
$title = addslashes($title) ;
$price = doubleval($price);
@$db = mysql_pconnect("localhost", "bookorama", "bookorama");
if (!$db) {
echo "Error: Could not connect to database. Please try again later."; exit;}
mysql_select_db ("books");
$query = "insert into books values
('".$isbn."', '".$author."', ' ".$title."', '".$price."')";
$result = mysql_query($query);
if ($result)
echo mysql_affected_rows()." book inserted into database.";
?>
</body> </html>
После изучения кода insert_book.php станет ясно, что он во многом похож на код сценария для извлечения данных из базы. Мы проверяем, чтобы все поля формы были заполнены и отформатированы с помощью addslashes() перед внесением данных в базу.
$isbn = addslashes($isbn); $author = addslashes($author);
$title = addslashes($title); $price = doubleval($price);
Поскольку цены хранятся в базе в виде чисел с плавающей запятой, символы наклонной черты они содержать не должны. Это достигается при помощи функции doubleval(), которая отфильтрует все неподходящие символы в числовом поле. Эта же функция позаботится и обо всех символах валюты, которые пользователь может печатать при заполнении формы.
Мы снова соединяемся с базой данных, используя mysql_pconnect(), и настраиваем запрос. В данном случае это INSERT.
$query = "insert into books values
('".$isbn."', '".$author."', '''.$title."', '''.$price.''')";
$result = mysql_query($query);
Выполнение происходит не без помощи mysql_query().
Одно существенное различие между INSERT и SELECT заключается в использовании mysql_affected_rows():
echo mysql_af£ected_rows(). " book inserted into database.";
В предыдущем сценарии функция mysql_num_rows() применялась для определения количества строк, которые будет возвращать SELECT. При написании запросов, которые изменяют базу данных, например, INSERT, DELETE, UPDATE, следует использовать mysql_affected_rows().
Мы рассмотрели основы использования баз данных MySQL из РНР. Взглянем еще на некоторые полезные функции, не упомянутые ранее.
Рассмотрим кратко несколько полезных функций PHP-MySQL.
Освобождение ресурсов. Если во время выполнения сценария возникают проблемы, связанные с нехваткой памяти, пригодиться функция mysql_free_result(). Вот ее прототип:
int mysql_free_result(int result);
Она вызывается с идентификатором результата:
mysql_free_result($result);
В итоге освобождается память, занимаемая результатом. Очевидно, что до обработки результата эта функция вызываться не должна.
Создание и удаление баз данных. Для создания новой базы данных MySQL из РНР-сценария применяется функция mysql_create_db(), а для удаления базы данных mysql_drop_db(). Рассмотрим прототипы этих функций:
int mysql_create_db(string database, [int database_connection]);
int mysql_drop_db(string database, [int database_connection]);
Обе функции используют имя базы данных и соединение. Если соединения нет, будет использоваться последнее открытое. Функции создают либо удаляют указанную базу данных. В случае успеха функции возвращают значение true, а в случае неудачи false.
Другие интерфейсы РНР-баз данных. РНР поддерживает различные библиотеки, что дает возможность подключаться к огромному количеству баз данных, включая Oracle, Microsoft SQL Server, mSQL и PostgreSQL. В целом принципы подключения и подачи запросов любой из этих баз данных одни и те же. Названия функций могут быть разными, разные базы данных могут иметь разную функциональность, но если вы можете подключиться к MySQL, то другие базы вряд ли поставят вас в безвыходное положение.
Если необходимо использовать базу данных, которая не имеет специфической библиотеки, доступной в РНР, можно прибегнуть к обобщенным функциям ODBC.
ODBC это открытый интерфейс доступа к базам данных и является стандартом подключения к базам данных. Функциональность ODBC нельзя назвать чересчур широкой, однако на то имеются вполне очевидные причины: либо универсальная совместимость, либо задействование специфических возможностей каждой системы.
Технологии применения.
Аутентификация с помощью PHP и MySQL.
К счастью для пользователей Web, информация, которую выдает браузер, не позволяет идентифицировать пользователя. Если хотите знать имя посетителя и другие детали, следует спросить его об этом. Запросив и получив в ответ информацию о посетителе, необходимо каким-то образом ассоциировать ее с посетителем при его следующих входах на сайт. Если предположить, что с определенного компьютера, используя определенное имя пользователя на этом компьютере, на сайт заходит только один пользователь, а каждый пользователь работает только на одном компьютере, то для идентификации пользователя можно создать cookie-набор на компьютере пользователя. Подобное предположение неверно для большинства пользователей. Часто многие люди поочередно пользуются одним и тем же компьютером, либо же кто-то использует несколько компьютеров. По крайней мере, иногда приходится повторно спрашивать пользователя о том, кто он, и попросить посетителя предоставить какие-то доказательства того, что он тот, за кого себя выдает.
Просьба к пользователю доказать свою личность называется аутентификацией. Обычный метод аутентификации в Web это требование к посетителям предоставить уникальное имя пользователя и пароль. Аутентификация обычно используется для разрешения или запрещения доступа к определенным страницам или ресурсам. Аутентификация может быть необязательной либо использоваться для других целей, например, для персонализации.
Реализация контроля доступа. Простой контроль доступа реализовать несложно. Код, показанный на листинге 7.10, выводит одну из трех возможных страниц. Если этот файл загружен без параметров, будет отображаться HTML-форма с приглашением вести имя пользователя и пароль. Если при загрузке параметры присутствуют, но они неправильные, отображается сообщение об ошибке. Если при загрузке параметры присутствуют и они правильные, посетителю отображается секретное содержимое.
Листинг 6.11. secret.php РНР-код для реализации простого механизма аутентификации
<?
if(!isset($name)&&!isset($password))
{// Посетитель должен ввести имя и пароль
?>
<h1>Please Log In</h1>This page is secret.
<form method = post action = "secret.php">
<table border = 1>
<tr><th>Username</th><td><input type = text name = name></td></tr>
<tr><th>Password</th><td><input type = password name = password>
</td></tr><tr><td colspan =2 align = center>
<input type = submit value = "Log In"></td></tr>
</table></form>
<? }
else if($name=="user"&&$password=="pass")
<? // Комбинация имени и пароля посетителя правильная
echo "<h1>Here it is!</h1>";
echo "I bet you are glad you can see this secret page."; }
else
{ // Комбинация имени и пароля посетителя неправильная
echo "<hl>Go Away!</hl>";
echo "You are not authorized to view this resource."; }
?>
Код, показанный в листинге 6.11, реализует простой механизм, позволяющий санкционированным посетителям видеть защищенную страницу, однако код содержит несколько значительных проблем. Этот сценарий:
Упомянутые проблемы можно разрешить с различной степенью усилий и успеха.
Хранение паролей. Для хранения паролей существует много более подходящих, нежели код сценария, мест. Внутри сценария очень трудно изменять данные. Можно написать сценарий, который будет изменять себя, но это плохая идея. Это будет означать существование выполняющегося на сервере сценария, доступного для записи и изменений со стороны других пользователей. Хранение паролей в отдельном файле на сервере позволит без труда написать программу для добавления и удаления пользователей, а также для изменения паролей.
Внутри сценария или другого файла данных существует ограничение на количество пользователей, которых можно обслуживать, серьезно не навредив общей производительности сценария. Если планируется сохранять большое количество элементов в файле или производить поиск в рамках большого числа элементов, то, как обсуждалось ранее, следует рассмотреть возможность использования базы данных вместо двумерного файла. Практический метод выбора гласит: если вы собираетесь хранить и производить поиск в более чем 100 элементах, следует отдать предпочтение базе данных.
Использование базы данных для хранения имен и паролей посетителей не сильно усложнит сценарий, но позволит быстро проводить аутентификацию множества пользователей. Это также упростит создание сценария для добавления и удаления пользователей, а также даст возможность пользователям изменять свои пароли.
Сценарий для аутентификации посетителей страницы с использованием базы данных приведен в листинге 6.12.
Листинг 6.12. secretdb.php применение MySQL для простого механизма аутентификации.
<?
if(!isset($name)&&!isset($password))
{ // Посетитель должен ввести имя и пароль
?>
<hl>Please Log In</hl>This page is secret.
<form method = post action = "secretdb.php"><table border = l>
<tr><th>Username</th><td><input type = text name = name></td></tr>
<tr><th>Password</th><td><input type = password name = password>
</td></tr><tr><td colspan =2 align = center>
<input type = submit value = "Log In"></td> </tr>
</table></form>
<? }
else
{ // Подключиться к MySQL
$mysql = mysql_connect( 'localhost', 'webauth', 'webauth' );
if(!$mysql)
{echo 'Cannot connect to database.'; exit;}
// Выбрать соответствующую базу данных
$mysql = mysql_select_db('auth');
if(!$mysql)
{ echo 'Cannot elect database.'; exit; }
// Запрос к базе данных, чтобы проверить,
// существует ли соответствующая запись
$query = "select count (*) from auth where name = '$name' and
pass = '$password'";
$result = mysql_query($query);
if (!$result)
{echo 'Cannot run query.';exit; }
$count = mysql_result($result, 0, 0 );
if ( $count > 0 )
{// Комбинация имени и пароля посетителя правильная
echo "<hl>Here it is!</hl>";
echo "I bet you are glad you can see this secret page."; }
else
<?
// Комбинация имени и пароля посетителя не правильная
echo "<hl>Go Away!</hl>";
echo "You are not authorized to view this resource."; }
}
?>
Используемую в примере базу данных можно создать, подключившись к MySQL как пользователь root и запустив показанный в листинге 6.13 сценарий.
Листинг 6.13. createauthdb.sql создание базы данных, таблицы и двоих пользователей.
create database auth;
use auth;
create table auth (
name varchar(10) not null,
pass varchar(30) not null,
primary key (name)
);
insert into auth values ('user', 'pass');
insert into auth values
( 'testuser', password('test123') );
grant select, insert, update, delete
on auth.*
to webauth@localhost
identified by 'webauth';
Шифрование паролей. Независимо от того, где хранятся пароли в базе данных или в файле хранение паролей в виде простого текста сопряжено с неоправданным риском. Однонаправленный алгоритм хэширования обеспечит дополнительную защиту при незначительных дополнительных затратах.
РНР-функция crypt() представляет собой однонаправленную криптографическую хэш-функцию. Прототип этой функции таков:
string crypt (string str[, string salt])
Получив на входе строку str, эта функция возвращает псевдослучайную строку. Например, если передать в функцию строку "pass" и аргумент salt равный "хх", то crypt() вернет строку "xxkTlmYjlikoII". Эта строка не может быть дешифрована и превращена обратно в "pass" даже ее создателем, поэтому на первый взгляд строка может и не показаться столь уж полезной. Что делает функцию crypt() полезной, так это то, что результат этой функции детерминирован. При каждом вызове с одними и теми же параметрами str и salt эта функция будет возвращать один и тот же результат.
Вместо РНР-кода, такого как
if( $username == "user" && password == "pass" )
{ // Пароль совпадает
}
можно воспользоваться таким кодом
if($username= 'user' && crypt($password,'хх') == 'xxkTlmYjlikoII')
{ // Пароль совпадает
}
Нам не требуется знать, как выглядела строка "xxkTlmYjlikoH" перед использованием функции crypt(). Необходимо только знать, совпадает ли введенный пароль тем паролем, для которого применялась функция crypt().
Как уже упоминалось, жесткое кодирование правильных имен и паролей посетителей - плохая идея. Для этого следует организовать отдельный файл или базу данных. Если для хранения данных аутентификации используется база данных MySQL, можно воспользоваться РНР-функцией crypt() или MySQL-функцией PASSWORD(). результат этих функций не совпадает, но они имеют одно предназначение. Обе функции crypt() и PASSWORD() получают строку как аргумент и применяют к поденной строке необращаемый алгоритм хэширования.
Чтобы задействовать функцию PASSWORD() в листинге 14.2 запрос SQL следует переписать так:
select count (*) from auth where
name = '$name' and
pass = password('$password')
Этот запрос посчитает количество строк в таблице auth, в которых значение поля name совпадает с содержимым переменной $name, а поля pass - с результатом функции PASSWORD(), примененной к значению переменной $password. Если мы заставляем посетителей выбирать уникальные имена, результатом запроса может быть 0 или 1.
Защита множества страниц. Защита более чем одной страницы с помощью подобных сценариев немного сложнее. Поскольку в HTTP-протоколе нет механизма состояний, то не существует автоматической связи или ассоциации между последовательными запросами от одного и того же посетителя. Это усложняет перенос между страницами введенных пользователем данных, таких как данные аутентификации.
Чтобы самостоятельно создать такую функциональность, потребуется включить части листинга 6.11 в каждую страницу, которую необходимо защитить. При помощи директив auto_prepend_file и auto_append_file требуемый файл можно автоматически вставить в начало (prepend) или в конец (append) каждого файла в указанных каталогах.
Если воспользоваться таким подходом, то что произойдет, когда пользователь откроет несколько страниц на сайте? Недопустимо запрашивать пароль отдельно для каждой страницы, которую желает просмотреть пользователь.
Можно включить введенную пользователем информацию в каждую гиперссылку на странице. Так как пользователи могут применять пробелы или другие символы, запрещенные в URL, следует обратиться к функции urlencode(), чтобы безопасно упаковать подобные символы.
С этим подходом связаны еще некоторые проблемы. Поскольку данные аутентификации будут присутствовать в отправляемой посетителю Web-странице, защищенные страницы, которые посетил пользователь, могут быть просмотрены любым человеком, работающим за тем же компьютером. Для этого достаточно щелкнуть на кнопке "Назад" в окне браузера и просмотреть кэшированные копии страниц или заглянуть в историю посещения страниц. Пароль пересылается в браузер и обратно с каждой запрошенной или предоставленной страницей, что происходит чаще, чем это необходимо.
Решить проблему можно с помощью двух механизмов базовой HTTP-аутентификации и поддержки сеансов. Базовая аутентификация позволяет решить проблему кэширования, но сервер все равно отправляет пароль Web-браузеру в каждом запросе. Управление сеансами позволяет решить обе проблемы. Сначала рассмотрим базовую HTTP-аутентификацию, а управление сеансами освещается далее.
Базовая аутентификация. К счастью, аутентификация пользователей это достаточно распространенная задача и существуют возможности аутентификации, встроенные в HTTP-протокол. Сценарии и Web-серверы могут запрашивать аутентификацию у Web-браузера. После этого Web-браузер должен вывести на экран диалоговое окно или что-то подобное и запросить у пользователя необходимую информацию.
Хотя Web-сервер запрашивает новые детали аутентификации в каждом запросе пользователя, Web-браузеру нет необходимости запрашивать эту информацию для каждой страницы. В общем случае браузер хранит детали аутентификации, пока открыто окно браузера, и автоматически отправляет их без вмешательства со стороны пользователя.
Описанная возможность HTTP-протокола называется базовой аутентификацией. Баовую аутентификацию можно включить средствами РНР или с помощью Web-сервера, Apache и IIS. Далее рассматриваются методы, предполагающие использование РНР.
Базовая аутентификация передает имя пользователя и пароль в виде простого текста и поэтому не особо безопасна. Протокол HTTP 1.1 обладает более безопасным методом, называемым дайджест-аутентификацией (digest authentication). Этот метод использует алгоритм хэширования (как правило, MD5) для маскировки деталей транзакции. Дайджест-аутентификация поддерживается во многих Web-серверах, но не поддерживается в значительном числе браузеров. Дайджест-аутентификация поддерживается в браузере Microsoft Internet Explorer начиная с версии 5.0. Поддержка дайджест-аутентификации включена в Netscape Navigator версию 6.0.
В дополнение к очень слабой поддержке в наборе доступных браузеров, дайджест-аутентификация к тому же и не очень безопасна. И базовая, и дайджест-аутентификация предоставляют низкий уровень защищенности. Ни один из этих методов не дает пользователю гарантий, что он работает именно с тем компьютером, доступ к которому он планировал получить. Оба метода позволяют взломщику повторить тот же запрос серверу. Поскольку базовая аутентификация передает пароль пользователя в открытом виде, любой взломщик, способный перехватывать пакеты, может сымитировать запрос пользователя.
Базовая аутентификация предоставляет низкий уровень защиты, подобный тому, который обеспечивается при подключении по протоколу Telnet или FTP. Эти методы также передают пароли в виде простого текста. Дайджест-аутентификация несколько более безопасна и шифрует пароли перед передачей. Использование протокола SSL и цифровых сертификатов позволяет надежно защитить все части транзакций в Web.
Однако во многих ситуациях наиболее подходящим будет быстрый и относительно незащищенный метод, такой как базовая аутентификация.
Базовая аутентификация позволяет защитить именованные области и требует от пользователей ввода правильного имени и пароля. Области именованные, поэтому на одном сервере может быть существовать множество областей. Различные файлы и каталоги на одном сервере могут принадлежать разным областям, каждая из которых защищена своими наборами имен пользователей и паролей. Именованные области позволяют также сгруппировать в одну область несколько каталогов на одном физическом или виртуальном узле и защитить всю область одним паролем.
Использование базовой аутентификации в РНР. РНР-сценарии, в основном, можно назвать кросс-платформенными, но использование базовой аутентификации базируется на переменных среды, устанавливаемых сервером. Сценарий HTTP-аутентификации должен определять тип сервера и вести себя соответствующим образом в зависимости от того, выполняется ли он как модуль Apache на сервере Apache или как ISAPI-модуль на сервере IIS. Показанный в листинге 6.14 сценарий будет выполняться на обоих серверах.
Листинг 6.14. http.php включение базовой HTTP-аутентификации средствами РНР.
<?
// Если используется сервер IIS, потребуется установить переменные
// среды $PHP_AUTH_USER и $PHP_AUTH_PW
if (substr($SERVER_SOFTWARE, 0, 9) == "Microsoft" &&
!isset($PHP_AUTH_USER) && !isset($PHP_AUTH_PW) &&
substr($HTTP_AUTHORIZATION, 0, 6) == "Basic" )
{ list($PHP_AUTH_USER, $PHP_AUTH_PW) =
explode(":", base64_decode(substr($HTTP_AUTHORIZATION, 6))); }
// Замените этот оператор if запросом к базе данных
if ($PHP_AUTH_USER != "user" || $PHP_AUTH_PW != "pass")
{
// Посетитель еще не передал деталей или имя и пароль неправильные
header('WWW-Authenticate: Basic realm="Realm-Name"');
if (substr($SERVER_SOFTWARE, 0, 9) == "Microsoft")
header("Status: 401 Unauthorized");
else header("HTTP/1.0 401 Unauthorized");
echo "<hl>Go Away!</hl>";
echo "You are not authorized to view this resource."; }
else {
// посетитель предоставил правильную информацию
echo "<h1>Here it is!</hl>";
echo "<p>I bet you are glad you can see this secret page.";
}
?>
Этот код работает так же, как и код из предыдущего листинга. Если пользователь не передал данных аутентификации, ему выдается запрос на аутентификацию. Если пользователь предоставил неправильную информацию, для него отображается сообщение об отказе в доступе. Если же информация правильная, пользователь увидит содержимое страницы.
Интерфейс данного примера отличается от интерфейса предыдущих примеров. Мы не создаем HTML-форму для ввода имени и пароля. Диалоговое окно для аутентификации выведет браузер пользователя. Некоторые рассматривают это как улучшение, другие предпочитают иметь полный контроль над визуальными аспектами интерфейса.
Поскольку для аутентификации применяются встроенные возможности браузеров, последние демонстрируют некоторую осторожность в обработке неудачных попыток аутентификации. Internet Explorer дает пользователю три попытки аутентификации, и если все они проходят неудачно, выводится сообщение об отказе в доступе. Netscape Navigator предоставляет неограниченное число попыток, но между попытками выводит диалоговое окно с запросом о повторе "Authentication Failed. Retry?". Netscape отображает сообщение об отказе в доступе, только когда пользователь щелкает на кнопке Cancel.
Как и код из листингов 6.11 и 6.12, код данного примера можно вставить в начало каждого файла, который требуется защитить. Это можно сделать вручную или автоматически для каждого файла в каталоге.
Управление сеансами в РНР.
Что такое управление сеансом. HTTP иногда называют "протоколом без состояния". Это означает, что данный протокол не имеет встроенного способа поддержки состояния между двумя транзакциями. Когда пользователь запрашивает друг за другом две страницы, HTTP не обеспечивает возможности уведомить, что оба запроса исходят от одного и того же пользователя. Таким образом, идея управления сеансами заключается в обеспечении отслеживания пользователя в течение одного сеанса связи с Web-сайтом. Если это удастся осуществить, мы сможем легко поддерживать подключение пользователя и предоставление ему содержимого сайта в соответствии с его уровнем прав доступа или персональными настройками. Мы сумеем отслеживать поведение пользователя.
В более ранних версиях РНР управление сеансами осуществлялось средствами PHPLib, базовой библиотеки РНР, которая и сейчас является полезным набором инструментов. Об этом можно прочесть на
http://phplib.netuse.de/index.php3
Четвертая версия РНР включает собственные встроенные функции управления сеансом. Концептуально они подобны PHPLib, но PHPLib помимо этого обеспечивает em и ряд дополнительных функциональных возможностей. Так что если окажется, что эти собственные функции не вполне отвечают вашим требованиям, ничто не мешает рассмотреть возможность использования PHPLib.
Основные функциональные средства управления сеансом. Для запуска сеанса в РНР используется уникальный идентификатор сеанса, представляющий собой зашифрованное случайное число. Идентификатор сеанса генерируется РНР и сохраняется на стороне клиента в течение всего времени жизни сеанса. Для хранения идентификатора сеанса используется либо cookie-набор на компьютере пользователя, либо URL.
Идентификатор сеанса играет роль ключа, обеспечивающего возможность регистрации некоторых специфических переменных в качестве так называемых переменных сеанса. Содержимое этих переменных сохраняется на сервере. Единственной информацией, "видимой" на стороне клиента, является идентификатор сеанса. Если во время определенного подключения к вашему сайту идентификатор сеанса является "видимым" либо в cookie-наборе, либо в URL, имеется возможность получить доступ к переменным сеанса, которые сохранены на сервере для данного сеанса. По умолчанию переменные сеанса хранятся в двумерных файлах на сервере (при желании способ хранения можно изменить и использовать вместо двумерного файла базу данных, но для этого потребуется написать собственную функцию).
Скорее всего, придется иметь дело с Web-сайтами, на которых для хранения идентификатора сеанса используется URL. Если в вашем URL имеется строка данных, которые выглядят случайными, то это, скорее всего, свидетельствует об использовании одной из двух описанных здесь разновидностей управления сеансом.
Другим решением проблемы сохранения состояния на протяжении некоторого количества транзакций, при наличии чистого внешнего вида URL, являются cookie-наборы.
Что такое cookie-набор? cookie-набор это небольшой фрагмент информации, который сценарии сохраняют на клиентской машине. Чтобы установить cookie-набор на машине пользователя, необходимо отправить ему HTTP-заголовок, содержащий данные в следующем формате.
Set-Cookie: NAME=VALUE; [expires=DATE;] [path=PATH;] [domain=DOMAIN_NAME;] [secure]
Это создаст cookie-набор с именем NAME и значением VALUE. Все остальные параметры являются необязательными. В expires задается дата истечения срока действия, после наступления которой cookie-набор перестанет рассматриваться как актуальный. Заметим, что если дата истечения срока действия не задана, cookie-набор будет постоянным, пока его кто-нибудь не удалит вручную либо вы, либо сам пользователь). Два параметра path и domain применяются для определения одного или нескольких URL, к которым относится данный cookie-набор. Ключевое слово secure означает, что cookie-набор не может отправляться через простое HTTP-соединение.
Когда браузер соединяется с URL, он сначала ищет cookie-наборы, хранящиеся локально. Если какие-либо из них относятся к URL, с которым установлено соединение, они передаются обратно на сервер.
Установка cookie-наборов из РНР. cookie-наборы в РНР можно установить вручную, используя функцию setcookie(). Она имеет следующий прототип:
int setcookie (string name [, string value [, int expire [, string path [, string domain [, int secure]]]]])
Параметры в точности соответствуют тем, которые используются в описанном выше заголовке Set-Cookie. Если cookie-набор установлен как
setcookie ("mycookie", "value");
то когда пользователь обращается к следующей странице на вашем сайте (или перезагружает текущую страницу), вы получаете доступ к переменной с именем $mycookie, которая содержит значение "value". Доступ к этой переменной можно получить также через $HTTP_COOKIE_VARS["mycookie"].
Для удаления cookie-набора необходимо вызвать setcookie() с тем же именем, но без указания значения. Если cookie-набор устанавливался с другими параметрами (такими как специфические URL или даты истечения), потребуется отправить те же параметры повторно, иначе cookie-набор удален не будет.
Для установки cookie-набора вручную можно воспользоваться также функцией Header() и описанным выше синтаксисом представления cookie-набора. Однако при этом следует иметь в виду, что заголовки cookie-наборов должны отправляться перед всеми другими заголовками (иначе заголовки cookie-наборов работать не будут).
Использование cookie-наборов в сеансах. При использовании cookie-наборов возникают некоторые проблемы: есть браузеры, которые не принимают cookie-наборы, а есть пользователи, которые запрещают использование cookie-наборов в своих браузерах. Это одна из причин, по которым в сеансах РНР используются двойной метод cookie-набор/адрес URL (ниже этот вопрос рассматривается более подробно).
В сеансе РНР нет необходимости задавать cookie-наборы вручную. Это за вас сделают функции сеанса. Для того чтобы просмотреть содержимое cookie-набора, установленное при управлении сеансом, можно воспользоваться функцией session_get_cookie_params(). Она возвращает ассоциативный массив, содержащий элементы lifetime, path и domain.
Можно использовать также:
session_set_cookie_params($lifetime, $path, $domain);
Этот оператор устанавливает параметры cookie-набора для сеанса.
Если возникнет желание получить более подробную информацию о cookie-наборах, то за консультациями по спецификации cookie-наборов следует обратиться на сайт компании Netscape:
http://home.netscape.com/newsref/std/cookie_spec.html
Сохранение идентификатора сеанса. В РНР cookie-наборы в сеансах используются по умолчанию. Если есть возможность установить cookie-наборы, то для сохранения идентификатора сеанса будет использоваться именно этот способ. Другой метод, который может применяться в РНР, заключается в добавлении идентификатора сеанса к адресу URL. Можно сделать так, чтобы идентификатор сеанса добавлялся к URL автоматически для этого следует скомпилировать РНР с опцией --enable-trans-sid.
Можно встроить идентификатор сеанса в ссылку, чтобы обеспечить его передачу. Идентификатор сеанса будет запоминаться в константе SID. Для того, чтобы передать его вручную, его потребуется добавить в конец ссылки, аналогично параметру GET:
<А HREF="link.php?<?=SID?>">
В общем случае проще компилировать РНР с --enable-trans-sid, если только это возможно (заметим попутно, что константа SID может использоваться для вышеописанных целей только в том случае, если конфигурация РНР выполнялась с --епаblе-track-vars).
Реализация управления простым сеансом. Основными этапами использования сеанса являются следующие:
Заметим, что все перечисленные этапы не обязательно могут содержаться в одном сценарии, и некоторые из них могут находиться в нескольких сценариях. Рассмотрим каждый из этих этапов последовательно.
Запуск сеанса. Прежде чем можно будет воспользоваться функциональными возможностями сеанса, следует запустить сам сеанс. Существует три способа сделать это.
Первый (и самый простой) заключается в том, что сценарий начинается с вызова функции
session_start() ;
Эта функция проверяет, существует ли идентификатор текущего сеанса. Если нет, она его создает. Если же идентификатор текущего сеанса уже существует, она загружает зарегистрированные переменные сеанса, чтобы они стали доступными для использования.
Надо отметить, что это прекрасный способ вызов session_start() в начале сценариев, в которых используются сеансы.
Второй способ заключается в том, что сеанс запускается при попытке зарегистрировать переменные сеанса (см. далее).
Третий способ запустить сеанс задать установки РНР, при которых сеанс будет запускаться автоматически, как только кто-либо посетит ваш сайт. Для этого следует воспользоваться опцией session.auto_start в файле php.ini (более подробно указанный способ будет описан при рассмотрении конфигурации).
Регистрация переменных сеанса. Для того чтобы получить возможность отслеживать переменные от одного сценария другому, их необходимо зарегистрировать. Это делается путем вызова функции session_register(). Например, для регистрации переменной $myvar применяется следующий код.
$myvar = 5; session_register("myvar");
Обратите внимание: вы должны передать в функцию session_register() строку, содержащую имя переменной. Эта строка не должна включать символ $. Данный оператор регистрирует имя переменной и отслеживает ее значение. Отслеживание переменной будет осуществляться, пока не завершится сеанс либо пока вручную не отменится ее регистрация.
За один прием можно зарегистрировать более одной переменной, передав разделенный запятыми список имен переменных:
session_register("myvar1", "myvar2");
Использование переменных сеанса. Чтобы сделать переменную сеанса доступной для использования, сначала необходимо запустить сеанс, воспользовавшись одним из описанных выше способов. После этого появляется доступ к этой переменной. Если опция register_globals включена, то доступ к этой переменной можно получить через сокращенную форму ее имени, например, $myvar. Если же упомянутая опция не включена, получить доступ к переменной можно через ассоциативный массив $HTTP_SESSION_VARS, например, $HTTP_SESSION_VARS ["myvar"].
Переменные сеанса не могут быть перезаписаны данными GET или POST. Это хорошо с точки зрения обеспечения безопасности, однако сопряжено с некоторыми ограничениями при кодировании.
С другой стороны, от вас потребуется тщательность при проверке на предмет того, установлены ли уже переменные сеанса (например, с использованием isset() либо empty()). Кроме того, следует иметь в виду, что переменные могут быть установлены пользователем через GET или POST. Проверить, является ли переменная зарегистрированной переменной сеанса, можно обратившись к функции session_is_registered(). Вызов функции выполняется следующим образом:
$result = session_is_registered("myvar");
Эта функция проверит, является ли $myvar зарегистрированной переменной сеанса, и вернет true или false. Можно поступить и по-другому проверить массив $HTTP_SESSION_VARS на предмет наличия в нем переменной.
Отмена регистрации переменных и завершение сеанса. После окончания работы с переменной сеанса ее регистрацию можно отменить, воспользовавшись функцией
session_unregister("myvar");
Подобно функции регистрации, эта функция требует указания имени переменной, регистрацию которой необходимо отменить, в виде строки, не включающей символ $. Данная функция за один раз может отменить регистрацию только одной переменной сеанса (в противоположность session_register()). Однако, для отмены регистрации всех переменных текущего сеанса можно обратиться к session_unset().
По завершении сеанса сначала потребуется отменить регистрацию всех переменных, а затем для обнуления идентификатора сеанса вызвать
session_destroy();
Пример простого сеанса. Изложенный выше материал может показаться несколько абстрактным, поэтому сейчас вашему вниманию предлагается пример. Приведенный в нем код обеспечивает обработку трех страниц. На первой странице мы запустим сеанс и зарегистрируем переменную $sess_var Код показан в листинге 6.15.
Листинг 6.15. page1.php запуск сеанса и регистрация переменной
<?
session_start ();
session_register("sess_var");
$sess_var = "Hello world!";
echo "The content of \ $sess_var is $sess_var<br>" ;
?>
<a href = "page2.php">Next page</a>
Мы зарегистрировали переменную и установили ее значение.
Заметим, что мы изменили значение переменной уже после ее регистрации. Можно, однако, сделать и наоборот установить значение, а после этого зарегистрировать переменную. Конечное значение переменной на странице это то значение, которое будет доступно на последующих страницах. В конце сценария переменная сеанса преобразуется в последовательную форму (сериализуется), или замораживается, до своей перезагрузки через следующий вызов session_start(). Таким образом, следующий сценарий начинается с вызова session_start(). Сценарий показан в листинге 6.16
Листинг 6.16. page2.php получение доступа к переменной сеанса и отмена регистрации
<?
session_start();
echo "The content of \ $sess_var is $sess_var<br>";
session_unregister("sess_var");
?>
<a href = "page3.php">Next page</a>
После вызова session_start() переменная $sess_var станет доступной, а ее значением будет то, которое сохранено в предыдущем сеансе.
Сделав с переменной все необходимые действия, мы вызываем session_unregister() для отмены ее регистрации. Обратите внимание: сеанс еще существует, но переменная $sess_var уже больше не является зарегистрированной.
И наконец, мы переходим к page3.php, последнему сценарию в рассматриваемом примере.
Листинг 6.17. page3.php завершение сеанса
<?
session_start();
echo "The content of \ $sess_var is $sess_var<br>";
session_destroy () ;
?>
Как можно видеть, доступа к значению $sess_var больше нет. И в завершение вызов session_destroy() для разрушения идентификатора сеанса.
Конфигурирование управления сеанса. А сейчас мы предлагаем ознакомиться с набором опций конфигурации для сеансов, которые можно установить в своем файле php.ini. В табл. 7.8 перечисляются некоторые из наиболее полезных опций вместе с их кратким описанием.
Таблица 6.8. Опции конфигурации сеанса
Имя опии и |
Значение no умолчанию |
Действие |
session.auto_start |
0 (запретить) |
Автоматический запуск сеансов. |
session.cache_expire |
180 |
Установка времени жизни для кэшированных станиц сеанса (в минутах). |
session.cookie_domain |
none |
Домен для установки в cookie-наборе сеанса. |
session.cookie_lifetime |
0 |
Определяет продолжительность существования cookie-набора идентификатора сеанса на машине пользователя. По умолчанию 0 пока не будет закрыт браузер. |
session.cookie_pafh |
/ |
Путь для установки в cookie-наборе сеанса. |
session.name |
PHPSESSID |
Имя сеанса, которое в системе пользователя используется как имя cookie-набора. |
session.save_handler |
файлы |
Определяет место хранения данных сеанса. Здесь можно указать базу данных, однако для этого потребуется реализовать собственные функции. |
session.save_path |
/tmp |
Путь к месту хранения данных сеанса. В более общем случае для определения и обработки передаваемых на хранение аргументов используется session.save_handler. |
session.use_cookies |
1 (разрешить) |
Конфигурация сеанса с возможностью использования cookie-наборов на стороне клиента. |
Выполнение аутентификации пользователей средствами управления сеансом. В завершение рассмотрим более важный пример использования контроля сеанса.
Наиболее часто, пожалуй, управление сеансом применяется в целях отслеживания пользователей после того, как они были аутентифицированы через механизм входной регистрации. В предлагаемом примере можно видеть, как эти функциональные возможности обеспечиваются за счет сочетания аутентификации при помощи базы данных MySQL и использования механизма управления сеансом.
В нашем примере мы воспользуемся базой данных аутентификации, которая была создана ранее. Это требуется для работы с модулем mod_auth_mysql. Пример включает три простых сценария. Первый, authmain.php, обеспечивает форму для входной регистрации и аутентификации пользователей Web-сайта. Второй, members_only.php, представляет информацию только для тех пользователей, которые успешно прошли входную регистрацию. Третий, logout.php. реализует выход пользователей из системы.
Первая страница предоставляет пользователю возможность войти в систему. В случае, если он предпримет попытку получить доступ к секции Members, не пройдя входную регистрацию, будет выдано сообщение. Если же пользователь сначала прошел входную регистрацию (с именем пользователя: testuser и паролем: test123, как было задано ранее), а потом попытался войти на страницу Members, он увидит соответствующее сообщение.
Давайте посмотрим на код приложения. Большая часть кода сосредоточена в сценарии authmain.php, приведенном в листинге 6.18. Давайте изучим его более подробно.
Листинг 6.18. authmain.php Основная часть приложения аутентификации
session_start();
if ($userid && $password)
{
// если пользователь как раз пытается зарегистрироваться
$db_conn = mysql_connect("localhost", "webauth", "webauth");
mysql_select_db("auth", $db_conn);
$query = "select * from auth where name='$userid'"
." and pass=password('$password')";
$result = mysql_query($query, $db_conn);
if (mysql_num_rows($result) >0 )
{// если пользователь найден в базе данных,
// зарегистрировать его идентификатор
$valid_user = $userid;
session_register{"valid_user"); }
}
?>
<html><body><h1>Home page</h1>
<?
if (session_is_registered("valid_user"))
{
echo "You are logged in as: $valid_user <br>";
echo "<a href=\"logout.php\">Log out</a><br>";
}
else
{
if (isset($userid)) {
// если пользователь пытался зарегистрироваться,
// но возникла ошибка
echo "Could not log you in"; }
else {
// если пользователь либо не пытался зарегистрироваться,
// либо покинул сайт
echo "You are not logged in.<br>"; }
// форма для аутентификации
echo "<form method=post action=\"authmain.php\">";
echo "<table>"; echo "<tr><td>Userid:</td>";
echo "<td><input type=text name=userid></td></tr>";
echo "<tr><td>Password:</td>";
echo "<td><input type=password name=password></td></tr>";
echo "<tr><td colspan=2 align=center>" ;
echo "<input type=submit value=\"Log in\"></td></tr>" ;
echo "</table></form>" ;
}
?> <br>
<a href="members_only.php">Members section</a>
</body></html>
Данный сценарий отличается сложной (в разумных пределах) логикой, но иначе нельзя: ведь он осуществляет представление формы для входной регистрации и ее обработку.
Работа этого сценария сосредоточена вокруг переменной сеанса $valid_user. Основная идея здесь заключается в следующем: если кто-либо успешно прошел процедуру входной регистрации, мы регистрируем переменную сеанса с именем $valid_user, которая содержит идентификатор пользователя.
Так что же первым делом выполняется в сценарии? Правильно, вызов session_start(). Эта функция загружает переменную сеанса $valid_user, если последняя была зарегистрирована.
При первом проходе по сценарию ни один из условных операторов if не сработает и неудачливому пользователю к концу сценария останется лишь внимательно прочесть сообщение о том, что он не прошел процедуру входной регистрации. После этого мы предоставляем ему форму, при помощи которой он сможет это сделать:
echo "<form method=post action=\ "authmain.php\ ">";
echo "<table>";
echo "<tr><td>Userid:</td>";
echo "<td><input type=text name=userid></td></tr>" ;
echo "<tr><td>Password:</td>";
echo "<td><input type=password name=password></td></tr>" ;
echo "<tr><td colspan=2 align=center>" ;
echo "<input type=submit value=\ "Log in\ "></td></tr>" ;
echo "</table></form>";
Когда пользователь нажмет кнопку отправки (Submit), сценарий вызывается заново и вновь все начинается с начала. На этот раз в нашем распоряжении будут имя пользователя и пароль, позволяющие его аутентифицировать (они хранятся в $userid и $password). Если эти переменные установлены, переходим к блоку аутентификации:
if ($userid && $password) {
// если пользователь как раз пытается зарегистрироваться
$db_conn = mysql_connect("localhost", "webauth", "webauth");
mysql_select_db("auth", $db_conn);
$query = "select * from auth "
."where name='$userid' " . " and pass=password('$password')";
$result = mysql_query($query, $db_conn);
И вот мы подключаемся к базе данных MySQL и проверяем имя пользователя и пароль. Если в базе данных существует соответствие этой паре, мы регистрируем переменную $valid_user, которая содержит идентификатор для конкретного пользователя. Таким образом, мы знаем, кто вошел в систему и, соответственно, будем его отслеживать.
if (mysql_num_rows($result) >0) {
// если пользователь найден в базе данных,
// зарегистрировать его идентификатор
$valid_user = $userid;
session_register("valid_user"); }
Поскольку уже известно, кто сейчас посещает сайт, то повторно предоставлять ему aорму входной регистрации нет необходимости. Вместо этого мы сообщаем пользователю, что мы знаем, кто он такой, и даем ему возможность выхода из системы:
if (session_is_registered("valid_user")) {
echo "You are logged in as: $valid user <br>";
echo "<a href=\"logout.php\">Log out</a><br>"; }
Если же при попытке произвести входную регистрацию пользователя, мы по какой-то причине терпим неудачу, то у нас имеется идентификатор пользователя, но нет переменной $valid_user, и ничего не остается, кроме как выдать сообщение об ошибке:
if (isset($userid)) {
// если пользователь пытался зарегистрироваться, но возникла ошибка
echo "Could not log you in"; }
Поскольку $valid_user является зарегистрированной переменной сеанса, ее нельзя перезаписать путем передачи другого значения через URL, например так:
members_only.php?valid_user=testuser
С основным сценарием, похоже, все понятно. А теперь посмотрим на страницу Members. Код этого сценария показан в листинге 6.19.
Листинг 6.19. members_only.php процедур проверки достоверности пользователя
<?
session_start();
echo "<h1>Members only</h1>";
// проверить переменные сеанса
if (session_is_registered("valid_user"))
{
echo "<p>You are logged in as $valid_user.</p>";
echo "<p>Members only.content goes here</p>";
}
else
{
echo "<p>You are not logged in.</p>";
echo "<p>Only logged in members may see this page.</p>";
}
echo "<a href=\"authmain.php\">Back to main page</a>";
?>
Приведенный выше код очень прост. Все, что он делает это запуск сеанса и проверка того, содержит ли текущий сеанс зарегистрированного пользователя, с использованием функции session_registered_user(). Если пользователь прошел процедуру входной регистрации, мы отображаем содержимое сайта для зарегистрированных пользователей, в противном случае мы сообщаем ему, что у него нет соответствующих полномочий.
И в завершение рассмотрим сценарий logout.php, который завершает регистрацию пользователя в системе. Код сценария показан в листинге 6.20.
Листинг 6.20. logout.php отмена регистрации переменных сеанса и завершение сеанса
<?
session_start();
$old_user = $valid_user; // сохранить для проверки,
// регистрировался ли пользователь
$result = session_unregister("valid_user");
session_destroy();
?>
<html><body><h1>Log out</h1>
<?
if (! empty ($old_user) ){
if ($result){
// если пользователь был зарегистрирован и не покинул систему
echo "Logged out.<br>"; }
else { // если пользователь не может покинуть систему
echo "Could not log you out.<br>"; } }
else { // если пользователь не был зарегистрирован,
echo "You were not logged in, and so have not been logged out <br>"; }
?>
Приведенный код очень прост. Мы запускаем сеанс, запоминаем старое имя пользователя, отменяем регистрацию переменной $valid_user и завершаем сеанс. После этого мы выдаем пользователю одно из следующих сообщений: он вышел из системы, не может выйти из системы или не может выйти из системы, поскольку первоначально даже не регистрировался.
Примеры программ.
Построение системы управления содержимым
Системы управления содержимым очень эффективны для Web-сайтов, где содержимое поддерживается не только одним автором, либо сопровождение осуществляет не технический персонал, либо содержимое и графическое оформление разрабатывается различными людьми или отделами.
Мы создадим приложение, помогающее уполномоченным пользователям управлять интеллектуальной собственностью организации в электронном виде.
Требования к проекту. Необходимо создать систему, которая:
Редактирование содержимого. Во-первых, необходимо продумать способ ввода содержимого в систему, а также методы его хранения и редактирования. Требуется выбрать метод передачи компонентов статей и оформления. Существует три возможности.
Преимущество баз данных перед файлами для хранения содержимого. На начальном этапе необходимо принять важное решение относительно метода хранения содержимого после его загрузки в систему.
Поскольку совместно с текстом хранятся метаданные, мы решили поместить текстовую часть содержимого в базу данных. Хотя MySQL способен хранить мультимедийные данные, принято решение хранить загружаемые изображения в файловой системе. Использование большого двоичного объекта (BLOB) в базе данных MySQL может снизить быстродействие.
В базе данных будут храниться лишь имена файлов изображений. Дескриптор <IMG SRC> может прямо ссылаться на каталог графических файлов обычным образом.
Когда объем данных велик, важно оптимизировать их хранение. Подобно тому, как эффективность базы данных зависит от правильной индексации, файловая система существенно выигрывает от хорошо продуманной системы каталогов.
Рис. 6.1. Структура каталогов для загрузки файлов
В данном случае файловая система содержит каталоги, представляющие первую букву каждого имени файла. Таким образом, файлы распределены по 26 каталогам. Это существенно ускоряет доступ по сравнению с ситуацией, когда все файлы хранятся в одном каталоге. Важно отметить, что имена файлов должны генерироваться сценарием обработки загрузки таким образом, чтобы гарантировалась их уникальность.
Структура документов. В качестве примеров статей будет использоваться краткий текст новостей, включающий один-два абзаца и единственное изображение. Он предназначен для тех, кто спешит. Подобные документы с заголовком, абзацами и иллюстрацией можно считать структурированными. Чем выше степень структурирования документа, тем проще его разбить на составляющие, хранимые в базе данных. Преимущество такого подхода состоит в возможности единообразного структурированного представления документов.
В качестве примера возьмем статью новостей. Заголовок будет храниться в своем поле отдельно от текста. Изображение по своей природе является отдельным компонентом документа. Поскольку заголовок является отдельным элементом, для его отображения можно задать стандартный шрифт и стиль, а также легко отделить заголовок от остальной части статьи, сформировав главную страницу заголовков.
Для крупных документов можно применить к отдельным абзацам отношение один ко многим. Другими словами, каждый абзац будет храниться в отдельной строке базы данных и иметь связь с идентификатором главного документа. Этот вид динамической структуры документа позволит представлять страницу содержания для каждого документа и отображать каждый раздел независимо либо отображать документ целиком.
Использование метаданных. Уже решено, что запись для каждой статьи содержит заголовок, текст и изображение. Однако ничто не мешает хранить в той же записи другие данные. Система автоматически вставляет значения, описывающие авторство статьи и время ее последней модификации. Они могут автоматически отображаться в нижней части статьи и служить подписью и меткой времени, избавляя автора от необходимости добавления этой информации. Кроме того, иногда полезно добавлять неотображаемые данные, называемые метаданными. Хорошим примером служит хранение ключевых слов, которые будут использоваться в качестве индексов поискового механизма.
Поисковый механизм будет извлекать ключевое слово для каждой статьи и на его основе определять соответствие критериям поиска. Это устраняет необходимость сканирования всего текста каждой статьи. Администратор сайта сможет управлять соответствием ключевых слов и фраз определенным документам.
В нашем примере допускается связывать со статьей любое количество ключевых слов, а также присваивать каждому ключевому слову "весовое" значение, указывающее степень его значимости в диапазоне от 1 до 10.
Затем можно разработать алгоритм поискового механизма, который располагает соответствия статьям согласно человеческой шкале значимости. Это заменит сложный алгоритм, который интерпретирует английский текст и принимает решения в зависимости от своего ограниченного понимания, а также управляется фиксированными правилами.
Метаданные должны храниться в базе данных. Можно использовать HTML-дескриптор <МЕТА> либо даже применять XML для создания документов. Однако лучше при любой возможности воспользоваться преимуществами базы данных для управления документами.
Форматирование вывода. В нашем примере сайта новостей страницы отображаются в простом, но структурированном формате. Каждая страница содержит ряд статей, сформатированных одинаково. Прежде всего, заголовок выводится крупным шрифтом, внизу слева отображает фотография, а справа текст статьи. Страница целиком содержится в стандартном шаблоне страниц, что создает последовательность в оформлении сайта.
<TABLE> |
<TR><TD COLSPAN=2>TOP ВАR</ТD></TR> |
<TR><TD> Side menu </TD><ТD> |
MAIN CONTENT CELL </TD></TR> </TAВLЕ> |
Рис. 6.2. Логическая структура страницы.
Этот вид компоновки крайне популярен сколько сайтов вы регулярно посещаете, где панель меню расположена слева, а ссылки быстрого доступа вверху? При этом средства навигации остаются неизменными независимо от просматриваемой страницы.
Реализация структуры шаблонов, подобной той, что используется для оформления страниц, очень проста. В HTML-коде можно найти дескриптор <TD>, начинающий ячейку главного содержимого. В этом месте HTML-код расщепляется на два файла. Первая половина составляет файл заголовка, а вторая нижний колонтитул. Когда отображается страница, сначала выводится заголовок, затем текст и, наконец, нижний колонтитул.
Реализация сайта с шаблоном заголовка и нижнего колонтитула позволяет модифицировать оформление сайта, изменяя файлы шаблонов.
РНР предоставляет две опции конфигурации, которые удобны в данной ситуации. Можно определить для каждого каталога директивы auto_prepend_file и auto_append_file, которые указывают файлы, подключаемые до или после выполнения любого сценария.
Однако существуют некоторые ограничения. Если существуют сценарии, которые не генерируют вывод и отправляют заголовок переадресации, такой как
<? header("Location: destination.php"); ?>
файлы заголовка и нижнего колонтитула будут отображаться, а переадресация не произойдет, поскольку вывод уже отправлен в Web-браузер. Это также создает проблемы с cookie-наборам и встроенными функциями управления сеансами РНР 4, поскольку cookie не будет правильно функционировать после отправки вывода Web-браузеру.
Управление изображениями. Авторы могут дополнять статьи своими фотографиями. Нам необходимо единообразие оформления, но что произойдет, когда один автор загрузит крупное изображение высокого качества, а другой свернутую в пиктограмму картинку?
Исходя из предположения, что изображения в основном представляют собой фотографии, можно ограничиться лишь форматом JPEG и воспользоваться РНР-функциями для управления графикой.
Напишем небольшой сценарий resize_image.php, который на лету изменяет размер изображений, в результате чего они могут отображаться с помощью дескриптор <IMG SRC>. Сценарий приводится в листинге 6.21.
Листинг 6.21. Сценарий resize_image.php изменяет размеры JPEG-изображения "на лету"
<?
if (!$max_width) $max_width = 150;
if (!$max_height) $max_height = 100;
$size = GetImageSize($image); $width = $size[0]; $height=$size[l];
$x_ratio = $max_width / $width; $y_ratio = $max_height / $height;
if ( ($width <= $max_width) && ($height <= $max_height) )
{ $tn_width = $width; $tn_height = $height; }
else if (($x_ratio * $height) < $max_height)
{ $tn_height = ceil($x_ratio * $height); $tn_width = $max_width; }
else {$tn_width=ceil($y_ratio * $width); $tn_height=$max_height; }
$src = ImageCreateFromJpeg($image);
$dst = ImageCreate($tn_width,$tn_height);
ImageCopyResized($dst, $src, 0, 0, 0, 0,
$tn_width,$tn_height,$width,$height);
header("Content-type: image/jpeg"); ImageJpeg($dst, null, -1);
ImageDestroy($src); ImageDestroy($dst);
?>
Этот сценарий принимает три параметра имя файла изображения, максимальную ширину и высоту в точках. Не стоит полагать, что если указан максимальный размер 200 х 200, изображение будет масштабировано в соответствии с этими значениями. Его масштаб будет уменьшен пропорционально таким образом, чтобы указанные максимальные размеры не превышались. Например, изображение размером 400 х 300 будет уменьшено до размера 200x150. Таким образом достигается сохранение пропорций изображения.
Изменение размера изображения на сервере предпочтительнее, чем простое задание атрибутов HEIGHT и WIDTH в дескрипторе <IMG SRC>. Размер большого изображения с высоким разрешением может составлять несколько мегабайт. Если же картинку уменьшить до приемлемых размеров, ее размер может не превышать 100 Кб. Нет надобности загружать файл большого размера, а затем указывать браузеру изменить размер изображения.
Здесь используется функция ImageCopyResized() для масштабирования изображений на лету. Суть операции изменения размера состоит в вычислении новых параметров ширины и высоты. Определяется соотношение между реальными и максимальными размерами. Параметры $max_width и $max_height могут быть переданы в одной строке запроса. Иначе будут использованы стандартные значения, указанные в верхней части листинга.
$x_ratio = $max_width / $width; $y_ratio = $max_height / $height;
Если изображение уже меньше указанных максимальных размеров, его ширина и высота остаются неизменными. В противном случае будет использован коэффициент X или Y для равного масштабирования обоих размеров, чтобы изображение уменьшенного размера не оказалось растянутым либо сплющенным:
if ( ($width <= $max_width) && ($height <= $max_height) )
{ $tn_width = $width; $tn_height = $height; }
else if (($x_ratio * $height) < $max_height)
{$tn_height = ceil($x_ratio * $height); $tn_width = $max_width; }
else {$tn_width=ceil($y_ratio * $width); $tn_height=$max_height; }
Обзор проекта. В табл. 6.9 содержится перечень файлов данного приложения.
Таблица 6.9 файлы приложения управления содержимым
Имя |
Тип |
Описание |
create_database.sql |
SQL |
SQL-запрос для создания базы данных и образцовых данных |
include_fns.php |
Функции |
Набор подключаемых файлов для данного приложения |
do_fns.php |
Функции |
Набор функций для подключения к базе данных содержимого |
select_fns.php |
Функции |
Набор вспомогательных функций для создания списков SELECT |
user_auth_fns.php |
Функции |
Набор функций для аутентификации пользователей |
header.php |
Шаблон |
Отображается в верхней части каждой страницы содержимого |
footer.php |
Шаблон |
Отображается в нижней части каждой страницы содержимого |
logo.gjf |
Изображение |
Файл логотипа, отображаемого сценарием header. php |
htadlines.php |
Приложение |
Отображает новейший заголовок каждой страницы сайта |
page.php |
Приложение |
Выводит список заголовков и текст для определенной страницы |
resize_image.php |
Приложение |
Изменяет на лету размеры изображений для сценария headlines. php |
search_form.php |
Приложение |
Форма ввода ключевых слов для ведения поиска в содержимом сайта |
search.php |
Приложение |
Отображает заголовки содержимого, соответствующего ключевым словам |
login.php |
Приложение |
Выполняет аутентификацию пароля пользователя и вводит его в систему |
logout.php |
Приложение |
Выводит пользователя из системы |
stories. php |
Приложение |
Перечисляет статьи, написанные вошедшим в систему пользователем. Ему предоставляются опции добавления, модификации и удаления статей |
Имя |
Тип |
Описание |
story.php |
Приложение |
Окно информации о статьях для редактирования, либо добавления новых статей |
story _submit.php |
Приложение |
Добавляет новую статью либо передает изменения на основе данных, введенных в окно story.php |
delete_story.php |
Приложение |
Обрабатывает запрос на удаление статьи, переданный из сценария stories. php |
keywords.php |
Приложение |
Выводит список ключевых слов для статьи с опцией их добавления либо удаления |
keyword_add.php |
Приложение |
Обрабатывает запрос на добавление ключевого слова, переданный из сценария keywords.php |
keyword_delete.php |
Приложение |
Обрабатывает запрос на удаление ключевого слова, переданный из сценария keywords. php |
publish.php |
Приложение |
Список статей для редактора с указанием, какие из них опубликованы, а также опцией переключения статуса каждой из них |
publish_story.php |
Приложение |
Обрабатывает запрос на публикацию, переданный из сценария publish. php |
unpublish_story.php |
Приложение |
Обрабатывает запрос на отмену публикации, переданный из сценария publish. php |
Создание базы данных. Листинг 6. 22 отображает SQL-запросы, используемые для создания базы данных содержимого системы. Этот листинг демонстрирует часть файла create_database.sql. Файл в приложении кроме этого содержит запросы для заполнения базы данных примерами пользователей и статей.
Листинг 6. 22. Фрагмент create_database.sql создание содержимого базы данных
DROP DATABASE IF EXISTS content;
CREATE DATABASE content;
USE content;
DROP TABLE IF EXISTS writers;
CREATE TABLE writers (
username varchar(16) PRIMARY KEY,
password varchar(16) NOT NULL,
full_name text);
DROP TABLE IF EXISTS stories;
CREATE TABLE stories (
id int PRIMARY KEY auto_increment,
writer varchar(16) NOT NULL, # FOREIGN KEY writers.username
page varchar(16) NOT NULL, # FOREIGN KEY pages.code
headline text,
story_text text,
picture text,
created int,
modified int,
published int);
DROP TABLE IF EXISTS pages;
CREATE TABLE pages (
code varchar(16) PRIMARY KEY,
description text);
DROP TABLE IF EXISTS writer_permissions;
CREATE TABLE writer_permissions (
writer varchar(16) NOT NULL, # FOREIGN KEY writers.username
page varchar(16) NOT NULL # FOREIGN KEY pages.code
);
DROP TABLE IF EXISTS keywords;
CREATE TABLE keywords (
story int NOT NULL, # FOREIGN KEY stories.id
keyword varchar(32) NOT NULL,
weight int NOT NULL);
GRANT select, insert, update, delete ON content.*
TO content@localhost identified by 'password';
GRANT all ON content.* to root@localhost;
Необходимо сохранить краткие сведения о каждом авторе, включая входное имя и пароль, в таблице writers. Полные имена авторов будут храниться для вывода после каждой статьи, а также приветствия авторов после входа в систему.
Таблица pages содержит заголовки каждой страницы, в которых будут отображаться статьи. Таблица writer_permissions реализует отношение многие ко многим с указанием, для каких страниц автор может передавать статьи.
Таблица stories содержит отдельные поля для компонентов headline (заголовок), story_text (текст статьи) и picture (изображение), о чем речь шла выше. Поля created (создана), modified (модифицирована) и published (опубликована) имеют целочисленный тип данных и хранят метки времени UNIX соответствующих событий.
Для создания базы данных необходимо выполнить следующую команду:
mysql -u root < create_database.sql
Реализация. Теперь, когда база данных и функция изменения размеров созданы, приступим к построению основной части системы.
Интерфейсная часть. Начнем с обзора сценария headlines.php, показанного в листинге 6. 23. Он выводит первую страницу, которая будет отображаться посетителям сайта. Необходимо отобразить заголовки последних статей из каждой страницы.
Листинг 6. 23. Сценарий headlines.php отображает новейшие заголовки каждой страницы
<?php
include "include_fns.php";
include "header.php";
$conn = db_connect();
$pages_sql = "select * from pages order by code";
$pages_result = mysql_query($pages_sql, $conn);
while ($pages = mysql_fetch_array($pages_result)) {
$story_sql = "select * from stories where page = '$pages[code]'
and published is not null order by published desc";
$story_result = mysql_query($story_sql, $conn);
if (mysql_num_rows($story_result)) {
$story = mysql_fetch_array($story_result);
print "<TABLE BORDER=0 WIDTH=400>";
print "<TR><TD ROWSPAN=2 WIDTH=100>";
if ($story[picture])
print "<IMG SRC=\"resize_image.php?image=$story[picture]\"></TD>";
print "<TD><H3>$pages[description]</H3>";
print $story[headline];
print "</TD></TR>";
print "<TR><TD ALIGN=RIGHT>";
print "<A HREF=\"page.php?page=$pages[code]\">";
print "<FONT SIZE=1>Read more $pages[code] ...</FONT>";
print "</A></TABLE>";
}
}
include "footer.php";
?>
Этот сценарий, как и все общедоступные сценарии, подключает файл header.php в начале и файл footer.php в конце. В результате любой вывод, генерируемый сценарием, отображается в ячейке основного содержимого страницы.
Основную нагрузку несут два запроса базы данных. Первый
select * from pages order by code
извлекает список страниц, содержащихся в базе данных Затем выполняется тело цикла
select * from stories where page = ' $pages [code] '
and published is not null order by published desc
для поиска статей данной страницы в обратном порядке дат публикации.
Рядом с каждым заголовком генерируется ссылка в следующей форме:
<А HREF="page.php?page=l"><FONT SIZE=1>Read more news..</FONT></A>
Рассмотренный выше цикл обеспечивает вывод значения строки запроса $page и имени страницы рядом с соответствующим заголовком. В результате щелчка на ссылке выводится страница, генерируемая сценарием page.php. Она содержит полный список статей определенной страницы. Код сценария page.php приводится в листинге 6. 24.
Листинг 6. 24. Сценарий page.php отображает на странице все опубликованные статьи
<?php
include "include_fns.php";
include "header.php";
$conn = db_connect();
if (!$page) { header("Location: headlines.php"); exit; }
$sql = "select * from stories where page = '$page'
and published is not null order by published desc";
$result = mysql_query($sql, $conn);
while ($story = mysql_fetch_array($result)) {
print "<H2>".$story[headline]."</H2>";
if ($story[picture]) {
$size = getImageSize($story[picture]);
$width = $size[0]; $height = $size[1];
print "<IMG SRC=\"$story[picture]\"HEIGHT=$height WIDTH=$width ALIGN=LEFT>"; }
print $story[story_text];
$w = get_writer_record($story[writer]);
print "<br><FONT SIZE=1>";
print $w[full_name].", ";
print date("M d, H:i", $story[modified]);
print "</FONT>";
}
include "footer.php";
?>
Обратите внимание, что сценарий page.php требует наличия значения у переменной $page, чтобы правильно сформировать первый запрос. Когда сценарий page.php вызывается непосредственно, без строки запроса, первый условный оператор
if (!$раgе) { header("Location: headlines.php"); exit; }
отсылает посетителя обратно к странице заголовков, чтобы отсутствие значения $page не привело к ошибке. Первый запрос
select * from stories where раgе = ' $раgе'
and published is not null order by published desc
выбирает все статьи, опубликованные на указанной странице, располагая их в обратном хронологическом порядке (самые свежие статьи следуют в начале). Внутри каждого цикла загруженное изображение и текст отображаются на экране, затем следует имя автора и дата последнего изменения.
Прикладная часть. Теперь рассмотрим метод добавления статей в систему. Сначала для авторов запускается сценарий stories.php. Этот сценарий после аутентификации автора отображает список написанных им статей и дату публикации и либо опции добавления новой статьи, редактирования или удаления существующей, либо установки ключевых слов поиска.
Эти окна не форматируются внутри файлов заголовка и нижнего колонтитула, хотя такое и возможно. Поскольку данные сценарии применяют только авторы и редактор, в нашем примере использован лишь необходимый объем форматирования для создания работоспособной системы. Код сценария stories.php показан в листинге 6. 25.
Листинг 6. 25. Сценарий stories.php - интерфейс авторов для управления своими статьями
<?php
include "include_fns.php";
session_register("auth_user");
if (!check_auth_user()) {
?>
<FORM ACTION="login.php" METHOD=POST><TABLE BORDER=0>
<TR><TD>Username</TD><TD><INPUT SIZE=16 NAME="username"></TD></TR>
<TR><TD>Password</TD><TD><INPUT SIZE=16 TYPE="PASSWORD"
NAME="password"></TD></TR></TABLE>
<INPUT TYPE=SUBMIT VALUE="Log in"></FORM>
<?php
}
else {
$conn = db_connect();
$w = get_writer_record($auth_user);
print "Welcome, ".$w[full_name];
print " (<A HREF=\"logout.php\">Logout</A>)<p>";
$sql = "select * from stories where writer = '$auth_user'
order by created desc";
$result = mysql_query($sql, $conn);
print "Your stories: ";
print mysql_num_rows($result);
print " (<A HREF=\"story.php\">Add new</A>)<br><br>";
if (mysql_num_rows($result)) {
print "<TABLE><TR><TH>Headline</TH><TH>Page</TH>";
print "<TH>Created</TH><TH>Last modified</TH></TR>";
while ($qry = mysql_fetch_array($result)) {
print "<TR><TD>";
print $qry[headline];
print "</TD><TD>";
print $qry[page];
print "</TD><TD>";
print date("M d, H:i", $qry[created]);
print "</TD><TD>";
print date("M d, H:i", $qry[modified]);
print "</TD><TD>";
if ($qry[published])
print "[Published ".date("M d, H:i", $qry[published])."]";
else {
print "[<A HREF=\"story.php?story=".$qry[id]."\">edit</A>] ";
print"[<A HREF=\"delete_story.php?story=".$qry[id]."\">delete</A>] ";
print"[<A HREF=\"keywords.php?story=".$qry[id]."\">keywords</A>]";
}
print "</TD></TR>";
}
print "</TABLE>";
}
}
?>
На первом этапе проверяется, выполнена ли аутентификация пользователя. Если нет, отображается только форма входной регистрации. После входа автора в систему переменной сеанса $auth_user присваивается значение. Используемая здесь аутентификация не очень надежна. В реальной ситуации необходимо обеспечить, чтобы аутентификация пользователей выполнялась должным образом.
Форма входной регистрации передает данные сценарию login.php, который сравнивает имя пользователя и пароль с соответствующими значениями базы данных. В случае успешности входной регистрации пользователю возвращается предыдущая страница с помощью значения $HTTP_REFERER. Это означает, что сценарий регистрации может вызываться из любой страницы системы. Затем автор приветствуется по имени и предоставляется возможность выхода из системы. Эта ссылка всегда отображается в верхней части страницы stories.php, что позволяет легко выйти из системы в любой момент.
$w = get_writer_record($auth_user);
echo "Welcome, ".$w[full_name];
echo " (<A HREF=\"logout.php\">Logout</A>)";
Функция get_writer_record() описана в библиотеке db_fns.php и возвращает массив полей таблицы автора на основе переданного имени пользователя. Сценарий logout.php просто сбрасывает значение переменной $auth_user.
SQL-запрос выбирает все статьи автора, начиная с добавленных в последнее время:
select * from stories where writer = '$auth_user'
order by created desc
Для каждой записи, связанной со статьей, хранятся метки времени добавления, модификации и публикации. Когда добавляется новая статья, меткам создания и модификации присваивается текущее системное время. Каждое последующее изменение статьи будет вызывать обновление лишь метки модификации.
Вся эта информация выводится в окне статей сначала с помощью кода:
echo date("M d, H:i", $qry[created]);
затем:
echo date("M d, H:i", $qry[modified]);
и, наконец:
if ($qry[published])
echo "[Published ".date("M d, H:i", $qry[published])."]";
else {
echo "[<A HREF=\"story.php?story=".$qry[id]."\">edit</A>]";
echo"[<A HREF=\"delete_story.php?story=".$qry[id]."\"> delete</A> ]";
echo "[<A HREF=\"keywords.php?story=".$qry[id]."\">keywords</A>]";}
Последний фрагмент отображает лишь дату публикации, если она имеет смысл. В противном случае выводятся ссылки для редактирования или удаления статьи, а также установки ключевых слов поискового механизма.
Сценарий ввода или редактирования статьи содержится в файле story.php.
Листинг 6. 26. Сценарий story.php служит для создания или редактирования статьи
<?
include "include_fns.php";
if (isset($story))
$s = get_story_record($story);
?>
<FORM ACTION="story_submit.php" METHOD=POST
ENCTYPE="multipart/form-data">
<INPUT TYPE=HIDDEN NAME="story" VALUE="<?php print $story;?>">
<INPUT TYPE=HIDDEN NAME="destination" VALUE="<? print $HTTP_REFERER;?>">
<TABLE><TR><TD ALIGN=CENTER>Headline<TD></TR>
<TR><TD><INPUT SIZE=80 NAME="headline"
VALUE="<? print $s[headline];?>"></TD>
</TR><TR><TD ALIGN=CENTER>Page<TD></TR><TR>
<TD ALIGN=CENTER><? print query_select("page", "select p.code,
p.description from pages p,
writer_permissions w where p.code=w.page
and w.writer = '$auth_user'", $s[page]); ?>
</TD></TR><TR><TD ALIGN=CENTER>Story text (can contain HTML)</TD>
</TR><TR><TD><TEXTAREA COLS=80 ROWS=7 NAME="story_text"
WRAP=VIRTUAL><?php print $s[story_text];?></TEXTAREA>
</TD></TR><TR><TD ALIGN=CENTER>Or upload HTML file</TD></TR>
<TR><TD ALIGN=CENTER><INPUT TYPE=FILE NAME="html" SIZE=40></TD>
</TR><TR><TD ALIGN=CENTER>Picture</TD></TR>
<TR><TD ALIGN=CENTER><INPUT TYPE=FILE NAME="picture" SIZE=40></TD></TR>
<?php
if ($s[picture]) {
$size = getImageSize($s[picture]);
$width = $size[0];
$height = $size[1];
?>
<TR><TD ALIGN=CENTER><IMG SRC="<?php print $s[picture];?>"
WIDTH=<? print $width;?> HEIGHT=<?php print $height;?>>
</TD></TR>
<?php } ?>
<TR><TD ALIGN=CENTER><INPUT TYPE=SUBMIT VALUE="Submit"></TD></TR>
</TABLE></FORM>
Для добавления статей и их редактирования может использоваться один и тот же сценарий. Выполняемое действие зависит от того, установлено ли значение переменой $story при вызове сценария.
if (isset($story))
$s = get_story_record($story);
Функция get_story_record() описана в db_fns.php. Она возвращает массив всех полей таблицы статей для указанного идентификатора статьи. Если идентификатор не передан, переменная $story имеет значение NULL, а переменная $s не содержит элементов массива.
<INPUT SIZE=80 NAME="headline" VALUE="<? echo $s[headline];?>">
Если значение переменной $story не установлено, предыдущий фрагмент кода не создаст значения из оператора РНР, поэтому поле ввода заголовка будет пустым. Когда значение переменной $story установлено, она содержит текст заголовка для редактируемой статьи.
echo query_select("page", "select p.code, p.description
from pages p, writer_permissions w
where p.code = w.page and w.writer = ' $auth_user'", $s[page]); "
Функция query_select() описана в select_fns.php. Она возвращает HTML-код вывода списка SELECT на основе данного SQL-запроса. Первый параметр представляет собой атрибут NAME для оператора SELECT. SQL-запрос из второго параметра выбирает два столбца, где первый является составляющей VALUE каждой опции, а второй следует после дескриптора OPTION и представляет собой текст, отображаемый в списке. Третий параметр необязателен. Он добавляет атрибут SELECTED к опции, значение которой совпадает с указанным.
<INPUT TYPE=HIDDEN NAME="story" VALUE="<?echo $story;?>">
Здесь создается переменная-заполнитель путем установки нового значения для статьи из переданного в переменной $story. После передачи формы сценарий story_submit.php проверяет, существует ли значение переменной $story, и генерирует в соответствии с этим SQL-оператор UPDATE либо INSERT.
Код сценария story_submit.php показан в листинге 6. 27.
Листинг 6. 27. Сценарий story_submit.php - вставка или обновление статей в базе данных
<?php
include "include_fns.php";
$conn = db_connect();
$time = time();
if ( ($html) && (dirname($html_type) == "text") ) {
$fp = fopen($html, "r");
$story_text = addslashes(fread($fp, filesize($html)));
fclose($fp);
}
if ($story) { // It's an update
$sql = "update stories set headline = '$headline',
story_text = '$story_text', page = '$page',
modified = $time where id = $story";
}
else { // It's a new story
$sql = "insert into stories (headline, story_text, page, writer, created, modified) values ('$headline', '$story_text', '$page', '$auth_user', $time, $time)";
}
$result = mysql_query($sql, $conn);
if (!$result) {
print "There was a database error when executing <PRE>$sql</PRE>";
print mysql_error();
exit;
}
if ( ($picture) && ($picture != "none") ) {
if (!$story) $story = mysql_insert_id();
$type = basename($picture_type);
switch ($type) {
case "jpeg": $filename = "pictures/$story.jpg";
copy ($picture, $filename);
$sql = "update stories set picture='$filename'
where id = $story";
$result = mysql_query($sql, $conn); break;
default: print "Invalid picture format: $picture_type";
}
}
header("Location: $destination");
?>
Ссылка удаления статьи вызывает сценарий delete_story.php. Он просто реализует оператор DELETE и возвращает автора к вызывающей странице. Код сценария delete_story.php показан в листинге 6. 28.
Листинг 6. 28. Сценарий delete story.php удаляет статью из базы данных
<?php
include "include_fns.php";
$conn = db_connect(); $now = time();
$sql = "delete from stories where id = $story";
$result = mysql_query($sql, $conn);
header("Location: $HTTP_REFERER"); ?>
Поиск статей. В результате щелчка на ссылке ключевых слов в списке статей вызывается новая форма для ввода ключевых слов, связанных со статьей. Количество ключевых слов не ограничено. Каждому из них присваивается значение весомости. Более высокие значения соответствуют более важным ключевым словам.
Сценарий keywords.php довольно прост, поэтому мы не будем его подробно рассматривать. Он содержится в приложении. Он запускает сценарии keyword_add.php и keyword_delete.php. Они также просты и здесь не рассматриваются.
Для ввода новых ключевых слов в базу данных сценарий keyword_add.php использует следующий запрос:
insert into keywords (story, keyword, weight)
values ($story, '$keyword', $weight)
Подобным же образом, сценарий keyword_delete.php для удаления ключевого слова использует следующий запрос:
delete from keywords where story = $story and keyword = '$keyword'
Интерес представляет способ использования показателей весомости для вычисления процентного отношения значимости в процессе поиска.
Форма поиска, генерируемая сценарием search_form.php, содержит поле для ключевых слов и передается в сценарий search.php, который запрашивает базу данных статей для поиска соответствующего содержимого. Код сценария search.php показан в листинге 6.29.
Листинг 6. 29. Сценарий search.php - поиск статей по ключевым словам
<?php
include "include_fns.php";
include "header.php";
$conn = db_connect();
if ($keyword) {
$k = split(" ", $keyword); $num_keywords = count($k);
for ($i=0; $i<$num_keywords; $i++) {
if ($i) $k_string .= "or k.keyword = '".$k[$i]."' ";
else $k_string .= "k.keyword = '".$k[$i]."' "; }
$and .= "and ($k_string) ";
}
$sql = "select s.id, s.headline, 10*sum(k.weight)/$num_keywords
as score from stories s, keywords k where s.id = k.story $and
group by s.id, s.headline order by score desc, s.id desc";
$result = mysql_query($sql, $conn);
print "<H2>Search results</H2>";
if (mysql_num_rows($result)) {
print "<TABLE>";
while ($qry = mysql_fetch_array($result)) {
print "<TR><TD>";
print $qry[headline];
print "</TD><TD>";
print floor($qry[score])."%";
print "</TD></TR>";
}
print "</TABLE>";
}
else { print "No matching stories found"; }
include "footer.php";
?>
Сначала строка ключевого слова, передаваемая в сценарий, разбивается на отдельные слова для поиска. В этом примере не используются какие-либо расширенные методы поиска, такие как возможность применения операторов AND и OR, либо объединение слов в фразу.
if ($keyword) {
$k = split(" ", $keyword); $num_keywords = count($k);
for ($i=0; $i<$num_keywords; $i++)
{ if ($i)
$k_string .= "or k.keyword = '".$k[$i]."' ";
else $k_string .= "k.keyword = '".$k[$i]."'";
}
$and .= "and ($k_string)";
}
В этом коде используется РНР-функция split() для создания массива, содержащего каждое слово строки $keyword, разделенное пробелами. Если указано только одно слово, возвращается массив с единственным элементом, и последующий цикл выполняется один раз. В результате условие, хранимое в переменной Sand, будет иметь следующий вид:
and (k.keyword = 'keyword1' or k.keyword = 'keyword2' or
k.keyword = ' keyword3')
Ниже приводится запрос поиска, основанный на рассмотренном ранее коде:
select s.id, s.headline, 10 * sum(k.weight)/$num_keywords as score
from stories s, keywords k
where s.id = k.story and (k.keyword = 'keyword1'
or k.keyword = ' keyword2' or k.keyword = 'keyword3' )
group by s.id, s.headline order by score desc, s.id desc
Общий показатель значимости определяется как сумма весовых значений всех соответствующих ключевых слов, деленная на количество искомых ключевых слов и умноженная на десять. Эта формула пригодна для поисков, когда все введенные ключевые слова соответствуют ключевым словам базы данных.
Поскольку показатели весомости находятся в диапазоне от 1 до 10, максимальное значение общего показателя значимости составляет 100. Эта величина для поиска по трем ключевым словам достигается, когда все три слова найдены и каждое из них имеет весовое значение 10.
Окно редактора. Осталась нерассмотренной одна составляющая проекта, отвечающая за публикацию статьи после ее написания. Это осуществляет сценарий publish.php, показанный в листинге 6. 20.
Листинг 6. 20. Сценарий publish.php - выбор из списка документов публикации
<?php
include "include_fns.php";
$conn = db_connect();
$sql = "select * from stories order by modified desc";
$result = mysql_query($sql, $conn);
print "<H2>Editor admin</H2><TABLE>";
print "<TR><TH>Headline</TH><TH>Last modified</TH></TR>";
while ($story = mysql_fetch_array($result)) {
print "<TR><TD>".$story[headline];
print "</TD><TD>.date("M d, H:i", $story[modified])."</TD><TD>";
if ($story[published]) { print "[<A HREF=\"unpublish_story.php? story=$story[id]\"> unpublish</A>]"; }
else {print "[<A HREF=\"publish_story.php?story=$story[id]\"> publish</A>] ";
print"[<A HREF=\"delete_story.php?story=$story[id]\">delete</A>]";
}
print "[<A HREF=\"story.php?story=$story[id]\">edit</A>] ";
print "</TD></TR>";
}
print "</TABLE>";
?>
Данный сценарий должен быть доступным только для лиц, уполномоченных публиковать статьи на сайте. В нашем приложении это редактор сайта. Однако в целях простоты здесь не реализовано управление доступом. В реальной ситуации сценарий должен быть защищен.
Он очень похож на сценарий stories.php с той разницей, что для редактора отображаются статьи всех авторов, а не только его собственные. Оператор if обеспечивает предоставление для каждой статьи необходимых опций. Публикация статей может отменяться, а неопубликованные статьи могут публиковаться либо удаляться.
Связанные с этим оператором три ссылки вызывают, соответственно, сценарии unpublish_story.php, publish_story.php и delete_story.php. Сценарий publish_story.php использует следующий SQL-запрос:
update stories set published = $now where id = $story
Он помечает статью как опубликованную и делает ее общедоступной для просмотра.
Подобно этому, сценарий unpublish_story.php использует следующий запрос, чтобы пометить статью как неопубликованную и прекратить ее отображение для всеобщего просмотра:
update stories set published = null where id = $story
Ссылка редактирования отображается независимо от факта публикации статьи, поэтому редактор всегда может внести в нее изменения. В этом состоит отличие от уровня доступа автора, который может изменять статью лишь до ее публикации.
?