LANGUAGE/ЯЗЫК
open
SETTINGS
PALETTE
gradient colors:
background:
open
Tuesday May 24, 2016 at 1:40 PM

Почему никогда не надо использовать OpenCart (пер. с англ.)

Ниже приводится перевод на русский язык статьи с сервиса TechChattr. Затем, после перевода, я добавлю несколько слов от себя.

Ссылка на оригинал: Why you should never use OpenCart
Автор:
Feras
Перевод на русский язык:
Serge Baumer

Недавно у меня была возможность поработать над e-commerce проектом приличного размера на основе OpenCart.

Я не пользовался этой системой ранее, но слышал, что она очень проста и удобна в использовании. Так что ниже только вкратце описываются основные изъяны и проблемы OpenCart и причины, почему вам НИКОГДА не стоит отдавать предпочтение этой платформе в ваших проектах, если только вы не хотите постоянной головной боли, безобразного и с невероятным количеством повторений программного кода.

Очевидное

Итак, начнем с очевидного. Беглый взгляд на установочную SQL-схему раскрывает нечто умопомрачительное. Для всех таблиц, включая таблицы онлайн-заказов, выбрано хранилище MyISAM, которое, во-первых, даже не должно рассматриваться в схемах, основанных на MySQL 5+, особенно для целей, относящихся к веб; во-вторых, и что, возможно, еще важнее: В НЕМ НЕ ПОДДЕРЖИВАЮТСЯ ТРАНЗАКЦИИ. Простая логика диктует, что транзакции — это фундаментальная составляющая электронной коммерции, но, по-видимому, команда OpenCart решила, что это не тот случай? Я уже ошеломлен, но давайте на этом не останавливаться. В MyISAM также нет внешних ключей (foreign keys), так что все проверки целостности и ограничений (integrity and constraint checks) выполняются с помощью PHP, нехорошо…

Повторения кода

Система до отказа забита кодом, подобным нижеследующему:

$this->language->load(’account/account’);
$this->data[’heading_title’] = $this->language->get(’heading_title’);
$this->data[’text_my_account’] = $this->language->get(’text_my_account’);
$this->data[’text_my_orders’] = $this->language->get(’text_my_orders’);
$this->data[’text_my_newsletter’] = $this->language->get(’text_my_newsletter’);
$this->data[’text_edit’] = $this->language->get(’text_edit’);
$this->data[’text_password’] = $this->language->get(’text_password’);
$this->data[’text_address’] = $this->language->get(’text_address’);
$this->data[’text_wishlist’] = $this->language->get(’text_wishlist’);
$this->data[’text_order’] = $this->language->get(’text_order’);
$this->data[’text_download’] = $this->language->get(’text_download’);
$this->data[’text_reward’] = $this->language->get(’text_reward’);
$this->data[’text_return’] = $this->language->get(’text_return’);
$this->data[’text_transaction’] = $this->language->get(’text_transaction’);
$this->data[’text_newsletter’] = $this->language->get(’text_newsletter’);

Итак. Мы загружаем языковой файл и вручную переносим КАЖДОЕ значение из простого массива (которым и является любой языковой файл) в новый массив, с тем же самым ключом. Это, как правило, превращает 10-строчный контроллер в содержащий 50 или 60 строк… Контроллеры покрупнее имеют по 150 избыточных строк только для отображения языков !? Далее, переходим к обработке форм. Хорошо, подсистемы обработки форм, о которой можно было бы говорить, нет, и весь ввод обрабатывается примерно так:

if (isset($this->request->post[’model’])) {
    $this->data[’model’] = $this->request->post[’model’];
} elseif (!empty($product_info)) {
    $this->data[’model’] = $product_info[’model’];
} else {
    $this->data[’model’] = ’’;
}

if (isset($this->request->post[’sku’])) {
    $this->data[’sku’] = $this->request->post[’sku’];
} elseif (!empty($product_info)) {
    $this->data[’sku’] = $product_info[’sku’];
} else {
    $this->data[’sku’] = ’’;
}

if (isset($this->request->post[’upc’])) {
    $this->data[’upc’] = $this->request->post[’upc’];
} elseif (!empty($product_info)) {
    $this->data[’upc’] = $product_info[’upc’];
} else {
    $this->data[’upc’] = ’’;
}


if (isset($this->request->post[’ean’])) {
    $this->data[’ean’] = $this->request->post[’ean’];
} elseif (!empty($product_info)) {
    $this->data[’ean’] = $product_info[’ean’];
} else {
    $this->data[’ean’] = ’’;
}

Опять же, для формы с 50 полями, мы имеем 50 блоков, подобных вышеприведенным. Конечно, весь этот код можно заменить тремя строками, которые будут делать то же самое — чем-то вроде этого:

if(!empty($this->request->post)){
    foreach($this->request->post as $k => $v){
        if (!empty($this->request->post[$k])) {
            $this->data[$k] = $this->request->post[$k];
        }
    }
}

Поскольку подсистема валидации отсутствует, каждый контроллер вызывает метод валидации для каждого обрабатываемого объекта, в котором валидация производится старыми добрыми PHP-функциями в ручном режиме для каждого поля формы. Я упомянул последнее ввиду того, что это вроде бы ничего для PHP-приложения 10-летней давности, но в наши дни такое не имеет смысла.

База данных

Я уже говорил об использовании MyISAM и отсутствии транзакций и внешних ключей, но давайте разовьем немного тему базы данных. В связи со всем вышеизложенным, каждый контроллер включает в себя метод валидации, где проверяются разнообразные ограничения (constraints) — вместо того, чтобы использовать движок MySQL для этих действий. То же самое и с удалением объектов из БД, т.е. вместо каскадных внешних ключей, мы сначала вручную находим все связанные записи, а затем удаляем их.

На этом сумасшествие не заканчивается. Запрос для извлечения реальной единицы товара довольно громоздкий, он объединяет (join) 8 или 9 таблиц чтобы собрать всю информацию о товаре. Уму непостижимо именно то, что этот запрос осуществляется неоднократно, в разных местах внутри циклов и без какого бы то ни было кэширования. То же и с функцией getProducts, которая представляет тяжелейший во всей системе запрос. Никаких признаков кэширования…

И во всей системе кэширования почти не встретишь. Еще забавнее то, что оно есть в некоторых местах бэкенда, но не во фронтенде. Там, где применяется кэширование, единственно поддерживаемый метод — файловый, который, если мы имеем нормальное кэширование в сервере MySQL, даже замедляет работу, т.к. нужно открыть и прочитать файл, затем десериализовать результат. Список можно продолжать и дальше, но суть в том, что это бардак.

Шаблоны

Шаблоны, вы можете подумать, являются просто старым добрым PHP, так что может быть не так? Не так… Система работает таким образом, что она трактует каждый отдельный блок контента на странице как модуль, так что, чтобы добавить что-нибудь — даже что-нибудь настолько простое, как:

<p>asdasd</p>

ok, вы должны создать модуль. Итак, создание модуля состоит из: добавления backend-модуля, которое заключается в копировании существующего пустого модуля, переписывании имен, изменении всех 50 языковых строк и т.д.; затем создания frontend-вида и контроллера для него, и вуаляаа! — 2 часа спустя вы можете что-то отобразить в браузере. Если простота здесь — основной коммерческий аргумент, то это, наверное, как угодно, но только не просто (или поправьте меня: это просто, но настолько утомительно и отнимает столько времени, что можно сказать, что сложно).

SEO URL

Опять невероятно. Взгляните на этот фрагмент (SEO-обработчик URL):

foreach ($parts as $part) {
    $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "url_alias WHERE keyword = ’" . $this->db->escape($part) . "’");

    if ($query->num_rows) {
        $url = explode(’=’, $query->row[’query’]);

        if ($url[0] == ’product_id’) {
            $this->request->get[’product_id’] = $url[1];
        }

        if ($url[0] == ’category_id’) {
            if (!isset($this->request->get[’path’])) {
                $this->request->get[’path’] = $url[1];
            } else {
                $this->request->get[’path’] .= ’_’ . $url[1];
            }
        }

        if ($url[0] == ’manufacturer_id’) {
            $this->request->get[’manufacturer_id’] = $url[1];
        }


        if ($url[0] == ’information_id’) {
            $this->request->get[’information_id’] = $url[1];
        }
    } else {
        $this->request->get[’route’] = ’error/not_found’;
    }
}

Связывающая таблица url_alias содержит два поля: entiti_id=id (идентификатор объекта) и keyword (ключевое слово) для этого объекта. Что произойдет, если у вас есть производитель (manufacturer) и категория (category) с одинаковыми именами, кто-то спросит… Да, что вы получите в результате — это решать отцам OpenCart. Конечно, они могли бы добавить префиксы category/ к категориям и manufacturer/ к производителям и добавить тип объекта в таблицу рерайта, но зачем заморачиваться. Вы не можете добавить собственную логику разбора и рерайта, если вы хотите расширить систему (это из-за архитектурных проблем, до которых мы вскоре доберемся), и это значит, что вы находите этот самый блок if и дописываете туда свой код (не забывая также сделать там что-то, чтобы один и тот же ключ не указывал на разные объекты, лол). В методе перезаписи (rewrite), собственно, все так же. В значительной степени — наихудшая система маршрутизации.

Безопасность

Я пока не заметил каких-либо серьезных проблем безопасности, все, что нужно фильтруется достаточно хорошо, пусть даже и реально тупым путем повторений, как и все остальное в системе, но вот сюда, возможно, следует заглянуть: http://www.opencart.com/index.php?route=common/home/__construct (на настоящий момент эта ссылка уже не работает — прим. перев.).

Архитектура

Начну с указания на то, что данное ПО не является объектно-ориентированным. Здесь используется объектно-ориентированный синтаксис PHP, но делается все в чисто процедурном стиле. Здесь нет абстракции, нет разделения ответственности, методы вызывают друг друга повсеместно, иногда так, что подсистема налогов и доставки передает ссылку на одну переменную сквозь 10 различных налоговых классов вообще без никакой ясности. Это не является объектно-ориентированным программированием или правильно структурированным приложением. Естественно, вы не можете наследовать и расширить (extend) ни один из сервисов ядра, вы должны модифицировать само ядро.

Приложения фронтенда и админ-зоны имеют одинаковое ядро, одинаковый функционал, одинаковые объекты, но при этом это две разные кодовые базы, которые повторяют друг друга почти один в один. Любое изменение в модели в админ-приложении должно быть вручную повторено в модели фронтенда… Так что, ввиду плохой архитектуры, ребята решили выкрутиться вот этим:

https://code.google.com/p/vqmod/

Пожалуйста, не вздумайте использовать это: после установки нескольких модулей, вы придете к тому, что будете работать более с XML, содержащим PHP, нежели, собственно, с PHP кодом. Огромнейший беспорядок.

Заключение

Таков был мой опыт работы с OpenCart, и я должен сказать, безусловно, наихудший опыт программирования. Это система которая каким-то образом работает, и положение ее сомнительно. Я посчитал, что неплохо будет поделиться с вами, чтобы вы, выбирая основу для нового проекта, связанного с электронной коммерцией, никогда не рассматривали в качестве варианта OpenCart. Просто для вашей информации: расширения не намного лучше, они такие же неуклюжие и запутанные, как и само ядро. Для серьезного проекта вы можете выбрать Magento либо неплохую Java-альтернативу BroadLeaf. Если вам нужно что-то, что можно быстро освоить, попробуйте Prestashop.

EDIT:

После публикации этой статьи, она приобрела некоторую популярность, и многие успели выразить как согласие, так и несогласие с моими взглядами, что, конечно, прекрасно. Однако, в комментариях от сторонников OpenCart все, в основном, сводилось к личным оскорблениям, ругани, абсолютно технически-неполноценным идеям (вроде разъяснений о том, что повторения кода — это нормально, если код "простой") и других абсурдных аргументов (зависть к уровню доходов создателя OpenCart, и т.д, и т.д.), и, если и были предоставлены сколько-нибудь реальные ответы на поднятые вопросы, то в очень незначительном количестве (в том числе и от автора OpenCart, который, по большей части, прибегал к пустой ругани). Я бы посоветовал читателям статьи также немного всмотреться и в комментарии, чтобы получить лучшее представление не только о недостатках OpenCart, но и об атмосфере и уровне профессионализма, которые преобладают в сообществе OpenCart.

=== КОНЕЦ ПЕРЕВОДА ===

Далее идет текст переводчика на русский язык, то есть, мой. Я попытаюсь немного развить тему последнего абзаца, и также, вскользь коснусь вопроса безопасности в OpenCart.

Предлагаемые автором варианты выбора платформы e-commerce, конечно, находятся в области личных предпочтений. Я предлагаю остановиться только на проблемах той системы, которой посвящена статья. В последнем абзаце упоминается создатель OpenCart Daniel Kerr, подключившийся впоследствии к обсуждению статьи в комментариях непосредственно на TechChattr. По общему впечатлению, он был более склонен не дискутировать на технические темы, либо нивелировать их до потребительского уровня. Ниже приводится один из его комментариев.

Привет, это Daniel Kerr, владелец из OpenCart.

Вы непрофессионал! Я не знаю, над каким именно большим проектом вы работали, но то, что вы заявляете, заслуживает жалости!

Вы также забыли упомянуть, когда советовали людям использовать Magento, что она требует выделенного сервера, только для того, чтобы продать один товар! К тому же, не очень оптимизирована! Даже с кэшем OpenCart порвет ее по скорости загрузки страницы.

Каждая компания из тех, что я встречал на выставках, отказывается от Magento, потому что это раздутый кусок дерьма!

Причина того, что OpenCart — это номер один, наиболее используемое решение для электронной коммерции в таких странах как Китай и Индия, в том, что это простейшая для понимания кодовая база.

Мой самый большой конкурент, Magento, тратит миллионы долларов на рекламу, в то время как OpenCart остается №3 в мире, не платя ни пенни!

Это первая реплика Daniel Kerr в комментариях на приведенную статью. Поскольку это комментарий на статью в блоге, то, наверное, есть некоторая возможность объяснить напряженный тон ответа болезненной реакцией на журнальную критику. Но интересно не это, а ответы в тикетах на GitHub (где, собственно, и сосредоточена разработка OpenCart) на отчеты о проблемах безопасности системы. Вот Daniel отвечает в тикете с рекомендацией сменить режим шифрования и просьбой связаться в личных сообщениях:

Я рекомендую вам прекратить присылать эти отчеты! Есть разные аргументы за и против разных типов алгоритмов шифрования. Фактом является то, что сейчас система достаточно безопасна для нужд пользователей OpenCart.

Еще один ответ пользователю на сообщение об уязвимости в хешировании паролей:

Вы, по-видимому, не имеете никакого опыта разработки скриптов open source. Если имели бы, то осознали, что минимальное требование для OpenCart — PHP 5.3, поэтому использование bcrypt не имеет места.

И, наконец, последний пример — пользователям, обнаружившим XSS-уязвимость, с просьбой сообщить координаты для приватного контакта и сообщения деталей:

Используйте форум, чтобы послать мне личное сообщение. Также, это не XSS-уязвимость, пока я не скажу, что это так! Было очень много людей, которые отнимали у меня время, заставляя поверить в уязвимости.

Собственно, о чем это все. Такой ответ разработчика open source проекта пользователю, который нашел ошибку в программном коде и потратил свое время на ее изучение, а затем на написание отчета — по очевидным причинам, является абсурдом на грани бреда. Мне кажется, что если бы Linus Torvalds позволял себе так реагировать на сообщения баг-репортеров, то бренд Linux, наверное, оставался бы широко известным, но только в очень узких кругах.