При разработке проекта на 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 это конфиг для пропеля