在上一個章節(jié)中我們介紹了映射層并且創(chuàng)建了 PostMapperInterface
。現(xiàn)在是時候?qū)⑦@個接口進(jìn)行實現(xiàn)了,以便讓我們能再次使用 PostService
。作為一個指導(dǎo)性示例,我們會使用 Zend\Db\Sql
類。
在我們能開始使用數(shù)據(jù)庫之前,我們應(yīng)該先準(zhǔn)備一個數(shù)據(jù)庫。在這個示例中我們會使用一個 MySQL 數(shù)據(jù)庫,名稱為 blog
,并且可以在 localhost
上被訪問。這個數(shù)據(jù)庫會擁有一個叫做 posts
的表,表擁有三個屬性 id
、title
、text
,其中 id
是主鍵。為了演示需要,請使用這個數(shù)據(jù)庫 dump:
CREATE TABLE posts (
id int(11) NOT NULL auto_increment,
title varchar(100) NOT NULL,
text TEXT NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO posts (title, text)
VALUES ('Blog #1', 'Welcome to my first blog post');
INSERT INTO posts (title, text)
VALUES ('Blog #2', 'Welcome to my second blog post');
INSERT INTO posts (title, text)
VALUES ('Blog #3', 'Welcome to my third blog post');
INSERT INTO posts (title, text)
VALUES ('Blog #4', 'Welcome to my fourth blog post');
INSERT INTO posts (title, text)
VALUES ('Blog #5', 'Welcome to my fifth blog post');
要使用 Zend\Db\Sql
來查詢一個數(shù)據(jù)庫,你需要擁有一個可用的數(shù)據(jù)庫連接。這個鏈接使用過任何實現(xiàn) Zend\Db\Adapter\AdapterInterface
接口的類創(chuàng)建的。最方便的創(chuàng)建這種類的方法是通過使用(監(jiān)聽配置鍵 db
的) Zend\Db\Adapter\AdapterServiceFactory
。讓我們從創(chuàng)建所需配置條目開始,修改你的 module.config.php
文件,添加一個頂級鍵 db
:
<?php
// 文件名: /module/Blog/config/module.config.php
return array(
'db' => array(
'driver' => 'Pdo',
'username' => 'SECRET_USERNAME', //edit this
'password' => 'SECRET_PASSWORD', //edit this
'dsn' => 'mysql:dbname=blog;host=localhost',
'driver_options' => array(
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
)
),
'service_manager' => array( /** ServiceManager Config */ ),
'view_manager' => array( /** ViewManager Config */ ),
'controllers' => array( /** ControllerManager Config */ ),
'router' => array( /** Router Config */ )
);
如您所見我們已經(jīng)添加了 db
鍵,并且在里面我們添加了必要的參數(shù)來創(chuàng)建一個驅(qū)動示例。
注意:一個需要注意的重要事情是,通常你不會希望你的憑證存放在一個普通的配置文件中,而是希望存放在本地配置文件中,例如
/config/autoload/db.local.php
。在使用 zend 骨架 .gitignore 文件標(biāo)記時,存放在本地的文件不會被推送到服務(wù)器。當(dāng)您共享您的代碼時請務(wù)必注意這點。參考下例代碼:
<?php
// 文件名: /config/autoload/db.local.php
return array(
'db' => array(
'driver' => 'Pdo',
'username' => 'SECRET_USERNAME', //edit this
'password' => 'SECRET_PASSWORD', //edit this
'dsn' => 'mysql:dbname=blog;host=localhost',
'driver_options' => array(
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
)
),
);
接下來我們要做的事情就是利用 AdapterServiceFactory
。這是一個 ServiceManager
條目,看上去像下面這樣:
<?php
// 文件名: /module/Blog/config/module.config.php
return array(
'db' => array(
'driver' => 'Pdo',
'username' => 'SECRET_USERNAME', //edit this
'password' => 'SECRET_PASSWORD', //edit this
'dsn' => 'mysql:dbname=blog;host=localhost',
'driver_options' => array(
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
)
),
'service_manager' => array(
'factories' => array(
'Blog\Service\PostServiceInterface' => 'Blog\Service\Factory\PostServiceFactory',
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory'
)
),
'view_manager' => array( /** ViewManager Config */ ),
'controllers' => array( /** ControllerManager Config */ ),
'router' => array( /** Router Config */ )
);
請注意名為 Zend\Db\Adapter\Adapter
的新 Service?,F(xiàn)在調(diào)用這個 Service 終會得到一個正在運行的,實現(xiàn) Zend\Db\Adapter\AdapterInterface
接口的一個示例,這取決于我們指定的驅(qū)動。
當(dāng) Adapter 就位時,我們現(xiàn)在可以對數(shù)據(jù)庫進(jìn)行查詢了。查詢的構(gòu)造最好通過 Zend\Db\Sql
的 “QueryBuilder” 功能完成,若要進(jìn)行 select 查詢則使用 Zend\Db\Sql\Sql
;若要進(jìn)行 insert 查詢則使用 Zend\Db\Sql\Insert
;若要進(jìn)行 update 或者 delete 查詢則分別使用 Zend\Db\Sql\Update
和 Zend\Db\Sql\Delete
。這些組件的基本工作流是:
Sql
、Insert
、Update
或 Delete
的查詢Sql
對象創(chuàng)建一個 SQL 語句知道這些之后我們現(xiàn)在可以編寫 PostMapperInterface
接口的實現(xiàn)了。
我們的映射器實現(xiàn)會存放在和它的接口同樣的名稱空間內(nèi)?,F(xiàn)在開始創(chuàng)建一個類,稱之為 ZendDbSqlMapper
然后 implements PostMapperInterface
。
現(xiàn)在回想我們之前學(xué)到的東西。為了讓 Zend\Db\Sql
能工作我們需要一個可用的 AdapterInterface
接口的實現(xiàn)。這是一個要求,所以會通過構(gòu)造器注入進(jìn)行注入。創(chuàng)建一個 __construct()
函數(shù)來接收 AdapterInterface
作為參數(shù),并且將其存放在類中:
<?php
// 文件名: /module/Blog/src/Blog/Mapper/ZendDbSqlMapper.php
namespace Blog\Mapper;
use Blog\Model\PostInterface;
use Zend\Db\Adapter\AdapterInterface;
class ZendDbSqlMapper implements PostMapperInterface
{
/**
* @var \Zend\Db\Adapter\AdapterInterface
*/
protected $dbAdapter;
/**
* @param AdapterInterface $dbAdapter
*/
public function __construct(AdapterInterface $dbAdapter)
{
$this->dbAdapter = $dbAdapter;
}
/**
* @param int|string $id
*
* @return PostInterface
* @throws \InvalidArgumentException
*/
public function find($id)
{
}
/**
* @return array|PostInterface[]
*/
public function findAll()
{
}
}
如您從以往的課程學(xué)到的內(nèi)容,每當(dāng)我們被要求參數(shù),就要為這個類編寫 factory。所以現(xiàn)在去為我們的映射器實現(xiàn)創(chuàng)建一個 factory 吧。
現(xiàn)在我們可以將映射器實現(xiàn)注冊成一個 Service 了。如果你能回想起以往的章節(jié),或者你有去查看一下當(dāng)前的錯誤信息,你就會注意到我們通過調(diào)用 Blog\Mapper\PostMapperInterface
Service 來獲取映射器的實現(xiàn)。修改配置文件,以便讓這個鍵能調(diào)用我們剛調(diào)用的 factory 類。
<?php
// 文件名: /module/Blog/config/module.config.php
return array(
'db' => array( /** Db Config */ ),
'service_manager' => array(
'factories' => array(
'Blog\Mapper\PostMapperInterface' => 'Blog\Factory\ZendDbSqlMapperFactory',
'Blog\Service\PostServiceInterface' => 'Blog\Service\Factory\PostServiceFactory',
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory'
)
),
'view_manager' => array( /** ViewManager Config */ ),
'controllers' => array( /** ControllerManager Config */ ),
'router' => array( /** Router Config */ )
);
當(dāng) adapter 就緒之后,你就能刷新博客站點 localhost:8080/blog
,然后發(fā)現(xiàn) ServiceNotFoundException
異常已經(jīng)消失,取而代之的是一個 PHP 警告信息:
Warning: Invalid argument supplied for foreach() in /module/Blog/view/blog/list/index.phtml on line 13
ID Text Title
這是因為實際上我們的映射器還不會返回任何東西。讓我們來修改一下 findAll()
函數(shù)來從數(shù)據(jù)庫表中返回所有博客帖子。
<?php
// 文件名: /module/Blog/src/Blog/Mapper/ZendDbSqlMapper.php
namespace Blog\Mapper;
use Zend\Db\Adapter\AdapterInterface;
class ZendDbSqlMapper implements PostMapperInterface
{
/**
* @var \Zend\Db\Adapter\AdapterInterface
*/
protected $dbAdapter;
/**
* @param AdapterInterface $dbAdapter
*/
public function __construct(AdapterInterface $dbAdapter)
{
$this->dbAdapter = $dbAdapter;
}
/**
* @param int|string $id
*
* @return \Blog\Entity\PostInterface
* @throws \InvalidArgumentException
*/
public function find($id)
{
}
/**
* @return array|\Blog\Entity\PostInterface[]
*/
public function findAll()
{
$sql = new Sql($this->dbAdapter);
$select = $sql->select('posts');
$stmt = $sql->prepareStatementForSqlObject($select);
$result = $stmt->execute();
return $result;
}
}
上述代碼應(yīng)該看上去十分直接??上У氖?,再次刷新頁面會發(fā)現(xiàn)另外一個錯誤信息。
讓我們暫時先不要返回 $result
變量,然后生成一個 dump 來看看到底這里發(fā)生了什么。更改 findAll()
函數(shù)來做一個 $result
變量的數(shù)據(jù) dump:
<?php
// 文件名: /module/Blog/src/Blog/Mapper/ZendDbSqlMapper.php
namespace Blog\Mapper;
use Blog\Model\PostInterface;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\Sql\Sql;
class ZendDbSqlMapper implements PostMapperInterface
{
/**
* @var \Zend\Db\Adapter\AdapterInterface
*/
protected $dbAdapter;
/**
* @param AdapterInterface $dbAdapter
*/
public function __construct(AdapterInterface $dbAdapter)
{
$this->dbAdapter = $dbAdapter;
}
/**
* @param int|string $id
*
* @return PostInterface
* @throws \InvalidArgumentException
*/
public function find($id)
{
}
/**
* @return array|PostInterface[]
*/
public function findAll()
{
$sql = new Sql($this->dbAdapter);
$select = $sql->select('posts');
$stmt = $sql->prepareStatementForSqlObject($select);
$result = $stmt->execute();
\Zend\Debug\Debug::dump($result);die();
}
}
刷新應(yīng)用程序后你應(yīng)該能看見以下輸出:
object(Zend\Db\Adapter\Driver\Pdo\Result)#303 (8) {
["statementMode":protected] => string(7) "forward"
["resource":protected] => object(PDOStatement)#296 (1) {
["queryString"] => string(29) "SELECT `posts`.* FROM `posts`"
}
["options":protected] => NULL
["currentComplete":protected] => bool(false)
["currentData":protected] => NULL
["position":protected] => int(-1)
["generatedValue":protected] => string(1) "0"
["rowCount":protected] => NULL
}
如您所見,沒有任何數(shù)據(jù)被返回,取而代之的是我們得到了一些 Result
對象的 dump,并且里面并不包含任何有用的數(shù)據(jù)。不過這是一個錯誤的推論,這個 Result
對象只有在當(dāng)你實際試圖訪問的時候才會擁有信息。所以若要利用 Result
對象內(nèi)的數(shù)據(jù),最佳方案是將 Result
對象傳給 ResultSet
對象,只要查詢成功就可以這樣做。
<?php
// 文件名: /module/Blog/src/Blog/Mapper/ZendDbSqlMapper.php
namespace Blog\Mapper;
use Blog\Model\PostInterface;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\Adapter\Driver\ResultInterface;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\Sql\Sql;
class ZendDbSqlMapper implements PostMapperInterface
{
/**
* @var \Zend\Db\Adapter\AdapterInterface
*/
protected $dbAdapter;
/**
* @param AdapterInterface $dbAdapter
*/
public function __construct(AdapterInterface $dbAdapter)
{
$this->dbAdapter = $dbAdapter;
}
/**
* @param int|string $id
*
* @return PostInterface
* @throws \InvalidArgumentException
*/
public function find($id)
{
}
/**
* @return array|PostInterface[]
*/
public function findAll()
{
$sql = new Sql($this->dbAdapter);
$select = $sql->select('posts');
$stmt = $sql->prepareStatementForSqlObject($select);
$result = $stmt->execute();
if ($result instanceof ResultInterface && $result->isQueryResult()) {
$resultSet = new ResultSet();
\Zend\Debug\Debug::dump($resultSet->initialize($result));die();
}
die("no data");
}
}
再次刷新頁面,你應(yīng)該能看見 ResultSet
對象的 dump 現(xiàn)在擁有屬性 ["count":protected] => int(5)
,這意味著我們有5列元組在我們的數(shù)據(jù)庫中。
object(Zend\Db\ResultSet\ResultSet)#304 (8) {
["allowedReturnTypes":protected] => array(2) {
[0] => string(11) "arrayobject"
[1] => string(5) "array"
}
["arrayObjectPrototype":protected] => object(ArrayObject)#305 (1) {
["storage":"ArrayObject":private] => array(0) {
}
}
["returnType":protected] => string(11) "arrayobject"
["buffer":protected] => NULL
["count":protected] => int(2)
["dataSource":protected] => object(Zend\Db\Adapter\Driver\Pdo\Result)#303 (8) {
["statementMode":protected] => string(7) "forward"
["resource":protected] => object(PDOStatement)#296 (1) {
["queryString"] => string(29) "SELECT `posts`.* FROM `posts`"
}
["options":protected] => NULL
["currentComplete":protected] => bool(false)
["currentData":protected] => NULL
["position":protected] => int(-1)
["generatedValue":protected] => string(1) "0"
["rowCount":protected] => int(2)
}
["fieldCount":protected] => int(3)
["position":protected] => int(0)
}
另外一個非常有趣的屬性是 ["returnType":protected] => string(11) "arrayobject"
。這告訴了我們所有的數(shù)據(jù)庫條目都會以 ArrayObject
的形式返回。這產(chǎn)生了一個小問題,因為 PostMapperInterface
要求我們返回一個 PostInterface
對象數(shù)組,幸運的是這里有非常簡單的辦法讓我們來解決它。在上面的例子中我們使用了默認(rèn)的 ResultSet
對象。其實這里還有 HydratingResultSet
對象來將給出的數(shù)據(jù)充水成給出的對象。意思就是,如果我們告訴 HydratingResultSet
對象來使用數(shù)據(jù)庫數(shù)據(jù)為我們生成 post
對象,那么它就能做到。讓我們修改一下我們的代碼:
<?php
// 文件名: /module/Blog/src/Blog/Mapper/ZendDbSqlMapper.php
namespace Blog\Mapper;
use Blog\Model\PostInterface;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\Adapter\Driver\ResultInterface;
use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Db\Sql\Sql;
class ZendDbSqlMapper implements PostMapperInterface
{
/**
* @var \Zend\Db\Adapter\AdapterInterface
*/
protected $dbAdapter;
/**
* @param AdapterInterface $dbAdapter
*/
public function __construct(AdapterInterface $dbAdapter)
{
$this->dbAdapter = $dbAdapter;
}
/**
* @param int|string $id
*
* @return PostInterface
* @throws \InvalidArgumentException
*/
public function find($id)
{
}
/**
* @return array|PostInterface[]
*/
public function findAll()
{
$sql = new Sql($this->dbAdapter);
$select = $sql->select('posts');
$stmt = $sql->prepareStatementForSqlObject($select);
$result = $stmt->execute();
if ($result instanceof ResultInterface && $result->isQueryResult()) {
$resultSet = new HydratingResultSet(new \Zend\Stdlib\Hydrator\ClassMethods(), new \Blog\Model\Post());
return $resultSet->initialize($result);
}
return array();
}
}
我們又進(jìn)行了幾項更改。首先我們將普通的 ResultSet
替換成 HydratingResultSet
。這個對象要求兩個參數(shù),第一個是使用的 hydrator
類型,第二個是 hydrator
目標(biāo)對象。hydrator
簡單來說,就是一個對象用來將任意類型的數(shù)據(jù)從一種格式轉(zhuǎn)換成另一種。我們現(xiàn)在的輸入類型是 ArrayObject
,但是我們想要 Post
模型。而 ClassMethods
hydrator 搞定這個轉(zhuǎn)換問題,通過調(diào)用我們的 Post
模型的 getter 和 setter 函數(shù)。
比起去 dump $result
變量,我們現(xiàn)在直接返回初始化過的 HydratingResultSet
對象,從而得以訪問里面存儲的數(shù)據(jù)。如果我們得到了一些不是 ResultInterface
的實例的返回值,那么就會返回一個空數(shù)組。
刷新頁面,現(xiàn)在你就能看見你所有的博客帖子了,很好!
在我們完成的事情中,還有一件事情并沒有做到最佳實踐。我們同時使用 hydrator
和一個對象在下述文件內(nèi):
<?php
// 文件名: /module/Blog/src/Blog/Mapper/ZendDbSqlMapper.php
namespace Blog\Mapper;
use Blog\Model\PostInterface;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\Adapter\Driver\ResultInterface;
use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Db\Sql\Sql;
use Zend\Stdlib\Hydrator\HydratorInterface;
class ZendDbSqlMapper implements PostMapperInterface
{
/**
* @var \Zend\Db\Adapter\AdapterInterface
*/
protected $dbAdapter;
/**
* @var \Zend\Stdlib\Hydrator\HydratorInterface
*/
protected $hydrator;
/**
* @var \Blog\Model\PostInterface
*/
protected $postPrototype;
/**
* @param AdapterInterface $dbAdapter
* @param HydratorInterface $hydrator
* @param PostInterface $postPrototype
*/
public function __construct(
AdapterInterface $dbAdapter,
HydratorInterface $hydrator,
PostInterface $postPrototype
) {
$this->dbAdapter = $dbAdapter;
$this->hydrator = $hydrator;
$this->postPrototype = $postPrototype;
}
/**
* @param int|string $id
*
* @return PostInterface
* @throws \InvalidArgumentException
*/
public function find($id)
{
}
/**
* @return array|PostInterface[]
*/
public function findAll()
{
$sql = new Sql($this->dbAdapter);
$select = $sql->select('posts');
$stmt = $sql->prepareStatementForSqlObject($select);
$result = $stmt->execute();
if ($result instanceof ResultInterface && $result->isQueryResult()) {
$resultSet = new HydratingResultSet($this->hydrator, $this->postPrototype);
return $resultSet->initialize($result);
}
return array();
}
}
現(xiàn)在我們的映射器需要更多的參數(shù)來更新 ZendDbSqlMapperFactory
并且注入那些參數(shù)了。
<?php
// 文件名: /module/Blog/src/Blog/Factory/ZendDbSqlMapperFactory.php
namespace Blog\Factory;
use Blog\Mapper\ZendDbSqlMapper;
use Blog\Model\Post;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Stdlib\Hydrator\ClassMethods;
class ZendDbSqlMapperFactory implements FactoryInterface
{
/**
* Create service
*
* @param ServiceLocatorInterface $serviceLocator
*
* @return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new ZendDbSqlMapper(
$serviceLocator->get('Zend\Db\Adapter\Adapter'),
new ClassMethods(false),
new Post()
);
}
}
當(dāng)這些就緒之后,你可以再次刷新應(yīng)用程序,就能看見你的博客帖子再次顯示出來了。我們的映射器現(xiàn)在擁有一個很不錯的架構(gòu),并且沒有更多隱含的依賴對象了。
在我們跨越到下一個章節(jié)之前,先快速地通過為 find()
函數(shù)編寫一個實現(xiàn)來完成映射器。
<?php
// 文件名: /module/Blog/src/Blog/Mapper/ZendDbSqlMapper.php
namespace Blog\Mapper;
use Blog\Model\PostInterface;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\Adapter\Driver\ResultInterface;
use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Db\Sql\Sql;
use Zend\Stdlib\Hydrator\HydratorInterface;
class ZendDbSqlMapper implements PostMapperInterface
{
/**
* @var \Zend\Db\Adapter\AdapterInterface
*/
protected $dbAdapter;
/**
* @var \Zend\Stdlib\Hydrator\HydratorInterface
*/
protected $hydrator;
/**
* @var \Blog\Model\PostInterface
*/
protected $postPrototype;
/**
* @param AdapterInterface $dbAdapter
* @param HydratorInterface $hydrator
* @param PostInterface $postPrototype
*/
public function __construct(
AdapterInterface $dbAdapter,
HydratorInterface $hydrator,
PostInterface $postPrototype
) {
$this->dbAdapter = $dbAdapter;
$this->hydrator = $hydrator;
$this->postPrototype = $postPrototype;
}
/**
* @param int|string $id
*
* @return PostInterface
* @throws \InvalidArgumentException
*/
public function find($id)
{
$sql = new Sql($this->dbAdapter);
$select = $sql->select('posts');
$select->where(array('id = ?' => $id));
$stmt = $sql->prepareStatementForSqlObject($select);
$result = $stmt->execute();
if ($result instanceof ResultInterface && $result->isQueryResult() && $result->getAffectedRows()) {
return $this->hydrator->hydrate($result->current(), $this->postPrototype);
}
throw new \InvalidArgumentException("Blog with given ID:{$id} not found.");
}
/**
* @return array|PostInterface[]
*/
public function findAll()
{
$sql = new Sql($this->dbAdapter);
$select = $sql->select('posts');
$stmt = $sql->prepareStatementForSqlObject($select);
$result = $stmt->execute();
if ($result instanceof ResultInterface && $result->isQueryResult()) {
$resultSet = new HydratingResultSet($this->hydrator, $this->postPrototype);
return $resultSet->initialize($result);
}
return array();
}
}
這個 find()
函數(shù)看上去真的很像 findAll()
函數(shù)。這里只有三點簡單的差別:首先我們需要為查詢添加一個條件,讓其只選擇一行,這通過使用 Sql
對象的 where()
函數(shù)實現(xiàn)。然后我們也要檢查 $result
變量內(nèi)是否有元組在內(nèi),這通過 getAffectedRows()
函數(shù)實現(xiàn)。返回語句會被注入的 hydrator
變成同樣被注入的原型中。
這次,但我們找不到任何元組時,會拋出一個 \InvalidArgumentException
異常,以便應(yīng)用程序可以方便的處理這種狀況。
完成這個章節(jié)之后,你現(xiàn)在了解了如何通過 Zend\Db\Sql
類來查詢數(shù)據(jù),也學(xué)習(xí)了關(guān)于 ZF2 新關(guān)鍵組件之一的 Zend\Stdlib\Hydrator
的知識。而且你再一次證明了你可以駕馭恰當(dāng)?shù)囊蕾噷ο笞⑷搿?/p>
在下一個章節(jié)中,我們會更進(jìn)一步地了解 router,這樣能讓我們在模組內(nèi)做更多動作。
更多建議: