Zend框架模型,Active Record模式替代

时间:2012-12-03 20:09:50

标签: php zend-framework activerecord

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

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

Zend Framework中有哪些好的设计模式,例如?


2012-12-05美国东部时间下午12:14更新

我目前正在为市场研究报告公司工作。在不使用实际函数名称或显示代码的任何有意义的细节的情况下,以下是基础知识:

code diagram

readreportAction()可以:

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

readsectionAction()确实:

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

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

3 个答案:

答案 0 :(得分:2)

我会推荐Data Mapper pattern

enter image description here

你所说的一切都很有意义,这种模式很合适。您的模型不应该知道或关心它是如何持久化的。相反,映射器会执行它的建议 - 将模型映射到数据库。我喜欢这种方法的一个方面是鼓励人们根据对象而不是关系数据库表来思考模型,就像活动记录模式和表行网关一样。

除非非常简单,否则您的对象通常不会反映数据库表的结构。这使您可以编写好的对象,然后担心持久性方面。有时需要更多手动,因为你的映射器需要处理复杂的连接,可能需要编写一些代码或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

<强>更新

你的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来发出命令。提供灵活的解决方案,在测试中易于使用模拟数据源。

答案 1 :(得分:0)

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

在Doctrine中,您的模型(实体)都只是普通的PHP对象,对数据库一无所知。您可以使用映射(xml,yaml或annotations)来告诉Doctrine它们在数据库中的显示方式,并将Entity Manager和存储库用作持久化实体或执行其他数据库操作的网关。

答案 2 :(得分:0)

首先看起来你需要做一点概念上的飞跃。使用数据映射器模式,它有助于根据对象而不是数据库表进行思考。当我需要实现飞跃时,我发现这两篇文章很有帮助。

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

据说ZF 1有一些非常有用的工具来构建数据映射器/域模型。

ZF 1中的约定适用于您正在使用的每个表,可通过Zend_Db_Table api访问。我发现最简单的方法就是为每个表使用DbTable resource。您还可以使用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,我发现这个方法很容易实现,它也使得设置mapper变得简单。

mapper类是数据源和对象(域实体)之间的桥梁。在此示例中,映射器与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();

因此,您真正需要决定的是如何构建应用程序。我认为显而易见的可能不是您希望实施的选项。您的问题的许多答案取决于如何使用这些资源。

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