Регулярно на PHP форумах встречается вопрос, как вынести часть методов класса в отдельный файл, и если у вас тоже возникал такой вопрос, значит что-то не так в вашем коде и пора что-то менять. Правильным решением будет логически сгруппировать методы в отдельные классы и, если требуется получить все методы в одном экземпляре класса, выполнить каскадное наследование:
class Base{ /* */ }
class Foo extends Base{ /* */ }
class Bar extends Foo { /* */ }
$obj = new Bar();
Но если вы решили, что это все таки необходимо — у меня есть для вас решение.
Чтобы достигнуть постановлений цели нам понадобится немного магии а именно магический метод __call, который будет вызван при попытке доступа к методу недоступному в контексте объекта, функцию call_user_func_array для вызова пользовательской функции с массивом параметров, а так-же, как вспомогательный инструмент, класс ReflectionClass, его мы будем использовать для получения некоторой информации о классе, ну и еще несколько стандартных функций.
Алгоритм решения прост, когда происходит обращение к недоступному методу объекта, срабатывает магический метод __call, в который передаются имя метода и параметры, нам на основе этой информации остается подключить файл с нужной функцией и вызвать её. Файл MyClass.php:
class MyClass{
public function __call( $method, $params = array() )
{
// Получение имени класса
$class_name = get_class( $this );
// Имя внешнего метода состоит из имени класса и имени метода
$external_method_name = 'calss_' . $class_name . '_method_' . $method;
// Проверяем, возможно функция ( внешний метод класса) уже есть, если нет,
// пробуем подключить файл с функцией
if( ! function_exists($external_method_name) ) {
// Ожидается, что файл расположен рядом с файлом класса, местоположение
// которого определим с помощью ReflectionClass
$r = new ReflectionClass($class_name);
$fn = dirname( $r->getFileName() ) . '/' . $external_method_name . '.php';
// Если файл существует, подключаем его
if( file_exists( $fn ) ) {
require_once( $fn );
}
}
// Проверяем, существует ли функция ( внешний метод класса)
if( function_exists($external_method_name) ) {
// В качестве первого параметра добавим текущий объект $this
array_unshift( $params, $this );
// Вызываем функцию и возвращаем результат
return call_user_func_array( $external_method_name, $params );
} else {
// Функции не существует. Генерируем исключение
trigger_error('Call to undefined method '.$class_name.'::'.$method.'()', E_USER_ERROR );
}
}
Внешний метод класса, представляет собой функцию, в качестве первого параметра которой передается сам объект. Файл calss_MyClass_method_hello.php:
function calss_MyClass_method_hello( $that )
{
echo( 'Hello World' );
}
Теперь можно проверить:
require( 'MyClass.php' );
$object= new MyClass();
$object->hello();
Данное решение рабочее, но имеет недостаток, мы не можем из внешнего метода, обращаться к свойствам и методам помеченным, как private. Эту проблему можно обойти расширив метод __call для приватных методов ( только для php >= 5.3 ) и добавив пару магических методов __get и __set для приватных свойств. Магические методы __get и __set вызываются при попытке доступа к свойствам недоступных в контексте объекта. Следует учитывать что приватные методы и свойства должны быть доступны только для внешних методов класса и не должны быть доступны из других мест, с этим нам поможет справиться функция debug_backtrace. Еще к недостаткам данного метода можно отнести и то, что внешние методы класса нельзя сделать приватными.
Далее я покажу, как расшарить приватные свойства для внешнего метода.
Добавим пару приватных свойств в класс:
private $_message = 'Hello';
private $_external_methods = array(); // Будем хранить имена функций, для которых разрешен доступ
Изменим немного метод __call, а именно секцию второй проверки существования функции:
// Проверяем, существует ли функция ( внешний метод класса)
if( function_exists($external_method_name) ) {
// Запомним имя функции, как собственный метод
$this->_external_methods[ $external_method_name ] = TRUE;
// В качестве первого параметра добавим текущий объект $this
array_unshift( $params, $this );
// Вызываем функцию и возвращаем результат
return call_user_func_array( $external_method_name, $params );
} else {
// Функции не существует. Генерируем исключение
trigger_error('Call to undefined method '.$class_name.'::'.$method.'()', E_USER_ERROR );
}
Получение значения приватного свойства:
public function __get( $var )
{
// Получение имени класса
$class_name = get_class( $this );
// Получим информацию о месте вызова метода __get
$bt = next( debug_backtrace() );
// Проверим является ли функция внешним членом класса
if( $var !== '_external_methods' && count($bt) && in_array( $bt['function'], $this->_external_methods )) {
// Проверяем существует ли запрошенное свойство среди свойств класса
if( array_key_exists( $var, get_class_vars( $class_name ) )){
// Ворачиваем результат
return $this->{$var};
} else {
// Совйство не найдено. Генерируем исключение
trigger_error('Undefined property '.$class_name.'::$'.$var.'');
}
} else {
// Доступ запрещен. Генерируем исключение
trigger_error('Cannot access private property '.$class_name.'::$'.$var.'', E_USER_ERROR);
}
}
Запись свойства:
public function __set( $var, $val )
{
// Получение имени класса
$class_name = get_class( $this );
// Получим информацию о месте вызова метода __set
$bt = next( debug_backtrace() );
// Проверим является ли функция внешним членом класса
if( $var !== '_external_methods' && count($bt) && in_array( $bt['function'], $this->_external_methods )) {
// Проверяем существует ли запрошенное свойство среди свойств класса
if( array_key_exists( $var, get_class_vars( $class_name ) )){
// Изменяем значение свойства
$this->{$var} = $val;
} else {
// Совйство не найдено. Генерируем исключение
trigger_error('Undefined property '.$class_name.'::$'.$var.'');
}
} else {
// Доступ запрещен. Генерируем исключение
trigger_error('Cannot access private property '.$class_name.'::$'.$var.'', E_USER_ERROR);
}
}
Изменим код внешнего метода:
function calss_MyClass_method_hello( $that, $message = '' )
{
// Меняем приватное свойство
$that->_message = $message;
// Получаем значение свойства и выводим на экран
echo( $that->_message );
}
Проверяем:
require( 'MyClass.php' );
$object= new MyClass();
// Вызов внешнего метода
$object->hello( 'Hello World!!!' );
// Попытка доступа к приватному свойству не из объекта
// и не из внешнего метода класса вызовет исключение
echo( $object->_message );
Цель достигнута. Доступ к приватным методам делается по принципу свойств, но в PHP версии ниже 5.3 этот трюк не сработает, магический метод __call не будет вызван, вместо этого сразу вызывается исключение.
Автор: Joo