<
  • Главная
Статьи

dkLab | конструктор | PHP_CodeFilter: перехоплення фатальних помилок PHP? Це можливо

  1. Перехоплення помилок в Perl
  2. Фатальні помилки в PHP: E_ERROR
  3. Перехопити за 60 секунд
  4. Префільтр коду: модуль PHP_CodeFilter
  5. Що далі?
  6. резюме

23 квітня 2005 р 23 квітня 2005 р   Обговорити на форумі   Дана стаття може здатися досить складною для розуміння Обговорити на форумі

Дана стаття може здатися досить складною для розуміння.
Пристебніть ремені безпеки!

Perl можна по праву назвати мовою, в якому однаково легко маніпулювати як даними, так і кодом програми. Дійсно, мова дозволяє створювати «на льоту» нові підпрограми і працювати з ними, як зі звичайними змінними. Завдяки виключно потужною інструкції eval і розвиненому механізму роботи з винятками дуже зручно писати транслятори з різних мета-мов в Perl-код (наприклад, шаблонизатор). При цьому програміст може бути впевненим: він має повний контроль над ходом роботи оттранслировать коду, і навіть в разі фатальної помилки в такому коді він отримає можливість її обробити (наприклад, сформувати осмислене повідомлення).

Перехоплення помилок в Perl

Розглянемо типовий приклад транслятора з деякого мета-мови шаблонів в Perl-код:

лістинг 1

# Перетворює HTML-шаблон з "псевдо-PHP вставками" (&lt;? ...?>) # В коректний Perl-код з серіями print-ів. При цьому всі, що # розташоване поза тегів, виводиться, а то, що всередині, - # виповнюється як Perl-програма. sub сompile {my ($ html) = @ _; $ C = chr (1); $ Code = "?> $ Html &lt;?"; $ Code = ~ s {<\? =} {&lt;? print} sgo; $ Code = ~ s {\?&gt; (. *?) <\?} {Print (qq $ c $ 1 $ c);} sgo; return $ code; } # Прочитуємо шаблон з файлу. my $ template = "template.htm"; local $ /; open (local * F, $ template); my $ code = compile (<F>); # Стартуємо код з "підміною" імені поточного файлу. eval ( "# line 1 \" $ template \ "\ n $ code"); # У разі будь-помилки в оттранслировать коді (будь то # помилка синтаксису, виклик невизначеною функції - що завгодно) # ми потрапимо сюди. Робота програми НЕ завершиться! if ($ @) die "Handled error: $ @";

Вам зараз немає потреби особливо вникати в наведений код. Замість цього ви можете звернутися до дев'ятої Набла , Де детально розглянуто запропонований підхід по найпростішої трансляції шаблонів в Perl (модуль CGI :: Embedder).

Однак, одна строчка в коді представляє для нас особливий інтерес. Ось вона:

Директива Perl #line, що стоїть на окремому рядку, змушує інтерпретатор «прикинутися», нібито наступний за нею код виповнюється «в контексті» зазначеного файлу, починаючи з наведеної рядки. У нашому випадку це файл template.htm, рядок 1. Тепер, навіть якщо в коді $ code відбудеться що-небудь серйозне (наприклад, виникне помилка), Perl повідомить про неї як про «проблеми in file template.htm line N», а не як про «проблеми в eval-коді»!

Щоб остаточно зрозуміти, як працює #line, спробуйте запустити наступну Perl-програму:

Ви побачите наступний результат:

Як бачите, Perl «впевнений», що він виконує код на рядку 100 файлу first.htm, в той час як насправді запускається команда на рядку 2 файли зі скриптом!

Але це ще не всі переваги Perl. Інструкція eval працює як універсальний перехоплювач винятків (помилок), за аналогією з блоком try ... catch в мовах програмування C ++ і Java. Іншими словами, будь-який виник всередині eval виключення (наприклад, виклик стандартної функції die () або синтаксична помилка) призведе не до завершення всієї програми, а тільки лише до виходу з інструкції eval. Це відкриває нам великі можливості по обробці виняткових ситуацій: дійсно, досить «обернути» небезпечну ділянку коду в eval і потім перевірити змінну $ @: в разі проблеми там міститиметься повідомлення про помилку (або об'єкт-виняток, що те ж саме).

Таким чином, Perl дозволяє програмісту:

  • Контролювати реакцію на будь-які помилки в динамічно створеному коді.
  • «Підставляти» в контекст виконуваного коду довільне ім'я файлу і номер рядка.

Фатальні помилки в PHP: E_ERROR

На жаль, мова PHP (поки що?) Не може похвалитися всіма цими можливостями. У ньому, звичайно, є вбудована функція eval (), проте вона зовсім не така потужна. Дійсно, фатальна помилка (E_ERROR), що виникла в занедбаному через eval () коді, призводить до негайної (і безумовної) зупинці скрипта!

Дійсно, фатальна помилка (E_ERROR), що виникла в занедбаному через eval () коді, призводить до негайної (і безумовної) зупинці скрипта

Що таке «фатальна помилка»? Наприклад, досить типовий випадок - виклик невизначеною функції.

PHP також не підтримує директиву #line, а тому все діагностичні повідомлення, згенеровані в eval-коді, будуть повідомляти: «проблема в eval () 'd code on line N». Звичайно, якщо ми намагаємося написати транслятор з мови шаблонів в PHP, це нас ні в якій мірі не влаштує.

Як же виходять з положення автори різних транслюють оброблювачів шаблонів (на зразок Smarty), що перетворюють свої шаблони в PHP-код з подальшим його запуском? А ніяк не виходять. Той, хто хоч раз використовував Smarty, знає, що повідомлення про помилки, які він генерує, посилаються на рядки в уже оттранслировать PHP-коді, розташованому в тимчасових файлах, а зовсім навіть не на рядки вихідного шаблону. Звичайно, це дуже незручно при налагодженні сайту.

Втім, перехопити нефатальні помилки (E_WARNING, E_NOTICE і т. Д.) Можливе за допомогою функції set_error_handler () . Однак виклик невизначеною функції (і будь-яка інша помилка класу E_ERROR) як і раніше призведе до негайного завершення роботи всієї програми і видачі безстороннього повідомлення про помилку в «eval () 'd code».

Перехопити за 60 секунд

З «слабкістю» PHP-шной функції eval (), що стосується безумовного завершення скрипта при помилку E_ERROR, ми ніяк боротися не можемо. Проте, формування «упередженими» посилання на файл, який містить помилку, нам все ж під силу!

Ми скористаємося одним «побічним ефектом», що має місце в усіх версіях PHP 4 і 5. Мова йде про «спрацьовуванні» обробника вихідного потоку, встановленого по ob_start () , При будь-якому завершенні скрипта - не важливо, помилковому або легальному.

Найпростіше продемонструвати цей ефект може наступний код:

лістинг 5

&lt;? Php # Встановлюємо оброблювач вихідного потоку скрипта. ob_start ( 'ob_handler'); # Друкуємо що-небудь. echo "Something"; # Викликаємо невизначену функцію всередині eval! eval ( 'undefinedFunc ();'); # Друкуємо ще щось (до цього моменту скрипт вже мертвий!). echo "Other"; # Функція-обробник просто додає деякий "хвіст" # до тексту, виведеному скриптом раніше. function ob_handler ($ text) {return "$ text <hr> Hello from handler!"; }?>

Ви побачите, що, не дивлячись на неперехвативаемой помилку класу E_ERROR, обробник ob_handler () успішно запустився:

Як же в обробнику визначити, завершився скрипт аварійно або ж коректно? Існує всього лише один спосіб зробити це, але він досить «брудний». Необхідно розібрати регулярним виразом «хвіст» тексту, переданого в функцію, і грубо перевірити, чи міститься в ньому повідомлення про помилку «в eval () 'd code». У разі необхідності саме це повідомлення і потрібно замінити на текст, що містить «фальшиве» ім'я файлу.

Префільтр коду: модуль PHP_CodeFilter

Подолання всіх наведених вище складнощів виливається в досить значний обсяг коду, який я помістив в модуль PHP_CodeFilter .

Подолання всіх наведених вище складнощів виливається в досить значний обсяг коду, який я помістив в модуль   PHP_CodeFilter

На щастя, ignorance is bliss , І тому ви можете не розбиратися у всій цій купі «брудних хаков», а просто використовувати модуль в своїх програмах.

Перетворення (трансляцію) файлу з деякого мета-мови шаблонів в PHP-код з подальшим його запуском я назвав Префільтрація коду (за аналогією з відомим модулем для Perl Filter :: Util :: Call ). З використанням цієї ідеології можна «запускати» деякий шаблон, як ніби-то він є звичайним PHP-кодом, але проводити в ньому попередню обробку - наприклад, «розгортати» власні директиви в «чистий» PHP-код.

Щоб не ходити далеко за прикладом, розглянемо одне з можливих застосувань префільтр коду: підвищення безпеки скрипта. Часто доводиться зустрічати в програмах такі конструкції:

Перед виведенням $ var її необхідно «квоти» - перетворювати лапки в entity & quot ;, знак <( «менше») - в & lt; і т. д., щоб не виникло конфліктів з лапками атрибутів або HTML-тегами. Годі й казати, що утомливо весь час писати одне і те ж. А що в програмуванні утомливо, то найбільшою мірою схильний до помилок і проблем з безпекою.

І ось, за допомогою префільтр коду ми можемо змусити оператор

автоматично

задіяти квотінг при виведенні! Для цього достатньо замінювати даний оператор «на льоту» на наступний код:

У прикладі, наведеному нижче, якраз для такої префільтраціі використовується бібліотека PHP_CodeFilter. Зверніть увагу, що замість include ми застосовуємо виклик $ filter-> includeFile (...): так відбувається не просто включення файлу шаблону, але і його Префільтрація.

Звичайно, ніхто не заважає вам створити функцію з ім'ям include_html () і записати в ній виклик $ filter-> includeFile (...), щоб надалі всюди писати просто include_html (...). Бо коротше.

лістинг 10

&lt;? Php # # Приклад скрипта, що використовує префільтр коду для шаблонів. show_source (__ FILE__); echo "<hr>"; require_once "../../lib/config.php"; require_once "PHP / CodeFilter.php"; // Якщо потрібно, підключаємо Debug_BacktraceDumper для // поліпшеного виведення налагоджувальних повідомлень PHP. if (true) {require_once "Debug / BacktraceDumper.php"; Debug_BacktraceDumper :: set_error_handler (); } // Деяка змінна з HTML-розміткою. $ Test = "<b> test </ b>"; // Створюємо об'єкт-фільтр і налаштовуємо його параметри. $ Filter = new PHP_CodeFilter (); $ Filter-> addFilter ( 'filter_phpOutputOperator'); // Запускаємо включається файл (аналог include), але його // код перед виконанням обробляється префільтром. $ Filter-> includeFile ( 'template.php'); // string filter_phpOutputOperator (string $ code) // Префільтр коду: замінює оператори '&lt;? = ...? > '// (без пробілів, звичайно) на' &lt;? = Htmlspecialchars (...)? > ', // що забезпечує автоматичний квотінг HTML в шаблонах // і підвищує безпеку скриптів. function filter_phpOutputOperator ($ code) {return preg_replace ( '/ (<\? =) (. *?) (?: \ s *;) * \ s * (\?>) / sx', '$ 1htmlspecialchars ($ 2, ENT_QUOTES) $ 3 ', $ code); }?> <

В скрипті також демонструється застосування модуля Debug_BacktraceDumper , Що дозволяє зробити все попередження інтерпретатора більш «доброзичливими», що сильно упрщает налагодження. Звичайно, ви можете і не підключати його, якщо не хочете.

Логіка квотінга тексту реалізована в функції filter_phpOutputOperator (), як раз і замінює стандартний оператор PHP &lt;? = ...?> На його «квотірущій» варіант.

Для повноти картини давайте подивимося на шаблон template.php , Що підключається в скрипті:

лістинг 11

<! - Шаблон, код якого пре-фільтрується перед запуском. -> <body> <p> <b> Перевірка htmlspecialchars-квотінга виведення: </ b> <br> <i> (Повинна бути виведена HTML-розмітка, як після <tt> htmlspecialchars () </ tt>.) </ i> <br> &lt;? = $ Test?> <P> <b> Перевірка використання невизначеною змінної: </ b> <br> <i> (Повинна бути посилання на <tt> &lt;? = $ __ FILE__?> </ Tt>.) </ I > <br> &lt;? = $ Undefined_variable?> &lt;? $ Filter-> includeFile ( 'template2.php')?> <P> <b> Перевірка виклику неіснуючої функції (фатальна помилка): </ b> <br> <i> (Повинна бути посилання на <tt> &lt;? = $ __ FILE__?> </ tt>!) </ i> &lt;? aaa ()?> </ body>

Погляньте прямо зараз на результат роботи скрипта .

  • Хоча змінна $ test містить HTML-розмітку, при виведенні операторами &lt;? = ...?> Вона «проквотілась», і теги стали «нешкідливими». Здавалося б, найпростіший PHP-код, а поведінка його змінилося. Магія? Ні, Префільтрація!
  • Всі повідомлення про помилки (навіть фатальних) виявилися забезпечені вірними посиланнями на файли, у яких вони були згенеровані, - незважаючи на те, що механізм префільтраціі використовує функцію eval () для запуску результуючого коду!

Бібліотека PHP_CodeFilter влаштована так, що вона коректно обробляє навіть режим log_errors = on, так що файли журнали не будуть захаращуватися.

Що далі?

Метод, реалізований в модулі PHP_CodeFilter, теоретично дозволяє в майбутньому ввести підтримку директиви #line, відсутньої в PHP. Справді, досить просто відразу після перехоплення помилки просканувати останній «відфільтрований» код в пошуках рядки, згаданої в повідомленні, і знайти директиву #line, безпосередньо їй передує.

Для оптимізації швидкості можлива реалізація кешуючих версій префільтри, які будуть транслювати код тільки при його першому запуску, в подальшому «підсовуючи» інтерпретатора вже готову PHP-програму. Кешування коду, наприклад, використовується в Smarty і сильно підвищує продуктивність.

Ну і, звичайно, простір для фантазії при написанні функцій-префільтри воістину необмежений! З їх допомогою ви можете реалізовувати будь-які транслятори - наприклад, підтримку мов на зразок Smarty, XTemplate і т. Д. При цьому з точки зору налагоджувальних повідомлень все буде виглядати так, ніби-то код шаблону «запускається» безпосередньо, минаючи фазу трансляції в PHP-код .

резюме

Ризикну припустити, що з застосуванням методу, реалізованого в модулі PHP_CodeFilter, процес написання шаблонних мов виходить на якісно новий щабель, раніше доступну тільки в мові Perl. Наприклад, з'являється можливість створення Smarty-подібного транслятора, що допускає зручну налагодження шаблонних файлів.

Всі вихідні коди скриптів і бібліотек цієї статті доступні в директорії прикладів ( zip ). Отже:

Інші корисні посилання:


Lt;?
Sub сompile {my ($ html) = @ _; $ C = chr (1); $ Code = "?
Code = ~ s {<\?
Lt;?
Print} sgo; $ Code = ~ s {\?
Gt; (. *?) <\?
Поки що?
Що таке «фатальна помилка»?
Як же виходять з положення автори різних транслюють оброблювачів шаблонів (на зразок Smarty), що перетворюють свої шаблони в PHP-код з подальшим його запуском?
Htmlspecialchars (...)?


Новости
  • Виртуальный хостинг

    Виртуальный хостинг. Возможности сервера распределяются в равной мере между всеми... 
    Читать полностью

  • Редизайн сайта

    Редизайн сайта – это полное либо частичное обновление дизайна существующего сайта.... 
    Читать полностью

  • Консалтинг, услуги контент-менеджера

    Сопровождение любых интернет ресурсов;- Знание HTML и CSS- Поиск и обновление контента;-... 
    Читать полностью

  • Трафик из соцсетей

    Сравнительно дешевый способ по сравнению с поисковым и контекстным видами раскрутки... 
    Читать полностью

  • Поисковая оптимизация

    Поисковая оптимизация (англ. search engine optimization, SEO) — поднятие позиций сайта в результатах... 
    Читать полностью