鍍金池/ 教程/ PHP/ 數(shù)據(jù)和模型
開始構(gòu)建一個框架應(yīng)用程序
模塊
路由和控制器
表單和動作
結(jié)尾
開始使用 Zend Framework 2
樣式和翻譯
數(shù)據(jù)和模型

數(shù)據(jù)和模型

數(shù)據(jù)庫

通過控制器動作方法和視圖腳本,建立了 Album 模塊,是時候注意到程序中的模型部分了。記住,模型是解決程序核心目的的一部分(也將模型稱之為業(yè)務(wù)規(guī)則),在本例中,也是數(shù)據(jù)處理的一部分。使用 Zend Framework 中的 Zend\Db\TableGateway\TableGateway 類,這個類是用來查找,插入,更新和刪除數(shù)據(jù)庫中表記錄的。

通過 PHP 的 PDO 驅(qū)動,使用 MySQL,創(chuàng)建名為 zf2tutorial 數(shù)據(jù)庫,然后運行 SQL 語句創(chuàng)建一個 album 表,并插入一些數(shù)據(jù)。

CREATE TABLE album (
   id int(11) NOT NULL auto_increment,
   artist varchar(100) NOT NULL,
   title varchar(100) NOT NULL,
   PRIMARY KEY (id)
 );
 INSERT INTO album (artist, title)
     VALUES  ('The  Military  Wives',  'In  My  Dreams');
 INSERT INTO album (artist, title)
     VALUES  ('Adele',  '21');
 INSERT INTO album (artist, title)
     VALUES  ('Bruce  Springsteen',  'Wrecking Ball (Deluxe)');
 INSERT INTO album (artist, title)
     VALUES  ('Lana  Del  Rey',  'Born  To  Die');
 INSERT INTO album (artist, title)
     VALUES  ('Gotye',  'Making  Mirrors');

(所選擇的測試數(shù)據(jù)是英國亞馬遜暢銷書的相關(guān)信息!)

現(xiàn)在數(shù)據(jù)庫中有了一些數(shù)據(jù),可以為其編寫一個簡單的模型。

模型文件

Zend Framework 沒有提供 Zend\Model 控件,因為模型是你的業(yè)務(wù)邏輯,取決于你想讓它如何工作。根據(jù)你的需求,這里仍然有很多控件可以供你使用。一種方法是,在你的程序中模型類對應(yīng)一種實體,然后使用映射器對象來加載和存儲實體到數(shù)據(jù)庫。另一種是使用對象關(guān)系映射計算,例如 Doctrine 或者 Propel。

在這個教程中,每一個專輯就是一個 Album 對象(或者說是實體),通過使用 Zend\Db\TableGateway\TableGateway 構(gòu)建一個 AlbumTable 類,然后使用 AlbumTable 構(gòu)建一個簡單的模型。在數(shù)據(jù)庫的表中,Table Data Gateway 的設(shè)計模式可以實現(xiàn)數(shù)據(jù)接口。請注意,雖然 Table Data Gateway 模式在大系統(tǒng)中可能會被限制。但這個也是個誘惑,如果你把數(shù)據(jù)庫訪問代碼放進控制器的動作方法,Zend\Db\TableGateway\AbstractTableGateway 這個類就會暴露這些代碼。請不要這么做!

module/Album/src/Album/Model 路徑下新建一個 Album.php

namespace Album\Model;

class Album
{
    public $id;
    public $artist;
    public $title;

    public function exchangeArray($data)
    {
        $this->id     = (!empty($data['id'])) ? $data['id'] : null;
        $this->artist = (!empty($data['artist'])) ? $data['artist'] : null;
        $this->title  = (!empty($data['title'])) ? $data['title'] : null;
    }
}

Album 實體對象是一個簡單的 PHP 類。為了與 Zend\DbTableGateway 類一起工作。需要實現(xiàn) exchangeArray() 方法。這個方法簡單地將 data 數(shù)組中的數(shù)據(jù)拷貝到對應(yīng)實體屬性。

下一步,在 module/Album/src/Album/Model 目錄下新建 AlbumTable.php,內(nèi)容如下:

namespace Album\Model;

use Zend\Db\TableGateway\TableGateway;

class AlbumTable
{
    protected $tableGateway;

    public function __construct(TableGateway $tableGateway)
    {
        $this->tableGateway = $tableGateway;
    }

    public function fetchAll()
    {
        $resultSet = $this->tableGateway->select();
        return $resultSet;
    }

    public function getAlbum($id)
    {
        $id  = (int) $id;
        $rowset = $this->tableGateway->select(array('id' => $id));
        $row = $rowset->current();
        if (!$row) {
            throw new \Exception("Could not find row $id");
        }
        return $row;
    }

    public function saveAlbum(Album $album)
    {
        $data = array(
            'artist' => $album->artist,
            'title'  => $album->title,
        );

        $id = (int) $album->id;
        if ($id == 0) {
            $this->tableGateway->insert($data);
        } else {
            if ($this->getAlbum($id)) {
                $this->tableGateway->update($data, array('id' => $id));
            } else {
                throw new \Exception('Album id does not exist');
            }
        }
    }

    public function deleteAlbum($id)
    {
        $this->tableGateway->delete(array('id' => (int) $id));
    }
}

這里做了許多事情。首先,我們在構(gòu)造函數(shù)中設(shè)置了保護屬性 $tableGatewayTableGateway 的實例。我們將使用這個來屬性操作數(shù)據(jù)庫的 album 表。

繼續(xù)創(chuàng)建一些幫助方法,程序會使用 table gateWay 和這個實例。fetchAll() 檢索數(shù)據(jù)庫中 albums 表所有的記錄,然后將結(jié)果返回 ResultSetgetAlbum() 通過 id 檢索一個 Album 對象,saveAlbum()要么創(chuàng)建一個新的記錄,或更新已經(jīng)存在記錄,deleteAlbum()移除一條記錄。這些代碼不需要解釋的。

使用 ServiceManager 來配置 table gateway 然后注入到 AlbumTable

為了總是使用同一個的 AlbumTable 實例,我們將使用 ServiceManager 來定義如何創(chuàng)建。最容易的是,在模塊類中,我們創(chuàng)建一個名為 getServiceConfig() 的方法,它可以被 ModuleManager 自動調(diào)用,適用于 ServiceManager。然后,當我們需要它的時候,就可以取回它。

為了配置 ServiceManager,在 ServiceManager 需要的時候,我們提供一個類的名字或者一個工廠(閉包或者回調(diào)),來產(chǎn)生實例化的對象。我們通過實現(xiàn) getServiceConfig() 來提供一個工廠,這個工廠用來創(chuàng)建一個 AlbumTable。添加這個方法到 module/Album 目錄下的 Module.php 文件的底部。

namespace Album;

// Add these import statements:
use Album\Model\Album;
use Album\Model\AlbumTable;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\TableGateway;

class Module
{
    // getAutoloaderConfig() and getConfig() methods here

    // Add this method:
    public function getServiceConfig()
    {
        return array(
            'factories' => array(
                'Album\Model\AlbumTable' =>  function($sm) {
                    $tableGateway = $sm->get('AlbumTableGateway');
                    $table = new AlbumTable($tableGateway);
                    return $table;
                },
                'AlbumTableGateway' => function ($sm) {
                    $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
                    $resultSetPrototype = new ResultSet();
                    $resultSetPrototype->setArrayObjectPrototype(new Album());
                    return new TableGateway('album', $dbAdapter, null, $resultSetPrototype);
                },
            ),
        );
    }
}

這個方法返回 factories 的數(shù)組,在傳遞給 ServiceManager 之前,通過 ModuleManager 進行合并。Album\Model\AlbumTable 的工廠使用 ServiceManager 來創(chuàng)建一個 AlbumTableGateway 對象,以便傳遞到 AlbumTable 對象。一般會告知 ServiceManager 對象,AlbumTableGateway 對象的創(chuàng)建是通過得到一個 Zend\Db\Adapter\Adapter對象(也是從 ServiceManager 獲?。﹣硗瓿傻模缓笫褂?AlbumTableGateway 對象來創(chuàng)建一個 TableGateway 對象。TableGateway 對象每當他創(chuàng)建一條新記錄時,都會告知一個 Album 對象。TableGateway 類使用原型模式創(chuàng)建結(jié)果集和實體。這意味著在請求的時候不是實例化,而是系統(tǒng)克隆先前實例化的對象。參考 PHP Constructor Best Practices and the Prototype Pattern。

最終,我們需要配置 ServiceManager,讓其知道如何獲取 Zend\Db\Adapter\Adapter。這是通過使用一個工廠類 Zend\Db\Adapter\AdapterServiceFactory,我們能在合并配置系統(tǒng)中配置它。Zend Framework 2 的 ModuleManager 合并每一個模塊的 module.config.php 文件中所有的配置,然后合并導(dǎo)出到 config/autoload 下的文件(*.global.php*.local.php 文件)。我們將添加我們的數(shù)據(jù)庫配置信息到 global.php 這個文件,這個文件應(yīng)該提交到你的版本控制系統(tǒng)。如果你想的話,現(xiàn)在可以使用 local.php (版本控制系統(tǒng)之外的)來存儲數(shù)據(jù)庫的證書。修改 config/autoload/global.php (在 Zend 骨架的 root 中,并不在 Album 模塊中),代碼如下:

return array(
     'db' => array(
         'driver'         => 'Pdo',
         'dsn'            => 'mysql:dbname=zf2tutorial;host=localhost',
         'driver_options' => array(
             PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
         ),
     ),
     'service_manager' => array(
         'factories' => array(
             'Zend\Db\Adapter\Adapter'
                     => 'Zend\Db\Adapter\AdapterServiceFactory',
         ),
     ),
 );

你應(yīng)該將你的數(shù)據(jù)庫證書放到 config/autoload/local.php,所以他們不是在 Git 存儲庫(就像 local.php 會被忽略):

return array(
     'db' => array(
         'username' => 'YOUR USERNAME HERE',
         'password' => 'YOUR PASSWORD HERE',
     ),
 );

回到控制器

現(xiàn)在 ServiceManager 可以創(chuàng)建一個 AlbumTable 實例了,我們可以添加一個方法到控制器去見檢索它。添加 getAlbumTable()AlbumController 類中:

// module/Album/src/Album/Controller/AlbumController.php:
     public function getAlbumTable()
     {
         if (!$this->albumTable) {
             $sm = $this->getServiceLocator();
             $this->albumTable = $sm->get('Album\Model\AlbumTable');
         }
         return $this->albumTable;
     }

你也應(yīng)該添加下面這句到類首部:

protected $albumTable;

每當我們需要與模型進行交互的時候,就可以在控制器中調(diào)用 getAlbumTable()

如果服務(wù)定位器在 Module.php 中正確配置,那么在調(diào)用 getAlbumTable() 時將會拿到一個 Album\Model\AlbumTable 實例。

列舉專輯

為了列舉專輯,我們需要從模型中檢索他們?nèi)缓髠鬟f到視圖。為此,我們在 AlbumController 填寫 indexAction()。更新 AlbumControllerindexAction() 方式如下:

// module/Album/src/Album/Controller/AlbumController.php:
 // ...
     public function indexAction()
     {
         return new ViewModel(array(
             'albums' => $this->getAlbumTable()->fetchAll(),
         ));
     }
 // ...

在 Zend Framework 2 中,為了在視圖中設(shè)置變量,我們返回一個 ViewModel 實例,構(gòu)造函數(shù)的第一個參數(shù)是一個來自動作的數(shù)組,包含我們需要的數(shù)據(jù)。然后會自動傳遞到視圖腳本。ViewModel 對象還允許我們改變視圖腳本,但是默認是使用{controller name}/{action name}。我們現(xiàn)在可以填寫視圖腳本 index.phtml。

<?php
 // module/Album/view/album/album/index.phtml:

 $title = 'My albums';
 $this->headTitle($title);
 ?>
 <h1><?php echo $this->escapeHtml($title); ?></h1>
 <p>
     <a href="<?php echo $this->url('album', array('action'=>'add'));?>">Add new album</a>
 </p>

 <table class="table">
 <tr>
     <th>Title</th>
     <th>Artist</th>
     <th>&nbsp;</th>
 </tr>
 <?php foreach ($albums as $album) : ?>
 <tr>
     <td><?php echo $this->escapeHtml($album->title);?></td>
     <td><?php echo $this->escapeHtml($album->artist);?></td>
     <td>
         <a href="<?php echo $this->url('album',
             array('action'=>'edit', 'id' => $album->id));?>">Edit</a>
         <a href="<?php echo $this->url('album',
             array('action'=>'delete', 'id' => $album->id));?>">Delete</a>
     </td>
 </tr>
 <?php endforeach; ?>
 </table>

第一件事是我們要為頁面設(shè)置標題,標題可以在瀏覽器的標題欄顯示,可以在 <head> 部分使用視圖幫助方法 headTitle() 來進行設(shè)置標題。然后我們添加一個創(chuàng)建新 album 的鏈接。

Zend Framework 2 提供了視圖幫助方法 url(),用來創(chuàng)建 URL。url() 的第一個參數(shù)是 route,用來創(chuàng)建 URL,第二個參數(shù)是所有變量的數(shù)組,用來填充字段的。在本例中,我們使用 ‘a(chǎn)lbum’ route 以及設(shè)置兩個字段變量 actionid

我們遍歷了 $albums 中從控制器分配的動作。Zend Framework 2 視圖系統(tǒng)自動確認了填充到視圖腳本域的變量,所以我們不用擔心還要像舊框架 Zend Framework 1 中那樣使用前綴 $this-> 來區(qū)別變量,當然你仍然可以在這么做。

然后創(chuàng)建一個表格來展示每一個 album 的標題和藝術(shù)家,提供一些鏈接,用以修改和刪除記錄。一個標準的變量是:使用迭代器循環(huán)遍歷 albums 的列表,我們交替使用冒號和 endforeach 的形式;相對于嘗試匹配括號,這就可以十分簡單訪問元素了。再一次使用視圖幫助方法 url() 來創(chuàng)建修改和刪除鏈接。

注意

我們可以使用視圖幫助方法 escapeHtml() 來輔助保護我們自己的跨站腳本的缺陷。(詳情查看 http://en.wikipedia.org/wiki/Cross-site_scripting

如果你打開 http://zf2-tutorial.localhost/album,就能看到如下效果:

http://wiki.jikexueyuan.com/project/zend2-user-guide/images/databaseandmodels.png" alt="image" />