返回

Prestashop 控制器钩子使用及视图渲染优化

javascript

Prestashop 控制器和钩子:在控制器中处理钩子并显示视图

最近遇到一个问题:在 Prestashop 里,需要用一个自定义的表单,并且想直接在 /controllers/admin/AjaxSeoScore.php 这个控制器里处理 hookDisplayAdminProductsMainStepLeftColumnMiddle 钩子。视图文件放在 /views/templates/admin/test.tpl

原来的做法,总感觉别扭,而且看不到任何显示。 所以,来捋一捋正确的操作方式,以及如何把产品 ID 这样的参数传给控制器。

一、 问题分析:为什么看不到东西?

原代码中,在钩子函数里实例化了一个新的控制器 AdminAjaxSeoScore,然后调用了它的 display 方法。看代码的逻辑,是想通过display方法指定模板去渲染, 但实际上Prestashop模块内这样是无法直接渲染模板的。而且控制器内指定了seoscore_header.tpl作为模板文件,实际我们要求的/views/templates/admin/test.tpl,因此会导致无法正确载入需要的模板文件。

更重要的是,ModuleAdminController 类的 display() 方法通常用于整个 Admin 页面的渲染,而不是用来在钩子中显示一小块内容。 这种用法有点“大材小用”。

还有参数的传递, 也没体现。

二、 解决方案:更直接,更 Prestashop

下面给出几种解决思路和对应的代码示例,你可以根据自己的实际情况选择合适的方法。

1. 方案一:直接在钩子函数中渲染模板

最简单粗暴的方式,就是直接在钩子函数里搞定模板渲染。不用绕到控制器。

  • 原理: Prestashop 的钩子函数本来就可以返回 HTML 内容。直接用 $this->context->smarty->fetch() 获取模板内容,再返回,就完事了。

  • 代码示例:

<?php

class YourModuleName extends Module
{
    // ... 模块的其他代码 ...
    
    public function hookDisplayAdminProductsMainStepLeftColumnMiddle($params)
    {
        // 获取产品 ID
        $product_id = (int)Tools::getValue('id_product');

        // 将变量分配给 Smarty
        $this->context->smarty->assign(array(
            'product_id' => $product_id,
            'my_var' => 'Hello from hook!', 
        ));

        // 渲染模板并返回
        return $this->context->smarty->fetch($this->getLocalPath().'views/templates/admin/test.tpl');
    }
}
  • 注意事项: $this->getLocalPath() 能拿到模块的绝对路径,这样拼接模板路径更稳妥。Tools::getValue('id_product') 可以从请求里拿到产品 ID。
  • 安全提示: (int)强制ID转换成int类型。

2. 方案二:利用模块的 display 方法

如果逻辑比较复杂,或者你就是想用控制器的方法,也可以利用模块自带的 display 方法,但要稍微改造一下。

  • 原理: Module 类有个 display 方法。我们可以在钩子函数里调用模块实例的 display(__FILE__, 'template.tpl'), 第一个参数通常是__FILE__, 第二个是你模板文件名(相对于模块目录)。这样既能用到控制器中的函数进行逻辑处理, 又不失灵活性.

  • 代码示例:

<?php
// 模块主文件
class YourModuleName extends Module
{
   // ... 其他代码 ...

    public function hookDisplayAdminProductsMainStepLeftColumnMiddle($params)
    {
       $controller = new AdminAjaxSeoScoreController();
        
       return  $controller->run($params);
    }
}

// 控制器文件 /controllers/admin/AdminAjaxSeoScoreController.php
class AdminAjaxSeoScoreController extends ModuleAdminController {
    public $bootstrap = true;
        
    public function run($params)
    {   
        // 获取产品ID.
        $product_id = (int)Tools::getValue('id_product');
        
        $this->context->smarty->assign(array(
            'product_id' =>  $product_id,            
             'my_var' => 'Hello from controller!',
        ));
            
         // 渲染模板.
        return $this->module->display($this->module->getLocalPath(),'views/templates/admin/test.tpl');
    }   
}

  • 进阶: 你甚至可以创建一个自定义的控制器基类,把一些通用的逻辑放进去,让代码更整洁。

3. 方案三: AJAX 请求(如果需要异步)

如果你的表单提交后不需要刷新整个页面,而是用 AJAX 方式,那么处理方式又不一样了。

  • 原理: 前端用 AJAX 发送请求到你的控制器,控制器处理完数据后,返回 JSON 格式的数据。前端再根据返回的数据更新页面。

  • 代码示例:

<?php
// 模块主文件
class YourModuleName extends Module
{
    // ... 其他代码 ...

     public function hookDisplayAdminProductsMainStepLeftColumnMiddle($params)
    {        
       $this->context->smarty->assign([
             'ajax_url' =>  $this->context->link->getAdminLink('AdminAjaxSeoScore')
        ]);
       return  $this->display(__FILE__, 'views/templates/admin/test.tpl'); //显示一个包含ajax触发的控件.
    }
}
//  /controllers/admin/AdminAjaxSeoScoreController.php
class AdminAjaxSeoScoreController extends ModuleAdminController
{
     public $bootstrap = true; // 如果你用了 Bootstrap 样式

      public function postProcess()
    {
         // 处理 AJAX 请求
          if(Tools::isSubmit('submitMyForm'))
          {
                 $product_id = (int)Tools::getValue('id_product');
                // ... 各种业务逻辑 ...
                // $result  例如数据库查询后的结果。
                $result = ['status'=>'success','data'=>"处理结果: ".$product_id];                 
                die(json_encode($result));  // 返回 JSON             
           }   
    }
}

  • 前端部分 (test.tpl):
<!-- views/templates/admin/test.tpl  -->
<div>
    <!-- 你的表单 -->
     <input type="hidden" id="ajax_url_for_seo" value="{$ajax_url}">
    <form id="my-form">
       产品ID: <input type='text' name="id_product"><br/>
        <button type="submit">提交</button>
    </form>
    <div id="result"></div>
</div>

<script>
  $(document).ready(function() {
    $('#my-form').on('submit', function(e) {
      e.preventDefault(); // 阻止表单默认提交

       // 构造FormData。
        let formData = new FormData(this); 

      $.ajax({
        url: $('#ajax_url_for_seo').val(), // 控制器的 URL
        type: 'POST',
        data: formData, // 把整个表单的数据传过去
        processData: false, // tell jQuery not to process the data
        contentType: false, // tell jQuery not to set contentType
        success: function(response) {
          // 处理成功返回的数据
          $('#result').html('成功:' + response.data);
          
        },
        error: function() {
          $('#result').html('出错了!');
        }
      });
    });
  });
</script>
  • 安全提示: 一定要对用户输入的数据进行验证和过滤,防止安全漏洞! Prestashop内置了非常多好用的函数例如: Validate::isCleanHtml(), pSQL() 等。
    一定要对传递的ID类数值进行强类型转换!

三、总结

具体选择哪种方法, 取决于你的实际需求。三种方法从简单到复杂都有。一般来说,能用方法一就尽量用方法一, 更简洁。记住一点: Prestashop开发的原则是,尽量用它提供的机制, 少自己造轮子。

如果功能更复杂, 交互多,用 AJAX 也挺好,体验更流畅。