Комментарии 75
У меня на 7.4 под Windows перестал работать preg replace callback, если в качестве аргумента указан eval строковое обращение к protected методу этого же класса через self. Пришлось заталкивать всю функцию в аргументы prc.
1). В 7.2 под UNIX работало прекрасно
protected static function hexEntToLetter( string $ord ): string {
$ord = $ord[1];
if( preg_match('/^x([0-9a-f]+)$/i', $ord, $match) ) {
$ord = hexdec($match[1]);
}
else {
$ord = intval($ord);
}
$no_bytes = 0;
$byte = [];
if ($ord < 128) {
return chr($ord);
}
else if ($ord < 2048) {
$no_bytes = 2;
}
else if ($ord < 65536) {
$no_bytes = 3;
}
else if ($ord < 1114112) {
$no_bytes = 4;
}
else {
return '';
}
switch( $no_bytes ) {
case 2:
$prefix = [ 31, 192 ];
break;
case 3:
$prefix = [ 15, 224 ];
break;
case 4:
$prefix = [ 7, 240 ];
}
for( $i = 0; $i < $no_bytes; $i++ ) {
$byte[ $no_bytes - $i - 1 ] = (($ord & (63 * pow(2, 6 * $i))) / pow(2, 6 * $i)) & 63 | 128;
}
$byte[0] = ( $byte[0] & $prefix[0] ) | $prefix[1];
$ret = '';
for ($i = 0; $i < $no_bytes; $i++) {
$ret .= chr($byte[$i]);
}
return $ret;
}
protected static function hexToSymbols( string $s ): string {
return preg_replace_callback('/([0-9a-fx]+);?/mi', 'self::hexEntToLetter', preg_replace('/\\\\u?{?([a-f0-9]{4,}?)}?/mi', '$1;', urldecode($s)));
}
Пришлось сделать не красиво:
protected static function hexToSymbols( string $s ): string {
return preg_replace_callback(
'/([0-9a-fx]+);?/mi', function( $ord ) {
$ord = $ord[1];
if( preg_match('/^x([0-9a-f]+)$/i', $ord, $match) ) {
$ord = hexdec($match[1]);
}
else {
$ord = intval($ord);
}
$no_bytes = 0;
$byte = [];
if ($ord < 128) {
return chr($ord);
}
else if ($ord < 2048) {
$no_bytes = 2;
}
else if ($ord < 65536) {
$no_bytes = 3;
}
else if ($ord < 1114112) {
$no_bytes = 4;
}
else {
return '';
}
switch( $no_bytes ) {
case 2:
$prefix = [ 31, 192 ];
break;
case 3:
$prefix = [ 15, 224 ];
break;
case 4:
$prefix = [ 7, 240 ];
}
for( $i = 0; $i < $no_bytes; $i++ ) {
$byte[ $no_bytes - $i - 1 ] = (($ord & (63 * pow(2, 6 * $i))) / pow(2, 6 * $i)) & 63 | 128;
}
$byte[0] = ( $byte[0] & $prefix[0] ) | $prefix[1];
$ret = '';
for ($i = 0; $i < $no_bytes; $i++) {
$ret .= chr($byte[$i]);
}
return $ret;
}, preg_replace(
'/\\\\u?{?([a-f0-9]{4,}?)}?/mi',
'$1;',
urldecode($s)
)
);
}
Еще у меня модели сломались. Раньше код не выбрасывал ошибку и исправно работал:
$user = iterator_to_array(
$model::get( 'users', [
'criterion' => 'email::'. $email,
'bound' => [
1
],
'course' => 'backward',
'sort' => 'id'
]),
)['model::users'];
Сейчас, видимо, в аргументах нельзя запятую в конце оставлять.
Какую вы задачу вообще решаете этим кодом?
Именно string, а не array. Этот код — это часть модуля для чистки SVG и HTML от XSS и по белому списку атрибутов. Он разворачивает entity's и роется в base64 до второй глубины, чтобы восстановить графику, если в ней найден запрещённый правилами JavaScript.
Используется везде, где разрешен HTML. Например, в письмах.
https://github.com/Full-R/RevolveR-CMF/blob/master/Kernel/Modules/Markup.php
В регулярке, возможно, не так идеально, как хотелось бы, но код работает хорошо.
protected static function hexEntToLetter( string $ord ): string {
$ord = $ord[1];
в колбек для preg_replace_callback всегда передается массив матчей, даже если матч будет только один.
Вы сами же потом берете у массива $ord второй элемент, но почему-то в сигнатуре функции обозначили $ord как string.
Код уже исправлен на
protected static function hexToSymbols( string $s ): string {
return preg_replace_callback(
'/([0-9a-fx]+);?/mi', function( $ord ) {
$ord = $ord[1];
Тип явно не указываю. Спасибо.
Пришлось сделать не красиво
Не хочу вас расстраивать..., но до этого тоже было так себе.
если в качестве аргумента указан eval строковое обращение к protected методу этого же класса через self.Потому что не нужно так делать.
<?php
class A {
public static function b() {
echo "Hello World!" . PHP_EOL;
}
public static function callSelf()
{
[self::class, 'b']();
[static::class, 'b']();
}
}
A::callSelf();
[A::class, 'b']();
public function sanitize(string|int $input): string;Сначала мы говорим, что хотим строгую типизацию, но потом сами же в нее не влезаем)))
В C# для таких целей есть перегрузка. В PHP к такому ещё не пришли.
Например то, что метод может принимать один и тот же параметр разных типов.
Классический пример любого языка со строгой типизацией, где есть алгебраические типы данных. Отвечая вашими словами: В C# к такому ещё не пришли (https://github.com/dotnet/csharplang/issues/399).
С другой стороны, а как вообще можно реализовать перегрузку при наличии такой, более мощной системы типов? Я вот не припомню ни одного языка, где есть подобное: Swift? TS? А про функциональный Idris даже говорить не стоит. Может я какой-то пропустил, где подобное есть?
Справедливости ради — перегрузку можно было бы реализовать и в упомянутых языках, например через введение неявно объявленной функции проксирующей исполнение в нужную перегрузку. Другое дело что у этого есть последствия с которыми означенные языки мириться не хотят, например в динамических языках это приведет к изменению сигнатуры в рантайме.
string|int
не алгебраический тип данных, а кривой костыль, использование которого говорит о том, что метод, использующий такой тип, спроектирован некорректно.Во-первых, примитивный тип не может быть алгебраическим.
Во-вторых, для алгебраического типа данных должен определяться интерфейс и несколько классов имплементящих этот интерфейс. При этом очень желательно, чтобы заранее были известны все типы имплементящие этот интерфейс. Это может быть достигнуто, например, фичей языка, которая разрешает создавать классы реализующих этот интерфейс только том же файле, где определен интерфейс.
Во-первых, примитивный тип не может быть алгебраическим
Это уже не примитивный, как не крути. Список примитивных типов фиксирован, а тут комплексный тип.
Во-вторых, для алгебраического типа данных должен определяться интерфейс и несколько классов имплементящих этот интерфейс.
Кому должен? Откуда такие требования?
Это уже не примитивный, как не крути. Список примитивных типов фиксирован, а тут комплексный тип.
Здесь int примитивный тип. Тип «или int, или строка» не алгебраический, из-за использования инта.
Кому должен? Откуда такие требования?
Вы можете посмотреть как алгебраические типы данных реализованы в других языках или почитать статью на википедии, вот цитата:
Конструктор представляет собой функцию, которая строит значение своего типа на основе входных значений. Для последующего извлечения этих значений из алгебраического типа используется сопоставление с образцом.
Ключевая фраза здесь: "используется сопоставление с образцом" (другими словами это паттерн матчинг). Если вам заранее неизвестны все классы, то и написать всеобъемлющий паттерн матчинг вы не сможете.
Тип «или int, или строка» не алгебраический, из-за использования инта.
Комплексные, составные типы данных для меня означают, прежде всего, комбинации примитивных типов. Какие конкретно комбинации (объединение, структура, какая-нибудь коллекция и т. д.) — детали.
Вы можете посмотреть как алгебраические типы данных реализованы в других языках или почитать статью на википедии, вот цитата:
Как реализованы в других языках — дело десятое. Главное — определения.
Ну вот тут имеем, что все классы известны (их два: int и string), и сопоставление делается без проблем — как в текстовом виде (int даёт число из цифр, с возможным минусом, а string — строку, обычно в двойных кавычках), так и внутри программы (int и string отличаются).
Что вам не нравится?
Или в PHP, аналогично Perl и JS, нет жёстких границ между числом и строкой? Ну тогда частично согласен, но это проблема одного конкретного языка.
Границы более чёткие чем в них, но не сильно и есть нюансы, зависящие от режима работы строгий или нет. В принципе с int|string могут быть неоднозначности, если передать числовую строку в нестрогом режиме: если просто int, то она преобразуется, но проверил в int проходят только числа, любые строки, включая числовые, после тайпхитинга — строки — попыток преобразования строк в число нет, как было бы с голым int в нестрогом режиме
Вот реально, научи дурака солиду молиться:)
Отсюда никак не следует, что типизация тут нестрогая.
Именно это и следует. Если нет, что что тогда такое для вас строгая типизация?
Тут нет возможности выполнить с аргументами, отличными от string или int, которые явно указаны. Строгая типизация не допускает исполнения с типами, противоречащими декларации и системе типов. Тут вы не сможете передать объект или булево, NPE внутри функции исключён гарантиями языка.
Тут нет возможности выполнить с аргументами, отличными от string или int
То что в качестве аргумента можно передать или int, или string это уже не строгая типизация. При строгой типизации метод или принимает только int, или принимает только string.
Это ваша личная трактовка, да?
Это ваша личная трактовка, да?
Нет. Давайте начнем отсюда:
По одной из классификаций, языки программирования неформально делятся на сильно и слабо типизированные.
…
В русскоязычной литературе часто используется термин «строгая типизация»; общепринятый вариант «сильная типизация» используется лишь при противопоставлении «слабой типизации».
Теперь посмотрим сюда:
Стати́ческая типиза́ция — приём, широко используемый в языках программирования, при котором переменная, параметр подпрограммы, возвращаемое значение функции связывается с типом в момент объявления и тип не может быть изменён позже (переменная или параметр будут принимать, а функция — возвращать значения только этого типа)
Определение «int|string» это не тип, это указание на то, что в этом месте будет или тип int, или тип string, что противоречит определению из цитаты выше.
С точки зрения разработчиков PHP это действительно отдельный тип, но с точки зрения здравого смысла это выглядит очень странно. Эту идею можно развить до примерно следующей (псевдокод):
interface Animal
class Cat implements Animal
class Dog implements Animal
interface Vehicle
class Car implements Vehicle
class Moto implements Vehicle
function process(Animal|Vehicle $item)
Очевидно, что метод с такой сигнатурой спроектирован некорректно, не может один метод что-то делать и с животными, и с транспортом. Точно также и «int|string» говорит о некорректном дизайне метода, даже если с точки зрения разработчиков языка это валидная конструкция.
Очевидно, что метод с такой сигнатурой спроектирован некорректно, не может один метод что-то делать и с животными, и с транспортом.
Может. Например регистрировать в реестре с бизнесовой точки зрения или сохранять состояние или сериализировать с технической
В каких-то случаях должен, а в каких-то даже не может. Например, их код мы не контролируем, они вообще из разных источников, но метод нам нужный есть и там, и там.
function register($input) {
if ($input instanceof Type1) { /* do smth with Type1 */}
elseif ($input instanceof Type2) { /* do smth with Type2 */}
// ...
elseif ($input instanceof Type999) { /* do smth with Type999 */}
}
В случае с интерфейсом Registerable код будет предельно простым:
function register(Registerable $input) {
$input.register();
}
В случае если мы не контролируем тип, который надо зарегистрировать, то просто делаем для него обертку:
class RegisterableInt implements Registerable {
function register(int $input) {
// do smth with int
}
}
с точки зрения здравого смысла это выглядит очень странноСчитаете ли вы, что union types тоже с точки зрения здравого смысла выглядят странно? Между «int|float» и union type разница только лишь в том, что «int|float» не имеет названия (уже сейчас планируется к реализации в будущем, смотреть RFC).
Эту идею можно развить до примерно следующей (псевдокод)На огромном количестве языков при желании можно написать откровенный говнокод. Это не значит, что какие-то конструкции не нужно было вводить или что их нужно выкинуть.
не может один метод что-то делать и с животными, и с транспортомЖивотное, как и транспорт, можно загонять в помещения, которые подходят исключительно под животных и транспорт из-за отсутствия «лицензии» на хранение всего другого.
Если вы спросите кто будет отвечать за «загон», то ни животное, ни транспорт, ни помещение, а отдельная «сторона» — человек.
Также вас может заинтересовать каким образом будет осуществляться «поддержка хранения» животных или транспорта. В таком случае отвечу, что это так же лежит на отдельной сущности. Помещение вполне может быть не обязано следить за «состоянием» животного или транспорта.
Я считаю, что существуют случаи, когда в каком-то из мест системы интерфейсы могут быть не нужны.
но с точки зрения здравого смысла это выглядит очень странно.
Есть области, где подобный здравый смысл вынужден отступить перед реальностью. Вот, например, вы тянете данные из электронной таблицы (Excel, Google Sheets...), а там по умолчанию автоопределение типа по содержимому (может, и нельзя фиксировать тип — я искал и не нашёл). И если вы читаете из подобного, должны быть готовы, что поступит string, которое на самом деле должно содержать целое, плавучее, дату или ещё что-то, или дату вместо денег, и прочая и прочая.
Это анонимный объединённый тип. Тип-сумма, логическая дизъюнкция, тип, множество значений которого является объединением множеств всех значений int и всех значений string. Ничего другого язык не пропустит и/или не вернёт.
Ничего другого язык не пропустит и/или не вернёт.Это понятно. Но на стороне клиентского кода все равно будет неочевидно, что конкретно будет возвращено методом: число или строка, отсюда мой вывод о том, что это не может считаться строгой типизацией.
Кроме того смешивание чисел и строк это явный пример плохого дизайна, как в моем примере выше. И если в случае «Animal|Vehicle» ошибка дизайна очевидна, то чем в таком случае отличается пример с «int|string».
В том плане, что предположим, перегрузка бы была. Тогда:
class A {
public function foo(int $a, int $b = 0): void {
echo "1";
}
public function foo(int $a): void {
echo "2";
}
}
$x = new A();
$x->foo(123); // что выведется?
по-нормальному и не сделаешь перегрузку из-за поддержки значений по-умолчанию для параметровКак минимум в Delphi нашли способ сделать. Если разработчиком будут написаны «неоднозначные» декларации методов, то процесс компиляции будет остановлен с ошибкой.
что выведется?PHP Fatal Error?
Вот реально, тяжёлый матан на пыхе не пишут. А дженерики, как и шаблоны плюсов, тащат только на матане, когда можно выбрать тип данных — байт или complex<double>, причём только в случае, если оба поддерживаются процессором/видеокартой.
В тех же плюсах вы технически можете засунуть в условную сортировку пузырьком тип Список или Хэш. Но вот честно — лучше бы не могли. Потому что она и так плоха, но на всём кроме настоящих массивов в стиле С совсем днище.
есть psalm.
Дженерики, равно как и тайпчек в рантайме не нужны
То, что происходит с php меня очень забавляет.
Сам я устал от php. Писал на нем лет восемь и стоило один раз попробовать c++ — и боже мой! Это так прекрасно!
А сейчас php пытается в строгость. Но какой от нее толк, если все эти проверки происходят в момент выполнения кода? Да, конечно, у вас всех умная IDE, которая всё подсвечивает (а чаще не всё), и прикрученный статический анализатор. Но это костыли.
Сам я устал от php. Писал на нем лет восемь и стоило один раз попробовать c++ — и боже мой! Это так прекрасно!
Рекомендую попробовать Rust, в C++ вы ещё успеете разочароваться.
return [
self::PENDING => 'orange',
self::PAID => 'green',
][$this->value] ?? 'gray';
/**
* @method static self PENDING()
* @method static self PAID()
*/
class InvoiceState extends Enum
{
private const PENDING = 'pending';
private const PAID = 'paid';
public function getColour(): string
{
return [
self::PENDING => 'orange',
self::PAID => 'green',
][$this->value] ?? 'gray';
}
}
Спасибо.
Создаётся одноразовая карта соответствий статус => цвет, тут же из неё извлекается элемент по текущему значению статуса и возвращается. Если элемента не существует, то возвращается 'gray' без каких-либо предупреждений или ошибок.
[self::PENDING => 'orange', self::PAID => 'green',]
Пытаемся взять из него значение по ключу "$this-value"
[...][$this->value]
Если ключа нет, вернем «gray»
?? 'gray'
Альтернативный код:
$list = [
self::PENDING => 'orange',
self::PAID => 'green',
];
if (!array_key_exists($this->value, $list)) {
return 'gray';
}
return $list[$this->value];
switch ($this->value) {
case self::PENDING:
return 'orange';
case self::PAID:
return 'green';
default:
return 'gray';
}
$message = match ($statusCode) {
200 => null,
500 => throw new ServerError(),
default => 'unknown status code',
};
Сильно сладко. С таким количеством сахара получается PHPerl какой-то :)
PHP 8: код «До» и «После» (сравнение с PHP 7.4)