Введение

cURL — библиотека с открытым исходным кодом, используемая для отправки HTTP-запросов с различных языков программирования, включая C, PHP и другие.

cURL также является программой командной строки, позволяющая взаимодействовать с множеством различных серверов.
Libcurl — это библиотека API для передачи, которую разработчики могут встроить в свои программы; cURL действует как автономная обёртка для библиотеки libcurl. Для libcurl имеются модули интеграции для работы с более чем 30 языками программирования.

cURL работает по множеству различных протоколов с синтаксисом URL. В данной статье рассмотрена работа библиотеки по протоколу HTTP/HTTPS.

Содержание:

Модуль PHP cURL обычно включён по умолчанию. Если это не так, то в файле php.ini уберите точку с запятой (;) у строки extension=php_curl.dll.

Настройка параметров сеанса

Для установки параметра сеанса cURL используется функция curl_setopt.

<?php
  curl_setopt(CurlHandle $handle, int $option, mixed $value): bool
  • handle — дескриптор  cURL, полученный из curl_init().

  • option — параметр сеанса в виде CURLOPT_XXX.

  • value — значение параметра option.

Функция возвращает true в случае успешного выполнения или false в случае возникновения ошибки.

Сразу несколько параметров сеанса можно установить с помощью функции curl_setopt_array.

<?php
  curl_setopt_array(CurlHandle $handle, array $options): bool

options — ассоциативный массив, определяющий устанавливаемые параметры и их значения. Ключи должны быть корректными константами для функции curl_setopt() или их целочисленными эквивалентами.
Функция возвращает true, если все параметры были успешно установлены. Если не удалось успешно установить какой-либо параметр, немедленно возвращается значение false, все последующие параметры игнорируются.

Роль параметров сеанса играют предопределённые константы. Рассмотрим основные из них.

CURLOPT_URL — параметр, который задает адрес ресурса, с которым вы хотите взаимодействовать или с которого хотите получить данные. Параметр является обязательным и должен быть установлен перед вызовом curl_exec().

CURLOPT_RETURNTRANSFER — константа, устанавливающая значение дескриптора cURL так, чтобы ответ от сервера возвращался в виде строкового значения вместо отправки непосредственно в поток вывода.
При установке CURLOPT_RETURNTRANSFER в значение true или 1, константа сообщает cURL вернуть ответ из HTTP-запроса в виде строки, которую затем можно сохранить в переменной или обработать по мере необходимости. Если этот параметр не задан или имеет значение false, ответ на HTTP-запрос будет отправлен непосредственно в поток вывода (например, в окно браузера или файл, установленный параметром CURLOPT_FILE).

CURLOPT_HEADER — параметр, который указывает, следует ли включать заголовок в ответ (true — для включения).Заголовок содержит информацию об ответе, такую как код состояния HTTP, тип содержимого и др.
Для получения информации об ответе также можно использовать функцию curl_getinfo()(будет рассмотрена позже в статье).

CURLOPT_HTTPHEADER — параметр cURL, который задает HTTP-заголовки, отправляемые вместе с запросом. Параметр принимает массив строк. Каждая строка должна содержать имя заголовка и его значение, разделенные двоеточием.

CURLOPT_FOLLOWLOCATION — константа, которая используется для настройки поведения cURL в случае, если сервер возвращает заголовок "Location" как часть ответа, код состояния которого находится в диапазоне 300 – 399 (сообщения о перенаправлении). Заголовок "Location" содержит в себе URL для редиректа.
Когда CURLOPT_FOLLOWLOCATION установлена в значение 1, cURL будет автоматически следовать любым редиректам, делая дополнительные запросы к новому URL до тех пор, пока в ответе не будет содержаться заголовок "Location". Значение по умолчанию - 0.

CURLOPT_POST — константа, указывающая, следует ли отправлять запрос методом POST. По умолчанию cURL отправляет GET-запросы. Если CURLOPT_POST установлен в значении 1 или true, то будет отправлен POST-запрос.

CURLOPT_POSTFIELDS — это параметр cURL, используемый для установки тела POST-запроса. Формат данных зависит от типа, указанного в заголовке Content-Type.

Простой GET-запрос

<?php
    // Инициализация сеанса cURL
    $ch = curl_init();
    // Установка URL
    curl_setopt($ch, CURLOPT_URL, "example.com");
    // Установка CURLOPT_RETURNTRANSFER (вернуть ответ в виде строки)
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    // Выполнение запроса cURL
	//$output содержит полученную строку
    $output = curl_exec($ch);
    // закрытие сеанса curl для освобождения системных ресурсов
    curl_close($ch);      
?>

Простой POST-запрос

Чтобы сделать POST-запрос, нужно установить параметры CURLOPT_POST и CURLOPT_POSTFIELDS.

<?php
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "http://www.example.com/api/resource");
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, "name=value");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $output = curl_exec($ch);
    curl_close($ch);
    echo $output;
?>

Существует несколько форматов тела запроса:

1) Формат JSON

<?php
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json');
    curl_setopt($ch, CURLOPT_POSTFIELDS,"{key1:value1,key2:value2}");
?>

2) Строка запроса HTTP

<?php
    curl_setopt($ch, CURLOPT_POSTFIELDS,"key1=value1&key2=value2");
?>

Для построения строки запроса используется функция http_build_query.

3) Формат массива POST

<?php 
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: multipart/form-data');
    curl_setopt($ch, CURLOPT_POSTFIELDS, array("key1"=>"value1", "key2"=>"value2");
?>

Для отправки файлов в тело запроса применяется класс CURLFile. Чтобы создать экземпляр класса можно воспользоваться конструктором или функцией curl_file_create. Экземпляр класса передаётся константе CURLOPT_POSTFIELDS как элемент массива.

Обработка исключений/ошибок

Обработка ошибок в ходе сеанса cURL осуществляется с помощью функций curl_errno и curl_error, которые фиксируют любые ошибки, возникающие во время сеанса.

curl_errno — принимает дескриптор cURL, полученный из curl_init() и возвращает номер ошибки последней операции cURL.

curl_error также принимает дескриптор, но возвращает строку с описанием последней ошибки текущего сеанса. Строка содержит информацию о том, что пошло не так во время операции cURL, что может пригодиться во время отладки.

Важно проверять значение curl_errno после операции cURL, чтобы убедиться, что операция завершена успешно, а также выявить и устранить любые ошибки, которые могли возникнуть.

Пример POST-запроса с телом формате JSON, обработкой исключений, и обработкой ответа от сервера

<?php
    // Определение параметров сеанса
    $CurlOptions = array(
        CURLOPT_URL 		   => 'http://domain-name/endpoint-path',
        CURLOPT_POST           => 1,
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_FOLLOWLOCATION => 1,
        CURLOPT_HTTPHEADER     => array( 'Content-Type' => 'application/json' )
    );
        
    // Сериализация тела запроса
    $data = array('field1' => 'value1', 'field2' => 'value2');
    $json_req = json_encode($data);
    // Установка тела запроса
    $CurlOptions[CURLOPT_POSTFIELDS] = $json_req;
        
    // Инициализация сеанса
    $ch = curl_init();    
    // установка параметров сеанса
    curl_setopt_array( $ch, $CurlOptions );
    // Выполнение запроса, в переменной хранится ответ от сервера
    $data = curl_exec( $ch );
        
    //получение информацию о сеансе
    $info = curl_getinfo($ch);
        
    // если в ходе сеанса произошла ошибка  
    // или если код HTTP-ответа не в диапазоне 200 – 299 (успешные запросы)
    if (curl_errno($ch) || substr($info['http_code'],0,1) !== '2') {
        // вызов пользовательского исключения
        throw new CustomException(curl_error($ch), $data, $info);
    }
    
    // закрытие сеанса
    curl_close( $ch );
?>

В данном примере информация о последнем сеансе была получена с помощью функции curl_getinfo(). Функция возвращает массив информации о различных характеристиках сеанса, таких как код ответа HTTP, тип содержимого, общее время и т.д.

Проверка SSL-сертификата

Для проверки SSL-сертификата необходимо использовать константу CURLOPT_SSL_VERIFYPEER.

CURLOPT_SSL_VERIFYPEER — это константа, которая определяет, должен ли curl проверять подлинность SSL-сертификата. Если установлено значение true, curl проверяет SSL-сертификат, представленный удаленным сервером, и выдаёт ошибку, если это сертификат недействительный Если установлено значение false, curl не проверяет SSL-сертификат и разрешает подключение, даже если SSL-сертификат недействителен. Однако это может привести к уязвимостям в системе безопасности. По умолчанию установлено значение true.

CURLOPT_SSL_VERIFYPEER работает только для SSL-соединений, при подключении к http-серверам константа будет проигнорирована.

Аутентификация на сервере

CURLOPT_HTTPAUTH — это константа, которая используется для установки типа HTTP-аутентификации, используемой для запроса.

<?php
    $CurlOptions = array(
      CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
      CURLOPT_USERPWD => "login:password");
?>

Константа принимает следующие значения:

  • CURLAUTH_BASIC: Базовая аутентификация HTTP, которая отправляет имя пользователя и пароль по сети в виде обычного текста, легко перехватываемого другими.

  • CURLAUTH_DIGEST: Аутентификация HTTP Digest, которая использует хэш-функцию для шифрования пароля перед отправкой его на сервер.

  • CURLAUTH_GSSNEGOTIATE: HTTP GSS-Negotiate аутентификация, которая является способом обеспечения безопасной аутентификации с использованием Kerberos.

  • CURLAUTH_NTLM: Аутентификация NTLM, которая представляет собой механизм запроса-ответа, используемый Windows. В данном случае используется концепция хэширования, аналогичная Digest, чтобы предотвратить перехват пароля.

  • CURLAUTH_ANY: Сообщает cURL попробовать все поддерживаемые методы аутентификации. cURL автоматически выберет тот, который он сочтет наиболее безопасным.

  • CURLAUTH_ANYSAFE: Работает аналогично CURLAUTH_ANY, но в этом случае cURL будет пробовать только безопасные методы (все методы кроме CURLAUTH_BASIC).

Значение по умолчанию: CURLAUTH_BASIC.

Значения могут быть объединены с помощью побитового ИЛИ. Например, CURLAUTH_BASIC | CURLAUTH_DIGEST. cURL выберет наиболее подходящий метод из представленных.

При использовании HTTPS все данные передаются в зашифрованном виде. При такой передаче CURLOPT_HTTPAUTH предоставляет дополнительные меры безопасности для обеспечения подлинности клиента и сервера и предотвращения несанкционированного доступа.

ИТОГ

cURL — удобная библиотека для передачи данных между клиентом и сервером. cURL позволяет взаимодействовать с множеством различных серверов по различным протоколам: http, https, ftp, gopher, telnet, dict, file и ldap.

Библиотека легка в использовании. cURL предоставляет инструменты для простых GET-запросов, но также имеет дополнительный функционал:

  • работа с сертификатами HTTPS;

  • загрузка файлов по протоколам HTTP и FTP (последнее можно сделать с помощью модуля FTP);

  • использование прокси-серверы;

  • cookies;

  • аутентификация пользователей.

Комментарии (19)


  1. FanatPHP
    00.00.0000 00:00
    +8

    У меня смешанные чувства.
    С одной стороны, я не согласен с теми кто считает, что на Хабре не должно быть статей базового уровня. Любой уровень важен. С другой стороны, как раз про базовые вещи должен писать специалист, а не тот, кто их только что для себя открыл. А здесь автор зачастую явно не понимает смысла написанного.
    С одной стороны, видеть на Хабре довольно беспомощный текст довольно непривычно. С другой — ну а где автору еще опыта набираться, фидбек получить?


    В общем, если автор переработает статью по результатам обсуждения, то из нее вполне может получиться что-то толковое.


    Из ключевых моментов.


    1. Информация про типы запросов довольно бесполезная. Надо написать хотя бы про http_build_query() и CURLFile. Про последний особенно, поскольку без него использование multipart/form-data не имеет смысла.
    2. Обработка ошибок. Всё плохо, сплошной кринж и карго-культ. Начиная с вступления, которое явно бездумно переписано с какого-то мусорного источника.
      • "В основном, это ошибки на стороне сервера" — шта? Какой сервер имеется в виду? Удалённый? Видимо, нет, хотя по логике должен быть именно удаленный, раз мы говорим о curl. Но если локальный, то снова вопрос — о каких конкретно исключениях речь? Вроде бы, тут только обращения к функциям curl, но они исключений не бросают.
      • Сам текст исключений — классическая ученическая бессмыслица. Видно, что человек ни разу не был в ситуации, когда ему надо отладить курл запрос, и пишет просто от балды, "шоб було". "Запрос не был успешен" — а как конкретно он был неуспешен? Какой конкретно код вернулся? 4хх? 5хх? 204? А это точно неуспешный запрос? Какой был актуальный ответ сервера? Не знаем, мы его убили поспешным декодированием.
      • проверка результата декодирования json — один сплошой кринж. Зачем там is_object($data)? Почему мы принимаем только массивы? А если сервер вернул булево значение или строку? Зачем вообще так криво проверять корректность декодирования, если в json_decode это делается СОВСЕМ по-другому, либо через json_last_error(), либо — предпочтительнее — через выброс исключения самой функцией
      • зачем здесь вообще раскодирование JSON? А если мы запрашиваем HTML? XML? Картинку? Каждая функция должна делать что-то одно. Функция для работы с curl должна возвращать полученный ответ. А как его декодировать, и декодировать ли вообще — не ее ума дело. И в итоге получается, что ни исключения, ни try-catch нам тут по сути и не нужны.
    3. "Стоит отметить, что URL-адреса "http" не имеют SSL-сертификата для проверки, поэтому установка этой константы в значение true приведет к неудачному запросу" — мягко говоря, не совсем true. А вот CURLOPT_FOLLOWLOCATION как раз следовало включить в список основных констант.

    В итоге пример должен получиться примерно таким


    // Определение параметров запроса
    $CurlOptions = [
        CURLOPT_URL            => 'http://domain-name/endpoint-path',
        CURLOPT_POST           => 1,
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_FOLLOWLOCATION => 1,
        CURLOPT_HTTPHEADER     => array( 'Content-Type' => 'application/json' ),
        CURLOPT_POSTFIELDS     => $json_data,
    ];
    
    // Инициализация запроса
    $ch = curl_init();    
    // установка параметров сеанса
    curl_setopt_array( $ch, $CurlOptions );
    // Выполнение запроса, в переменной хранится ответ от сервера
    $data = curl_exec($ch);
    $info = curl_getinfo($ch);
    if (curl_errno($ch) || substr($info['http_code'],0,1) !== '2') {
        // обработка ошибки, 
        // желательно пользовательским исключением, 
        // у которого есть свойства для $data и $info
        var_export(curl_error($ch));
        var_export($data);
        var_export($info);
    }

    При этом лишний раз напомню, ни для того, чтобы РНР выбросил исключение, ни для того чтобы прочитать текст ошибки выброшенного исключения, писать try… catch не надо! Этот оператор следует применять очень редко, и только когда мы точно знаем, как именно будем обрабатывать исключение.


    1. Sanchous98
      00.00.0000 00:00
      +1

      "try...catch нужно применять очень редко" - это очень спорное утверждение. Как раз в большинстве случаев исключение надо обрабатывать. В реальном проекте большинство случаев будет обрабатывать фреймворк, но это значит только то, что кто-то за вас сделал эту работу, а на самом то деле под капотом надо красиво логировать исключения, возвращать правильный код статуса, может красивы стэк трейс на экран вывести итп.


      1. FanatPHP
        00.00.0000 00:00

        Вы совершенно правы, исключения надо обрабатывать. Вот только пихать везде try...catch к нормальной обработке исключений не имеет ни малейшего отношения.


        Вот вы сами над своими словами задумайтесь:


        надо красиво логировать исключения, возвращать правильный код статуса, может красивы стэк трейс на экран вывести итп

        — и все это — внутри try...catch? Каждого? ;-)


        1. Sanchous98
          00.00.0000 00:00

          Я лишь привел пример того, что надо будет делать с исключением. Есть огромное количество примеров, когда обрабатывать исключение надо немедленно в том самом месте, где оно выброшено. Для логирование и стэк трейса конечно можно гораздо выше по стеку вызовов, но обернуть ошибку валидации, например, с указанием причины ошибки надо немедленно, пока у нас есть доступ к контексту валидации. Ошибки типа деления на ноль тоже необходимо оборачивать немедленно, пока в самом коде можно понять, что где и почему выбрасывает исключение, а не искать все это в стек трейсе. И таких примеров очень много, и даже сам php предусматривает механизм добавления новой информации на каждом уровне стека вызовов через передачу текущего исключения в конструктор нового через параметр previous, что мы активно в наших проектах используем для того, чтобы в каждом архитектурном слое добавить информации из контекста. Условный пример: пользователю не интересно видеть исключение от доктрины NoResultException. В рамках symfony/laravel это надо обернуть в HttpNotFoundException, который в свою очередь будет пойман фреймворком и обернут в html с 404 ошибкой или в соответствующий json. Пример очень простой, но если не вдаваться в детали, то примерно такой подход мы используем в наших проектах с многослойными архитектурами


          1. FanatPHP
            00.00.0000 00:00
            +1

            Вы привели очень кривой пример того, что надо будет делать с исключением. Классическую похапешную бессмыслицу.
            Давайте я повторю ещё раз: "чтобы логировать исключения, возвращать правильный код статуса, может красивы стэк трейс на экран вывести" трай кетч писать не надо.


            Новые примеры уже ближе к реальности.
            Но если дать себе труд подумать, то окажется, что исключение либо вообще не нужно, либо его можно точно так же обработать централизованно, без замусоривания кода бессмысленными try...catch.


            "Исключение с ошибкой валидации" — это бессмыслица, оксюморон. Исключение может возникнуть, если функция, которая уже использует данные, не может этого сделать. То есть, не может выполнить свою функцию. А при валидации возникает не ошибка, а собственно результат — валидные данные, или нет. Поскольку валидация и является функцией функции. И выбрасывать исключение она будет только если не может провалидировать данные. Но не тогда, когда может, и видит, что они не валидные.


            Вообще, рассуждая о ловле исключений, приучайтесь всегда упоминать конкретный сценарий обработки. Потому что поймать исключение много ума не надо. Но вот написать в catch что-то осмысленное получается далеко не всегда. Вот вы пишете, "Ошибки типа деления на ноль тоже необходимо оборачивать" — и? Что делать с ними? Где эта ошибка? В модели? В контроллере? Как ее обрабатывать? Следует ли любую операцию деления оборачивать? Без ответов на все эти вопросы разговор получается пустой.


            По поводу конверсии в HTTP исключения у меня есть готовый рецепт, вот неплохая идея: Centralized exception handling with Symfony and custom PHP attributes. Все-таки, ходить за каждым исключением с трай-кетчем, как за слоном с лопатой — это как-то совсем по-деревенски. Программисты мы, или нет, в конце концов? Зачем вообще тогда париться с исключениями? Сделать по-старинке, проверку результата функции на false. И замусоривать код проверками на каждый чих.


            Исключения придуманы, чтобы сделать код чище, а не чтобы постоянно спотыкаться об try..catch.


            Случаи, когда нужно поймать исключение действительно бывают. Но далеко не так часто, как это делают начинающие программисты, с их вечным "стэк трейс на экран вывести". Сказать "исключение надо обрабатывать!" любой дурак может. Но вот написать в catch что-то осмысленное куда сложнее. И любой разговор надо начинать именно с этого — с кода, который мы собираемся писать для обработки.


    1. HIntergalactic Автор
      00.00.0000 00:00

      Спасибо за комментарий! Немного поправил статью. На большее пока, к сожалению, нет ресурса.


  1. Sanchous98
    00.00.0000 00:00

    @FanatPHP Во-первых, по поводу исключений с ошибкой валидаций: laravel кидает исключение, ктторое мапится на 429 уод http при ошибке валидации. Это норма.

    Во-вторых, по поводу стек трейсов, логирования итп. Лично ВАМ этого делать не надо, потому что используя фреймворк, кто-то за ВАС уже это сделал. Это не значит, что теперь вам об исключениях не надо заботиться. Как только вы изолируетесь от фреймворков, или используете минималистичные фреймворки, это становится ВАШЕЙ проблемой. Это как раз случай автора.

    В-третьих, по поводу вашего решения. У меня при прочтении возникало только одно слово в голове: "зачем?!"(в оригинале грубее). Вы напрочь игнорируете принцип KISS, и вы напрочь игнорируете существующие решения. Ваше решение в отличии даже от первого варианта выглядит проигрышнее по 2ум причинам: 1) ваше решение актуально только для симфони, в то время как первое решение с обычным try с несколькими блоками catch работает везде и всегда 2) первое решение максимально простое, если не сказать что тупое, его поймет любой джун. Но если вас смущает, что в блоках catch почти одинаковый код, то для вас есть простое решение: из вашего решения выкидываете все, кроме интерфейса StatusCodeProvider, наследуете его от Throwable, что автоматически обяжет пользовательское исключение наследовать Exception или Error, а затем вместо нескольких catch блоков использовать один, который ловит StatusCodeProvider. Это если придумываете велосипед. В симфони просто реализуйте интерфейс HttpExceptionInterface, остальное за вас сделает фреймворк. К чему эта магия с глобальным перехватчиком ивентов симфони, рефлексией и атрибутами? Это называется Overengineered garbage code: вы решаете очень простую задачу каким-то хитровыдуманным способом. Нафига?! Вы любите острые ощущения в продакшене?

    Исключения действительно нужны для чистоты кода, но ваше решение - это прямая противоположность чистоте кода. Чистый код в первую очередь - это понятный каждому код, выполняющий четко свою задачу. Для простой задачи вы придумываете магическое решение, которое решается намного проще, если не пытаться ничего выдумывать. Не усложняйте жизнь себе и вашим коллегам.


    1. FanatPHP
      00.00.0000 00:00
      +1

      Мне кажется, вы опять запутались, и вернулись к своему первому комментарию.


      по поводу стек трейсов, логирования итп. Лично ВАМ этого делать не надо

      Давайте постараемся запомнить две вещи:


      1. Здесь никто здесь не говорит, что исключения вообще обрабатывать не надо.
      2. Что натыкивание try..catch по всему коду не является единственным способом обработки исключений.

      "стек трейсов, логирования итп." — вещи очень нужные, и мне в том числе. Я говорю, уже в который раз, не об этом. А том, что использовать для этого try-catch — глупость. С этим прекрасно справляется централизованный обработчик ошибок. И давайте уже не будем поднимать эту тему, хорошо?


      Это как раз случай автора.

      Нас не интересует случай автора. Код, в любом случае, будет один и тот же. Работа с курлом — это как раз один из примеров, когда использование try-catch вполне может понадобиться. К примеру, если просрочился токен авторизации, и надо запросить новый. Или чтобы сделать небольшую систему обработки отказов, повторяя при некоторых ошибках запрос несколько раз.


      Но при этом надо понимать, что в любом случае бросок исключения не завязан на его обработку. А у вас, похоже, эти понятия так в голове перемешались, что вы просто не различаете, что это, вообще-то, две разные, никак не связанные друг с другом задачи. В общем случае код должен просто кинуть исключение, и не думать о том, кто и как его будет ловить. Вот это и есть "случай автора". Код бросает исключение, и дальше все будет зависеть от потребностей и инфраструктуры. На начальном этапе разработки исключение будет вываливаться на экран, ближе к выкатке появится централизованный обработчик ошибок, который, в зависимости о окружения, будет показывать либо красивы стэк трейс, либо красиво логировать и возвращать HTML/JSON для 500. При этом сам код функции не поменяется.
      Дальше автор может захотеть систему обработки отказов, и обернуть вызов этой функции в try-catch, который проверяет тип ошибки, и если она подходит под определенные условия, то через небольшой таймаут пытается сделать запрос снова. А если не подходит под условия, то исключение перевыбрасывается, и ловится уже либо централизованным обработчиком, либо где-то еще. При этом сам код функции снова не поменялся. А сейчас у автора обработка исключения вкрячена прямо в код функции выполнения запроса, что, конечно же, никуда не годится. И я очень надеюсь, что вы с этим согласитесь.


      По поводу "моего" решения. Я не настаиваю, что оно лучшее. Это просто один из примеров того, что механического натыкивания try-catch и ручной конвертации исключений можно вполне избежать. И если присмотреться к своему коду, то выяснится, что такое решение найдется для всех массовых вариантов использования try-catch, в которых, как вы правильно заметили, "почти одинаковый код".


      1. Sanchous98
        00.00.0000 00:00

        С этим прекрасно справляется централизованный обработчик ошибок

        Для того, чтобы у вас был централизованный обработчик ошибок, вы должны написать его или воспользоваться готовым решением. Возможно я открою вам тайну, но симфони позволяет выкидывать ненужные компоненты

        Нас не интересует случай автора.

        С него все и началось, поэтому глупо от него отмахиваться. Такое ощущение, что вам просто не хватит аргументов, чтобы поспорить с тем, что данные пример - наглядное доказательство того, о чем я говорю

        бросок исключения не завязан на его обработку

        Какая глупость... Да будет вам известно, что исключение - это такой же легальный возврат из функции, как и return. Бывают случаи, когда вам возвращаемое значение из функции не нужно, но таких случаев единицы. Если исключение кидать, если его не надо обрабатывать или случаев, когда его надо обработать очень мало? Проще уже самому исключительные случаи предусмотреть и не кидать исключение. Наверное его кидают, потому что вариантов обработки, как и случаев, когда это действительно нужно много, и проще отдать это на усмотрение пользователю. С исключениями то же самое, что и возвращаемым значением: в единицах случаев нам надо не ловить исключение, чтобы уронить скрипт. В чуть большем количестве случаев можно обойтись глобальным перехватчиком. В остальных случаях нам почти всегда исключения надо ловить и правильно обрабатывать. Может не прямо в месте вызова функции, а на несколько уровней выше, но ловить надо, иначе у вас будут странности, типа того, что я порой вижу как на некоторых сайтах с упавшей базой прямо в браузер кидается PDOException

        По поводу "моего" решения. Я не настаиваю, что оно лучшее. Это просто один из примеров того, что механического натыкивания try-catch и ручной конвертации исключений можно вполне избежать.

        Такое решение не должно быть в пользовательском коде, потому что вы решаете простую задачу сложным путем. Помимо этого магия с атрибутами и рефлексией... Вместо слов, стоит процитировать 2 поговорки из Zen of Go: Clear is better then clever, Reflection is never clear. Максимально тупое, в хорошем смысле, решение всегда выигрышнее: оно понятно любому джуну, и вам не нужно будет тратить часы на объяснение того, каким образом работает решение подобно вашему. С более опытными разработчиками вы тоже потратите часы, но на объяснение причины того, почему так


        1. FanatPHP
          00.00.0000 00:00
          +1

          Мда, ну у вас и каша в голове :)
          Вы мне напоминаете Буратино из сказки, с его упрямиством и эээ… прямолинейностью. Помните?


          — Предположим, что у вас в кармане два яблока. Некто взял у вас одно яблоко. Сколько у вас осталось яблок?
          — Два.
          — Подумайте хорошенько. Буратино сморщился, — так здорово подумал.
          — Два… — Почему?>
          — Я же не отдам Некту яблоко, хоть он дерись!

          Вы смотрите на вещи очень буквально, и никак не можете абстрагироваться от прямолинейного отношения к исключениям, как к банальным ошибкам.


          Для того, чтобы у вас был централизованный обработчик ошибок, вы должны написать

          Должен, должен. Разговор не об этом. А про try..catch. Который не должен использоваться для репортинга ошибок. Если у вас еще нету централизованного обработчика, это не повод пихать везде трай с кетчем. Понимаете? Это же будет двойная работа, сначала писать его на каждый чих, а потом выковыривать, когда появится глобальный.


          глупо от него отмахиваться

          Вы опять ничего не поняли :)
          Случай автора нас не интересует, потому что он нам не принципиален. Он ничем не отличается от любого другого случая выброса исключения. И точно так же, как и в любом другом случае, связав его трай кетчем вы тут же ограничили себя в вариантах обработки.


          Да будет вам известно, что исключение — это такой же легальный возврат из функции, как и return.

          Здесь у вас совсем логика захромала :(
          Да, легальный. Но из этого никак не следует, что любую функцию надо оборачивать в трай-кетч! Ну неужели это непонятно? Исключение тем и отличается от возврата, что поймать его можно где угодно.


          в единицах случаев нам надо не ловить исключение, чтобы уронить скрипт. В чуть большем количестве случаев можно обойтись глобальным перехватчиком.

          И здесь :(
          Вы правда не понимаете, что это одно и то же?


          прямо в браузер кидается PDOException

          И здесь :(
          Уж это-то никаким боком не имеет отношения к обработке исключений.
          Если прямо в браузер кидается ошибка, то это значит, что на сервере криво настроено отображение ошибок. И исправлять надо именно это. А исключения обрабатывать обычным порядком. Ошибки PDO как раз относятся, за небольшим исключением, к тем самым исключениям, которые "роняют скрипт". И ничего глупее, чем заворачивать каждый запрос в трай кетч (чтобы предотвратить отображение ошибки в браузер) придумать невозможно. И я просто счастлив, что с моей подачи в мануале начали наконец-то показывать нормальную работу с ошибками, а не эти вездесущие трайкетчи. Хотя конечно за столько лет эти примеры попортили немало неокрепших умов. Но если сейчас посмотреть на примеры для работы с PDO, то там этого мусора становится все меньше.


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


          1. Sanchous98
            00.00.0000 00:00
            -1

            Вы смотрите на вещи очень буквально, и никак не можете абстрагироваться от прямолинейного отношения к исключениям, как к банальным ошибкам.

            Вы совершаете типичную ошибку: исключения != ошибка. В php к сожалению разница между Exception и Error условная. Но в других языках это не так. Именно ошибки ловить не стоит, а в других языках это вообще невозможно, потому что они действительно нужны чтобы просто уронить программу, но речь то про исключения, а исключения != ошибки. Вы же их мешаете в своих утверждениях. Пример исключения: передача 0 в делитель, потому что операция деления принимает 2 числовых операнда, но есть 1 случай, когда деление не сработает. Пример ошибки: передача строки в операцию деления, потому что деление работает только с числами. То, что php позволяет ловить и ошибки, и исключения - это, имхо, большое упущение, поэтому надеяться можно только на линтеры и code review.

            Он ничем не отличается от любого другого случая выброса исключения.

            Он отличается тем, о чем я вам твержу уже несколько комментариев: это случай, когда у вас нет глобального обработчика исключений

            Исключение тем и отличается от возврата, что поймать его можно где угодно.

            Опять же, разница исключений и ошибок: исключения стоит ловить сразу. Зачем мне на уровне контроллера оборачивать метод, в котором через несколько слоев есть вызов метода из доменного слоя, в котором есть деление и потенциально там может быть деление на 0? Нет, надо ловить исключение сразу. При чем возможно ловить его в самом доменном слое, а затем оборачивать в исключения доменного уровня.

            Если прямо в браузер кидается ошибка, то это значит, что на сервере криво настроено отображение ошибок.

            Опять же путаются исключения с ошибками. Я не знаю, что у вас за принципы, но даже если настроить отображение ошибок правильно, то вы просто ошибку замаскируете под статус код 500, чего пользователь в нормальном сервисе видеть никогда не должен.

            Смотрите на исключения не как на банальные ошибки

            В том то и дело, что как на ошибки смотрите на исключения вы. Вы же их мешаете в своих утверждениях, хотя это разные вещи, пусть и технически в php они практически не отличаются


            1. FanatPHP
              00.00.0000 00:00
              +1

              Вы так восхитительно боретесь с ветряными мельницами :)


              Он отличается тем, о чем я вам твержу уже несколько комментариев: это случай, когда у вас нет глобального обработчика исключений

              Вот это как раз хороший пример того самого ограничения, которые вы себе выдумали, как буратина своего Некту, и отчаянно с ним деретесь :)


              Сначала ваш воображаемый Некто предлагал вообще не обрабатывать исключения, и вы на него с пылом наскакивали. Хотя с вами никто не спорил. Теперь вот он отказывается делать глобальный обработчик ошибок. Вопрос — кто ему мешает-то?


              Вы правда не понимаете, что сейчас глупость говорите? Ну просто если включить банальный здравый смысл, то если у нас нет глобального обработчика ошибок,


              1. Кто нам мешает его сделать?
              2. Почему вместо того, чтобы его сделать, вы предлагаете на каждый чих писать кучу трай-кетчей? Вот это я никак не могу понять. И просто логически, но — главное — потому что в принципе невозможно каждую строчку, которая может выкинуть исключение, обернуть в трай-кетч. И у вас все равно будут неотловленные исключения. За что боролись, называется :)

              Или вот ещё из той же серии


              если настроить отображение ошибок правильно, то вы просто ошибку замаскируете под статус код 500, чего пользователь в нормальном сервисе видеть никогда не должен.

              Ну вот с чего вы это взяли-то? Во-первых, это не я путаю ошибки с исключениями, а РНР. Неотловленное исключение превращается в ошибку. Т.е. "задача сводится к предыдущей" :)
              А во-вторых, мы точно говорим о "правильной настройке отображения ошибок"? При правильной клиент получит HTML/JSON вариант 500 ошибки, как и положено. Что здесь-то вам не так?


              В целом, у меня к вам только одна просьба. Давайте не будем углубляться в дискуссию error vs. exception. Хотя там действительно можно провести несколько вариантов разделения, как формальных, типа warning/fatal error vs exception или Exception exception vs Error exception, так и неформальных, типа Control flow exceptions vs. Error exceptions, но все эти разделения довольно условны и не имеют прямого отношения к нашему вопросу. Не говоря уже про ваше, высосанное из пальца, при котором внезапно DivisionByZeroError это "исключение", а TypeError — уже "ошибка" :)


  1. abyrvalg
    00.00.0000 00:00
    +1

    Автору:

    • расматриваются базовые (очень базовые) возможности курла. При чём здесь пхп?

    • если пхп "при чём", то где curl_multi_init? Это же чуть ли не единственный способ распарралелить однопоточный пхп без использования event loop'ов.

    • если пхп "при чём", то где Guzzle? Лично мне он не очень-то нравится интерфейсной частью, но архитектурно он вполне хорош. И он работает.

    Вышеотписавшимся комментаторам:

    • каждому слою - свой эксепшен. Если на "верх" прилетает эксепшен от инфраструктуры -- у вас проблемы с архитектурой.

    • за сигнатуру Response | flase надо изгонять из общества. Даже похапешное ядро отказывается от этого.


    1. FanatPHP
      00.00.0000 00:00
      +1

      Видимо, пхп тут при том, что статья про php-curl и все примеры даются на PHP? Этак вы будете капризничать, если вам про mysqli будут рассказывать, под тем предлогом, что это обертка над Mysql API.
      Статья откровенно слабая, это правда. Но ваши придирки как-то совсем не в тему. Это описание начинающим своего опыта с php-curl. Она имеет прямое отношение к РНР. Про Guzzle и curl_multi он не слышал. Упомянуть их, возможно, и стоило, но только вскользь. Потому что это именно что про "очень базовые возможности курла". Да, статьи про базовые возможности тоже нужны. Как и про небазовые. Просто это два разных типа статей, и их не надо смешивать.


      1. abyrvalg
        00.00.0000 00:00

        mysqli разве не deprecated? Новость.

        Я по-дефолту плюсую статьи про пхп, просто потому что сейчас процентов 80 времени пишу на нём. Но поверхностное описание сишной либы, даже если примеры даны на пхп, не делают это статьёй про пхп.

        Давайте лучше про ексепшены поговорим?


        1. FanatPHP
          00.00.0000 00:00

          Вообще-то нет, mysqli не deprecated. Для меня новость, что для кого-то это новость :)


          У вас какое-то странное представление о РНР. Давайте я вам еще одну новость сообщу: 50% РНР — это тончайшие обертки над системными библиотеками/вендорскими API. От strtotime и тех же mysqli с курлом до imagick и OpenSSL. Повторюсь: если так капризничать, то про половину функций РНР писать будет нельзя, поскольку это будет "поверхностное описание сишной либы" :)
          Давайте все-таки попустимся немного, и договоримся, что если в РНР есть какой-то функционал, то писать про него можно, не оглядываясь на то, лежит под ним какая-то "сишная либа", или нет :)


          Вот про исключения я с вами в целом согласен. Каждому слою — свой эксепшен. Это осмысленная позиция. Она не противоречит моей, которая заключается в том, что обработка исключения должна быть абстрагирована от его выброса. А не жестко на него завязана, как предлагает коллега выше. "Танцуй, как будто никто не видит, бросай, как будто никто не ловит".


          1. abyrvalg
            00.00.0000 00:00

            Вообще-то нет, mysqli не deprecated. Для меня новость, что для кого-то это новость :)

            Спасибо. Видимо, это это была ложная память.

            У вас какое-то странное представление о РНР

            Нормальное представление, в сорцах ориентируюсь довольно свободно. Кроме поддержки utf. Лол. Три-четыре уровня макросов, пока доберёшься до кода, уже забудешь, зачем пришёл.


            1. abyrvalg
              00.00.0000 00:00

              Ой, простите. Не utf, конечно.
              I18N


            1. FanatPHP
              00.00.0000 00:00

              Верю, и уважаю. Сам я в сорцах "читаю со словарем".


              Но все же, если вернуться к вопросу о "системных либах".
              Если вам не нравится mysqli (хотя она к 8.2 по функционалу практически догнала PDO, в ней нет только именованных плейсхолдеров, а кое в чем и обогнала), давайте возьмем php-pgsql. Тоже ведь все построено вокруг системной либы, тончайшая обертка над Postgres C API. Это же не повод не писать статьи о том, как правильно пользоваться. Как раз наоборот, писать надо. Потому что нормальных материалов по ванильному РНР практически нет. Есть только типичный ад из прошлого века, со всем этим catch (PDOException $e) { echo$e->errorMessage(); } и проч.


              Ведь просто знать набор функций мало. Надо еще и с умом их применять. Вот взять хотя бы бессмысленные исключения, которые автор кидал в исходном варианте статьи, а потом тупо выводил. То есть вполне можно было обойтись die("Запрос не был успешен"); с тем же самым результатом.


              Или другой пример: в другой системной либе есть функция mysql_real_escape_string(). Сколько РНР сайтов светили и до сих пор светят инъекциями только потому что средний пхешник просто не понимает, для чего эта функция нужна и как ее применять? И таких примеров миллион. Так что статьи по правильному использованию "системных либ" нужны обязательно. Надеюсь, я вас убедил :)