Главное меню

Многократное использование кода

В этой главе подробно объясняется, как повторное использование кода способствует созданию более понятных, надежных и удобных для сопровождения программ, причем с меньшими затратами. Покажем принципы разбиения на модули и повторного использования кода, начиная с простых операторов require() и include(), которые позволяют использовать один и тот же код на нескольких страницах. Мы покажем, в чем эти функции превосходят серверные включения. Предложенные примеры продемонстрируют использование включаемых файлов для обеспечения однотипного внешнего вида всего сайта. Мы также поясним, как писать и вызывать ваши собственные функции на примере функций генерации страниц и форм.

В этой главе рассматриваются следующие темы:

• Преимущества многократного использования кода.

• Использование функций require() и include().

• Начальные сведения о функциях.

• Определение функций.

• Параметры функций.

• Область действия переменных.

• Возврат значений.

• Передача по ссылке и передача по значению.

• Реализация рекурсии.

• Пространства имен.

Преимущества многократного использования кода

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

Затраты

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

Надежность

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

Единообразие

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

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

Использование операторов require() и include()

РНР предоставляет в распоряжение программиста два простых, но очень полезных оператора, которые обеспечивают повторное использование любого типа кода. Посредством операторов require() и include() можно загрузить файл в РНР-сценарий. Файл может содержать всё, что вы обычно включаете в сценарий, в том числе РНР-операторы, текст, HTML-дескрипторы, РНР-функции или РНР-классы.

Эти операторы работают аналогично серверным включениям (server-side include – SSI), которые поддерживаются многими Web-серверами, а также операторам #include в языке С и C++.

Функции require() и include() почти эквивалентны. Единственное различие между ними состоит в том, что при неудачном выполнении require() генерирует фатальную ошибку, a include() – лишь предупреждение.

Имеются две модификации require() и include(), которые называются require_once() и include_once() соответственно. Из их названий ("once" – "однажды") понятно, что они предназначены для однократного включения файла. Но из известных примеров применения – верхних и нижних колонтитулов – их польза не очевидна.

Однако функции require_once() и include_once() удобны для включения библиотек функций. Эти конструкции защищают от случайного повторного включения той же библиотеки, когда переопределение функций может привести к ошибке. Но при достаточно аккуратном кодировании лучше применять require() и include(), т.к. они выполняются быстрее.

Расширения имен файлов и require()

В файле с именем reusable.php хранится следующий код:

<?php

echo 'А это очень простой РНР-оператор.<br />';

?>

А в файле с именем main.php содержится такой код:

<?php

echo 'Это главный файл.<br />';

require('reusable.php');

echo 'Сейчас сценарий завершится.<br />';

?>

Когда вы загружаете файл гeusable.php, вас вряд ли удивит, что в окне браузера отображается текст "А это очень простой PHP-оператор". Однако при загрузке файла main.php происходит нечто более интересное. Вывод этого сценария показан на рис. 5.1.

Многократное использование кода

Чтобы можно было использовать оператор require(), необходим файл. В предыдущем примере использовался файл reusable.php. При выполнении этого сценария оператор

require('reusable.php');

заменяется содержимым запрошенного файла, после чего сценарий выполняется.

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

<?php

echo 'Это главный файл.<br />';

echo 'А это очень простой РНР-оператор.<br />' ;

echo 'Сейчас сценарий завершится.<br />';

?>

При использовании оператора require() следует обратить внимание на различия в обработке расширений имен файлов и РНР-дескрипторов.

РНР игнорирует расширение имени запрашиваемого файла. Это означает, что файл можно называть как угодно, если только вы не собираетесь вызывать его непосредственно. При использовании оператора require() для загрузки файла, он фактически становится частью PHP-файла и выполняется в его составе.

Обычно PHP-операторы не выполняются, если они находятся в файле с именем, например, page.html. Как правило, РНР вызывается только для анализа файлов с определенными расширениями, такими как, например, .php. (Это можно изменить с помощью конфигурационного файла веб-сервера.) Однако если загрузить файл page.html с помощью оператора require(), любые хранящиеся внутри него РНР-операторы будут обработаны. Следовательно, для включаемых файлов можно выбирать любые расширения, однако имеет смысл придерживаться разумного соглашения вроде .inc или .php.

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

В рассматриваемом примере повторно используемый файл (reusable.php) содержит текст:

<?php

echo 'А это очень простой РНР-оператор.<br />';

?>

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

Если не открывать РНР-дескриптор, РНР будет рассматривать этот код просто как текст или HTML-код и выполнять его не будет.

Использование оператора require() для шаблонов веб-сайта

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

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

Многократное использование кода

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

Непосредственное многократное использование разделов HTML, общих для всех страниц, представляет собой значительно более рациональный подход, нежели вырезание и вставка, выполняемая в десятках, сотнях или даже тысячах страниц. Исходный код домашней страницы (home.html), показанной на рис. 5.2, приведен в листинге 5.1.

Многократное использование кода

Как видно из листинга 5.1, в этом файле имеется несколько отдельных разделов кода. HTML-заголовок содержит CSS-определения (Cascading Style Sheet – каскадные таблицы стилей), применяемые на странице. Раздел, озаглавленный как "верхний колонтитул страницы", выводит название компании и ее логотип, раздел "меню" создает линейку навигационного меню, а раздел "содержимое страницы" содержит текст, уникальный для данной страницы. Под ними расположен раздел "нижний колонтитул страницы". Мы можем успешно разделить этот файл на части и присвоить соответствующим частям имена header.inc, home.php и footer.inc.

Файлы header.inc и footer.inc содержат код, который будет повторно использоваться на других страницах.

Файл home.php служит заменой для файла home.html и содержит уникальное содержимое страницы и два оператора require(), как показано в листинге 5.2.

Многократное использование кода

Операторы require() в файле home.php загружают файлы header.php и footer.php.

Как уже отмечалось ранее, имена, присвоенные этим файлам, не влияют на способ их обработки при вызове с помощью оператора require(). Часто такие частичные файлы, которые предназначены для включения в другие файлы, называют наподобие что-то.inc (в данном случае "inc" означает "include" – "включаемый"). Однако мы не рекомендуем такую стратегию, поскольку .inc-файлы не интерпретируются как РНР-код, если только веб-сервер не настроен специальным образом.

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

Файл header.php содержит CSS-определения, используемые страницей, и таблицы, которые отображают название компании и навигационное меню (см. листинг 5.3).

Многократное использование кода

Файл footer.php содержит таблицу, которая выводит нижний колонтитул в нижней части каждой страницы. Этот файл показан в листинге 5.4.

Многократное использование кода

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

<?php require('header.php'); ?>

Здесь находится содержимое новой страницы

<?php require('footer.php'); ?>

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

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

Если требуется, чтобы файл трактовался как простой текст или HTML, а РНР-код не выполнялся, следует использовать функцию readfile(). Эта функция выводит содержимое файла без какого-либо анализа. Подобный подход может существенно повысить безопасность при обработке текста, введенного пользователем.

Использование конфигурационных параметров auto_prepend_file и auto_append_file

Если вы не хотите использовать оператор require() или include() для включения верхнего и нижнего колонтитулов в каждую страницу, можно решить эту задачу другим способом. В файле php.ini имеются два конфигурационных параметра: auto_prepend_file и auto_append_file. Указав с их помощью файлы верхнего и нижнего колонтитулов, можно обеспечить загрузку этих файлов, соответственно, перед и после каждой страницы. Файлы, включаемые с использованием данных директив, ведут себя так, как будто они включены с помощью оператора include(); т.е., если файл не существует, выдается предупреждение.

В Windows настройки будут выглядеть примерно так:

auto_prepend_file = "С:/Apache/include/header.php"

auto_append_file = "C:/Apache/include/footer.php"

А в UNIX так:

auto_prepend_file = "/home/username/include/header.php"

auto_append_file = "/home/username/include/footer.php"

В случае использования этих директив отпадает необходимость вводить операторы include(), однако верхние и нижние колонтитулы будут выводиться на страницах всегда.

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

php_value auto_prepend_file "/home/username/include/header.php"

php_value auto_append_file "/home/username/include/footer.php"

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

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

Использование функций в РНР

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

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

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

Вызов функций

Следующая строка есть простейшее обращение к функции:

function_name();

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

Множество функций вызываются именно таким образом. Функция phpinfo() часто оказывается полезной во время тестирования, поскольку она показывает, какая версия РНР установлена, сообщает информацию о РНР, параметры настройки вебсервера, а также значения различных переменных РНР и сервера. Эта функция не принимает никаких параметров, а мы обычно игнорируем значение, которое она возвращает; поэтому вызов phpinfo() будет иметь следующий вид:

phpinfo();

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

function_name('параметр');

В этом случае параметром является строка, содержащая слово параметр; следующие вызовы также являются допустимыми обращениями к функции (в зависимости от ожидаемого типа параметра):

function_name(2);

function_name(7.993);

function_name($variable);

В последней строке переменная $variable может быть РНР-переменной любого типа, в том числе и массивом или объектом.

Параметр может быть данными любого типа, но конкретные функции обычно требуют передачи конкретных типов данных.

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

Вот, например, как выглядит прототип функции fopen():

resource fopen(string filename, string mode

[, bool use_include_path [, resource context ]]);

Прототип представляет собой описание функции, и очень важно, чтобы вы умели правильно интерпретировать его спецификации. В данном случае слово resource перед именем функции указывает, что эта функция возвращает некоторый ресурс (здесь дескриптор открытого файла). Параметры функции заключаются в круглые скобки. В случае функции fopen() в прототипе указаны четыре параметра. Параметры filename и mode являются строками, параметр use_include_path – логическим значением, а параметр context – ресурсом. Квадратные скобки вокруг use_include_path и context показывают, что эти параметры являются необязательными. Для необязательных параметров можно либо передавать значения, либо игнорировать; в таких случаях используется значение, определенное по умолчанию. Однако следует отметить, что если функция принимает более одного необязательного параметра, опускать можно только самые правые из них. Например, в случае fopen() опустить можно либо context, либо use_include_path и context, но нельзя оставить context и опустить use_include_path.

После ознакомления с прототипом этой функции становится понятно, что в приведенном ниже фрагменте кода находится допустимый вызов fopen():

$name = 'myfile.txt';

$openmode = ' r ';

$fp = fopen($name, $openmode)

Этот код вызывает функцию с именем fopen(). Возвращаемое функцией значение будет сохранено в переменной $fp. В данном примере в функцию передается переменная $name, которая содержит строку с именем открываемого файла, и переменная $openmode, которая содержит строку, указывающую режим для этого файла. Здесь мы не передаем функции необязательные третий и четвертый параметры.

Вызов неопределенной функции

При попытке вызвать несуществующую функцию вы получите сообщение об ошибке, как показано на рис. 5.3.

Многократное использование кода

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

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

1. Правильно ли указано имя функции?

2. Существует ли указанная в сообщении функция в используемой версии РНР?

Не всегда легко запомнить, как правильно пишется название функции. Например, некоторые имена функций, состоящие из двух слов, содержат символ подчеркивания между словами, а некоторые – нет. Так, в имени функции stripslashes() два слова слиты в одно, в то время как в имени функции strip_tags() они разделены символом подчеркивания. Неправильный ввод имени функции в вызове приводит к ошибке (см. рис. 5.3).

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

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

Регистр символов и имена функций

Обратите внимание, что имена функций не чувствительны к регистру, поэтому любое из обращений function_name(), Function_Name() или FUNCTION_NAME() является допустимым и приводит к одному и тому же результату. Прописные буквы можно использовать в имени функции любым образом, который, по вашему мнению, облегчает чтение, но при этом все же следует стремиться к какому-то единообразию. В нашем материале и в большей части документов по РНР принято применять строчные буквы.

Важно отметить, что имена функций ведут себя иначе, чем имена переменных. Имена переменных чувствительны к регистру, и поэтому $Name и $name – это разные переменные, тогда как Name() и name() – одна и та же функция.