2

15 MB 以上のレポート全体ではなく、AJAX 呼び出しを使用して、要求されたものだけを動的に読み込むことで、ユーザーがサイトのレポートを読めるようにするコードを書いています。

データベースからすべてのレポート データにアクセスするモデルを作成していますが、Active Record パターンを使用したくありません。このモデルは 5 つの異なるテーブルにアクセスし、これらのテーブル間にいくつかの複雑な MySQL JOIN があるため、「モデルには IS-A テーブルではなくテーブルがあります」という考えに従っています。

この例では、Zend Framework で従うべき適切な設計パターンは何ですか?


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

私は現在、市場調査レポート会社で働いています。実際の関数名を使用したり、コードの意味のある詳細を明らかにしたりせずに、基本は次のとおりです。

コード図

readreportAction() は次のことを行います。

  • レポートのメタデータを取得する
  • レポート「目次」を取得する

readsectionAction() は次のことを行います。

  • レポート テキストの一部のみを取得する
  • 埋め込まれた表形式のデータを取得する
  • 図/画像を取得する
  • 脚注を入手する
  • レポート テキストの書式を設定する

reportpdfAction() は readreportAction() および readsectionAction() とまったく同じことを行いますが、すべてを一度に実行します。このコード/プログラミングロジックをコピーして貼り付けない方法を概念化しようとしています。データマッパーはこれを解決するようです。

4

3 に答える 3

3

Data Mapper パターンをお勧めします。

ここに画像の説明を入力

あなたが言ったことはすべて理にかなっており、このパターンが当てはまります。モデルは、それがどのように永続化されるかを認識したり気にしたりしてはなりません。代わりに、マッパーはそれが示唆することを行います - モデルをデータベースにマップします。このアプローチで私が気に入っている点の 1 つは、アクティブ レコード パターンやテーブル行ゲートウェイでよくあるように、リレーショナル データベース テーブルではなく、オブジェクトの観点からモデルを考えるように促すことです。

オブジェクトは、非常に単純でない限り、通常、データベース テーブルの構造を反映していません。これにより、適切なオブジェクトを作成し、その後の永続性について心配することができます。場合によっては、マッパーが複雑な結合を処理する必要があり、おそらくコードや SQL を記述する必要があるという点で、より手作業が必要になりますが、最終的には、必要なことだけを実行し、それ以上のことは何もしません。それらを活用したくない場合は、魔法や慣習は必要ありません。

これらの記事は、ZF でうまく使用できる設計パターンのいくつかをうまく説明しています。 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」が有効なデータベースのマッパー抽象を作成します。その具象マッパーの属性の 1 つは、コマンドを発行する Zend_Db_Adapter です。柔軟なソリューションを実現し、テストでモック データ ソースを簡単に使用できます。

于 2012-12-03T20:39:47.177 に答える
0

Doctrine 2の使用を検討できます。ActiveRecord パターンを使用しない ORM です。

Doctrine では、モデル (エンティティ) はすべて、データベースの知識がまったくない通常の PHP オブジェクトです。マッピング (xml、yaml、または注釈) を使用して Doctrine にそれらがデータベースでどのように表示されるかを伝えます。Entity Manager とリポジトリは、エンティティを永続化するか、他のデータベース アクションを実行するためのゲートウェイとして使用されます。

于 2012-12-03T20:14:44.667 に答える
0

まず、もう少し概念的な飛躍が必要なようです。データ マッパー パターンを使用すると、データベース テーブルではなくオブジェクトの観点から考えることができます。この 2 つの記事は、飛躍する必要があるときに役立ちました。

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::factoryまた、またはnew 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 に答える