пятница, 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) каким-либо образом исправит положение, но пока что это мечты.

среда, 19 сентября 2018 г.

Методы document.location

Каждый раз когда мне требуется перезагрузить страницу средствами javascript, я пытаюсь вспомнить, что же нужно сделать с объектом document.location. Поэтому сегодня мы быстренько пройдемся по методам данного объекта. Информация взята из MDN, и если вы мне не доверяете, можете обратиться к первоисточнику. Итак, продолжим.

пятница, 3 августа 2018 г.

Реконнект mysql-соединения

Иногда, если ваш скрипт долго получает данные из других источников (парсит чужие сайты, например, или запрашивает медленно думающее API), запись в БД может оборваться с известным сообщением Mysql server has gone away.

Но, скажете вы, я поставил set_time_limit в 7200 секунд. Этого недостаточно. Помимо лимита на время исполнения скрипта, указываемого в php.ini или конкретно для скрипта, существует еще wait_timeout, настроенный в конфиге mysql. Как только данный таймаут истекает, mysql-сервер перестает обращать внимание на ваш скрипт, вследствие чего вы получаете типичную ошибку Mysql server has gone away.

Естественно, если у вас есть доступ к конфигурации mysql, то можно подредактировать wait_timeout. Но если, например, доступ к БД вам запретили ДБА, или скрипт работает раз в полгода, или в целом не хочется лезть в кишки mysql, то можно воспользоваться простейшим подходом - разрывать и восстанавливать соединение заново. Также никто не запрещает вам запускать скрипт через крон, например, и обрабатывать данные частями.

Для примера приведу код как разрывать-восстанавливать соединение с БД в битриксе:

// Старое ядро, $GLOBALS['DB'] аналог $DB
$dbHost = $GLOBALS['DB']->DBHost;
$dbName = $GLOBALS['DB']->DBName;
$dbLogin = $GLOBALS['DB']->DBLogin;
$dbPassword = $GLOBALS['DB']->DBPassword;

$GLOBALS['DB']->Disconnect();
$GLOBALS['DB']->Connect($dbHost, $dbName, $dbLogin, $dbPassword);

// Новое ядро
$cn = \Bitrix\Main\Application::getConnection();
$cn->disconnect();
$cn->connect();

среда, 23 мая 2018 г.

Заполнение массива числами.

Недавно от коллег пришла задача - заполнить javascript-массив определенной последовательностью чисел без явного использования цикла.

То есть в простейшем случае требуется получить массив вида:

[1,2,3,4,5]

Естественно, используя цикл for можно все сделать просто и понятно, например:

var a = [];
for (i = 1; i <= 5; i++) {
    a.push(i)
}


Но мы пойдем другим путем.

четверг, 19 апреля 2018 г.

Текущий год в Twig

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

echo date('Y').

Для того, чтобы показать текущий год в Twig достаточно одной строки:

{{ 'now'|date('Y') }}

Кстати, если у вас под рукой нет Twig или заводить его напряжно - можно воспользоваться TwigFiddle.

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

вторник, 6 марта 2018 г.

Mysql и проверка внешних ключей

Начнем с простейшей команды:

> mysql -u user -p databasename < dump.sql

Иногда дамп бывает сделан так, что сначала создается таблица, в которой имеется foreign key на еще не созданную. В этом случае, процедура импорта будет прервана с сообщением вида:

ERROR 1215 (HY000) at line 42: Cannot add foreign key constraint

Что же делать? Специально для такого случая имеется системная переменная FOREIGN_KEY_CHECKS. Оптимальным способом ее использования будет следующий:

> mysql -u user -p --init-command="SET SESSION FOREIGN_KEY_CHECKS=0;" databasename < dump.sql

После этого импорт отлично отрабатывает.

Чтобы два раза не вставать с дивана: для просмотра больших дампов можно использовать утилиту sed.
Например, при импорте вышеуказанного дампа вам вывелось сообщение вида.

ERROR 1215 (HY000) at line 19242: Cannot add foreign key constraint

Допустим, вы не знаете причину и хотите посмотреть, что же там такого на строке 19242. Конечно, можно открыть файл и медленно и печально листать до нужной строки. А можно сделать так:

sed -n '19240,19245p; 19246q' dump.sql

Данная команда покажет вам строки с 19240 по 19245, а поиск этих строк в файле и последующий вывод будут осуществлены очень быстро.