Форумы портала PHP.SU » PHP » Пользовательские функции » Получение родительских областей видимости

Страниц (1): [1]
 

1. EuGen - 25 Июня, 2013 - 16:52:24 - перейти к сообщению
Приветствую.

Как известно, в PHP не существует понятия вложенных контекстов. То есть, находясь внутри функции, невозможно получить родительский контекст. Это приводит, например, к тому, что переменные контекста не видны в замыканиях, хотя это и исправляется при помощи ключевого слова use. И если с замыканиями всё более или менее очевидно, то получить родительский контекст, отличный от глобального, возможно только если имеется парадигма вложенных контекстов. Так как в PHP она отсутствует, то я написал реализацию этого механизма.

На деле, ничего хорошего такой подход не сулит - как правило, нет необходимости обращаться к родительским контекстам. Тем не менее, возможность работать внутри замыканий с контекстом родителя кажется естественной. Ниже привожу код класса, который может реализовать эту функциональность:
PHP:
скопировать код в буфер обмена
  1. class Scope
  2. {
  3.     const SCOPE_TYPE_PARENT = 2;
  4.     const SCOPE_TYPE_SELF   = 1;
  5.     protected static $_rInstance = null;
  6.    
  7.     public static function getInstance()
  8.     {
  9.         if(!self::$_rInstance)
  10.         {
  11.             self::$_rInstance = new self;
  12.         }
  13.         return self::$_rInstance;
  14.     }
  15.    
  16.     public function getScope($iLevel = self::SCOPE_TYPE_PARENT)
  17.     {
  18.         $rgTrace  = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
  19.         if(!($rgCaller = $rgTrace[$iLevel]))
  20.         {
  21.             return null;//TODO
  22.         }
  23.         $rContainer = array_key_exists('class', $rgCaller)?new ReflectionMethod($rgCaller['class'], $rgCaller['function']):new ReflectionFunction($rgCaller['function']);
  24.         $rgParameters = array();
  25.         foreach($rContainer->getParameters() as $iIndex=>$rReflection)
  26.         {
  27.            $rgParameters[$rReflection->name] = isset($rgCaller['args'][$iIndex])?$rgCaller['args'][$iIndex]:null;
  28.         }
  29.         eval('$fnParent=function($rgParameters)
  30.           {'.'extract($rgParameters);'.
  31.               PHP_EOL.trim(join('',array_slice(file(__FILE__), $rContainer->getStartLine(), $rgTrace[$iLevel-1]['line']-$rContainer->getStartLine()-1)),"\n\r {").';'.
  32.               PHP_EOL.'return get_defined_vars();'.
  33.               PHP_EOL.'};');
  34.         if(array_key_exists('class', $rgCaller))
  35.         {
  36.             $fnParent   = $fnParent->bindTo($rgCaller['object'], $rgCaller['class']);
  37.         }
  38.         return array_merge($rgParameters, array_diff_key($fnParent($rgParameters), ['rgParameters'=>null]));
  39.     }
  40. }

Примеры использования. Для функций:
PHP:
скопировать код в буфер обмена
  1. function foo($mVar='argvar')
  2. {
  3.    $sLocal    = 'localvar';
  4.    $fnClosure = function()
  5.    {  
  6.        var_dump(Scope::getInstance()->getScope());
  7.    };
  8.    $fnClosure();
  9. }
  10. foo('somevar');


Для классов:
PHP:
скопировать код в буфер обмена
  1. class bar
  2. {
  3.    protected $_protected_var = 'hidden data';
  4.    public function __construct($mVar='classvar', $bPass=false)
  5.    {
  6.       $sObjVar = 'objectvar';
  7.       $sData   = $this->__getData();
  8.       testFunction();
  9.    }
  10.    private function __getData()
  11.    {
  12.       return $this->_protected_var;
  13.    }
  14. }
  15. function testFunction()
  16. {
  17.     var_dump(Scope::getInstance()->getScope());//get parent scope vars
  18. }
  19. $rObj = new bar('objvar');


Существующие минусы данного решения:

0. Самый очевидный - это ужасный подход, поскольку приводит к исполнению родительского кода. Для небольших вычислений это допустимо, но если в родителе исполняется что-либо наподобие тяжёлых SQL-запросов или сетевых вычислений, то идея использовать Scope - не из разряда хороших.
1. На текущий момент нельзя обратиться к глобальному контексту через Scope. Недостаток, впрочем, не слишком значительный - так как есть $GLOBALS
2. На текущий момент я не придумал, как обращаться к переменным, которые являются значениями параметров по-умолчанию (они не объявляются в контексте родителя, не передаются при вызове родителя, но ссылаться на них в коде родителя корректно) - вот это существенный недостаток.
3. Невозможно использовать Scope из eval - просто потому, что я пока не придумал, как получить исполняемый код в eval() (тем не менее, сам признак того, что код исполняется в eval(), легко получить)

Интерес эта парадигма представляет скорее академический - я решал задачу, исходя из необходимости обращаться извне замыкания к контексту, не используя use или конструкции типа get_defined_vars - поскольку можно было модифицировать код внутри тела замыкания, но не код родителя.

Я не пробовал испытывать этот код на каких-нибудь нестандартных связках и сложном коде. Требуемая версия PHP - 5.4+

upd. Сохранено на http://pastebin[dot]com/LKb85QJ4
На stackoverflow это назвали "магией"
2. DlTA - 25 Июня, 2013 - 18:50:24 - перейти к сообщению
а зачем этот штамповщик костылей? что вам дадут переменные окружения родителя?
ведь это попытка "открыть карты" которые советую тщательно прикрывать.
3. EuGen - 25 Июня, 2013 - 19:03:24 - перейти к сообщению
Одно из применений уже сказано - получение полного контекста замыканием (вообще говоря, для замыкания классически свойственно иметь полный контекст - другое дело, что в PHP этого нет).

 

Powered by ExBB FM 1.0 RC1