Посты / Почему я перешел с Drupal на Laravel. Муки разработчика.

20.02.2017 15:56
Привет, друзья. Сегодня я хочу затронуть одну интересную тему. А именно, когда приходит понимание того, что технология, любимая многими, тебя уже на столько достала, что ты готов к кардинальным изменениям. Я более 4 лет своей жизни отдал профессиональной разработке на Друпал, и активно участвовал в жизни Drupal сообщества. Но в какой-то момент я сказал себе, хватит. И изменил свою жизнь. Здесь я расскажу о том, почему я перешел от Друпал к Ларавел, что меня побудило к этому, и как я вообще это сделал.

До 2011 года я активно крутил свои велосипеды. Писал на голом PHP, создал свой небольшой CMS, который и пытался развивать. Но в какой-то момент я устал заниматься "извращениями" и решил подмять под себя что-то серьезное, на чем можно было бы создавать большие проекты и за меньшие потери времени.

У меня есть приятель программист, который тогда активно изучал Друпал. И как раз нашел неплохую работу в датской компании на позицию Друпал разработчика. Мы с ним плотно пообщались, я побывал у него в офисе, посмотрел чем они занимаются, и в принципе  тоже решил плотно заняться изучением Drupal. Как раз вышел Друпал 7, поэтому я на него и набросился. Я уделял ему по 12 часов ежедневно на протяжении 3 месяцев.

Это были ужасные 3 месяца моей жизни. Понять Друпал вэй задача не из легких. Тем более, что эта CMS не использовала ООП, от слова совсем. А использовала систему хуков (крючки), которые вызывались в момент формирования страницы. Подключать jQuery скрипты нельзя в шаблонах, а нужно подключать в коде своего модуля! Вынести сами скрипты в футер, тоже нельзя, потому что поломается нормальная работа функционала со скриптами. Просто стандартный jQuery скрипт, тоже нельзя подключить. Его нужно оборачивать в свой специальный друпаловский обработчик. И если в обычной жизни разработчика, скрипт получает в себя настройки, через вызов аякса, к примеру, то в Друпал при подключении скрипта из своего модуля, в него можно передать параметры с бэкенда.

Все эти штучки и приколы были не просто необычны, но еще и очень тяжелы для понимания и восприятия. На картинке ниже можно увидеть кривую изучения разных CMS.

Чтобы не быть голословным, давайте разберем ряд примеров, как реализовать что-то на Друпал, и повторим это же на Laravel.

Давайте напишем роут для страницы добавления нового объявления для недвижимости (посуточная аренда):
Drupal:
/**
* Implementation of hook_menu().
*/
function itp_menu() {
$items = array();
// Add new announcement Daily rent.
$items['add_new_daily_rent'] = array(
'title' => t('Add: Daily rent'),
'description' => '',
'page callback' => 'itp_add_new_daily_rent',
'page arguments' => array(),
'access arguments' => array('seller user'),
'type' => MENU_CALLBACK,
// Add widget for upload files to form.
'file path' => drupal_get_path('module', 'node'),
'file' => 'node.pages.inc',
);
}
В коде выше мы видим хук меню, который заменяет систему роутинга во фреймворках. Именно в этом хуке и нужно описывать все наши ссылки. При чем, каждый модуль использует этот хук. В "голом" виде он выглядит так: hook_menu(). В каждом модуле, слово hook заменяется названием своего модуля. Весь этот ужас, в случае ошибки, отлавливать крайне непросто. По сути,
$items['add_new_daily_rent'] - это адрес, вида http://mysite/add_new_daily_rent
'page callback' - это функция, которая будет вызвана при переходе на эту страницу
'page arguments' - это директива, через которую можно передать переменную из адресной строки, например, так: array(1). Это означает, что если бы адрес выглядел так: add_new_daily_rent/10, то array(1) - это получение первого аргумента, то есть 10, и передача его в функцию вызова. Аргументы считаются с нуля.
'access arguments' - проверка ваших прав. Только пользователи с ролью продавцы и с правами на добавление объявления, могут вызвать функцию.
'type' - это тип нашего действия. Можно создать ссылку в меню или закладку, но в нашем случае, это просто вызов колбека.
Две последних строчки дают возможность работать с загрузкой изображений через стандартные механизмы (по сути это костыль).

А теперь все тоже, но уже на Ларавел:
// Show form.
Route::get('add_new_daily_rent', 'DailyRentController@create')->name('daily.create');
Теперь заглянем в колбек Друпал:
/**
* Output form for add new Daily rent.
*
* @global object $user
* @return object form.
*/
function itp_add_new_daily_rent() {
// Machine name content.
$node_type = 'daily_rent';
$form_id = $node_type . '_node_form';
// Add current users info.
global $user;
global $language;
// Create empty node.
$node = new stdClass();
$node->uid = $user->uid;
$node->name = (isset($user->name) ? $user->name : '');
$node->type = $node_type;
$node->language = $language->language;
// Ready object node for editing.
node_object_prepare($node);
return drupal_get_form($form_id, $node);
}
Если в двух словах, то мы формируем название формы, создаем новый пустой стд класс ноды, передаем туда пользователя, локализацию, тип ноды, подготавливаем саму ноду и вызываем отображение формы, куда и передаем пустую ноду.

Сама форма построена в конструкторе форм, и хранится в базе данных. Но можно строить и свои формы в коде. Вот пример построения формы для связи с владельцем недвижимости:
// Create form for send message author's.
function itp_send_mail_form() {
if (arg(0) == 'modal_forms') {
// Load options node.
$node = node_load(arg(3));
}
else {
$node = node_load(arg(1));
}
// If isset node.
if (!empty($node)) {
$user_name = '';
$user_tel = '';
$user_email = '';

$form['nid'] = array(
'#type' => 'hidden',
'#value' => $node->nid,
);
$form['user_name'] = array(
'#type' => 'textfield',
'#title' => t('Your name'),
'#description' => t('Enter your name'),
'#default_value' => $user_name,
'#size' => 50,
'#maxlength' => 50,
'#required' => TRUE,
);
$form['user_tel'] = array(
'#type' => 'textfield',
'#title' => t('Your phone'),
'#description' => t('Enter your phone (with code), example, +380(67)-321-21-21'),
'#default_value' => $user_tel,
'#size' => 50,
'#maxlength' => 30,
'#required' => TRUE,
);
$form['user_email'] = array(
'#type' => 'textfield',
'#title' => t('Your e-mail'),
'#description' => t('Enter your e-mail address'),
'#default_value' => $user_email,
'#size' => 50,
'#maxlength' => 30,
'#required' => TRUE,
);
$form['user_text'] = array(
'#type' => 'textarea',
'#resizable' => FALSE,
'#rows' => 3,
'#title' => t('Message'),
'#description' => t('Enter your message'),
'#required' => TRUE,
);
$form['user_submit'] = array(
'#type' => 'submit',
'#value' => t('Send'),
'#weight' => 20,
'#submit' => array('itp_send_mail_form_submit'),
);

$form['cancel'] = array(
'#markup' => l(t('Cancel'), '#',
array(
'attributes' => array(
'class' => 'ctools-close-modal'
),
'external' => TRUE
)
),
'#weight' => 21,
);
return $form;
}
else {
drupal_set_message(t('This announcement does not exist!'), 'error');
return '';
}
}

// Check entered data.
function itp_send_mail_form_validate($form, &$form_state) {
$email = $form_state['values']['user_email'];
if (!valid_email_address($email)) {
form_set_error('user_email', t('E-mail is not correct!'));
}
}

// Submit form.
function itp_send_mail_form_submit($form, &$form_state) {
$nid = $form['nid']['#value'];
$user_name = $form['user_name']['#value'];
$user_tel = $form['user_tel']['#value'];
$user_email = $form['user_email']['#value'];
$user_text = $form['user_text']['#value'];
$sent_mess = itp_send_mail($nid, $user_name, $user_tel, $user_email, $user_text);

// If message don't send.
if (!$sent_mess) {
$sent_mess_text = '<div class="error">' . t('Error sending message. Message not sent.') . '</div>';
}
else {
$sent_mess_text = '<div>' . t('Your message successful sent.') . '</div>';
}
$sent_mess_text .= '<div id="close_dialog_box">' . l(t('Close'), '#',
array(
'attributes' => array(
'class' => 'ctools-close-modal',
),
'external' => TRUE
)
) . '</div>';

// Reload dialog page and show message status.
$form_state['ajax_commands'][] = ctools_modal_command_display(t('Message status'), $sent_mess_text);
}
Честно говоря, мне даже не хочется это комментировать.

Вот так выглядит вызов метода для Ларавел:
/**
* Show the form for creating a new daily announcement.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
// Check permission.
$this->authorize('content-users');

return view('daily.create');
}
А создание формы - это простой процесс в шаблоне. Именно там, где ей и место.

Следующий пример. Друпал - это CMS,  а если точнее, то CMF система. Поэтому в нем запилено огромнейшее количество готовых решений. Но, елки-моталки, мы не хотим кушать то, что нам подсовывают, мы хотим изменить вывод, например формы, так, как нужно нам. Для этого существуют всяки хуки алтеры:
/**
* Implementation of hook_form_FORM_ID_alter().
*/
function itp_form_user_profile_form_alter(&$form, &$form_state) {
$form['account']['name']['#title'] = t('Login');
if (!user_access('administer users')) {
unset($form['path']);
unset($form['field_my_favorites']);
$form['field_user_status'][LANGUAGE_NONE]['#disabled'] = TRUE;
}

$form['account']['mail']['#prefix'] = '<div class="messages warning">' .
t("Attention! Messages can get into the SPAM folder. If you don't see post into the folder INBOX, please check SPAM folder.
Find a message and change status on the NOT SPAM. Please don't use e-mail from hotmail.com!") .
'</div>';


$form['field_user_status']['#prefix'] = '<div class="messages warning">' .
t("Only seller can add announcements!") .
'</div>';

// Delete field progress.
unset($form['field_progress']);
}

/**
* Implementation of hook_form_FORM_ID_alter().
*/
function itp_form_user_register_form_alter(&$form, &$form_state) {
global $language;
$path = url('agreement', array('absolute' => TRUE));
$form['account']['name']['#title'] = t('Login');
$form['path']['#disabled'] = TRUE;
$form['path']['#prefix'] = '<div style="display:none">';
$form['path']['#suffix'] = '</div>';
$form['agree'] = array(
'#type' =>
'checkbox',
'#title' => t('User Agreement'),
'#description' => t('You must read and agree to the <a href="@agree" target="_blank">User Agreement</a>',
array('@agree' => $path)),
'#weight' => 14,
'#required' => TRUE,

);

$form['account']['mail']['#prefix'] = '<div class="messages warning">' .
t("Attention! Messages can get into the SPAM folder. If you don't see post into the folder INBOX, please check SPAM folder.
Find a message and change status on the NOT SPAM. Please don't use e-mail from hotmail.com!") .
'</div>';

$form['field_user_status']['#prefix'] = '<div class="messages warning">' .
t("Only seller can add announcements!") .
'</div>';

// Add own validator.
$form['#validate'][] = 'itp_register_form_validate';
}
В Ларавел, вы просто зайдете в шаблон и допишите, что вам надо за две минуты!

В Друпал я так навострился писать свой функционал и свои модули, что для меня это вообще было не проблемой. Проблемы начались позже. Когда я сделал громадный портал по недвижимости http://best-house.org Я создал для него больше десятка кастомных модулей. Всевозможные фиды для экспорта и импорта XML файлов партнеров, Яндекс Маркета и т.д. Работа с фасетами, с сервером индексации Apache Solr, создал сложную систему оплаты за Вип объявления, с очень нетривиальным функционалом автоматического прикручивания любой существующей платежной системы, создал систему верификации и валидации e-mail списка рассылки, создал систему онлайн бронирования по посуточной аренде. Да до фига чего еще создал.

В итоге, все это поддерживать стало не просто трудно - это превратилось в самоистязание. Любое новое обновление ядра приводило к каким-то глюкам. Все эти, такие "удобные" роуты и формы, плюс типы контента и др., которое создавалось из админки Друпала, хранятся в базе данных. Для того, чтобы это все не потерять и хранить в git, нужен жестокий костыль - модуль Features. Кто понимает, как он работает и заглядывал в тот хаос, который он создает в экспортированных файлах, тот должен меня понять.

А если выходит новая версия одного из сотен, установленных в Друпал модулей, то начинает бить мелкая дрожь. Начинается полный бэкап сервера и базы. А это, между прочим, терабайты данных! И восстановится из бэкапа - это порой очень непростая задача.

Так вот, очень часто бывает, что при обновлении чужого модуля с Drupal Org, сайт так громко на продакшне падает, что это слышно даже на другом континенте. Первым делом отваливается база, где хранятся все системные настройки самой CMS!!! Что, как вы уже догадались, делает дебагинг не просто сложной задачей, а порой - архи сложной.

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

Поэтому, испытав определенные ломки, я стал искать альтернативу. Тем более, что после выхода Друпал 8, там все переделали и перелопатили. Теперь это уже был и не Друпал 7, но и не Симфони. А смесь бульдога с носорогом.

Именно по ряду вышеизложенных причин, я вообще решил полностью отказаться от использования CMS, даже на мелких проектах. Да, несомненно, что с CMS быстро, но реально - это путь в никуда.

А так как я очень люблю писать разные алгоритмы и находить новые пути решения задач, то тратить свое время на борьбу с CMS у меня нет никакого желания. Лучше сосредоточится на своих идеях и написать код, почти на голом PHP, используя удобный механизм, который дает мне Ларавел.

Вот примерно мои мысли вслух о том, как я перешел с Друпал на Лару. Тот кто обожает писать свой код и свои решения, тот должен оценить фреймворк. А кто ленивый и ищет уже готовые решения из коробки, тот не оценит, до поры до времени.

Всем удачи. И до новой встречи. Жду ваши комментарии и ваши истории.
2

Комментарии (2):

EGORR писал(а):
Однажды меня просили отремонтировать сайт на Битрикс. И я его скачал и начал изучать код. Я был в шоке! Такой помойки я не видел! Куча чудовищного, разнопёрого кода! Подход к формированию HTML просто варварский. И я отказался. Как сказано ещё первобытными программистами: чем в чужом коде разбираться, легче свой написать. Никогда не использовал CMS. Скачивал, смотрел что это такое. Но когда видел, что простую страницу с простым текстом формируют 28 запросов к базе данных, как куски HTML хранятся в огромном количестве таблиц!.. Всякое желание что-то с ними делать отпадало. Я очень долго писал на голом php и признал фреймворки только когда понял, что иначе вымру как мамонт.
Я с вами согласен на все 100%. Но к огромному моему сожалению, подавляющее большинство заказчиков хотят на CMS. Так как дешево и быстро. Качество многих вообще не волнует. А фреймворки - это уже корпоративный сегмент. Найти независимому программисту такого заказчика задача не простая. А сидеть на фирмах с детворой - это тоже не вариант.
Однажды меня просили отремонтировать сайт на Битрикс. И я его скачал и начал изучать код. Я был в шоке!
Такой помойки я не видел! Куча чудовищного, разнопёрого кода! 
Подход к формированию HTML просто варварский. И я отказался. Как сказано ещё первобытными программистами: чем в чужом коде разбираться, легче свой написать.
Никогда не использовал CMS. Скачивал, смотрел что это такое. Но когда видел, что простую страницу с простым текстом формируют 28 запросов к базе данных, как куски HTML хранятся в огромном количестве таблиц!.. Всякое желание что-то с ними делать отпадало. Я очень долго писал на голом php и признал фреймворки только когда понял, что иначе вымру как мамонт.