在上一個章節(jié)中我們已經(jīng)學(xué)習(xí)了如何使用 Zend\Form
組件和 Zend\Db
組件來編寫建立新數(shù)據(jù)集的功能。這一章節(jié)會專注于介紹編輯數(shù)據(jù)與刪除數(shù)據(jù),從而完全實現(xiàn)增刪改查等功能。我們首先從編輯數(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ù)路徑不外乎是一個普通的段路徑,和 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)建新的模板 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);
如果現(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();
最后終于是時候來刪除一些數(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
,那么就通過 PostService
的 deletePost()
函數(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é)中我們學(xué)習(xí)了在 Zend\Form
組件中的數(shù)據(jù)綁定是如何工作的,并且通過它完成了我們的更新數(shù)據(jù)程序。然后我們還學(xué)了如何使用 HTML 表單和在不依賴 Zend\Form
組件的前提下檢查表單數(shù)據(jù),最終我們完成了一個完整的博客帖子增刪改查示例。
在下個章節(jié)中我們會重新概括一次我們完成的所有事情。然后會談?wù)勎覀兪褂玫脑O(shè)計模式和回答在這整個教程實踐中經(jīng)常出現(xiàn)的一些問題。
更多建議: