关于 prestashop-module-development
prestashop-module-development 是一项指导 PrestaShop 模块新建与现代化改造的开发技能,面向模块开发者。内容涵盖 Symfony 架构、配置、数据库、前台集成和安全实践,并强调职责划分与现代模式。
主要功能
- 01搭建现代模块结构
- 02实现 Symfony 配置页面
- 03使用 Doctrine 管理实体
- 04集成 Hooks 与前台功能
- 05应用安全与输入验证规范
适用场景
- 01创建 PrestaShop 新模块
- 02将旧模块改造为现代架构
- 03开发管理配置与列表页面
- 04添加数据库实体和批处理命令
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jeffsenso/ prestashop-skills --skill prestashop-module-development安装后在 Claude Code 中输入/prestashop-module-development确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
CHANGELOG.mdps9-core-ai/bin/generate-ai-index.shps9-core-ai/Component/BackOfficeHelp/CONTEXT.mdps9-core-ai/Component/Behat/CONTEXT.mdps9-core-ai/Component/Configuration/CONTEXT.mdps9-core-ai/Component/Console/CONTEXT.mdps9-core-ai/Component/Context/CONTEXT.mdps9-core-ai/Component/ContextStateManager/CONTEXT.mdps9-core-ai/Component/Controller/CONTEXT.mdps9-core-ai/Component/Cookie/CONTEXT.mdps9-core-ai/Component/CQRS/CONTEXT.mdps9-core-ai/Component/Database/CONTEXT.mdps9-core-ai/Component/Export/CONTEXT.mdps9-core-ai/Component/FacetedSearch/CONTEXT.mdps9-core-ai/Component/Forms/CONTEXT.mdps9-core-ai/Component/Grid/CONTEXT.mdps9-core-ai/Component/Hook/CONTEXT.mdps9-core-ai/Component/Import/CONTEXT.mdps9-core-ai/Component/Javascript/CONTEXT.mdps9-core-ai/Component/Link/CONTEXT.mdps9-core-ai/Component/Locale/CONTEXT.mdps9-core-ai/Component/MailTemplate/CONTEXT.mdps9-core-ai/Component/Migration/CONTEXT.mdps9-core-ai/Component/Migration/skills/legacy-to-symfony-migration/step-00-audit.mdstep-01-feature-flag.mdstep-02-domain-layer.mdstep-03-adapter-layer.mdstep-04-behat-tests.mdstep-05-listing-page.mdstep-06-form-page.md详细介绍
PrestaShop 模块开发
适用场景
在以下 PrestaShop 模块开发任务中使用此技能:
- 使用现代架构(Symfony 控制器、服务、实体)创建新模块
- 重构旧版模块,使其采用现代 PrestaShop 模式
- 实现钩子、操作和事件监听器
- 添加配置页面(采用现代 Symfony 表单方式)
- 创建前台功能和小组件
- 设置数据库实体和迁移
- 实施安全措施(CSRF、输入验证、防止 SQL 注入)
- 添加多语言支持和翻译
- 将旧版代码模式(HelperForm、jQuery UI sortable、ObjectModel)转换为现代替代方案
- 使用 PrestaShop Grid 系统构建列表页面(筛选、分页、切换、拖放排序)
- 创建和管理 PrestaShop 购物车规则(代金券、折扣码、促销码)
- 实现用于后台任务和批量操作的 Symfony 控制台命令
所需输入
- PrestaShop 版本(现代开发建议使用 8.x/9.x)
- 模块范围和功能需求
- 现有模块路径(如果要更新旧版代码)
- 数据库架构需求(如适用)
- 前台集成需求(钩子、小组件、页面)
- 配置需求(设置、管理界面)
- 多语言需求和支持的语言
流程
0) 项目结构和命名空间命名
阅读:references/module-structure.md
关键规则:
- 根据模块名称前缀推导 PSR-4 命名空间 — 切勿使用
PrestaShop\Module\ - 使用 PrestaShop 模块生成器搭建新模块的脚手架
1) 主模块类和安装程序
阅读:references/module-class-and-installer.md
强制模式:
- install() 方法:必须始终先执行
if (!parent::install()) { return false; },然后再委托给安装程序 - uninstall() 方法:必须始终先调用安装程序,然后通过
&&链接调用parent::uninstall():return $installer->uninstall() && parent::uninstall(); - 钩子:在 Installer 中定义类属性数组
private array $hooks = [...],使用$module->registerHook($this->hooks)注册 - 选项卡:以结构化数据的形式定义类属性数组
private array $tabs = [...],包含:name、class_name、label、parent_class_name、visible - 父级分组选项卡:先检查是否存在;若不存在,则使用
array_unshift($this->tabs, $parentTab)将其添加到选项卡数组开头 - 不要使用 getTabs():绝不在模块类中添加
getTabs()方法 — 所有选项卡管理仅在 Installer 中进行
关键规则:
- 始终在
_PS_VERSION_检查后添加require_once __DIR__ . '/vendor/autoload.php'; - 切勿直接在
install()中注册钩子、查询数据库或调用Configuration::— 应委托给src/Install/Installer.php getContent()必须仅使用现代 Symfony 路由器重定向到 Symfony 路由:$router = $this->get('router'); $route = $router->generate('route_name'); Tools::redirectAdmin($route);— 绝不要使用$this->context->link->getAdminLink()- 主模块类中不得包含 SQL — 所有数据库访问(包括
hookActionShopDataDuplication等钩子,以及getWidgetVariables等小组件方法中的访问)都必须通过$this->get('service.id')委托给 Repository 或 Manager 类 - 服务访问:在管理端上下文中使用
$this->has()并检查是否为 null;在前台上下文中使用普通的$this->get()并检查是否为 null。绝不要使用ContainerFinder— 没有必要。请参阅references/module-class-and-installer.md→ Guard patterns 部分。
1b)配置变量管理
阅读:references/configuration-management.md
强制模式:基于 YAML 的配置,并使用 ConfigurationManager 服务
关键规则:
- 绝不要在业务逻辑中直接使用
Configuration::updateValue()或Configuration::get() - 始终创建
/config_data/module.yml,定义所有配置键和默认值(唯一真实来源) - 始终使用
ConfigurationInstaller服务执行安装/卸载操作 - 始终使用
ConfigurationManager服务读取配置(从数据库或 YAML 回退读取) - 结构:
config_data/module.yml— 所有键及默认值src/Install/ConfigurationInstaller.php— 安装/卸载配置值src/Configuration/ConfigurationManager.php— 读取配置(数据库或 YAML)config/components/configuration/configuration_manager.yml— 注册服务
- 优势:唯一真实来源、类型安全的访问方式、无需硬编码键、易于测试和维护
2)现代配置页面
阅读:references/configuration-page.md
强制模式:使用构造函数依赖注入的 FormHandler
关键规则:
- 不要使用
HelperForm— 使用 Symfony 表单组件和FrameworkBundleAdminController - 不要在控制器中通过
$this->get()访问服务 — 控制器的服务定位器功能有限。改用 FormHandler 和构造函数依赖注入 - 五个类:
DataConfiguration、FormDataProvider、FormType、FormHandler(PrestaShop Core)、Controller(使用 DI) - 在
config/components/configuration/services.yml(由config/admin/services.yml导入)和config/routes.yml中配置所有内容 - FormHandler:使用
PrestaShop\PrestaShop\Core\Form\Handler服务封装表单创建、验证和保存 - 控制器注册:必须注册为
public: true服务,并添加controller.service_arguments标签 - FormType 注册:必须设置
parent: 'form.type.translatable.aware'和tags: [{ name: form.type }] - Twig 模板的表单包装:始终在
{% extends %}之前使用{% form_theme configurationForm '@PrestaShop/Admin/TwigTemplateForm/prestashop_ui_kit.html.twig' %};将{{ form_start() }}放在<div class="card">之前,并将{{ form_end() }}放在</div>之后 — 表单标签必须在相同嵌套层级包裹整个卡片结构,以避免 HTML 损坏 - 控制器模式:
private FormHandlerInterface $formHandler; public function __construct(FormHandlerInterface $formHandler) { $this->formHandler = $formHandler; } public function indexAction(Request $request): Response { $form = $this->formHandler->getForm(); // ... handle request and save via $this->formHandler->save() } - CategoryChoiceTreeType:PrestaShop 提供的层级分类选择树表单类型。需要进行特定设置:
- 有效的表单选项:多选时使用
'multiple' => true;可选时使用'required' => false。不要使用'expanded'选项 — 此类型不支持该选项 - 模板要求:在
{% extends %}指令之前添加{% form_theme configurationForm '@PrestaShop/Admin/TwigTemplateForm/prestashop_ui_kit.html.twig' %};在{% block javascripts %}中包含 JS bundle - JavaScript 初始化:创建
views/js/mymodule.form.bundle.js,其中包含window.prestashop.component.initComponents(['ChoiceTree'])和new window.prestashop.component.ChoiceTree('#form_categories').enableAutoCheckChildren()— 选择器必须与 HTMLid属性匹配(通常为form_<field_name>) - 表单数据处理:返回分类 ID 数组;通过
implode(',', $data['categories'])存储为逗号分隔的字符串;读取时使用array_filter(array_map('intval', explode(',', $storedValue)))并转换回数组 - 常见问题:树渲染为普通复选框 → 缺少表单主题;展开/折叠无效 → 缺少 JS 初始化;出现 "ChoiceTree is not defined" → 使用
window.prestashop.component.ChoiceTree,不要使用普通的ChoiceTree
- 有效的表单选项:多选时使用
3)数据库操作与实体
阅读:references/database-and-entities.md
对于使用 Grid 的可翻译实体:阅读 references/entity-doctrine.md
- 对于任何具有 Grid 列表页或可翻译字段的实体,始终使用 Doctrine ORM(Entity + LangEntity + Repository + Manager)
- ObjectModel 已过时;不要在新模块或现代化改造的模块中使用
- 实体类名 = 不带
_DB_PREFIX_的表名——PS 会全局添加前缀;使用不带name=参数的@ORM\Table() - 表名使用一致的公司或模块前缀(例如
prefix_mymodule_items、prefix_mymodule_items_lang),以便将相关表归在一起并避免与 PS 核心表冲突 - Lang 实体的属性类型必须与数据库列的可空性一致:声明为
NOT NULL DEFAULT ''的列使用string $field = '';声明为NULL的列使用?string $field = null - 对于用户未填写的语言,
TranslatableType会返回null——在 Manager 中,始终将null转换为'',用于NOT NULL字符串字段:$row->setName((string) ($value ?? ''))。绝不要将原始表单值直接传给NOT NULL列对应的 setter - 不要为表命名创建
MetadataListener或 Doctrine 事件监听器 - 始终清理原始 DBAL SQL:使用
(int)转换 ID,并使用绑定参数 - Repository 和 Manager 类之外,不得使用原始 SQL(
Db::getInstance()、pSQL()、拼接_DB_PREFIX_字符串)。 此规则适用于所有位置:主模块类、Installer、FixturesInstaller、钩子和小组件方法。唯一例外是 Installer 的 SQL 架构查询(CREATE TABLE、DROP TABLE),因为没有对应的 Repository 实现。 - FixturesInstaller 必须使用
Db::getInstance()执行原始 SQL——在pr:moSymfony 控制台上下文中,SymfonyContainer::getInstance()会返回null(从未设置global $kernel)。所有 Doctrine ORM 调用在安装时都会静默失效。参见references/module-class-and-installer.md→ FixturesInstaller 部分。 Db::getValue()会在内部追加LIMIT 1——传给它的 SQL 字符串中绝不要写LIMIT 1;否则会导致 MariaDB 语法错误。
服务拆分与组件架构
阅读:references/services-split.md
关键规则:
- 不要创建
config/services.yml——只需要config/admin/services.yml和config/front/services.yml;根目录下的config/services.yml从来都不是必需的,也不应存在 config/admin/services.yml仅由后台内核加载——导入../common.yml和后台组件(绝不要在没有../前缀的情况下使用common.yml)- Repository 服务只能放在
config/common.yml的组件中(Doctrine 层级,不依赖PrestaShopBundle) - 所有依赖
PrestaShopBundle的服务都放在config/admin/services.yml中 - 始终在
config/components/下拆分为组件子文件夹——绝不要使用单一的扁平services.yml - 只有通过
$this->get('service.id')访问的服务才需要public: true——对每个此类服务显式声明;所有其他服务保持私有(省略public:)。避免将_defaults: public: true用作全局设置:这违反 Symfony 最佳实践,并且与使用parent:的服务搭配时,会在 PrestaShop 的 Symfony 版本中导致致命错误 - 使用
parent:且需要公开访问的服务,必须在服务定义中显式声明public: true——设置parent时无法从_defaults继承public(PrestaShop 的 Symfony 版本对此有强制要求) - 绝不要让
config/services.yml指向admin/services.yml——这会将仅限后台的服务加载到前台内核,导致容器编译失败,并使所有$this->get()调用静默失败
4)安全(必需)
阅读:references/security.md
- CSRF:Symfony 表单会自动处理;对于原始 AJAX 端点,需手动验证
- SQL 注入:对每个值使用
(int)+pSQL(),或使用DbQuery构建器 - 文件上传:将完整的、兼容
$_FILES的数组(包括type、size、error)传给ImageManager::validateUpload()
5)Hook 与前台集成
阅读:references/hooks-and-front-office.md
- 重要:注册前台 CSS/JS 时,始终使用
actionFrontControllerSetMediahook,绝不要使用header或displayHeader——在该 hook 中使用$this->context->controller->registerStylesheet()和->registerJavascript() - 在
Installer中注册 hooks,不要直接在install()中注册 - 实现
WidgetInterface时,不要定义显式的 hook 方法,例如仅调用renderWidget()的hookDisplayHome()——PrestaShop 会自动为所有已注册的显示 hook 调用renderWidget() - 对于前台 Smarty 模板:直接在模板中使用
{l s='Text' d='Modules.Modulename.Front'},绝不要从getWidgetVariables()将预翻译字符串作为 Smarty 变量传入——只传递动态数据(URL、ID 等),不要传递静态的可翻译 UI 文本
5b)主题模板注入(安装时调用 widget)
阅读:references/theme-template-injection.md
- PS8 不支持模块主题覆盖——应改用基于标记的文件补丁
- 双类设计:
ThemeTemplateInjector(服务,可复用)+ThemeTemplateInstaller(安装协调器) - 安装时绝不要使用
Theme::getThemes()——这是一个旧版类,在 Symfony 控制台中不会自动加载;改用scandir(_PS_ALL_THEMES_DIR_) - 将安装/卸载调用包装在 try/catch 中并返回
true——主题注入绝不能阻止模块安装
6)翻译
阅读:references/translations.md
FrameworkBundleAdminController::trans()的签名是trans($id, $domain, $parameters = [])——与 Symfony 的参数顺序不同——务必调用$this->trans('Text', 'Modules.Mymodule.Admin')(不要使用$this->trans('Text', [], 'Domain')——将[]作为 domain、将字符串作为$parameters传入会导致致命类型错误)- 在 Twig 中使用
'Text'|trans({}, 'Modules.Mymodule.Admin') - 在模块类中声明
isUsingNewTranslationSystem(): true
7)旧版代码转换
阅读:references/legacy-conversion.md
常见转换:HelperForm → Symfony form,jQuery UI sortable → Grid PositionColumn,ModuleAdminController → FrameworkBundleAdminController。
8)服务与依赖注入
阅读:references/services-and-di.md
重要:不要在服务/控制器中使用旧版静态调用:
- ❌
Context::getContext()——改为注入$context: "@=service('prestashop.adapter.legacy.context').getContext()" - ❌
Configuration::get()/updateValue()——改为注入@prestashop.adapter.legacy.configuration - ❌
Context::getContext()->getTranslator()——改为注入@translator
关键规则:
- 在
config/components/子目录中定义服务(由config/admin/services.yml导入) - 在 Symfony 控制器中使用
$this->get('service.id') - 对计算得出的构造函数参数(context、language ID、shop ID)使用 Expression Language(
@=) - 始终通过构造函数注入依赖项,绝不要使用静态访问器
Symfony 控制台命令
阅读:references/services-and-di.md → Symfony 控制台命令部分
重要:命令必须在 config/admin/services.yml 中注册(不要在 common.yml 或 front/services.yml 中注册):
# config/admin/services.yml
mymodule.command.my_command:
class: Vendor\MyModule\Command\MyCommand
arguments: ["@mymodule.service.my_service"]
tags:
- { name: console.command, command: modulename:action }
关键规则:
- 命令命名:采用
modulename:action格式(例如,wsautocartrules:create、ws_keepdblight:cleandb) - 返回码:成功时为
0,失败时为1(不要使用Command::SUCCESS/FAILURE常量) - 使用
SymfonyStyle输出丰富的控制台内容(表格、进度条、样式化消息) - 为批量操作添加
--limit选项,以便使用小型数据集进行测试 - 始终通过构造函数注入服务,绝不要使用静态调用或
$this->get()
9) 购物车规则与优惠券
阅读:references/cart-rules.md
以编程方式创建和管理 PrestaShop 购物车规则(折扣优惠券)的完整指南:
关键模式:
- 代码生成:使用字符集
123456789ABCDEFGHIJKLMNPQRSTUVWXYZ(不包含 O/0,以免混淆;与 PrestaShop admin.js 相同) - 唯一性检查:创建前使用
CartRule::getIdByCode($code)验证 - 多语言名称:通过
Language::getLanguages(true)为所有启用的语言设置name - 客户限制:使用
$cartRule->id_customer创建特定客户的优惠券 - 日期范围:
date_from和date_to均为必填项(格式:Y-m-d H:i:s) - 折扣类型:
reduction_percent、reduction_amount、free_shipping或gift_product - 必填字段:
quantity、quantity_per_user、priority、highlight、partial_use、active - 限制条件:默认均为
false(无任何限制);根据需要设置具体限制
文档中介绍的常见用例:
- 针对特定客户的百分比折扣优惠券
- 自动应用购物车规则(无需代码)
- 带高亮显示的限时闪购
- 免运费优惠券
- 赠品优惠券
10) 网格系统(支持拖放排序的列表页)
阅读:references/grid-system.md
使用 PS Grid 系统构建 CRUD 列表页的完整模式:
GridDefinitionFactory— 列(PositionColumn、ToggleColumn、ActionColumn)、筛选器、行操作QueryBuilder— 支持排序、分页和筛选的 Doctrine DBAL 查询Filters— 默认排序和限制设置config/components/grid/中的 5 个服务定义(factory、query、data、grid、position)+ 1 个 Twig FilesystemLoader- Twig FilesystemLoader 路径为
%kernel.project_dir%/modules/mymodule/views—%kernel.project_dir%是 PS 根目录(app/的父目录),因此modules/是其直接子目录。切勿使用%kernel.project_dir%/../modules/ routes.yml中的 4 个路由(index、search、toggle、update-position)- 4 个控制器操作(
indexAction、searchAction、toggleStatus、updatePositionAction) - 通用网格脚本
scripts/grid.bundle.js复制为views/js/{modulename}.bundle.js— 不做编辑,不设置网格 ID(核心window.prestashop.componentGrid + 扩展,PS 8.0+ / 9.x)
验证
- 模块安装时没有 PHP 错误:
php bin/console pr:mo install mymodule - 配置能正确保存,并经过适当验证
- 前台功能显示正常且运行正常
- 翻译在所有已配置的语言中均可正常使用
校验
AI agent 规则——绝不可跳过任何一个步骤。完整说明请阅读
references/validation.md。
步骤 1 — lotr(在模块根目录运行)
vendor/websenso/prestashop-module-devtools/bin/lotr
预期结果:🎉 All commands completed successfully! Executed: 6/6
步骤 2 — 安装测试(在 PS 根目录运行)
php bin/console pr:mo install mymodule
预期结果:L'action Install sur le module … a réussi.
失败情况 / 调试
阅读:references/debugging.md
常见故障领域:
references/debugging.md— 所有症状/原因/修复表(安装、配置页面、Grid、InputBag、ImageManager、lotr 步骤)
PrestaShop 9 核心文档
以下文件随此技能一同打包在 ps9-core-ai/ 目录中。
阅读这些文件,深入了解 PS9 核心架构、约定和模式。
ps9-core-ai/CONTEXT.md— PS9 代码库的 AI 根上下文:项目级编码 标准、架构层(Core/Adapter/Bundle/Legacy)、CQRS 模式、分支策略,以及领域和组件上下文的完整索引。ps9-core-ai/STRUCTURE.md—.ai/文件夹的架构:上下文、 技能和指针文件的组织方式,以及 AI 工具如何发现它们。
技能维护者须知:这些文件是来自官方 PrestaShop 仓库的静态快照。若要更新,技能维护者可以在模块目录中运行
lotr --install。这不是运行时操作——这些文件已预先打包,并与技能一同进行版本控制。
Steering
如果你的项目或组织定义了 steering 层(用于编码标准、架构约定和项目专属覆盖规则的分层上下文规则),请在开始任何任务前加载 steering 文件。
查找解析器——按以下顺序搜索:
- 模块根目录中的
steering/resolver.md(自定义安装) vendor/websenso/prestashop-module-devtools/steering/resolver.md——当 devtools 作为 Composer 包安装时的规范路径(与提供vendor/websenso/prestashop-module-devtools/bin/lotr的包相同)- 任意
vendor/*/*/steering/resolver.md——扫描所有 vendor 子目录,查找steering/resolver.md文件(使用找到的第一个匹配项)
如果不存在,则静默跳过 steering,仅应用技能默认规则。
典型的 steering 结构(路径相对于找到解析器的位置):
steering/resolver.md ← load order and conflict rules
steering/company/ ← organisation-wide standards
steering/languages/php/coding-standards.md ← PHP conventions
steering/frameworks/prestashop/ ← PrestaShop-specific rules
按优先级从低到高加载 steering 文件(公司 → 语言 → 框架 → 项目)。后续层级会覆盖较早的层级。
升级处理
- PrestaShop 9 模块创建
- 模块最佳实践
- 官方示例模块
- demosymfonyform — Symfony 表单配置规范页面
- 模块验证器
- PrestaShop 开发者 Slack