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

от автора

Регулярно на 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 не будет вызван, вместо этого сразу вызывается исключение.

ссылка на оригинал статьи http://habrahabr.ru/post/179437/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *