Pull to refresh

Comments 88

Зачем перед именем класса всегда писать \?
namespace A\B\C;
class Exception extends \Exception {}

$a = new Exception('hi'); // $a - это объект класса A\B\C\Exception
$b = new \Exception('hi'); // $b - это объект класса Exception

$c = new ArrayObject; // фатальная ошибка, класс A\B\C\ArrayObject не найден

Использование пространств имен: переход к глобальной функции/константе

лично очень жду именнованные параметры в методах/функциях, надеялся, что, быть может, придут к консенсусу до PHP 7, но к сожалению нет :(
По-мне так просто сахарок. Ничего принципиально нового не вносит. Тренарник + isset() или empty() вполне себе годный для этих целей. Разве что цепочки не так красиво делаются, но цепочки это уже совсем экзотика.
Для красоты всегда хватало

function is(){
  foreach(func_get_args() as $v){
    if(!is_null($v)){
      return $v;
    }
  }
}

$value = is(null,null,false,true,1);
Дело в isset, а не в проверке на null.
function is(&$args) {
    foreach (func_get_args() as $v) {
        if (!is_null($v)) {
            return $v;
        }
    }
}

$arr = array('a' => 1, 'b' => false);

var_dump(is($arr['c'], $arr['b'], $arr['a']));

Работает как isset
Ага, почти:

<?php
ini_set('display_errors', true);
error_reporting(-1);

function is(&$args) {
    foreach (func_get_args() as $v) {
        if (!is_null($v)) {
            return $v;
        }
    }
}

$arr = array('a' => 1, 'b' => false);

var_dump(is($arr['a'], $arr['b'], $arr['c']));


PHP Notice:  Undefined index: c in test.php on line 15

Notice: Undefined index: c in test.php on line 15
int(1)
У меня 5.4.32 версия под винду. Уведомление о несуществующим индексе не выскакивает. Разбираться, почему так происходит, совсем не хочется.
Проверил на убунте, так же версия из 5.4 ветки, уведомление есть.
Печаль, что тут сказать. А счастье было близко.
Разбираться, почему так происходит, совсем не хочется.

Ответ содержится прямо в вопросе:
версия под винду

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

Так исторически сложилось — сервера с виндой, Internet Explorer, бюрократия…

А убунта личная.
<?php

function is() {
    foreach (func_get_args() as $v) {
        if (!is_null($v)) {
            return $v;
        }
    }
}

$arr = array('a' => 1, 'b' => false);

var_dump(is($arr['a'], $arr['b'], $arr['c']));


дерзай

PS сделать надо «default» return значение
Если принять тот факт, что первый параметр это «default» значение, то и эта задача решается.

function is($default = null, &$args1 = null, &$args2 = null, &$args3 = null) {
    for ($i = 1; func_num_args() > $i; $i++) {
        $value = func_get_arg($i);
        if (!is_null($value)) {
            return $value;
        }
    }
    return $default;
}

$arr = array();
$return = is(false, $arr['a'], $arr['b'], $arr['c']);
var_dump($return);
Чуть чуть PHP5.6

function is($default = null, ...$args) {
    foreach($args as $arg) {
         if (!is_null($arg)) {
             return $arg;
         }
    }

    return $default;
}

$arr = array();
$return = is(false, $arr['a'], $arr['b'], $arr['c']);
var_dump($return);
для 5.6 я так же описывал вариант, несколькими комментариями ниже.
Нужно добавить передачу параметров по ссылке. Иначе будут выскакивать уведомление о несуществующем индексе.
function is($default = null, &...$args) {
  ...
}



Магия…
А вы запустите мой-то пример вместо своего. У вас там первый и единственный параметр по ссылке передаётся, а вы именно туда отсутствующий подставили, вот и вся разгадка.
Вы абсолютно правы. Получилось повторить ошибку. Можно решить эту проблему с кучей входных параметров по ссылкам, с дефолтным значением null. Не слишком элегантно, но можно:

 function is(&$args1=null, &$args2=null, &$args3=null, &$args4=null, ...) {


Ещё вариант для версии 5.6
function is(&...$args) {
Да это-то понятно, но тем не менее писать
$blablabla ?? $default ?? 'None'

приятнее чем
isset($blablabla) ? $blablabla : (isset($default) ? $default : 'None')
Если приходится часто писать isset, это что-то не то с архитектурой.
Ну как сказать, внешние данные же принимать как-то нужно.
В том числе и опциональные. И не всегда можно обойтись указанием $default в $request->param().
+ данные моделей тоже могут быть необязательными.
$request->get('key', 'default')

Для «не всегда» — проверка на value lambda внутри обработчика default (как в Laravel):
$request->get('key', function() {
return result_of_some_computations;
});
Да, признаю, куда удобнее лепить многословное php-замыкание (небось еще и замкнуть внешние переменные придётся через use), чем написать простой one-liner.
Удобнее не нарушать инкапсуляцию. Количество кода — странный аргумент, так можно и переменные начать одной буквой называть.
Каким образом isset() нарушает принципы инкапсуляции?

Не спорю, что красивая архитектура зачастую влечёт за собой большее число абстракций и больше кода, но в данном случае я в упор не вижу, чем пример с замыканием лучше использования isset().
А раз не видно других отличий, то логично уделить внимание читаемости кода, не?
Вопрос в том, зачем нам вообще знать напрямую о наличии атрибутов модели вне модели. Такая необходимость — четкий признак применения антипаттерна anemic models.
Не все параметры, обрабатываемые в контроллере, являются полями модели.
В контроллере вообще не должны обрабатываться поля модели.

Касаемо же request-объекта — если нужно что-то сложнее, чем присвоение значения по умолчанию, это признак протекания бизнес-логики в контроллер.
Поля модели не должны, а поля пришедшие от пользователя должны. Нужно что-то сложнее. Конкретно, в зависимости от выбора пользователя выбрать какую модель дернуть и какой метод из контроллера. Соответственно, выбор пользователя тоже нужно проверить.
Какой дернуть метод — это уровень роутера.

Какую модель — мм, не могу придумать случая, который не сводится к передаче поля запроса в фабрику. Если вдруг что-то будет зависеть от наличия поля — мм, окей, это будет if ($request->has('foo')), куда тут всунуть оператор ??, все равно непонятно.
Какой дернуть метод — это уровень роутера.

Я про метод модели.
Если вдруг что-то будет зависеть от наличия поля — мм, окей, это будет if ($request->has('foo')), куда тут всунуть оператор ??, все равно непонятно.

Входящее поле не обязано быть таким простым, это может быть JSON, XML, массив и т.д. Его нужно будет распарсить в контроллере, выяснить нужные детали, а потом может и модель вызывать не придется, какой-нибудь редирект получится. И ваш пример с «has» ничем по сути не отличается от isset, вы ведь говорили именно о том, что не нужно проверять наличие существования полей в контроллере.
Я говорил именно про isset, намекая на две плохие практики — непосредственную работу с superglobals и anemic models. Касаемо JSON etc — _иногда_ это действительно надо, когда есть какой-то особый случай и такая необходимость возникает в конкретном методе 1 раз. В общем же случае — это признак «толстого контроллера» и звоночек, говорящий о необходимости декомпозиции: JSON принципиально ничем не отличается от form-urlencoded, и точно так же может преобразовываться в request и подлежать маршрутизации.
Я говорил именно про isset, намекая на две плохие практики — непосредственную работу с superglobals и anemic models.

Т.е. вы говорили про три несвязанные вещи, про isset, про superglobals и про anemic model. И почему-то это должно было оказаться аргументом против isset.
В общем же случае — это признак «толстого контроллера» и звоночек, говорящий о необходимости декомпозиции: JSON принципиально ничем не отличается от form-urlencoded, и точно так же может преобразовываться в request и подлежать маршрутизации.

В общем случае, нет никаких звоночков, задача контроллера и есть связь между пользователем (запросом) и моделью (бизнес-логикой). А толстый контроллер — это о прямом изменении модели в контроллере. А у нас речь об обработке сложных входящих пользовательских данных, не связанных с моделью.
Конечно, все может преобразовываться в request, только тогда мы придем к другому антипаттерну GodObject.
Частое использование isset в контроллере обычно сочетается с superglobals и anemic models. Вот и всё, что имелось ввиду :)

God Object не получится, если не пихать все в одну кучу. Из request-а может делаться FormRequest, из него Criteria и т.д.
Частое использование isset в контроллере обычно сочетается с superglobals и anemic models. Вот и всё, что имелось ввиду :)

Наконец-то выяснили, что вы имеете в виду. Писали вы совершенно другое.
Если приходится часто писать isset, это что-то не то с архитектурой.

Не все задачи есть сайты. Не все придерживаются MVC. Не каждое MVC такое, как у вас. Любые внешние данные это потенциальный isset. А также,
God Object не получится, если не пихать все в одну кучу. Из request-а может делаться FormRequest, из него Criteria и т.д.

Ага, а кто должен эти объекты производить?
это уровень роутера

Есть даже такой уровень? Мне казалось это все уровень фронт-контроллера.
Фронт-контроллер — это понятие довольно абстрактное. Много что туда относят: единая точка входа, подключение composer autoloader, инициализация DI, сборка реквеста, подключение какой-нибудь дебаг-консоли в отладочном режиме… Я предпочитаю не использовать этот термин вообще, он слишком широк.
ну как, подключение composer autoload я лично считаю процессом бутстрапинга, и это еще не фронт контроллер. Фронт контроллер в моем понимании это вот такой вот простой интерфейс (будем уже писать в терминах PHP7):

interface Controller {
    public function handle(Request $request): Response;
}


а уж что там внутри особо разницы не имеет, маршрутизаторы ли там, иссеты и ифы со свитчами…
В том-то и проблема с этим термином. Авторы Symfony, например, считают иначе:

symfony.com/doc/current/cookbook/configuration/front_controllers_and_kernel.html#the-front-controller
Есть уже гид как старые extensions переписывать чтобы в PHP7 работало? Типа XSLCache того же.
Со всем согласен, но BaseException — это какой-то отвратительный костыль
Вынужденный. Слишком уж много людей ленятся наследоваться от \Exception и отлавливать именно свои исключения :(
Можно было бы отнаследоваться от ErrorException. Его в 99% случаев используют по делу.

А код, смешивающий «свои» обработчики исключений с «олдскульной» обработкой engine errors, все равно ж сломается, не?
ErrorException extends Exception {

Документация

Тут скорее полезно именно добавление абстрактного \BaseException, после которого уже не будет возникать подобных случаев

BaseException (abstract)
 +- EngineException
 +- ParseException
 +- Exception
     +- ErrorException
     +- RuntimeException
         +- ...
     +- ...
А как введение еще одного базового класса поможет остаться работоспособным коду вида

$f = fopen(...);
if (!$f) {
$error = error_get_last();
// handle error…
}

?

Никак же, улетит исключение еще из fopen.

Другое дело, что, наверное, с этой точки зрения необработанное исключение лучше, чем catch чего-то «не того». Но кода, обрабатывающего php-ошибки «по-старинке», не заворачивающего их через set_error_handler, и при этом в принципе использующего исключения, я вообще не встречал на практике. Так что тут лечится какой-то невообразимый сферический говнокод в вакууме, а нормально написанный код, обрабатывающий ErrorException, придется переписывать.
Да, php был и остается живее всех живых, если еще после выхода PHP 7 проекты с веток 5.4 — 5.6 удастся почти безболезненно и быстро перевести, то цены не будет. В случае с python конечно грустно получилось, люди так до конца все и не перешли на 3 версию из за медленного перехода пакетов и библиотек.
Посмотрите на коммент ниже от Arilas

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

В общем, не вырастит этот язык до энтерпрайза, особенно с нынешними конкурентами. Нет, нет, писать большие проекты можно, но это как в м$ пейнте рисовать анимацию.
Да бросьте. Да у php есть свои недостатки в тех же стандартах, именование функций, но все это постепенно исправляется и улучшается от версии к версии. Свои задачи он прекрасно решает, а это низкий порок вхождения в язык, дешевизна разработки для бизнеса, просто огромное комьюнити которым не каждый язык может похвалиться. Да бывает что встречается плохой код, но это не от языка зависит, а от того, кто на нем пишет.
<irony> Простите был не прав, рфц принимаются на ура и не доводят контрибютеров до желания уйти! Реализация примесей — безупречна. А ооп, джависты обзавидуются! А скорость, еслиб не си, всё писали бы на php!!! синтаксис — эталон, который стоит в университетах подавать как учебный материал. </irony>

С точки зрения бизнеса у php есть право на жизнь, пока… Но сегодня написать «hello world» на play2, проще чем на symfony2. И не надо говорить про порок вхождения, я не о домашних сайтах на коленке.
«Порок вхождения»! Боже… это шедевр!
Рад, что и вам понравилось, но лавры не мои.
контрибютеров до желания уйти

Вы про драму со статическим тайп хинтингом?
В целом да, но ведь это уже не первый громкий уход.
Я уже даже доклад про «особенности» PHP 7 делал, очень много кривых и недоделанных возможностей:
1. Нет nullable, если что-то может вернуть null, тип указать нельзя (привет find по id)
2. declare(strict_types=1); — вот этот вот костыль просто говорит, что входить может int, выйдет string (оно если что отконвертирует), без него Type Hints — это конвертеры типов (написано string, передает объект, оно дергает __toString()).

Несколько примеров, и комменты снизу кода:
class Foo {
    public static function test() : self {
        return new Foo;
    }
}
class Bar extends Foo {
    public static function test() : parent {
        return new Bar;
    }
    public function action() : int {
        return 5;
    }
}

Как вам parent?
namespace Bar;
interface Foo {}
interface FooManager {
	public function bind():Foo;
}
class FooImpl implements Foo {}
class FooManagerImpl implements FooManager {
	public function bind():FooImpl {
		return new FooImpl();
	}
}
//Fatal error: Declaration of Bar\FooManagerImpl::bind() must be compatible with Bar\FooManager::bind(): Bar\Foo in *** on line 14

Нет переопределения return'а, в других языках есть
function foo(string $a):bool {
    return $a + 1;
}
var_dump(foo(5));//bool(true)

А почему бы и нет?
declare(strict_types=1);
function foo($a):float {
    return $a + 1;
}
var_dump(foo("asdf5"));//float(1)

Тоже нормально
declare(strict_types=1);
function foobar(float $abc): int {
    return ceil($abc + 1);
}
foobar(123.0);

А тут ошибка, ceil возвращает float (хотя строку в предыдущем примере отконвертировало)
В любом языке можно найти способ выстрелить себе в ногу
strict_types — это вынужденный костыль для BC. Отсутствие nullable return types — да, серьезный косяк. С parent странно, конечно; видимо, из-за проблем с производительностью не проверяется цепочка наследования; хотя я бы тут использовал интерфейс. С floor/ceil/round проблема в том, что на 32-битных системах невозможно в общем случае сконвертировать в int без потерь, полагаю.
Ну слушайте, чего вы хотите от альфы-то? Про такие вещи пишите в bugs.php.net, сделаете язык лучше.
Это не баги, это то, что вошло в PHP 7 и не будет изменено(новые RFC не принимаются в PHP 7).
1. По поводу Nullable — уже есть RFC, который висит очень давно, и даже не рассматривался (хотя ссылки на него есть в Return Type Hints)
2. По поводу переопределения типа возврата, в изначальном RFC по Return Type Hints это было, и даже изначально его таким приняли, потом вспыло про BC и про перекрестную имплементацию, в итоге убрали.
3. По поводу Scalar Type Hints, изначальный RFC был сделан очень интересно, там даже было оптимизировано выделение памяти для типов (по примеру статического анализатора типов в strict mode у hack)
4. По поводу нового оператора ??, зачем-то изобрели новый костыль, могли бы заюзать то, как это допустим в том же js: var name = obj.name || «Name»
5. По поводу нового оператора сравнения <=>, я не совсем понял зачем он (зачем сравнивать массивы и объекты?) а для чисел получать результат сравнения в виде -1 либо 0 (если равны) либо 1 (если больше) тоже не совсем понятно зачем.

Вообще на PHP 7 я возлагал большие надежды, сейчас смотрю на HACK от HHVM, там есть очень много плюшек и нормальные Type Hint's(даже для массивов, Векторов, Мап, Callable и т.д.). А их статический анализатор — это просто сказка, он находит 90% багов прямо во время сохранения файлов.
4. По поводу нового оператора ??, зачем-то изобрели новый костыль, могли бы заюзать то, как это допустим в том же js: var name = obj.name || «Name»
Во-первых, зачем использовать то, как в JS, если это не JS?

Во-вторых, $var || $smth имеет тип возвращаемый тип bool, менять это — ломать обратную совместимость.

В-третьих, «||» в JS и «??» в PHP ни разу не эквивалентны. $obj->name()['abc'] ?? $smth сработает в PHP, но не свалится в JS, если $obj не определён.
$obj->name()['abc'] ?? $smth

Оно разве отловит Exception о том, что мы пытаемся вызвать метод у null? Это Recoverable Exception, но все же в определении ?? не указано, что он Exception'ы отлавливает, если бы он так себя вел, то помойму использовать его вообще нельзя (если нужно наружу выкинуть Exception, то что тогда?)

Проверил:
<?php
$a = $obj->name()['abs'] ?? 'asfd';
var_dump($a);

Вывод:
Fatal error: Call to a member function name() on null in *** on line 2

То-есть он аналогичен js по поведению:
var a = obj.some() || 5

Uncaught ReferenceError: obj is not defined


Большинство php разработчиков пишет часто на js, сейчас такая специфика, очень много client-side кода, и разное поведение одного и того же функционала часто приводит к банальным ошибкам (в большом проекте всегда есть коммит, который фиксит strpos с === либо сравнение с -1 вместо false во многих случаях)
Да, действительно, тут я сглупил. isset поведёт себя так же. Скорее надо проверять что-то вроде

$obj->name->value ?? 'asdf';
Да, в данном случае сработало, но помойму это костыль, сразу через два null'а идти, напрямую дергать поля, а не методы, если у кого-то такое будет в коде — нужно по рукам сразу же :)
Во всех языках это встречается. В JS — вообще постоянно.
Нет nullable

Ну тут да, это проблема. К сожалению в момент накала страстей, когда принимали статический тайп хинтинг, разработчица которая ввела эти RFC просто скипнула, и из всех ее RFC (в числе которых был и нуллабл) сделали по сути только тайп хинтинг для скаляров. Думаю к версии 7.1 сделают.

оно если что отконвертирует

при strict_types=1 оно кинет ошибку, если вы что-то не то куда-то передали.

Как вам parent?

уже обсуждалось как-то. Не сказать что это частый кейс, но да, не приятно. Хотя в вашем примере пусть оно уж лучше Foo возвращает.
А почему бы и нет?

А что в этом примере плохого? Вы передали значение, которое нельзя без потери данных привести к флоту. В результате там 0.

А если так:
declare(strict_types=1);
function foo(float $a):float {
    return $a + 1;
}

var_dump(foo("asdf5"));
//Fatal error: Argument 1 passed to foo() must be of the type float, string given


или так:

declare(strict_types=1);
function foo(float $a):float {
    return $a + 1;
}

var_dump(foo("asdf5"));
//Fatal error: Argument 1 passed to foo() must be of the type float, string given
// к слову раньше там писало про catchable error....

Если сравнить strict types с тем же HACK, то он обозначает, что в файле должны быть указаны все типы, для всех входящих/выходящих переменных, в PHP 7 strict_types просто отключает некоторые конвертирования типов (то-есть если указан int, а передается string, он не будет строку в int перегонять).

Я знаю как сделать чтобы код, который я скинул работал, проблема в другом, что все равно RFC очень поверхностные. Что дает strict mode в HACK:
1. Полный статический анализ кода, и типов каждой переменной (в hh_client можно узнать тип каждой переменной, указав номер строки и символа, также можно получить автодополнение в любой редактор для любой части кода)
2. Из-за того, что типы известны, есть экономия памяти, потому что не нужно делать структуру как в PHP для хранения значения
3. Исходя из пункта выше, JIT некоторые части кода после прогрева переводит в нативный код (если помните HPHPc, то он компилил тогда весь проект, здесь, только если может, и может отдельные части)

Что дает strict_types в PHP 7:
1. Дополнительные проверки в runtime для того, чтобы проверить, что переменная действительно нужного типа
2. Нельзя статически анализировать(нет полной картины типов, из-за отсутствия Nullable)
Исходя из пункта выше, JIT некоторые части кода после прогрева переводит в нативный код


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

В PHP7 нет JIT, а к 8-ой версии в принципе не проблема это сделать. Ну и да, судя по тому что от билда к билду поведение того же тайп хинтинга меняется, можно судить что это еще не стабильная реализация. И да, если вы считаете что-то багом, надо бежать в багтрекер а не пилить RFC.
За все время использования PHP в своих проектах, так и не придумал задачи с использованием генераторов… Пользуется, кто ими и для какой цели?
Как альтернатива lock'ам в cron job'ах, из PDOStatement каждый раз выбираем только одну вещь(ей сразу ставим статус, чтобы она не подходила под условия выборки), и если запрос достаточно большой, то это дает профит(несколько cron job одновременно в несколько процессов могут спокойно несколько часов работать, что дает хороший профит).
Ну самая стандартная задача генерировать id внутри транзакции еще какой-то дополнительный.
Lazy evaluation/Stream function типа чтения из файлов и т.д. Ну вообщем-то не сказать, чтоб обширное поле деятельности было, но вполне себе имеет право на жизнь.
Например, если нам нужно перевести массив в вид [userId => user]. Преимущество — не расходуется память на лишнюю переменную.

К тому же, разница на лицо

function users()
{
    foreach($users as $user)
        yield $user['id'] => $user;
}

function users()
{
    $_users = [];

    foreach($users as $user)
        $_users[$user['id']] = $user;

    return $_users;
}
Вы посмотрите языки, где они есть давно, это лучший способ их понят. Мне вспоминается хорошая статья Вани Сагалаева, где он рассказывал как он применил генераторы для конкретной задачи (язык там — «Пайтон», но кажется это не мешает восприятию): softwaremaniacs.org/blog/2010/09/18/ijson
А вот я сейчас прямо пишу статью, где парсер ijson на Rust реализую, и там будет как раз очень показательный пример полезности `yield`. Завтра, надеюсь, допишу.
Буду ждать статью.
Самый распространенный пример использования генераторов — реализация xrange для экономии памяти на больших размерах массивов.

А еще есть такие клевые штуки как корутины.
Самый распространенный пример использования генераторов — реализация xrange для экономии памяти на больших размерах массивов.
xrange с лёгкостью заменяется циклом. Это где же это самый распространённый пример?
Да как бы все на что-то слегкостью заменяется. Генераторы это ни что иное как сахар для итераторов. В контексте питона удобно, в контексте PHP единственный кейс когда мне пригодились генераторы — корутины.

А ну и еще стрим-парсеры удобно делать.
Ну, короткого синтаксиса (генераторный выражений из «Пайтона») мне в ПХП не хватает, но в основном — годно. Для интереса запилил пайтонячий itertools на генераторах ПХП, получилось вполне съедобно.
Как правило это длинные задачи, когда процесс работает больше секунд. Например работа в режиме демона с сокетами, или парсинг, обсчёт большого количества данных, постороение каких нибудь кайнтеров или коэфициентов.
Заключение особо порадовало :) PHP — уже велик! (ударение поставьте сами :))
С ударениями там 4 варианта)
Круто! — уходит функция at(), которую я вечно пишу где-то

function at($array, $index, $default)
{
    return isset($array[$index]) ? $array[$index] : $default;
}

...

$name = at($_POST, 'name', '');
// Стало
$name = $_POST['name'] ?? '';
Давно уже можно так:
<?php
$a = $_POST['a'] ?: 'asdf';
var_dump($a);

И без всякого ??
Надо будет проверить, насколько работает «Элвис». Он в PHP относительно недавно, а т.к. я не профессионал, за этим не слежу.
1) Как я понял, он работает по вычислимости в true, т.е. дока скрывает, что будет, когда такой переменной вообще нет.
2) По той же причине для строки '0', скорее всего, вернёт значение по умолчанию.
$a ?: $b это аналог $a? $a: $b, а $a ?? $b — isset($a)? $a: $b

совершенно разные вещи.
Sign up to leave a comment.

Articles