在学习Magento之后学习Zend Framework:模型

9

我已经在Magento上工作了一年,学会了足够的技能。现在我想学习Zend,并且我被模型卡住了。

在Magento中,我习惯于使用实体和实体集合,我很可能会使用Zend_Db_TableZend_Db_Table_Row和/或Zend_Db_Table_Rowset。但我对每个类的角色感到困惑。

我知道我可以扩展每个类,并且我理解在我的Product_Table类(扩展了Zend_Db_Table_Abstract)中,可以有私有方法告诉Zend要使用哪些行和行集类,但我感觉不舒服。

在Magento中有这样的代码:

示例1

// I understand that maybe I'll use the `new` keyword instead
// Mage::getModel() is only for exemplification
$product = Mage::getModel('catalog/product');
$product->setName('product name');
$product->setPrice(20);
$product->save();

if($id = $product->getId()){
    echo 'Product saved with id' . $id;
}
else{
    echo 'Error saving product';
}

例子2

$collection = Mage::getModel('catalog/product')->getCollection();
// this is the limit, I'm ok with other method's name
$collection->setPageSize(10);
$collection->load()

foreach($collection as $product){
    echo $product->getName() . ' costs ' . $product->getPrice() . PHP_EOL;
}

我怎样才能在Zend Framework中实现类似的东西?或者,如果这真的是一个很糟糕的想法,那么在Zend Framework中实现模型的最佳实践是什么?

谢谢。


1
Zend Framework 的问题在于你可以以多种方式创建模型。没有固定的构建方式。你只需找到适合自己的方法并坚持下去。就我个人而言,我不会继承任何 Zend_Db 类。 - Chris Laplante
@SimpleCoder 我同意。 Zend_Db != model。这是大多数新手常犯的错误。仅仅因为它与模型中的数据有关,他们就认为它是模型。 - Xerkus
我知道ZF没有模型。问题是我如何实现模型? - s3v3n
@SimpleCoder,我不认为ZF中没有标准的Model实现是一个问题。这使您可以按照对您和应用程序有意义的方式设置数据。但是,我同意不要从Zend_Db类扩展模型。在模型内部使用Zend_Db_Select等。 - adlawson
@adlawson:同意。对我来说,当我创建我的第一个ZF项目时,它只是增加了学习曲线。现在我已经熟悉ZF了,我再也不会回到不使用框架的时候了。 - Chris Laplante
4个回答

5

正如在其他地方提到的那样,Zend团队对模型层的看法与大多数其他PHP框架创建者不同。他们目前认为使用原始工具提供基于数据库的实体模型的“最佳”方法可以在快速入门指南中找到。

话虽如此,大多数人在Zend Framework中解决模型问题的方案是引导Doctrine


1
我对快速入门指南中的解决方案感到不满意,因为我需要编写很多代码,而我本可以使用一些魔术方法来代替。然而,我的第一个尝试使用 setter 和 getter 失败了,而我现在也不想学习 Doctrine(至少目前不想)。 - s3v3n
1
我必须同意Doctrine的建议。它并不难学,而且可以相当优雅地解决整个问题。我再也不会创建没有Doctrine的ZF项目了。 - CashIsClay
我必须承认,Zend的本地模型让我非常失望。但是,由于Zend完全是模块化的,没有理由不用优秀的Doctrine替换不太令人满意的部分。Doctrine得到了+1的评价。 - cwallenpoole

4
以下是我个人实现模型的方式。我将使用一个真实的例子:我的“User”模型。
每当我创建一个模型时,我使用两个文件和两个类:该模型本身(例如“Application_Model_User”)和一个映射器对象(例如“Application_Model_UserMapper”)。模型本身显然包含数据、保存、删除、修改等方法。映射器对象包含获取模型对象、查找对象等方法。
这里是用户模型的前几行代码:
class Application_Model_User {

    protected $_id;
    protected $_name;
    protected $_passHash;
    protected $_role;
    protected $_fullName;
    protected $_email;
    protected $_created;
    protected $_salt;

    // End protected properties

对于每个属性,我都有一个getter和setter方法。例如对于id

/* id */

public function getId() {
    return $this->_id;
}

public function setId($value) {
    $this->_id = (int) $value;
    return $this;
}

我还使用了一些标准的"魔法方法"来公开暴露getter和setter(在每个模型的底部):

public function __set($name, $value) {
    $method = 'set' . $name;
    if (('mapper' == $name) || !method_exists($this, $method)) {
        throw new Exception('Invalid user property');
    }
    $this->$method($value);
}

public function __get($name) {
    $method = 'get' . $name;
    if (('mapper' == $name) || !method_exists($this, $method)) {
        throw new Exception('Invalid user property');
    }
    return $this->$method();
}

public function setOptions(array $options) {
    $methods = get_class_methods($this);
    foreach ($options as $key => $value) {
        $method = 'set' . ucfirst($key);
        if (in_array($method, $methods)) {
            $this->$method($value);
        }
    }
    return $this;
}

示例的save方法:

我在save()方法内进行验证,在信息未通过验证时使用异常。

public function save() {        
    // Validate username
    if (preg_match("/^[a-zA-Z](\w{6,15})$/", $this->_name) === 0) {
        throw new Application_Exception_UserInfoInvalid();
    }

    // etc.

    $db = Zend_Registry::get("db");

    // Below, I would check if $this->_id is null. If it is, then we need to "insert" the data into the database. If it isn't, we need to "update" the data. Use $db->insert() or $db->update(). If $this->_id is null, I might also initialize some fields like 'created' or 'salt'.
}

对于mapper对象,我至少有两种方法:一种方法返回一个查询对象以选择对象,另一种方法执行查询,初始化并返回对象。我这样做是为了在控制器中操作查询以进行排序和过滤。

编辑

正如我在评论中所说,这篇文章http://weierophinney.net/matthew/archives/202-Model-Infrastructure.html启发了我当前的模型实现。

更多选项

您也可以使用Zend_Form进行验证,而不是自己编写代码: http://weierophinney.net/matthew/archives/200-Using-Zend_Form-in-Your-Models.html。我个人不喜欢这个选项,因为我认为Zend_Form很难使用和精确控制。

当大多数人第一次学习Zend Framework时,他们学习如何子类化与Zend_Db相关的类。这里有一篇文章演示了这一点: http://akrabat.com/zend-framework/on-models-in-a-zend-framework-application/ 我提到我不喜欢这样做的原因如下:
- 很难创建包含派生/计算字段(即从其他表填充数据)的模型 - 我发现无法合并来自我的数据库的访问控制 - 我喜欢对我的模型有完全控制权 编辑2 对于您的第二个示例:您可以使用Zend_Paginator。我提到,在您的包装器中,您可以创建一个方法,返回用于选择对象的数据库查询对象。这是我的简化但有效的用户映射程序:
class Application_Model_UserMapper {

    public function generateSelect() {
        $db = Zend_Registry::get("db");

        $selectWhat = array(
            "users_id",
            "name",
            "role",
            "full_name",
            "email",
            "DATE_FORMAT(created, '%M %e, %Y at %l:%i:%s %p') as created",
            "salt",
            "passhash"
        );

        return $db->select()->from(array("u" => "users"), $selectWhat);
    }


    public function fetchFromSelect($select) {
        $rows = $select->query()->fetchAll();
        $results = array();

        foreach ($rows as $row) {
            $user = new Application_Model_User();

            $user->setOptions(array(
                "id" => $row["users_id"],
                "name" => $row["name"],
                "role" => $row["role"],
                "fullName" => $row["full_name"],
                "email" => $row["email"],
                "created" => $row["created"],
                "salt" => $row["salt"],
                "passHash" => $row["passhash"]
            ));

            $results[] = $user;
        }

        return $results;
    }

}

为了处理分页器,我编写了一个自定义的Paginator插件,并将其保存到library/Application/Paginator/Adapter/Users.php。确保在application.ini中正确设置了appnamespaceautoloaderNamespaces[]。这是插件的代码:
class Application_Paginator_Adapter_Users extends Zend_Paginator_Adapter_DbSelect {
    public function getItems($offset, $itemCountPerPage) {
        // Simply inject the limit clause and return the result set
        $this->_select->limit($itemCountPerPage, $offset);
        $userMapper = new Application_Model_UserMapper();
        return $userMapper->fetchFromSelect($this->_select);
    }
}

在我的控制器中:
// Get the base select statement
$userMapper = new Application_Model_UserMapper();
$select = $userMapper->generateSelect();

// Create our custom paginator instance
$paginator = new Zend_Paginator(new Application_Paginator_Adapter_Users($select));

// Set the current page of results and per page count
$paginator->setCurrentPageNumber($this->_request->getParam("page"));
$paginator->setItemCountPerPage(25);

$this->view->usersPaginator = $paginator;

然后在您的视图脚本中呈现分页器。

非常感谢您的回答!我将为此答案发起一项悬赏,以获得更多可能的模型实现,但无论如何,我都非常感谢您的回答,谢谢! - s3v3n
@s3v3n:很高兴能帮上忙!这是启发我当前进行模型的博客文章:http://weierophinney.net/matthew/archives/202-Model-Infrastructure.html;其中一个区别是该文章的作者将映射器称为“网关”。 - Chris Laplante
谢谢 :) 你正在使用魔术方法,这给了我一些想法,我开始了一些实现。我会在这里发布我的实现(我不知道是否可以向自己提供赏金,但无论如何我都不会这样做 :))。 - s3v3n

2
我做的事情类似于SimpleCode的方式。我的风格源自Pádraic Brady。他有多篇博客文章,但他最好、最快的资源是他写的一本在线书籍:Survive the Deep End!。这个链接应该会直接带你到他关于模型、数据映射器以及其他很酷的东西(如惰性加载)的章节。思路如下:
你有实体,例如一个用户,它的属性在一个数组中定义。所有实体都扩展了一个抽象类,具有魔法getter/setter,可以从这个数组中获取或更新它们。
class User extends Entity
{
    protected $_data = array(
        'user_id' => 0,
        'first_name' => null,
        'last_name' => null
    );
}

class Car extends Entity
{
    protected $_data = array(
        'car_id' => 0,
        'make' => null,
        'model' => null
    );
}

class Entity
{
    public function __construct($data)
    {
        if(is_array($data))
        {
            $this->setOptions($data);
        }
    }

    public function __get($key)
    {
        if(array_key_exists($key, $this->_data)
        {
            return $this->_data[$key];
        }

        throw new Exception("Key {$key} not found.");
    }

    public function __set($key, $value)
    {
        if(array_key_exists($key, $this->_data))
        {
            $this->_data[$key] = $value;
        }

        throw new Exception("Key {$key} not found.");
    }

    public function setOptions($data)
    {
        if(is_array($data))
        {   
            foreach($data as $key => $value)
            {
                $this->__set($key, $value);
            }
        }
    }

    public function toArray()
    {
        return $this->_data;
    }
}

$user = new User();
$user->first_name = 'Joey';
$user->last_name = 'Rivera';

echo $user->first_name; // Joey

$car = new Car(array('make' => 'chevy', 'model' => 'corvette'));
echo $car->model; // corvette

对我来说,数据映射器与实体是分开的,它们的工作是对数据库执行CRUD(创建、读取、更新和删除)操作。因此,如果我们需要从数据库加载一个实体,我会调用特定于该实体的映射器来加载它。例如:
<?php

class UserMapper
{
    $_db_table_name = 'UserTable';
    $_model_name = 'User';

    public function find($id)
    {
        // validate id first

        $table = new $this->_db_table_name();
        $rows = $table->find($id);

        // make sure you get data

        $row = $rows[0]; // pretty sure it returns a collection even if you search for one id
        $user = new $this->_model_name($row); // this works if the naming convention matches the user and db table
        //else
        $user = new $this->_model_name();

        foreach($row as $key => $value)
        {
            $user->$key = $value;
        }

        return $user;
    }
}

$mapper = new UserMapper();
$user = $mapper->find(1); // assuming the user in the previous example was id 1
echo $user->first_name; // Joey

这段代码是为了展示如何以这种方式构建代码的思路。我没有测试过,因此在编写时可能会出现一些拼写错误/语法错误。正如其他人所提到的,Zend允许您对模型进行任何操作,没有对错之分,真正取决于您。通常,我为要使用的每个数据库表创建一个表类。因此,如果我有一个用户表,我通常会有一个User实体、User Mapper和User Table类。UserTable将扩展Zend_Db_Table_Abstract,并根据我的需要不会有任何方法或者有时我会覆盖像插入或删除这样的方法。我最终会得到很多文件,但我相信代码的分离使得我可以更快地到达需要添加更多功能或修复错误的位置,因为我知道代码的所有部分都在哪里。

希望这有所帮助。


创建一个带有共享样板的基础模型类是个好主意。我已经将其应用到了自己的项目中,谢谢! - Chris Laplante

0

文件夹结构

application
--models
----DbTable
------User.php
--controllers
----IndexController.php
--forms
----User.php
--views
----scripts
------index
--------index.phtml

application/models/DbTable/User.php

class Application_Model_DbTable_User extends Zend_Db_Table_Abstract
{
    protected $_name = 'users';
    protected $_primary = 'user_id';
}

application/forms/User.php

class Form_User extends Zend_Form
{
    public function init()
    {       
        $this->setAction('')
            ->setMethod('post');

        $user_name = new Zend_Form_Element_Text('user_name');
        $user_name->setLabel("Name")->setRequired(true);

        $user_password = new Zend_Form_Element_Text('user_password');
        $user_password->setLabel("Password")->setRequired(true);

        $submit = new Zend_Form_Element_Submit('submit');
        $submit->setLabel('Save');

        $this->addElements(array(
            $user_name,
            $user_password,
            $submit
        ));
    }
}

application/controllers/IndexController.php

class IndexController extends Zend_Controller_Action
{
    public function init()
    {

    }

    public function indexAction()
    {
        $form = new Form_User();
        if($this->getRequest()->isPost() && $form->isValid($this->getRequest()->getPost()))
        {
            $post = $this->getRequest()->getPost();
            unlink($post['submit']);

            $ut = new Application_Model_DbTable_User();
            if($id = $ut->insert($post))
            {
                $this->view->message = "User added with id {$id}";
            } else {
                $this->view->message = "Sorry! Failed to add user";
            }
        }
        $this->view->form = $form;
    }
}

application/views/scripts/index/index.phtml

echo $this->message;
echo $this->form;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接