Zend Framework 2-編輯數(shù)據(jù)和刪除數(shù)據(jù)

2018-09-28 20:18 更新

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

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

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

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

當(dāng)你提供一個編輯數(shù)據(jù)表單的時候,需要做的事情只有將你感興趣的對象從你的服務(wù)中進(jìn)行 bind 和表單綁定。下例演示了如何在你的控制器內(nèi)實現(xià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ù)庫錯誤,進(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() 只有三行代碼是不一樣的。第一個不一樣的地方是曾經(jīng)用來根據(jù)路徑的 id 參數(shù)從服務(wù)獲得相關(guān)的 Post 對象(我們即將會對這個部分進(jìn)行編寫)。

第二個不一樣的地方是,新代碼可以讓你綁定數(shù)據(jù)到 Zend\Form 組件上。我們在這里之可以有效使用對象是因為我們的 PostFieldset 使用了 hydrator 來將對象中的數(shù)據(jù)進(jìn)行處理并顯示。

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

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

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

 <?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ù)模板

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

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

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

這樣做的缺點是 $id 變量不可用,因為我們沒有將其指定給視圖。然而 Zend\Mvc\Router 組件給了我們一個很不錯的功能來重用目前已經(jīng)匹配的參數(shù)。這可以通過設(shè)定 viewHelper 的最后一個參數(shù)為 true 實現(xiàn):

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

檢查狀態(tài)

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

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

實現(xiàn)刪除功能

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

 <?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+'
                             )
                         )
                     ),
                 )
             )
         )
     )
 );

請注意這里我們制定了又一個控制器 Blog\Controller\Delete,這是因為實際上這個控制器不會要求 PostForm。更進(jìn)一步,甚至連 Zend\Form 組件都不要求使用的一個完美示例就是 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
         ));
     }
 }

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

在您實際編寫這段代碼的時候會注意到你沒有得到關(guān)于 deletePost() 自動完成提示,這是因為我們還未將其添加到服務(wù)/接口上?,F(xiàn)在我們將這個函數(shù)添加到接口上,并且在服務(wù)里面對其進(jìn)行實現(xiàn):

Interface

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

 use Blog\Model\PostInterface;

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

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

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

     /**
      * 應(yīng)該刪除給出的 PostInterface 的一個實現(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ù)。我們還沒對其進(jìn)行實現(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ù),是時候在 ZendDbSqlMapper 內(nèi)對其進(jìn)行實現(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 存在,是一個 Update
             $action = new Update('post');
             $action->set($postData);
             $action->where(array('id = ?' => $postObject->getId()));
         } else {
             // ID 不存在,是一個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)一個值被生成時,將其賦給對象
                 $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īng)該看上去十分熟悉,畢竟這事情和你之前做的所有其他類型查詢基本是一樣的工作。當(dāng)這些都弄好了之后我們可以繼續(xù)編寫我們的視圖文件,然后我們就能刪除博客帖子了。

 <!-- 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é)

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

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

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號