Пользователь
1,6
рейтинг
16 ноября 2011 в 15:17

Разработка → PHP Namespace из песочницы

PHP*
Недавно инкапсулировал свой проект в 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');


Надеюсь, что моя статья будет полезна кому-нибудь.
Макс @Slavenin999
карма
24,0
рейтинг 1,6
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (44)

  • +8
    >… и столкнулся с проблемой отсутствия нормальной документации. Все, что удалось найти датируется примерно 2009 годом, а на дворе почти 2012

    ru2.php.net/namespaceчем не угодил?
    • +2
    • 0
      как видно из даты обновления официальной документации это 11.11.11, а статья писалась примерно 10.11.11, долго была на модерации.
      • +2
        11.11.11 была выложена русская документация. Причем namespace еще не переведены.
        Англоязычная документация по неймспейсам доступна с выпуска php 5.3…
      • 0
        Вот именно что дата обновления, а не добавления

        svn log svn.php.net/repository/phpdoc/en/trunk/language/namespaces.xml --limit=10

        То что написано в вашей статье уже длительное время в документации.
        • 0
          не буду с вами спорить, но на тот момент когда я искал информацию, первым делом я смотрел в оф доки, там информация была устаревшей.
          • +2
            Вы не спорите… но спорите, это как? =)
            Вот ревизия файла 2008 года, когда была альфа-версия php 5.3.
            Эта документация была опубликована… но вы не спорите, не спорите…
            • 0
              Пардон, я имею ввиду, что ничего там устаревшего нет, т.к. функционал не менялся.
  • +4
    Конечно же ru2.php.net/namespace, извиняюсь
  • +4
    Непонятно зачем вы везде пишете use A as A, оно и так будет работать. Use имеет смысл только с составными неймспейсами (use A\B\C as someNS)

    > Все, что удалось найти датируется примерно 2009 годом

    Видимо вы плохо искали. Документация на официальном сайте достаточно актуальна. ru.php.net/namespace — Last updated: Fri, 11 Nov 2011
    • 0
      А, наверно парсер съел, должно быть так: use A\A as A; — поправьте
      • 0
        use A\A as A

        эквивалентно
        use A\A


        Лучше
        use ParentNS\ChildNS as SomeNameNS
        • 0
          Да, я имел ввиду use A; use B;
  • +4
    Зачем писать то, что есть в документации? Если б тут хотя бы какой-то финт ушами был показан. 5.3 достаточно давно вышел, чтобы изучить namespace в php.
    • +1
      Уже 5.4 скоро зарелизится, теперь про его финты ушами надо думать, а про 5.3 по-моему уже большинство финтов известны :)
      • +2
        Я тоже так думаю. Но человеку минусов не ставил. Всё-таки старался, писал.
        • 0
          Благодарю.
  • +2
    A\subA\::say();

    Вы уверены насчет последнего слеша?=)

    Насчет не нашли материалов — официальная документация, хоть и на английском (пока что) хорошо освещает вопрос namespace. (мое субъективноличное мнение)

    use Autoloader as Autoloader;
    use A as A;
    use B as B;

    Зачем здесь использовать алиасы?

    И насчет автозагрузки классов: тут на вкус и цвет. Но просто для общего образования можно взглянуть на " PHP Standards Recommendation #0" или PSR-0
    Это набор рекомендаций.

    • 0
      ошибку поправил.
      По приведенной вами ссылке отсутствует класс умеющий рекурсивно искать необходимые файлы
      • 0
        По приведенной мной ссылке находится рекомендации по организации файловой системы для автолоада и трансляции имен классов в нее.
        Никакого готового класса там и не должно было быть =)
      • 0
        Между прочим, по ссылке дело говорится, которое позволяет классы из зенда и симфони классы дёргать.
        • 0
          А что помешает приведенному выше классу «дернуть» класс из зенда или симфони? укажите ему нужную корневую паку и все.
  • +10
    До версии 5.3 в php существовало всего два пространства — глобальное(в котором выполнялся ваш основной код) и локальное(в котором определялись переменные функций).

    Мне кажется, вы путаете пространства имен и области видимости.
  • +4
    Расскажу, пожалуй, про константы в NS.
    Если используете константу в пространстве имён то интерпретатор с начало ищет в текущем пространстве имён, если не находит то берёт из корневого пространства имён (на на самом деле там просто ссылка на коренную константу по умолчанию, никакого двойного поиска там нет). Это свойство я использую для частичного дебага (не всего кода, а из нужных пространств имён). Так, например, в коде у меня расставлены

    DEBUG && Log::debug("some debug data");
    


    благодаря этому, определяя DEBUG в нужных мне пространствах имён (дешево и сердито), я могу получить дебаг-информацию из нужных NS.
    Насчет define(). там нужно указывать полное имя константы с пространством имён:
    define('MyNS\DEBUG', 1);
    


    Аналогично работают и функции, но их свойство я не особо использую.
  • 0
    Америку не открыли… Все это я читал в книге Мэтта Зандстры
    • 0
      на это и не претендовал
  • +3
    Почти 2012 год на дворе и php 5.3 а Вы позволяете себе такое, как будто из 4-ки:
    • +4
      Сорри, рано.
           function sayName($name)
               ...
           static function sayOther()
               ...
      

      Где public/protected/private?
      • –2
        согласен, обязательно исправлюсь, хотя даже С++ не настаивает на указании модификатора доступа public, все функции и так подразумеваются как public. В protected нет необходимости так как отсутсвует наследование.
        • +1
          Всё дело в том, что нестатический метод, объявленный без модификатора может быть выхван как статически, так и в контексте объекта. Это нерационально и удручает если сделано так намеренно и один метод вызывается как class::method() и $class->method().
        • +1
          Есть мнение, что в средних и больших проектах, которые будут жить и поддерживаться длительное время хорошей практикой считается политика «скрывать всё по-умолчанию». Т.е. по умолчанию — всё private (в очень больших — ещё и final — чтобы меньше «думать»). Если есть какие-то явные основания то protected или public. В языках, где namespace не только пространство имён но и один из инструментов для инкапсуляции — и классы по умолчанию хорошо бы делать package-private (доступные только классам из того же namespace)
          • 0
            package-private — в PHP можно разве? Сцылку почитать неплохо бы.

            А вообще, зависит не от величины проекта использование private и других ключевых слов. Очень помогает рефакторить потом, когда проект меняется.
            • 0
              нет — в php пока нельзя. я говорил о других языках.

              По поводу вашего утверждения: в «маленьком» проекте, где каждый из нескольких участников может полностью владеть кодом это не так важно как в большом проекте, где каждый занимается своей частью и дальше контрактов компонентов не может видеть. А т.к. поддержание любого порядка требует дополнительных ресурсов — то в маленьком проекте от некоторых практик (например нестрого соблюдать правило Деметры) можно «контролируемо» отказаться. Это может не иметь фатальных последствий, при этом немного сэкономить времени в ситуации когда это очень важно.
              • 0
                Ну если говорить конкретно про private/protected/public, то это в любом коде должно быть, независимо от размеров проекта. Это стиль кодирования, а не поддержание порядка и я ни за что от этого не откажусь.
        • +1
          ошибаетесь, товарищ! В C++ без указания доступа, всё пихается в private. А вот struct{} это то, о чём вы говорите; просто struct это алиас для class:
          class 
          {
          public: 
          //your code here
          }
          • 0
            а ведь действительно, что-то меня переклинило…
  • 0
    когда же как в java будут введены пакетные области видимости? очень нехватает.
  • +1
    благодарю всех за комментарии и критику. В будущих статьях постараюсь учесть все, что было сказано выше. Ну а относительно того, что все это есть в документации, так много статей, которые являются просто ее переводом.
  • 0
    Вот еще совсем не новая статейка на русском:
    www.vr-online.ru/content/kak-ispolzovat-namespace-v-php-glubokoe-pogruzhenie-313

    Все лаконично и понятно.
  • 0
    интересно как обходятся глобальные переменные типа $_SERVER в cli mode
  • 0
    Общими усилиями подготовили перевод раздела Namespaces в оф. документации:
    docs.php.net/manual/ru/language.namespaces.php.
  • 0
    Вопрос: допустим, у меня есть 10 файлов с различными классами и все они в одном неймспейсе (движок моего приложения):
    a.php: namespace myns; class A {...}
    b.php: namespace myns; class A {...}

    h.php: namespace myns; class H {...}

    и наконец у меня есть само приложение (index.php) в котором я хочу использовать все классы движка. Мне НЕ хочется везде писать имя неймспейса myns: new myns\A(); new myns\B(); myns\C::foo();…

    Я могу в начале index.php написать 10 инструкций use myns\A, myns\B, myns\C,… и тогда использовать имена классов не добавляя к ним неймспейс — это понятно.

    А могу ли я как-то загрузить ВСЕ классы сразу? Что-то вроде use myns\* существует? (звёздочку пробовал — syntax error).

    В документации есть намёки на это. Такой пример:
    use My\Full\NSname;
    (http://www.php.net/manual/en/language.namespaces.importing.php)
    «NSname» как бы намекает, что они импортировали не конкретный класс а целиком какой-то неймспейс NSName, однако у меня это никак не получается!
    • 0
      Ой, не туда
  • –1
    Автору спасибо за старательное изложение

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.