среда, 4 августа 2021 г.

Интерпретация переменных в строке

Сегодня еще один пост о всяких веселых способах написать код в php.

Всем известно (а если не известно, то читайте мануал), что в строках в двойных кавычках переменные и некоторые другие выражения обрабатываются и вместо этой переменной\выражения выводится результат. Простейший пример:

$number = 42;
echo "Number is $number";
// outputs "Number is 42"

Теперь зададимся вопросом - а можно ли как-то интерпретировать в строках какие-либо выражения? Например, сложение двух чисел. Естественно, написание в лоб приводит к ошибкам:

$a = 42;
$b = 24;
echo "Sum is {$a + $b}";

Что же делать? Понятно, что можно завести класс, объявить в нем метод суммирования, но это как-то много кода:

class Summator {
    public function sum($a, $b) {
        return $a + $b;
    }
}

$a = 42;
$b = 24;
$o = new Summator();
echo "Sum is {$o->sum($a, $b)}";
// outputs "Sum is 66"

Подумав еще немного можно прийти к следующему - создадим переменную, которая будет хранить анонимную функцию, и в строке вызовем эту переменную:

$a = 42;
$b = 24;
$sum = function($a, $b) { return $a + $b; };
echo "Sum is {$sum($a, $b)}";
// outputs "Sum is 66"

А с учетом стрелочных функций (с версии 7.4), которые имеют доступ к переменным родительской области видимости, код упрощается до такого:

$a = 42;
$b = 24;
$sum = fn() => $a + $b;
echo "Sum is {$sum()}";
// outputs "Sum is 66"

Ну и наконец самая безумная версия:

$a = 42;
$b = 24;
echo "Sum is ${0 * ${0}=$a+$b}";
// outputs "Sum is 66"

Что же здесь происходит?

Здесь мы пытаемся вывести переменную с названием, которое получается как результат умножения 0 на переменную ${0}. А значение ${0} является суммой значений $a и $b. Почему php выполняет присваивание значения переменной при парсинге строки - не знаю. Но тем не менее, мы получаем, что переменной ${0} присваивается 66. А так как результатом присваивания является присвоенное значение, то 0 * ${0}=$a+$b превращается в 0 * 66, и выражение ${0 * ${0}=$a+$b} сворачивается в ${0}. То есть мы хотим вывести переменную с названием 0, которую мы уже определили ранее, как сумму $a и $b. Вот и получаем в выводе 66.

Спасибо за внимание и никогда не пишите такой код)

пятница, 18 июня 2021 г.

Ликбез по подсчету количества частей sms

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

Вводные данные: текст, который вы хотите отправить с помощью sms-сервиса, может быть достаточно большим, например, содержать 150-200 символов. Сотовый оператор (или другой sms-провайдер) не может отправить такой длинный текст как одну sms-ку, и потому разбивает ее на несколько частей. В зависимости от кодировки символов в тексте максимальная длина части разная. Отправка сообщения частями не значит, что получатель получит 2 разные sms-ки. В телефоне получателя части соединятся в одну sms-ку. Поэтому для получателя сообщение любой длины будет выглядеть как одна sms-ка.

А вот отправителю сложнее. Если вы используете какое-то стороннее API для отправки sms, то будьте уверены, что длинное сообщение будет отправлено частями. Таким образом, если вы снимаете со своего пользователя сколько-то денег за отправку сообщения, но при этом не учитываете факт отправки по частям, то баланс может не сойтись. Кстати, интересный факт - не все sms-провайдеры держат информацию о правилах деления на части в более-менее открытом доступе. У кого-то придется пошерстить мануал или FAQ, кому-то придется задать вопрос в саппорт. (Из личного опыта интеграции как минимум с 7-8 провайдерами, один из которых - заграничный).

В случае если в сообщении есть хотя бы один символ кириллицы - то максимальная длина одного сообщения составит 70 символов. Точнее даже не символов, а позиций (но об этом позже). То есть 70 и меньше символов будут отправлены как одна sms-ка. Если символов становится больше 70, то провайдер начнет разбивать сообщение на части. Обычно длина каждой части - 67 символов. 70 и 67 это общепринятые значения на данный момент, но лучше уточнить эти лимиты в документации оператора\провайдера.

То есть, если ваше сообщение состоит из 90 символов, то провайдер отправит его как две части: первая часть длиной 67 символов, вторая - 23 символа. Сообщение длиной 71 символ не удастся отправить как одно, тут тоже две части - 67 + 4. Так что либо укладывайтесь в 70, либо пишите еще больше текста, все равно за вторую часть придется платить

Переходим к сообщениям, написанным без использования кириллицы. Здесь лимит длины сообщения составляет уже 160 символов на одну смс и 153 символа на часть (хотя я встречал одного провайдера со 157 символами на часть). Расчет частей аналогичен: сообщение из 190 символов будет состоять из двух частей: 153 и 37 символов каждая.

Следующий важный момент: как определить какую из пар (70/67 или 160/153) использовать для расчета числа частей? Понятно, что если в тексте сообщения есть хотя бы один кириллический символ, то выбор очевиден - 70/67. В остальных случаях используем 160/153. Однако, копнем дальше - что делать, если у вас какой-нибудь многоязычный сервис и хочется корректно рассчитывать число частей для разных языков, даже с учетом национальных особенностей? Здесь на помощь приходит тот факт, что базовой кодировкой сообщений, отправляемых через sms, является GSM 03.38. Таблица символов данной кодировки содержит помимо некоторых ASCII-символов (цифры, латинский алфавит и некоторые знаки препинания) также символы, которые не относятся к ASCII. Пример: испанская ñ или немецкое ß хоть и не входят в ASCII-символы, но в смске займут один символ в отличие от кириллических символов.

Приглядевшись к таблице символов кодировки GSM 03.38 можно заметить, что в ней есть некоторые символы, которые занимают две позиции в смске. К таким символам относится, например, знак евро . Это значит, что если в тексте сообщения есть 69 кириллических символов и семидесятым символом идет знак евро, то формально в сообщении 71 символ, так как знак евро занимает две позиции. Тут тоже надо быть внимательным, чтобы правильно вычислять число частей и тарифицировать отправку.

Как следует из всего вышесказанного, для расчета числа частей надо написать собственный, не самый сложный код. Однако, можно использовать уже имеющиеся библиотеки. Для php можно использовать, например, мной же и написанную библиотеку sms-charset-detector, которая определяет кодировку сообщения. В зависимости от результата, вы можете выбрать какую пару чисел использовать. Для javascript можно использовать, например, sms-counter, здесь можно сразу увидеть число частей, на которое будет разбито сообшение.

Итак, в данном посте вы познакомились с ограничениями на отправку sms-сообщений и узнали основные факты, необходимые для корректного расчета количества частей sms-сообщения.

понедельник, 3 мая 2021 г.

Приведение переменной к массиву

Сегодня краткий пост о том, как привести переменную какого-то типа к типу массив (array).

Итак, первый вариант, сразу приходящий в голову:

$var = 42;
$varArray = [$var];

Он хорош до тех пор, пока $var не является массивом. В случае с массивом мы получаем двумерный массив. Естественно, это нас не устраивает. Поэтому улучшаем конструкцию:

$var = 42;
if (!is_array($var)) {
    $var = [$var];
}

Но тут многовато строк, можно ли попроще?

Вспоминаем, зачем мы здесь реально собрались, и используем приведение типов:

$var = 42;
$varArray = (array) $var;
print_r($varArray);
// Вывод: Array
// (
//   [0] => 42
// )

$var = [42, 42];
$varArray = (array) $var;
print_r($varArray);
// Вывод: Array
// (
//   [0] => 42
//   [1] => 42
// )

На этом всё.

четверг, 11 марта 2021 г.

Doctrine: ошибка парсинга запроса

На самом деле пост довольно специфический. Начну с описания проблемы, встреченной мной уже минимум два раза. Имеем обычный с виду код выполнения запроса:

$conn = $this->getEntityManager()->getConnection();
$conn->executeQuery($query, [
    'param1' => 42,
    'param2' => 'forty-two',
    'param3' => '%some_other_data%',
]);

При выполнении этого кода ловим ошибку типа такой:

SQLSTATE[08P01]: <<Unknown error>>: 7 ERROR:  bind message supplies 0 parameters, but prepared statement "pdo_stmt_000...." requires 3

Казалось бы, что может пойти не так? Мы взяли запрос с именованными параметрами, взяли данные для подстановки и передали всё это в Доктрину. Однако, практика показывает, что при неком специфичном тексте запроса Доктрина не справляется с парсингом текста запроса и считает, что в данном запросе нет именованных параметров. Исходя из моего опыта, такое случалось на запросах с огромным количеством :, так как именно двоеточие определяет начало именованного параметра.

В качестве запроса, ломающего Доктрину можно привести такой (использую postgresql, текст запроса очень приблизительный):

SELECT
    id,
    value::FLOAT,
    exec_date
FROM (
    VALUES
    (
        8095,
        41,
        '2020-01-02 10:48:17'
    ),
    --- Таких values тут сотни две
) as vs (id, value, exec_date)
JOIN
    some_table t ON vs.id = t.foreign_id::INT
WHERE
   t.created_at::TIMESTAMP + interval '3600 second' <= vs.executed_at::TIMESTAMP
   AND t.field_one = :param1
   AND t.field_two = :param2
   --- и еще что-нибудь

Найденное мной быстрое решение - это поменять именованные параметры (:param1) на позиционные (?), с такими данными Доктрина успешно справляется. Если есть желание - можете самостоятельно нырнуть в дебри и поковыряться в регулярках, а возможно где-то уже висит issue (но это не точно), или в третьей версии Доктрины это вообще уже исправлено.

среда, 17 февраля 2021 г.

Интеграция php-кода и html-верстки

Сегодня у нас урок для начинающих. Допустим, вы - начинающий программист, или верстальщик, или вообще пытались мимо проходить, но жизнь заставила соединить верстку (она же html-разметка) и php-код. Как это сделать с минимальными усилиями?

Итак, допустим у вас есть верстка для списка новостей:

<div>
  <h1>Это заголовок списка новостей</h1>
  <div class="some-class">
    <h2>Заголовок новости 1</h2>
    <p>Небольшой блок текста для ознакомления</p>
    <a href="/detail.php?id=1">Читать текст целиком</a>
    <span class="author">Автор новости: И. Иванов</span>
  </div>
  <div class="some-class">
    <h2>Заголовок новости 2</h2>
    <p>Небольшой блок текста для ознакомления</p>
    <a href="/detail.php?id=2">Читать текст целиком</a>
    <span class="author">Автор новости: И. Иванов</span>
  </div>
  <div class="some-class">
    <h2>Заголовок новости 3</h2>
    <p>Небольшой блок текста для ознакомления</p>
    <a href="/detail.php?id=3">Читать текст целиком</a>
    <span class="author">Автор новости: И. Иванов</span>
  </div>
</div>

И есть массив новостей $newsArray, полученный из БД/API/откуда-то ещё. Мы не будем рассматривать, как вы получили этот массив, будем считать, что он у вас есть.

Для лучшего понимания, что и как нужно сделать, разобьем нашу задачу на несколько шагов.