Выносим методы класса во внешний файл

в 20:26, , рубрики: php, php5, Веб-разработка, Программирование, метки: , , ,

Регулярно на 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

Источник

Поделиться

* - обязательные к заполнению поля