四、前端开发

到目前为止,我们主要关注 Magento 背后的理论及其架构,并熟悉日常 Magento 开发的常见和重要概念。

在本章中,我们将通过为前端逐步构建一个 Magento 扩展来实际使用我们迄今为止所获得的技能和知识。我们将构建一个功能齐全的礼品注册扩展。

延伸 Magento

在开始构建扩展之前,让我们定义一个示例场景和扩展范围。通过这种方式,我们将清楚地知道我们正在建设什么,更重要的是,我们没有建设什么。

情景

我们的场景很简单;我们希望扩展 Magento,允许客户创建礼品登记列表,并与朋友和家人共享。客户应该能够创建多个礼品登记处,并指定这些礼品登记处的收件人。

礼品登记处将保存以下信息:

  • 事件类型
  • 事件名称
  • 活动日期
  • 活动地点
  • 产品清单

特征

请看以下功能:

  • 商店管理员可以定义多种事件类型(生日、婚礼和礼品登记)
  • 创建事件并为每个事件分配多个礼品登记列表
  • 客户可以从购物车、愿望列表或直接从产品页面向其注册中心添加产品
  • 客户可以有多个礼品登记处
  • 人们可以通过电子邮件和/或直接链接与朋友和家人共享他们的注册信息
  • 朋友和家人可以从礼品登记处购买这些物品

进一步改进

以下是一个可能的功能列表,这些功能由于其复杂性或社交媒体的 API 和社交媒体平台的数量不断变化而被排除在本示例扩展之外,但对于希望进一步扩展本模块的读者来说,它们仍然是一个很好的挑战:

  • 社交媒体整合
  • 注册表可以跟踪每个注册表项的请求和完成数量
  • 指定多个不同的注册表所有者
  • 交付至注册表所有者地址

你好,马根托

在前面的章节中,我们了解了 Magento 代码池(核心、社区、本地)。由于我们不打算在 Magento Connect 上分发我们的模块,我们将在本地目录下创建它。

所有 Magento 模块都保存在包或名称空间中;例如,所有核心 Magento 模块都保存在 Mage 名称空间下。在本书中,我们将使用Magento 开发者指南MDG

模块的 Magento 命名约定为Namespace_Modulename

我们的下一步将是创建模块结构和配置文件。我们需要在app/code/local/下创建一个名称空间目录。名称空间可以是您喜欢的任何内容。公认的约定是使用公司名称或作者名称作为名称空间。因此,我们的第一步将是创建目录app/code/local/Mdg/。该目录不仅包含我们的礼品注册模块,还包含我们将来开发的任何模块。

在我们的名称空间目录下,我们还需要用模块的名称创建一个新目录,它将保存所有自定义扩展的代码。

让我们继续创建一个Giftregistry目录。完成后,让我们创建目录结构的其余部分。

注意,由于使用工厂方法,Magento 对骆驼套管的使用有点敏感。一般来说,最好避免在模块/控制器/动作名称中使用驼峰式大小写。有关 Magento 命名约定的更多信息,请参阅本书的附录

文件位置为/app/code/local/Mdg/Giftregistry/

Block/
Controller/
controllers/
Helper/
etc/
Model/
sql/

到目前为止,我们已经了解到,Magento 使用.xml文件作为其配置的核心部分。为了让 Magento 识别和激活模块,我们需要按照Namespace_Modulename.xml约定在app/etc/modules/下创建一个文件。让我们创建我们的文件。

文件位置为app/etc/modules/Mdg_Giftregistry.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Mdg_Giftregistry>
            <active>true</active>
            <codePool>local</codePool>
        </Mdg_Giftregistry >
    </modules>
</config>

提示

下载示例代码

您可以下载您在账户购买的所有 Packt 书籍的示例代码文件 http://www.packtpub.com 。如果您在其他地方购买了本书,您可以访问http://www.packtpub.com/support 并注册,将文件直接通过电子邮件发送给您。

创建此文件或对模块配置文件进行任何更改后,我们需要刷新 Magento 配置缓存:

  1. 导航到 Magento 后端。
  2. 打开系统缓存管理
  3. 点击刷新 Magento

由于我们正在开发一个扩展,并且我们将对配置和扩展代码进行频繁更改,因此禁用缓存是一个好主意。遵循以下步骤:

  1. 导航到 Magento 后端。
  2. 打开系统缓存管理
  3. 选中所有缓存类型复选框。
  4. 动作下拉列表中选择禁用
  5. Click on the Submit button.

    Hello Magento

一旦我们清除了缓存,我们可以通过进入系统高级来确认我们的扩展正在被激活。

Hello Magento

Magento 现在知道我们的模块,但我们还没有告诉 Magento 我们的模块应该做什么;为此,我们需要设置模块配置。

XML 模块配置

模块配置中涉及两个主文件:config.xmlsystem.xml。除了这些模块配置外,还存储在:

  • api.xml
  • adminhtml.xml
  • cache.xml
  • widget.xml
  • wsdl.xml
  • wsi.xml
  • convert.xml

在本章中,我们将只关注config.xml文件。让我们创建基本文件,并按照以下步骤分解每个节点:

  1. 首先在我们的模块etc/directory下创建config.xml文件。
  2. 现在,将以下代码复制到config.xml文件中(文件位置为app/code/local/Mdg/Giftregistry/etc/config.xml

    php <?xml version="1.0"?> <config> <modules> <Mdg_Giftregistry> <version>0.1.0</version> </Mdg_Giftregistry> </modules> <global> <models> <mdg_giftregistry> <class>Mdg_Giftregistry_Model</class> </mdg_giftregistry> </models> <blocks> <mdg_giftregistry> <class>Mdg_Giftregistry_Block</class> </mdg_giftregistry> </blocks> <helpers> <mdg_giftregistry> <class>Mdg_Giftregistry_Helper</class> </mdg_giftregistry> </helpers> <resources> <mdg_giftregistry_setup> <setup> <module>Mdg_Giftregistry</module> </setup> </mdg_giftregistry_setup> </resources> </global> </config>

所有模块配置都包含在<config>节点中。在这个节点中,我们有<global><modules>节点。

<modules>节点只用于指定当前模块版本,稍后用于决定运行哪些安装和升级文件。

有三个主要配置节点最常用于指定配置范围:

  • <global>
  • <adminhtml>
  • <frontend>

现在,我们将在<global>范围内工作。这将使任何配置对 Magento 前端和后端都可用。在<global>节点下,我们有以下节点:

  • <models>
  • <blocks>
  • <helpers>
  • <resources>

如我们所见,每个节点遵循相同的配置模式:

<context>
   <factory_alias>
       <class>NameSpace_ModuleName_ClassType</class>
   </factory_alias>
</context>

Magento 类工厂使用的每个节点都实例化了我们的自定义对象。<factory_alias>节点是我们扩展配置的关键部分。<factory_alias>节点由工厂方法使用,如Mage::getModel()Mage::getHelper()

请注意,我们并不是定义每个特定的模型、块或辅助对象,而是定义 Magento 工厂可以找到它们的路径。Magento 命名约定允许我们在每个文件夹下都有任何文件夹结构,Magento 将足够聪明,可以在 Magento 的类名中加载适当的类。

在 Magento 中,类名和目录结构是相同的。

例如,我们可以在app/code/local/Mdg/Giftregistry/Models/Folder1/Folder2/Folder3下创建一个新的模型类,用于从该类实例化对象的工厂名称为:

Mage::getModel('mdg_giftregistry/folder1_folder2_folder3_classname');

让我们创建第一个模型,或者更具体地说,一个助手类。Helpers 用于包含用于执行常见任务并可在不同类之间共享的实用程序方法。

让我们继续创建一个空的helper类;我们将在本章后面添加辅助逻辑。

文件位置为app/code/loca/Mdg/Giftregistry/Helper/Data.php。请参阅以下代码:

<?php
class Mdg_Giftregistry_Helper_Data extends Mage_Core_Helper_Abstract {

}
?>

我们将助手命名为Data似乎有些奇怪,但这实际上是 Magento 标准的一部分,每个模块都有一个名为Data的默认helper类。helper类的另一个有趣之处是,我们只需将<factory_alias>节点(不带特定于类的类名)传递给helper工厂方法,这将默认为Data助手类。

因此,如果我们想实例化我们的默认helper类,我们只需要执行以下操作:

Mage::helper('mdg_registry');

模型及数据保存

在直接创建模型之前,我们需要明确定义要构建的模型类型和数量。让我们回顾一下我们的示例场景。对于我们的礼品登记处,我们似乎需要两种不同的型号:

  • 注册表模型:此模型用于存储礼品注册表信息,如礼品注册表类型、地址、收件人信息等
  • 登记项目:此型号用于存储每个礼品登记项目的信息(申请数量、购买数量、product_id

尽管这种方法是正确的,但它并不满足示例场景的所有要求。通过将所有注册表信息存储到一个表中,我们无法在不修改代码的情况下添加更多注册表类型。

因此,在本例中,我们希望将数据分解为多个表:

  • 注册实体:此表用于存储礼品注册和活动信息
  • 注册表类型:通过将礼品注册表类型存储到单独的表中,我们可以添加或删除事件类型
  • 登记项目:此表用于存储每个礼品登记项目的信息(申请数量、购买数量、product_id)

现在我们已经定义了数据结构,我们可以开始构建相应的模型,让我们能够访问和操作数据。

创建模型

让我们从开始创建礼物注册类型模型,用于管理注册类型(婚礼、生日、婴儿淋浴等)。为此,请执行以下步骤:

  1. 导航到模块目录上的Model文件夹。
  2. 创建一个名为Type.php的新文件,并将以下内容复制到该文件中(文件位置为app/code/local/Mdg/Giftregistry/Model/Type.php

    php <?php class Mdg_Giftregistry_Model_Type extends Mage_Core_Model_Abstract { public function __construct() { $this->_init('mdg_giftregistry/type'); parent::_construct(); } }

我们还需要创建一个资源类;每个 Magento 数据模型都有自己的资源类。还需要澄清的是,只有直接处理数据的模型,无论是简单数据模型还是 EAV 模型,才需要resource类。为此,请执行以下步骤:

  1. 导航到模块目录上的Model文件夹。
  2. Model下创建一个名为Mysql4的新文件夹。
  3. 创建一个名为Type.php的新文件,并将以下内容复制到该文件中(文件位置为app/code/local/Mdg/Giftregistry/Model/Mysql4/Type.php

    php <?php class Mdg_Giftregistry_Model_Mysql4_Type extends Mage_Core_Model_Mysql4_Abstract { public function _construct() { $this->_init('mdg_giftregistry/type', 'type_id'); } }

最后,我们还需要一个collection类来检索所有可用的事件类型:

  1. 导航到模块目录上的Model文件夹。
  2. 创建一个名为Type.php的新文件,并将以下内容复制到该文件中(文件位置为app/code/local/Mdg/Giftregistry/Model/Mysql4/Type/Collection.php

    php <?php class Mdg_Giftregistry_Model_Mysql4_Type_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract { public function _construct() { $this->_init('mdg_giftregistry/type'); parent::_construct(); } }

让我们通过创建一个处理礼品注册表项的模型来做同样的事情。此模型将保存注册表项的所有相关产品信息。为此,请执行以下步骤:

  1. 导航到模块目录上的Model文件夹。
  2. 创建一个名为Item.php的新文件,并将以下内容复制到该文件中(文件位置为app/code/local/Mdg/Giftregistry/Model/Item.php

    php <?php class Mdg_Giftregistry_Model_Item extends Mage_Core_Model_Abstract { public function __construct() { $this->_init('mdg_giftregistry/item'); parent::_construct(); } }

让我们继续创建资源类:

  1. 导航到模块目录上的Model文件夹。
  2. 打开Mysql4文件夹
  3. 创建一个名为Item.php的新文件,并将以下内容复制到该文件中(文件位置为app/code/local/Mdg/Giftregistry/Model/Mysql4/Item.php

    php <?php class Mdg_Giftregistry_Model_Mysql4_Item extends Mage_Core_Model_Mysql4_Abstract { public function _construct() { $this->_init('mdg_giftregistry/item', 'item_id'); } }

最后,让我们创建对应的collection类:

  1. 导航到模块目录上的Model文件夹。
  2. 创建一个名为Collection.php的新文件,并将以下内容复制到该文件中(文件位置为app/code/local/Mdg/Giftregistry/Model/Mysql4/Item/Collection.php

    php <?php class Mdg_Giftregistry_Model_Mysql4_Item_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract { public function _construct() { $this->_init('mdg_giftregistry/item'); parent::_construct(); } }

我们的下一步将是创建我们的注册实体;这是我们注册中心的核心,是将所有内容联系在一起的模型。为此,请执行以下步骤:

  1. 导航到模块目录上的Model文件夹。
  2. 创建一个名为Entity.php的新文件,并将以下内容复制到该文件中(文件位置为app/code/local/Mdg/Giftregistry/Model/Entity.php

    php <?php class Mdg_Giftregistry_Model_Entity extends Mage_Core_Model_Abstract { public function __construct() { $this->_init('mdg_giftregistry/entity'); parent::_construct(); } }

让我们继续创建resource类:

  1. 导航到模块目录上的Model文件夹。
  2. 打开Mysql4文件夹。
  3. 创建一个名为Entity.php的新文件,并将以下内容复制到该文件中(文件位置为app/code/local/Mdg/Giftregistry/Model/Mysql4/Entity.php

    php <?php class Mdg_Giftregistry_Model_Mysql4_Entity extends Mage_Core_Model_Mysql4_Abstract { public function _construct() { $this->_init('mdg_giftregistry/entity', 'entity_id'); } }

最后,让我们创建相应的collection类:

  1. 导航到模块目录上的Model文件夹。
  2. 创建一个名为Collection.php的新文件,并将以下内容复制到该文件中(文件位置为app/code/local/Mdg/Giftregistry/Model/Mysql4/Entity/Collection.php

    php <?php class Mdg_Giftregistry_Model_Mysql4_Entity_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract { public function _construct() { $this->_init('mdg_giftregistry/entity'); parent::_construct(); } }

到目前为止,除了通过复制代码和向模块中添加 model 类来盲目创建新模型之外,我们还没有做任何事情。让我们使用交互式 Magento 控制台IMC)来测试我们新创建的模型。

让我们在 Magento 安装的根目录中运行以下命令,启动 IMC 并试用新型号:

$ php shell/imc.php

以下代码假定您正在运行带有示例数据的 Magento 测试安装,如果您正在使用 Vagrant box 安装,则您已经拥有所有预加载的数据:

  1. 我们将首先加载客户模型:

    ```php magento > $customer = Mage::getModel('customer/customer')->load(1);

    ```

  2. 接下来我们需要实例化一个新的注册表对象:

    ```php magento > $registry = Mage::getModel('mdg_giftregistry/entity');

    ```

  3. 作为所有 Magento 模型的一部分,一个方便的函数是getData()函数,它返回所有对象属性的数组。让我们在 a、注册表和客户对象上运行此函数,并比较输出:

    ```php magento > print_r($customer->getData()); magento > print_r($registry->getData());

    ```

  4. 我们注意到,客户拥有 John Doe 示例记录的所有数据集,而 registry 对象返回完全为空的$regiarray。让我们通过运行以下代码来改变这一点:

    ```php magento > $registry->setCustomerId($customer->getId()); magento > $registry->setTypeId(1); magento > $registry->setWebsiteId(1); magento > $registry->setEventDate('2012-12-12'); magento > $registry->setEventCountry('CA'); magento > $registry->setEventLocation('Toronto');

    ```

  5. 现在,让我们通过运行:

    ```php magento > print_r($registry->getData());

    ```

    再次尝试打印注册表数据 6. 最后,为了使更改永久化,我们需要调用模型save函数:

    ```php magento > $registry->save();

    ```

哎呀!保存产品时出现问题;控制台中出现以下错误:

Fatal error: Call to a member function beginTransaction() on a non-object in …/app/code/core/Mage/Core/Model/Abstract.php on line 313

怎么搞的?被调用的save()函数是父类Mage_Core_Model_Mysql4_Abstract的一部分,父类反过来调用抽象类save()函数,但我们缺少config.xml文件的一个关键部分。

为了让 Magento 正确识别要使用的资源类,我们需要为每个实体指定资源模型类和匹配表。让我们按照以下步骤继续更新配置文件:

  1. 导航到扩展名etc/文件夹。
  2. 打开config.xml
  3. 使用以下代码更新<model>节点(文件位置为app/code/local/Mdg/Giftregistry/Model/Entity.php

    php … <models> <mdg_giftregistry> <class>Mdg_Giftregistry_Model</class> <resourceModel>mdg_giftregistry_mysql4</resourceModel> </mdg_giftregistry> <mdg_giftregistry_mysql4> <class>Mdg_Giftregistry_Model_Mysql4</class> <entities> <entity> <table>mdg_giftregistry_entity</table> </entity> <item> <table>mdg_giftregistry_item</table> </item> <type> <table>mdg_giftregistry_type</table> </type> </entities> </mdg_giftregistry_mysql4> </models> …

现在,在我们将产品保存到数据库之前,我们必须先创建数据库表;接下来,我们将学习如何使用设置资源来创建表结构和设置默认数据。

设置资源

既然我们已经创建了我们的模型代码,我们需要创建设置资源以便能够保存它们。安装资源将负责创建相应的数据库表。现在,我们可以使用直接 SQL 或 PHPMyAdmin 之类的工具来创建所有表,但这不是标准做法,而且根据一般规则,我们永远不应该直接修改 Magento 数据库。

为此,我们将采取以下措施:

  • 在配置文件上定义安装资源
  • 创建一个资源类
  • 创建安装程序脚本
  • 创建一个数据脚本
  • 创建升级脚本

定义设置资源

在首次定义配置文件时,我们定义了一个<resources>节点:

文件位置为app/code/local/Mdg/Giftregistry/etc/config.xml。请参阅以下代码段:

…
<resources>
    <mdg_giftregistry_setup>
        <setup>
            <module>Mdg_Giftregistry</module>
        </setup>
    </mdg_giftregistry_setup>
</resources>
…

首先要注意的是,<mdg_giftregistry_setup>节点被用作我们设置资源的唯一标识符;标准命名约定为<modulename_setup>,虽然不是必需的,但强烈建议遵循此命名约定。

我们还需要更改<setup>节点,添加额外的类节点,以及读写连接:

文件位置为app/code/local/Mdg/Giftregistry/etc/config.xml

…
<resources>
    <mdg_giftregistry_setup>
        <setup>
            <module>Mdg_Giftregistry</module>
            <class>Mdg_Giftregistry_Model_Resource_Setup</class>
        </setup>
        <connection>
            <use>core_setup</use>
        </connection>
    </mdg_giftregistry_setup>
    <mdg_giftregistry_write>
        <connection>
            <use>core_write</use>
        </connection>
    </mdg_giftregistry_write>
    <mdg_giftregistry_read>
        <connection>
            <use>core_read</use>
        </connection>
    </mdg_giftregistry_read>
</resources>
…

基本设置脚本不需要创建此设置资源,也可以使用Mage_Core_Model_Resource_Setup,但通过创建我们自己的设置类,我们正在提前计划,并为将来的改进提供更大的灵活性。接下来,我们将在文件位置下创建安装资源类,否则我们将得到一个错误,说明 Magento 找不到安装资源类。

在文件位置app/code/local/Mdg/Giftregistry/Model/Resource/Setup.php下创建安装资源类。请参阅以下代码段:

<?php
class Mdg_Giftregistry_Model_Resource_Setup extends Mage_Core_Model_Resource_Setup
{

}

目前,我们不需要对 setup 资源类执行任何其他操作。

创建安装程序脚本

我们的下一步将是创建一个安装脚本。此脚本包含用于创建表的所有 SQL 代码,并在初始化模块时运行。首先,让我们再快速查看一下我们的config.xml文件。如果我们还记得,在我们的<global>节点之前定义的第一个节点是<modules>节点。

文件位置为app/code/local/Mdg/Giftregistry/etc/config.xml。请参阅以下代码段:

<modules>
  <Mdg_Giftregistry>
     <version>0.1.0</version>
   </Mdg_Giftregistry>
</modules>

如前所述,所有 Magento 模块都需要此节点,并用于标识模块的当前安装版本。Magento 使用此版本号来确定是否以及要运行哪些安装和升级脚本。

关于命名约定的一句话:自 Magento 1.6 以来,设置脚本命名约定已更改。最初使用了Mysql4-install-x.x.x.php命名约定,目前已弃用,但仍受支持。

自 Magento 1.6 以来,安装脚本的命名约定已经改变,现在开发人员可以使用三种不同的脚本类型:

  • 安装:此脚本在模块首次安装时使用,在core_resource表中没有记录
  • 升级:如果core_resource表中的版本低于config.xml文件中的版本,则使用此脚本
  • Data: This script will run after the matching version install/upgrade script and are used to populate the tables with required data

    数据脚本在 Magento 1.6 中引入,存储在模块根目录下的 Data/目录下。通过添加前缀,它们遵循与安装和升级脚本稍有不同的约定。

让我们在下面的安装脚本中继续创建注册表实体表。

文件位置为app/code/local/Mdg/Giftregistry/sql/mdg_giftregistry_setup/install-0.1.0.php。请参阅以下代码:

<?php

$installer = $this;
$installer->startSetup();
// Create the mdg_giftregistry/registry table
$tableName = $installer->getTable('mdg_giftregistry/entity');
// Check if the table already exists
if ($installer->getConnection()->isTableExists($tableName) != true) {
    $table = $installer->getConnection()
        ->newTable($tableName)
        ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null,
            array(
                'identity' => true,
                'unsigned' => true,
                'nullable' => false,
                'primary' => true,
            ),
            'Entity Id'
        )
        ->addColumn('customer_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null,
            array(
                'unsigned' => true,
                'nullable' => false,
                'default' => '0',
            ),
            'Customer Id'
        )
        ->addColumn('type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null,
            array(
                'unsigned' => true,
                'nullable' => false,
                'default' => '0',
            ),
            'Type Id'
        )
        ->addColumn('website_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null,
            array(
                'unsigned' => true,
                'nullable' => false,
                'default' => '0',
            ),
            'Website Id'
        )
        ->addColumn('event_name', Varien_Db_Ddl_Table::TYPE_TEXT, 255,
            array(),
            'Event Name'
        )
        ->addColumn('event_date', Varien_Db_Ddl_Table::TYPE_DATE, null,
            array(),
            'Event Date'
        )
        ->addColumn('event_country', Varien_Db_Ddl_Table::TYPE_TEXT, 3,
            array(),
            'Event Country'
        )
        ->addColumn('event_location', Varien_Db_Ddl_Table::TYPE_TEXT, 255,
            array(),
            'Event Location'
        )
        ->addColumn('created_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null,
            array(
                'nullable' => false,
            ),
            'Created At')
        ->addIndex($installer->getIdxName('mdg_giftregistry/entity', array('customer_id')),
            array('customer_id'))
        ->addIndex($installer->getIdxName('mdg_giftregistry/entity', array('website_id')),
            array('website_id'))
        ->addIndex($installer->getIdxName('mdg_giftregistry/entity', array('type_id')),
            array('type_id'))
        ->addForeignKey(
            $installer->getFkName(
                'mdg_giftregistry/entity',
                'customer_id',
                'customer/entity',
                'entity_id'
            ),
            'customer_id', $installer->getTable('customer/entity'), 'entity_id',
            Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
        ->addForeignKey(
            $installer->getFkName(
                'mdg_giftregistry/entity',
                'website_id',
                'core/website',
                'website_id'
            ),
            'website_id', $installer->getTable('core/website'), 'website_id',
            Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE)
        ->addForeignKey(
            $installer->getFkName(
                'mdg_giftregistry/entity',
                'type_id',
                'mdg_giftregistry/type',
                'type_id'
            ),
            'type_id', $installer->getTable('mdg_giftregistry/type'), 'type_id',
            Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE);

    $installer->getConnection()->createTable($table);
}
$installer->endSetup();

请注意,由于空间限制,我们没有添加完整的安装脚本;您仍然需要为 item 和 type 表添加安装程序代码。完整的安装文件和代码文件可直接从下载 https://github.com/amacgregor/mdg_giftreg

既然看起来像很多代码,但它只是创建了一个表的输出,为了理解它,让我们把它分解一下,看看代码到底在做什么。

首先要注意的是,即使我们正在创建和设置数据库表,我们也没有编写任何 SQL 代码。Magento ORM 为数据库提供了一个适配器。所有安装、升级和数据脚本都继承自Mage_Core_Model_Resource_Setup。让我们分析一下安装脚本中使用的每个函数。

脚本的前三行实例化了resource_setup模型和连接。脚本的其余部分将设置一个新的表实例并对其调用以下函数:

  • addColumn:此函数用于定义每个表列,并采用以下五个参数:
    • name:此为栏目名称
    • type:此为数据存储类型(intvarchartext等)
    • size:此为列长
    • options:这是数据存储的附加选项数组
    • Comment:此为栏目说明
  • addIndex:此函数用于定义特定表的索引,并采用以下三个参数:
    • index:这是一个索引名
    • columns:此可以是单个列名的字符串,也可以是多个列名的数组
    • options:这是数据存储的附加选项数组
  • addForeginKey:此函数用于定义外键关系,需要以下六个参数:
    • fkName:这是一个外键名
    • column:此为外键列名
    • refTable:此为参考表名
    • refColumn:此为参考表列名
    • onDelete:这是对删除行执行的操作
    • onUpdate:这是对行进行更新的动作

创建我们每个表的代码基本上由这三个函数组成,在每个表定义之后,执行以下代码:

$installer->getConnection()->createTable($table);

这是告诉我们的数据库适配器将代码转换为 SQL 并在数据库上运行。有一件重要的事情需要注意;也就是说,不提供或硬编码数据库名称,而是调用以下代码:

$installer->getTable('mdg_giftregistry/entity')

这是我们之前在config.xml文件中定义的表别名。要完成安装程序,我们需要为每个实体创建一个newTable实例。

这是给你的一个挑战。使用安装程序脚本创建缺少的表。要查看完整代码和完整细分的答案,请访问http://www.magedevguide.com/challenge/chapter4/1

数据脚本可用于填充我们的表;在我们的例子中,这将有助于设置一些基本事件类型。

我们首先需要在data文件夹下创建一个数据安装脚本;正如我们前面提到的一样,结构与 SQL 文件夹非常相似,唯一的区别是我们将数据前缀附加到匹配的安装/升级脚本中。为此,请执行以下步骤:

  1. 导航到模块数据文件夹app/code/local/Mdg/Giftregistry/data/
  2. 基于资源创建新目录;在这种情况下,它将是mdg_giftregistry_setup
  3. mdg_giftregistry_setup下,创建一个名为data-install-0.1.0.php的文件。
  4. 将以下代码复制到data-install-0.1.0.php文件中(文件位置为app/code/local/Mdg/Giftregistry/data/mdg_giftregistry_setup/data-install-0.1.0.php

    ```php <?php $registryTypes = array( array( 'code' => 'baby_shower', 'name' => 'Baby Shower', 'description' => 'Baby Shower', 'store_id' => Mage_Core_Model_App::ADMIN_STORE_ID, 'is_active' => 1, ), array( 'code' => 'wedding', 'name' => 'Wedding', 'description' => 'Wedding', 'store_id' => Mage_Core_Model_App::ADMIN_STORE_ID, 'is_active' => 1, ), array( 'code' => 'birthday', 'name' => 'Birthday', 'description' => 'Birthday', 'store_id' => Mage_Core_Model_App::ADMIN_STORE_ID, 'is_active' => 1, ), );

    foreach ($registryTypes as $data) { Mage::getModel('mdg_giftregistry/type') ->addData($data) ->setStoreId($data['store_id']) ->save(); } ```

让我们来看看 To.T0.脚本上最后一个条件块:

foreach ($registryTypes as $data) {
    Mage::getModel('mdg_giftregistry/type')
        ->addData($data)
        ->setStoreId($data['store_id'])
        ->save();
}

现在,如果我们刷新 Magento 安装,错误应该消失,如果我们仔细查看mdg_giftregistry_type表,我们应该会看到以下记录:

Creating the Installer Script

正如我们之前了解到的,安装和数据脚本将在第一次安装模块时运行。但是,在 Magento 认为我们的模块已经安装的情况下会发生什么呢?

由于模块已在core_resource表中注册,安装脚本将不会再次运行,除非 Magento 检测到扩展中的版本更改。这对于处理一个扩展的多个版本是很好的,但对于开发目的来说不是很实用。

幸运的是,很容易欺骗 Magento 再次运行我们的扩展安装脚本。我们只需要删除core_resource表中相应的条目。为此,请执行以下步骤:

  1. 打开您的 MySQL 控制台;如果您正在使用我们的“流浪汉”框,您只需键入mysql即可打开它。
  2. 一旦我们进入 MySQL 外壳,我们需要选择我们的工作数据库;在我们的例子中,它是ce1702_magento
  3. 最后,我们需要使用以下查询进入core_resource表:

    ``php mysql> DELETE FROMcore_resourceWHEREcode` = 'mdg_giftregistry_setup'

    ```

我们学到了什么?

到目前为止,我们已经学会了:

  • 为我们的 Magento 模块创建基本目录结构
  • 配置文件的作用和重要性
  • 创建模型和设置资源
  • 安装、升级和数据脚本的角色和顺序

这是给你的一个挑战。通过将实体转换为 EAV 模型,尝试进一步改进我们模块的模型结构;这将需要修改安装脚本和资源模型。要查看完整代码和完整细分的答案,请访问http://www.magedevguide.com/challenge/chapter4/2

设置我们的路线

既然我们能够使用我们的模型保存和操作数据,我们需要为客户提供一种与实际礼品登记处交互的方式;这是我们的第一步。我们需要在前端创建有效的路由或 URL。

与 Magento 中的许多内容一样,这是由配置文件控制的。路由将 URL 转换为有效的控制器、操作和方法。

打开我们的config.xml文件。文件位置为app/code/local/Mdg/Giftregistry/etc/config.xml。请参阅以下代码:

<config>
…
    <frontend>
        <routers>
            <mdg_giftregistry>
                <use>standard</use>
                <args>
                    <module>Mdg_Giftregistry</module>
                    <frontName>giftregistry</frontName>
                </args>
            </mdg_giftregistry>
        </routers>
    </frontend>
…
</config>

让我们分析一下刚才添加的配置代码:

  • <frontend>:之前我们添加了全局范围内的所有配置;因为我们希望我们的路线只在前端可用,所以我们需要在前端范围内声明我们的自定义路线
  • <routers>:这是保存我们定制路线配置的容器标签
  • <mdg_giftregistry>:此标签的命名约定与模块名称匹配,是我们路由的唯一标识符
  • <frontName>:正如我们在第 2 章Magento 开发者基础中了解到的,Magento 将 URL 分解为http://localhost.com /frontName/actionControllerName/actionMethod/

一旦我们定义了路由配置,我们需要创建一个实际的控制器来处理所有传入的请求。

索引控制器

我们的第一步是在模块控制器目录下创建IndexController。如果未指定控制器名称,Magento 将始终尝试加载IndexController

文件位置为app/code/local/Mdg/Giftregistry/controllers/Index.php。请参阅以下代码:

<?php 
class Mdg_Giftregistry_IndexController extends Mage_Core_Controller_Front_Action
{
     public function indexAction()
  {
    echo 'This is our test controller';
     }
}

创建完文件后,如果我们转到http://localhost.com/giftregistry/index/index,我们会看到一个空白页面,上面有一条消息,这是我们的测试控制器。这是因为我们没有正确加载客户控制器的布局。文件位置为app/code/local/Mdg/Giftregistry/controllers/IndexController.php。我们需要将行动代码更改为:

<?php 
class Mdg_Giftregistry_IndexController extends Mage_Core_Controller_Front_Action
{
     public function indexAction()
  {
    $this->loadLayout();
    $this->renderLayout();
     }
}

在进入控制器动作中发生的细节之前;让我们创建其余的控制器和相应的操作。

我们需要一个控制器,负责客户的基本操作,以便他们能够创建、管理和删除他们的注册表。此外,我们还需要一个搜索控制器,以便家人和朋友能够找到匹配的礼品登记处,最后,我们需要一个视图控制器来显示登记处的详细信息。

我们的第一步是将剩余操作添加到索引控制器(文件位置为app/code/local/Mdg/Giftregistry/controllers/IndexController.php

<?php
class Mdg_Giftregistry_IndexController extends Mage_Core_Controller_Front_Action
{
    public function indexAction()
    {
        $this->loadLayout();
        $this->renderLayout();
        return $this;
    }

    public function deleteAction()
    {
        $this->loadLayout();
        $this->renderLayout();
        return $this;
    }

    public function newAction()
    {
        $this->loadLayout();
        $this->renderLayout();
        return $this;
    }

    public function editAction()
    {
        $this->loadLayout();
        $this->renderLayout();
        return $this;
    }

    public function newPostAction()
    {
        $this->loadLayout();
        $this->renderLayout();
        return $this;
    }

    public function editPostAction()
    {
        $this->loadLayout();
        $this->renderLayout();
        return $this;
    }
}

在我们开始向索引控制器添加所有逻辑之前,我们需要采取额外的步骤,防止未登录的客户访问giftregistry功能。Magento 前端控制器对于处理此问题已经非常有用;它被称为preDispatch()函数,在控制器中任何其他动作之前执行。

打开你的IndexController.php并在课程开始处添加以下代码。

文件位置为app/code/local/Mdg/Giftregistry/controllers/IndexController.php。请参阅以下代码:

<?php
class Mdg_Giftregistry_IndexController extends Mage_Core_Controller_Front_Action
{
    public function preDispatch()
    {
        parent::preDispatch();
        if (!Mage::getSingleton('customer/session')->authenticate($this)) {
            $this->getResponse()->setRedirect(Mage::helper('customer')->getLoginUrl());
            $this->setFlag('', self::FLAG_NO_DISPATCH, true);
        }
    }
…

现在,如果我们尝试加载http://localhost.com/giftregistry/index/index,我们将被重定向到登录页面,除非我们登录到前端。

我们的下一步将是向每个控制器操作添加所有逻辑,以便控制器能够正确处理创建、更新和删除。

索引、新建和编辑动作主要用于加载和呈现布局,因此控制器、newPostAction()editPostAction()deleteAction()中涉及的逻辑不多;另一方面,它们处理的逻辑更重、更复杂。

让我们从newPostAction()开始。此操作用于处理从newAction表单收到的数据。为此,请执行以下步骤:

  1. 打开IndexController.php
  2. 我们将添加到操作中的第一件事是一个if语句,用于检查请求是否为 post 请求,我们可以使用以下代码检索该请求:

    php $this->getRequest()->isPost()

  3. 除此之外,我们还要检查请求是否有实际数据;为此,我们可以使用以下代码:

    php $this->getRequest()->getParams()

一旦我们验证了这一点,请求就是一个正确的请求,当我们接收数据时,我们需要实际创建礼品注册中心。为此,我们将通过以下步骤在注册表模型中添加一个新函数:

  1. 打开注册表实体模型。
  2. 创建一个名为updateRegistryData()的新函数,并确保该函数采用两个参数:$customer$data
  3. 文件位置为app/code/local/Mdg/Giftregistry/Model/Entity.php。在此函数中添加以下代码:

    php public function updateRegistryData(Mage_Customer_Model_Customer $customer, $data) { try{ if(!empty($data)) { $this->setCustomerId($customer->getId()); $this->setWebsiteId($customer->getWebsiteId()); $this->setTypeId($data['type_id']); $this->setEventName($data['event_name']); $this->setEventDate($data['event_date']); $this->setEventCountry($data['event_country']); $this->setEventLocation($data['event_location']); }else{ throw new Exception("Error Processing Request: Insufficient Data Provided"); } } catch (Exception $e){ Mage::logException($e); } return $this; }

此函数将帮助我们将表单数据添加到注册表对象的当前实例中,这意味着我们需要在控制器内创建一个。让我们把控制器的代码放在一起:

文件位置为app/code/local/Mdg/Giftregistry/controllers/IndexController.php。请参阅以下代码段:

public function newPostAction()
{
    try {
        $data = $this->getRequest()->getParams();
        $registry = Mage::getModel('mdg_giftregistry/entity');
        $customer = Mage::getSingleton('customer/session')->getCustomer();

        if($this->getRequest()->getPost() && !empty($data)) {
            $registry->updateRegistryData($customer, $data);
            $registry->save();
            $successMessage = Mage::helper('mdg_giftregistry')->__('Registry Successfully Created');
            Mage::getSingleton('core/session')->addSuccess($successMessage);
        }else{
            throw new Exception("Insufficient Data provided");
        }
    } catch (Mage_Core_Exception $e) {
        Mage::getSingleton('core/session')->addError($e->getMessage());
        $this->_redirect('*/*/');
    }
    $this->_redirect('*/*/');
}

我们已经创建了一个非常基本的控制器操作,该操作将处理注册表创建,并将处理大多数可能的异常。

我们继续创建editPostAction;此动作与newPostAction非常相似。主要区别是在editPostAction的情况下,我们正在处理一个已经存在的注册表记录,因此我们需要在设置数据之前添加一些验证。

文件位置为app/code/local/Mdg/Giftregistry/controllers/IndexController.php。让我们仔细看看下面的动作代码:

public function editPostAction()
{
    try {
        $data = $this->getRequest()->getParams();
        $registry = Mage::getModel('mdg_giftregistry/entity');
        $customer = Mage::getSingleton('customer/session')->getCustomer();

        if($this->getRequest()->getPosts() && !empty($data) )
        {
            $registry->load($data['registry_id']);
            if($registry){
                $registry->updateRegistryData($customer, $data);
                $registry->save();
                $successMessage =  Mage::helper('mdg_giftregistry')->__('Registry Successfully Saved');
                Mage::getSingleton('core/session')->addSuccess($successMessage);
            }else {
                throw new Exception("Invalid Registry Specified");
            }
        }else {
            throw new Exception("Insufficient Data provided");
        }
    } catch (Mage_Core_Exception $e) {
        Mage::getSingleton('core/session')->addError($e->getMessage());
        $this->_redirect('*/*/');
    }
    $this->_redirect('*/*/');
}

正如我们可以看到的,这段代码与我们的newPostAction()控制器几乎相同,关键区别在于它试图在更新数据之前加载现有注册表。

这是给你的一个挑战。由于editPostAction()newPostAction()之间的代码非常相似,请尝试将两者组合成一个可以重用的 post 操作。要查看完整代码和完整细分的答案,请访问http://www.magedevguide.com/challenge/chapter4/3

要完成IndexController,我们需要添加一个允许我们删除特定注册表记录的操作;为此,我们将使用deleteAction()

多亏了 Magento ORM 系统,这个过程非常简单,因为 Magento 模型继承了delete()函数,顾名思义,该函数将简单地删除特定的模型实例。

文件位置为app/code/local/Mdg/Giftregistry/controllers/IndexController.php。在IndexController内增加以下代码:

public function deleteAction()
{
    try {
        $registryId = $this->getRequest()->getParam('registry_id');
        if($registryId && $this->getRequest()->getPost()){
            if($registry = Mage::getModel('mdg_giftregistry/entity')->load($registryId))
            {
                $registry->delete();
                $successMessage =  Mage::helper('mdg_giftregistry')->__('Gift registry has been succesfully deleted.');
                Mage::getSingleton('core/session')->addSuccess($successMessage);
            }else{
                throw new Exception("There was a problem deleting the registry");
            }
        }
    } catch (Exception $e) {
        Mage::getSingleton('core/session')->addError($e->getMessage());
        $this->_redirect('*/*/');
    }
}

我们的删除控制器中需要注意的重要操作如下:

  1. 我们在行动中检查请求的正确类型。
  2. 我们实例化注册表对象并验证它是否有效。
  3. 最后,我们在注册表实例上调用delete()函数。

您现在可能已经注意到,由于我们已经做了一个关键的省略,所以无法将实际产品添加到我们的购物车中。

现在我们将跳过这个特定的操作,我们将在更好地理解所涉及的块和布局以及它如何与自定义控制器交互后创建它。

搜索控制器

现在我们有了一个工作的IndexController,它将处理修改实际注册表的大部分逻辑,我们将创建的下一个控制器是SearchController。为此,请执行以下步骤:

  1. 在控制器目录下创建一个名为SearchController的新控制器。
  2. 文件位置为app/code/local/Mdg/Giftregistry/controllers/SearchController.php。将以下代码复制到搜索控制器中:

    php <?php class Mdg_Giftregistry_SearchController extends Mage_Core_Controller_Front_Action { public function indexAction() { $this->loadLayout(); $this->renderLayout(); return $this; } public function resultsAction() { $this->loadLayout(); $this->renderLayout(); return $this; } }

现在我们将离开indexAction,我们将关注resultsAction()中涉及的逻辑,它将获取搜索参数并加载注册表集合。

文件位置为app/code/local/Mdg/Giftregistry/controllers/SearchController.php。让我们看一看完整的动作代码并把它分解:

public function resultsAction()
{
    $this->loadLayout();
    if ($searchParams = $this->getRequest()->getParam('search_params')) {
        $results = Mage::getModel('mdg_giftregistry/entity')->getCollection();
        if($searchParams['type']){
            $results->addFieldToFilter('type_id', $searchParams['type']);
        }
        if($searchParams['date']){
            $results->addFieldToFilter('event_date', $searchParams['date']);
        }
        if($searchParams['location']){
            $results->addFieldToFilter('event_location', $searchParams['location']);
        }
        $this->getLayout()->getBlock('mdg_giftregistry.search.results')
            ->setResults($results);
    }
    $this->renderLayout();
    return $this;
}

与前面的操作一样,我们使用请求参数,但在这种特殊情况下,我们加载礼品注册表集合,并为每个可用字段应用字段过滤器。值得一提的是,这是我们第一次直接从 Magento 控制器与布局交互。

$this->getLayout()->getBlock('mdg_giftregistry.search.results')
        ->setResults($results);

我们在这里所做的是使加载的注册表集合可用于特定的块实例。

视图控制器

最后,我们需要一个控制器,该控制器允许显示注册表详细信息,而不管客户是否登录。遵循以下步骤:

  1. 在控制器目录下创建一个名为ViewController的新控制器。
  2. 打开我们刚刚创建的控制器,并参考以下占位符代码(文件位置为app/code/local/Mdg/Giftregistry/controllers/ViewController.php

    php <?php class Mdg_Giftregistry_ViewController extends Mage_Core_Controller_Front_Action { public function viewAction() { $registryId = $this->getRequest()->getParam('registry_id'); if($registryId){ $entity = Mage::getModel('mdg_giftregistry/entity'); if($entity->load($registryId)) { Mage::register('loaded_registry', $entity); $this->loadLayout(); $this->_initLayoutMessages('customer/session'); $this->renderLayout(); return $this; } else { $this->_forward('noroute'); return $this; } } } }

因此,我们在这里使用一个新函数Mage::register(),它设置了一个全局变量,我们以后可以通过任何方法检索到应用程序流中。此函数是 Magento 注册表模式的一部分,该模式受以下三个函数的影响:

  • Mage::register():此功能用于设置全局变量
  • Mage::unregister():此函数用于设置全局变量
  • Mage::registry():此函数用于检索全局变量

在本例中,我们使用 registry 函数在应用程序流之前提供对注册表实体的访问,尤其是在我们将要创建的视图块中。

区块和布局

正如我们在第 2 章Magento 开发者基础中了解到的,Magento 将其视图层分为块、模板和布局文件。块是处理部分逻辑的对象。模板是混合了 HTML 和 PHP 代码的phtml文件。布局文件是控制块位置的 XML 文件。

每个模块都有自己的布局文件,负责更新特定的模块布局。我们需要按照以下步骤为我们的模块创建布局文件:

  1. 导航到app/design/frontend/base/default/layout/
  2. 创建一个名为mdg_giftregistry.xml的文件。
  3. 添加以下代码(文件位置为app/design/frontend/base/default/layout/mdg_giftregistry.xml

    ```php

    ```

请注意,通过将模板和布局添加到基本/默认主题,我们将使模板和布局可用于所有商店和主题。

如果我们仔细查看刚才粘贴的 XML,我们可以看到我们有一个默认的<xml>标记和其他几组标记。如前所述,在 Magento 中,路由由前端名称、控制器和动作组成。

布局文件中的每个 XML 标记表示我们的一个控制器和操作;例如,<giftregistry_index_index>将控制我们IndexController动作的布局;Magento 为每个页面指定一个唯一的句柄。

为了让 Magento 识别我们的布局文件,我们需要通过以下步骤在config.xml文件中声明布局文件:

  1. 导航到extension etc/文件夹。
  2. 打开config.xml
  3. <frontend>节点内添加以下代码(文件位置为app/design/frontend/base/default/layout/mdg_giftregistry.xml

    php <frontend> <layout> <updates> <mdg_giftregistry module="mdg_giftregistry"> <file>mdg_giftregistry.xml</file> </mdg_giftregistry> </updates> </layout> … </frontend>

IndexController 块和视图

正如我们之前所做的,我们将从构建索引控制器开始。让我们定义需要为每个操作定义哪些模板和块:

  • 索引:这是当前客户可用注册的列表
  • 新增:提供了一个新的表单来捕获注册表信息
  • 编辑:加载特定的注册表数据并加载到表单中

对于索引操作,我们将需要创建一个名为List.php的新块。让我们按照以下步骤创建注册表列表块:

  1. 导航到app/code/local/Mdg/Giftregistry/Block/
  2. 创建一个名为List.php的文件。
  3. 复制以下代码(文件位置为app/code/local/Mdg/Giftregistry/Block/List.php

    php <?php class Mdg_Giftregistry_Block_list extends Mage_Core_Block_Template { public function getCustomerRegistries() { $collection = null; $currentCustomer = Mage::getSingleton('customer/session')->getCustomer(); if($currentCustomer) { $collection = Mage::getModel('mdg_giftregistry/entity')->getCollection() ->addFieldToFilter('customer_id', $currentCustomer->getId()); } return $collection; } }

前面的代码声明了我们将在IndexController中使用的列表块。这些块声明了getCustomerRegistries()方法,该方法将检查当前客户并尝试检索基于该客户的注册表集合。

现在我们创建了一个新块,需要将其添加到布局 XML 文件中:

  1. 打开mdg_giftregistry.xml
  2. <mdg_gifregistry_index_index>内添加以下代码(文件位置为app/design/frontend/base/default/layout/mdg_giftregistry.xml

    php <reference name="content"> <block name="giftregistry.list" type="mdg_giftregistry/list" template="mdg/list.phtml" as="giftregistry_list"/> </reference>

在布局中,我们宣布我们的区块;在该声明中,我们正在设置块名、模板、和类型。如果我们现在尝试加载 index controller 页面,因为我们还没有创建模板文件,那么我们应该会看到关于缺少模板的错误。

让我们创建模板文件:

  1. 导航到design/frontend/base/default/template/
  2. 创建mdg/文件夹。
  3. 在该文件夹中,创建一个名为list.phtml的文件(文件位置为app/design/frontend/base/default/template/mdg/list.phtml

    php <?php $_collection = $this->getCustomerRegistries(); ?> <div class="customer-list"> <ul> <?php foreach($_collection as $registry): ?> <li> <h3><?php echo $registry->getEventName(); ?></h3> <p><strong><?php echo $this->__('Event Date:') ?> <?php echo $registry->getEventDate(); ?></strong></p> <p><strong><?php echo $this->__('Event Location:') ?> <?php echo $registry->getEventLocation(); ?></strong></p> <a href="<?php echo $this->getUrl('giftregistry/view/view', array('_query' => array('registry_id' => $registry->getEntityId()))) ?>"> <?php echo $this->__('View Registry') ?> </a> </li> <?php endforeach; ?> </ul> </div>

这是我们第一次生成。phtml文件。正如我们前面提到的,.phtml文件只是 PHP 和 HTML 代码的组合。

对于list.phtml文件,首先调用getCustomerRegistries()方法加载集合;需要注意的一点是,我们实际上是在调用$this->getCustomerRegistries(),因为每个模板都被分配给一个特定的块。

我们遗漏了以下几点重要内容:

  • 如果当前客户没有注册,我们只会显示一个空的无序列表
  • 没有用于删除或编辑特定注册表的链接

检查集合是否有注册表的一种快速方法是调用count函数,并在集合实际为空时显示错误消息。

文件位置为app/design/frontend/base/default/template/mdg/list.phtml。请参阅以下代码:

<?php
    $_collection = $this->getCustomerRegistries();
?>
<div class="customer-list">
    <?php if(!$_collection->count()): ?>
        <h2><?php echo $this->__('You have no registries.') ?></h2>
        <a href="<?php echo $this->getUrl('giftregistry/index/new') ?>">
            <?php echo $this->__('Click Here to create a new Gift Registry') ?>
        </a>
    <?php else: ?>
        <ul>
            <?php foreach($_collection as $registry): ?>
                <li>
                    <h3><?php echo $registry->getEventName(); ?></h3>
                    <p><strong><?php echo $this->__('Event Date:') ?> <?php echo $registry->getEventDate(); ?></strong></p>
                    <p><strong><?php echo $this->__('Event Location:') ?> <?php echo $registry->getEventLocation(); ?></strong></p>
                    <a href="<?php echo $this->getUrl('giftregistry/view/view', array('_query' => array('registry_id' => $registry->getEntityId()))) ?>">
                        <?php echo $this->__('View Registry') ?>
                    </a>
                    <a href="<?php echo $this->getUrl('giftregistry/index/edit', array('_query' => array('registry_id' => $registry->getEntityId()))) ?>">
                        <?php echo $this->__('Edit Registry') ?>
                    </a>
                    <a href="<?php echo $this->getUrl('giftregistry/index/delete', array('_query' => array('registry_id' => $registry->getEntityId()))) ?>">
                        <?php echo $this->__('Delete Registry') ?>
                    </a>

                </li>
            <?php endforeach; ?>
        </ul>
    <?php endif; ?>
</div>

我们已经添加了一个新的if语句来检查集合计数是否为空,并添加了一个指向IndexController编辑操作的链接。最后,如果没有要显示的注册表,我们将显示一条链接到新操作的错误消息。

让我们继续为新操作添加块和模板:

  1. 打开mdg_giftregistry.xml布局文件。
  2. <mdg_gifregistry_index_new>节点内添加以下代码(文件位置为app/design/frontend/base/default/layout/mdg_giftregistry.xml

    php <reference name="content"> <block name="giftregistry.new" type="core/template" template="mdg/new.phtml" as="giftregistry_new"/> </reference>

因为我们只是显示一个表单来将注册表信息发布到newPostAction(),所以我们只是使用包含表单代码的自定义模板文件创建一个核心/模板块。我们的模板文件看起来像下面的代码。

文件位置为app/design/frontend/base/default/template/mdg/new.phtml

<?php $helper = Mage::helper('mdg_giftregistry'); ?>
<form action="<?php echo $this->getUrl('giftregistry/index/newPost/') ?>" method="post" id="form-validate">
    <fieldset>
        <?php echo $this->getBlockHtml('formkey')?>
        <ul class="form-list">
            <li>
                <label for="type_id"><?php echo $this->__('Event type') ?></label>
                <select name="type_id" id="type_id">
                    <?php foreach($helper->getEventTypes() as $type): ?>
                        <option id="<?php echo $type->getTypeId(); ?>" value="<?php echo $type->getCode(); ?>">
                            <?php echo $type->getName(); ?>
                        </option>
                    <?php endforeach; ?>
                </select>
            </li>
            <li class="field">
                <input type="text" name="event_name" id="event_name" value="" title="Event Name"/>
                <label class="giftreg" for="event_name"><?php echo $this->__('Event Name') ?></label>
            </li>
            <li class="field">
                <input type="text" name="event_location" id="event_location" value="" title="Event Location"/>
                <label class="giftreg" for="event_location"><?php echo $this->__('Event Location') ?></label>
            </li>
            <li class="field">
                <input type="text" name="event_country" id="event_country" value="" title="Event Country"/>
                <label class="giftreg" for="event_country"><?php echo $this->__('Event Country') ?></label>
            </li>
        </ul>
        <div class="buttons-set">
            <button type="submit" title="Save" class="button">
                <span>
                    <span><?php echo $this->__('Save') ?></span>
                </span>
            </button>
        </div>
    </fieldset>
</form>
<script type="text/javascript">
    //<![CDATA[
    var dataForm = new VarienForm('form-validate', true);
    //]]>
</script>

这一次我们正在做一些新的事情。我们正在呼叫助手;helper 是一个类,它包含可以从块、模板、控制器等重用的方法。在我们的例子中,我们正在创建一个助手,它将检索所有可用的注册表类型。遵循以下步骤:

  1. 导航到app/code/local/Mdg/Giftregistry/Helper
  2. 打开Data.php课程。
  3. 在其内部添加以下代码(文件位置为app/code/local/Mdg/Giftregistry/Helper/Data.php

    ```php <?php class Mdg_Giftregistry_Helper_Data extends Mage_Core_Helper_Abstract {

    public function getEventTypes() { $collection = Mage::getModel('mdg_giftregistry/type')->getCollection(); return $collection; } } ```

最后,我们需要设置编辑模板;编辑模板将与新模板完全相同,但有一个主要区别。我们将检查是否存在已加载的注册表,并预填充字段的值。

文件位置为app/design/frontend/base/default/template/mdg/edit.phtml。请参阅以下代码:

<?php
    $helper = Mage::helper('mdg_giftregistry');
    $loadedRegistry = Mage::getSingleton('customer/session')->getLoadedRegistry();
?>
<?php if($loadedRegistry): ?>
    <form action="<?php echo $this->getUrl('giftregistry/index/editPost/') ?>" method="post" id="form-validate">
        <fieldset>
            <?php echo $this->getBlockHtml('formkey')?>
            <input type="hidden" id="type_id" value="<?php echo $loadedRegistry->getTypeId(); ?>" />
            <ul class="form-list">
                <li class="field">
                    <label class="giftreg" for="event_name"><?php echo $this->__('Event Name') ?></label>
                    <input type="text" name="event_name" id="event_name" value="<?php echo $loadedRegistry->getEventName(); ?>" title="Event Name"/>
                </li>
                <li class="field">
                    <label class="giftreg" for="event_location"><?php echo $this->__('Event Location') ?></label>
                    <input type="text" name="event_location" id="event_location" value="<?php echo $loadedRegistry->getEventLocation(); ?>" title="Event Location"/>
                </li>
                <li class="field">
                    <label class="giftreg" for="event_country"><?php echo $this->__('Event Country') ?></label>
                    <input type="text" name="event_country" id="event_country" value="<?php echo $loadedRegistry->getEventCountry(); ?>" title="Event Country"/>
                </li>
            </ul>
            <div class="buttons-set">
                <button type="submit" title="Save" class="button">
                    <span>
                        <span><?php echo $this->__('Save') ?></span>
                    </span>
                </button>
            </div>
        </fieldset>
    </form>
    <script type="text/javascript">
        //<![CDATA[
        var dataForm = new VarienForm('form-validate', true);
        //]]>
    </script>
<?php else: ?>
    <h2><?php echo $this->__('There was a problem loading the registry') ?></h2>
<?php endif; ?>

让我们继续,为编辑操作添加块和模板:

  1. 打开mdg_giftregistry.xml布局文件。
  2. <mdg_gifregistry_index_edit>节点内添加以下代码(文件位置为app/design/frontend/base/default/layout/mdg_giftregistry.xml

    php <reference name="content"> <block name="giftregistry.edit" type="core/template" template="mdg/edit.phtml" as="giftregistry_edit"/> </reference>

设置好之后,我们可以尝试创建几个测试注册表并修改它们的属性。

这是给你的一个挑战。与控制器一样,编辑表单和新表单可以组合成一个可重用的表单。尝试将它们结合起来,查看完整代码和完整细分的答案,访问http://www.magedevguide.com/challenge/chapter4/4

搜索控制器块和视图

对于我们的搜索控制器,我们需要索引的搜索模板。对于结果,我们实际上可以通过以下步骤对控制器进行更改来重用注册表列表模板:

  1. 导航到模板文件夹。
  2. 创建一个名为search.phtml的文件。
  3. 添加以下代码(文件位置为app/design/frontend/base/default/template/mdg/search.phtml

    php <?php $helper = Mage::helper('mdg_giftregistry'); ?> <form action="<?php echo $this->getUrl('giftregistry/search/results/') ?>" method="post" id="form-validate"> <fieldset> <?php echo $this->getBlockHtml('formkey')?> <ul class="form-list"> <li> <label for="type">Event type</label> <select name="type" id="type"> <?php foreach($helper->getEventTypes() as $type): ?> <option id="<?php echo $type->getTypeId(); ?>" value="<?php echo $type->getCode(); ?>"> <?php echo $type->getName(); ?> </option> <?php endforeach; ?> </select> </li> <li class="field"> <label class="giftreg" for="name"><?php echo $this->__('Event Name') ?></label> <input type="text" name="name" id="name" value="" title="Event Name"/> </li> <li class="field"> <label class="giftreg" for="location"><?php echo $this->__('Event Location') ?></label> <input type="text" name="location" id="location" value="" title="Event Location"/> </li> <li class="field"> <label class="giftreg" for="country"><?php echo $this->__('Event Country') ?></label> <input type="text" name="country" id="country" value="" title="Event Country"/> </li> </ul> <div class="buttons-set"> <button type="submit" title="Save" class="button"> <span> <span><?php echo $this->__('Save') ?></span> </span> </button> </div> </fieldset> </form> <script type="text/javascript"> //<![CDATA[ var dataForm = new VarienForm('form-validate', true); //]]> </script>

有几件事需要注意:

  • 我们正在使用 helper 模型来填充Event类型的 ID
  • 我们将直接发布到搜索/结果

现在,让我们对布局文件进行适当的更改:

  1. 打开mdg_giftregistry.xml
  2. <mdg_gifregistry_search_index>内添加以下代码(文件位置为app/design/frontend/base/default/layout/mdg_giftregistry.xml

    php <reference name="content"> <block name="giftregistry.search" type="core/template" template="mdg/search.phtml" as="giftregistry_search"/> </reference>

对于搜索结果,我们不需要创建新的块类型,因为我们将结果集合直接传递给块。在布局中,我们的更改将是最小的,并且我们可以重用列表块来显示搜索注册表结果。

但是,我们确实需要对控制器进行更改。我们需要将功能从setResults()更改为setCustomerRegistries()

文件位置为app/code/local/Mdg/Giftregistry/controllers/SearchController.php。参考以下代码:

public function resultsAction()
{
    $this->loadLayout();
    if ($searchParams = $this->getRequest()->getParam('search_params')) {
        $results = Mage::getModel('mdg_giftregistry/entity')->getCollection();
        if($searchParams['type']){
            $results->addFieldToFilter('type_id', $searchParams['type']);
        }
        if($searchParams['date']){
            $results->addFieldToFilter('event_date', $searchParams['date']);
        }
        if($searchParams['location']){
            $results->addFieldToFilter('event_location', $searchParams['location']);
        }
        $this->getLayout()->getBlock('mdg_giftregistry.search.results')
            ->setCustomerRegistries($results);
    }
    $this->renderLayout();
    return $this;
}

最后,让我们按照以下步骤更新布局文件:

  1. 打开mdg_giftregistry.xml
  2. <mdg_gifregistry_search_results>内添加以下代码(文件位置为app/design/frontend/base/default/layout/mdg_giftregistry.xml

    php <reference name="content"> <block name="giftregistry.results" type="mdg_giftregistry/list" template="mdg/list.phtml"/> </reference>

这将是我们SearchController模板的结束;然而,我们的搜索结果显示有一个问题。对于注册表的删除和编辑链接,我们需要一种方法将这些链接仅限于所有者。

我们可以通过以下Helper功能实现:

文件位置为app/code/local/Mdg/Giftregistry/Helper/Data.php。请参阅以下代码:

public function isRegistryOwner($registryCustomerId)
{
    $currentCustomer = Mage::getSingleton('customer/session')->getCustomer();
    if($currentCustomer && $currentCustomer->getId() == $registryCustomerId)
    {
        return true;
    }
    return false;
}

让我们更新模板以使用新的helper方法。

文件位置为app/design/frontend/base/default/template/mdg/list.phtml。请参阅以下代码:

<?php
    $_collection = $this->getCustomerRegistries();
    $helper = Mage::helper('mdg_giftregistry')
?>
<div class="customer-list">
    <?php if(!$_collection->count()): ?>
        <h2><?php echo $this->__('You have no registries.') ?></h2>
        <a href="<?php echo $this->getUrl('giftregistry/index/new') ?>">
            <?php echo $this->__('Click Here to create a new Gift Registry') ?>
        </a>
    <?php else: ?>
        <ul>
            <?php foreach($_collection as $registry): ?>
                <li>
                    <h3><?php echo $registry->getEventName(); ?></h3>
                    <p><strong><?php echo $this->__('Event Date:') ?> <?php echo $registry->getEventDate(); ?></strong></p>
                    <p><strong><?php echo $this->__('Event Location:') ?> <?php echo $registry->getEventLocation(); ?></strong></p>
                    <a href="<?php echo $this->getUrl('giftregistry/view/view', array('_query' => array('registry_id' => $registry->getEntityId()))) ?>">
                        <?php echo $this->__('View Registry') ?>
                    </a>
                    <?php if($helper->isRegistryOwner($registry->getCustomerId())): ?>
                        <a href="<?php echo $this->getUrl('giftregistry/index/edit', array('_query' => array('registry_id' => $registry->getEntityId()))) ?>">
                            <?php echo $this->__('Edit Registry') ?>
                        </a>
                        <a href="<?php echo $this->getUrl('giftregistry/index/delete', array('_query' => array('registry_id' => $registry->getEntityId()))) ?>">
                            <?php echo $this->__('Delete Registry') ?>
                        </a>
                    <?php endif; ?>

                </li>
            <?php endforeach; ?>
        </ul>
    <?php endif; ?>
</div>

视图控制器块和视图

对于我们的视图,我们只需要在layout.xml文件中创建一个新模板文件和一个新条目:

  1. 导航到模板目录。
  2. 创建一个名为view.phtml的模板。
  3. 添加以下代码(文件位置为app/design/frontend/base/default/template/mdg/view.phtml

    php <?php $registry = Mage::registry('loaded_registry'); ?> <h3><?php echo $registry->getEventName(); ?></h3> <p><strong><?php $this->__('Event Date:') ?> <?php echo $registry->getEventDate(); ?></strong></p> <p><strong><?php $this->__('Event Location:') ?> <?php echo $registry->getEventLocation(); ?></strong></p>

  4. 更新布局 XML 文件<mdg_gifregistry_view_view>

    php <reference name="content"> <block name="giftregistry.view" type="core/template" template="mdg/view.phtml" as="giftregistry_view"/> </reference>

这是给你的一个挑战。改进视图表单,以便在没有实际加载的注册表时返回错误。要查看完整代码和完整细分的答案,请访问http://www.magedevguide.com/challenge/chapter4/5

向注册中心添加产品

我们已经快到本章末尾了,我们还没有讨论如何将产品添加到我们的注册中心。考虑到本书中的空间问题,我决定将此部分移至http://www.magedevguide.com/chapter6/adding-products-registry

总结

在本章中,我们涵盖了很多方面。我们已经学习了如何扩展 Magento 的前端,以及如何使用路由和控制器。

Magento 布局系统允许我们修改和控制块,并将其显示在我们的商店中。我们还开始使用 Magento 数据模型,学习了如何使用它们,以及如何处理和操作数据。

我们只触及了前端开发和数据模型的表面。在下一章中,我们将对配置、模型和数据等主题进行更多的扩展,我们将探索并创建 Magento 后端的管理部分。