Включения и пространство имён
Конструкции включений:
Конструкции включений позволяют собирать PHP программу (скрипт) из нескольких отдельных файлов.
require()
include
require_once и include_once
Конструкция require позволяет включать код до выполнения сценария.
require имя_файла;
При запуске программы интерпретатор заменит инструкцию на содержимое файла имя_файла (этот файл может также содержать сценарий на PHP).
Конструкция include также предназначена для включения файлов в код сценария PHP. В отличие от конструкции require конструкция include позволяет включать файлы в код PHP скрипта во время выполнения сценария.
include имя_файла;
Используя конструкции однократного включения require_once и include_once, можно быть уверенным, что один файл не будет включен дважды. Работают конструкции require_once и include_once так же, как и requre и include. Разница в их работе лишь в том, что перед включением файла интерпрететор проверяет, включен ли указанный файл ранее или нет. Если да, то файл не будет включен вновь.
<?php // file: print.php function sayHi() { print_r('Hi!'); } // file: index.php require_once 'print.php'; sayHi(); // => Hi!
Рассмотрим следующий пример
<?php // file: print.php print_r("Hi from print.php!\n"); // file: index.php require_once 'print.php'; print_r("Hi from index.php!\n");
Попробуем запустить:
$ php index.php Hi from print.php! Hi from index.php!
Видно, что сначала выполнился код, находящийся во включаемом файле, и только потом код в файле index.php. Этот механизм рекурсивен по своей природе: если во включаемом файле есть другой включаемый файл, то сначала исполнится он. Такое поведение чем-то похоже на матрешку. Можно сказать, что весь код всех файлов проекта находится в одном пространстве и доступен для использования напрямую.
Из всех четырех конструкций для включения файлов пользоваться имеет смысл только одной — require_once. Она обладает двумя важными свойствами:
- Если файл отсутствует, то эта конструкция приведет к ошибке, и программа остановит свое выполнение;
- Если в require_once передается файл, который уже был где-то загружен ранее, то она не будет выполнять этот файл повторно, но код этого файла все равно станет доступным.
Эти два условия важны, так как код, опирающийся на них, получается более качественным и простым. В отличие от require_once, остальные конструкции не отвечают этим требованиям: require исполняет включаемый файл каждый раз, а include и include_once не приводят к ошибке в ситуации, когда файла не существует.
Сейчас пррграммисты называют такое поведение языка абсолютным злом. Т.к когда создавался PHP болших библиотек ещё не существовало, и такого механизма включения чтобы разбить сложный код на части вполне было достаточно.
Cовременный стандарт разработки на языке PHP полагается на механизм автозагрузки и запрещает использование явного включения файлов. И в реальном коде за включение файлов отвечает Composer — менеджер управления зависимостями, его мы рассмотрим позже.
Пространства имён.
Но всё же механизм включения в PHP существует. И мы его рассмртрим что бы понять как он работает впринципе.
Приложения на PHP состоят из многих тысяч строк кода.
Значительная часть этого кода приходит из библиотек, написанных другими людьми,
и почти наверняка мы не знаем, как они там внутри устроены.
Чем больше библиотек вы используете, чем больше разработчиков в проекте и,
собственно, кода проекта, тем выше вероятность того, что вы начнете натыкаться на ошибку Cannot redeclare
.
Это прризойдёт когда мы попытатемся определить в коде две разные функции,
но с одинаковыми именами:
<?php
// file: index.php
function foo()
{
print_r('one');
}
function foo()
{
print_r('two');
}
$ php index.php
PHP Fatal error: Cannot redeclare foo() (previously declared in /private/var/tmp/index.php:3)
Более того, может оказаться так, что две разные библиотеки, которые вам так нужны в проекте, имеют одинаковые имена функций, а, следовательно, вы физически не сможете их использовать вместе. А включение каждой новой библиотеки в код проекта почти наверняка заставит переименовывать функции самого проекта.
Начиная с PHP версии 5.3 в языке появился механизм пространств имен, в задачу которого входит изоляция кода разных файлов друг от друга. Концепция такого механизма встречается в нашей жизни повсеместно. В файловой системе роль пространств имён выполняют директории, в телефонах - код страны, в адресах - страны, города и улицы.
В силу исторических причин, пространства имён появились позже включений. По этому в языке PHP появились по сути, два независимых механизма, один - включение файлов как таковых, другой - пространства имён.
Перейдём к примеру:
<?php // math.php namespace math; function sum($a, $b) { return $a + $b; }
Пространство имён задается с помощью ключевого слова namespace,
за которым следует имя пространства имён.
По стандарту, один файл должен соответствовать одному пространству имён,
как в примере выше. В случае, когда внутри пространства имён
определяются только функции (а не классы, с которыми мы познакомимся позже),
имя пространства имён должно соответствовать имени файла с учетом регистра,
то есть, для нашего примера имя файла - math.php
, следовательно,
имя пространства имён - math
.
Теперь посмотрим на то, как использовать функции, определенные в пространстве имён:
<?php // index.php require_once 'math.php'; // Обратиться напрямую к функции sum не получится: // PHP Fatal error: Uncaught Error: Call to undefined function sum() sum(3, 2); // А если указать пространство имён, то все работает: \math\sum(5, 8); // 13
Попытка обратиться к функции по имени приведет к ошибке, так как она скрыта за пространством имён. Правильный вызов выглядит так: \<имя пространства имён>\<имя функции>.
Рассмотрим еще один пример, когда функция с одним и тем же именем определена в разных пространствах имён:
<?php // index.php require_once 'text.php'; require_once 'number.php'; // Эта функция повторяет строчку, переданную первым параметром столько раз, сколько указано во втором параметре: \text\multi('hi', 3); // hihihi \number\multi(3, 2); // 6
Два пространства имён имеют одинаковые функции, но, как видно из кода, это не создает неудобств.
Каждая функция вызывается с указанием собственного пространства имен.
Более того, мы можем определить функцию multi
прямо в том месте,
куда производится включение других пространств имён:
<?php // index.php require_once 'text.php'; require_once 'number.php'; function multi($a, $b) { print_r('it works!'); } // Эта функция повторяет строчку, переданную первым параметром столько раз, сколько указано во втором параметре: \text\multi('hi', 3); // hihihi \text\multi('ho', 2); // hoho \number\multi(3, 2); // 6 multi(2, 3); // it works!
Вложенные пространства имён
Файловая структура практически любого проекта на PHP выглядит так:
src/ tests/ composer.json composer.lock .git README.md
Директория src предназначена для хранения исходного кода программы (сайта). Внутри src могут находиться другие директории с PHP файлами внутри них:
my-site/ src/ Formatters/ Pretty.php Generator.php
Как же отразить файловую структуру на пространство имён?
Если посмотреть на другие языки, например, python или java, то там структура пакетов "намертво" связана с файловой структурой. В PHP это правило задано на уровне соглашений. По стандарту структура пространств имён должна один в один соответствовать файловой структуре.
Если взять файл Pretty.php
, то его пространство имён следовало бы назвать
FormattersPretty, что отражает вложенность Formatters/Pretty.
Но можно сделать еще лучше — использовать вложенные пространства имён:
<?php namespace Formatters\Pretty; function render($data) { // some code }
Возможность вкладывать пространства имён друг в друга позволяет думать о пространствах имен как о файловой структуре, где пространства имён — это директории, а функции — это файлы. Кроме вложенности, такие пространства имён ничем не отличаются от обычных пространств имён:
<?php namespace Generator; function generate($data) { return \Formatters\Pretty\render($data); }
Описанная выше схема именования пространств помогает избавиться
от большого числа проблем, связанных с коллизиями.
Но все же этого недостаточно.
Дело в том, что теперь сами пространства становятся уникальными и не должны
пересекаться. А пространство с именем Generator,
скорее всего, будет периодически встречаться в разных библиотеках
(так как это слишком общее слово). Поэтому каждый проект или пакет принято помещать в одно
общее пространство и не загрязнять глобальное пространство множеством пространств имён.
Это название выбирается на основе названия самого проекта —
той директории, внутри которой лежит src
.
В нашей структуре директорий это my-site.
Это значит,
что общим пространством для всех файлов внутри src
будет MySite
:
<?php // src/Formatters/Pretty.php namespace MySite\Formatters\Pretty; // some code <?php // src/Generator.php namespace MySite\Generator; // some code
Импорт функций
Теперь снова вернёмся к предыдущему примеру:
<?php namespace Generator; function generate($data) { return \Formatters\Pretty\render($data); }
А если понадобится вызвать эту функцию много раз? В глазах быстро начнёт рябить от обратных слешей. Для решения этой задачи придумали механизм импорта. С его помощью можно "импортировать" функцию в текущее пространство имён так, как будто она определена прямо здесь:
<?php namespace Generator; use function Formatters\Pretty\render; function generate($data) { return render($data); }
Импорт функции, выполняется оператором use, за которым идёт ключевое слово function и затем полный путь до функции с указанием всех его пространств имён без ведущего обратно слеша. Количество импортов ничем не ограничено. Их используют и добавляют исключительно по соображениям удобства:
<?php namespace PhpPairs\Lists; use function PhpPairs\Pairs\cons; use function PhpPairs\Pairs\car; use function PhpPairs\Pairs\cdr; use function PhpPairs\Pairs\toString;
Если в текущем пространстве определена функция с таким именем, либо функция с таким именем была импортирована ранее из другого пространства имён. Сделать это можно через алиасы (псевдонимы) — механизм, позволяющий переименовывать импортируемые функции.
<?php namespace Generator; use function Formatters\Pretty\render; use function Formatters\Simple\render as simpleRender; function generate($data) { return simpleRender($data); }
Для переименования достаточно в конце импорта добавить ключевое слово as, а затем имя, под которым функция должна стать доступна.