Добавляем в OpenCart ORM библиотеку

При разработке проекта на opencart у меня возникает сильный дискомфорт, так как отсутствуют столь привычные вещи, к которым достаточно быстро привыкаешь. На этот раз я раскритиковал понятие моделей в данной CMS и решил интегрировать в проект какую нибудь популярную ORM.

Немного изучив исходники нашел, что opencart может работать с PDO и по этому я решил взять популярный Propel ORM, так как он так же использует PDO для своей работы.

Первое что надо сделать, это его установить, я брал 2 версию

composer require propel/propel "~2.0@dev"

Дальше нужно сделать конфиг для настройки.
Я в корне сайта сделал propel.php и сделал базовую настройку

<?php

return [
    'propel' => [
        'database' => [
            'connections' => [
                'default' => [
                    'adapter'    => 'mysql',
                    'classname'  => 'Propel\Runtime\Connection\ConnectionWrapper',
                    'dsn'        => 'mysql:host=localhost;dbname=DB_DATABASE',
                    'user'       => 'DB_USERNAME',
                    'password'   => 'DB_PASSWORD',
                    'attributes' => [],
                    'settings'   => [
                        'charset'    => 'utf8mb4',
                        'queries'    => [
                            'utf8' => 'SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci, COLLATION_CONNECTION = utf8mb4_unicode_ci, COLLATION_DATABASE = utf8mb4_unicode_ci, COLLATION_SERVER = utf8mb4_unicode_ci'
                        ]
                    ]
                ]
            ]
        ]
    ]
];

Константы не сработают по этому нужно туда добавить настройки текущего подключения как есть, но в проекте в итоге мы это исправим.

Теперь так как у нас база готовая есть, сгенерируем схему БД, она нам понадобится для генерации моделей:

vendor/propel/propel/bin/propel reverse "mysql:host=localhost;dbname=db;user=root;password=pwd"

Появится папка generated-reversed-database, а в ней файл со схемой schema.xml

Теперь сделаем модели данных, я сложил их в папку system/entity

vendor/propel/propel/bin/propel model:build --schema-dir=generated-reversed-database --output-dir=system/entity

где schema-dir пупка с сгенерированной темой и output-dir папка с кучкой свеженьких моделей.

Теперь последний шаг для приготовления propel — сгенерировать стартовый конфиг.

vendor/propel/propel/bin/propel config:convert

Получим на выходе generated-conf/config.php который я перенес в корень и переименовал как propel-config.php и вот в него уже можно войти и заменить значения настроек бд на константы опенкарта. Собственно на этом propel приготовлен и внедряем все это дело в проект.

Наши модельки нужно как то подгрузить, по этому отредактируем файл composer.json

{
  ...
  "autoload": {
    "classmap": ["system/entity/"]
  }
}

И сгенерируем файл автолоада

composer install

Тут потребуется подредачить ядро, так как куда то нужно воткнуть автолоад композера, я поставил в файл ядра system/startup.php у меня все под git и в случае обновления я увижу что моя вставка была удалена и поставлю её на место.

if (is_file(DIR_SYSTEM . '../vendor/autoload.php')) {
    require_once(DIR_SYSTEM . '../vendor/autoload.php');
}

Хотя замечу, что там есть папка для композера и его автозагрузка, но система её вынесла выше уровня, а я наткнулся на неё уже после того как установил композер в корень. Ну да ладно, вы можете поступить умнее и править ядро вам не понадобится 🙂

Теперь интегрируем все дело в Opencart. Для этого нам нужно создать новый драйвер для бд и указать его в конфиге.

// system/library/db/propel.php

/**
 * Created by PhpStorm.
 * Author: Shabalin Pavel
 * Date: 15.11.2017
 */

namespace DB;

final class Propel
{
    private $connection = null;
    /**
     * @var  \PDOStatement
     */
    private $statement = null;

    public function __construct($hostname, $username, $password, $database, $port = '3306') {
        require_once DIR_APPLICATION . '../propel-config.php';
        $this->connection = \Propel\Runtime\Propel::getConnection();
    }

    public function prepare($sql) {
        $this->statement = $this->connection->prepare($sql);
    }

    public function bindParam($parameter, $variable, $data_type = \PDO::PARAM_STR, $length = 0) {
        if ($length) {
            $this->statement->bindParam($parameter, $variable, $data_type, $length);
        } else {
            $this->statement->bindParam($parameter, $variable, $data_type);
        }
    }

    public function execute() {
        try {
            if ($this->statement && $this->statement->execute()) {
                $data = array();

                while ($row = $this->statement->fetch(\PDO::FETCH_ASSOC)) {
                    $data[] = $row;
                }

                $result = new \stdClass();
                $result->row = (isset($data[0])) ? $data[0] : array();
                $result->rows = $data;
                $result->num_rows = $this->statement->rowCount();
            }
        } catch(\PDOException $e) {
            throw new \Exception('Error: ' . $e->getMessage() . ' Error Code : ' . $e->getCode());
        }
    }

    public function query($sql, $params = array()) {
        $this->statement = $this->connection->prepare($sql);

        $result = false;

        try {
            if ($this->statement && $this->statement->execute($params)) {
                $data = array();

                while ($row = $this->statement->fetch(\PDO::FETCH_ASSOC)) {
                    $data[] = $row;
                }

                $result = new \stdClass();
                $result->row = (isset($data[0]) ? $data[0] : array());
                $result->rows = $data;
                $result->num_rows = $this->statement->rowCount();
            }
        } catch (\PDOException $e) {
            throw new \Exception('Error: ' . $e->getMessage() . ' Error Code : ' . $e->getCode() . ' <br />' . $sql);
        }

        if ($result) {
            return $result;
        } else {
            $result = new \stdClass();
            $result->row = array();
            $result->rows = array();
            $result->num_rows = 0;
            return $result;
        }
    }

    public function escape($value) {
        return str_replace(array("\\", "\0", "\n", "\r", "\x1a", "'", '"'), array("\\\\", "\\0", "\\n", "\\r", "\Z", "\'", '\"'), $value);
    }

    public function countAffected() {
        if ($this->statement) {
            return $this->statement->rowCount();
        } else {
            return 0;
        }
    }

    public function getLastId() {
        return $this->connection->lastInsertId();
    }

    public function isConnected() {
        if ($this->connection) {
            return true;
        } else {
            return false;
        }
    }

    public function __destruct() {
        $this->connection = null;
    }
}

И в конфигах указываем что наш драйвер это propel

// config.php

define('DB_DRIVER', 'propel');

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

Для чего же нужно это мне? Я не привык, что система сама решает какие данные мне нужны, а орм это удобный инструмент для работы с данными из базы, как с объектами. Такие решения конечно нельзя распространять в маркетах и на продажу, но для проектов заказчиков очень пригодится и сократит множество человекачасов и самое главное ошибок.

Опенкарт, как по мне достаточно кривоват и скорее всего я добавлю статьи по роутингу, расширениям twig и созданию шаблонов twig по феншую, а не как сейчас через пятую точку, так что следите за обновлениями 🙂

И пользуйтесь на здоровье!

Напоследок, кусочек кода с propel который проставит категорию всем товарам у которого атрибут имеет заданное значение.

protected function setCategoryByAttributeValue($category, $attribute_value)
    {
        $category = OcCategoryDescriptionQuery::create()->findOneByName($category);
        $products = OcProductAttributeQuery::create()->findByText($attribute_value);

        if ($category) {
            foreach ($products as $product) {
                $productCategory = OcProductToCategoryQuery::create('pc')
                    ->where('pc.category_id = ?', $category->getCategoryId())
                    ->where('pc.product_id = ?', $product->getProductId())
                    ->findOne();

                if (!$productCategory) {
                    $productCategory = new OcProductToCategory();
                    $productCategory->setProductId($product->getProductId());
                    $productCategory->setCategoryId($category->getCategoryId());
                    $productCategory->save();
                }
            }
        }
    }

 

Добавляем в OpenCart ORM библиотеку: 3 комментария

  1. Спасибо. Это действительно полезно когда работы и так валом, еще сырые запросы писать к БД. Значительно ускоряет разработку.

  2. Добрый день! Спасибо очень помогли настроить ORM
    У меня вопрос, какие фалы за gitinore-ить?
    Файл schema.xml ее удалить или она всегда должна стоять?
    Зачем нужен файл propel.php в корне сайта, и что он из себя представляет?

    1. Игнорить можно все за чем не нужно вести контроль версий или переносить с площадки на площадку. Можно удалить — это схема данных снятая с БД. Файл propel.php это конфиг для пропеля

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *