Обработчики запросов — Веб-разработка на PHP
Главная содержательная часть в файле index.php — обработчик запроса:
$app = AppFactory::create(); // Обработчик $app->get('/', function ($request, $response) return $response->write('Welcome to Slim!'); >);
Общий принцип работы любого веб-фреймворка отражает архитектуру HTTP. На каждый адрес задается обработчик (функция, handler), который выполняет необходимые действия и возвращает ответ.
В Slim все приложение представлено объектом класса Slim\App . Этот объект содержит методы на каждый глагол HTTP: get , post , put и так далее. Эти методы принимают на вход два параметра:
- Адрес или маршрут, для которого вызовется обработчик
- Обработчик. Лямбда-функция с двумя параметрами $request и $response
Во фреймворках принято определять маршрут как комбинацию метода HTTP и адреса. Это соответствует идеям REST . То есть GET /users и POST /users с точки зрения большинства фреймворков — разные маршруты со своими обработчиками. В этом достаточно просто убедиться, если определить соответствующие маршруты и выполнить к ним запросы с помощью curl:
$app = AppFactory::create(); $app->get('/users', function ($request, $response) return $response->write('GET /users'); >); $app->post('/users', function ($request, $response) return $response->write('POST /users'); >); $app->run();
-XPOST localhost:8080/users POST /users
Перед тем как двигаться дальше, обязательно попробуйте повторить примеры выше в вашей локальной среде. Это важно для понимания дальнейшего контента.
Первое, что бросается в глаза, — у нас всего один входной файл для всех адресов. Пользователь может запрашивать сколь угодно сложный адрес /companies/3/photos5. Всё сведется к запуску файла index.php, а сам адрес становится лишь значением $_SERVER[‘REQUEST_URI’] :
Такой подход имеет название FrontController в противовес подходу, когда каждый адрес фактически отображался на конкретный файл файловой системы — PageController.
В интернете до сих пор встречаются сайты, построенные по этой модели, но она давно вышла из употребления. Заметить ее легко. Если есть адреса наподобие /users.php , то почти наверняка в корне сайта лежит файл users.php, который отвечает за обработку этой страницы:
Во FrontController процесс поиска нужного обработчика называется диспетчеризацией, по аналогии с тем как это слово используется в оффлайн жизни. Пошагово он выглядит так:
До входа во фреймворк:
- Клиент выполняет запрос к веб-серверу, расположенному на сервере. Клиент — это не обязательно браузер, в примере выше клиентом выступает программа curl
- Веб-сервер перенаправляет запрос на index.php и устанавливает правильные параметры запроса
После входа в сам PHP (именно это и есть диспетчеризация):
- Фреймворк анализирует параметры запроса и пытается сопоставить маршруты, добавленные в объект $app (как в примерах в начале урока) с тем, что пришло. Он сравнивает комбинацию метода запроса и сам адрес. Этот процесс называется роутингом или маршрутизацией. А место, где внутри хранятся все добавленные маршруты, называют роутером
- Если в процессе роутинга был найден соответствующий маршрут, то вызывается его обработчик
- Ответ, сформированный обработчиком, отправляется обратно клиенту
Рассмотрим конкретный пример. Возьмем за основу следующий код:
$app = AppFactory::create(); $app->get('/', function ($request, $response) return $response->write('GET /'); >); $app->get('/companies', function ($request, $response) return $response->write('GET /companies'); >); $app->post('/companies', function ($request, $response) return $response->write('POST /companies'); >); $app->run();
После запуска этого кода формируется роутер, который содержит в себе три маршрута:
Теперь предположим, что клиент выполнил такой запрос:
-XPOST localhost:8080/companies
Веб-сервер запустил index.php, который проинициализировал объект $app . Затем фреймворк сопоставил маршруты и нашел, что за этот запрос отвечает POST /companies. Далее фреймворк вызвал обработчик, который вернул клиенту ответ: POST /companies.
Если фреймворк не обнаружит соответствия, например, клиент запросит страницу /comments, то он возьмет управление на себя по умолчанию и автоматически отдаст браузеру ответ 404. Так он говорит о том, что страница не найдена.
Всегда нужно внимательно смотреть, какие делаются запросы и есть ли подходящие под них ответы, чтобы не думать о том, почему браузер ничего не показывает. Проще всего увидеть ответ от сервера через консоль разработчика браузера:
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
ob_list_handlers
Функция вернёт массив с заданными обработчиками вывода (если есть).
Возвращает строку «default output handler» , если настройка output_buffering включена и настройка output_handler не установлена, или в функцию ob_start() не передана callback-функция или вместо неё передано значение null . Включение настройки output_buffering и установка значения для настройки output_handler эквивалентно передаче в функцию ob_start() внутренней (встроенной) функции.
Возвращает абсолютное имя переданной в параметр callable функции, если в параметр callable функции ob_start() была передана callback-функция. Возвращает абсолютное имя объекта с методом __invoke(), если параметр callable — это объект, который реализует метод __invoke(). Возвращает строку «Closure::__invoke» , если параметр callable — это объект класса Closure .
Примеры
Пример #1 Пример использования функции ob_list_handlers()
// настройка включена output_buffering=On, значение для настройки output_handler не установлено
var_dump ( ob_list_handlers ());
ob_end_flush ();
?php
// callback-функция не передана или null
ob_start ();
var_dump ( ob_list_handlers ());
ob_end_flush ();
// анонимная функция
ob_start (function( $string ) < return $string ; >);
var_dump ( ob_list_handlers ());
ob_end_flush ();
// стрелочная функция
ob_start (fn( $string ) => $string );
var_dump ( ob_list_handlers ());
ob_end_flush ();
// callback-функция как объект первого класса
$firstClassCallable = userDefinedFunction (. );
ob_start ([ $firstClassCallable , ‘__invoke’ ]);
var_dump ( ob_list_handlers ());
ob_end_flush ();
// внутренняя (встроенная функция)
ob_start ( ‘print_r’ );
var_dump ( ob_list_handlers ());
ob_end_flush ();
// пользовательская функция
function userDefinedFunction ( $string , $flags ) < return $string ; >;
ob_start ( ‘userDefinedFunction’ );
var_dump ( ob_list_handlers ());
ob_end_flush ();
class MyClass public static function staticHandle ( $string ) return $string ;
>
public static function handle ( $string ) return $string ;
>
public function __invoke ( $string ) return $string ;
>
>
// класс и статический метод
ob_start ([ ‘MyClass’ , ‘staticHandle’ ]);
var_dump ( ob_list_handlers ());
ob_end_flush ();
// объект и нестатический метод
ob_start ([new MyClass , ‘handle’ ]);
var_dump ( ob_list_handlers ());
ob_end_flush ();
// объект вызываемого класса
ob_start (new MyClass );
var_dump ( ob_list_handlers ());
ob_end_flush ();
?>
Результат выполнения этого примера:
array(1) < [0]=>string(22) "default output handler" > array(1) < [0]=>string(22) "default output handler" > array(1) < [0]=>string(7) "print_r" > array(1) < [0]=>string(19) "userDefinedFunction" > array(1) < [0]=>string(17) "Closure::__invoke" > array(1) < [0]=>string(17) "Closure::__invoke" > array(1) < [0]=>string(17) "Closure::__invoke" > array(1) < [0]=>string(21) "MyClass::staticHandle" > array(1) < [0]=>string(15) "MyClass::handle" > array(1) < [0]=>string(17) "MyClass::__invoke" >
Смотрите также
- ob_end_clean() — Очищает (стирает) содержимое активного буфера вывода и отключает его
- ob_end_flush() — Сбрасывает (отправляет) возвращаемое значение активного обработчика вывода и отключает активный буфер вывода
- ob_get_flush() — Сбрасывает (отправляет) возвращённое активным обработчиком вывода значение, возвращает содержимое активного буфера вывода и отключает его
- ob_start() — Включает буферизацию вывода
User Contributed Notes
There are no user contributed notes for this page.
- Функции контроля вывода
- flush
- ob_clean
- ob_end_clean
- ob_end_flush
- ob_flush
- ob_get_clean
- ob_get_contents
- ob_get_flush
- ob_get_length
- ob_get_level
- ob_get_status
- ob_implicit_flush
- ob_list_handlers
- ob_start
- output_add_rewrite_var
- output_reset_rewrite_vars
- Copyright © 2001-2024 The PHP Group
- My PHP.net
- Contact
- Other PHP.net sites
- Privacy policy
Handler php что это
@print($a);
is equivalent to
if isset($a) echo $a ;@a++;
is equivalent to
if isset($a) $a++ ;
else $a = 1;2 years ago
It’s still possible to detect when the @ operator is being used in the error handler in PHP8. Calling error_reporting() will no longer return 0 as documented, but using the @ operator does still change the return value when you call error_reporting().
My PHP error settings are set to use E_ALL, and when I call error_reporting() from the error handler of a non-suppressed error, it returns E_ALL as expected.
But when an error occurs on an expression where I tried to suppress the error with the @ operator, it returns: E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR (or the number 4437).
I didn’t want to use 4437 in my code in case it changes with different settings or future versions of PHP, so I now use:
function my_error_handler ( $err_no , $err_msg , $filename , $linenum ) if ( error_reporting () != E_ALL ) return false ; // Silenced
>// .
>
?>If the code needs to work with all versions of PHP, you could check that error_reporting() doesn’t equal E_ALL or 0.
And, of course, if your error_reporting settings in PHP is something other than E_ALL, you’ll have to change that to whatever setting you do use.
13 years ago
After some time investigating as to why I was still getting errors that were supposed to be suppressed with @ I found the following.
1. If you have set your own default error handler then the error still gets sent to the error handler regardless of the @ sign.
2. As mentioned below the @ suppression only changes the error level for that call. This is not to say that in your error handler you can check the given $errno for a value of 0 as the $errno will still refer to the TYPE(not the error level) of error e.g. E_WARNING or E_ERROR etc
3. The @ only changes the rumtime error reporting level just for that one call to 0. This means inside your custom error handler you can check the current runtime error_reporting level using error_reporting() (note that one must NOT pass any parameter to this function if you want to get the current value) and if its zero then you know that it has been suppressed.
// Custom error handler
function myErrorHandler ( $errno , $errstr , $errfile , $errline )
if ( 0 == error_reporting () ) // Error reporting is currently turned off or suppressed with @
return;
>
// Do your normal custom error reporting here
>
?>For more info on setting a custom error handler see: http://php.net/manual/en/function.set-error-handler.php
For more info on error_reporting see: http://www.php.net/manual/en/function.error-reporting.php13 years ago
Be aware that using @ is dog-slow, as PHP incurs overhead to suppressing errors in this way. It’s a trade-off between speed and convenience.
12 years ago
If you use the ErrorException exception to have a unified error management, I’ll advise you to test against error_reporting in the error handler, not in the exception handler as you might encounter some headaches like blank pages as error_reporting might not be transmitted to exception handler.
function exception_error_handler ( $errno , $errstr , $errfile , $errline )
throw new ErrorException ( $errstr , 0 , $errno , $errfile , $errline );
>function catchException ( $e )
if ( error_reporting () === 0 )
return;
>?>
It would be better to do :
function exception_error_handler ( $errno , $errstr , $errfile , $errline )
if ( error_reporting () === 0 )
return;
>throw new ErrorException ( $errstr , 0 , $errno , $errfile , $errline );
>function catchException ( $e )
// Do some stuff
>Урок 80. Handler. Немного теории. Наглядный пример использования
Для полного понимания урока желательно иметь представление о потоках (threads) в Java.
Так просто ведь и не объяснишь, что такое Handler. Можете попробовать почитать официальное описание, но там достаточно нетривиально и мало написано. Я попробую здесь в двух словах рассказать.
В Android к потоку (thread) может быть привязана очередь сообщений. Мы можем помещать туда сообщения, а система будет за очередью следить и отправлять сообщения на обработку. При этом мы можем указать, чтобы сообщение ушло на обработку не сразу, а спустя определенное кол-во времени.
Handler — это механизм, который позволяет работать с очередью сообщений. Он привязан к конкретному потоку (thread) и работает с его очередью. Handler умеет помещать сообщения в очередь. При этом он ставит самого себя в качестве получателя этого сообщения. И когда приходит время, система достает сообщение из очереди и отправляет его адресату (т.е. в Handler) на обработку.
Handler дает нам две интересные и полезные возможности:
1) реализовать отложенное по времени выполнение кода
2) выполнение кода не в своем потоке
Подозреваю, что стало не сильно понятнее, что такое Handler, а главное – зачем он вообще нужен 🙂 . В ближайшие несколько уроков будем с этим разбираться, и все станет понятно.
В этом уроке сделаем небольшое приложение. Оно будет эмулировать какое-либо долгое действие, например закачку файлов и в TextView выводить кол-во закачанных файлов. С помощью этого примера мы увидим, зачем может быть нужен Handler.
Project name: P0801_Handler
Build Target: Android 2.3.3
Application name: Handler
Package name: ru.startandroid.develop.p0801handler
Create Activity: MainActivityHandler Start Test ProgressBar у нас будет крутиться всегда. Позже станет понятно, зачем. TextView – для вывода информации о закачке файлов. Кнопка Start будет стартовать закачку. Кнопка Test будет просто выводить в лог слово test.
Кодим MainActivity.java:
package ru.startandroid.develop.p0801handler; import java.util.concurrent.TimeUnit; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MainActivity extends Activity < final String LOG_TAG = "myLogs"; Handler h; TextView tvInfo; Button btnStart; /** Called when the activity is first created. */ public void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.main); tvInfo = (TextView) findViewById(R.id.tvInfo); >public void onclick(View v) < switch (v.getId()) < case R.id.btnStart: for (int i = 1; i break; case R.id.btnTest: Log.d(LOG_TAG, "test"); break; default: break; > > void downloadFile() < // пауза - 1 секунда try < TimeUnit.SECONDS.sleep(1); >catch (InterruptedException e) < e.printStackTrace(); >> >
В обработчике кнопки Start мы организуем цикл для закачки файлов. В каждой итерации цикла выполняем метод downloadFile (который эмулирует закачку файла), обновляем TextView и пишем в лог информацию о том, что кол-во закачанных файлов изменилось. Итого у нас должны закачаться 10 файлов и после закачки каждого из них лог и экран должны показывать, сколько файлов уже закачано.
По нажатию кнопки Test – просто выводим в лог сообщение.
downloadFile – эмулирует закачку файла, это просто пауза в одну секунду.
Все сохраним и запустим приложение.

Мы видим, что ProgressBar крутится. Понажимаем на кнопку Test, в логах появляется test. Все в порядке, приложение отзывается на наши действия.
Теперь расположите AVD на экране монитора так, чтобы он не перекрывал вкладку логов в Eclipse (LogCat). Нам надо будет видеть их одновременно.
Если мы нажмем кнопку Start, то мы должны наблюдать, как обновляется TextView и пишется лог после закачки очередного файла. Но на деле будет немного не так. Наше приложение просто «зависнет» и перестанет реагировать на нажатия. Остановится ProgressBar, не будет обновляться TextView, и не будет нажиматься кнопка Test. Т.е. UI (экран) для нас станет недоступным. И только по логам будет понятно, что приложение на самом деле работает и файлы закачиваются. Нажмите Start и убедитесь.
Экран «висит», а логи идут. Как только все 10 файлов будут закачаны, приложение оживет и снова станет реагировать на ваши нажатия.
А все почему? Потому что работа экрана обеспечивается основным потоком приложения. А мы заняли весь этот основной поток под свои нужды. В нашем случае, как будто под закачку файлов. И как только мы закончили закачивать файлы – поток освободился, и экран стал снова обновляться и реагировать на нажатия.
Для тех, кто имеет опыт кодинга на Java, я ничего нового не открыл. Для остальных же, надеюсь, у меня получилось доступно объяснить. Тут надо понять одну вещь — основной поток приложения отвечает за экран. Этот поток ни в коем случае нельзя грузить чем-то тяжелым – экран просто перестает обновляться и реагировать на нажатия. Если у вас есть долгоиграющие задачи – их надо вынести в отдельный поток. Попробуем это сделать.
Перепишем onclick:
public void onclick(View v) < switch (v.getId()) < case R.id.btnStart: Thread t = new Thread(new Runnable() < public void run() < for (int i = 1; i > >); t.start(); break; case R.id.btnTest: Log.d(LOG_TAG, "test"); break; default: break; > >
Т.е. мы просто помещаем весь цикл в новый поток и запускаем его. Теперь закачка файлов пойдет в этом новом потоке. А основной поток будет не занят и сможет без проблем прорисовывать экран и реагировать на нажатия. А значит, мы будем видеть изменение TextView после каждого закачанного файла и крутящийся ProgressBar. И, вообще, сможем полноценно взаимодействовать с приложением. Казалось бы, вот оно счастье 🙂
Все сохраним и запустим приложение. Жмем Start.

Приложение вылетело с ошибкой. Смотрим лог ошибок в LogCat. Там есть строки:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at ru.startandroid.develop.p0801handler.MainActivity$1.run(MainActivity.java:37)
Смотрим, что за код у нас в MainActivity.java в 37-й строке:
tvInfo.setText("Закачано файлов: " + i);При попытке выполнить этот код (не в основном потоке) мы получили ошибку «Only the original thread that created a view hierarchy can touch its views». Если по-русски, то «Только оригинальный поток, создавший view-компоненты, может взаимодействовать с ними». Т.е. работа с view-компонентами доступна только из основного потока. А новые потоки, которые мы создаем, не имеют доступа к элементам экрана.
Т.е. с одной стороны нельзя загружать основной поток тяжелыми задачами, чтобы не «вешался» экран. С другой стороны – новые потоки, созданные для выполнения тяжелых задач, не имеют доступа к экрану, и мы не сможем из них показать пользователю, что наша тяжелая задача как-то движется.
Тут нам поможет Handler. План такой:
— мы создаем в основном потоке Handler
— в потоке закачки файлов обращаемся к Handler и с его помощью помещаем в очередь сообщение для него же самого
— система берет это сообщение, видит, что адресат – Handler, и отправляет сообщение на обработку в Handler
— Handler, получив сообщение, обновит TextViewЧем это отличается от нашей предыдущей попытки обновить TextView из другого потока? Тем, что Handler был создан в основном потоке, и обрабатывать поступающие ему сообщения он будет в основном потоке, а значит, будет иметь доступ к экранным компонентам и сможет поменять текст в TextView. Получить доступ к Handler из какого-либо другого потока мы сможем без проблем, т.к. основной поток монополизирует только доступ к UI. А элементы классов (в нашем случае это Handler в MainActivity.java) доступны в любых потоках. Таким образом Handler выступит в качестве «моста» между потоками.
Перепишем метод onCreate:
public void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.main); tvInfo = (TextView) findViewById(R.id.tvInfo); btnStart = (Button) findViewById(R.id.btnStart); h = new Handler() < public void handleMessage(android.os.Message msg) < // обновляем TextView tvInfo.setText("Закачано файлов: " + msg.what); if (msg.what == 10) btnStart.setEnabled(true); >; >; >Здесь мы создаем Handler и в нем реализуем метод обработки сообщений handleMessage. Мы извлекаем из сообщения атрибут what – это кол-во закачанных файлов. Если оно равно 10, т.е. все файлы закачаны, мы активируем кнопку Start. (кол-во закачанных файлов мы сами кладем в сообщение — сейчас увидите, как)
Метод onclick перепишем так:
public void onclick(View v) < switch (v.getId()) < case R.id.btnStart: btnStart.setEnabled(false); Thread t = new Thread(new Runnable() < public void run() < for (int i = 1; i > >); t.start(); break; case R.id.btnTest: Log.d(LOG_TAG, "test"); break; default: break; > >
Мы деактивируем кнопку Start перед запуском закачки файлов. Это просто защита, чтобы нельзя было запустить несколько закачек одновременно. А в процессе закачки, после каждого закачанного файла, отправляем (sendEmptyMessage) для Handler сообщение с кол-вом уже закачанных файлов. Handler это сообщение примет, извлечет из него кол-во файлов и обновит TextView.
Все сохраняем и запускаем приложение. Жмем кнопку Start.

Кнопка Start стала неактивной, т.к. мы ее сами выключили. А TextView обновляется, ProgressBar крутится и кнопка Test нажимается. Т.е. и закачка файлов идет, и приложение продолжает работать без проблем, отображая статус закачки.
Когда все файлы закачаются, кнопка Start снова станет активной.

Подытожим все вышесказанное.
1) Сначала мы попытались грузить приложение тяжелой задачей в основном потоке. Это привело к тому, что мы потеряли экран – он перестал обновляться и отвечать на нажатия. Случилось это потому, что за экран отвечает основной поток приложения, а он был сильно загружен.
2) Мы создали отдельный поток и выполнили весь тяжелый код там. И это бы сработало, но нам надо было обновлять экран в процессе работы. А из не основного потока доступа к экрану нет. Экран доступен только из основного потока.
3) Мы создали Handler в основном потоке. А из нового потока отправляли для Handler сообщения, чтобы он нам обновлял экран. В итоге Handler помог нам обновлять экран не из основного потока.
Достаточно сложный урок получился. Наверняка, мало, что понятно. Не волнуйтесь, в этом уроке я просто показал, в какой ситуации Handler может быть полезен. А методы работы с ним мы рассмотрим подробно в следующих уроках.
Eclipse может подчеркивать Handler желтым цветом и ругаться примерно такими словами: «This Handler class should be static or leaks might occur«. Тем самым он сообщает нам, что наш код немного плох и может вызвать утечку памяти. Тут он прав абсолютно, но я в своих уроках все-таки буду придерживаться этой схемы, чтобы не усложнять.
А на форуме я отдельно расписал, почему может возникнуть утечка и как это можно пофиксить. Как закончите тему Handler-ов, обязательно загляните туда и почитайте — http://forum.startandroid.ru/viewtopic.php?f=30&t=1870.
На следующем уроке:
— посылаем простейшее сообщение для Handler
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня