среда, 27 марта 2019 г.

Трансформация DateTime в DateInterval

Недавно в вопросе на stackoverflow.com автор пытался добавить к объекту \DateTime некоторый временной интервал. Интервал, однако, также являлся объектом \DateTime. Так как метод add ожидает на вход объект \DateInterval, то ничего не работало. Возможно, есть другие решения этой проблемы, но мне пришло в голову такое:

$dateAppointment = (new \DateTime());
$dtDuration = (new \DateTime())->setTime(1, 15, 0);
// Для примера получаем интервал только с учетом часов, минут и секунд
$duration = $dtDuration->format('\P\TH\Hi\Ms\S');
$dateAppointment->add(new \DateInterval($duration));

воскресенье, 10 февраля 2019 г.

Строковые представления типов данных, часть 1

Всем привет. Сегодня будем разбираться в том, как php преобразует различные типы данных к строковому виду.

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

Второй случай, где работает приведение к строке это когда вы конкатенируете или подставляете в строку какую-либо переменную, например:

$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";


Начнем издалека. Для полноты картины я добавлю работу с переменными типа строка, но понятно что здесь нет никаких подводных камней)

$var = 'foo';
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo $strConcat;
echo $strEval;
// вывод:
"foo"
"Эту строку я конкатенирую с переменной foo"
"В эту строку я подставляю переменную foo"


Перейдем с булевым переменным. Булевая переменная принимает одно из двух значений - true или false. Каждое из них приводится к разному строковому представлению:

$var = false;
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo $strConcat;
echo $strEval;
// вывод:
"" // на самом деле здесь ничего не выводится, я просто отмечаю что это пустая строка
"Эту строку я конкатенирую с переменной "
"В эту строку я подставляю переменную "


$var = true;
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo $strConcat;
echo $strEval;
// вывод:
"1"
"Эту строку я конкатенирую с переменной 1"
"В эту строку я подставляю переменную 1"


Следующий, отдельный тип данных - null. Здесь все просто:

$var = null;
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo $strConcat;
echo $strEval;
// вывод:
"" // на самом деле здесь ничего не выводится, я просто отмечаю что это пустая строка
"Эту строку я конкатенирую с переменной "
"В эту строку я подставляю переменную "


Таким образом, если вы выводите переменную, а получаете пустую строку, то не факт, что в переменной хранится пустая строка. В переменной с таким же успехом может храниться false или она вообще может быть не определена (null). Поэтому следует использовать var_dump, чтобы увидеть реальное значение переменной.

В следующей части рассмотрим преобразование к строке числовых типов данных - int и float.

пятница, 11 января 2019 г.

Локальный .gitignore

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

И такой файл есть. Это файл .git/info/exclude. В нем, в формате аналогичном .gitignore, можно указать все свои исключения и, тем самым, почистить вывод git status от лишних файлов. А так как папка .git не связана с удаленным репозиторием, то эта информация никуда не уедет. Единственно, не забудьте этот файл, если вдруг будете переезжать, например, на другой компьютер или разворачивать репозиторий заново где-нибудь по другому пути.

Больше про игноры в оф-доке.

пятница, 23 ноября 2018 г.

Проверка isset() && !empty()

Очень часто в коде разработчиков встречается конструкция вида:

if (isset($var) && !empty($var)) {}

Аргументация людей, пишущих такой код, заключается в следующем:
empty выведет warning если переменная $var не была определена ранее.
Теперь откроем официальный мануал по empty и прочтем:
empty() does not generate a warning if the variable does not exist.
В русском варианте:
empty() не генерирует предупреждение, если переменная не существует.
Что это значит? Это значит, что все коды вида:

if (isset($var) && !empty($var)) {}

можно взять и заменить на:

if (!empty($var)) {}

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

На этом тему можно закрывать.

вторник, 9 октября 2018 г.

Автокомплит для поля с предустановленным значением

Недавно столкнулся со следующей проблемой. Имеется форма с автокомплитом, реализованным через плагин devbridge/jQuery-Autocomplete. При первой загрузке поле, к которому привязан автокомплит, не заполнено, пользователь начинает что-то вводить и получает подсказки. Отдельно уточню, что подсказки выводятся в результате ajax-запроса на сервер. Далее, при выборе конкретной подсказки происходит сабмит формы, и страница перезагружается. Тут все хорошо.

После перезагрузки страницы в поле с автокомплитом уже предустановлено поисковое значение из предыдущего шага.

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

Мое решение не претендует на правильность, возможно, есть какая-то опция в настройках, но я ее не нашел. Поэтому решил сделать следующее: перед активацией автокомплита фиксируем начальное значение поля. Далее используем опцию onSearchStart, которая определяет, можно ли начинать поиск или нет. В этой функции проверяем - если начальное значение поля пустое или текущее значение отличается от начального, то возвращаем true, разрешая поиск подсказок, иначе возвращаем false и запрещаем поиск. Получается вот такой сеттинг (html + javascript):

<div id="autocomplete_field_wrap">
    <input id="autocomplete_field" type="text" value="" />
</div>
<script src="./jquery.min.js"></script>
<script src="./jquery.autocomplete.js"></script>
<script>
$(document).ready(function () {
   var initialValue = $('#autocomplete_field').val();

   $('#autocomplete_field').autocomplete({
       appendTo: $('#autocomplete_field_wrap'),
       dataType: "json",
       minChars: 3,
       paramName: "q",
       serviceUrl: 'ajax/autocomplete.php',
       onSearchStart: function () {
           return initialValue === "" || initialValue !== $('#autocomplete_field').val();
       },
       onSelect: function () {
           console.log('value selected');
       }
   });
});
</script>


С поставленной задачей такой костылик вполне справляется.

суббота, 29 сентября 2018 г.

Прерывание работы компонента на классе

При работе с компонентом иногда приходится прерывать работу компонента еще на стадии проверки входных параметров $arParams. Например, если вам требуется ИД инфоблока, а его не указано, то нет смысла заводить всю шарманку с кешированием и прочим, так как получать в общем-то нечего.

В компонентах, построенных на процедурном подходе в файле component.php это решалось приблизительно так:

if (!CModule::IncludeModule('iblock')) {
    ShowError(GetMessage("IBLOCK_MODULE_NOT_INSTALLED"));
    return;
}


В этом случае на странице выводилось красным текстом какое-либо сообщение, и страница продолжала свою работу.
Теперь давайте рассмотрим как реализовать сходное поведение в компоненте на классах (с файлом class.php вместо component.php).

пятница, 21 сентября 2018 г.

Кеширование пропертей инфоблока

Сегодня столкнулся со следующей проблемой: в скрипте импорта выполняются два абсолютно одинаковых CIBlockElement::GetList. Между ними происходит одно важное действие - пересоздание свойства инфоблока (удаление и создание заново с таким же кодом). Казалось бы, может ли что-то пойти не так? Как оказалось - может.

Суть в том, что когда вы в GetList пытаетесь отфильтровать записи по фильтру типа:
[
    'IBLOCK_ID' => $this->arParams['IBLOCK_ID'],
    'ACTIVE' => 'Y',
    'ACTIVE_DATE' => 'Y',
    'PROPERTY_FOO' => 'BAR',
],

то происходит следующее:
  • битрикс по коду FOO свойства находит о нем информацию в таблице b_iblock_property
  • из этой информации извлекается ИД свойства и подставляется в запрос, получается условие вида WHERE tablename.PROPERTY_42 = 'BAR', где 42 - это ИД свойства с кодом FOO
  • битрикс кеширует эту информацию в простом массиве с ключом определенного формата
  • последующие GetList с использованием свойства с данным кодом и для данного инфоблока обращаются к этому кешу и вытягивают из него ИД свойства.

В моем случае свойство удалялось из таблицы, но не удалялось из этого кеша. Таким образом, условие WHERE tablename.PROPERTY_42 = 'BAR' хранило код старого, уже удаленного свойства с кодом FOO, а про новое не знало.
Какое решение? Правильно, очистить кеш. Так как кеш - это простой массив, хранящийся как глобальная переменная (это же ведь битрикс), то достаточно сделать нечто такое:

$GLOBALS['IBLOCK_CACHE_PROPERTY'] = [];

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

А вот зачем мне понадобилось пересоздавать свойство - это уже совсем другая история.

P.S. Возможно, переход на D7 (когда в D7 появится возможность тянуть проперти через ORM) каким-либо образом исправит положение, но пока что это мечты.