При разработке проекта на 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(); } } } }
Спасибо. Это действительно полезно когда работы и так валом, еще сырые запросы писать к БД. Значительно ускоряет разработку.
Добрый день! Спасибо очень помогли настроить ORM
У меня вопрос, какие фалы за gitinore-ить?
Файл schema.xml ее удалить или она всегда должна стоять?
Зачем нужен файл propel.php в корне сайта, и что он из себя представляет?
Игнорить можно все за чем не нужно вести контроль версий или переносить с площадки на площадку. Можно удалить — это схема данных снятая с БД. Файл propel.php это конфиг для пропеля