php
PHP: что ты такое?
Мне сегодня показали вот такой код и спросили, знал ли я, что так можно. Речь идёт о вызове $this::w00t(). То есть мы используем $this как бы в статическом контексте. Мне бы такое в голову не пришло.
Код ниже, понятное дело, чисто демонстрационный.
Нет, я не знал, но, как ни удивительно, эта конструкция работает! Причём работает уже очень давно — это не свежее веяние: она появилась ещё в версии 5.4!
(new class() {
public function w00t():void {
echo "WHAT THE HELL ARE YOU?\n";
}
public function test():void {
$this::w00t();
}
})->test();Дополнено: работает как эквивалент static, что ожидаемо, конечно.
Коммунистический массив
Я тут в мемах, которые кинули бывшие коллеги, увидел «коммунистическую сортировку» — она каждому присваивает среднее значение, и не смог пройти мимо. Написал «коммунистический массив»:
class CommunistArray extends \ArrayObject
{
public function __construct(array $array = [])
{
parent::__construct(self::createCommunistArray($array));
}
public function offsetSet(mixed $key, mixed $value): void
{
parent::offsetSet($key, $value);
$this->redistribute();
}
public function offsetUnset(mixed $key): void
{
$first = $this->getIterator()->key();
if ($first !== null) {
$this[$first] += $this[$key];
parent::offsetUnset($key);
$this->redistribute();
}
}
private static function createCommunistArray(array $array): array
{
if ($array) {
$avg = array_sum($array) / sizeof($array);
return array_fill_keys(array_keys($array), $avg);
}
return [];
}
private function redistribute(): void
{
(void) $this->exchangeArray(self::createCommunistArray((array) $this));
}
}Он делает всех равными и перераспределяет блага автоматически:
$comrades = new CommunistArray([0b1, 0o0, 3e1, 0x45]);
print_r($comrades); # 25, 25, 25, 25
$comrades[2] = 4;
print_r($comrades); # 19.75, 19.75, 19.75, 19.75
unset($comrades[0]);
print_r($comrades); # 24.6875, 24.6875, 24.6875Возвращение Text_Figlet
Нейросеть «Опус 4.6», конечно, жрёт невероятное количество денег.
Я сделал небольшую паузу в парном программировании с ней, но недавно не выдержал, потому что решил потихоньку заняться тем, до чего у меня много лет не доходили руки, — закончить или переделать некоторые проекты. Наверное,
У меня уже много лет чешутся руки переделать мою старенькую библиотеку Text_Figlet. Это, наверное, самое популярное моё детище — она время от времени встречается мне
Библиотека написана на ПХП и умеет выводить текст специальными
«Смашинг» был сделан только по горизонтали; вертикальный алгоритм я в своё время не дописал, и этот факт — одна из причин, почему мне захотелось вернуться к библиотеке.
_ _ _ _ _ _ _
| || |___| | |___ __ __ _____ _ _| |__| | |
| __ / -_) | / _ \_ \ V V / _ \ '_| / _` |_|
|_||_\___|_|_\___( ) \_/\_/\___/_| |_\__,_(_)
|/
В итоге я так разогнался, что полностью переписал библиотеку при помощи нейросетей, реализовав всё задуманное и даже больше — например, добавил поддержку шрифтов «TOIlet» и цветных фильтров для них, а также покрыл всё тестами и подготовил библиотеку к установке через «композер».
PHP: (binary)
И, чтобы, как говорится «два раза не вставать», расскажу про самую бесполезную конструкцию в ПХП. Я знаю, что не так много народу знают про (unset), про которую я ворчал вчера, а про эту вещицу, как мне кажется, знает ещё меньше программистов на ПХП.
Одна из неприятных вещей, которые остались в ПХП с давних времён — работа встроенных функций и конструкций только со строками в однобайтовых кодировках. С этим можно жить — есть библиотеки, которые позволяют замести эту проблему под ковёр, но сам язык помогать вам в этом не будет.
К сожалению, работу доделать не осилили, очень уж было сложно, особенно тяжело шло с расширениями. В итоге версия так и не вышла, но так как о ней широко раструбили, пришлось даже пропустить номер — после ПХП 5 вышла сразу седьмая версия.
Но некоторые отголоски этой не совершившейся революции
В версии 8.5 по сих пор есть преобразование типа (binary), которое является алиасом к (string), но выдаёт предупреждение об устаревшей конструкции. А кроме этого, у строки есть префикс b: b"string", придуманный для того, чтобы создавать в ПХП6 однобайтовые строки. В современных версиях ПХП он, разумеется, ничего со строкой не делает.
И эту всю красоту из языка
PHP: (unset) и (void)
Язык PHP часто ругают. Большей частью — заслуженно, но часть давно решённых проблем тянется за ним, как смрад за народным ополчением в Средние века. Ненавистники часто глубоко в проблемы языка не вникают — им неинтересно, перепечатывая аргументы, которые много лет как несостоятельны.
Как мне кажется, самая большая проблема языка сейчас — это его процедурное прошлое, которое торчит изо всех щелей. Но есть и менее заметная, при этом системная проблема: у языка нет «великодушного пожизненного диктатора», как у Пайтона или Линукса, или более «сыгранной» команды, определяющей долгосрочные цели в развитии языка.
Некоторые разумные,
Я хочу поворчать на одну из таких мелких тем.
В ПХП с незапамятных времён была конструкция (unset), которая приводила любой тип к NULL. Не самое очевидное название, поэтому, наверное, и применял её мало кто — не всем очевидно чем она могла бы быть полезна.
Тем не менее, я со временем догадался, что можно её приспособить, чтобы обозначать, что
class Smth
{
private function saveSmth($stat)
{
// тут какие-то действия
// прошло успешно, но выше где-то может быть и false
return true;
}
public function doSmth($action, $stat)
{
// тут какой-то код
// результат вызова метода ниже мы игнорируем
(unset) $this->saveSmth($stat);
return true;
}
}Мне казалось и до сих пор кажется, что это полезный паттерн. В «Гоу», например, в этом случае нужно присвоить возвращаемое значение специальной переменной _, иначе компилятор ругается.
И вот, много лет назад, в версии 7.2 использование (unset) в коде стало вызывать предупреждение об устаревшей конструкции. Я тогда попытался
А в версии 8.5 внезапно та же идея возвращается под новым именем, только теперь нужно писать (void), а не (unset).
Да, имя более логичное, да функционал расширен — появился ещё и новый атрибут, да синтаксис изменён — результат (unset) можно было
Митап по PHP
Признаюсь, я
Если судить по отзывам, событие чрезвычайно всем понравилось. Многие сказали, что давно не участвовали
Не знал, что организация подобный событий отнимает столько сил. Несмотря на список, который мы начали вести с самого начала, то одно, то другое постоянно выпадало из зоны внимания, настолько много всего пришлось учесть.
Мы начали примерно за два месяца, тем не менее,
Я очень переживал за эти вещи, потому что
Большое спасибо докладчиками — Александру Макарову, Максиму Пестову и Станиславу Кошевому за то, что вызвались поучаствовать и интересные доклады.
Видео я показать не могу, верьте на слово, — мы его принципиально не записывали. Если мы сделаем наши митапы регулярными, не будем записывать и впредь — наша идея в том, чтобы люди больше общались, хотя бы вынуждено, приходя вот на такие события.
Анонимные функции в PHP
Относительно недавно в ПХП появился новый способ создания анонимных функций. Раньше мы писали array_filter($strs, 'strlen'), теперь можем писать array_filter($strs, strlen(...)).
В первом случае у нас указывается строка, во втором — анонимная функция. Последнее намного лучше, конечно, потому что помогает строгой типизации и лучше понимается редакторами кода.
Когда этот синтаксис появился, в моей голове он стал частным случаем стрелочного синтаксиса. В самом деле, ведь fn($t) => sin($t) и sin(...) — это одно и то же. Но недавно я понял, что у второго синтаксиса есть побочный эффект.
Если посмотреть на код ниже, всё должно стать понятно, но если нет, то я поясню.
Тут мы видим класс, конструктор которого печатает код, находящийся в строке, которая его вызвала. Ниже класса создаются две анонимные функции, одна — стрелочная через fn(), вторая — через многоточие.
Этот код позволяет проиллюстрировать разницу.
В случае создания анонимной функции через fn(), конструктор будет вызван каждый раз заново на любой вызов функции.
В случае синтаксиса с многоточием, конструктор будет вызван сразу при создании, а при вызове, в том числе многократном, он вызываться не будет.
То есть каждый способ имеет особенности, которые надо учитывать.
class Sample
{
public function __construct()
{
$bt = debug_backtrace();
['line' => $line, 'file' => $filename] = end($bt);
echo file($filename)[$line - 1];
}
public function method(): void
{
}
}
$a = fn() => (new Sample)->method(); // не выведет ничего
$b = (new Sample)->method(...); // выведется «$b = (new Sample)->method(...);»
$a(); $a(); // выведется «$a(); $a();» два раза
$b(); $b(); // не выведет ничегоБиблиотека itertools для PHP
В качестве развлечения написал тесты к своей старой библиотеке itertools для ПХП и выяснил, что в ней
Пример использования теперь стал выглядеть вот так:
use Itertools\Itertools as it;
foreach (it::islice(it::cycle('ABC'), 10) as $element) {
echo $element;
}
Простейший HTTP-прокси
У Романа Парпалака прочитал про «простейший HTTP-прокси». Ну нет, действительно простейший HTTP-прокси
Выглядит он так:
<?php readfile($_SERVER["REQUEST_URI"]);Запускается так:
php -S 127.0.0.1:8080 proxy.phpА вот так работает:
Модуль scalar_objects
Очень мне нравится идея навести порядок в функциях ПХП при помощи присобачивания к примитивным типам
К именованию встроенных функций есть много претензий:
- различный стиль наименований: html_entity_decode, но htmlentities;
- неинтуитивность некоторых названий: strcspn, strpbrk;
- разный порядок параметров: strpos($haystack, $needle), array_search($needle, $haystack);
Считается, что это один из серьёзных недостатков ПХП. Напрашивается очевидное решение — сделать всем проблемным функциям алиасы, но так язык замусорится ещё сильнее.
А вот добавив псевдометоды у примитивных типов, можно сразу сразу сделать хорошо. Один из контрибьютеров даже сделал модуль, который показывает как это могло бы выглядеть:
declare(strict_types=1);
class IntHandler {
public static function abs(int $self): int {
return abs($self);
}
public static function div(int $self, int $other): int {
return intdiv($self, $other);
}
public static function sqrt(int $self): float {
return sqrt($self);
}
}
register_primitive_type_handler('int', 'IntHandler');
$i = -5;
var_dump($i->abs()->div(2)->sqrt()); // 1.4142135623730951Разумеется, если это решение поместить в язык, никаких классов регистрировать не надо будет, так что можно смотреть только на последнюю строку.
Кроме того, повышается читаемость. Сравните два подхода
$result = substr(strtr(rtrim($className, '_'), '\\', '_'), 15);
$result = $className->trimRight('_')->replace('\\', '_')->slice(15);Как по мне, очень хорошее решение, совершенно непонятно почему оно ещё не в языке.
Бессонный хакатон: где экран?
Продолжаю писать о рефакторинге кода, написанного в свой бессонный хакатон. На этот раз хочется избавиться от подобранных вручную координат экрана эмулируемого компьютера — это неаккуратно и не универсально, — в другом браузере эти координаты скорее всего будут другими.
У меня
Такое решение наводило на меня тоску, но потом вспомнилось, что в графической библиотеке ПХП есть фильтр, который выделяет края и я решил глянуть не будет ли от него пользы.
Код для наложения фильтров выглядит следующим образом:
$im = imagecreatefrompng($screenshot_file);
imagefilter($im, IMG_FILTER_GRAYSCALE);
imagefilter($im, IMG_FILTER_EDGEDETECT);Мне казалось, что на получившемся изображении можно искать четыре точки экрана на известном расстоянии, но оказалось, что у такого метода есть ложные срабатывания. Поэтому я стал перебирать на экране все горизонтальные белые линии, пока не встречаю две параллельные нужной мне длины.
Таким образом очень надёжно находится граница экрана эмулятора, от которой на фиксированном расстоянии находится искомый экран. В итоге получается вот такой код:
const FRAME_W = 1039;
const FRAME_H = 808;
const FRAME_COLOR = 0xFFFFFF;
const FRAME_BORDER = 30;
$im = imagecreatefrompng($_SERVER["argv"][1]);
[$w, $h] = [imagesx($im), imagesy($im)];
imagefilter($im, IMG_FILTER_GRAYSCALE);
imagefilter($im, IMG_FILTER_EDGEDETECT);
for ($y = 0; $y <= $h - FRAME_H; $y++) {
for ($x = 0; $x <= $w - FRAME_W; $x++) {
$is_frame = imagecolorat($im, $x, $y) === FRAME_COLOR;
for ($i = 1; $i<FRAME_W; $i+=2) {
$is_frame =
$is_frame &&
imagecolorat($im, $x + $i, $y) === FRAME_COLOR &&
imagecolorat($im, $x + $i, $y + FRAME_H - 1) === FRAME_COLOR;
if (!$is_frame) {
break;
}
}
if ($is_frame) {
printf(
"%d,%d\n",
intdiv($x, 2) + FRAME_BORDER,
intdiv($y, 2) + FRAME_BORDER
);
exit(0);
}
}
}
exit(1);У меня тут всё в удвоенных координатах, которые я потом делю на два, чтобы получить реальные, потому что у меня экран «Ретина». Надо бы найти
CVE 2023-0567
В ПХП нашли достаточно серьёзный, на мой взгляд, баг — на некоторых повреждённых хешах функция password_verify возвращает true на любой пароль. Получается, хитрый хакер может оставить себе тайный ход — поменять в базе хеш на битый, и пользователь ничего не заметит —
Вот как это выглядит:
var_dump(password_verify("foo", '$2y$04$00000000$')); // true
var_dump(password_verify("bar", '$2y$04$00000000$')); // trueУдивляет, что один из разработчиков попытался защитить текущее поведение интерпретатора:
The documentation for password_verify() clearly states that the $hash has to be valid:
> hash
> A hash created by password_hash().I fail to see how this is a bug if you feed it garbage; also the documentation to crypt states:
> Using characters outside of this range in the salt will cause crypt() to return
a zero-length string. The two digit cost parameter isthe base-2 logarithm of the iteration count for the underlyingBlowfish-based hashing algorithm and must be in range04-31, values outside this range will cause crypt() to fail.PHP clearly behaves as described.
Что бы ни было написано в документации, возвращать true на любой пароль из функции проверки правильности пароля — очень плохая идея, как мне кажется, это должно быть довольно очевидно.
При анализе оказалось, что баг занесён ещё
PHP’s implementation of crypt_blowfish differs from the upstream Openwall version by adding a «PHP Hack», which allows one to cut short the BCrypt salt by including a $ character within the characters that represent the salt.
Hashes that are affected by the «PHP Hack» may erroneously validate any password as valid when used with password_verify and when comparing the return value of crypt() against the input.
The PHP Hack exists since the first version of PHP’s own crypt_blowfish implementation that was added in 1e820ec.
No clear reason is given for the PHP Hack’s existence. This commit removes it, because BCrypt hashes containing a $ character in their salt are not valid BCrypt hashes.
Анекдот на Пайтоне
У меня есть друг, которому я регулярно шлю шутки про «Вархаммер». Сам в этой теме не разбираюсь, но хочется думать, что его они веселят. А я — тот самый друг, которому шлют то же, но на тему программирования. Пару раз даже присылали мои собственные шутеечки.
Вчера прислали известный неприличный анекдот про Василия Ивановича и Петьку, но написанный на «Пайтоне». И хотя у меня довольно пуританский блог, анекдот так вырвиглазно написан, что пришлось его переписать.
Вот что мне прислали:
class Chapaev:
nyuans = True
def fuck(Class):
Class.nyuans = True
Class.ass.append('Dick')
class Petyka:
nyuans = False
ass = []
if Chapaev.nyuans == True and Petyka.nyuans == False:
Chapaev.fuck(Petyka)Как по мне, плохо написано примерно всё, мне очень не понравилось, так что держите, вот вам моя версия, которая, кстати, как мне кажется, более точно отражает суть анекдота:
Chapaev = []
Petka = []
dick = [20, Chapaev]
Petka.insert(*dick)
isNuance = Chapaev in Petka and Petka not in Chapaev
print(isNuance) // TrueНу и пока у меня ещё был запал, переписал на ПХП известнейший мем из фильма «ДМБ»:
class Gopher implements Countable
{
public function count(): int
{
return 0;
}
}
$gopher = new Gopher;
var_dump(count($gopher)); // 0
var_dump(empty($gopher)); // falseAVIF в GD
Вчера ночью чах, в очередной раз, над форматом AVIF — мысль была в качестве эксперимента добавить его в наш документооборот — продукт, разработкой которого я управляю.
Сейчас, чтобы показать документ в браузере, мы его конвертируем в JPEG — дёшево, сердито и везде поддерживается. Но страницы получаются большими (200 с лишком килобайт каждая) и иногда грузятся дольше, чем хотелось бы. Вот я и придумал проверить гипотезу, что AVIF тут поможет.
Пока примотал реализацию на соплях, как концепт. Результаты, кстати, хорошие, можно посмотреть на скриншоте разницу:
Но пока мотал, наткнулся на прекрасное. 🤦🏻♂️
В ПХП есть несколько модулей для работы с картинками, но самых распространённых два — GD и Imagick. Я начал с GD, так как на виртуалке он собран с поддержкой AVIF (а Imagick — нет), по крайней мере сам модуль так утверждает:
var_dump(gd_info()['AVIF Support']); // true
var_dump(imagetypes() & IMG_AVIF); // 256Так же существует функция imageavif, что хороший знак — модуль устроен так, что если поддержки
Но при попытке сконвертировать картинку из JPEG в AVIF создавался файл нулевой длины, хотя функция возвращала true:
var_dump(imageavif(imagecreatefromjpeg('0.jpg'), '0.jpg.avif')); // trueGD это очень старый модуль, он родился, когда в PHP главенствовал процедурный модуль, поэтому его суть — функции; в PHP, если мы вызываем функции
array(4) {
["type"]=>
int(2)
["message"]=>
string(50) "imageavif(): AVIF image support has been disabled
"
["file"]=>
string(17) "Command line code"
["line"]=>
int(1)
}Я погуглил ошибку, но не смог быстро разобраться что с ней сделать, поэтому переключился на Imagick. Там всё завелось без особых проблем — заменил шестую версию на седьмую и поставил недостающие пакеты:
yum shell
> remove php-pecl-imagick-im6
> install php-pecl-imagick-im7
> install ImageMagick7-heic
> install libheif
> runВ этом модуле, кстати, проверка на поддержку AVIF делается вот таким немного неочевидным способом:
$isAVIFSupported = Imagick::queryformats("AVIF") !== [];
Определение ширины и высоты файла JPEG
В «Эгее» есть
Пока рабочая теория такая, что функция ПХП getimagesize (она возвращает тип изображения и его высоту с шириной) иногда выдаёт неправильные значения.
Опровергнуть или подтвердить эту теорию
Вот Илья и попросил написать меня такой код на чистом ПХП. Надеюсь это позволит продвинуться в изучении проблемы — код вошёл в одиннадцатую версию «Эгеи».
Выложу его у себя тоже, чтобы не потерялся, ну и может
function readJPEGWxH(string $filename): array
{
$file = new SplFileObject($filename);
if (!$file->flock(LOCK_SH)) {
throw new RuntimeException('Cannot lock the file');
}
if ($file->fread(2) !== "\xFF\xD8") {
throw new RuntimeException('Unknown format');
}
for (;;) {
$decoded = @unpack('H4segment/nlen', $file->fread(4));
if ($decoded === false) {
throw new RuntimeException('Unknown format');
}
['segment' => $segment, 'len' => $len] = $decoded;
// SOFn (Start Of Frame)?
if (preg_match('/^ffc[0-3]/', $segment)) {
$segdata = @unpack('Cbits/nheight/nwidth', $file->fread(5));
if ($segdata === false) {
throw new RuntimeException('Unknown format');
}
return [$segdata['width'], $segdata['height']];
}
// len = full lenght of segment except segment magic code
if ($file->fseek($len - 2, SEEK_CUR) !== 0) {
throw new RuntimeException('Cannot find start of frame');
}
}
}Десятичная запятая
Затронули тут в одном из айтишных чатов тему десятичной запятой — я считаю, что в русском тексте должна использоваться десятичная запятая, хотя и понимаю, что программистам привычнее точка, так как во всех языках программирования используется именно она.
На этом месте я вдруг задумался — видел ли я хоть раз язык, где используется десятичная запятая?
Так и не вспомнил, зато на память пришёл забавный баг, который был
<?php
var_dump('0,4' + '0,7');
setlocale(LC_ALL, 'ru_RU.koi8r');
var_dump('0,4' + '0,7');Как можно убедиться онлайн, результат будет следующим:
int(0)
float(1,1)Работает в версиях 4.0—4.2, в 4.3 уже не повторяется. Я не помню каким образом на него наткнулся, но чувство недоумения от этого эффекта до сих пор живо в моей памяти.
Неблокирующий режим файлов в PHP
Позже я внимательно прочитал документацию на теме. По всей видимости, это ничего не даст в отношении проверки системы хранения на зависание.
Смотрел недавно исходники языка ПХП, хотел изучить подробнее как реализованы там стримы. Пока читал, случайно натолкнулся на недокументированную возможность — у функции fopen, как оказалось, есть режим открытия в неблокирующем режиме (буква n).
#if defined(O_NONBLOCK)
if (strchr(mode, 'n')) {
flags |= O_NONBLOCK;
}
#endifДоступно не на всех системах, только там, где есть константа O_NONBLOCK в fcntl.h, то есть в Линуксах, например.
Для меня это довольно полезное знание, так как я давно ищу возможность проверить из ПХП не зависла ли подмонтированная через NFS система хранения.
В общем, надо будет попробовать при следующем таком инциденте — не спасает ли открытие в неблокирующем режиме. Код, кажется, должен выглядеть примерно так:
function tryToOpen(string $path, float $timeout): ?bool
{
$dio_fd = dio_open($path, O_RDONLY | O_NONBLOCK);
if ($dio_fd === false) {
return null;
}
foreach (glob('/proc/self/fd/*') as $file) {
if (is_link($file) && readlink($file) === $path) {
$fd = basename($file);
$read = [fopen("php://fd/$fd", 'rn')];
$write = $except = null;
$timeout_us = (int) ($timeout * 1e6);
return stream_select($read, $write, $except, 0, $timeout_us) === 1;
}
}
return null;
}Тут используется модуль ПХП Direct IO, чтобы обратиться к системной функции open мимо ПХП. Приходится так делать, потому что функция fopen, которая есть в языке, сначала попытается проверить существование файла и в моей задаче сразу зависнет.
После этого я, пользуясь /proc, ищу среди своих файловых дескрипторов тот, который отвечает за только что открытый файл и переоткрываю его через fopen (в этом месте делается дубликат файлового дескриптора, а прежний закрывается — вызывается dup), так как мы тут работаем с номером файлового дескриптора, то проверка на существование файла не используется.
Переоткрытие файла нужно, так как в вызов stream_select, который я использую для ожидания данных из файла с таймаутом, можно передать результат fopen, но не dio_open.
Надеюсь случай испытать этот код представится нескоро.
PHPFuck для PHP8
Немного про программирование.
Один из читателей написал, что программа PHPFuck (это способ писать программы
В самом деле, восьмая версия стала куда строже к программисту,
<?=[[$_=@([].[]),$___=-!![],$$_=![]+![],$$_=$$_*$$_,$__=$$_+![],$$__=$$_+$__,
$____=$_[-![]-![]],$$___="{$$__}"^$____,$$___=($$___^"$___"). ($$___^"{$$__}"^"$___"^"{$$_}").
($$$_=$$___^"{$$_}"). $$$_.($_____="{$$__}"^"{$$_}"^++$____).("$___"&$____).
([$$___=$_[+![]],"{$$_}"|++$$___][+![]]).$_____.$_[+![]].$$$_. ("$__"^$_[-![]-![]]^"$___")
],$$___][+![]];Выше новая версия.
Кстати, она компактнее, но при этом выводит больше текста — «hello world», вместо «Hello!» предыдущего варианта. Ниже я немного расскажу откуда взялась экономия и с чем ещё пришлось столкнуться.
Принцип формирования текста остался, в общих чертах, прежним — корнем всего является комбинация @([].[]), которая даёт слово ArrayArray
Из этой строки потом рождаются все остальные символы. Дело в том, что в ПХП над символами можно производить некоторые бинарные и математические операции, что иногда позволяет получить другие символы. Например, первая буква — h в слове hello получается из сочетания '9' ^ 'a' ^ '0' (где ^ — бинарная операция «исключающего или»).
При этом цифры я получаю довольно простым способом — 0 как +!![] (преобразованием false в число), «девятку», — набирая нужные цифры по единицам (из +![]), а a — как предпоследнюю букву из полученной ранее строки ArrayArray, то есть @([].[])[-2] (аналогично, -2 получается тут из двух замаскированных единиц со знаком «минус»).
По сравнению с предыдущим вариантом в новом коде сделаны следующие изменения.
В этой связи весь код теперь — большое выражение, все переменные в нём лежат внутри массива. Если давать им нормальные имена, то основной принцип выглядит
Кроме того, мне надоело подбирать буквы вручную, вместо этого я написал небольшую программу, которая делает это за меня. Я не стал придумывать алгоритм, который бы анализировал какие биты нужно изменить, а решил задачу полным перебором.
Ниже приведен листинг этой программы, вдруг
<?php
declare(strict_types=1);
// Доступный алфавит
$abc = str_split(count_chars("Array0123456789", 3));
// буквы, которые надо получить
$need = str_split(count_chars("hello world", 3));
function gen(array $chrs): Generator
{
// чем операции длиннее, тем ближе к концу
// это нужно, чтобы более короткие конструкции выигрывали
foreach ($chrs as $ch) {
yield $ch => [$ch, $ch];
}
// возврат: из чего делаем, [как выглядит, значение]
foreach ($chrs as $ch) {
yield $ch => ["~$ch", ~$ch];
}
// для символа инкремент работает, а декремент — нет (особенность ПХП)
foreach ($chrs as $ch) {
$value = $ch;
yield $ch => ["++$ch", (string)++$value];
}
}
function eval_op(string $op, string $a, string $b): string
{
return ([
'|' => fn($a, $b) => $a | $b,
'&' => fn($a, $b) => $a & $b,
'^' => fn($a, $b) => $a ^ $b,
][$op])($a, $b);
}
function generate(array $set): Generator
{
foreach (gen($set) as $src1 => [$p1, $ch1]) {
foreach (gen($set) as $src2 => [$p2, $ch2]) {
foreach (['|', '&', '^'] as $op) {
$result = eval_op($op, $ch1, $ch2);
if ($result !== $ch1 && $result !== $ch2) {
yield [
$src1,
$src2,
"$p1 $op $p2",
$result,
];
}
}
}
}
}
function out(string $str): string
{
return preg_replace_callback(
'/[\x00-\x1F\x80-]/s',
fn($m) => sprintf("\\x%02X", ord($m[0])),
$str
);
}
$set = $abc;
do {
foreach (generate($set) as [$src1, $src2, $str, $res]) {
$idx = array_search($res, $need, true);
if (!isset($rules[$res])) {
$rules[$res] = $str;
}
if ($idx !== false) {
if (!in_array($src1, $abc, true)) {
echo out("{$rules[$src1]} = '$src1'"), "\n";
}
if (!in_array($src2, $abc, true)) {
echo out("{$rules[$src2]} = '$src2'"), "\n";
}
echo out("$str = '$res'"), "\n\n";
unset($need[$idx]);
}
$set[] = $res;
}
$set = array_unique($set);
} while ($need);Это ни в коем случае не инструмент для оптимизации программ в такой технике, а набросок, позволяющий ускорить подбор букв.
Для более глубоких оптимизаций нужно научить эту программу дробить выражения, которые уже вычислялись, на куски и эффективно использовать их, а так же подбирать частоиспользуемым кускам имена переменных меньшей длины.
У меня дробления нет, я использую предыдущие выражения целиком, а имена переменным моя программа вообще не подбирает.
Длинные имена и PHP
На днях наткнулись на немаловажную особенность, — на системе примонтированной по CIFS (эта система — наследница небезызвестной «Самбы»), программа ПХП не видела некоторые файлы. В принципе, мы довольно быстро разобрались, что дело в длине имени — все функции для обхода директориев в языке такие файлы игнорировали.
Оказалось, что наша версия ПХП использует устаревший вызов readdir_r. У него много недостатков, но среди прочих — на некоторых системах он не может читать длинные имена. Проблему исправили в версии 7.4, но у нас всё ещё есть проекты на 7.3.
До конца перехода будем использовать системную утилиту /bin/ls, у неё таких трудностей не возникает.
Починил ucfirst в PHP
В отпуске дошли руки посмотреть и починить баг в функции ucfirst — по задумке она должна приводить в верхний регистр первый символ однобайтовой строки, учитывая текущую локаль. Всё бы хорошо, но с буквой «я» кодовой страницы 1251 это не срабатывало — она оставалась в нижнем регистре.
Интересно, что похожая функция — strtoupper тут работает прекрасно, то есть вот такой код на текущих версиях ПХП выдаёт false:
if (setlocale(LC_CTYPE, "ru_RU.cp1251") === false) {
throw new RuntimeException();
}
// буква «я» имеет в кодировке «1251» код 255
var_dump(ucfirst(chr(255)) === strtoupper(chr(255));Поскольку эта беда уже второй раз ломает тесты в нашем продукте (программисты об этой особенности просто забывают), решил глянуть в исходники. Внутри ПХП обе функции — проблемная и рабочая, используют вызов toupper, в документации к которой человеческим языком сказано, что передаваемый параметр должен иметь тип unsigned char.
Функция strtoupper как раз использует unsigned char, а ucfirst — char (который signed). Документация к toupper такую вольность по отношению к типам осуждает — там прямым текстом написано, что могут быть проблемы, так как внутри входной параметр будет расширен до int, знак перенесётся на старший разряд и всё сломается.
Что, собственно, и происходит с буквой «я», код которой как раз получается отрицательным. Удивительно, что с другими «отрицательными» буквами всё
В общем, мой патч приняли в версию 7.3, и как только выйдут новые версии поддерживаемых веток интерпретаторов, мы, наконец, перестанем спотыкаться об этот баг.