Zend Framework 2-為不同的數(shù)據(jù)庫(kù)后臺(tái)做準(zhǔn)備

2018-09-28 20:18 更新

為不同的數(shù)據(jù)庫(kù)后臺(tái)做準(zhǔn)備

再上一個(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ù)據(jù)庫(kù)抽象化?

術(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í)踐吧。

創(chuàng)建 PostMapperInterface

首先我們來(lái)思考一下有什么可能的數(shù)據(jù)庫(kù)操作,我們需要:

  • 尋找一個(gè)博客帖子
  • 尋找所有博客帖子
  • 插入新的博客帖子
  • 更新已有的博客帖子
  • 刪除已有的博客帖子

上面提到的這些功能都是目前我想到的最重要的功能??紤]到 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ǔ)全。

重構(gòu) PostService

現(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ù),而這是唯一重要的事情。

PostService 擁有依賴對(duì)象

現(xiàn)在我們介紹了 PostMapperInterfacePostService 的一個(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

總結(jié)

我們?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)。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)