再上一個章節(jié)我們創(chuàng)建了一個 PostService
類來返回博客帖子的數(shù)據(jù)。雖然那個章節(jié)作為一個簡單易懂的教程十分稱職,但是在現(xiàn)實世界應(yīng)用中卻是十分不使用的。沒有人會想要每次有一個新帖子產(chǎn)生就去修改一次源代碼。幸運的是我們都了解數(shù)據(jù)庫。我們所需要的就是去學(xué)習(xí)如何通過 ZF2 應(yīng)用程序和數(shù)據(jù)庫進行互動。
不過這里有一個問題。目前有許多數(shù)據(jù)庫后臺系統(tǒng),例如 SQL 類數(shù)據(jù)庫和非 SQL 類數(shù)據(jù)庫。雖然在現(xiàn)實世界中你會直接使用一個你認為最合適的解決方案,但是在實際數(shù)據(jù)庫操作前面創(chuàng)建多一個抽象層來抽象化數(shù)據(jù)庫操作會是更好的實踐。我們將這層稱之為映射層(Mapper-Layer)。
術(shù)語“數(shù)據(jù)庫抽象化”聽上去好像挺令人困惑的,實際上這是一個非常簡單的概念。假設(shè)有一個 SQL 數(shù)據(jù)庫和一個非 SQL 數(shù)據(jù)庫。兩者都有增刪改查操作所對應(yīng)的函數(shù)。例如要在數(shù)據(jù)庫中查詢某列數(shù)據(jù),在 MySQL 中你會使用這個命令 mysqli_query('SELECT foo FROM bar')
;但如果你是用的是 ORM for MongoDB,那么你就要使用類似這樣的命令 $mongoODM->getRepository('bar')->find('foo')
。兩種數(shù)據(jù)庫引擎都會給你同樣的結(jié)果但是其執(zhí)行方法確實完全不同的。
所以如果我們一開始使用一個 SQL 數(shù)據(jù)庫,然后將這些代碼直接寫進我們的 PostService
中。一年之后,卻決定遷移到一個非 SQL 數(shù)據(jù)庫上,那么我們將不得不刪除所有之前的代碼并且重新編寫新代碼。再過幾年,新的狀況又出現(xiàn)了,然后我們又要刪除所有代碼然后重新編寫...這顯然不是最好的實現(xiàn)方法,這也正說明了為何數(shù)據(jù)庫抽象化/映射層是如此的實用。
我們要做的事情根本上就是創(chuàng)建一個新的接口。這個接口定義了數(shù)據(jù)庫操作應(yīng)該如何運作,但是實際實現(xiàn)是留空的。我們不要停留在理論上,現(xiàn)在開始進行編碼實踐吧。
首先我們來思考一下有什么可能的數(shù)據(jù)庫操作,我們需要:
上面提到的這些功能都是目前我想到的最重要的功能??紤]到 insert()
和 update
函數(shù)都是對數(shù)據(jù)庫進行寫入,所以將兩者合并到一個 save()
函數(shù)是個不錯的主意,讓 save()
函數(shù)根據(jù)情況調(diào)用合適的函數(shù)。
首先我們在 Blog\Mapper
名稱空間下創(chuàng)建一個新文件,叫做 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();
}
如您所見我們定義了兩個不同的函數(shù)。我們覺得一個映射實現(xiàn)(mapper-implementation)應(yīng)該擁有一個 find()
函數(shù)來返回一個實現(xiàn)了 PostInterface
的對象。然后還要擁有一個叫做 findAll()
的函數(shù)來返回一個實現(xiàn)了 PostInterface
的對象的數(shù)組。在這里沒有添加 save()
和 delete()
函數(shù),因為我們目前只考慮只讀部分的功能,當(dāng)然稍后這些功能也會被補全。
現(xiàn)在我們定義了我們的映射層應(yīng)該如何工作,我們可以在 PostService
內(nèi)對其進行調(diào)用。要開始重構(gòu),我們要先清空我們的類并且刪除所有現(xiàn)有的內(nèi)容,然后實現(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)
{
}
}
第一件你需要牢記于心的事情是這個接口并不是在 PostService
中實現(xiàn)的,而是在這里用作依賴對象,一個被要求的依賴對象,所以我們需要創(chuàng)建 __construct()
函數(shù)來接收任意這個接口所需的實現(xiàn)作為參數(shù)。同時你也要創(chuàng)建一個 protected 變量來存放參數(shù)。
當(dāng)你完成上述內(nèi)容之后,我們需要一個 PostMapperInterface
接口的實現(xiàn)來讓我們的 PostService
得以運作。由于什么都不存在,所以我們現(xiàn)在是沒法讓我們的應(yīng)用程序運作的,刷新您的瀏覽器就能看見如下 PHP 錯誤:
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
不過我們的正在做的東西的權(quán)力取決于我們可以做出的假設(shè)。這個 PostService
總會接收到一個映射器作為參數(shù)。所以在我們的 find*()
函數(shù)中我們可以假設(shè)其存在。回想 PostMapperInterface
定義了一個 find($id)
函數(shù)和一個 findAll()
函數(shù)。讓我們在 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
來獲取我們所需要的數(shù)據(jù)。這個過程是如何發(fā)生的再也和 PostService
沒有任何關(guān)系。PostService
只知道他會接收到什么類型的數(shù)據(jù),而這是唯一重要的事情。
現(xiàn)在我們介紹了 PostMapperInterface
是 PostService
的一個依賴對象,我們再也沒辦法將這個 Service 定義為 invokable
了,因為它有了依賴對象。所以我們需要為這個 Service 創(chuàng)建一個 Factory,就和我們?yōu)?ListController
做的一樣。首先更改配置文件,將其從 invokable
數(shù)組中移動至 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 */ )
);
完成上述配置文件之后我們需要創(chuàng)建 Blog\Factory\PostServiceFactory
類,所以現(xiàn)在我們來實現(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')
);
}
}
這個工作完成之后你現(xiàn)在應(yīng)該能看到 ServiceNotFoundException
異常了,由 ServiceManager
拋出,告訴你所請求的 Service 無法被找到。
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
我們在此寫上本章節(jié)的最終結(jié)語,事實上我們已經(jīng)成功的將數(shù)據(jù)庫操作邏輯隔離在我們的 Service 之外。現(xiàn)在,若情況需要的話,我們可以根據(jù)我們的需求來方便的對實際數(shù)據(jù)庫操作進行變更了。
在下一章節(jié)我們會通過 Zend\Db\Sql
來創(chuàng)建關(guān)于 PostMapperInterface
的實現(xiàn)。
更多建議: