tag:blogger.com,1999:blog-88917143871251338572024-03-14T08:03:34.699+03:00Блог о починке примусов... и программированииДядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.comBlogger69125tag:blogger.com,1999:blog-8891714387125133857.post-42423611955496706352023-04-04T11:12:00.024+03:002023-04-04T11:12:00.196+03:00Регулярные выражения в postgresql<p>Сегодня поразбираемся с тем, как в postgresql работать с регулярными выражениями.</p>
<p>Исходные данные: таблица <code>users</code> следующего вида:</p><code class="prettyprint" style="white-space: pre;"> id | first_name | full_name | last_name | email
-------------------------------------------------------------------
1 | Иван | Иван Дмитриев | - | ivan@mail.ru
2 | Ольга | Ольга Корнева | - | ko@ya.ru
3 | Вячеслав | Вячеслав Домашнев | - | domv@yandex.ru
4 | Игорь | Игорь Черников | - | ichern@gmail.com
5 | Галина | Галина Горных | - | Galina@Yandex.ru
</code>
<p>Начнем с простейшего - выберем все записи, где поле <code>email</code> содержит подстроку <b>ya</b>.</p>
<a name='more'></a>
<p>Это можно сделать несколькими способами. Самый известный - использовать оператор <code>LIKE</code>. Запрос будет выглядеть так:<br />
<code class="prettyprint" style="white-space: pre;">SELECT email FROM users WHERE email LIKE '%ya%'</code>
</p>
<p><code>%</code> означает любое количество символов. <code>_</code> означает один символ.</p>
<p>Результат выполнения:<br />
<code class="prettyprint" style="white-space: pre;"> id | first_name | full_name | last_name | email
----+------------+-------------------+-----------+----------------
2 | Ольга | Ольга Корнева | | ko@ya.ru
3 | Вячеслав | Вячеслав Домашнев | | domv@yandex.ru
</code></p>
<p>Второй вариант: используя оператор <code>~</code> (тильда) текст запроса можно сократить до:<br />
<code class="prettyprint" style="white-space: pre;">SELECT email FROM users WHERE email ~ 'ya'</code></p>
<p>Результат выполнения <b>аналогичен</b> предыдущему.</p>
<p>Заметим, что при использовании <code>~</code> не требуется использовать <code>%</code> или <code>_</code>, что сокращает текст запроса. Однако, <code>~</code> может не поддерживаться по умолчанию в используемом фреймворке или библиотеке, например в <b>Doctrine</b>.</p>
<p>Далее: если присмотреться, то эти запросы возвращают не все подходящие под условие результаты. А именно не возвращается адрес <b>Galina@Yandex.ru</b>.</p>
<p>Так происходит потому, что поиск выполнялся с учетом регистра. Для регистронезависимого поиска запросы выглядят так:<br />
<code class="prettyprint" style="white-space: pre;">
SELECT email FROM users WHERE email ILIKE '%ya%'
SELECT email FROM users WHERE email ~* 'ya'
</code><br />
Результат выполнения:<br />
<code class="prettyprint" style="white-space: pre;"> id | first_name | full_name | last_name | email
----+------------+-------------------+-----------+------------------
2 | Ольга | Ольга Корнева | | ko@ya.ru
3 | Вячеслав | Вячеслав Домашнев | | domv@yandex.ru
5 | Галина | Галина Горных | | Galina@Yandex.Ru
</code></p>
<p>Если регулярное выражение требуется усложнить и модификаторов <code>%/_</code> недостаточно, то используем уже известную <code>~</code>. Например, найдем все адреса с доменом <b>mail</b> или <b>gmail</b>:<br />
<code class="prettyprint" style="white-space: pre;">
SELECT email FROM users WHERE email ~ '@(gmail|mail)'
-- или
SELECT email FROM users WHERE email ~ '@(g?)mail'
</code><br />
Для обоих запросов результат выполнения:<br />
<code class="prettyprint" style="white-space: pre;"> email
------------------
ivan@mail.ru
ichern@gmail.com
</code>
</p>
<p>Теперь попробуем в деле функции, которые возвращают подстроки, соответствующие регулярному выражению. Для начала найдем все домены из значений поля <code>email</code>:
<br />
<code class="prettyprint" style="white-space: pre;">
SELECT SUBSTRING(email FROM '.*@(.*)') AS domain FROM users
</code><br />
Результат выполнения:<br />
<code class="prettyprint" style="white-space: pre;"> domain
-----------
mail.ru
ya.ru
yandex.ru
gmail.com
Yandex.Ru
</code>
</p>
<p>Комбинируя <code>SUBSTRING</code> с другими функциями, можно найти, например, все различные домены первого уровня в почтовых адресах:<br />
<code class="prettyprint" style="white-space: pre;">
SELECT DISTINCT(LOWER(SUBSTRING(email FROM '.*\.(.*)$'))) AS tld FROM users;
</code><br />
Результат выполнения:<br />
<code class="prettyprint" style="white-space: pre;"> tld
----
ru
com
</code>
</p>
<p>И последним заданием попробуем заполнить пока еще пустое поле <code>last_name</code> значениями, взятыми из поля <code>full_name</code>. Считаем, что фамилия - это все символы после пробела в поле <code>full_name</code>:<br />
<code class="prettyprint" style="white-space: pre;">
UPDATE users SET last_name = SUBSTRING(full_name FROM '.*\s(.*)$')
-- затем
SELECT fist_name, last_name FROM users
</code><br />
Результат выполнения:<br />
<code class="prettyprint" style="white-space: pre;"> first_name | last_name
------------+-----------
Иван | Дмитриев
Ольга | Корнева
Вячеслав | Домашнев
Игорь | Черников
Галина | Горных
</code>
</p>
<p>Резюмируя:</p>
<ul>
<li>простейший поиск осуществляем с помощью <code>LIKE</code> или <code>~</code> и их регистронезависимых аналогов <code>ILIKE</code> или <code>~*</code>;</li>
<li>поиск посложнее осуществляем с помощью <code>~/~*</code>;</li>
<li>для получения подстрок, соответствующих регулярному выражению, используем функцию <code>substring()</code></li>
</ul>
<p>Больше информации об использовании регулярных выражений - в <a href="https://postgrespro.ru/docs/postgresql/14/functions-matching" rel="nofollow" target="_blank">официальном руководстве</a>.</p>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-46904427046579265402022-06-17T10:10:00.003+03:002022-07-08T19:02:17.885+03:00Константы и пространства имен в php<p>Сегодня поисследуем использование пространств имен (они же <a href="https://www.php.net/namespace" rel="nofollow" target="_blank">неймспейсы</a>) для объявления констант и обращения к ним.</p>
<p>Как сказано в мануале: </p><blockquote>классы (включая абстрактные и трейты), интерфейсы, функции и <b>константы</b> зависят от пространства имен.</blockquote>
<p>То есть, если зарегистрировать константы с <b>одинаковыми именами</b> в нескольких неймспейсах, в том числе и глобальном, то за это ничего не будет, и обращение к каждой из констант будет работать без ошибок.</p>
<p>Проверим:<br /><br />
<code class="prettyprint">
namespace FirstNs {<br />
const SUPER_VALUE = 'NS-1';<br />
}<br /><br />
namespace SecondNs {<br />
const SUPER_VALUE = 'NS-2';<br />
}<br /><br />
namespace {<br />
const SUPER_VALUE = 'NS-GLOBAL';<br />
var_dump(<br />
SUPER_VALUE,<br />
FirstNs\SUPER_VALUE,<br />
SecondNs\SUPER_VALUE,<br />
);<br />
}<br /><br />
// Вывод:<br />
// string(9) "NS-GLOBAL"<br />
// string(4) "NS-1"<br />
// string(4) "NS-2"
</code></p>
<p>Ровно то, что нужно.</p>
<p>Но что если мы хотим определить константы методом <a href="https://www.php.net/manual/ru/function.define.php" rel="nofollow" target="_blank"><code>define</code></a>.</p>
<p>Не проблема, пробуем написать так:<br /><br />
<code class="prettyprint">
namespace FirstNs {<br />
define('SUPER_VALUE', 'NS-1');<br />
}<br /><br />
namespace SecondNs {<br />
define('SUPER_VALUE', 'NS-2');<br />
}<br /><br />
namespace {<br />
define('SUPER_VALUE', 'NS-GLOBAL');<br />
var_dump(<br />
SUPER_VALUE,<br />
FirstNs\SUPER_VALUE,<br />
SecondNs\SUPER_VALUE,<br />
);<br />
}
</code>
</p>
<p>Запускаем и видим:<br /><br />
<code class="prettyprint">
Warning: Constant SUPER_VALUE already defined in ...<br />
Warning: Constant SUPER_VALUE already defined in ...<br />
Fatal error: Uncaught Error: Undefined constant "FirstNs\SUPER_VALUE" in ...
</code></p>
<p>Судя по всему, мы пытаемся три раза определить одну и ту же константу в глобальном неймспейсе. Что же делать?</p>
<p>А просто указать нужный неймспейс при определении константы, благо php это позволяет:<br /><br />
<code class="prettyprint">
namespace FirstNs {<br />
define('FirstNs\\SUPER_VALUE', 'NS-1');<br />
// или<br />
define(__NAMESPACE__ . '\\SUPER_VALUE', 'NS-1');<br />
<br />
}<br /><br />
namespace SecondNs {<br />
define('SecondNs\\SUPER_VALUE', 'NS-2');<br />
}<br /><br />
namespace {<br />
define('SUPER_VALUE', 'NS-GLOBAL');<br />
var_dump(<br />
SUPER_VALUE,<br />
FirstNs\SUPER_VALUE,<br />
SecondNs\SUPER_VALUE,<br />
);<br />
}<br /><br />
// Вывод:<br />
// string(9) "NS-GLOBAL"<br />
// string(4) "NS-1"<br />
// string(4) "NS-2"
</code>
</p>
<p>Можно даже пойти дальше и в одном неймспейсе определить константу другого неймспейса:<br /><br />
<code class="prettyprint">
namespace FirstNs {<br />
define('SecondNs\\SUPER_VALUE', 'NS-2-1');<br />
}<br /><br />
namespace SecondNs {<br />
define('FirstNs\\SUPER_VALUE', 'NS-1-2');<br />}
</code>
</p>
<p>Делать так в продакшене <b>категорически не рекомендуется</b>.</p>
<p>Итак, сегодня мы разобрались с тем, как определять константы с учетом неймспейсов, используя как ключевое слово <code>const</code>, так и функцию <code>define</code>.</p>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-23325222036948495752022-02-08T05:55:00.001+03:002022-02-08T12:54:26.094+03:00Длина строк в Go<p>Этот пост родился из-за странного поведения валидатора <code>Length</code> из пакета <a href="https://github.com/go-ozzo/ozzo-validation" rel="nofollow" target="_blank">ozzo-validation</a>.</p>
<p>Следуя руководству, можно зарегистрировать, например, такой валидатор для строк:<br /><br />
<code class="prettyprint">
data := "Некоторая строка"<br />
err := validation.Validate(data,<br />
validation.Required,<br />
validation.Length(10, 16),<br />
)<br />
fmt.Println(err)
</code>
</p>
<p>И так как в строке у нас кириллические символы, и более того - кодировка строки <b>utf-8</b>, то валидация не пропускает строку, падая с ошибкой "<i>the length must be between 10 and 16</i>". Почему так происходит, ведь валидируемая строка содержит ровно <b>16</b> символов?</p>
<p>Для того, чтобы понять почему валидация выдает ошибку - придется сходить в <a href="https://github.com/go-ozzo/ozzo-validation/blob/master/length.go#L53" rel="nofollow" target="_blank">исходный код валидатора</a>. Поиски приводят к тому, что для определения длины валидируемой строки используется метод <code>Len()</code> из типа <code>reflect#Value</code>. Путешествие дальше приводит нас к тому, что свойство <code>Len</code> вычисляется с помощью встроенной функции <code>len</code>. А как сообщает <a href="https://pkg.go.dev/builtin#len" rel="nofollow" target="_blank">мануал</a>:</p>
<blockquote class="tr_bq">
The len built-in function returns the length of v, according to its type:<br />
<code>String: the number of bytes in v.</code>
</blockquote>
<p>Следовательно, валидация на основании длины строки в байтах нас не устраивает, так как в случае многобайтных кодировок (коей является <b>utf-8</b>) число символов в строке <b>не равно</b> числу байт в этой же строке.</p>
<p>Хотелось бы, чтобы был метод типа <code>utfLen()</code>. И он есть в пакете <a href="https://pkg.go.dev/unicode/utf8" rel="nofollow" target="_blank"><code>unicode/utf8</code></a>, правда, с немного другим названием - <a href="https://pkg.go.dev/unicode/utf8#RuneCountInString" rel="nofollow" target="_blank"><code>RuneCountInString</code></a>. И о чудо, данный метод даже <a href="https://github.com/go-ozzo/ozzo-validation/blob/master/length.go#L64" rel="nofollow" target="_blank">используется</a> в валидаторе, если установить дополнительный флаг <code>rune</code>. И это можно сделать применив валидатор <code>RuneLength</code>.</p>
<p>Обновленный код:<br /><br />
<code class="prettyprint">
data := "Некоторая строка"<br />
err := validation.Validate(data,<br />
validation.Required,<br />
validation.RuneLength(10, 16),<br />
)<br />
fmt.Println(err)
</code>
</p>
<p>Валидация отрабатывает как и ожидается, выводя <code><nil></code>.</p>
<p>И не забудьте написать для этого случая <b>тест</b> (ну или хотя бы комментарий), чтобы пытливый программист, использующий код после вас, не смог бы заменить <code>RuneLength</code> на <code>Length</code>, думая, что "и так сойдёт".</p>
<p><b>P.S.</b> Больше разъяснений про длины строк можно почитать в <a href="https://stackoverflow.com/a/12668840/1553888" rel="nofollow" target="_blank">этом ответе</a> на stackoverflow.</p><p><b>P.P.S</b>. Отличное <a href="https://youtu.be/4MFcmreAUhs" rel="nofollow" target="_blank">видео на Youtube</a>, раскрывающее суть кодировок. <br /></p>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-32294854076487112262021-08-04T10:45:00.005+03:002021-08-09T23:25:52.913+03:00Интерпретация переменных в строке<p>Сегодня еще один пост о всяких веселых способах написать код в php.</p>
<p>Всем известно (а если не известно, то читайте <a href="https://www.php.net/manual/ru/language.types.string.php#language.types.string.parsing" rel="nofollow" target="_blank">мануал</a>), что в строках в двойных кавычках переменные и некоторые другие выражения обрабатываются и вместо этой переменной\выражения выводится результат. Простейший пример:<br /><br />
<code class="prettyprint">
$number = 42;<br />
echo "Number is $number";<br />
// outputs "Number is 42"</code>
</p>
<p>Теперь зададимся вопросом - а можно ли как-то интерпретировать в строках какие-либо выражения? Например, сложение двух чисел. Естественно, написание в лоб приводит к ошибкам:<br /><br />
<code class="prettyprint">
$a = 42;<br />
$b = 24;<br />
echo "Sum is {$a + $b}";
</code>
</p>
<p>Что же делать? Понятно, что можно завести класс, объявить в нем метод суммирования, но это как-то много кода:<br /><br />
<code class="prettyprint">
class Summator {<br />
public function sum($a, $b) {<br />
return $a + $b;<br />
}<br />
}<br /><br />
$a = 42;<br />
$b = 24;<br />
$o = new Summator();<br />
echo "Sum is {$o->sum($a, $b)}";<br />
// outputs "Sum is 66"
</code>
</p>
<p>Подумав еще немного можно прийти к следующему - создадим переменную, которая будет хранить анонимную функцию, и в строке вызовем эту переменную:<br /><br />
<code class="prettyprint">
$a = 42;<br />
$b = 24;<br />
$sum = function($a, $b) { return $a + $b; };<br />
echo "Sum is {$sum($a, $b)}";<br />
// outputs "Sum is 66"
</code>
</p>
<p>А с учетом стрелочных функций (с версии <b>7.4</b>), которые имеют доступ к переменным родительской области видимости, код упрощается до такого:<br /><br />
<code class="prettyprint">
$a = 42;<br />
$b = 24;<br />
$sum = fn() => $a + $b;<br />
echo "Sum is {$sum()}";<br />
// outputs "Sum is 66"
</code>
</p>
<p>Ну и наконец самая безумная версия:<br /><br />
<code class="prettyprint">
$a = 42;<br />
$b = 24;<br />
echo "Sum is ${0 * ${0}=$a+$b}";<br />
// outputs "Sum is 66"
</code>
</p>
<p>Что же здесь происходит?<br /><br />Здесь мы пытаемся вывести переменную с названием, которое получается как результат умножения <code>0</code> на переменную <code>${0}</code>. А значение <code>${0}</code> является суммой значений <code>$a</code> и <code>$b</code>. Почему php выполняет присваивание значения переменной при парсинге строки - <b>не знаю</b>. Но тем не менее, мы получаем, что переменной <code>${0}</code> присваивается <code>66</code>. А так как результатом присваивания является присвоенное значение, то <code>0 * ${0}=$a+$b</code> превращается в <code>0 * 66</code>, и выражение <code>${0 * ${0}=$a+$b}</code> сворачивается в <code>${0}</code>. То есть мы хотим вывести переменную с названием <code>0</code>, которую мы уже определили ранее, как сумму <code>$a</code> и <code>$b</code>. Вот и получаем в выводе <code>66</code>.</p>
<p>Спасибо за внимание и <b>никогда не пишите</b> такой код)</p>Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-23447246380955106902021-06-18T11:00:00.000+03:002021-06-18T12:39:42.545+03:00Ликбез по подсчету количества частей sms<p>Еще один специфический пост - на сей раз расскажу вам как правильно подсчитывать количество sms-ок, необходимых для отправки того или иного текста.</p>
<p>Вводные данные: текст, который вы хотите отправить с помощью sms-сервиса, может быть достаточно большим, например, содержать 150-200 символов. Сотовый оператор (или другой sms-провайдер) не может отправить такой длинный текст как одну sms-ку, и потому разбивает ее на несколько <b>частей</b>. В зависимости от кодировки символов в тексте максимальная длина части разная. Отправка сообщения частями не значит, что получатель получит 2 разные sms-ки. В телефоне получателя части соединятся в одну sms-ку. Поэтому для получателя сообщение любой длины будет выглядеть как одна sms-ка.</p>
<p>А вот отправителю сложнее. Если вы используете какое-то стороннее API для отправки sms, то будьте уверены, что длинное сообщение будет отправлено частями. Таким образом, если вы снимаете со своего пользователя сколько-то денег за отправку сообщения, но при этом не учитываете факт отправки по частям, то баланс может не сойтись. Кстати, интересный факт - не все sms-провайдеры держат информацию о правилах деления на части в более-менее открытом доступе. У кого-то придется пошерстить мануал или FAQ, кому-то придется задать вопрос в саппорт. (Из личного опыта интеграции как минимум с 7-8 провайдерами, один из которых - заграничный).</p>
<p>В случае если в сообщении есть хотя бы один символ кириллицы - то максимальная длина одного сообщения составит <b>70</b> символов. Точнее даже не символов, а позиций (но об этом <a href="#symbols-extended">позже</a>). То есть 70 и меньше символов будут отправлены как одна sms-ка. Если символов становится больше 70, то провайдер начнет разбивать сообщение на части. Обычно длина каждой части - <b>67</b> символов. <b>70</b> и <b>67</b> это общепринятые значения на данный момент, но лучше уточнить эти лимиты в документации оператора\провайдера.</p>
<p>То есть, если ваше сообщение состоит из <code>90</code> символов, то провайдер отправит его как две части: первая часть длиной <code>67</code> символов, вторая - <code>23</code> символа. Сообщение длиной <code>71</code> символ не удастся отправить как одно, тут тоже две части - <code>67</code> + <code>4</code>. Так что либо укладывайтесь в 70, либо пишите еще больше текста, все равно за вторую часть придется платить</p>
<p>Переходим к сообщениям, написанным без использования кириллицы. Здесь лимит длины сообщения составляет уже <b>160</b> символов на одну смс и <b>153</b> символа на часть (хотя я встречал одного провайдера со <b>157</b> символами на часть). Расчет частей аналогичен: сообщение из <code>190</code> символов будет состоять из двух частей: <code>153</code> и <code>37</code> символов каждая.</p>
<p>Следующий важный момент: как определить какую из пар (<b>70</b>/<b>67</b> или <b>160</b>/<b>153</b>) использовать для расчета числа частей? Понятно, что если в тексте сообщения есть хотя бы один кириллический символ, то выбор очевиден - <b>70</b>/<b>67</b>. В остальных случаях используем <b>160</b>/<b>153</b>. Однако, копнем дальше - что делать, если у вас какой-нибудь многоязычный сервис и хочется корректно рассчитывать число частей для разных языков, даже с учетом национальных особенностей? Здесь на помощь приходит тот факт, что базовой кодировкой сообщений, отправляемых через sms, является <a href="https://en.wikipedia.org/wiki/GSM_03.38" target="_blank" rel="nofollow">GSM 03.38</a>. Таблица символов данной кодировки содержит помимо некоторых ASCII-символов (цифры, латинский алфавит и некоторые знаки препинания) также символы, которые не относятся к ASCII. Пример: испанская <code>ñ</code> или немецкое <code>ß</code> хоть и не входят в ASCII-символы, но в смске займут один символ в отличие от кириллических символов.</p>
<p id="symbols-extended">Приглядевшись к таблице символов кодировки GSM 03.38 можно заметить, что в ней есть некоторые символы, которые занимают <b>две позиции</b> в смске. К таким символам относится, например, знак евро <code>€</code>. Это значит, что если в тексте сообщения есть 69 кириллических символов и семидесятым символом идет знак евро, то формально в сообщении 71 символ, так как знак евро занимает две позиции. Тут тоже надо быть внимательным, чтобы правильно вычислять число частей и тарифицировать отправку.</p>
<p>Как следует из всего вышесказанного, для расчета числа частей надо написать собственный, не самый сложный код. Однако, можно использовать уже имеющиеся библиотеки. Для <b>php</b> можно использовать, например, мной же и написанную библиотеку <a href="https://github.com/u-mulder/sms-charset-detector" target="_blank" rel="nofollow">sms-charset-detector</a>, которая определяет кодировку сообщения. В зависимости от результата, вы можете выбрать какую пару чисел использовать. Для <b>javascript</b> можно использовать, например, <a href="https://github.com/matteogatti/sms-counter/" target="_blank" rel="nofollow">sms-counter</a>, здесь можно сразу увидеть число частей, на которое будет разбито сообшение.</p>
<p>Итак, в данном посте вы познакомились с ограничениями на отправку sms-сообщений и узнали основные факты, необходимые для корректного расчета количества частей sms-сообщения.</p>Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-25386180977079126862021-05-03T11:00:00.001+03:002021-05-03T11:00:00.201+03:00Приведение переменной к массиву<p>Сегодня краткий пост о том, как привести переменную какого-то типа к типу массив (<b>array</b>).</p>
<p>Итак, первый вариант, сразу приходящий в голову:<br /><br />
<code class="prettyprint">
$var = 42;<br />
$varArray = [$var];
</code>
</p>
<p>Он хорош до тех пор, пока <code>$var</code> не является массивом. В случае с массивом мы получаем двумерный массив. Естественно, это нас не устраивает. Поэтому улучшаем конструкцию:<br /><br />
<code class="prettyprint">
$var = 42;<br />
if (!is_array($var)) {<br />
$var = [$var];<br />
}
</code>
</p>
<p>Но тут многовато строк, можно ли попроще?</p>
<p>Вспоминаем, зачем мы здесь реально собрались, и используем <a href="https://www.php.net/manual/ru/language.types.type-juggling.php" rel="nofollow" target="_blank">приведение типов</a>:<br /><br />
<code class="prettyprint">
$var = 42;<br />
$varArray = (array) $var;<br />
print_r($varArray);<br />
// Вывод: Array<br />
// (<br />
// [0] => 42<br />
// )<br /><br />
$var = [42, 42];<br />
$varArray = (array) $var;<br />
print_r($varArray);<br />
// Вывод: Array<br />
// (<br />
// [0] => 42<br />
// [1] => 42<br />
// )
</code>
</p>
<p>На этом всё.</p>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-36770429225779989042021-03-11T17:27:00.144+03:002021-03-11T17:27:01.281+03:00Doctrine: ошибка парсинга запроса<p>На самом деле пост довольно специфический. Начну с описания проблемы, встреченной мной уже минимум два раза. Имеем обычный с виду код выполнения запроса:</p>
<code class="prettyprint">
$conn = $this->getEntityManager()->getConnection();<br />
$conn->executeQuery($query, [<br />
'param1' => 42,<br />
'param2' => 'forty-two',<br />
'param3' => '%some_other_data%',<br />
]);
</code>
<p>При выполнении этого кода ловим ошибку типа такой:</p>
<blockquote>
<code class="prettyprint">SQLSTATE[08P01]: <<Unknown error>>: 7 ERROR: bind message supplies 0 parameters, but prepared statement "pdo_stmt_000...." requires 3</code>
</blockquote>
<p>Казалось бы, что может пойти не так? Мы взяли запрос с <b>именованными параметрами</b>, взяли данные для подстановки и передали всё это в Доктрину. Однако, практика показывает, что при неком специфичном тексте запроса Доктрина не справляется с парсингом текста запроса и считает, что в данном запросе нет именованных параметров. Исходя из моего опыта, такое случалось на запросах с огромным количеством <code>:</code>, так как именно <b>двоеточие</b> определяет начало именованного параметра.</p>
<p>В качестве запроса, ломающего Доктрину можно привести такой (использую postgresql, текст запроса очень приблизительный): </p>
<code class="prettyprint">
SELECT<br /> id,<br />
value::FLOAT,<br />
exec_date<br />
FROM (<br /> VALUES<br />
(<br />
8095,<br />
41,<br />
'2020-01-02 10:48:17'<br />
),<br />
--- Таких values тут сотни две<br />
) as vs (id, value, exec_date)<br />
JOIN<br />
some_table t ON vs.id = t.foreign_id::INT<br />
WHERE<br />
t.created_at::TIMESTAMP + interval '3600 second' <= vs.executed_at::TIMESTAMP<br />
AND t.field_one = :param1<br />
AND t.field_two = :param2<br />
--- и еще что-нибудь</code>
<p>Найденное мной быстрое решение - это поменять именованные параметры (<code>:param1</code>) на позиционные (<code>?</code>), с такими данными Доктрина успешно справляется. Если есть желание - можете самостоятельно нырнуть в <a href="https://github.com/doctrine/dbal/blob/2.12.x/lib/Doctrine/DBAL/SQLParserUtils.php" rel="nofollow" target="_blank">дебри</a> и поковыряться в <a href="https://github.com/doctrine/dbal/blob/2.12.x/lib/Doctrine/DBAL/SQLParserUtils.php#L90" rel="nofollow" target="_blank">регулярках</a>, а возможно где-то уже висит issue (но это не точно), или в <b>третьей</b> версии Доктрины это вообще уже исправлено.<br /></p>Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-11722072276879250162021-02-17T11:00:00.001+03:002021-02-19T22:26:45.432+03:00Интеграция php-кода и html-верстки<p>Сегодня у нас урок для начинающих. Допустим, вы - начинающий программист, или верстальщик, или вообще пытались мимо проходить, но жизнь заставила соединить верстку (она же html-разметка) и php-код. Как это сделать с минимальными усилиями?</p>
<p>Итак, допустим у вас есть верстка для списка новостей:</p>
<code class="prettyprint">
<div><br />
<h1>Это заголовок списка новостей</h1><br />
<div class="some-class"><br />
<h2>Заголовок новости 1</h2><br />
<p>Небольшой блок текста для ознакомления</p><br />
<a href="/detail.php?id=1">Читать текст целиком</a><br />
<span class="author">Автор новости: И. Иванов</span><br />
</div><br />
<div class="some-class"><br />
<h2>Заголовок новости 2</h2><br />
<p>Небольшой блок текста для ознакомления</p><br />
<a href="/detail.php?id=2">Читать текст целиком</a><br />
<span class="author">Автор новости: И. Иванов</span><br />
</div><br />
<div class="some-class"><br />
<h2>Заголовок новости 3</h2><br />
<p>Небольшой блок текста для ознакомления</p><br />
<a href="/detail.php?id=3">Читать текст целиком</a><br />
<span class="author">Автор новости: И. Иванов</span><br />
</div><br />
</div>
</code>
<p>И есть массив новостей <code>$newsArray</code>, полученный из БД/API/откуда-то ещё. Мы не будем рассматривать, как вы получили этот массив, будем считать, что он у вас есть.</p>
<p>Для лучшего понимания, что и как нужно сделать, разобьем нашу задачу на несколько шагов.</p>
<a name='more'></a>
<ul style="text-align: left;">
<li>Шаг первый - обойдем массив (проитерируемся по массиву) и выведем некий блок-заглушку на каждой итерации. <a href="#s1">Перейти</a></li>
<li>Шаг второй - заменим блок-заглушку на статические данные из разметки выше. <a href="#s2">Перейти</a></li>
<li>Шаг третий - заменим статические данные на реальные данные из массива. <a href="#s3">Перейти</a></li>
</ul>
<p><a name="s1"></a>Итак, поехали - <b>обходим массив поэлементно</b>.</p>
<p><b>Первое правило</b> - в <b>большинстве случаев</b> любой массив можно обойти с помощью <code>foreach</code>.</p>
<p>Да, в php есть цикл <code>for</code> и другие варианты обхода массива, но для простого обхода любых массивов используйте <code>foreach</code>.</p>
<p>На каждой итерации выведите что вам угодно, пусть даже пресловутый <b>Hello, world!</b>. Главное на этом шаге - убедиться, что массив обойден и выводимая строка повторена столько раз, сколько элементов в массиве <code>$newsArray</code>.</p>
<p>На этом шаге получаем такой код:<br /><br />
<code class="prettyprint">
foreach ($newsArray as $news) {<br />
echo 'Hello, world! <br />';<br />
}
</code><br /><br />
Заглядываем в полученный html, считаем число "хеллоуворлдов" и убеждаемся, что все в порядке.</p>
<p><a name="s2"></a>Затем переходим к шагу два - <b>выводим заглушку-разметку</b>. В качестве разметки возьмем разметку одного блока <code class="prettyprint"><div class="some-class"></code>, и заменим ею вывод <b>"Hello world"</b>.</p>
<p>Получим такой код:<br /><br />
<code class="prettyprint">
foreach ($newsArray as $news) {?><br />
<div class="some-class"><br />
<h2>Заголовок новости 1</h2><br />
<p>Небольшой блок текста для ознакомления</p><br />
<a href="/detail.php?id=1">Читать текст целиком</a><br />
<span class="author">Автор новости: И. Иванов</span><br />
</div><br />
<?php<br />
}
</code></p>
<p><b>Правило номер два</b> - для вывода разметки закрывайте php-тег и выводите разметку как есть.</p>
<p>Другой вариант вывода может выглядеть так:<br /><br />
<code class="prettyprint">
foreach ($newsArray as $news) {<br />
echo '<div class="some-class">';<br />
echo ' <h2>Заголовок новости 1</h2>';<br />
echo ' <p>Небольшой блок текста для ознакомления</p>';<br />
echo ' <a href="/detail.php?id=1">Читать текст целиком</a>';<br />
echo ' <span class="author">Автор новости: И. Иванов</span>';<br />
echo '</div>';<br />
}
</code></p>
<p>Нельзя сказать, что он неправилен, ведь он также работает. Но, <b>во-первых</b> - этот код читается хуже, чем первый вариант. А <b>во-вторых</b> - когда мы начнем выводить вместо шаблонного текста какие-то данные из массива, мы замучаемся с конкатенацией строк и слежением за кавычками. Поэтому: выключаем режим php и выводим html как есть, производительность от этого не пострадает.</p>
<p>Также на этом шаге мы сделаем следующее - выведем статические данные с помощью конструкции <code>echo</code>. Обычно вывод можно записать так <code class="prettyprint"><?php echo 'String here'; ?></code>, но мы воспользуемся сокращенной записью <code><?='String here'?></code>, это здорово сократит и сам код и время его написания.</p>
<p>В итоге на этом шаге получаем такой код:<br /><br />
<code class="prettyprint">
foreach ($newsArray as $news) {?><br />
<div class="some-class"><br />
<h2><?='Заголовок новости 1'?></h2><br />
<p><?='Небольшой блок текста для ознакомления'?></p><br />
<a href="<?='/detail.php?id=1'?>">Читать текст целиком</a><br />
<span class="author">Автор новости: <?='И. Иванов'?></span><br />
</div><br />
<?php<br />
}
</code></p>
<p>Снова смотрим на результат работы скрипта и переходим к последнему шагу.</p>
<p><a name="s3"></a>Осталась самая малость - <b>заменить статические данные на реальные данные</b> из массива <code>$newsArray</code>. Здесь все совсем просто - в каждое <code><?=</code> просто подставляем обращение к нужному ключу текущего массива <code>$news</code>:<br /><br />
<code class="prettyprint">
foreach ($newsArray as $news) {?><br />
<div class="some-class"><br />
<h2><?=$news['title']?></h2><br />
<p><?=$news['preview']?></p><br />
<a href="<?=$news['detail_url']?>">Читать текст целиком</a><br />
<span class="author">Автор новости: <?=$news['author']?></span><br />
</div><br /><?php<br />
}
</code></p>
<p>Обновляем страницу и смотрим, что получилось. Если все в порядке, то работа успешно закончена - вы соединили верстку и html-код. Следующий шаг в развитии - использование <b>шаблонизаторов</b>. Но это тема для другого поста.</p>
<p>Финальный код в виде <a href="https://gist.github.com/u-mulder/5c3762c40ce404a13ecc09b5a8b798ce" rel="nofollow" target="_blank">гиста</a>, там же пример с обсуждаемыми выше вариантами вывода данных: с закрытием php-тега и без этого.</p>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-85838545695013628732020-12-30T23:25:00.000+03:002020-12-30T23:25:30.826+03:00Порционная обработка записей в цикле<p>Прямо вот совсем недавно на стеке возник вопрос - как порционно загружать записи из CSV в БД? Вопрос-то простой, но дополнительным условием было - после окончания цикла <code>while</code> нельзя делать дополнительный запрос на вставку.</p>
<p>Простое и понятное решение:<br /><br />
<code class="prettyprint">
$handle = fopen(/* ... */);<br />
$batch = [];<br />
while (($data = fgetcsv($handle, 4096, ';')) !== false) {<br />
$batch[] = processData($data);<br /><br />
if(100 === \count($batch)) {<br />
runBatchInsert($batch);<br /><br />
$batch = [];<br />
}<br />
}<br />
fclose($handle);<br />
if ($batch) {<br />
runBatchInsert($batch);<br />
}
</code>
</p><p>Как мы видим - здесь мы собираем записи в массив <code>$batch</code>, и как только в этом массиве будет 100 элементов - выполняем запрос на вставку в функции <code>runBatchInsert()</code>. Понятно, что после завершения цикла в <code>$batch</code> могут находиться данные, которые также надо вставить, что мы и делаем вторым вызовом функции <code>runBatchInsert()</code>.</p>
<p>Однако, по условию задачи мы не должны использовать второй вызов <code>runBatchInsert()</code>. Это сделать вполне возможно, однако читабельность кода немного ухудшится, поэтому придется добавить немного комментариев, чтобы через месяц не забыть какого лешего это написано именно так, а не как у всех. В результате получаем такой вот код:<br /><br />
<code class="prettyprint">
$handle = fopen(/* ... */);<br />
$batch = [];<br />
// запускаем "вечный" цикл<br />
while (true) {<br />
$data = fgetcsv($handle, 4096, ';');<br />
// если данные есть - добавляем их в $batch<br />
if (false !== $data) {<br />
$batch[] = processData($data);<br />
}<br /><br />
// если размер батча достиг лимита или мы больше не получили данных<br />
if(100 === \count($batch) || false === $data) {<br />
// в батче есть данные - вставляем<br />
if ($batch) {<br />
runBatchInsert($batch);<br />
}<br /><br />
// данных больше нет - прерываем цикл<br />
if (false === $data) {<br />
break;
<br />
}<br /><br />
// очищаем батч<br />
$batch = [];<br />
}<br />
}<br />
fclose($handle);
</code></p>
<p>Вы сами вольны выбирать какой из подходов использовать, но лично я использую первый.</p>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-31262400727425432852020-11-23T23:26:00.001+03:002020-12-06T16:34:12.724+03:00Загрузка файла при обновлении записи<p>Иногда на стеке встречается вопрос от новичков - как заменить файл, принадлежащий некоторой сущности. Допустим, в программе есть сущность Книга, и у нее есть файл обложки. Когда книга редактируется - файл обложки может быть изменен.</p>
<p>Многие начинающие разработчики будут полагать, что путь к текущему файлу обложки надо выводить на форму редактирования, затем, аналогично полям <code class="prettyprint"><input type="text" /></code>, добавлять его значение в запрос\метод\функцию обновления. По их мнению получается, что если файл не был обновлен, то в БД запишется то же самое значение пути к файлу, что есть в БД сейчас. Выглядит это в их голове как-то так:<br /><br />
<code class="prettyprint">
if (isset($_POST['update-btn'])) {<br />
$name = $_POST['name'];<br /><br />
// вроде как тут должен быть путь к файлу,<br />
// но <input type="file" /> работает не так<br />
$file = $_POST['file'];<br /><br />
$query = "UPDATE tbl_book SET name = '$name', filepath = '$file' WHERE id = 42";<br />
$dbh->execute($query);<br />}</code>
</p>
<p>Как следствие, у них возникает вопрос - как вывести текущий путь к файлу в <code class="prettyprint"><input type="file" /></code>? Ответ прост - <b>никак</b>. Нужно понять две вещи: первое - никакое значение от сервера в <code class="prettyprint"><input type="file" /></code> предустановить нельзя. Второе - работать с заменой файла следует иначе, чем с заменой текстовых значений.</p>
<p>Поэтому правильный алгоритм обновления файла выглядит так:</p>
<p><b>1.</b> На форме обновления рядом с полем загрузки файла (<code class="prettyprint"><input type="file" /></code>) выводим текущий файл, например: <code class="prettyprint">Текущая обложка <img src="path/to/file.jpg" /></code>. Так как в общем случае никому не интересно, как файл назван в системе, то такого вывода достаточно: пользователь видит содержимое файла (собственно картинку) и может решить - следует ли загрузить новый файл. Если же действительно надо показать текущий путь к файлу, то выводим, например, <code class="prettyprint">Текущий файл расположен по пути "path/to/file.jpg"</code>. В обоих случаях <code class="prettyprint">path/to/file.jpg</code> - это путь к файлу, хранящийся в БД.</p>
<p>В поле загрузки файла пользователь может загрузить новый файл, а может не загрузить. Поэтому дальше идут два варианта развития событий.</p>
<p><b>2.1.</b> Пользователь <b>не загрузил</b> файл. Значит, в запросе\методе\функции обновления записи не требуется указывать поле с файлом.</p>
<p><b>2.2.</b> Пользователь <b>загрузил</b> новый файл. Значит, надо этот файл сохранить в каком-то каталоге и сформировать путь к нему. Далее, в запросе\методе\функции обновления записи указать путь к новому файлу, и после успешного обновления записи удалить с диска старый файл. Старый файл можно и не удалять, если на диске достаточно места.</p>
<p><b>Схематично</b> код выглядит так, его аналоги можно реализовать в любом фреймворке: <br /><br />
<code class="prettyprint">
if (isset($_POST['update-btn'])) {<br />
$name = $_POST['name'];<br /><br />
if ($_FILES['file']) {<br />
move_uploaded_file();<br />
$file = 'path/to/file.jpg';<br /><br />
// опционально, за этим значением нужно сходить в БД<br />
$oldFile = 'path/to/old_file.jpg';<br />
}<br />
<br />
if (isset($file)) {<br />
$query = "UPDATE tbl_book SET name = '$name', filepath = '$file' WHERE id = 42";<br />
} else {<br />
$query = "UPDATE tbl_book SET name = '$name' WHERE id = 42";<br />
}<br />
<br />
$dbh->execute($query);<br />
<br />
// опционально<br />
unlink($oldFile);<br />
}</code></p>
<p>Это всё, что требуется сделать в случае замены файла в сущности. Никаких костылей изобретать не требуется.</p>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0Петровский пр-д, Липецк, Липецкая обл., Россия, 39805952.605082 39.597074652.603778866582189 39.594928832788085 52.606385133417817 39.599220367211913tag:blogger.com,1999:blog-8891714387125133857.post-76442596056182693542020-10-26T18:50:00.000+03:002020-10-26T18:50:44.382+03:00Short circuit evaluation в php<p>Сегодня поближе познакомимся с short circuit evaluation: выясним что это за зверь такой, посмотрим примеры и выясним, как он нам может помочь.</p>
<p>Для начала немного теории. <b>Short circuit evaluation</b> (не могу предложить простого русского перевода) - это стратегия в языках программирования, которая используется, чтобы избежать ненужных вычислений. <br /> Лучше всего это понять на примере булевых выражений. Допустим, мы проверяем условие вида <code>if (checkSomething() && checkSomethingElse())</code>. Если <code>checkSomething()</code> вернет <code>false</code>, то <code>true</code> в итоге уже никак не получить, следовательно, вычислять второе значение в <code>checkSomethingElse()</code> не имеет смысла - любое вычисленное значение никак не повлияет на итоговый результат.</p>
<a name='more'></a>
<p>Теперь примеры - short circuit evaluation в действии. Определим четыре функции, две из них возвращают <code>true</code>, две других - <code>false</code>. Для проверки поместим в тела этих функций вывод сообщений:<br /><br />
<code class="prettyprint">
function returnTrue(): bool<br />
{<br />
echo 'Вызван метод ' . __METHOD__ . PHP_EOL;<br />
<br />
return true;<br />
}<br /><br />
function returnTrueToo(): bool<br />
{<br />
echo 'Вызван метод ' . __METHOD__ . PHP_EOL;<br />
<br /> return true;<br />
}<br /><br />
function returnFalse(): bool<br />
{<br />
echo 'Вызван метод ' . __METHOD__ . PHP_EOL;<br />
<br />
return false;<br />
}<br /><br />
function returnFalseToo(): bool<br />
{<br />
echo 'Вызван метод ' . __METHOD__ . PHP_EOL;<br />
<br />
return false;<br />
}
</code></p>
<p>Проверяем:<br /><br />
<code class="prettyprint">
if (returnFalse() && returnTrue()) {<br />
echo 'Эта строка не выведется';<br />
} else {<br />
echo 'Эта строка выведется и это правильно';<br />
}<br />
// Вывод<br />
Вызван метод returnFalse<br />
Эта строка выведется и это правильно
</code></p>
<p>Как видим - short circuit evaluation действительно работает: так как <code>&&</code> вернет <code>true</code> <b>только</b> в случае если <b>оба аргумента</b> равны <code>true</code>, а первый вычисленный аргумент не равен <code>true</code>, то вычислять второй не имеет смысла, итоговое выражение от этого не станет <code>true</code>. Поэтому видим, что выполнилась только первая функция <code>returnFalse</code>.</p>
<p>Пример посложнее:<br /><br />
<code class="prettyprint">
if ((returnTrue() || returnFalse()) && (returnFalseToo() && returnTrueToo())) {<br />
echo 'Эта строка не выведется';<br />
} else {<br />
echo 'Эта строка выведется и это правильно';<br />
}<br />
// Вывод<br />
Вызван метод returnTrue<br />
Вызван метод returnFalseToo<br />
Эта строка выведется и это правильно
</code>
</p>
<p>Третья проверка показывает, что если все условия могут повлиять на результат - то все они и проверятся:<br /><br />
<code class="prettyprint">
if (returnFalse() || returnFalseToo() || returnTrueToo()) {<br />
echo 'Эта строка выведется и это правильно';<br />
} else {<br />
echo 'Эта строка не выведется';<br />
}<br />
// Вывод<br />
Вызван метод returnFalse<br />
Вызван метод returnFalseToo<br />
Вызван метод returnTrueToo<br />
Эта строка выведется и это правильно
</code>
</p>
<p>Итак, short circuit evaluation действительно работает и не вызывает функции, если их выполнение не повлияет на результат вычисления. И стоит это учитывать в ваших условиях. Например, рассмотрим такой гипотетический случай:<br /><br />
<code class="prettyprint">
if (returnsTrue() || checkSomethingFromApi()) {<br />
// more code<br />
}
</code>
</p>
<p>Здесь <code>checkSomethingFromApi</code> некая функция, которая достает результат из апи, что-либо проверяет в нем и возвращает какой-то результат. Если вы будете думать, что <code>checkSomethingFromApi</code> будет вызываться всегда, то увы. Так как первой части достаточно для всего результата, то функция <code>checkSomethingFromApi</code> не вызовется <b>никогда</b>. И если <code>checkSomethingFromApi</code> дополнительно делает какое-то еще, явно не обозначенное действие (например, пишет в БД или в кеш), то это действие не выполнится, и в БД не запишется ничего. Следовательно, вам гарантированы часы дебага и разочарование, что "ларчик просто открывался". Инспекции PHPStorm, кстати, могут обратить ваше внимание на такое поведение.</p>
<p>Теперь перейдем к другому моменту: что если мы объединим некоторые логические условия в скобки и возьмем от них отрицание. Будет ли применён short circuit evaluation или будут выполнены все вычисления в скобках, и только потом инвертируется результат? Смотрим в пример:<br /><br />
<code class="prettyprint">
if (!(returnTrue() || returnFalse()) || !(returnFalse() && returnFalseToo())) {<br />
echo 'Эта строка выведется и это правильно';<br />
} else {<br />
echo 'Эта строка не выведется';<br />
}<br />
// Вывод<br />
Вызван метод returnTrue<br />
Вызван метод returnFalse<br />
Эта строка выведется и это правильно
</code>
</p>
<p>Как видим, и тут срабатывает short circuit evaluation. В <b>первом</b> условии <code>returnTrue()</code> однозначно определяет результат всего условия как <code>true</code>, отрицание дает <code>false</code>, значит надо перейти к вычислению <b>второго</b> условия. Во втором условии <code>returnFalse()</code> также достаточно для определения результата, и отрицание <code>false</code> дает <code>true</code>. Как видим - вместо предполагаемых четырех функций выполнились всего лишь две.</p>
<p>Итак, мы разобрались что такое short circuit evaluation, посмотрели примеры с его использованием и даже выяснили один подводный камень его использования.</p>Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-72439240364661132652020-09-20T20:49:00.000+03:002020-09-20T20:49:18.923+03:00Размеры таблиц и индексов в БД PostgreSQL<p>Сегодня рассмотрим несколько команд для определения размеров различных сущностей в PostgreSQL.</p>
<p>Перед тем как начать - рассмотрим вспомогательную функцию <code>pg_size_pretty()</code>, которая:</p>
<blockquote>Преобразует размер в байтах, представленный в 64-битном целом, в понятный человеку формат с единицами измерения</blockquote>
<p>То есть вместо какого-то огромного числа байтов показывает понятную строчку, например:<br /><br />
<code class="prettyprint">select pg_size_pretty(100250408::bigint);<br />
-- Вывод:<br />
pg_size_pretty<br />
text<br />
--------------<br />
96 MB
</code></p>
<p>Теперь, вооружившись этой функцией, переходим к основным функциям.</p>
<p>Первое и самое нужное - размер таблицы PostgreSQL без учета индексов и прочих деталей. Для этого есть функция <code>pg_relation_size()</code>, принимающая в качестве аргумента название таблицы. Комбинируем ее с <code>pg_size_pretty()</code> и получаем:<br /><br />
<code class="prettyprint">select pg_size_pretty(pg_relation_size('my_table'));<br />
-- Вывод (небольшая тестовая таблица в моей БД):<br />
pg_size_pretty<br />
text<br />
--------------<br />
48 kB
</code></p>
<p>Далее - определим сколько места занимают <b>все</b> индексы для таблицы. Для этого есть функция <code>pg_indexes_size()</code>, также принимающая в качестве аргумента название таблицы:<br /><br />
<code class="prettyprint">select pg_size_pretty(pg_indexes_size('my_table'));<br />
-- Вывод (опять же для некоей тестовой таблицы):<br />
pg_size_pretty<br />
text<br />
--------------<br />
88 kB
</code></p>
<p>Функция <code>pg_total_relation_size()</code> определяет сколько места суммарно занимает таблица, ее индексы и данные TOAST, так что ее результат будет отличаться от суммы результатов двух предыдущих запросов.</p>
<p>Ну и чтобы не складывать на калькуляторе размеры всех таблиц и индексов - размер базы данных PostgreSQL определим с помощью <code>pg_database_size()</code>:<br /><br />
<code class="prettyprint">select pg_size_pretty(pg_indexes_size('my_table'));<br />
-- Вывод (опять же для некоей тестовой базы данных):<br />
pg_size_pretty<br />
text<br />
--------------<br />
8137 kB
</code>
</p>
<p>Больше информации про описанные функции - здесь: <a href="https://postgrespro.ru/docs/postgrespro/12/functions-admin#FUNCTIONS-ADMIN-DBOBJECT" target="_blank" rel="nofollow">https://postgrespro.ru/docs/postgrespro/12/functions-admin#FUNCTIONS-ADMIN-DBOBJECT</a></p>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-44749965661193145642020-06-01T23:49:00.000+03:002020-06-01T23:49:45.182+03:00Неочевидное поведение функций конвертации даты<div>
Продолжаем изучать неочевидное поведение функций php.<br /><br />
Рассмотрим такой код:<br /><br />
<code class="prettyprint">
$var = strtotime(date('d.m.Y', time()));
</code><br /><br />
Кажется, что здесь выполняется лишняя работа. Сначала мы берем таймштамп, возвращаемый функцией <code>time()</code>, и на его основе получаем строку времени определенного формата. Далее мы преобразуем полученную строку времени обратно в таймштамп. И логично, что таймштампы должны совпадать. Но нет.<br /><br />
Неочевидность в том, что строка времени в формате <code>"d.m.Y"</code> преобразуется в таймштамп <b>начала дня</b>, то есть <code>"d.m.Y"</code> аналогичен <code>"d.m.Y 00:00:00"</code>. <br /><br />
Добавляем немного вывода и видим:
<code class="prettyprint"><br /><br />
$ts = time();<br /><br />
$var = strtotime(date('d.m.Y', $ts));<br /><br />
var_dump(date('d.m.Y H:i:s', $ts)); // string(19) "01.06.2020 22:36:09"<br />
var_dump(date('d.m.Y H:i:s', $var)); // string(19) "01.06.2020 00:00:00"<br /><br />
$varDayStart = strtotime(date('d.m.Y 00:00:00', $ts));<br />
var_dump($var === $varDayStart); // bool(true)
</code><br /><br />
Таким образом, данный код можно считать одним из способов получения таймптампа начала текущего дня.</div>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-78624065283276951232020-04-10T22:09:00.000+03:002020-04-10T22:09:14.044+03:00Уникальные или неуникальные значения в массиве<div dir="ltr" style="text-align: left;" trbidi="on">
В качестве разминки сегодня решим следующую задачу - предположим, есть массив, некоторые значения в нем повторяются. Требуется получить массив неуникальных (повторяющихся) значений или массив не повторяющихся (уникальных) значений.<br />
<br />
Сразу к примеру:<br />
<br />
<code class="lang-php prettyprint">
$array = [1, 2, 3, 4, 3, 4, 5, 6, 6];<br /><br />
// Для любого из вариантов задачи нам потребуется знать<br />
// сколько раз в массиве встречается каждое значение<br />
$freqs = array_count_values($array);<br /><br />
// Массив уникальных значений - [1, 2, 5]<br />
$unique = array_keys(<br />
array_filter(<br />
$freqs,<br />
function ($freq) { return 1 === $freq; }<br />
)<br />
);<br /><br />
// Массив неуникальных значений - [3, 4, 6]<br />
$nonunique = array_keys(<br />
array_filter(<br />
$freqs,<br />
function ($freq) { return 1 < $freq; }<br />
)<br />
);<br /><br />
// Фильтрация исходного массива с оставлением только повторяющихся значений -[3, 4, 3, 4, 6, 6]<br />
$allNonunique = array_filter(<br />
$array,<br />
function ($v) use ($freqs) { return 1 < $freqs[$v]; }<br />
);
</code><br /><br />
В общем-то всё, на досуге можете расширить этот код и задать количество появлений в виде переменной, а не фиксированного значения <code>1</code>.</div>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-62186413719523827562020-04-06T18:51:00.001+03:002020-06-01T23:05:07.058+03:00Неявное поведение DateTime::createFromFormat<div dir="ltr" style="text-align: left;" trbidi="on">
Данный пост есть результат недавнего обсуждения в одном из php-каналов некоторого странного (как кажется изначально) поведения функции <code>DateTime::createFromFormat</code>.<br />
<br />
Рассмотрим простейший код, надо отметить, что исследуемое поведение отмечается только <b>31-го числа</b> каждого месяца (ну и плюс 29-30 для февраля):<br />
<br />
<code class="prettyprint">
$months = [1, 2, 3, 4];<br /><br />
foreach ($months as $month) {<br />
$dt = '2019-' . $month;<br />
echo $dt . ': ' . (\DateTime::createFromFormat('Y-m', $dt))->format('Y-m-d') . PHP_EOL;<br />
}</code><br />
<br />
Обратите внимание, что при создании объекта не указывается <b>день</b>. Так как день не указан, то разумно предположить (это же подтверждается в <a href="https://www.php.net/manual/ru/datetime.createfromformat.php#124160" rel="nofollow" target="_blank">комментариях</a>), что php берет в качестве дня <b>текущий день</b> запуска скрипта из системных настроек.<br />
<br />
Таким образом, запуская скрипт, например, 31-го мая, получим такой вывод:<br />
<br />
<code class="prettyprint">
// ожидаемо, в январе есть 31 число<br />
2019-1: 31.01.2019<br />
// в феврале-2019 нет 29 (и 30 и 31) числа, потому 31-му февраля<br />
// соответствует третье марта (а 29-му февраля - первое марта)<br />
2019-2: 03.03.2019<br />
// ожидаемо, в марте есть 31 число<br />
2019-3: 31.03.2019<br />
// в апреле нет 31 числа, и следующим после 30 апреля идет 1 мая<br />
2019-4: 01.05.2019
</code>
<br />
<br />
Как видим, происходит не то, что ожидается, хотя в другие дни - всё работает нормально, и даже имеющиеся тесты будут проходить.
<br />
<br />
Можно, конечно, рассуждать о том, что раз день не указан, то может надо кидать эксепшен при создании объекта, но так как такого поведения нет, то придерживаемся мудрого принципа "<b>Явное лучше, чем неявное</b>" и повнимательней пишем свой код.<br />
<br />
Всем здоровья)</div>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-53862518008615968382020-03-08T13:10:00.000+03:002020-03-08T13:10:57.646+03:00Строковые представления типов данных, часть 3<div dir="ltr" style="text-align: left;" trbidi="on">
В предыдущих частях (<a href="http://www.u-mulder.com/2019/02/php-cast-to-string-part-1.html" rel="nofollow">часть 1</a>, <a href="http://www.u-mulder.com/2020/02/php-cast-to-string-part-2.html" rel="nofollow" target="_blank">часть 2</a>) мы рассмотрели как к строковому типу приводятся скалярные типы данных и <code>null</code>. Сейчас перейдем к таким типам данных как <code>массив</code>, <code>объект</code> и <code>ресурс</code>.<br />
<br />
<b>Массив</b>, сразу смотрим пример:<br />
<br />
<code class="prettyprint">
$var = [1,2,3];<br />
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;<br />
$strEval = "В эту строку я подставляю переменную $var";<br />
echo $var;<br />
echo PHP_EOL;<br />
echo $strConcat;<br />
echo PHP_EOL;<br />
echo $strEval;<br />
// вывод:<br />
Notice: Array to string conversion in file on line ..<br />
Notice: Array to string conversion in file on line ..<br />
Notice: Array to string conversion in file on line ..<br />
Array <br />
Эту строку я конкатенирую с переменной Array <br />
В эту строку я подставляю переменную Array
</code>
<br />
<br />
Итак, строковое представление массива это просто слово <code>Array</code>. И так как это не ожидаемая операция над массивом, то выводится еще и <code>Notice</code>. Таким образом, просто взять и вывести массив на экран <b>не получится</b>. Можно обойти массив с помощью цикла и вывести элементы в нужном формате. Естественно, если элементы тоже массивы - их также требуется обойти циклом. И так далее. В случае если массив одномерный - можно воспользоваться функцией <a href="http://php.net/implode" rel="nofollow" target="_blank"><code>implode</code></a>. Также, если у вас при вставке в БД в поле появляется слово <code>Array</code> - вы поняли, в чем ошибка.<br />
<br />
<b>Объект</b>. С объектами ситуация следующая: не всякий объект можно вывести на экран. Например, возьмем такой класс и попытаемся вывести на экран объект данного класса:<br />
<br />
<code class="prettyprint">
class myStdClass<br />
{<br />
public $intField;<br />
public $strField;<br />
}<br />
<br />
$var = new myStdClass();<br />
$var->intField = 42;<br />
$var->strField = 'Forty two';<br />
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;<br />
$strEval = "В эту строку я подставляю переменную $var";<br />
echo $var;<br />
echo PHP_EOL;<br />
echo $strConcat;<br />
echo PHP_EOL;<br />
echo $strEval;<br />
// вывод:<br />
Recoverable fatal error: Object of class stdClass could not be converted to string in ... on line ...
</code><br />
<br />
Получаем фатальную ошибку, так как php не представляет как привести данный объект к строковому представлению. Но ситуация поправима. Для приведения к объекта к строке требуется определить в классе "магический" метод <code>__toString</code>. Метод, естественно, должен вернуть некую строку, которая и будет строковым представлением объекта.<br />
Модифицируем класс из предыдущего примера:<br />
<br />
<code class="prettyprint">
class myStdClass<br />
{<br />
public $intField;<br />
public $strField;<br /><br />
public function __toString(): string<br />
{<br />
return 'intField: ' . $this->intField . '; strField: ' . $this->strField;<br />
}<br />
}<br />
<br />
$var = new myStdClass();<br />
$var->intField = 42;<br />
$var->strField = 'Forty two';<br />
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;<br />
$strEval = "В эту строку я подставляю переменную $var";<br />
echo $var;<br />
echo PHP_EOL;<br />
echo $strConcat;<br />
echo PHP_EOL;<br />
echo $strEval;<br />
// вывод:<br />
intField: 42; strField: Forty two<br />
Эту строку я конкатенирую с переменной intField: 42; strField: Forty two<br />
В эту строку я подставляю переменную intField: 42; strField: Forty two
</code><br />
<br />
Как видим, теперь наш объект прекрасно приводится к строковому представлению.
<br />
<br />
Я не знаю ни одного случая, когда требуется строковое представление <b>ресурса</b>. Но для полноты картины посмотрим на следующий код:<br />
<br />
<code class="prettyprint">
$var = fopen('/tmp/1.tmp', 'a');<br />
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;<br />
$strEval = "В эту строку я подставляю переменную $var";<br />
echo $var;<br />
echo PHP_EOL;<br />
echo $strConcat;<br />
echo PHP_EOL;<br />
echo $strEval;<br />
// вывод:<br />
Resource id #5<br />
Эту строку я конкатенирую с переменной Resource id #5<br />
В эту строку я подставляю переменную Resource id #5
</code><br />
<br />
Как видим, толку в этом выводе мало, только убедиться что в переменной хранится ресурс. Надеяться на то, что ИД ресурса (в данном выводе - 5) не изменится при следующем запуске, также не стоит.<br />
<br />
На этом я завершаю цикл статей по преобразованию типов данных в строки, всем спасибо за внимание.</div>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-21115190316450638252020-02-23T23:28:00.000+03:002020-02-23T23:31:01.750+03:00Строковые представления типов данных, часть 2<div dir="ltr" style="text-align: left;" trbidi="on">
Продолжим рассматривать php преобразует различные типы данных к строковому виду. Первая часть <a href="http://www.u-mulder.com/2019/02/php-cast-to-string-part-1.html" rel="nofollow" target="_blank">здесь</a>.<br /><br />
Рассмотрим тип данных <code>int</code>. Переменная типа <code>int</code> может быть задана в четырех системах счисления: десятичной, восьмеричной, шестнадцатеричной и двоичной. Независимо от системы счисления, вывод будет преобразован к десятичной системе счисления:<br />
<br />
<code class="prettyprint">
$a = 0;<br />
$b = 42;<br />
$c = 0b101011; // 43 в десятичной<br />
$d = 054; // 44 в десятичной<br />
$e = 0x2D; // 45 в десятичной<br />
$f = -42;<br /><br />
echo $a . PHP_EOL;<br />
echo $b . PHP_EOL;<br />
echo $c . PHP_EOL;<br />
echo $d . PHP_EOL;<br />
echo $e . PHP_EOL;<br />
echo $f . PHP_EOL;<br />
// вывод:<br />
0<br />
42<br />
43<br />
44<br />
45<br />
-42
</code><br />
<br />
Числа, превышающие размер типа <code>int</code> (<code>PHP_INT_MAX</code> / <code>PHP_INT_MIN</code>), автоматически конвертируются в тип <code>float</code> и выводятся соответствующе:<br />
<br />
<code class="prettyprint">
$a = PHP_INT_MAX;<br />
$b = 2 + PHP_INT_MAX;<br />
$c = PHP_INT_MIN;<br />
$d = PHP_INT_MIN -1;<br /><br />
echo $a . PHP_EOL;<br />
echo $b . PHP_EOL;<br />
echo $c . PHP_EOL;<br />
echo $d . PHP_EOL;<br />
// вывод:<br />
9223372036854775807<br />
9.2233720368548E+18<br />
-9223372036854775808<br />
-9.2233720368548E+18
</code><br />
<br />
Для того чтобы вывести <code>int</code> переменную не в десятичной, а другой системе счисления, нужно воспользоваться функциями форматирования, например, <code>sprintf</code>/<code>printf</code>:<br />
<br />
<code class="prettyprint">
$a = 42;<br /><br />
echo sprintf("%o", $a) . PHP_EOL; // восьмеричная<br />
echo sprintf("%x", $a) . PHP_EOL; // шестнадцатиричная, буквы в нижнем регистре<br />
echo sprintf("%X", $a) . PHP_EOL; // шестнадцатиричная, буквы в верхнем регистре<br />
echo sprintf("%b", $a) . PHP_EOL; // двоичная<br />
// вывод:<br />
52<br />
2a<br />
2A<br />
101010
</code><br />
<br />
Переходим к выводу переменных типа <code>float</code>.<br />
<br />
<code class="prettyprint">
$floatNums = [<br />
// Первый способ записи float-чисел<br />
4.2,<br />
4.2222,<br />
4.22222222,<br />
0.2,<br />
0.0002,<br />
0.00002,<br />
0.000002,<br />
// Второй способ записи float-чисел<br />
1.2e4,<br />
// Третий способ записи float-чисел<br />
1.2E4,<br />
5E-3,<br />
5E-4,<br />
5E-5,<br />
// int значения превышающие размер типа int конвертируются во float<br />
PHP_INT_MAX + 20,<br />
];<br /><br />
foreach ($floatNums as $num) {<br />
echo $num . PHP_EOL;<br />
}<br />
// вывод:<br />
4.2<br />
4.2222<br />
4.22222222<br />
0.2<br />
0.0002<br />
2.0E-5 //числа с пятью и более знаками после запятой выводятся в нотации с основанием и мантиссой<br />
2.0E-6<br />
12000<br />
12000<br />
0.005<br />
0.0005<br />
5.0E-5<br />
9.2233720368548E+18
</code><br />
<br />
Как видим - числа выводятся с тем же количеством знаков после запятой, что были указаны при их объявлении. Для указания нужного числа знаков после запятой - пользуйтесь функциями форматирования, например, теми же <code>sprintf</code>/<code>printf</code> или <code>number_format</code>:<br />
<br />
<code class="prettyprint">
$floatNum = 4.23456789;<br />
echo sprintf('%f', $floatNum) . PHP_EOL;<br />
echo sprintf('%.2f', $floatNum) . PHP_EOL;<br />
echo sprintf('%.5f', $floatNum) . PHP_EOL;<br />
echo sprintf('%.12f', $floatNum) . PHP_EOL;<br />
<br />
echo number_format($floatNum) . PHP_EOL;<br />
echo number_format($floatNum, 2) . PHP_EOL;<br />
echo number_format($floatNum, 5) . PHP_EOL;<br />
echo number_format($floatNum, 6) . PHP_EOL;<br />
<br />
// вывод<br />
4.234568 // по умолчанию выводится 6 знаков после запятой<br />
4.23<br />
4.23457 // можно подумать что это округление, но нет, такой вывод связан с представлением числа в памяти программы <br />
4.234567890000 // оставшиеся позиции заменяются нулями<br />
4<br />
4.23<br />
4.23457 // аналогично, это НЕ округление<br />
4.234568
</code><br />
<br />
Оставшиеся типы данных - <code>массив</code>, <code>объект</code> и <code>ресурс</code>, рассмотрим в заключительном посте.</div>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-10623055519107713152019-12-17T19:06:00.000+03:002019-12-17T19:06:18.758+03:00Сборка мусора в php<div dir="ltr" style="text-align: left;" trbidi="on">
Недавно встретился хороший пост для начинающих о сборке мусора (он же <code>garbage collection</code>) в php. Далее я попытаюсь перевести этот пост на русский и добавить немного собственных данных. Ссылка на оригинальный пост - в конце.<br />
<br />
Начнем с того, что так как php - язык интерпретируемый, то вам не нужно заморачиваться управлением памятью - выделением памяти, и что более важно - очисткой памяти. Этим в php занимается специальный механизм, называемый <b>сборкой мусора</b> (или <b>garbage collection</b>, или же <b>gc</b>).<br />
<br />
<b>Сборка мусора</b> работает тремя способами:<br />
<ol style="text-align: left;">
<li>При уходе переменной из области видимости</li>
<li>При подсчете ссылок</li>
<li>При сборе циклических ссылок</li>
</ol>
<a name='more'></a><b>-</b> Как только переменная <i>уходит из области видимости</i> и больше нигде не используется - она автоматически собирается <b>gc</b>. Также с помощью <code>unset</code> можно явно определить, что переменную пора собирать <b>gc</b><code></code>. Пример кода:<br />
<br />
<code class="pretty-print">
function display_var() {<br />
$foo = "bar";<br />
echo $foo;<br />
}<br /><br />
$user = "Mister X";<br />
unset($user);</code><br />
<br />
В данном коде:<br />
<ul style="text-align: left;">
<li>переменная <code>$foo</code> будет автоматически собрана <b>gc</b> сразу после завершения выполнения функции <code>display_var</code></li>
<li>переменная <code>$user</code> будет собрана <b>gc</b>, так как она явно удалена с помощью <code>unset</code></li>
</ul>
<br />
<b>-</b> С <i>подсчетом ссылок</i> разобраться чуть сложнее. Официальная часть <a href="https://www.php.net/manual/ru/features.gc.refcounting-basics.php" rel="nofollow" target="_blank">здесь</a>.<br />
<br />
Кратко - для того чтобы понять, можно ли безопасно собрать переменную с помощью <b>garbage collection</b>, используется <i>механизм подсчета ссылок</i>. Данный механизм заключается в следующем - при создании переменной в php создается не просто переменная, а контейнер типа <code>zval</code>, в котором помимо собственно <b>типа</b> и <b>значения переменной</b>, хранится еще два поля - <code>ref_count</code> и <code>is_ref</code>. Далее, если вы присваиваете другой переменной значение этой переменной, то контейнер не копируется с имеющимися данными, а php просто увеличивает <code>ref_count</code> на единицу, так как контейнер используется уже двумя переменными. И так далее.<br />
<br />
Как только в какой-то момент переменная удаляется (с помощью <code>unset</code> или ухода из зоны видимости), счетчик <code>ref_count</code> в контейнере уменьшается. Как только счетчик дошел до нуля - считается, что контейнер готов к сборке мусора.<br />
<br />
Мониторить состояние контейнера можно при наличии расширения <a href="http://xdebug.org/" rel="nofollow" target="_blank"><b>Xdebug</b></a> с помощью функции <code>xdebug_debug_zval</code>.<br />
<br />
<b>-</b> Сборка мусора при наличии циклических ссылок не так сложна, как предыдущий пункт. В этом случае сборка мусора активируется в тот момент, когда в памяти находится <b>10000</b> объектов с циклическими ссылками, и один из них уходит из области видимости. Значение <b>10000</b> установлено на уровне ядра php и может быть изменено только путем изменения исходного кода и его перекомпиляции. Однако, процесс сборки мусора можно запустить явно, не дожидаясь накопления 10000 объектов, с помощью метода <code>gc_collect_cycles</code>.<br />
<br />
Также, так как сборка мусора при наличии циклических ссылок может потребовать значительное количество ресурсов, то такую сборку можно запретить одним из двумя способов:<br />
<ul style="text-align: left;">
<li>вызвать метод <a href="https://www.php.net/gc_disable" rel="nofollow" target="_blank"><code>gc_disable</code></a></li>
<li>установить значение <code class="parameter">zend.enable_gc</code> в <code>false</code> в файле php.ini (<code>gc_disable</code> делает то же самое)</li>
</ul>
<br />
Также можно отметить, что изменения в php7.3 серьезно улучшили механизм сборки мусора - в оригинальном посте можно увидеть бенчмарки сравнения предыдущих релизов и версии 7.3, плюс появилась полезная функция <a href="https://www.php.net/gc_status" rel="nofollow" target="_blank">gc_status</a>, выводящая данные об использовании <b>gc</b>. А при наличии уже упомянутого <a href="http://xdebug.org/" rel="nofollow" target="_blank"><b>Xdebug</b></a> можно получить еще больше информации с помощью функции <code>xdebug_start_gcstats</code>.
<br />
<br />
Вот все основные моменты, что следует знать и помнить о сборке мусора. Оригинальная статья с некоторыми дополнительными плюшками - <a href="https://tideways.com/profiler/blog/what-is-garbage-collection-in-php-and-how-do-you-make-the-most-of-it" rel="nofollow" target="_blank">тут</a>.<br />
<code></code></div>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-980871938529057962019-12-12T21:11:00.001+03:002019-12-12T21:11:19.255+03:00Or-pattern в glob<div dir="ltr" style="text-align: left;" trbidi="on">
Короткий пост о том, как в аргументе функции <a href="https://www.php.net/glob" rel="nofollow" target="_blank"><code>glob</code></a> использовать шаблон <b>или</b>.<br />
<br />
Предположим, каталог содержит следующие файлы:<br />
<br />
<code class="prettyprint">
- file.txt<br />
- picture<br />
- picture.gif<br />
- picture.jpg<br />
- picture.png<br />
- picture1.jpg
</code><br />
<br />
И требуется получить файлы имеющие в названии только "picture" ("picture1" не подходит) и с расширениями <b>jpg</b>, <b>png</b> или вообще <b>без расширения</b>.<br />
<br />
Естественно, простейшим решением можно считать объединение результатов трех вызовов <code>glob</code>:<br />
<br />
<code class="prettyprint">
print_r(array_merge(<br />
glob('./picture\.jpg'),<br />
glob('./picture\.png'),<br />
glob('./picture')<br />
));
</code><br />
<br />
Но давайте попробуем ограничиться одним вызовом. В этом нам поможет изучение странички мануала функции <code>glob</code>, а точнее - списка параметров. В нем нас интересует второй аргумент <code>flags</code> и его значение <code>GLOB_BRACE</code>:<br /><br />
<code class="prettyprint">
print_r(<br />
glob('./picture{,\.jpg,\.png}', GLOB_BRACE)<br />);
</code><br /><br />
Получаем тот же самый набор файлов, что и в первом примере (сортировка не в счет). На этом всё, читайте почаще мануалы и находите решения попроще. Кстати, в комментах можете поделиться еще более хитрыми шаблонами для <code>glob</code>, если у вас таковые есть.</div>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-76388213358268372062019-11-20T10:51:00.000+03:002019-11-20T10:51:15.575+03:00Удаление всех таблиц в схеме данных postgresql<div dir="ltr" style="text-align: left;" trbidi="on">
Иногда требуется удалить все таблицы в выбранной схеме в базе данных postgresql.<br />
<br />
Вы можете сказать - можно удалить сразу всю схему (или еще убойней - всю базу). Но нет, при удалении схемы или базы удалится все остальное, что напрямую не зависит от таблиц. Поэтому просто удалим только таблицы. Для этого нам пригодится следующий запрос, результатом которого будет список запросов на удаление каждой таблицы:<br />
<br />
<code class="pretty-print">
SELECT 'drop table if exists "' || tablename || '" cascade;' as pg_tbl_drop<br />
FROM pg_tables<br />
WHERE schemaname='public';
</code><br />
<br />
Естественно, вместо <code>schemaname='public'</code> нужно подставить вашу конкретную схему.<br />
<br />
Выполнив все полученные запросы, получаем схему без таблиц. Однако, может возникнуть ситуация, что в схеме остались последовательности (sequences). Получить все запросы на удаление последовательностей можно таким запросом:<br />
<br />
<code class="pretty-print">
SELECT 'drop sequence if exists "' || relname || '" cascade;' as pg_sec_drop<br />
FROM pg_class<br />
WHERE relkind = 'S';
</code><br /><br />
Посмотреть все последовательности (на всякий случай) можно с помощью запроса:<br /><br />
<code class="pretty-print">
SELECT c.relname<br />
FROM pg_class c<br />
WHERE c.relkind = 'S';
</code><br />
<br />
И как всегда - при удалении важных данных не забудьте про <b>бэкап</b>.</div>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-1452093848865275782019-10-20T23:39:00.000+03:002019-10-20T23:39:26.097+03:00Неявные преобразования данных в PHP<div dir="ltr" style="text-align: left;" trbidi="on">
Маленький пост о том, как поиметь проблем на ровном месте из-за <b>неявного</b> преобразования данных из одного типа в другой.<br />
<br />
Допустим, у вас есть такой простенький код:<br />
<br />
<code class="pretty-print">
$a = ['1' => 'v1', '1-2' => 'v2', '1-3' => 'v3'];<br />
$search = '1-2';<br />
foreach ($a as $key => $value) {<br />
if ($key == $search) {<br />
echo 'Key found: ' . $key . PHP_EOL;<br />
}<br />
}</code>
<br />
<br />
Видим вывод:<br />
<br />
<code class="pretty-print">
Key found: 1<br />
Key found: 1-2
</code><br />
<br />
Возникает закономерный вопрос - почему? Мы же ожидали, что выведется только ключ <code>1-2</code>. Откуда же в выводе взялся ключ <code>1</code>?<br />
<br />
Давайте копнем чуть глубже и модифицируем код проверки:<br />
<br />
<code class="pretty-print">
if ($key == $search) {<br />
var_dump($key, $search);<br />
echo PHP_EOL;<br />
}
</code><br />
<br />
Видим вывод:<br />
<br />
<code class="pretty-print">
int(1)<br />
string(3) "1-2"<br /><br />
string(3) "1-2"<br />
string(3) "1-2"
</code><br />
<br />
Что мы видим? Что ключ первого элемента массива вместо типа <b>строка</b> (<code>string</code>) стал типом <b>целое число</b> (<code>integer</code>).<br />
<br />
Это <b>стандартное поведение</b> ключей типа <b>строка</b>, отмеченное в <a href="https://www.php.net/manual/ru/language.types.array.php#language.types.array.syntax.array-func" rel="nofollow" target="_blank">официальном руководстве</a>. Получаем, что на первой итерации цикла мы сравниваем <b>целое число</b> <code>1</code> со <b>строкой</b> <code>1-2</code>. Немного неожиданно.<br />
<br />
Казалось бы - <b>строка</b> <code>1-2</code> уж никак не может быть равна <b>целому числу</b> <code>1</code>. Однако, правила сравнения разных типов (также отраженные в <a href="https://www.php.net/manual/ru/language.operators.comparison.php" rel="nofollow" target="_blank">официальном руководстве</a>, таблица <b>Сравнение различных типов</b>) сообщают, что при сравнении <b>числа</b> и <b>строки</b>, <b>строка</b> приводится к <b>числу</b>. И по правилам приведения к <b>числу</b> (опять же описанным к <a href="https://www.php.net/manual/ru/language.types.string.php#language.types.string.conversion" rel="nofollow" target="_blank">официальном руководстве</a>) мы получаем, что строка <code>1-2</code> приводится к числу <code>1</code>. А уж <code>1</code> <b>точно равно</b> <code>1</code>.<br />
<br />
Что можно сказать в заключение:<br />
<ul style="text-align: left;">
<li>используйте <code>===</code> вместо <code>==</code></li>
<li>почаще читайте <a href="https://www.php.net/manual/ru/" rel="nofollow" target="_blank">официальное руководство</a>.</li>
</ul>
<br />
<b>P.S.</b> Интересно, предложат и заапрувят ли когда-нибудь rfc, который сделает оба сравнения (<code>===</code> и <code>==</code>) строгими?</div>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com1tag:blogger.com,1999:blog-8891714387125133857.post-12206542501745634402019-09-24T19:14:00.000+03:002019-09-24T19:14:16.876+03:00Список аргументов консольного скрипта<div dir="ltr" style="text-align: left;" trbidi="on">
Всем привет. Сегодня будем разбираться, как получить список аргументов php-скрипта, запущенного из консоли.<br />
<br />
Обычно запуск скрипта выглядит так:<br />
<br />
<code class="prettyprint">
> php script.php run 20 zzz<br />
// или если у скрипта есть право на исполнение<br />
> ./script.php run 20 zzz</code><br />
<br />
Как же получить переданные в командной строке аргументы <code>run, 20, zzz</code>?<br />
<br />
Для этого в php есть две зарезервированные переменные:<br />
<ul style="text-align: left;">
<li><code>$argv</code> - массив, содержит список аргументов. При этом учтите, что имя исполняемого скрипта также является аргументом командной строки и присутствует в списке аргументов. Поэтому, чтобы обратиться к значению <code>run</code> из нашего примера требуется использовать <code>$argv[1]</code>, а не 0, так как <code>$argv[0]</code> - это <code>script.php</code>, <a href="https://www.php.net/manual/ru/reserved.variables.argv.php" rel="nofollow" target="_blank">офссылка</a>.</li>
<li><code>$argc</code> - число, содержит количество переданных аргументов, также учитывается имя исполняемого скрипта. Для нашего примера <code>$argc</code> равно 4, <a href="https://www.php.net/manual/ru/reserved.variables.argc.php" rel="nofollow" target="_blank">офссылка</a>.</li>
</ul>
<br />
Как и любые другие глобальные переменные в скрипте, <code>$argv</code> и <code>$argc</code> <b>не защищены от перезаписи</b>. Так что если вы где-то в вашем скрипте напишете <code>$argv = 42;</code> то все ваши входные аргументы будут потеряны. Также, эти переменные не являются суперглобальными, то есть использовать их в функциях без явного указания <code>global</code> (фу) или передачи как аргумент функции - <b>не получится</b>.<br />
<br />
Однако, выведя на экран содержимое суперглобальной переменной <code>$_SERVER</code> можно заметить, что данные из <code>$argv</code> и <code>$argc</code> дублируются в аналогичных ключах массива <code>$_SERVER</code> - <code>argv</code> и <code>argc</code>.<br />
<br />
В случае если вы хотите передавать именованные аргументы, например:<br />
<br />
<code class="prettyprint">
> php script.php --action=run --time=20 --option=zzz
</code><br />
<br />
то php их никак <b>не парсит</b> и просто выдает в <code>$argv</code> массив вида:<br /><br />
<code class="prettyprint">
Array<br />
(<br />
[0] => script.php<br />
[1] => --action=run<br />
[2] => --time=20<br />
[3] => --option=zzz<br />
)
</code><br />
<br />
Для парсинга таких входных данных существует функция <a href="https://www.php.net/manual/ru/function.getopt.php" rel="nofollow" target="_blank"><code>getopt</code></a>, но ее рассмотрение - это повод для отдельного поста.</div>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-3070327821898735762019-07-06T23:16:00.001+03:002019-07-06T23:16:41.151+03:00Перенос данных нескольких полей в единое поле hstore<div dir="ltr" style="text-align: left;" trbidi="on">
Сегодня опишу вам как решить следующую сверхспецифическую задачу: у нас в БД PostgreSQL есть таблица некоторой структуры:<br />
<br />
<code class="prettyprint">
id | name | attr_1 | attr_2 | attr_3 |<br />
---------------------------------------|<br />
1 | NAME | v1 | v2 | v3 |
</code>
<br />
<br />
Вследствие каких-то причин в проекте решено соединить данные полей <code>attr_1, attr_2, attr_3</code> в одно поле <code>attrs</code> типа <b><a href="https://postgrespro.ru/docs/postgrespro/9.5/hstore" rel="nofollow" target="_blank">hstore</a></b>.<br />
<br />
Таким образом, новая таблица и запись в ней выглядит так:<br />
<br />
<code class="prettyprint">
id | name | attrs |<br />
---------------------------------------------------------|<br />
1 | NAME | attr_1 => v1, attr_2 => v2, attr_3 => v3 |</code>
<br />
<br />
Естественно, будем все максимально автоматизировать. Для работы нам пригодятся некоторые функции для работы с hstore и немного php, просто чтобы сформировать общий текст запроса. Общий текст запроса выглядит вот так:<br />
<br />
<code class="prettyprint">
UPDATE tableName<br />
SET attrs =<br />
hstore(<br />
string_to_array(<br />
rtrim(<br />
(CASE WHEN (attr_1 IS NOT NULL) THEN ('attr_1' || '~~~' || attr_1 || '~~~') ELSE '' END)<br />
||<br />
(CASE WHEN (attr_2 IS NOT NULL) THEN ('attr_2' || '~~~' || attr_2 || '~~~') ELSE '' END)<br />
||<br />
(CASE WHEN (attr_3 IS NOT NULL) THEN ('attr_3' || '~~~' || attr_3 || '~~~') ELSE '' END),<br />
'~'<br />
),<br />
'~~~'<br />
)<br />
)
</code>
<br />
<br />
Итак, что же здесь происходит? Начнем с внутренней части.<br />
<br />
1. Для значения каждого из полей <code>attr_1, attr_2, attr_3</code> мы создаем строку вида <code class="prettyprint">НазваниеПоля~~~ЗначениеПоля~~~</code> или просто <b>пустую строку</b>, если значение поля <code class="prettyprint">NULL</code>. Все эти строки объединяем в одну результрующую.<br />
<br />
2. Далее нам требуется избавиться от <code class="prettyprint">~</code> в конце объединенной строки. В этом нам помогает <code>rtrim</code>.<br />
<br />
3. Потом из объединенной строки мы создаем массив, разбивая строку по разделителю <code>~~~</code>.<br />
<br />
4. И, наконец, полученный массив передаем в метод <code>hstore</code>. Готово.<br />
<br />
Отдельно замечу, что разделителем выбран <code>~~~</code> потому, что встретить его в значениях полей <code>attr_1, attr_2, attr_3</code> <b>невозможно</b>. Если в ваших данных может встречаться такой набор символов - используйте <b>другой</b> разделитель из более "странных" символов.<br />
<br />
С использованием php можно создать такой скрипт генерации и выполнения запроса:<br />
<br />
<code class="prettyprint">
$fields = [<br />
'attr_1',<br />
'attr_2',<br />
'attr_3',<br />
// еще поля<br />
];<br />
$glue = '~~~';<br />
$selectPattern = "(CASE WHEN (%s IS NOT NULL) THEN ('%s' || '$glue' || %s || '$glue') ELSE '' END)";<br />
$select = [];<br />
foreach ($fields as $field) {<br />
$select[] = vsprintf($selectPattern, array_fill(0, 3, $field);<br />
}<br />
$select = implode(' || ', $select);<br /><br />
$this->runSql("<br />
UPDATE {$table}<br />
SET attrs = hstore(string_to_array(rtrim(({$select}), '~'), '{$glue}'))<br />
");
</code><br />
<br />
По <a href="https://gist.github.com/u-mulder/e0359477861b3e63de87388655426e01" rel="nofollow" target="_blank">ссылке</a> - улучшенный гист с кодом, обрабатывающим даже поле типа <code>datetime</code>.
</div>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-475079523539122842019-03-27T12:55:00.000+03:002019-03-27T12:55:35.883+03:00Трансформация DateTime в DateInterval<div dir="ltr" style="text-align: left;" trbidi="on">
Недавно в вопросе на stackoverflow.com автор пытался добавить к объекту <a href="https://www.php.net/manual/en/class.datetime.php" rel="nofollow" target="_blank"><code>\DateTime</code></a> некоторый временной интервал. Интервал, однако, также являлся объектом <code>\DateTime</code>. Так как метод <code>add</code> ожидает на вход объект <a href="https://www.php.net/manual/en/class.dateinterval.php" rel="nofollow" target="_blank"><code>\DateInterval</code></a>, то ничего не работало. Возможно, есть другие решения этой проблемы, но мне пришло в голову такое: <br />
<br />
<code class="prettyprint">
$dateAppointment = (new \DateTime());<br />
$dtDuration = (new \DateTime())->setTime(1, 15, 0);<br />
// Для примера получаем интервал только с учетом часов, минут и секунд<br />
$duration = $dtDuration->format('\P\TH\Hi\Ms\S');<br />
$dateAppointment->add(new \DateInterval($duration));<br />
</code>
</div>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0tag:blogger.com,1999:blog-8891714387125133857.post-85256991316751944062019-02-10T13:42:00.001+03:002020-02-25T23:59:12.088+03:00Строковые представления типов данных, часть 1<div dir="ltr" style="text-align: left;" trbidi="on">
Всем привет. Сегодня будем разбираться в том, как php преобразует различные типы данных к строковому виду.<br />
<br />
Приведение к строке используется в нескольких случаях. Главный из них - это вывод с помощью конструкции <code>echo</code>. Обращаю внимание, что функции <code>var_dump</code> и <code>print_r</code> также выводят данные, однако, руководствуются своими алгоритмами для вывода.<br />
<br />
Второй случай, где работает приведение к строке это когда вы конкатенируете или подставляете в строку какую-либо переменную, например:<br />
<br />
<code class="prettyprint">
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;<br />
$strEval = "В эту строку я подставляю переменную $var";
</code>
<br />
<br />
Начнем издалека. Для полноты картины я добавлю работу с переменными типа <code>string</code>, но понятно что здесь нет никаких подводных камней)<br />
<br />
<code class="prettyprint">
$var = 'foo';<br />
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;<br />
$strEval = "В эту строку я подставляю переменную $var";<br />
echo $var; <br />
echo $strConcat;<br />
echo $strEval;<br />
// вывод:<br />
"foo"<br />
"Эту строку я конкатенирую с переменной foo"<br />
"В эту строку я подставляю переменную foo"
</code><br />
<br />
Перейдем с булевым переменным. Булевая переменная принимает одно из двух значений - <code>true</code> или <code>false</code>. Каждое из них приводится к разному строковому представлению:<br />
<br />
<code class="prettyprint">
$var = false;<br />
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;<br />
$strEval = "В эту строку я подставляю переменную $var";<br />
echo $var; <br />
echo $strConcat;<br />
echo $strEval;<br />
// вывод:<br />
"" // на самом деле здесь ничего не выводится, я просто отмечаю что это пустая строка<br />
"Эту строку я конкатенирую с переменной "<br />
"В эту строку я подставляю переменную "
</code><br />
<br />
<code class="prettyprint">
$var = true;<br />
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;<br />
$strEval = "В эту строку я подставляю переменную $var";<br />
echo $var; <br />
echo $strConcat;<br />
echo $strEval;<br />
// вывод:<br />
"1"<br />
"Эту строку я конкатенирую с переменной 1"<br />
"В эту строку я подставляю переменную 1"
</code>
<br />
<br />
Следующий, отдельный тип данных - <code>null</code>. Здесь все просто:<br />
<br />
<code class="prettyprint">
$var = null;<br />
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;<br />
$strEval = "В эту строку я подставляю переменную $var";<br />
echo $var; <br />
echo $strConcat;<br />
echo $strEval;<br />
// вывод:<br />
"" // на самом деле здесь ничего не выводится, я просто отмечаю что это пустая строка<br />
"Эту строку я конкатенирую с переменной "<br />
"В эту строку я подставляю переменную "
</code>
<br />
<br />
Таким образом, если вы выводите переменную, а получаете пустую строку, то не факт, что в переменной хранится пустая строка. В переменной с таким же успехом может храниться <code>false</code>, или она вообще может быть не определена (<code>null</code>). Поэтому следует использовать <code>var_dump</code>, чтобы увидеть реальное значение переменной.<br />
<br />
В <a href="http://www.u-mulder.com/2020/02/php-cast-to-string-part-2.html" rel="nofollow" target="_blank">следующей части</a> рассмотрим преобразование к строке числовых типов данных - <code>int</code> и <code>float</code>.</div>
Дядька_Малдерhttp://www.blogger.com/profile/03225294768545490563noreply@blogger.com0