PrestaShop 9 въвежда значителни промени в архитектурата и стандартите за разработка на модули. Тази статия ще ви въведе в новите особености и ще ви помогне да адаптирате вашия workflow към най-новата версия.
Кратко резюме
✅ Нови изисквания: PHP 8.1+, строго типизиране, Symfony DI
✅ Ключови промени: Нова файлова структура, нови hook-ове
✅ Съвместимост: Как да поддържате PS 8.x паралелно
✅ Практически пример: Готов за употреба модул с код
Ключови промени в архитектурата на модулите
1. Нова структура на файловете
PrestaShop 9 въвежда по-стриктна организация на файловете в модулите:
my_module/
├── config/
│ └── services.yml
├── src/
│ ├── Controller/
│ ├── Entity/
│ ├── Repository/
│ └── Service/
├── templates/
│ ├── admin/
│ └── front/
├── translations/
├── views/
│ ├── css/
│ └── js/
├── my_module.php
└── composer.json
2. Задължителна употреба на Symfony компоненти
Модулите в PS9 трябва да използват Symfony dependency injection контейнера:
// config/services.yml
services:
my_module.service.product_manager:
class: MyModule\Service\ProductManager
arguments:
- '@doctrine.orm.entity_manager'
- '@prestashop.core.admin.url_generator'
3. Типизирани свойства и методи
Новите стандарти изискват строго типизиране:
<?php
declare(strict_types=1);
namespace MyModule\Service;
use PrestaShop\PrestaShop\Core\Domain\Product\ValueObject\ProductId;
class ProductManager
{
public function __construct(
private EntityManagerInterface $entityManager,
private AdminUrlGenerator $urlGenerator
) {
}
public function getProductById(int $productId): ?Product
{
return $this->entityManager->getRepository(Product::class)
->find(new ProductId($productId));
}
}
Новите Hook-ове в PrestaShop 9
PrestaShop 9 въвежда нови hook-ове, които предоставят по-добър контрол над функционалността:
Административни Hook-ове
// В главния клас на модула
public function hookActionAdminProductsControllerSaveBefore($params): void
{
$product = $params['product'];
// Валидация или модификация преди запазване
if (!$this->validateCustomFields($product)) {
throw new PrestaShopException('Невалидни данни');
}
}
public function hookDisplayAdminProductsMainStepLeftColumnMiddle($params): string
{
$productId = $params['id_product'];
$this->context->smarty->assign([
'custom_data' => $this->getCustomProductData($productId),
'module_path' => $this->_path,
]);
return $this->display(__FILE__, 'views/templates/admin/product_form.tpl');
}
Front-end Hook-ове
public function hookDisplayProductAdditionalInfo($params): string
{
$product = $params['product'];
return $this->display(__FILE__, 'views/templates/hook/product_additional_info.tpl');
}
public function hookActionFrontControllerSetMedia(): void
{
// Регистриране на CSS и JS файлове
$this->context->controller->registerStylesheet(
'module-mystyle',
'modules/' . $this->name . '/views/css/front.css'
);
$this->context->controller->registerJavascript(
'module-myjs',
'modules/' . $this->name . '/views/js/front.js'
);
}
Съвместимост с предишни версии
За да осигурите съвместимост с PS 1.7.x и PS 8.x:
1. Проверка на версията
public function install(): bool
{
if (version_compare(_PS_VERSION_, '9.0.0', '>=')) {
return $this->installForPS9();
}
return $this->installForLegacy();
}
private function installForPS9(): bool
{
return parent::install() &&
$this->registerHook('actionAdminProductsControllerSaveBefore') &&
$this->registerHook('displayProductAdditionalInfo');
}
private function installForLegacy(): bool
{
return parent::install() &&
$this->registerHook('actionProductSave') &&
$this->registerHook('displayProductTab');
}
2. Условни hook методи
public function hookActionProductSave($params): void
{
// Логика за PS 1.7.x и PS 8.x
if (version_compare(_PS_VERSION_, '9.0.0', '<')) {
$this->handleLegacyProductSave($params);
return;
}
// Логика за PS 9+
$this->handlePS9ProductSave($params);
}
Примерен модул с пълен код
Ето пример за прост модул, който добавя custom поле към продуктите:
Основен клас (customproductfields.php)
<?php
declare(strict_types=1);
if (!defined('_PS_VERSION_')) {
exit;
}
require_once __DIR__ . '/vendor/autoload.php';
use CustomProductFields\Service\ProductFieldsManager;
class CustomProductFields extends Module
{
public function __construct()
{
$this->name = 'customproductfields';
$this->tab = 'administration';
$this->version = '1.0.0';
$this->author = 'Presta.bg';
$this->need_instance = 0;
$this->ps_versions_compliancy = ['min' => '8.0', 'max' => _PS_VERSION_];
parent::__construct();
$this->displayName = $this->l('Custom Product Fields');
$this->description = $this->l('Добавя персонализирани полета към продуктите');
$this->confirmUninstall = $this->l('Сигурни ли сте, че искате да деинсталирате модула?');
}
public function install(): bool
{
return parent::install() &&
$this->installDatabase() &&
$this->registerHook('displayAdminProductsMainStepLeftColumnMiddle') &&
$this->registerHook('actionAdminProductsControllerSaveBefore') &&
$this->registerHook('displayProductAdditionalInfo');
}
public function uninstall(): bool
{
return parent::uninstall() && $this->uninstallDatabase();
}
private function installDatabase(): bool
{
$sql = 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'custom_product_fields` (
`id_product` int(10) unsigned NOT NULL,
`custom_field_1` text,
`custom_field_2` text,
PRIMARY KEY (`id_product`)
) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8;';
return Db::getInstance()->execute($sql);
}
private function uninstallDatabase(): bool
{
$sql = 'DROP TABLE IF EXISTS `' . _DB_PREFIX_ . 'custom_product_fields`';
return Db::getInstance()->execute($sql);
}
public function hookDisplayAdminProductsMainStepLeftColumnMiddle($params): string
{
$productId = (int) $params['id_product'];
$customData = $this->getCustomFields($productId);
$this->context->smarty->assign([
'custom_field_1' => $customData['custom_field_1'] ?? '',
'custom_field_2' => $customData['custom_field_2'] ?? '',
]);
return $this->display(__FILE__, 'views/templates/admin/product_form.tpl');
}
public function hookActionAdminProductsControllerSaveBefore($params): void
{
$productId = (int) $params['id_product'];
$customField1 = Tools::getValue('custom_field_1');
$customField2 = Tools::getValue('custom_field_2');
$this->saveCustomFields($productId, $customField1, $customField2);
}
private function getCustomFields(int $productId): array
{
$sql = 'SELECT * FROM `' . _DB_PREFIX_ . 'custom_product_fields`
WHERE `id_product` = ' . (int) $productId;
$result = Db::getInstance()->getRow($sql);
return $result ?: [];
}
private function saveCustomFields(int $productId, string $field1, string $field2): bool
{
$sql = 'INSERT INTO `' . _DB_PREFIX_ . 'custom_product_fields`
(`id_product`, `custom_field_1`, `custom_field_2`)
VALUES (' . (int) $productId . ', "' . pSQL($field1) . '", "' . pSQL($field2) . '")
ON DUPLICATE KEY UPDATE
`custom_field_1` = "' . pSQL($field1) . '",
`custom_field_2` = "' . pSQL($field2) . '"';
return Db::getInstance()->execute($sql);
}
}
Template за администрацията (views/templates/admin/product_form.tpl)
<div class="form-group">
<label class="control-label col-lg-3">
{l s='Custom Field 1' mod='customproductfields'}
</label>
<div class="col-lg-9">
<input type="text"
name="custom_field_1"
value="{$custom_field_1|escape:'html':'UTF-8'}"
class="form-control" />
</div>
</div>
<div class="form-group">
<label class="control-label col-lg-3">
{l s='Custom Field 2' mod='customproductfields'}
</label>
<div class="col-lg-9">
<textarea name="custom_field_2"
class="form-control"
rows="3">{$custom_field_2|escape:'html':'UTF-8'}</textarea>
</div>
</div>
Заключение
PrestaShop 9 носи значителни подобрения в архитектурата на модулите, които правят разработката по-структурирана и модерна. Ключовите промени включват:
- Задължителна употреба на типизирани методи и свойства
- Интеграция със Symfony компоненти
- Нови и по-гъвкави hook-ове
- По-добра организация на файловете
При разработка на нови модули препоръчваме да следвате новите стандарти, като същевременно поддържате съвместимост с предишни версии за по-широко покритие на пазара.
За по-подробна информация относно специфични API промени, консултирайте се с официалната документация на PrestaShop 9.