За последние 24 часа нас посетили 22954 программиста и 1234 робота. Сейчас ищут 708 программистов ...

мощные инклуды

Тема в разделе "Решения, алгоритмы", создана пользователем dark-demon, 27 июн 2007.

  1. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    проблематика:
    1. требуется подгружать код по мере надобности,
    2. код для подгрузки другого кода должен содержать всю необходимую информацию для точного определения расположения подгружаемого кода на диске. (в идеале - чтоб ещё и IDE могла подхватывать такие связи)
    3. должна быть возможность образовывать пакеты с относительными связями между файлами. то есть при перемещении пакета или при создании его копии не должно быть необходимости править файлы внутри пакета. но, ссылки на другие пакеты должны быть относительно некоего общего корня (если, конечно, несколько пакетов не образуют более крупный пакет).

    существующие решения:
    a) include - подходит почти идеально, но есть проблема последействия (когда подгружаемый код после исполнения может оставлять произвольный мусор в пространстве имён вызывающего кода) и отсутствия кэширования интерпретированного кода (для решения этой проблемы приходится юзать APC и иже с ним).
    б) require - то же самое, но в случае отсутствия файла пытается убить скрипт
    в) eval+file_get_contents - принципиальное отсутствие кэширования интерпретированного кода и проблемы при отладке (невозможно отловить в каком файле хранится код вызвавший фатальную ошибку)
    г) create_function+file_get_contents - то же самое, но производится кэширование интерпретированного кода на время выполнения скрипта.
    д) __autoload - работает только с классами, при этом перенос пакета в другую директорию требует изменения всех определяемых внутри пакета классов, ибо в названии класса кодируется путь к нему.

    решение: единственное более-менее вменяемое решение - это include. остальные лечению не поддаются. для выделения подгружаемому коду своего пространства имён оборачиваем вызов include в обычную функцию.
    вот, что из этого получилось:

    PHP:
    1. <?php
    2. function inc ($scriptPath) {
    3.     static $exeCounter= 0;
    4.     ++$exeCounter;
    5.     $scriptArgs= func_get_args();
    6.     $scriptPath= realpath($scriptPath.'.inc');
    7.     $Path= dirname($scriptPath).'/';
    8.     ob_start();
    9.         $rets= include ($scriptPath);
    10.     $obs= ob_get_clean();
    11.     return ($obs==='') ? $rets : $obs;
    12. }
    13. ?>
    тут есть несколько простых условностей:
    1. зарезервированная переменная $exeCounter - просто статистика, плюс в некоторых случаях этот счётчик оказывается весьма полезным.
    2. зарезервированная переменная $scriptArgs - через неё подгружаемый код может получить передаваемые ему дополнительные параметры, задаваемые при вызове inc.
    3. зарезервированныея переменные $scriptPath и $Path представляют из себя полные пути до подгружаемого скрипта и до директории в которой он расположен соответственно.
    4. подгружаемый скрипт может возвращать данные. либо через echo (и иже с ним), либо через ключевое слово return
    5. к передаваемому пути автоматически приписывается расширение .inc - это, конечно, спорный момент, но код от этого становится опрятней $-)

    проиллюстрирую сказанное на примерах...

    чтобы просто выполнить скрипт anyscript.inc в поддиректории libs нужно просто написать:
    PHP:
    1. <?php inc ('libs/anyscript'); ?>
    скрипту можно передавать дополнительные параметры и получать возвращаемое значение:
    PHP:
    1. <?php echo inc ('dump',$var); ?>
    этот код вызывает скрипт dump.inc, который является по сути php-темплейтом для дампа переменной. сам скрипт выводит данные как обычно в выходной поток, но благодаря буферизации функция inc вызвращает это дело в виде строки, которая уже с помощью echo выводится в браузер.

    вызовы inc могут происходить рекурсивно из подгружаемых скриптов. для формирования пути относительно текущего скрипта можно использовать предопределённую переменную $Path:
    PHP:
    1. <?php inc($Path.'another/any_another_script'); ?>
    под конец, приведу лаконичный скрипт, который возвращает исходный код вызывающего скрипта:
    PHP:
    1. <?php
    2.     $path= @$scriptPath[1];
    3.     if (!$path):
    4.         $trace= debug_backtrace();
    5.         $path= $trace[1]['file'];
    6.     endif;
    7.     readfile($path);
    8. ?>
    теперь можно написать в любом скрипте:
    PHP:
    1. <?php echo 'исходный код текущего скрипта: <pre>'.inc('source').'</pre>'; ?>
    и будет выведен исходный код текущего скрипта. в данном случае вторым параметром можно указать путь к какому-нибудь файлу и будет возвращён его исходный код (в итоге получается такой хитрый file_get_contents ;-))

    как видим, решение удовлетворяет поставленным условиям: код подгружается только по необходимости, для подгрузки кода фактически передаётся чёткий путь, можно задавать путь как относительно текущего скрипта, так и относительно основного.

    но не каждый код нужно выполнять каждый раз! например, скрипты, где определяются новые функции или классы повторно вызывать нельзя. да, можно применить для этих целей include_once, но гораздо интересней выглядит вариант с кэшированием:

    PHP:
    1. <?php
    2. function onc ($scriptPath) {
    3.     static $exeCounter= 0;
    4.     static $scriptCache= array();
    5.     ++$exeCounter;
    6.     $scriptArgs= func_get_args();
    7.     $scriptPath= realpath($scriptPath.'.onc');
    8.     $Path= dirname($scriptPath).'/';
    9.     $scriptCallHash= md5($scriptPath);
    10.     if (array_key_exists($scriptCallHash,$scriptCache)):
    11.         return $scriptCache[$scriptCallHash];
    12.     else:
    13.         ob_start();
    14.             $rets= include ($scriptPath);
    15.         $obs= ob_get_clean();
    16.         return $scriptCache[$scriptCallHash]= ($obs==='') ? $rets : $obs;
    17.     endif;
    18. }
    19. ?>
    при первом выполнении onc работает также как и inc, но результат работы (возвращаемое значение) запоминается и при следующем вызове этого скрипта с теми же параметрами значение берётся из кэша. это может оказаться полезным при многокртном вызове одного и того же темплейта с повторяющимися параметрами, но основное применение - определение классов и функций. например, мы можем использовать в пакете сколь угодно длинное название класса (к сожадению создать объект не создавая класс в пыхе нельзя, поэтому нужно создавать класс так, чтобы минимизировать вероятность коллизии имён), но остальные скрипты могут создавать объекты этого класса временно давая ему своё короткое имя. поясню, пусть у нас есть подгружаемый скрипт:
    PHP:
    1. <?php
    2. class abrakadaba1999 {...};
    3. return 'abracadabra1999';
    4. ?>
    , теперь во внешнем скрипте можно написать так:
    PHP:
    1. <?php
    2. $abrclass= onc('lib/abr');
    3. $obj= new $abrclass($var1,$var2);
    4. ?>
    вместо строки можно возвращать и свежесозданный объект этого класса, тогда сам объект попадёт в кэш и фактически будет реализовывать паттерн синглетон:
    PHP:
    1. <?php
    2. class abrakadaba1999 {...};
    3. return new abracadabra1999();
    4. ?>
    к сожалению вот так написать нельзя:
    PHP:
    1. <?php
    2. $class1= onc('lib/class1');
    3. class class2 extends $class1 {};
    4. return 'class2';
    5. ?>
    но необходимость расширять классы из сторонних пакетов - не такая уж актуальная, так что с этим ограничением можно смириться. также настоятельно рекомендуется поднятие APC или чего-нибудь в этом духе, дабы зэнд не тратил лишнее время на интерпретацию скрипта в байт-код.
    ещё один недостаток - ввиду отсутствия в php замыканий использование $Path внутри определения класса невозможно - его нужно заменять на dirname(__FILE__).'/'

    уф... а теперь собственно вопрос. у меня тут сделано явное деление скриптов на одноразовые и многоразовые. расширения они имеют соответственно .onc и .inc, вызываются тоже разными функциями - onc() и inc(). я вот никак не могу определиться, хорошо это или плохо :) а вы как думаете? если конкретней, то имеет ли практическую ценность возможность в зависимости от ситуации один и тот же скрипт вызывать либо inc-ом, либо onc-ом?

    ps: последнюю версию этих двух функций можно наблюдать тут: http://mojura.googlecode.com/svn/trunk/_engine.onc
     
  2. Anonymous

    Anonymous Guest

    dark-demon, многа букаф =)))
    Имхо, сугубо имхо, — любые ограничения уже не есть хорошо. Возможность вызывать по-разному, хорошо, ограничение... не очень.
     
  3. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    ну, это также как "возможность шагнуть в пропость - хорошо, но перила не помешают" :)
     
  4. armadillo

    armadillo Активный пользователь

    С нами с:
    6 апр 2007
    Сообщения:
    2.380
    Симпатии:
    0
    Адрес:
    Russia, Moscow
    вот такой вопрос.
    модули распиханы по своим папкам в папке modules
    изначально инклудится только .h.php файлы где связываются внешние и внутренние имена функций
    выглядят они как 20 строчек.
    $modules[$name]=$func;

    время выполнения 20 таких инклудов, каждый в своей папке - пусть и на дурном селероне с дурным диском - 0.2-0.3 сек.
    Много. Почему?
     
  5. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    armadillo
    Ну кажется ответ лежит на поверхности - убери все содержимое из этих .h.php файлов, пусть будут пустые, и если скрипт по-прежнему будет работать долго - причина одна, если начнет работать быстро - виноват сомнительный код...
     
  6. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    по списку или по результатам поиска на диске?

    "кручу, верчу, запутать хочу"?

    в целом же, если планируется много инклудов - нужно ставить APC либо его аналоги.