|
 |
Путь: >
Готовые решения
Готовые решения
Автор: - standov
Дата публикации - 16.08.2005
Просмотров: - 6804
Универсальный автоматический загрузчик классов PHP5
[p]Жили были классы....[/p]
Многие веб-разработчики сталкивались с тем, что при разработке сложных систем, включающих не одну сотню классов, достаточно сложно следить за их своевременным подключением.
Данная проблемма неоднократно пыталась решаться, так создавались библиотекари классов, одни удобнее других.
Среда программирования PHP также не стояла на месте, появившийся недавно пятый релиз привнес полностью переработанную объектную модель, работать с которой стало гараздо приятнее. Старая проблемма стала актуальной как никогда.
Понимая это, разарботчики PHP5 предоставили в наше расспоряжение штатное средство автозагрузки - функцию [php]
__autoload()[
/php]. Однако, как часто бывает со штатными механизмами, она удручала своей простотой.
После достаточно продолжительных размышлений, было принято решение создать новый, современный, автозагрузчик, который позволил-бы, используя одну-две инструкции выполнять все функции.
В качестве основной идеи использовался механизм подключения классов из Java, где классы групируются в пакеты, пакеты имеют иерархическкую структуру и опираются на файловую систему. Аналогичная схема реализована в амбициозном проекте Japha, однако исполнение собственно загрузчика в нем, IMHO, на низком уровне.
После нескольких попыток было решено, что классы должны загружаться исключительно по мере необходимости, в тот момент когда к ним обращается интерпретатор PHP.
Теперь к множеству классов можно обращаться и загружать их в стиле Java.
Например создадим файл A.class.php со следущим содержанием:
[php]
package("package1");
class A {
....
}
[
/php]
И разместим его в каталоге "package1/" относительно скрипта, теперь мы всегда сможем его подгрузить, используя следующий механизм:
[php]
import('package1.A');
[
/php]
Если в каталоге несколько описаний классов, можно использовать следующее:
[php]
import('package1.*');
[
/php]
Если класс нуждается в другом классе из текущего каталога (например наследует его) и этот класс не подключен через import(); - он будет подключен автоматически.
[p]Исходный код, снабженный комментариями[/p]
[php]
<?php
/**
* Загрузчик классов в стиле Java.
*
* @package com.standov.loader
* @version 1.0
* @author Стас Довгодько <admin@standov.com> <{@link url=http://standov.com/}>]http://standov.com/}>/url]
* @link <{@link url=http://standov.com/}>]http://standov.com/}>/url]
* @copyright Copyright (c) 2005 standov.com, all rights reserved
* @license GNU General Public License.
* This program is free software.
* You can redistribute it and/or modify it.
* Either version 2 of the License, or (at your option) any later version.
*/
/**
* Библиотекарь пакетов и классов загрузчика.
* Реализует шаблон Singleton
*
* @package com.standov.loader
* @author Стас Довгодько <admin@standov.com> <{@link url=http://standov.com/}>]http://standov.com/}>/url]
* @link <{@link url=http://standov.com/}>]http://standov.com/}>/url]
* @copyright Copyright (c) 2005 standov.com, all rights reserved
* @license GNU General Public License.
* This program is free software.
* You can redistribute it and/or modify it.
* Either version 2 of the License, or (at your option) any later version.
*/
final class librarian {
/**
* Единственый объект класса, реализация шаблона Singleton
*
* @var librarian
*/
private static $instance;
/**
* Рабочие классы и пакеты
*
* @var string[]
*/
private $packages, $names;
/**
* Стартовый каталог поиска и текущий пакет
*
* @var string
*/
private $classpath, $currentPackage;
/**
* Отключение возможности создания объекта вне его контекста
* Реализация шаблона Singleton
*
* @ignore
*/
private function __construct()
{
// установка стартового катагола поиска
$this->classpath = dirname( __FILE__ );
}
/**
* Отключение возможности клонирования объекта.
* Реализация шаблона Singleton
*
* @ignore
*/
private function __clone() {}
/**
* Обращение к объекту класса.
* Реализация шаблона Singleton
*
* @return librarian
*/
public static function getInstance()
{
if (!self::$instance) self::$instance = new librarian;
return self::$instance;
}
/**
* Включение списка классов (пакета)
*
* @throws librarianException
* @param string $packageQualifiedNamee Квалифицирующее имя пакета
* @return void
*/
public function registerPackage( $packageQualifiedName )
{
// преобразование имени пакета в имя каталога
$qualifiedPackage = str_replace( ".", "/", $packageQualifiedName );
// проход по файлам пакета и регистрация их
if( is_dir( $this->classpath."/".$qualifiedPackage ) )
{
$dirname = realpath($this->classpath.'/'.$qualifiedPackage);
if ($dh = opendir($dirname))
{
while (($entry = readdir($dh)) !== false)
{
if( $entry !== '.' and $entry !== '..' and substr( $entry, -10 ) == ".class.php" )
{
$this->registerClass($packageQualifiedName.'.'.substr( $entry, 0,-10 ));
}
}
closedir($dh);
return;
}
}
throw new librarianException("Specified package (\"$packageQualifiedName\") not found");
}
/**
* Регистрация необходимого класса
*
* @throws librarianException
* @param string $classQualifiedName Квалифицирующее имя класса
* @return string Имя импортированного класса
*/
public function registerClass( $classQualifiedName )
{
// получение имени пакета и имени указанного класса
$package_name = substr($classQualifiedName, 0, $pos = strrpos($classQualifiedName,'.'));
$class_name = substr($classQualifiedName, $pos+1);
// формирование файлового имени класса
$class_filename = $this->classpath.'/'.str_replace( '.', '/', $classQualifiedName ).'.class.php';
// проверка наличия класса
if( file_exists($class_filename ) )
{
// приведение файлового имени к форме, установленной в системе
$class_filename = realpath($class_filename);
// регистрация пакета класса
$this->packages[dirname($class_filename)] = $package_name;
$this->currentPackage = $package_name;
// регистрация файлового имени класса
$this->names[$class_name] = $class_filename;
return $class_name;
} else throw new librarianException("Could not find class in classpath! (<b>".$class_filename."</b>)");
}
/**
* Включение единственного класса
*
* @param string $className Короткое имя класса
* @return string Имя импортированного класса
*/
public function import( $className )
{
// поиск файла класса среди зарегистрированных
if (isset($this->names[$className]))
{
// установка текущего пакета по каталогу класса
librarian::getInstance()->setPackage(librarian::getInstance()->getDirectoryPackage(dirname($this->names[$className])));
// физическое включение файла
require_once ($this->names[$className]);
// проверка объявления класса в указанном файле
if (class_exists($className, false) or interface_exists($className, false))
{
return $className;
} else throw new librarianException("Could not find class \"$className\" in package! (<b>".$this->currentPackage."</b>)");
} else return false;
}
/**
* Установка указанного пакета текущим
*
* @ignore
* @param string $qualifiedPackage Квалифицирующее имя пакета
*/
public function setPackage( $qualifiedPackage )
{
$this->currentPackage = $qualifiedPackage;
}
/**
* Текущий пакет класса
*
* @return string
*/
public function getPackage()
{
return $this->currentPackage;
}
/**
* Пакет зарегистрированного каталога
*
* @param string $directory
* @return string Если каталог не использовался - false
*/
public function getDirectoryPackage($directory)
{
if ($directory = realpath($directory) and isset($this->packages[$directory]))
{
return $this->packages[$directory];
} else return false;
}
}
/**
* Описание исключительных ситуаций загрузчика
*
* @package com.standov.loader
* @author Стас Довгодько <admin@standov.com> <{@link url=http://standov.com/}>]http://standov.com/}>/url]
* @link <{@link url=http://standov.com/}>]http://standov.com/}>/url]
* @copyright Copyright (c) 2005 standov.com, all rights reserved
* @license GNU General Public License.
* This program is free software.
* You can redistribute it and/or modify it.
* Either version 2 of the License, or (at your option) any later version.
*/
class librarianException extends Exception {}
/**
* Установка квалифицирующего имени текущего пакета
* Данная операция необязательна, для включенного класса имя пакета выставляется автоматически
*
* @throws librarianException
* @param string $pkg Квалифицирующее имя пакета
*
* @package com.standov.loader
* @author Стас Довгодько <admin@standov.com> <{@link url=http://standov.com/}>]http://standov.com/}>/url]
* @link <{@link url=http://standov.com/}>]http://standov.com/}>/url]
* @copyright Copyright (c) 2005 standov.com, all rights reserved
* @license GNU General Public License.
* This program is free software.
* You can redistribute it and/or modify it.
* Either version 2 of the License, or (at your option) any later version.
*/
function package( $pkg )
{
// проверка совпадения реального пакета с указанным
if (librarian::getInstance()->getPackage() !== $pkg) {
throw new librarianException("Specified class package not valid");
}
}
/**
* Импорт класса или списка классов по квалифицирующему имени
*
* @throws librarianException
* @param string $name Квалифицирующее имя
* @return string Список импортированных классов
*
* @package com.standov.loader
* @author Стас Довгодько <admin@standov.com> <{@link url=http://standov.com/}>]http://standov.com/}>/url]
* @link <{@link url=http://standov.com/}>]http://standov.com/}>/url]
* @copyright Copyright (c) 2005 standov.com, all rights reserved
* @license GNU General Public License.
* This program is free software.
* You can redistribute it and/or modify it.
* Either version 2 of the License, or (at your option) any later version.
*/
function import( $name ) {
static $imported;
// если класс/пакет уже импортировался - возвращение предыдущего результата
if (isset($imported[$name])) return $imported[$name];
// импортироваться могут как отдельные классы, так и пакеты
// при импорте пакета используется синтаксис [пакет.li]
/li] if (substr($name,-1) === '*')
{
// регистрация пакета
return (boolean)librarian::getInstance()->registerPackage(substr($name,0,-2));
} else {
// регистрация единственного класса
return ($imported[$name] = librarian::getInstance()->registerClass($name));
}
}
/**
* Автозагрузка классов по требованию
* Классы могут загружаться как из текущего пакета, так и из другого
*
* @param string $name Требующийся класс текущего пакета
* @return boolean
*
* @package com.standov.loader
* @author Стас Довгодько <admin@standov.com> <{@link url=http://standov.com/}>]http://standov.com/}>/url]
* @link <{@link url=http://standov.com/}>]http://standov.com/}>/url]
* @copyright Copyright (c) 2005 standov.com, all rights reserved
* @license GNU General Public License.
* This program is free software.
* You can redistribute it and/or modify it.
* Either version 2 of the License, or (at your option) any later version.
*/
function __autoload($name)
{
// к сожалению, автозагрузка не допускает "бросания" исключительных ситуаций
try {
// попытка включения класса из зарегистрированного пакета
if (!librarian::getInstance()->import($name))
{
// класс не зарегистрирован, попытка включения из пакета текущего(вызвавшего) документа
// получение контекстного пакета
$backtrace = debug_backtrace();
$filename = @$backtrace[0]['file'];
// получение имени пакета
if ($package = librarian::getInstance()->getDirectoryPackage(dirname($filename)))
{
librarian::getInstance()->registerClass($package.'.'.$name);
}
librarian::getInstance()->import($name);
}
return true;
} catch (Exception $e) {
return false;
}
}
?>
[
/php]
[p]Пример использования[/p]
После размещения указанного кода в отдельном файле (например loader.php) рассмотрим пример работы с классом.
[b]classes/sample1/B.class.php[/b]
[php]
<?php
// необязательное объявление пакета
// используется для проверки
package('classes.sample1');
// сам класс
class B {
}
?>
[
/php]
[b]classes/sample1/С.class.php[/b]
[php]
<?php
// необязательное объявление пакета
// используется для проверки
package('classes.sample1');
// класс расширяет другой класс из текущего пакета,
// такой класс можно не регистрировать, а можно и зарегистрировать
// возможные варианты:
import('classes.sample1.B');
// или
import('classes.sample1.*');
// или вместе
// сам класс
// включение класса B произойдет только тут
class C extends B {
...
}
?>[
/php]
[b]classes2/sample2/A.class.php[/b]
[php]
<?php
// необязательное объявление пакета
package('classes2.sample2');
// сам класс
class A {
...
}
?>
[
/php]
[b]index.php[/b]
[php]
<?php
// стартовое подключение загрузчика
include_once 'loader.php';
// регистрация классов (включение пока не происходит)
import('classes.sample1.С');
import('classes2.sample2.A');
// обращение к зарегистрированому классу ('classes2.sample2.A')
$a = new A();
// аналогично
// все необходимые ему классы будут загружены автоматически
$c = new C();
?>[
/php]
Вот и все. Успешной работы.
Обсудить в ФОРУМе - комментариев ()
Путь: >
Готовые решения
Если вы заметили орфографическую, стилистическую или другую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
|
|