Время идет, прогресс приносит свои плоды, каждый месяц выходят новые версии того или иного программного обеспечения. То же происходит и с языком PHP. Наша команда проекта krisha.kz решила, что уже пора совершить переход на новую версию интерпретатора. Мы поделимся опытом перехода PHP с версии 5.6 на 7.1, который обслуживает наш основной монолит.
Существует видео об устройстве этого монолита. Его особенностью является, то что он основан на фреймворке Phalcon версии 2. В связи с этим, помимо обновления самого PHP, нам нужно было поработать и над переходом на 3-ю версию Phalcon.
Собственно, сам переезд был осуществлен еще 11 октября 2017 — руки не доходили написать про это. Но, думаю, тем кто использует флакон будет интересно.
Профит
Начнем сразу с пользы которую мы получили.
На графике показан промежуток в три месяца, стрелочка указывает на момент перехода на php7.1 (11 октября 2017 г.). Голубая линия по оси Y справа показывает количество запросов. Две остальных линии по оси Y слева показывают скорость рендеринга страницы на PHP. Данные сгруппированы по дням.
Как видно, профит составляет примерно 30% по скорости отдачи страницы.
Потребление процессора также снизилось, но к сожалению графика по этим данным не сохранилось.
Помимо материальной стороны произошли и, так сказать, культурные изменения при написании кода на PHP. Было принято решение всегда объявлять тип аргумента метода и тип возвращаемого значения. Понятно, что производительность от этого не возрастет, как утверждают сами разработчики PHP7, но это позволяет писать более предсказуемый код.
PHP7 привнес новую порцию синтаксического сахара такого как “оператор объединения с null”, “оператор spaceship” и многое другое. Все эти нововведения помогают писать код проще и понятнее.
Есть и негативная сторона — вследствии обновления библиотек до последней версии, чтобы они работали на PHP7.1, пришлось отказаться от некоторой функциональности зависимых библиотек. Например, при обновлении twig до версии ~2.0 пришлось отказаться от Traceable в debugbar.
Обновление Phalcon — это боль в двойне. Требуется не только обновить кодовую базу, но и затронуть инфраструктуру обновлением extension. Тем не менее подробное руководство по обновлению от разработчиков смягчает этот момент
Как это было
Стояла задача обновить PHP с версии 5.6 до 7.1 и Phalcon c 2.0 до 3.2.
На момент середины 2017 года PHP 7.1 показал себя стабильно и все вендоры, что мы используем, обзавелись поддержкой.
Phalcon 3.2 уверенно работал с PHP 7.1 и имелась достаточно подробная документация с примерами кода по миграции на новую версию.
Обновление двух важных компонентов системы может показаться труднореализуемым решением. Однако, проанализировав changelog Phalcon мы пришли к выводу, что не требуется глобальных изменений в коде. Обратная совместимость была нарушена не сильно. В этой задаче убить двух зайцев все же получилось.
Важным было избегать ненужного рефакторинга и прочих соблазнов, в которых можно “утонуть” растянув срок выполнения и усложнив тестирование.
Обновление PHP
Анализатор php7cc показал, что у нас имеется несколько классов, которые следует переименовать. Также используется устаревшее расширение mcrypt.
Еще обработчик ошибок нужно было научить понимать Throwable.
Классы с зарезервированными именами переименовали с постфиксом директории, в которой они находятся. Было решено пройтись и по soft-reserved-words, чтобы уменьшить правки при переходе на последующие версии.
Расширение mcrypt сменили в пользу openssl.
Было:
$ivSize = mcrypt_get_iv_size(self::MCRYPT_CIPHER, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($ivSize, self::RANDOMNESS_SOURCE);
Стало:
$ivSize = openssl_cipher_iv_length(self::OPENSSL_CIPHER);
$iv = openssl_random_pseudo_bytes($ivSize);
На этом список основных изменений по переходу на php 7.1 можно считать оконченным.
Обновление Phalcon
Более сильно код затронули изменения, связанные с переходом на Phalcon. Хоть правки носили больше структурный характер. DI и поведение валидации форм предстояло переделать под новые требования.
Изменился внутренний контекст DI, благодаря чему код стал более логичным.
Было:
$this->di->set('api', function () {
// здесь $this это текущий класс
$api = new Api($this->di->config->api->toArray());
$userCookie = $this->di->config->get('cookieId');
if ($this->di->getCookies()->has($userCookie)) {
$api->setUserId($this->di->getCookies()->getValue($userCookie));
}
return $api;
}, true);
Стало:
$this->di->set('api', function () {
// здесь $this это \Phalcon\Di
$api = new Api($this->config->api->toArray());
$userCookie = $this->config->get('cookieId');
if ($this->getCookies()->has($userCookie)) {
$api->setUserId($this->getCookies()->getValue($userCookie));
}
return $api;
}, true);
Чтобы понять, что изменилось в классах Form и Validator, мы отправились в исходный код фреймворка. Огромным плюсом является то, что zephir синтаксически похож на PHP. Благодаря этому разобраться в коде было проще и быстрее.
Вот основное изменение, которое было сделано в коде нашего проекта для работы форм:
Было:
public function appendMessage($message, $field, $type = null)
{
if (is_string($message)) {
$message = new Message($message, $field, $type);
}
if (!is_null($this->_messages) && array_key_exists($field, $this->_messages)) {
$this->_messages[$field]->appendMessage($message);
} else {
$this->_messages[$field] = new Message\Group([$message]);
}
}
Стало:
public function appendMessage($message, $field, $type = null)
{
if (is_string($message)) {
$message = new Message($message, $field, $type);
}
// Изменился способ хранения сообщений формы
if (!$this->_messages) {
$this->_messages = new Message\Group([$message]);
} else {
$this->_messages->appendMessage($message);
}
}
У нас имеются методы по работе с формами, которые стали несовместимы с текущим интерфейсом Phalcon\Forms. Сообщения теперь накапливаются в объекте Phalcon\Validation\Message\Group вместо массива. Один элемент формы теперь содержит множество сообщений валидации.
Настройка cancelOnFail поменяла свою логику, в случае ошибки валидация отменяется для всей формы, пропуская остальные элементы. Раньше процесс закачивался только для проверяемого поля и переходил к последующим полям.
Настройка окружения
На продакшене были созданы новые виртуальные бэкенды с равнозначными параметрами, правда, с другой операционной системой и обновленным nginx.
Старые:
PHP 5.6.17
php5-phalcon 2.0.8-php56~jessie
nginx 1.10
Linux version 3.16.0-4-amd64 (debian-kernel@lists.debian.org) (gcc version 4.8.4 (Debian 4.8.4-1) )
Новые:
PHP 7.1.10
php7.1-phalcon 3.2.2-1+php7.1
nginx 1.11
Linux version 4.11.0-14-generic (buildd@lcy01-08) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) )
Особых изменений в конфигурации php делать не пришлось. Серверы с кэшем (Redis) также не претерпели никаких изменений, поменяли только префикс кэша.
Так как релиз производился на новые бэкенды, дополнительной страховкой было, то что в случае фиаско можно было быстро переключиться на старые бэкенды с версией кода для php 5.6.
Переход на PHP7 не обошел и нашего CI сервера. Так как автотесты запускаются в docker-контейнере с определенной версией PHP, то мы создали образы с новыми версиями php и phalcon. К слову, вот как у нас происходит запуск тестов:
set -eux
docker pull ${bamboo.docker.base.image.php.krisha}
docker run \
--rm \
--volume $(pwd):/code \
--volume /etc/passwd:/etc/passwd:ro \
--volume /etc/group:/etc/group:ro \
--user $(id -u):$(id -g) \
--workdir /code \
--interactive \
${bamboo.docker.base.image.php.krisha} \
/code/vendor/bin/robo parallel mobile
Этот код находится в таске бамбу на стадии тестирования. Тема настройки CI заслуживает отдельного освещения, поэтому мы не будем углубляться в это.
Выводы
Благодаря большому количеству автотестов в совокупности с ручным тестированием удалось избежать серьезных проблем при обновлении. Своевременность постановки задачи позволила воспользоваться опытом других проектов и обойти грабли, на которые они наступили. Работы по переходу уложились в срок 1 месяц. После, в течение недели были поправлены несколько минорных багов.
Особая благодарность comment за помощь в написании статьи и реализации перехода на PHP7.
Комментариев нет:
Отправить комментарий