Pull to refresh

Comments 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'];



Сейчас, видимо, в аргументах нельзя запятую в конце оставлять.
У вас входной аргумент у первого метода должен быть array, а не string, кроме того, в регулярках какой-то адок, там явно что-то неправильно работает.

Какую вы задачу вообще решаете этим кодом?
похоже на костыль для конвертации экранированного юникода.
Похоже, да, но хотелось бы убедиться, там какие-то слеши в регулярках ещё.

Full-R, верно ли, что весь этот код можно заменить на
$ php -r 'var_dump(html_entity_decode("&#10731;&#x02A13;", ENT_XML1, "UTF-8"));'
string(7) "⧫⨓"

Попробую вечером проверить будет ли работать также.

Верно. Код вроде работает также. Теперь использую html_entity_decode().

Спасибо!

Именно 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];

Тип явно не указываю. Спасибо.

Ваш первый вариант у меня работает (PHP 7.4.8, MacOS), если исправить тип параметра. Посмотрите чуть выше, похоже весь этот код одной строкой надо заменить.
Что конкретно вы делаете в этом методе, можете сказать?

Маскированные хексом символы преобразую в текстовый вид.

Пришлось сделать не красиво

Не хочу вас расстраивать..., но до этого тоже было так себе.


если в качестве аргумента указан 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 — строку, обычно в двойных кавычках), так и внутри программы (int и string отличаются).
Что вам не нравится?
Или в PHP, аналогично Perl и JS, нет жёстких границ между числом и строкой? Ну тогда частично согласен, но это проблема одного конкретного языка.

Границы более чёткие чем в них, но не сильно и есть нюансы, зависящие от режима работы строгий или нет. В принципе с int|string могут быть неоднозначности, если передать числовую строку в нестрогом режиме: если просто int, то она преобразуется, но проверил в int проходят только числа, любые строки, включая числовые, после тайпхитинга — строки — попыток преобразования строк в число нет, как было бы с голым int в нестрогом режиме

Зато в теле вызывающего таких проверок нет.
Вот реально, научи дурака солиду молиться:)
Отсюда никак не следует, что типизация тут нестрогая.
Отсюда никак не следует, что типизация тут нестрогая.

Именно это и следует. Если нет, что что тогда такое для вас строгая типизация?

Тут нет возможности выполнить с аргументами, отличными от string или int, которые явно указаны. Строгая типизация не допускает исполнения с типами, противоречащими декларации и системе типов. Тут вы не сможете передать объект или булево, NPE внутри функции исключён гарантиями языка.

Тут нет возможности выполнить с аргументами, отличными от string или int


То что в качестве аргумента можно передать или int, или string это уже не строгая типизация. При строгой типизации метод или принимает только int, или принимает только string.

Это ваша личная трактовка, да?

Это ваша личная трактовка, да?

Нет. Давайте начнем отсюда:
По одной из классификаций, языки программирования неформально делятся на сильно и слабо типизированные.

В русскоязычной литературе часто используется термин «строгая типизация»; общепринятый вариант «сильная типизация» используется лишь при противопоставлении «слабой типизации».

Теперь посмотрим сюда:
Стати́ческая типиза́ция — приём, широко используемый в языках программирования, при котором переменная, параметр подпрограммы, возвращаемое значение функции связывается с типом в момент объявления и тип не может быть изменён позже (переменная или параметр будут принимать, а функция — возвращать значения только этого типа)

Определение «int|string» это не тип, это указание на то, что в этом месте будет или тип int, или тип string, что противоречит определению из цитаты выше.
Это union type, объединённый тип.
Почитал про юнион типы в PHP 8, в этом посте в качестве примера приводится тип «int|float». Такой тип в сравнении с «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» говорит о некорректном дизайне метода, даже если с точки зрения разработчиков языка это валидная конструкция.
Очевидно, что метод с такой сигнатурой спроектирован некорректно, не может один метод что-то делать и с животными, и с транспортом.

Может. Например регистрировать в реестре с бизнесовой точки зрения или сохранять состояние или сериализировать с технической

Нет. В таком случае метод на вход должен принимать тип Registerable или Serializable (названия из головы), а соответствующие классы их должны имплементить.

В каких-то случаях должен, а в каких-то даже не может. Например, их код мы не контролируем, они вообще из разных источников, но метод нам нужный есть и там, и там.

В долгосрочной перспективе с таким подходом ничего хорошего не получится. В случае если у нас есть десятки типов, которые надо как-то регистрировать или сериализовать, то метод, принимающий на вход много разных типов получится похожим на что-то вроде:
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».

Очевидность — это не про строгую типизацию :) Для анализатора и/или рантайма будет очевидно (в некоторых случаях, если о PHP говорить) что пытаются обращаться с результатом, не сделав проверки на тип и он будет требовать её сделать или явно привести.

в PHP по-нормальному и не сделаешь перегрузку из-за поддержки значений по-умолчанию для параметров. В ЯП обычно либо одно, либо другое.

В том плане, что предположим, перегрузка бы была. Тогда:

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?
Мне следовало выразиться более точно: в скриптовых языках обычно либо то, либо другое.
пока не будет дженериков хрен нам а не строгая типизация 8(
А, простите, зачем?
Вот реально, тяжёлый матан на пыхе не пишут. А дженерики, как и шаблоны плюсов, тащат только на матане, когда можно выбрать тип данных — байт или complex<double>, причём только в случае, если оба поддерживаются процессором/видеокартой.
В тех же плюсах вы технически можете засунуть в условную сортировку пузырьком тип Список или Хэш. Но вот честно — лучше бы не могли. Потому что она и так плоха, но на всём кроме настоящих массивов в стиле С совсем днище.

Типизированных коллекции — это матан? Промисы?

Затем что заколебали костыли с коллекциями. Еще больше заколебал меня синтаксис аля доктрины с 95% кода в комментариях.

Особенно со ссылкой на psalm, где они есть.

Советую открыть для себя psalm. Ну и до кучи попробовать разобраться: для чего вам в рантайме (!) нужны дженерики.

Сначала мы не хотим возится с этими типами, потом мы вдруг понимаем, что нам нужна типизация, а уже потом мы в нее не влезаем.
То, что происходит с php меня очень забавляет.
Сам я устал от php. Писал на нем лет восемь и стоило один раз попробовать c++ — и боже мой! Это так прекрасно!
А сейчас php пытается в строгость. Но какой от нее толк, если все эти проверки происходят в момент выполнения кода? Да, конечно, у вас всех умная IDE, которая всё подсвечивает (а чаще не всё), и прикрученный статический анализатор. Но это костыли.
Сам я устал от php. Писал на нем лет восемь и стоило один раз попробовать c++ — и боже мой! Это так прекрасно!

Рекомендую попробовать Rust, в C++ вы ещё успеете разочароваться.

Rust уже пробовал. Rust прекрасен. Язык который сам проверяет больше случаев, чем многие статические анализаторы. Это закономерное и логичное развитие ЯП.
Только вот работы на нем нет. Для меня.
код становится все менее читабельным. Так и до brain***k недалеко :(
иной раз я думаю что пхп стал популярен знаку доллара вначале переменой и тому что там статической типизации шел 20 год, и первый вопрос о приеме на работу студента звучал так. # а знаете ли вы пхп #
А может кто-нибудь обьяснить как это работает?

        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:
switch ($this->value) {
    case self::PENDING:
        return 'orange';
    case self::PAID:
        return 'green';
    default:
        return 'gray';
}
Тоже не понял зачем создавать массив под такую задачу (пусть и immutable в случае opcache). Сам смысл `match` — короткая форма `switch`. Куда интересней вот такое использование:
$message = match ($statusCode) {
    200 => null,
    500 => throw new ServerError(),
    default => 'unknown status code',
};

Когда вариантов много, массив как-то проще читается на предмет вход-выход

Отличные штуки! Заметно, что многие идеи PHP принимает из Java и C# и даже TypeScript.

Сильно сладко. С таким количеством сахара получается PHPerl какой-то :)

Был себе нормальный язык у которого было хотя бы преимущество в читабельности, теперь это похоронили. Ну что ж, похоже теперь на пыхе только вордпресеры останутся писать свою лапшу во вьхах.
PHP плавно превращается в самостоятельный фреймворк для формошлёпов… неплохо, да. Нечего полянку нормальных ЯПов занимать. Здесь и так тесно.
Sign up to leave a comment.