Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
PHP, начиная с 7 версии, кардинально изменился. Код стал куда быстрее и надёжнее, и писать его стало намного приятнее. Но вот, уже релиз 8 версии! Ноябрь 26, 2020 — примерно на год раньше, чем обещали сами разработчики. И всё же, не смотря на это, мажорная версия получилась особенно удачной. В этой статье я попытаюсь выложить основные приятные изменения, которые мы должны знать.
1. JIT
Как говорят сами разработчики, они выжали максимум производительности в 7 версии (тем самым сделав PHP наиболее шустрым среди динамических ЯПов). Для подальшего ускорения, без JIT-компилятора не обойтись. Справедливости ради, стоит сказать, что для веб-приложений использование JIT не сильно улучшает скорость обработки запросов (в некоторых случаях скорость будет даже меньше, чем без него). А вот, где нужно выполнять много математических операций — там прирост скорости очень даже значительный. Например, теперь можно делать такие безумные вещи, как ИИ на PHP.
Включить JIT можно в настройках opcache
в файле php.ini
.
Подробнее 1 | Подробнее 2 | Подробнее 3
2. Аннотации/Атрибуты (Attributes)
Все мы помним, как раньше на Symfony код писался на языке комментариев. Очень радует, что такое теперь прекратится, и можно будет использовать подсказки любимой IDE, функция "Find usages", и даже рефакторинг!
Забавно, что символ #
также можно было использовать для создания комментариев. Так что ничего не меняется в этом мире.
Было очень много споров о синтаксисе для атрибутов, но приняли Rust-like синтаксис:
#[ORM\Entity]
#[ORM\Table("user")]
class User
{
#[ORM\Id, ORM\Column("integer"), ORM\GeneratedValue]
private $id;
#[ORM\Column("string", ORM\Column::UNIQUE)]
#[Assert\Email(["message" => "The email '{{ value }}' is not a valid email."])]
private $email;
}
Подробнее 1 | Атрибуты в Symfony
3. Именованые параметры (Named Arguments)
Если вы когда-либо видели код, где есть булевы параметры, то понимаете насколько он ужасен. Ещё хуже, когда этих параметров 8 штук, 6 из которых имеют значение по-умолчанию, а вам нужно изменить значение последнего параметра.
К примеру, код для использования библиотеки phpamqplib:
$channel->queue_declare($queue, false, true, false, false);
// ...
$channel->basic_consume($queue, '', false, false, false, false, [$this, 'consume']);
С использованием именованых параметров, код становится намного легче читать:
$channel->queue_declare($queue, durable: true, auto_delete: false);
// ...
$channel->basic_consume($queue, callback: [$this, 'consume']);
Ещё несколько примеров:
htmlspecialchars($string, default, default, false);
// vs
htmlspecialchars($string, double_encode: false);
Внимание! Можно также использовать ассоциативные массивы для именованых параметров (и наоборот).
$params = ['start_index' => 0, 'num' => 100, 'value' => 50];
$arr = array_fill(...$params);
function test(...$args) { var_dump($args); }
test(1, 2, 3, a: 'a', b: 'b');
// [1, 2, 3, "a" => "a", "b" => "b"]
Подробнее
4. Оператор безопасного null (Nullsafe operator)
Null — сам по себе не очень хорошая штука (даже очень плохая). Когда функция возвращает null
, то в каждом месте, где идёт её вызов, программист обязан проверить на null
. И это приводит к ужасным последствиям.
$session = Session::find(123);
if ($session !== null) {
$user = $session->user;
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$country = $address->country;
}
}
}
По хорошему, должен быть метод Session::findOrFail
, который будет кидать исключение в случае отсутствия результата. Но когда эти методы диктует фреймворк, то мы не можем ничего сделать. Единственное, это проверять каждый раз на null
либо, где это уместно, использовать ?->
.
Да, с операторомnullsafe
код станет немного лучше, но всё же это не повод возвращатьnull
.
$country = $session?->user?->getAddress()?->country;
Этот код нельзя назвать чистым, только лишь от части. Для чистого кода, нужно использовать шаблон Null Object, либо выбрасывать exception. Идеальным вариантом было б:
$country = $session->user->getAddress()->country;
Поэтому, если возможно с вашей стороны, никогда не возвращайте null
(к Римлянам 12:18).
Также интересным моментом в использовании nullsafe есть то, что при вызове метода с помощью ?->
, параметры будут обработаны только если объект не null
:
function expensive_function() {
var_dump('will not be executed');
}
$foo = null;
$foo?->bar(expensive_function());
5. Оператор выбора match (Match expression v2)
Для начала покажу код до и после:
$v = 1;
switch ($v) {
case 0:
$result = 'Foo';
break;
case 1:
$result = 'Bar';
break;
case 2:
$result = 'Baz';
break;
}
echo $result; // Bar
VS
$v = 1;
echo match ($v) {
0 => 'Foo',
1 => 'Bar',
2 => 'Baz',
}; // Bar
Как видим, это очень приятный оператор для выбора значений, который удобно заменяет switch
.
Но есть очень важное отчилие switch
от match
: первый сравнивает нестрого ==
, а во втором производится строгое ===
сравнение.
Наглядный пример различия:
switch ('foo') {
case 0:
$result = "Oh no!\n";
break;
case 'foo':
$result = "This is what I expected\n";
break;
}
echo $result;
// Oh no!
VS
echo match ('foo') {
0 => "Oh no!\n",
'foo' => "This is what I expected\n",
};
// This is what I expected
В PHP8 этот пример со switch работает по другому, далее рассмотрим это.
Также, сравниваемыми значениями оператора match
могут быть выражения. При этом, будут выполнены только те, пока не будет найден первый совпадающий вариант:
$result = match ($x) {
foo() => ...,
$this->bar() => ..., // bar() isn't called if foo() matched with $x
$this->baz => ...,
// etc.
};
6. Адекватное приведение строки в число (Saner string to number comparisons)
Проблема
$validValues = ["foo", "bar", "baz"];
$value = 0;
var_dump(in_array($value, $validValues));
// bool(true) ???
Это происходит потому, что при нестрогом ==
сравнении строки с числом, строка приводится к числу, то-есть, например (int)"foobar"
даёт 0
.
В PHP8, напротив, сравнивает строку и число как числа только если строка представляет собой число. Иначе, число будет конвертировано в строку, и будет производиться строковое сравнение.
Comparison | Before | After |
---|---|---|
0 == "0" |
true |
true |
0 == "0.0" |
true |
true |
0 == "foo" |
true |
false |
0 == "" |
true |
false |
42 == " 42" |
true |
true |
42 == "42foo" |
true |
false |
Стоит отметить, что теперь выражение0 == ""
даётfalse
. Если у вас из базы пришло значение пустой строки и обрабатывалось как число 0, то теперь это не будет работать. Нужно вручную приводить типы.
Эти изменения относятся ко всем операциям, которые производят нестрогое сравнение:
- Операторы
<=>
,==
,!=
,>
,>=
,<
,<=
. - Функции
in_array()
,array_search()
,array_keys()
с параметромstrict: false
(то-есть по-умолчанию). - Сотрировочные функции
sort()
,rsort()
,asort()
,arsort()
,array_multisort()
с флагомsort_flags: SORT_REGULAR
(то-есть по-умолчанию).
Также, есть специальные значения которые при нестрогом сравнении дают true
:
Expression | Before | After |
---|---|---|
INF == "INF" |
false |
true |
-INF == "-INF" |
false |
true |
NAN == "NAN" |
false |
false |
INF == "1e1000" |
true |
true |
-INF == "-1e1000" |
true |
true |
7. Constructor Property Promotion
Изначально идея позаимствована в языка-брата Hack. Она состоит в том, чтобы упростить инициализацию полей класса в конструкторе.
Вместо прописания полей класа, параметров конструктора, инициализации полей с помощью параметров, можно просто прописать поля параметрами конструктора:
class Point {
public function __construct(
public float $x = 0.0,
public float $y = 0.0,
public float $z = 0.0,
) {}
}
Это эквивалентно:
class Point {
public float $x;
public float $y;
public float $z;
public function __construct(
float $x = 0.0,
float $y = 0.0,
float $z = 0.0,
) {
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}
С этим всё просто, так как это синтаксический сахар. Но интересный момент возникает при использовании вариативные параметры (их нельзя объявлять таким образом). Для них нужно по-старинке вручную прописать поля и установить их в конструкторе:
class Test extends FooBar {
private array $integers;
public function __construct(
private int $promotedProp,
Bar $bar,
int ...$integers,
) {
parent::__construct($bar);
$this->integers = $integers;
}
}
8. Новые функции для работы со строками (str_contains, str_starts_with, str_ends_with)
Функция str_contains
проверяет, содержит ли строка $haystack
строку $needle
:
str_contains("abc", "a"); // true
str_contains("abc", "d"); // false
str_contains("abc", "B"); // false
// $needle is an empty string
str_contains("abc", ""); // true
str_contains("", ""); // true
Функция str_starts_with
проверяет, начинается ли строка $haystack
строкой $needle
:
$str = "beginningMiddleEnd";
var_dump(str_starts_with($str, "beg")); // true
var_dump(str_starts_with($str, "Beg")); // false
Функция str_ends_with
проверяет, кончается ли строка $haystack
строкой $needle
:
$str = "beginningMiddleEnd";
var_dump(str_ends_with($str, "End")); // true
var_dump(str_ends_with($str, "end")); // false
Вариантов mb_str_ends_with
, mb_str_starts_with
, mb_str_contains
нету, так как эти функции уже хорошо работают с мутльтибайтовыми символами.
На самом деле, очень приятно, что наконец-то добавили эти функции. Теперь не нужно писать каждый раз своих костылей с помощьюsubstr
,strncmp
,strpos
.
9. Использование ::class на объектах (Allow ::class on objects)
Раньше, чтобы получить название класса, к которому принадлежит объект, нужно было использовать get_class
:
$object = new stdClass;
$className = get_class($object); // "stdClass"
Теперь же, можно использовать такую же нотацию, как и ClassName::class
:
$object = new stdClass;
var_dump($object::class); // "stdClass"
10. Возвращаемый тип static (Static return type)
Тип static
был добавлен для более явного указания, что используется позднее статическое связывание (Late Static Binding) при возвращении результата:
class Foo {
public static function createFromWhatever(...$whatever): static {
return new static(...$whatever);
}
}
Также, для возвращения $this
, стоит указывать static
вместо self
:
abstract class Bar {
public function doWhatever(): static {
// Do whatever.
return $this;
}
}
11. Weak Map
Это специальная структура данных для хранения значений, ключами которых являются объекты, в основном используемая для кеширования.
Интерфейс класса выглядит следующим образом:
WeakMap implements Countable , ArrayAccess , Iterator {
public __construct ( )
public count ( ) : int
public current ( ) : mixed
public key ( ) : object
public next ( ) : void
public offsetExists ( object $object ) : bool
public offsetGet ( object $object ) : mixed
public offsetSet ( object $object , mixed $value ) : void
public offsetUnset ( object $object ) : void
public rewind ( ) : void
public valid ( ) : bool
}
Особенностью есть то, что объекты, используемые как ключи, подвержены сборке мусора. Поэтому, WeakMaps особенно пригодные для долгоживущих процессов.
class FooBar {
private WeakMap $cache;
public function getSomethingWithCaching(object $obj) {
return $this->cache[$obj] ??= $this->decorated->getSomething($obj);
}
// ...
}
Подробнее можно почитать в документации.
12. Убрано возможность использовать левоассоциативный оператор (Deprecate left-associative ternary operator)
Рассмотрим код:
return $a == 1 ? 'one'
: $a == 2 ? 'two'
: $a == 3 ? 'three'
: $a == 4 ? 'four'
: 'other';
Вот как он всегда работал:
$a | Result |
---|---|
1 | 'four' |
2 | 'four' |
3 | 'four' |
4 | 'four' |
В 7.4 код как прежде, отрабатывал, но выдавался Deprecated Warning
.
Теперь же, в 8 версии, код упадёт с Fatal error
.
13. Изменение приоритета оператора конкатенации (Change the precedence of the concatenation operator)
Раньше, приоритет оператора конкатенации .
был на равне с +
и -
, поэтому они исполнялись поочерёдно слева направо, что приводило к ошибкам. Теперь же, его приоритет ниже:
Expression | Before | Currently |
---|---|---|
echo "sum: " . $a + $b; |
echo ("sum: " . $a) + $b; |
echo "sum :" . ($a + $b); |
14. Удалены краткие открывающие php теги
В каждом скрипте, где в настоящее время используется короткий <?
открывающий тег, нужно будет внести изменения и использовать стандартный тег <?php
.
Это не касается тега <?=
, так как он, начиная с 5.4 всегда доступен.
15. Новый интерфейс Stringable
Объекты, которые реализуют метод __toString
, неявно реализуют этот интерфейс. Сделано это в большей мере для гарантии типобезопасности. С приходом union-типов, можно писать string|Stringable
, что буквально означает "строка" или "объект, который можно преобразовать в строку". В таком случае, объект будет преобразован в строку только когда уже не будет куда оттягивать.
interface Stringable
{
public function __toString(): string;
}
Рассмотрим такой код:
class A{
public function __toString(): string
{
return 'hello';
}
}
function acceptString(string $whatever) {
var_dump($whatever);
}
acceptString(123.45); // string(6) "123.45"
acceptString(new A()); // string(5) "hello"
Здесь функция acceptString
принимает строку, но что если нам нужно конкретно объект, что может быть преобразован в строку, а не что-либо иное. Вот тут нам поможет интерфейс Stringable
:
function acceptString(Stringable $whatever) {
var_dump($whatever);
var_dump((string)$whatever);
}
// acceptString(123.45);
/*
TypeError
*/
acceptString(new A());
/*
object(A)#1 (0) {
}
string(5) "hello"
*/
16. Теперь throw
— это выражение
Примеры использования:
// This was previously not possible since arrow functions only accept a single expression while throw was a statement.
$callable = fn() => throw new Exception();
// $value is non-nullable.
$value = $nullableValue ?? throw new InvalidArgumentException();
// $value is truthy.
$value = $falsableValue ?: throw new InvalidArgumentException();
// $value is only set if the array is not empty.
$value = !empty($array)
? reset($array)
: throw new InvalidArgumentException();
Подробнее можно почитать здесь.
17. Стабильная сортировка
Теперь все сортировки в php стабильные. Это означает, что равные элементы будут оставаться в том же порядке, что и были до сортировки.
Сюда входят sort, rsort, usort, asort, arsort, uasort, ksort, krsort, uksort, array_multisort, а также соответствующие методы в ArrayObject.
18. Возможньсть опустить переменную исключения (non-capturing catches)
Раньше, даже если переменная исключения не использовалась в блоке catch
, её всё равно нужно быто объявлять (и IDE подсвечивала ошибку, что переменная нигде не используется):
try {
changeImportantData();
} catch (PermissionException $ex) {
echo "You don't have permission to do this";
}
Теперь же, можно опустить переменную, если никакая дополнительная информация не нужна:
try {
changeImportantData();
} catch (PermissionException) { // The intention is clear: exception details are irrelevant
echo "You don't have permission to do this";
}
19. Обеспечение правильной сигнатуры магических методов (Ensure correct signatures of magic methods):
Когда были добавлены type-hints в php, оставалась возможность непавильно написать сигнатуру для магических методов.
К примеру:
class Test {
public function __isset(string $propertyName): float {
return 123.45;
}
}
$t = new Test();
var_dump(isset($t)); // true
Теперь же, всё жёстко контролируется, и допустить ошибку сложнее.
Foo::__call(string $name, array $arguments): mixed;
Foo::__callStatic(string $name, array $arguments): mixed;
Foo::__clone(): void;
Foo::__debugInfo(): ?array;
Foo::__get(string $name): mixed;
Foo::__invoke(mixed $arguments): mixed;
Foo::__isset(string $name): bool;
Foo::__serialize(): array;
Foo::__set(string $name, mixed $value): void;
Foo::__set_state(array $properties): object;
Foo::__sleep(): array;
Foo::__unserialize(array $data): void;
Foo::__unset(string $name): void;
Foo::__wakeup(): void;
20. Включить расширение json по-умолчанию (Always available JSON extension)
Так как функции для работы с json постоянно используются, и нужны чуть ли не в каждом приложении, то было принято решение включить ext-json в PHP по-умолчанию.
21. Более строгие проверки типов при для арифметических и побитовых операторов (Stricter type checks for arithmetic/bitwise operators)
Проблема, которую разработчики здесь решили предоставлена кодом ниже:
var_dump([] % [42]);
Что должен вывести этот код? Здесь непредсказуемое поведение (будет 0
). Всё потому, что большинство арифметических операторов не должны применятся на массивах.
Теперь, при использовании операторов +
, -
, *
, /
, **
, %
, <<
, >>
, &
, |
, ^
, ~
, ++
, --
будет вызывать исключение TypeError
для операндов array
, resource
и object
.
22. Валидация абстрактных методов в трейтах (Validation for abstract trait methods)
До восьмой версии, можно было писать что-то вроде:
trait T {
abstract public function test(int $x);
}
class C {
use T;
// Allowed, but shouldn't be due to invalid type.
public function test(string $x) {}
}
Начиная с восьмой версии, такой код будет падать с ошибкой. Да, и теперь есть возможность в трейте сделать приватный абстрактный метод, который будет реализован в использующем трейт классе.
trait MyTrait {
abstract private function neededByTheTrait(): string;
public function doSomething() {
return strlen($this->neededByTheTrait());
}
}
class TraitUser {
use MyTrait;
// This is allowed:
private function neededByTheTrait(): string { }
// This is forbidden (incorrect return type)
private function neededByTheTrait(): stdClass { }
// This is forbidden (non-static changed to static)
private static function neededByTheTrait(): string { }
}
Случаи, когда реализация приходит из родительского класса, или трейт применён в родительском классе, также проверяются.
23. Объединения типов (Union Types 2.0)
Рассмотрим код:
class Number {
/**
* @var int|float $number
*/
private $number;
/**
* @param int|float $number
*/
public function setNumber($number) {
$this->number = $number;
}
/**
* @return int|float
*/
public function getNumber() {
return $this->number;
}
}
Здесь тип переменной $number
контролируется только соглашениями программистов. На самом деле, туда может попасть что-угодно, и выйти отсюда может также что-угодно, так как проверки на тип не обеспечиваются ядром языка.
Теперь же, можно прописать тип int|float
(или любой другой) явно, чтобы обеспечить корректность работы модуля:
class Number {
private int|float $number;
public function setNumber(int|float $number): void {
$this->number = $number;
}
public function getNumber(): int|float {
return $this->number;
}
}
А также, код становится намного чище.
Как вы уже могли заметить, типы-объединения имеют синтаксис T1|T2|...
и могут быть использованы во всех местах, где можно прописать type-hints.
Некоторые оговорки:
- Тип
void
не может быть частью объединения. - Чтобы обозначить отсутствие результата, можно объявить "Nullable union type", который имеет следующий синтаксис:
T1|T2|null
. - Тип
null
не может быть использован вне объединения. Вместо него стоит использоватьvoid
. - Существует также псевдотип
false
, который по историческим причинам уже используется некоторыми функциями в php. С другой стороны, не существует типtrue
, так как он нигде не использовался ранее.
Типы полей класса инвариантны, и не могут быть изменены при наследовании.
А вот с методами всё немного интересней:
- Параметры методов можно расширить, но нельзя сузить.
- Возвращаемые типы можно сузить, но нельзя расширить.
Вот как это выглядит в коде:
class Test {
public function param1(int $param) {}
public function param2(int|float $param) {}
public function return1(): int|float {}
public function return2(): int {}
}
class Test2 extends Test {
public function param1(int|float $param) {} // Allowed: Adding extra param type
public function param2(int $param) {} // FORBIDDEN: Removing param type
public function return1(): int {} // Allowed: Removing return type
public function return2(): int|float {} // FORBIDDEN: Adding extra return type
}
То же самое происходит при типах, которые получились как результат наследования:
class A {}
class B extends A {}
class Test {
public function param1(B|string $param) {}
public function param2(A|string $param) {}
public function return1(): A|string {}
public function return2(): B|string {}
}
class Test2 extends Test {
public function param1(A|string $param) {} // Allowed: Widening union member B -> A
public function param2(B|string $param) {} // FORBIDDEN: Restricting union member A -> B
public function return1(): B|string {} // Allowed: Restricting union member A -> B
public function return2(): A|string {} // FORBIDDEN: Widening union member B -> A
}
Интереснее становится когда strict_types
установлен в 0
, то-есть по-умолчанию. Например, функция принимает int|string
, а мы передали ей bool
. Что в результате должно быть в переменной? Пустая строка, или ноль? Есть набор правил, по которым будет производиться приведение типов.
Так, если переданный тип не является частью объединения, то действуют следующие приоритеты:
- int;
- float;
- string;
- bool;
Так вот, будет перебираться этот список с типами, и для каждого проверяться: Если тип существует в объединении, и значение может быть приведёно к нему в соответствии с семантикой PHP, то так и будет сделано. Иначе пробуем следующий тип.
Как исключение, если string
должен быть приведён к int|float
, то сравнение идёт в первую очередь в соответствии с семантикой "числовых строк". К примеру, "123"
станет int(123)
, в то время как "123.0"
станет float(123.0)
.
К типамnull
иfalse
не происходит неявного преобразования.
Таблица неявного приведения типов:
Original type | 1st try | 2nd try | 3rd try |
---|---|---|---|
bool |
int |
float |
string |
int |
float |
string |
bool |
float |
int |
string |
bool |
string |
int/float |
bool |
|
object |
string |
Типы полей и ссылки
class Test {
public int|string $x;
public float|string $y;
}
$test = new Test;
$r = "foobar";
$test->x =& $r;
$test->y =& $r;
// Reference set: { $r, $test->x, $test->y }
// Types: { mixed, int|string, float|string }
$r = 42; // TypeError
Здесь проблема в том, что тип устанавливаемого значения не совместим с объявленными в полях класса. Для Test::$x
— это могло быть int(42)
, а для Test::$y
— float(42.0)
. Так как эти значения не эквивалентны, то невозможно обеспечить единую ссылку, и TypeError
будет сгенерирован.
24. Тип mixed (Mixed Type v2)
Был добавлен новый тип mixed
.
Он эквивалентен типу array|bool|callable|int|float|object|resource|string|null
.
Когда параметр объявлен без типа, то его тип — это mixed
.
Но стоит отметить, что если функция не объявляет возвращаемого значения, то это не mixed
, а mixed|void
. Таким образом, если функция гарантировано должна что-то возвращать, но тип результата не известен заранее, то стоит написать его mixed
.
При наследовании действуют следующие правила:
class A
{
public function bar(): mixed {}
}
class B extends A
{
// return type was narrowed from mixed to int, this is allowed
public function bar(): int {}
}
class C
{
public function bar(): int {}
}
class D extends C
{
// return type cannot be widened from int to mixed
// Fatal error thrown
public function bar(): mixed {}
}
Подробнее можно почитать здесь
Где смотреть новые фичи
Более информации про новые функции в PHP можно посмотреть на rfc watch.
IMHO хорошие идеи для PHP
- Неизменные (постоянные, надёжные) значения. Когда это внедрят в PHP — код станет намного более безопасным и удобочитаемым.
С текущей реализацией PHP можно писать программы практически не используя переназначение переменной. Мы могли бы для каждого нового значения создавать свою собственную переменную. Тем самым код стал бы куда лучше понятным, так как для каждой переменной нужно дать имя в соответствии с тем, что там храниться. Но сейчас так никто не делает. Отчасти это потому что язык не позволяет ограничить переприсваивание значения.
Вот пример проблемного кода. Со временем значение изменяется, и при добавлении ещё какой-либо обработки велика вероятность сломать что-то на следующих строках:
$invoice = getInvoice();
$invoice = loadDependencies($invoice);
$invoice = formatInvoice($invoice);
// hm... how do I access initial $invoice now?
return $invoice;
Я вижу как минимум 4 недостатка в этом коде:
- Никогда точно не знаешь что в переменной;
- Невозможность использовать уже перезаписанное значение где-то дальше в коде;
- Неустойчивость к изменениям — если производиться копипаст большой части кода с такими-же переменными где-то во вложенном
if
, тогда ночь отладки обеспеченна. - Каждый раз нужно писать знак
$
перед$переменной
. Да, это спорно, но ведь без долларов проще читать код. Возьмите какого-либо джависта, что он скажет про ваш код? — Уххх как много долларов!
Вот каким мог быть этот код:
invoice = getInvoice();
invoiceWithDependencies = loadDependencies(invoice);
invoiceFormatted = formatInvoice(invoiceWithDependencies);
// someAnotherAction(invoice);
return invoiceFormatted;
Значения, что содержатся в invoice
, invoiceWithDependencies
, invoiceFormatted
не могут быть перезаписаны. Да, и теперь мы точно знаем что и где хранится.
function printArr(array arr) {
foreach (arr as firmValue) {
print strtr(
"Current value is {current}. +1: {next}",
[
'{current}' => firmValue,
'{next}' => firmValue + 1
]
);
}
}
- Перегрузка операторов. Из 66 разработчиков, 28 проголосовали против, и поэтому, не смотря на то, что идея уже была реализована, предложение было отклонено. Думаю стоит поднять дискуссию на эту тему.
- Разрешить вычисление в константных выражениях. Как по мне, этого очень не хватает.
Вот пример кода, где это очень полезно:
use Spatie\ModelStates\State;
abstract class OrderStatus extends State
{
public static string $name = static::getName();
abstract protected function getName(): string;
}
Как видим, при первом обращении к $name
, будет вызван метод getName
финального класса. Это дает нам возможность настраивать какие значения будут попадать в поля в зависимости от каких-либо условий. А в данном примере это использовано с шаблоном "Template Method", и финальные классы обязаны предоставить нам значение для поля.
Сейчас многие фреймворки имеют значени по-умолчанию для большинства конфигураций в своих классах. Проблема с таким подходом заключается в том, что мы можем предоставить только примитивное значение. Никаких вызовов функций не разрешено. А что если мы хотим вызвать хелпер config
для предоставления конфигурации, которая задаётся в поле класса? Тогда у нас проблемы, и нужно переопределять конструктор, где уже задавать значение поля. Хорошо, когда конструктор не имеет много параметров. А что, если там много параметров (к примеру, 7)? Тогда для простого создания поля, нужно 20 дополнительных бесполезных строк кода. И выглядит это ещё как уродливо!
Просто сравните это:
protected string $whatever = $this->doCalculate();
И это:
public function __construct(
array $query = [],
array $request = [],
array $attributes = [],
array $cookies = [],
array $files = [],
array $server = [],
$content = null
) {
parent::__construct(
$query,
$request,
$attributes,
$cookies,
$files,
$server,
$content
);
$this->whatever = $this->doCalculate();
}
Почему мы должны инициализировать поле в конструкторе, если оно не зависит от его параметров? Как по мне, мы не должны.