2

我正在编写一些代码,允许用户阅读网站上的报告,使用 AJAX 调用动态加载仅请求的内容,而不是整个 15+MB 报告。

我正在编写一个模型来访问数据库中的所有报告数据,并且我不想使用 Active Record 模式。我遵循“一个模型有一个表,而不是 IS-A 表”的想法,因为这个模型将访问 5 个不同的表,并且这些表之间有一些复杂的 MySQL JOIN。

Zend 框架中遵循的好的设计模式是什么,示例?


更新于 2012-12-05 @ 12:14PM EST

我目前在一家市场研究报告公司工作。不使用实际的函数名称,或透露代码的任何有意义的细节,这里是基础:

代码图

readreportAction() 会:

  • 获取报表元数据
  • 获取报告“目录”

readsectionAction() 会:

  • 获取报告文本,只是其中的一部分
  • 获取嵌入的表格数据
  • 获取数字/图像
  • 获取脚注
  • 格式化报告文本

reportpdfAction() 与 readreportAction() 和 readsectionAction() 做的事情完全相同,除了一次。我正在尝试概念化一种不复制+粘贴此代码/编程逻辑的方法。数据映射器似乎可以解决这个问题。

4

3 回答 3

3

我会推荐Data Mapper 模式

在此处输入图像描述

你说的一切都很有道理,这种模式很合适。你的模型不应该知道或关心它是如何被持久化的。相反,映射器会按照它的建议进行 - 将您的模型映射到您的数据库。我喜欢这种方法的一件事是它鼓励人们根据对象而不是关系数据库表来考虑模型,就像活动记录模式和表行网关经常发生的那样。

您的对象,除非非常简单,否则通常不会反映数据库表的结构。这使您可以编写好的对象,然后再担心持久性方面。有时需要更多的手动操作,因为您的映射器需要处理复杂的连接,可能需要编写一些代码或 SQL,但最终结果是它只做您想要的,仅此而已。如果您不想利用它们,则不需要魔法或约定。

我一直认为这些文章很好地解释了一些可以在 ZF 中很好使用的设计模式:http: //survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries。 and.authors#zfbook.implementing.the.domain.model.entries.and.authors.exploring.the.entry.data.mapper

更新:

好吧,您的映射器可能会从类似于以下的接口扩展:

<?php
interface Mapper_Interface
{

    /**
     * Sets the name of the entity object used by the mapper.    
     */
    public function setObjectClass($class);

    /**
     * Sets the name of the list class used by the mapper.
     */
    public function setObjectListClass($listClass);

    /**
     * Get the name of the object class used by the mapper.
     * 
     */
    public function getObjectClass();

    /**
     * Get the name of the object list class used by the mapper.
     * 
     * @return string
     */
    public function getObjectListClass();

    /**
     * Fetch one row.
     *   
     * @param array $where Criteria for the selection.
     * @param array [$order = array()] Optionally the order of results
     * @return Object_Abstract
     * @throws Mapper_Exception
     */
    public function fetchRow($where, $order = array());

    /**
     * Fetch all records.  If there is no underlying change in the persisted data this    should
     * return a consistant result.
     * 
     * @param string|array|Zend_Db_Table_Select $where  OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
     * @param string|array                    $order  OPTIONAL An SQL ORDER clause.
     * @param int                              $count  OPTIONAL An SQL LIMIT count.
     * @param int                              $offset OPTIONAL An SQL LIMIT offset.
     * @return Object_List_Abstract 
     * @throws Mapper_Exception
     */
    public function fetchAll($where = null, $order = null, $count = null, $offset = null);

    /**
     * Deletes one or more object.
     * 
     * @param array|string $where Criteria for row deletion.
     * @return integer $affectedRows  
     * @throws Mapper_Exception
     */
    public function delete($where);

    /**
     * Saves a record. Either updates or inserts, as required.
     *   
     * @param $object Object_Abstract
     * @return integer $lastInsertId
     * @throws Mapper_Exception
     */
    public function save($object);
}

您将与映射器进行交互,例如:

$fooObjectMapper = new Foo_Mapper;
$fooObjectList = $fooObjectMapper->fetchAll();
var_dump($fooObjectList->first());

或者

$fooObjectMapper = new Foo_Mapper;
$fooObject = $fooObject->fetch(array('id = ?' => 1));
$fooObject->setActive(false);
$fooObjectMapper->save($fooObject);

我通常为任何启用“PDO”的数据库编写映射器摘要。该具体映射器的属性之一是 Zend_Db_Adapter 来发出命令。提供灵活的解决方案,易于在测试中使用模拟数据源。

于 2012-12-03T20:39:47.177 回答
0

您可以考虑使用Doctrine 2。这是一个不使用 ActiveRecord 模式的 ORM。

在 Doctrine 中,您的模型(实体)都只是普通的 PHP 对象,对数据库的了解为零。您使用映射(xml、yaml 或注释)来告诉 Doctrine 它们在数据库中的显示方式,而实体管理器和存储库用作持久实体或执行其他数据库操作的网关。

于 2012-12-03T20:14:44.667 回答
0

首先,您似乎需要在概念上进行更多的飞跃。使用数据映射器模式,它有助于根据对象而不是数据库表进行思考。当我需要实现飞跃时,我发现这两篇文章很有帮助。

http://phpmaster.com/building-a-domain-model/
http://phpmaster.com/integrating-the-data-mappers/

话虽如此,ZF 1 有一些非常有用的工具可用于构建数据映射器/域模型。

ZF 1 中的约定是通过 Zend_Db_Table api 访问您正在使用的每个表。我发现的最简单的方法是只为每个表使用DbTable 资源。您也可以使用Zend_Db::factorynew Zend_Db_Table('tableName')或任何其他吸引您的方法。

此示例基于 mp3 歌曲曲目。

//in effect this is the database adapter for database table 'track', This is $tableGateway used later.
<?php
class Application_Model_DbTable_Track extends Zend_Db_Table_Abstract
{
    //name of database table, required to be set if name of class does not match name of table
    protected $_name = 'track';
    //optional, column name of primary key
    protected $_primary = 'id';

}

有几种方法可以将表附加到 Db 适配器和 Zend_Db_Table api,我只是发现这种方法实现起来很简单,而且它也使设置映射器变得简单。

映射器类是数据源和您的对象(域实体)之间的桥梁。在本例中,映射器与 Zend_Db_Table 的 api 交互。

要理解的一个非常重要的点:当使用扩展 Zend_Db_Table_Abstract 的类时,您可以使用 Zend_Db 组件的所有基本功能。(find(),fetchall(), fetchRow(), select() ...)

<?php
class Music_Model_Mapper_Track extends Model_Mapper_Abstract
{

    //the mapper to access the songs artist object
    protected $artistMapper;
    //the mapper to access to songs album object
    protected $albumMapper;

    /**
     * accepts instance of Zend_Db_Table_Abstract
     *
     * @param Zend_Db_Table_Abstract $tableGateway
     */
    public function __construct(Zend_Db_Table_Abstract $tableGateway = null)
    {
        //at this point I tend to hardcode $tablegateway but I don't have to
        $tableGateway = new Application_Model_DbTable_Track();
        parent::__construct($tableGateway);
        //parent sets the $tablegateway variable and provides an abstract requirement
        //for createEntity(), which is the point of this class
    }
    /**
     * Creates concrete object of Music_Model_Track
     *
     * @param object $row
     * @return Music_Model_Track
     */
    public function createEntity($row)
    {
        $data = array(
            'id'           => $row->id,
            'filename'     => $row->filename,
            'format'       => $row->format,
            'genre'        => $row->genre,
            'hash'         => $row->hash,
            'path'         => $row->path,
            'playtime'     => $row->playtime,
            'title'        => $row->title,
            'track_number' => $row->track_number,
            'album'        => $row->album_id,//foriegn key
            'artist'       => $row->artist_id//foriegn key
        );
        //instantiate new entity object
        return new Music_Model_Track($data);
    }
    /**
     * findById() is proxy for find() method and returns
     * an entity object.
     *
     * @param type $id
     * @return object Model_Entity_Abstract
     */
    public function findById($id)
    {
        //instantiate the Zend_Db_Select object
        $select = $this->getGateway()->select();
        $select->where('id = ?', $id);
        //retrieve one database table row
        $row = $this->getGateway()->fetchRow($select);
        //create one entity object Music_Model_Track
        $entity = $this->createEntity($row);
        //return one entity object Music_Model_Track
        return $entity;
    }

  //truncated
}

之前所做的一切都是为了明确构建以下对象:

<?php
class Music_Model_Track extends Model_Entity_Abstract
{
    /**
     * $id, __set, __get and toArray() are implemented in the parent
     */
    protected $album;
    protected $artist;
    protected $filename;
    protected $format;
    protected $genre;
    protected $hash;
    protected $path;
    protected $playtime;
    protected $title;
    protected $track_number;
    //artist and album mappers
    protected $albumMapper  = null;
    protected $artistMapper = null;


   //these are the important accessors/mutators because they convert a foreign key
   //in the database table to an entity object.
    public function getAlbum()
    {
        //if the album object is already set, use it.
        if(!is_null($this->album) && $this->album instanceof Music_Model_Album) {
            return $this->album;
        } else {
            //else we make a new album object
            if(!$this->albumMapper) {
                $this->albumMapper = new Music_Model_Mapper_Album();
            }
            //This is the album object we get from the id in our reference array.
            return $this->albumMapper->findById($this->getReferenceId('album'));
        }
    }
    //same as above only with the artist object.
    public function getArtist()
    {
        if(!is_null($this->artist) && $this->artist instanceof Music_Model_Artist) {
            return $this->artist;
        } else {
            if(!$this->artistMapper) {
                $this->artistMapper = new Music_Model_Mapper_Artist();
            }
            return $this->artistMapper->findById($this->getReferenceId('artist'));
        }
    }
    //the setters record the foriegn keys recorded in the table row to an array,
    //this allows the album and artist objects to be loaded only when needed.
    public function setAlbum($album)
    {
        $this->setReferenceId('album', $album);
        return $this;
    }

    public function setArtist($artist)
    {
        $this->setReferenceId('artist', $artist);
        return $this;
    }

  //standard setter and getters truncated...
}

so when using the track object you would get album or artist info like:

//this would be used in a controller most likely.
$mapper = new Music_Model_Mapper_Track();
$track = $mapper->findById('1');
//all of the information contained in the album or artist object is
//available to the track object.
//echo album title, year or artist. This album object also contains the artist object
//so the artist object would be available in two ways.
echo $track->album->title; //or
echo $track->artist->name;
echo $track->album->artist->name;
echo $track->getAlbum()->getArtist()->getName();

所以你真正需要决定的是你想如何构建你的应用程序。我认为显而易见的可能不是您希望实施的选项。您的问题的很多答案都取决于这些资源的确切使用方式。

我希望这至少对您有所帮助。

于 2012-12-06T09:08:34.577 回答