鍍金池/ 教程/ PHP/ 編輯數(shù)據(jù)和刪除數(shù)據(jù)
了解 Router
回顧博客應(yīng)用程序
應(yīng)用 Form 和 Fieldset
編輯數(shù)據(jù)和刪除數(shù)據(jù)
介紹我們第一個(gè)“博客” Module
介紹 Zend\Db\Sql 和 Zend\Stdlib\Hydrator
介紹 Service 和 ServiceManager
為不同的數(shù)據(jù)庫(kù)后臺(tái)做準(zhǔn)備

編輯數(shù)據(jù)和刪除數(shù)據(jù)

在上一個(gè)章節(jié)中我們已經(jīng)學(xué)習(xí)了如何使用 Zend\Form 組件和 Zend\Db 組件來(lái)編寫(xiě)建立新數(shù)據(jù)集的功能。這一章節(jié)會(huì)專注于介紹編輯數(shù)據(jù)與刪除數(shù)據(jù),從而完全實(shí)現(xiàn)增刪改查等功能。我們首先從編輯數(shù)據(jù)開(kāi)始。

為表單綁定數(shù)據(jù)

插入數(shù)據(jù)表單和編輯數(shù)據(jù)表單之間的一個(gè)根本性的區(qū)別是,事實(shí)上在編輯數(shù)據(jù)表單中,數(shù)據(jù)已經(jīng)存在。這意味著我們需要先找到一個(gè)方法從數(shù)據(jù)庫(kù)獲得數(shù)據(jù),再將其預(yù)先填入表單中。幸運(yùn)地,Zend\Form 組件提供了一個(gè)非常方便的方法來(lái)實(shí)現(xiàn)這些功能,并將其稱之為數(shù)據(jù)綁定。

當(dāng)你提供一個(gè)編輯數(shù)據(jù)表單的時(shí)候,需要做的事情只有將你感興趣的對(duì)象從你的服務(wù)中進(jìn)行 bind 和表單綁定。下例演示了如何在你的控制器內(nèi)實(shí)現(xiàn)這一點(diǎn):

 <?php
 // 文件名: /module/Blog/src/Blog/Controller/WriteController.php
 namespace Blog\Controller;

 use Blog\Service\PostServiceInterface;
 use Zend\Form\FormInterface;
 use Zend\Mvc\Controller\AbstractActionController;
 use Zend\View\Model\ViewModel;

 class WriteController extends AbstractActionController
 {
     protected $postService;

     protected $postForm;

     public function __construct(
         PostServiceInterface $postService,
         FormInterface $postForm
     ) {
         $this->postService = $postService;
         $this->postForm    = $postForm;
     }

     public function addAction()
     {
         $request = $this->getRequest();

         if ($request->isPost()) {
             $this->postForm->setData($request->getPost());

             if ($this->postForm->isValid()) {
                 try {
                     $this->postService->savePost($this->postForm->getData());

                     return $this->redirect()->toRoute('blog');
                 } catch (\Exception $e) {
                     die($e->getMessage());
                     // 發(fā)生了一些數(shù)據(jù)庫(kù)錯(cuò)誤,進(jìn)行記錄并且讓用戶知道
                 }
             }
         }

         return new ViewModel(array(
             'form' => $this->postForm
         ));
     }

     public function editAction()
     {
         $request = $this->getRequest();
         $post    = $this->postService->findPost($this->params('id'));

         $this->postForm->bind($post);

         if ($request->isPost()) {
             $this->postForm->setData($request->getPost());

             if ($this->postForm->isValid()) {
                 try {
                     $this->postService->savePost($post);

                     return $this->redirect()->toRoute('blog');
                 } catch (\Exception $e) {
                     die($e->getMessage());
                     // Some DB Error happened, log it and let the user know
                 }
             }
         }

         return new ViewModel(array(
             'form' => $this->postForm
         ));
     }
 }

比起 addAction(),editAction() 只有三行代碼是不一樣的。第一個(gè)不一樣的地方是曾經(jīng)用來(lái)根據(jù)路徑的 id 參數(shù)從服務(wù)獲得相關(guān)的 Post 對(duì)象(我們即將會(huì)對(duì)這個(gè)部分進(jìn)行編寫(xiě))。

第二個(gè)不一樣的地方是,新代碼可以讓你綁定數(shù)據(jù)到 Zend\Form 組件上。我們?cè)谶@里之可以有效使用對(duì)象是因?yàn)槲覀兊?PostFieldset 使用了 hydrator 來(lái)將對(duì)象中的數(shù)據(jù)進(jìn)行處理并顯示。

最后,比起真的去執(zhí)行 $form->getData(),我們只需要簡(jiǎn)單地使用之前的 $post 變量就可以達(dá)到目的,因?yàn)檫@個(gè)變量會(huì)被自動(dòng)更新成表單中最新的數(shù)據(jù),這是數(shù)據(jù)綁定功能的功勞。這些就是所有要做的事情了,現(xiàn)在唯一要加的東西就是添加新的編輯數(shù)據(jù)路徑和編寫(xiě)針對(duì)該操作的視圖文件。

添加編輯數(shù)據(jù)路徑

編輯數(shù)據(jù)路徑不外乎是一個(gè)普通的段路徑,和 blog/detail 沒(méi)什么區(qū)別。配置你的路徑配置文件來(lái)添加一個(gè)新路徑:

 <?php
 // 文件名: /module/Blog/config/module.config.php
 return array(
     'db'              => array( /** Db Config */ ),
     'service_manager' => array( /** ServiceManager Config */ ),
     'view_manager'    => array( /** ViewManager Config */ ),
     'controllers'     => array( /** ControllerManager Config* */ ),
     'router'          => array(
         'routes' => array(
             'blog' => array(
                 'type' => 'literal',
                 'options' => array(
                     'route'    => '/blog',
                     'defaults' => array(
                         'controller' => 'Blog\Controller\List',
                         'action'     => 'index',
                     )
                 ),
                 'may_terminate' => true,
                 'child_routes'  => array(
                     'detail' => array(
                         'type' => 'segment',
                         'options' => array(
                             'route'    => '/:id',
                             'defaults' => array(
                                 'action' => 'detail'
                             ),
                             'constraints' => array(
                                 'id' => '\d+'
                             )
                         )
                     ),
                     'add' => array(
                         'type' => 'literal',
                         'options' => array(
                             'route'    => '/add',
                             'defaults' => array(
                                 'controller' => 'Blog\Controller\Write',
                                 'action'     => 'add'
                             )
                         )
                     ),
                     'edit' => array(
                         'type' => 'segment',
                         'options' => array(
                             'route'    => '/edit/:id',
                             'defaults' => array(
                                 'controller' => 'Blog\Controller\Write',
                                 'action'     => 'edit'
                             ),
                             'constraints' => array(
                                 'id' => '\d+'
                             )
                         )
                     ),
                 )
             )
         )
     )
 );

創(chuàng)建編輯數(shù)據(jù)模板

下一個(gè)需要做的事情就是創(chuàng)建新的模板 blog/write/edit

你需要對(duì)視圖端做的所有改變,僅僅是將目前的 id 傳給 url() viewHelper。要實(shí)現(xiàn)這點(diǎn)你有兩種選擇:第一種是將 ID 通過(guò)參數(shù)數(shù)組傳值,如下例所示:

$this->url('blog/edit', array('id' => $id));

這樣做的缺點(diǎn)是 $id 變量不可用,因?yàn)槲覀儧](méi)有將其指定給視圖。然而 Zend\Mvc\Router 組件給了我們一個(gè)很不錯(cuò)的功能來(lái)重用目前已經(jīng)匹配的參數(shù)。這可以通過(guò)設(shè)定 viewHelper 的最后一個(gè)參數(shù)為 true 實(shí)現(xiàn):

$this->url('blog/edit', array(), true);

檢查狀態(tài)

如果現(xiàn)在打開(kāi)你的瀏覽器并且在 localhost:8080/blog/edit/1 打開(kāi)編輯數(shù)據(jù)表單,就可以看見(jiàn)表單已經(jīng)包含你已經(jīng)選中的博客帖子的數(shù)據(jù)。而且當(dāng)你提交表單的時(shí)候就會(huì)發(fā)現(xiàn)數(shù)據(jù)已經(jīng)被成功變更。然而悲劇的是提交按鈕仍然包含這段文字 Insert new Post (插入新帖子)。這個(gè)問(wèn)題也可以通過(guò)修改視圖解決:

 <!-- Filename: /module/Blog/view/blog/write/add.phtml -->
 <h1>WriteController::editAction()</h1>
 <?php
 $form = $this->form;
 $form->setAttribute('action', $this->url('blog/edit', array(), true));
 $form->prepare();

 $form->get('submit')->setValue('Update Post');

 echo $this->form()->openTag($form);

 echo $this->formCollection($form);

 echo $this->form()->closeTag();

實(shí)現(xiàn)刪除功能

最后終于是時(shí)候來(lái)刪除一些數(shù)據(jù)了。我們從創(chuàng)建一個(gè)新路徑和添加一個(gè)新控制器開(kāi)始。

 <?php
 // 文件名: /module/Blog/config/module.config.php
 return array(
     'db'              => array( /** Db Config */ ),
     'service_manager' => array( /** ServiceManager Config */ ),
     'view_manager'    => array( /** ViewManager Config */ ),
     'controllers'     => array(
         'factories' => array(
             'Blog\Controller\List'   => 'Blog\Factory\ListControllerFactory',
             'Blog\Controller\Write'  => 'Blog\Factory\WriteControllerFactory',
             'Blog\Controller\Delete' => 'Blog\Factory\DeleteControllerFactory'
         )
     ),
     'router'          => array(
         'routes' => array(
             'post' => array(
                 'type' => 'literal',
                 'options' => array(
                     'route'    => '/blog',
                     'defaults' => array(
                         'controller' => 'Blog\Controller\List',
                         'action'     => 'index',
                     )
                 ),
                 'may_terminate' => true,
                 'child_routes'  => array(
                     'detail' => array(
                         'type' => 'segment',
                         'options' => array(
                             'route'    => '/:id',
                             'defaults' => array(
                                 'action' => 'detail'
                             ),
                             'constraints' => array(
                                 'id' => '\d+'
                             )
                         )
                     ),
                     'add' => array(
                         'type' => 'literal',
                         'options' => array(
                             'route'    => '/add',
                             'defaults' => array(
                                 'controller' => 'Blog\Controller\Write',
                                 'action'     => 'add'
                             )
                         )
                     ),
                     'edit' => array(
                         'type' => 'segment',
                         'options' => array(
                             'route'    => '/edit/:id',
                             'defaults' => array(
                                 'controller' => 'Blog\Controller\Write',
                                 'action'     => 'edit'
                             ),
                             'constraints' => array(
                                 'id' => '\d+'
                             )
                         )
                     ),
                     'delete' => array(
                         'type' => 'segment',
                         'options' => array(
                             'route'    => '/delete/:id',
                             'defaults' => array(
                                 'controller' => 'Blog\Controller\Delete',
                                 'action'     => 'delete'
                             ),
                             'constraints' => array(
                                 'id' => '\d+'
                             )
                         )
                     ),
                 )
             )
         )
     )
 );

請(qǐng)注意這里我們制定了又一個(gè)控制器 Blog\Controller\Delete,這是因?yàn)閷?shí)際上這個(gè)控制器不會(huì)要求 PostForm。更進(jìn)一步,甚至連 Zend\Form 組件都不要求使用的一個(gè)完美示例就是 DeleteForm。首先我們先創(chuàng)建我們的控制器:

Factory

 <?php
 // 文件名: /module/Blog/src/Blog/Factory/DeleteControllerFactory.php
 namespace Blog\Factory;

 use Blog\Controller\DeleteController;
 use Zend\ServiceManager\FactoryInterface;
 use Zend\ServiceManager\ServiceLocatorInterface;

 class DeleteControllerFactory implements FactoryInterface
 {
     /**
      * Create service
      *
      * @param ServiceLocatorInterface $serviceLocator
      *
      * @return mixed
      */
     public function createService(ServiceLocatorInterface $serviceLocator)
     {
         $realServiceLocator = $serviceLocator->getServiceLocator();
         $postService        = $realServiceLocator->get('Blog\Service\PostServiceInterface');

         return new DeleteController($postService);
     }
 }

Controller

 <?php
 // 文件名: /module/Blog/src/Blog/Controller/DeleteController.php
 namespace Blog\Controller;

 use Blog\Service\PostServiceInterface;
 use Zend\Mvc\Controller\AbstractActionController;
 use Zend\View\Model\ViewModel;

 class DeleteController extends AbstractActionController
 {
     /**
      * @var \Blog\Service\PostServiceInterface
      */
     protected $postService;

     public function __construct(PostServiceInterface $postService)
     {
         $this->postService = $postService;
     }

     public function deleteAction()
     {
         try {
             $post = $this->postService->findPost($this->params('id'));
         } catch (\InvalidArgumentException $e) {
             return $this->redirect()->toRoute('blog');
         }

         $request = $this->getRequest();

         if ($request->isPost()) {
             $del = $request->getPost('delete_confirmation', 'no');

             if ($del === 'yes') {
                 $this->postService->deletePost($post);
             }

             return $this->redirect()->toRoute('blog');
         }

         return new ViewModel(array(
             'post' => $post
         ));
     }
 }

如您所見(jiàn)這里并沒(méi)有什么新東西。我們將 PostService 注入到控制器,然后在 action 里面我們首先檢查目標(biāo)博客帖子是否存在。如果存在我們就檢測(cè)請(qǐng)求是不是一個(gè) POST 請(qǐng)求,如果是的話再看 POST 數(shù)據(jù)里面有沒(méi)有一個(gè)叫做 delete_confirmation 參數(shù)存在,如果這個(gè)參數(shù)存在而且其值為 yes,那么就通過(guò) PostServicedeletePost() 函數(shù)對(duì)目標(biāo)博客帖子進(jìn)行刪除。

在您實(shí)際編寫(xiě)這段代碼的時(shí)候會(huì)注意到你沒(méi)有得到關(guān)于 deletePost() 自動(dòng)完成提示,這是因?yàn)槲覀冞€未將其添加到服務(wù)/接口上?,F(xiàn)在我們將這個(gè)函數(shù)添加到接口上,并且在服務(wù)里面對(duì)其進(jìn)行實(shí)現(xiàn):

Interface

 <?php
 // 文件名: /module/Blog/src/Blog/Service/PostServiceInterface.php
 namespace Blog\Service;

 use Blog\Model\PostInterface;

 interface PostServiceInterface
 {
     /**
      * 應(yīng)該會(huì)分會(huì)所有博客帖子集,以便我們對(duì)其遍歷。數(shù)組中的每個(gè)條目應(yīng)該都是
      * \Blog\Model\PostInterface 接口的實(shí)現(xiàn)
      *
      * @return array|PostInterface[]
      */
     public function findAllPosts();

     /**
      * 應(yīng)該會(huì)返回單個(gè)博客帖子
      *
      * @param  int $id 應(yīng)該被返回的帖子的標(biāo)識(shí)符
      * @return PostInterface
      */
     public function findPost($id);

     /**
      * 應(yīng)該會(huì)保存給出了的 PostInterface 實(shí)現(xiàn)并且返回。如果是已有的帖子那么帖子
      * 應(yīng)該被更新,如果是新帖子則應(yīng)該去創(chuàng)建。
      *
      * @param  PostInterface $blog
      * @return PostInterface
      */
     public function savePost(PostInterface $blog);

     /**
      * 應(yīng)該刪除給出的 PostInterface 的一個(gè)實(shí)現(xiàn),如果刪除成功就返回 true
      * 否則返回 false.
      *
      * @param  PostInterface $blog
      * @return bool
      */
     public function deletePost(PostInterface $blog);
 }

Service

 <?php
 // 文件名: /module/Blog/src/Blog/Service/PostService.php
 namespace Blog\Service;

 use Blog\Mapper\PostMapperInterface;
 use Blog\Model\PostInterface;

 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);
     }

     /**
      * {@inheritDoc}
      */
     public function savePost(PostInterface $post)
     {
         return $this->postMapper->save($post);
     }

     /**
      * {@inheritDoc}
      */
     public function deletePost(PostInterface $post)
     {
         return $this->postMapper->delete($post);
     }
 }

現(xiàn)在我們認(rèn)為 PostMapperInterface 應(yīng)該有 delete() 函數(shù)。我們還沒(méi)對(duì)其進(jìn)行實(shí)現(xiàn)所以先將其加入 PostMapperInterface。

 <?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();

     /**
      * @param PostInterface $postObject
      *
      * @param PostInterface $postObject
      * @return PostInterface
      * @throws \Exception
      */
     public function save(PostInterface $postObject);

     /**
      * @param PostInterface $postObject
      *
      * @return bool
      * @throws \Exception
      */
     public function delete(PostInterface $postObject);
 }

現(xiàn)在我們已經(jīng)在接口內(nèi)聲明了函數(shù),是時(shí)候在 ZendDbSqlMapper 內(nèi)對(duì)其進(jìn)行實(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\Delete;
 use Zend\Db\Sql\Insert;
 use Zend\Db\Sql\Sql;
 use Zend\Db\Sql\Update;
 use Zend\Stdlib\Hydrator\HydratorInterface;

 class ZendDbSqlMapper implements PostMapperInterface
 {
     /**
      * @var \Zend\Db\Adapter\AdapterInterface
      */
     protected $dbAdapter;

     protected $hydrator;

     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;
     }

     /**
      * {@inheritDoc}
      */
     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.");
     }

     /**
      * {@inheritDoc}
      */
     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();
     }

     /**
      * {@inheritDoc}
      */
     public function save(PostInterface $postObject)
     {
         $postData = $this->hydrator->extract($postObject);
         unset($postData['id']); // Insert 和 Update 都不需要數(shù)組中存在 ID

         if ($postObject->getId()) {
             // ID 存在,是一個(gè) Update
             $action = new Update('post');
             $action->set($postData);
             $action->where(array('id = ?' => $postObject->getId()));
         } else {
             // ID 不存在,是一個(gè)Insert
             $action = new Insert('post');
             $action->values($postData);
         }

         $sql    = new Sql($this->dbAdapter);
         $stmt   = $sql->prepareStatementForSqlObject($action);
         $result = $stmt->execute();

         if ($result instanceof ResultInterface) {
             if ($newId = $result->getGeneratedValue()) {
                 // 每當(dāng)一個(gè)值被生成時(shí),將其賦給對(duì)象
                 $postObject->setId($newId);
             }

             return $postObject;
         }

         throw new \Exception("Database error");
     }

     /**
      * {@inheritDoc}
      */
     public function delete(PostInterface $postObject)
     {
         $action = new Delete('posts');
         $action->where(array('id = ?' => $postObject->getId()));

         $sql    = new Sql($this->dbAdapter);
         $stmt   = $sql->prepareStatementForSqlObject($action);
         $result = $stmt->execute();

         return (bool)$result->getAffectedRows();
     }
 }

delete 語(yǔ)句應(yīng)該看上去十分熟悉,畢竟這事情和你之前做的所有其他類型查詢基本是一樣的工作。當(dāng)這些都弄好了之后我們可以繼續(xù)編寫(xiě)我們的視圖文件,然后我們就能刪除博客帖子了。

 <!-- Filename: /module/Blog/view/blog/delete/delete.phtml -->
 <h1>DeleteController::deleteAction()</h1>
 <p>
     Are you sure that you want to delete
     '<?php echo $this->escapeHtml($this->post->getTitle()); ?>' by
     '<?php echo $this->escapeHtml($this->post->getText()); ?>'?
 </p>
 <form action="<?php echo $this->url('blog/delete', array(), true) ?>" method="post">
     <input type="submit" name="delete_confirmation" value="yes">
     <input type="submit" name="delete_confirmation" value="no">
 </form>

總結(jié)

在這個(gè)章節(jié)中我們學(xué)習(xí)了在 Zend\Form 組件中的數(shù)據(jù)綁定是如何工作的,并且通過(guò)它完成了我們的更新數(shù)據(jù)程序。然后我們還學(xué)了如何使用 HTML 表單和在不依賴 Zend\Form 組件的前提下檢查表單數(shù)據(jù),最終我們完成了一個(gè)完整的博客帖子增刪改查示例。

在下個(gè)章節(jié)中我們會(huì)重新概括一次我們完成的所有事情。然后會(huì)談?wù)勎覀兪褂玫脑O(shè)計(jì)模式和回答在這整個(gè)教程實(shí)踐中經(jīng)常出現(xiàn)的一些問(wèn)題。