再上一個(gè)章節(jié)我們創(chuàng)建了一個(gè) PostService
類來(lái)返回博客帖子的數(shù)據(jù)。雖然那個(gè)章節(jié)作為一個(gè)簡(jiǎn)單易懂的教程十分稱職,但是在現(xiàn)實(shí)世界應(yīng)用中卻是十分不使用的。沒(méi)有人會(huì)想要每次有一個(gè)新帖子產(chǎn)生就去修改一次源代碼。幸運(yùn)的是我們都了解數(shù)據(jù)庫(kù)。我們所需要的就是去學(xué)習(xí)如何通過(guò) ZF2 應(yīng)用程序和數(shù)據(jù)庫(kù)進(jìn)行互動(dòng)。
不過(guò)這里有一個(gè)問(wèn)題。目前有許多數(shù)據(jù)庫(kù)后臺(tái)系統(tǒng),例如 SQL 類數(shù)據(jù)庫(kù)和非 SQL 類數(shù)據(jù)庫(kù)。雖然在現(xiàn)實(shí)世界中你會(huì)直接使用一個(gè)你認(rèn)為最合適的解決方案,但是在實(shí)際數(shù)據(jù)庫(kù)操作前面創(chuàng)建多一個(gè)抽象層來(lái)抽象化數(shù)據(jù)庫(kù)操作會(huì)是更好的實(shí)踐。我們將這層稱之為映射層(Mapper-Layer)。
術(shù)語(yǔ)“數(shù)據(jù)庫(kù)抽象化”聽(tīng)上去好像挺令人困惑的,實(shí)際上這是一個(gè)非常簡(jiǎn)單的概念。假設(shè)有一個(gè) SQL 數(shù)據(jù)庫(kù)和一個(gè)非 SQL 數(shù)據(jù)庫(kù)。兩者都有增刪改查操作所對(duì)應(yīng)的函數(shù)。例如要在數(shù)據(jù)庫(kù)中查詢某列數(shù)據(jù),在 MySQL 中你會(huì)使用這個(gè)命令 mysqli_query('SELECT foo FROM bar')
;但如果你是用的是 ORM for MongoDB,那么你就要使用類似這樣的命令 $mongoODM->getRepository('bar')->find('foo')
。兩種數(shù)據(jù)庫(kù)引擎都會(huì)給你同樣的結(jié)果但是其執(zhí)行方法確實(shí)完全不同的。
所以如果我們一開(kāi)始使用一個(gè) SQL 數(shù)據(jù)庫(kù),然后將這些代碼直接寫進(jìn)我們的 PostService
中。一年之后,卻決定遷移到一個(gè)非 SQL 數(shù)據(jù)庫(kù)上,那么我們將不得不刪除所有之前的代碼并且重新編寫新代碼。再過(guò)幾年,新的狀況又出現(xiàn)了,然后我們又要?jiǎng)h除所有代碼然后重新編寫...這顯然不是最好的實(shí)現(xiàn)方法,這也正說(shuō)明了為何數(shù)據(jù)庫(kù)抽象化/映射層是如此的實(shí)用。
我們要做的事情根本上就是創(chuàng)建一個(gè)新的接口。這個(gè)接口定義了數(shù)據(jù)庫(kù)操作應(yīng)該如何運(yùn)作,但是實(shí)際實(shí)現(xiàn)是留空的。我們不要停留在理論上,現(xiàn)在開(kāi)始進(jìn)行編碼實(shí)踐吧。
首先我們來(lái)思考一下有什么可能的數(shù)據(jù)庫(kù)操作,我們需要:
上面提到的這些功能都是目前我想到的最重要的功能??紤]到 insert()
和 update
函數(shù)都是對(duì)數(shù)據(jù)庫(kù)進(jìn)行寫入,所以將兩者合并到一個(gè) save()
函數(shù)是個(gè)不錯(cuò)的主意,讓 save()
函數(shù)根據(jù)情況調(diào)用合適的函數(shù)。
首先我們?cè)?Blog\Mapper
名稱空間下創(chuàng)建一個(gè)新文件,叫做 PostMapperInterface.php
,然后參考下例添加內(nèi)容:
<?php
// 文件名: /module/Blog/src/Blog/Mapper/PostMapperInterface.php
namespace Blog\Mapper;
use Blog\Model\PostInterface;
interface PostMapperInterface
{
/**
* @param int|string $id
* @return PostInterface
* @throws \InvalidArgumentException
*/
public function find($id);
/**
* @return array|PostInterface[]
*/
public function findAll();
}
如您所見(jiàn)我們定義了兩個(gè)不同的函數(shù)。我們覺(jué)得一個(gè)映射實(shí)現(xiàn)(mapper-implementation)應(yīng)該擁有一個(gè) find()
函數(shù)來(lái)返回一個(gè)實(shí)現(xiàn)了 PostInterface
的對(duì)象。然后還要擁有一個(gè)叫做 findAll()
的函數(shù)來(lái)返回一個(gè)實(shí)現(xiàn)了 PostInterface
的對(duì)象的數(shù)組。在這里沒(méi)有添加 save()
和 delete()
函數(shù),因?yàn)槲覀兡壳爸豢紤]只讀部分的功能,當(dāng)然稍后這些功能也會(huì)被補(bǔ)全。
現(xiàn)在我們定義了我們的映射層應(yīng)該如何工作,我們可以在 PostService
內(nèi)對(duì)其進(jìn)行調(diào)用。要開(kāi)始重構(gòu),我們要先清空我們的類并且刪除所有現(xiàn)有的內(nèi)容,然后實(shí)現(xiàn) PostServiceInterface
接口,現(xiàn)在你的 PostService
應(yīng)該看上去像這樣:
<?php
// 文件名: /module/Blog/src/Blog/Service/PostService.php
namespace Blog\Service;
use Blog\Mapper\PostMapperInterface;
class PostService implements PostServiceInterface
{
/**
* @var \Blog\Mapper\PostMapperInterface
*/
protected $postMapper;
/**
* @param PostMapperInterface $postMapper
*/
public function __construct(PostMapperInterface $postMapper)
{
$this->postMapper = $postMapper;
}
/**
* {@inheritDoc}
*/
public function findAllPosts()
{
}
/**
* {@inheritDoc}
*/
public function findPost($id)
{
}
}
第一件你需要牢記于心的事情是這個(gè)接口并不是在 PostService
中實(shí)現(xiàn)的,而是在這里用作依賴對(duì)象,一個(gè)被要求的依賴對(duì)象,所以我們需要?jiǎng)?chuàng)建 __construct()
函數(shù)來(lái)接收任意這個(gè)接口所需的實(shí)現(xiàn)作為參數(shù)。同時(shí)你也要?jiǎng)?chuàng)建一個(gè) protected 變量來(lái)存放參數(shù)。
當(dāng)你完成上述內(nèi)容之后,我們需要一個(gè) PostMapperInterface
接口的實(shí)現(xiàn)來(lái)讓我們的 PostService
得以運(yùn)作。由于什么都不存在,所以我們現(xiàn)在是沒(méi)法讓我們的應(yīng)用程序運(yùn)作的,刷新您的瀏覽器就能看見(jiàn)如下 PHP 錯(cuò)誤:
Catchable fatal error: Argument 1 passed to Blog\Service\PostService::__construct()
must implement interface Blog\Mapper\PostMapperInterface, none given,
called in {path}\module\Blog\src\Blog\Service\PostServiceFactory.php on line 19
and defined in {path}\module\Blog\src\Blog\Service\PostService.php on line 17
不過(guò)我們的正在做的東西的權(quán)力取決于我們可以做出的假設(shè)。這個(gè) PostService
總會(huì)接收到一個(gè)映射器作為參數(shù)。所以在我們的 find*()
函數(shù)中我們可以假設(shè)其存在?;叵?PostMapperInterface
定義了一個(gè) find($id)
函數(shù)和一個(gè) findAll()
函數(shù)。讓我們?cè)?Service 函數(shù)里面上面提到的函數(shù):
<?php
// 文件名: /module/Blog/src/Blog/Service/PostService.php
namespace Blog\Service;
use Blog\Mapper\PostMapperInterface;
class PostService implements PostServiceInterface
{
/**
* @var \Blog\Mapper\PostMapperInterface
*/
protected $postMapper;
/**
* @param PostMapperInterface $postMapper
*/
public function __construct(PostMapperInterface $postMapper)
{
$this->postMapper = $postMapper;
}
/**
* {@inheritDoc}
*/
public function findAllPosts()
{
return $this->postMapper->findAll();
}
/**
* {@inheritDoc}
*/
public function findPost($id)
{
return $this->postMapper->find($id);
}
}
看著這些代碼,你就能發(fā)現(xiàn)我們使用 postMapper
來(lái)獲取我們所需要的數(shù)據(jù)。這個(gè)過(guò)程是如何發(fā)生的再也和 PostService
沒(méi)有任何關(guān)系。PostService
只知道他會(huì)接收到什么類型的數(shù)據(jù),而這是唯一重要的事情。
現(xiàn)在我們介紹了 PostMapperInterface
是 PostService
的一個(gè)依賴對(duì)象,我們?cè)僖矝](méi)辦法將這個(gè) Service 定義為 invokable
了,因?yàn)樗辛艘蕾噷?duì)象。所以我們需要為這個(gè) Service 創(chuàng)建一個(gè) Factory,就和我們?yōu)?ListController
做的一樣。首先更改配置文件,將其從 invokable
數(shù)組中移動(dòng)至 factories
數(shù)組,然后賦予合適的 factory 類,如下例:
<?php
// 文件名: /module/Blog/config/module.config.php
return array(
'service_manager' => array(
'factories' => array(
'Blog\Service\PostServiceInterface' => 'Blog\Factory\PostServiceFactory'
)
),
'view_manager' => array( /** ViewManager Config */ ),
'controllers' => array( /** ControllerManager Config */ ),
'router' => array( /** Router Config */ )
);
完成上述配置文件之后我們需要?jiǎng)?chuàng)建 Blog\Factory\PostServiceFactory
類,所以現(xiàn)在我們來(lái)實(shí)現(xiàn)它:
<?php
// 文件名: /module/Blog/src/Blog/Factory/PostServiceFactory.php
namespace Blog\Factory;
use Blog\Service\PostService;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class PostServiceFactory implements FactoryInterface
{
/**
* Create service
*
* @param ServiceLocatorInterface $serviceLocator
* @return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new PostService(
$serviceLocator->get('Blog\Mapper\PostMapperInterface')
);
}
}
這個(gè)工作完成之后你現(xiàn)在應(yīng)該能看到 ServiceNotFoundException
異常了,由 ServiceManager
拋出,告訴你所請(qǐng)求的 Service 無(wú)法被找到。
Additional information:
Zend\ServiceManager\Exception\ServiceNotFoundException
File:
{libraryPath}\Zend\ServiceManager\ServiceManager.php:529
Message:
Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Blog\Mapper\PostMapperInterface
我們?cè)诖藢懮媳菊鹿?jié)的最終結(jié)語(yǔ),事實(shí)上我們已經(jīng)成功的將數(shù)據(jù)庫(kù)操作邏輯隔離在我們的 Service 之外。現(xiàn)在,若情況需要的話,我們可以根據(jù)我們的需求來(lái)方便的對(duì)實(shí)際數(shù)據(jù)庫(kù)操作進(jìn)行變更了。
在下一章節(jié)我們會(huì)通過(guò) Zend\Db\Sql
來(lái)創(chuàng)建關(guān)于 PostMapperInterface
的實(shí)現(xiàn)。
更多建議: