Pull to refresh

PHP Namespace

Reading time6 min
Views111K
Недавно инкапсулировал свой проект в namespace и столкнулся с проблемой отсутствия нормальной документации. Все, что удалось найти датируется примерно 2009 годом, а на дворе почти 2012… В найденном материале куча нерабочих мест, использующих то, что в нынешней версии php нет. В связи с этим хочу немного осветить этот вопрос.
Итак, что же такое Namespace или пространство имен? Великая wikipedia определяет их так:
Пространство имён (англ. namespace) — некоторое множество, под которым подразумевается модель, абстрактное хранилище или окружение, созданное для логической группировки уникальных идентификаторов (то есть имён). Идентификатор, определенный в пространстве имён, ассоциируется с этим пространством. Один и тот же идентификатор может быть независимо определён в нескольких пространствах. Таким образом, значение, связанное с идентификатором, определённым в одном пространстве имён, может иметь (или не иметь) такое же (а скорее, другое) значение, как и такой же идентификатор, определённый в другом пространстве. Языки с поддержкой пространств имён определяют правила, указывающие, к какому пространству имён принадлежит идентификатор (то есть его определение).wiki


Все ясно? На самом деле все просто. До версии 5.3 в php существовало всего два пространства — глобальное(в котором выполнялся ваш основной код) и локальное(в котором определялись переменные функций).
image
С версии 5.3 все изменилось. Теперь можно определить свое пространство имен, в котором будут существовать ваши классы методы и т.д.
image
Надеюсь стало немного понятнее.

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

Для того, чтобы использовать классы определенные в своем пространстве имен, необходимо в нужном месте(я как правило предпочитаю делать это в начале файла) импортировать определенное вами пространство в глобальное для этого используется ключевое слово
use

Внимание: по каким-то своим основаниям php не допускает использование ключевого слова use в блоках условий и циклах


возьмем пример с картинок и воплотим его в коде:
Внимание: ключевое слово namespase должно быть расположено в самом начале файла сразу после <? php

файл A.php

<? php
namespace A
{ 
  class A
  {
    public static function say()
    {
      echo 'Я пространство имен А';
    }
  }
}

файл B.php

<? php
namespace B
{ 
  class A
  {
    public static function say()
    {
      echo 'Я пространство имен B';
    }
  }
}

Возможен альтернативный синтаксис:

<? php
namespace A;
  class A
  {
    public static function say()
    {
      echo 'Я пространство имен А';
    }
  }


Рекомендуется объявлять каждое пространство имен в отдельном файле. Хотя можно и в одном, но это строго не рекомендуется!
Теперь переместимся в третий файл, в котором будет функционировать наш основной скрипт
index.php

<? php
require_once 'A.php';
require_once 'B.php';

use A\A;
use B\A;

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

<? php
require_once 'A.php';
require_once 'B.php';

use A\A;
use B\A;

A\A::say();
B\A::say();

Внимание: использование оператора разрешения области видимости (::) в пространствах имен php не допускается! Единственное для чего он годится — это для обращения к статичным методам класса и константам. Вначале хотели использовать для пространства имен именно его, но затем из-за возникших проблем отказались. Поэтому конструкция вида A::A::say(); недопустима и приведет к ошибке.

Для пространств имен необходимо использовать символ обратного слеша "\"
Внимание: во избежание недоразумений необходимо экранировать данный символ при его использовании в строках: '\\'


Пространства имен можно вкладывать друг в друга, дополним наш файл A.php:

<? php
namespace A
{ 
  class A
  {
    public static function say()
    {
      echo 'Я пространство имен А';
    }
  }
  
}

namespace A\subA
{ 
  class A
  {
    public static function say()
    {
      echo 'Я подпространство имен А';
    }
  }
}

а в индексе напишем следующее:

<? php
require_once 'A.php';
require_once 'B.php';

use A\A as A;
use B\A as B;
use A\subA as sub

A::say();
A::say();
sub::say();


Важным моментом является использование алиасов для импортированных пространств. Можно было написать A\subA::say(); согласитесь, каждый раз писать полные пути к пространствам затруднительно для того, чтобы этого избежать были введены алиасы. При компилировании произойдет следующее вместо алиаса sub будет подставлено A\subA, таким образом мы получим вызов A\subA::say();

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

Для того чтобы не было проблем с автозагрузкой классов из пространств файловую систему нужно организовать аналогично организации пространств. Например, есть у нас корневая папка classes, где и будут храниться наши классы, тогда наши пространства могут быть организованы следующим образом
classes\A\A.php
classes\A\sub\A.php(подпространство sub вынесем в отдельный файл)
classes\B\B.php

В php есть магическая константа __NAMESPACE__ которая содержит имя текущего пространства.

А теперь об автозагрузке.


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



<?php
namespace yourNameSpace
{ 
  class Autoloader
  {
    const debug = 1;
    public function __construct(){}

    public static function autoload($file)
    {
      $file = str_replace('\\', '/', $file);
      $path = $_SERVER['DOCUMENT_ROOT'] . '/classes';
      $filepath = $_SERVER['DOCUMENT_ROOT'] . '/classes/' . $file . '.php';

      if (file_exists($filepath))
      {
        if(Autoloader::debug) Autoloader::StPutFile(('подключили ' .$filepath));
        require_once($filepath);
        
      }
      else
      { 
        $flag = true;
        if(Autoloader::debug) Autoloader::StPutFile(('начинаем рекурсивный поиск'));
        Autoloader::recursive_autoload($file, $path, &$flag);
      }
    }

    public static function recursive_autoload($file, $path, $flag)
    {
      if (FALSE !== ($handle = opendir($path)) && $flag)
      {
        while (FAlSE !== ($dir = readdir($handle)) && $flag)
        {
          
          if (strpos($dir, '.') === FALSE)
          {
            $path2 = $path .'/' . $dir;
            $filepath = $path2 . '/' . $file . '.php';
            if(Autoloader::debug) Autoloader::StPutFile(('ищем файл <b>' .$file .'</b> in ' .$filepath));
            if (file_exists($filepath))
            {
              if(Autoloader::debug) Autoloader::StPutFile(('подключили ' .$filepath ));
              $flag = FALSE;
              require_once($filepath);
              break;
            }
            Autoloader::recursive_autoload($file, $path2, &$flag); 
          }
        }
        closedir($handle);
      }
    }
  
    private static function StPutFile($data)
    {
      $dir = $_SERVER['DOCUMENT_ROOT'] .'/Log/Log.html';
      $file = fopen($dir, 'a');
      flock($file, LOCK_EX);
      fwrite($file, ('║' .$data .'=>' .date('d.m.Y H:i:s') .'<br/>║<br/>' .PHP_EOL));
      flock($file, LOCK_UN);
      fclose ($file);
    }
    
  }
  \spl_autoload_register('yourNameSpace\Autoloader::autoload');
}

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

Теперь наш индекс можно написать так:

<? php
require_once 'Autoloader.php';
use Autoloader as Autoloader;
use A\A as A;
use B\A as B;
use A\subA as sub

A::say();
A::say();
sub::say();

теперь все классы и интерфейсы, которые вы будет использовать будут загружены автоматически.

Для демонстрации некоторых динамических возможностей языка с пространствами объявим еще один класс:
test.php

<? php
namespace mySpace
{
  class test
  {
    __construct()
    {
      //конструктор;
    }

    function sayName($name)
    {
      echo 'Привет ' . $name;
    }
    static function sayOther()
    {
      echo 'статичный вызов';
    }
  }
}


index.php

<? php
require_once 'Autoloader.php';
use Autoloader as Autoloader;

use mySpace\test as test
//можно, например сделать так
$class = 'test';
//приведет к вызову конструктора
$obj = new $class;
$obj->sayName('test');
//а можно так
test\sayName('test2');
//или так
$obj::sayName('test');
//а можно так
test::sayName('test2');


Надеюсь, что моя статья будет полезна кому-нибудь.
Tags:
Hubs:
+32
Comments46

Articles