Включения и пространство имён

Конструкции включений:

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

  1. require()

  2. Конструкция require позволяет включать код до выполнения сценария.

    require имя_файла;
    

    При запуске программы интерпретатор заменит инструкцию на содержимое файла имя_файла (этот файл может также содержать сценарий на PHP).

  3. include

  4. Конструкция include также предназначена для включения файлов в код сценария PHP. В отличие от конструкции require конструкция include позволяет включать файлы в код PHP скрипта во время выполнения сценария.

    include имя_файла;
    
  5. require_once и include_once

  6. Используя конструкции однократного включения 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 исполняет включаемый файл каждый раз, а 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, а затем имя, под которым функция должна стать доступна.