Verification: a143cc29221c9be0

Php catch warnings try catch

Что такое «исключения»?

Вы удивитесь, но даже в официальном руководстве PHP нет ответа на этот вопрос. Там как бы сразу предполагается, что программисты уже знают что это такое или понимают это из семантики самого слова «исключения».

Так вот под «исключением» понимается обработка исключительных ситуаций. Простой пример из «больших» языков.

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

Поэтому такие критически важные участки кода оборачивают в специальный блок, который позволяет программе продолжить работу в случае «вылета».

На Паскале это может выглядеть так:

try
    TreeView.LoadFromStream(stream);
except
    showmessage('Ошибка загрузки потока. Что-то с системой...');
end;

В данном примере блок try — это т.н. защищённый блок кода — если в нём возникнет исключение, то управление будет передано в блок except — это обработчик исключения.

В другом примере используется блок finally.

FIniFile := TRegIniFile.Create(s);

try
    FIniFile.ReadString('', 'data','');
    ...
finally
    FIniFile.Free;
end;

Здесь переменная FIniFile считывает некие данные, но если они ошибочные, то возникает исключение. Но здесь главная задача — это корректно освободить память и это происходит в блоке finally, который гарантированно сработает как при возникновении исключения, так и без него.

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

Исключения в PHP

В PHP исключения реализуются похожим образом.

try {
    ... защищённый участок кода ...
} catch (Exception $e) {
    ... код, если возникла исключительная ситуация ...
}

Или вариант с finally:

try {
    ... защищённый участок кода ...
} catch (Exception $e) {
    ... код, если возникла исключительная ситуация ...
} finally {
    ... код с гарантированный выполнением ...
}

При этом можно использовать как try/catch/finally, так и try/finally.

Казалось бы всё просто, но как обычно в PHP есть нюансы.

«Спотыкачка» для новичков

Рассмотрим простой пример. Есть функция для деления двух чисел.

function my1($a, $b)
{
    try {
        $r = $a / $b;
    } catch (Exception $e) {
        $r = 'Нельзя делить на ноль';
    }

    return $r;
}

echo my1(1, 0);

Мы ожидаем, что выполнение этого выведет «Нельзя делить на ноль». Однако на самом деле мы получаем сообщение от PHP «Warning: Division by zero». То есть блок catch не сработал.

Почему так происходит?

Не все ошибки — исключения

В PHP все ошибки имеют тип в виде предопределённых констант: E_ERROR, E_WARNING, E_NOTICE и т.д.

Так вот ошибки E_WARNING не являются исключительной ситуацией, поскольку программа может продолжить своё выполнение дальше. Если после echo my1(1, 0); мы разместим ещё какой-то код, то PHP его выполнит.

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

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

Обработка данных

PHP в первую очередь это процедурное программирование, где функции самостоятельно умеют обрабатывать входящие данные. Хороший пример это функции для текста, например strpos(), которая возвращает не только позицию вхождения, но и FALSE, если его нет.

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

Наш пример можно было бы переписать так:

function my2($a, $b)
{
    if ($b == 0)
        return false; // 'Нельзя делить на ноль';
    else
        return $a / $b;
}

if ($r = my2(1, 0) !== FALSE)
    echo $r;
else
    echo 'Ошибка';

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

Как не нужно делать

Плохая практика — вместо обработки входящих данных, генерировать исключение. В PHP можно сгенерировать исключение вручную с помощью throw.

function my3($a, $b)
{
    if ($b === 0)
        throw new Exception('Нельзя делить на ноль');
    else
        return $a / $b;
}

try {
    echo my3(1, 0);
} catch (Exception $e) {
    echo $e->getMessage();
}

Здесь, если $b равен нулю, генерируется исключение. Проблема здесь в том, что использовать такую функцию приходится только в блоке try/catch, что не только усложнят код, но и смешивает логику приложения с исключениями. Причём этот пример достаточно простой: если копнуть глубже, то исключения могут быть разных типов (классов) и их «ветвистая» обработка безумно усложняет код.

Правильны подход

Функция должна сама обрабатывать свои исключения.

function my4($a, $b)
{
    try {
        if ($b === 0) 
            throw new Exception('Нельзя делить на ноль');
        else
            return $a / $b;

    } catch (Exception $e) {
        return $e->getMessage();
    }
}

echo my4(1, 0);

Если $b равен нулю, генерируется исключение, которое тут же ловится в блоке catch. Это как бы совмещённый подход, когда функция и проверяет данные, и генерирует исключения. Поскольку можно отследить разные ситуации, то можно сгенерировать и разные исключения. Например если в функцию будет передано не число, а строка или другой нечисловой тип данных, то возвратить либо сообщение об ошибке, либо просто 0.

Как отследить WARNING

Важно понимать, что в PHP ошибки и исключения — разные вещи и работают они по разному. По большому счёту можно вообще обойтись без исключений, поскольку функции PHP сами по себе неплохо справляются с нештатными ситуациями.

Однако в PHP есть механизм по «превращению» ошибок в исключения. Для этого следует переопределить стандартный обработчик ошибок с помощью функции set_error_handler().

function exception_error_handler($severity, $message, $file, $line) {
    if (!(error_reporting() & $severity)) {
        // Этот код ошибки не входит в error_reporting
        return;
    }
    throw new ErrorException($message, 0, $severity, $file, $line);
}

set_error_handler('exception_error_handler');

После этого мы можем вызвать самый первый пример с my1()

echo my1(1, 0);

и получить как и ожидаем: «Нельзя делить на ноль».

Новые особенности

Разработчики языка отмечают, что PHP 8 до сих пор находится в стадии активной разработки, поэтому список новых функций со временем будет увеличиваться.

Union Types

Учитывая динамически типизированную природу PHP, существует множество случаев, когда использование типов объединения может быть полезным. Union Types представляют собой совокупность двух или более типов, которые указывают, что можно использовать любой из них.

public function foo(Foo|Bar $input): int|float;

При этом void никогда не может быть частью Union Types, так как он означает «вообще никакого возвращаемого значения». Кроме того, с помощью |null или ?-нотации можно объявить nullable-объединения:

public function foo(Foo|null $foo): void;

public function bar(?Bar $bar): void;

JIT

Разработчики обещают улучшения в производительности PHP 8 в том числе благодаря стратегии компиляции JIT (just in time, "в нужный момент"). В JIT код сначала переводится в промежуточное представление, и только потом — в машинный код, зависящий от архитектуры. Это значит, что он в ходе исполнения будет превращаться в машинный код, который будет выполняться не виртуальной машиной Zend VM, на которой построен язык, а непосредственно на процессоре.

Nullsafe-оператор

Если вы работали с оператором объединения с null, вы уже знакомы с его недостатками: его нельзя применить в цепочке вызовов методов. Вместо этого вам нужны промежуточные проверки или придется полагаться на optional помощников, предоставляемых некоторыми платформами:

$startDate = $dateAsString = $booking->getStartDate();

$dateAsString = $startDate ? $startDate->asDateTimeString() : null;

С добавлением nullsafe-оператора разработчик может видеть поведение методов, подобное поведению при объединению с null.

$dateAsString = $booking->getStartDate()?->asDateTimeString();

Именованные аргументы

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

function foo(string $a, string $b, ?string $c = null, ?string $d = null)
{ /* … */ }
foo(
    b: 'value b',
    a: 'value a',
    d: 'value d',
);

Атрибуты

Атрибуты — в других языках программирования известные как аннотации, предлагают новый способ добавить метаданные в классы, без необходимости парсить докблок-комментарии.

Вот пример того, как выглядят атрибуты:

use App\Attributes\ExampleAttribute;
@@ExampleAttribute
class Foo
{
    @@ExampleAttribute
    public const FOO = 'foo';

    @@ExampleAttribute
    public $x;

    @@ExampleAttribute
    public function foo(@@ExampleAttribute $bar) { }
}
@@Attribute
class ExampleAttribute
{
    public $value;

    public function __construct($value)
    {
        $this->value = $value;
    }
}

Выражение соответствия

Выражение match можно назвать старшим братом выражения switch — оно может возвращать значения, комбинировать условия, и при этом не требует break операторов. Кроме того, match не выполняет никакого приведения типов.

$result = match($input) {
    0 => "hello",
  '1', '2', '3' => "world",
};

Краткий синтаксис для объединения свойств класса и конструктора

В PHP 8 появится синтаксический сахар для создания объектов значений или объектов передачи данных. Вместо отдельного указания определения свойств класса и конструктора для них, PHP теперь может объединять их в один краткий синтаксис.

Вместо такого кода:

class Money 
{
    public Currency $currency;

    public int $amount;

    public function __construct(
        Currency $currency,
        int $amount,
    ) {
        $this->currency = $currency;
        $this->amount = $amount;
    }
}

Теперь можно будет кратко писать вот так:

class Money 
{
    public function __construct(
        public Currency $currency,
        public int $amount,
    ) {}
}

Новый тип возврата static

Несмотря на то, что в PHP уже были возвращаемые типы — self и parent, до сих пор static не был допустимым типом возврата для этого языка программирования. Учитывая динамически типизированный характер PHP, эта функция будет полезна для многих разработчиков.

class Foo
{
    public function test(): static
    {
        return new static();
    }
}

Новый тип mixed

С добавлением скалярных типов в PHP 7, обнуляемых — в PHP 7.1, и, наконец, — объединенных типов в 8.0, PHP-разработчики могут явно объявлять информацию о типе для большинства параметров функции, callable-функций, а также свойств класса. Тем не менее, PHP не всегда поддерживает типы, и, скорее всего, он иногда будет пропускать информацию о них. Это приводит к тому, что значение будет неоднозначным, когда отсутствует информация о типе.

Явный mixed тип позволит разработчикам добавлять типы в параметры, свойства класса и возвращаемые значения из функции, чтобы указать, что информация о типе не была забыта, а её просто нельзя указать более точно.

mixed сам по себе означает один из этих типов:

  • array
  • bool
  • callable
  • int
  • float
  • null
  • object
  • resource
  • string

Обратите внимание, что mixed также может использоваться как параметр или тип свойства, а не только как тип возвращаемого значения.

Также обратите внимание, что, поскольку mixed уже включает null, его нельзя делать nullable. Такой код вызовет ошибку:

// Fatal error: Mixed types cannot be nullable, null is already part of the mixed type.
function bar(): ?mixed {}

Зачем изучать PHP: рейтинг, перспективы, сферы применения

Throw-выражения

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

$triggerError = fn () => throw new MyError();

$foo = $bar['offset'] ?? throw new OffsetDoesNotExist('offset');

Наследование частными методами

Раньше PHP применял одинаковые проверки наследования для public, protected и private- методов. Другими словами: private методы должны были быть определены так же, как protected и public. При этом такое определение не имеет смысла, так как private методы не будут доступны дочерним классам.

Это обновление изменило поведение таких методов — теперь проверки наследования больше не выполняются для private методов. Кроме того, использование final private function и раньше не имело особого смысла, поэтому теперь оно вызовет предупреждение:

Warning: Private methods cannot be final as they are never overridden by other classes

Weak maps

Слабая карта — это набор объектов, на которые в коде слабо ссылаются ключи, что может привести к их удалению сборщиками мусора. В PHP 8 добавляется класс WeakMaps для создания сохранения ссылки на объект, при этом она не препятствует удалению самого объекта.

Например, возьмем пример ORM — они часто реализуют кэши, где содержатся ссылки на классы сущностей, чтобы улучшить производительность отношений между сущностями. Эти сущности не могут быть собраны сборщиком мусора, пока кэш имеет ссылку на них, даже если кэш является единственной реальной ссылкой на них. Если теперь кэш будет использовать слабые ссылки и WeakMaps, то PHP сможет справляться с мусором. Особенно в случае с ORM, которые могут управлять несколькими сотнями, если не тысячами объектов в ходе запроса.

Пример использования WeakMaps в PHP 8:

class Foo 
{
    private WeakMap $cache;

    public function getSomethingWithCaching(object $obj): object
    {
        return $this->cache[$obj]
           ??= $this->computeSomethingExpensive($obj);
    }
}

::class на объектах

Небольшая, но полезная новая фича: теперь можно использовать ::class на объектах, вместо того, чтобы использовать get_class(). Работает так же, как и get_class().

$foo = new Foo();

var_dump($foo::class);

Неименованные исключения

Всякий раз, когда вы хотели перехватить исключение до PHP 8, нужно было сохранить его в переменной независимо от того, использовали ли вы эту переменную или нет. Теперь можно перехватить исключения без указания переменной.

Если раньше программисту приходилось писать такой код:

try {
    // Something goes wrong
} catch (MySpecialException $exception) {
    Log::error("Something went wrong");
}

То в PHP 8 это будет выглядеть уже так:

try {
    // Something goes wrong
} catch (MySpecialException) {
    Log::error("Something went wrong");
}

Обратите внимание, что необходимо всегда указывать тип, поскольку PHP 8 не разрешает иметь пустой catch. Если вы хотите перехватить все исключения и ошибки, вы можете использовать их Throwable как тип исключения.

Завершающая запятая в списках параметров

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

public function(
    string $parameterA,
    int $parameterB,
    Foo $objectfoo,
) {
    // …
}

Обратите внимание на запятую после $objectfoo.

Создать DateTime объекты из интерфейса

Вы уже можете создать DateTime объект из DateTimeImmutable объекта. Однако наоборот в PHP было сделать очень сложно. Теперь в языке появился новый обобщенный способ конвертировать эти объекты друг в друга.

DateTime::createFromImmutable($immutableDateTime)DateTime::createFromInterface()DatetimeImmutable::createFromInterface()DateTimeDateTimeImmutable
DateTime::createFromInterface(DateTimeInterface $other);

DateTimeImmutable::createFromInterface(DateTimeInterface $other);

Новый Stringable интерфейс

Интерфейс Stringable можно использовать для аннотации типов или имплементации метода __toString (). Кроме того, всякий раз, когда класс, содержащий __toString (), будет автоматически имплементировать этот интерфейс "под капотом", и нет необходимости вручную реализовывать его.

class Foo
{
    public function __toString(): string
    {
        return 'foo';
    }
}

function bar(Stringable $stringable) { /* … */ }

bar(new Foo());
bar('abc');

Новая функция проверки str_contains()

Наконец-то на PHP больше не нужно полагаться на strpos(), чтобы узнать, содержит ли строка другую строку.

Вместо этого:

if (strpos('string with lots of words', 'words') !== false) { /* … */ }

Можно использовать вот это:

if (str_contains('string with lots of words', 'words')) { /* … */ }

Новые функции str_starts_with() и str_ends_with()

Ещё две давно назревшие функции. str_starts_with() проверяет, начинается ли строка с другой строки, и возвращает логическое значение ( true/ false).

str_ends_with() проверяет, заканчивается ли строка другой строкой, и возвращает логическое значение ( true/ false).

str_starts_with('haystack', 'hay'); // true
str_ends_with('haystack', 'stack'); // true

Новая функция деления на 0 fdiv()

Новая fdiv() функция делает процессы, похожие на функции fmod()и intdiv() функции — позволяет делить на 0. Вместо ошибки в результате вы получите INF-INF или NaN, в зависимости от случая.

Новая функция get_debug_type()

Функция get_debug_type() возвращает тип переменной. При этом get_debug_type() возвращает более полезный вывод для массивов, строк, анонимных классов и объектов, чем стандартный gettype().

Например, вызов gettype() для класса \Foo\Bar вернет object. Использование get_debug_type() вернет имя класса.

Новая функция get_resource_id()

Ресурсы — это специальные переменные в PHP, ссылающиеся на внешние ресурсы. Например, соединение MySQL или обработчик файла — каждому из них присваивается идентификатор, хотя ранее единственным способом узнать, что это идентификатор, было преобразование ресурса в int:

$resourceId = (int) $resource;

PHP 8 добавляет get_resource_id() функции, делая эту операцию более очевидной и безопасной для типов:

$resourceId = get_resource_id($resource);

Улучшение абстрактных методов трейтов

Трейты могут содержать абстрактные методы, которые должны быть реализованы классами, использующими их. При этом важно, что до PHP 8 сигнатура этих реализаций методов не проверялась. То есть раньше приходилось писать так:

trait Test {
    abstract public function test(int $input): int;
}

class UsesTrait
{
    use Test;

    public function test($input)
    {
        return $input;
    }
}

PHP 8 будет выполнять правильную проверку сигнатуры метода при использовании признака и реализации его абстрактных методов. Это означает, что вам нужно написать теперь так:

class UsesTrait
{
    use Test;

    public function test(int $input): int
    {
        return $input;
    }
}

Объект реализации token_get_all()

token_get_all() в настоящее время возвращает токены либо в виде односимвольной строки, либо в виде массива с идентификатором токена, текстом токена и номером строки. Это обновление языка предлагает добавить альтернативу token_get_all (), которая вместо этого возвращает массив объектов. Это уменьшает использование памяти и делает код, работающий с токенами, более читабельным.

Изменения синтаксиса переменных

В этом обновлении изменились правила синтаксиса при работе с переменными в PHP. Например, при их разыменовывании.

Аннотация аргументов внутренней функции и типов возвращаемых значений

Теперь разработчики смогут добавлять информацию о типе внутренних функций. Это была давняя проблема языка, и, наконец, её удалось решить. Это означает, что внутренние функции и методы будут иметь полную информацию о типе в Reflection.

Запрет на выключение JSON

В настоящее время возможно скомпилировать PHP без расширения JSON с помощью ./configure --disable-json. Тем не менее, JSON чрезвычайно полезен, потому что он широко используется во многих случаях — в работе сайтов, выходных данных журналов, в качестве формата данных, который можно использовать для обмена данными со многими приложениями и языками программирования. Поэтому разработчики языка запретили выключать JSON.