《PHP實戰:Zend Framework教程之響應對象的封裝Zend_Controller_Response實例詳解》要點:
本文介紹了PHP實戰:Zend Framework教程之響應對象的封裝Zend_Controller_Response實例詳解,希望對您有用。如果有疑問,可以聯系我們。
PHP實戰本文實例講述了Zend Framework教程之響應對象的封裝Zend_Controller_Response用法.分享給大家供大家參考,具體如下:
PHP實戰概述
PHP實戰響應對象邏輯上是哀求對象的搭檔.目的在于收集消息體和/或消息頭,因而可能返回大批的結果.
PHP實戰Zend_Controller_Response響應對象的基本實現
PHP實戰├── Response
│?? ├── Abstract.php
│?? ├── Cli.php
│?? ├── Exception.php
│?? ├── Http.php
│?? └── HttpTestCase.php
PHP實戰Zend_Controller_Response_Abstract
PHP實戰
abstract class Zend_Controller_Response_Abstract
{
/**
* Body content
* @var array
*/
protected $_body = array();
/**
* Exception stack
* @var Exception
*/
protected $_exceptions = array();
/**
* Array of headers. Each header is an array with keys 'name' and 'value'
* @var array
*/
protected $_headers = array();
/**
* Array of raw headers. Each header is a single string, the entire header to emit
* @var array
*/
protected $_headersRaw = array();
/**
* HTTP response code to use in headers
* @var int
*/
protected $_httpResponseCode = 200;
/**
* Flag; is this response a redirect?
* @var boolean
*/
protected $_isRedirect = false;
/**
* Whether or not to render exceptions; off by default
* @var boolean
*/
protected $_renderExceptions = false;
/**
* Flag; if true, when header operations are called after headers have been
* sent, an exception will be raised; otherwise, processing will continue
* as normal. Defaults to true.
*
* @see canSendHeaders()
* @var boolean
*/
public $headersSentThrowsException = true;
/**
* Normalize a header name
*
* Normalizes a header name to X-Capitalized-Names
*
* @param string $name
* @return string
*/
protected function _normalizeHeader($name)
{
$filtered = str_replace(array('-', '_'), ' ', (string) $name);
$filtered = ucwords(strtolower($filtered));
$filtered = str_replace(' ', '-', $filtered);
return $filtered;
}
/**
* Set a header
*
* If $replace is true, replaces any headers already defined with that
* $name.
*
* @param string $name
* @param string $value
* @param boolean $replace
* @return Zend_Controller_Response_Abstract
*/
public function setHeader($name, $value, $replace = false)
{
$this->canSendHeaders(true);
$name = $this->_normalizeHeader($name);
$value = (string) $value;
if ($replace) {
foreach ($this->_headers as $key => $header) {
if ($name == $header['name']) {
unset($this->_headers[$key]);
}
}
}
$this->_headers[] = array(
'name' => $name,
'value' => $value,
'replace' => $replace
);
return $this;
}
/**
* Set redirect URL
*
* Sets Location header and response code. Forces replacement of any prior
* redirects.
*
* @param string $url
* @param int $code
* @return Zend_Controller_Response_Abstract
*/
public function setRedirect($url, $code = 302)
{
$this->canSendHeaders(true);
$this->setHeader('Location', $url, true)
->setHttpResponseCode($code);
return $this;
}
/**
* Is this a redirect?
*
* @return boolean
*/
public function isRedirect()
{
return $this->_isRedirect;
}
/**
* Return array of headers; see {@link $_headers} for format
*
* @return array
*/
public function getHeaders()
{
return $this->_headers;
}
/**
* Clear headers
*
* @return Zend_Controller_Response_Abstract
*/
public function clearHeaders()
{
$this->_headers = array();
return $this;
}
/**
* Clears the specified HTTP header
*
* @param string $name
* @return Zend_Controller_Response_Abstract
*/
public function clearHeader($name)
{
if (! count($this->_headers)) {
return $this;
}
foreach ($this->_headers as $index => $header) {
if ($name == $header['name']) {
unset($this->_headers[$index]);
}
}
return $this;
}
/**
* Set raw HTTP header
*
* Allows setting non key => value headers, such as status codes
*
* @param string $value
* @return Zend_Controller_Response_Abstract
*/
public function setRawHeader($value)
{
$this->canSendHeaders(true);
if ('Location' == substr($value, 0, 8)) {
$this->_isRedirect = true;
}
$this->_headersRaw[] = (string) $value;
return $this;
}
/**
* Retrieve all {@link setRawHeader() raw HTTP headers}
*
* @return array
*/
public function getRawHeaders()
{
return $this->_headersRaw;
}
/**
* Clear all {@link setRawHeader() raw HTTP headers}
*
* @return Zend_Controller_Response_Abstract
*/
public function clearRawHeaders()
{
$this->_headersRaw = array();
return $this;
}
/**
* Clears the specified raw HTTP header
*
* @param string $headerRaw
* @return Zend_Controller_Response_Abstract
*/
public function clearRawHeader($headerRaw)
{
if (! count($this->_headersRaw)) {
return $this;
}
$key = array_search($headerRaw, $this->_headersRaw);
if ($key !== false) {
unset($this->_headersRaw[$key]);
}
return $this;
}
/**
* Clear all headers, normal and raw
*
* @return Zend_Controller_Response_Abstract
*/
public function clearAllHeaders()
{
return $this->clearHeaders()
->clearRawHeaders();
}
/**
* Set HTTP response code to use with headers
*
* @param int $code
* @return Zend_Controller_Response_Abstract
*/
public function setHttpResponseCode($code)
{
if (!is_int($code) || (100 > $code) || (599 < $code)) {
require_once 'Zend/Controller/Response/Exception.php';
throw new Zend_Controller_Response_Exception('Invalid HTTP response code');
}
if ((300 <= $code) && (307 >= $code)) {
$this->_isRedirect = true;
} else {
$this->_isRedirect = false;
}
$this->_httpResponseCode = $code;
return $this;
}
/**
* Retrieve HTTP response code
*
* @return int
*/
public function getHttpResponseCode()
{
return $this->_httpResponseCode;
}
/**
* Can we send headers?
*
* @param boolean $throw Whether or not to throw an exception if headers have been sent; defaults to false
* @return boolean
* @throws Zend_Controller_Response_Exception
*/
public function canSendHeaders($throw = false)
{
$ok = headers_sent($file, $line);
if ($ok && $throw && $this->headersSentThrowsException) {
require_once 'Zend/Controller/Response/Exception.php';
throw new Zend_Controller_Response_Exception('Cannot send headers; headers already sent in ' . $file . ', line ' . $line);
}
return !$ok;
}
/**
* Send all headers
*
* Sends any headers specified. If an {@link setHttpResponseCode() HTTP response code}
* has been specified, it is sent with the first header.
*
* @return Zend_Controller_Response_Abstract
*/
public function sendHeaders()
{
// Only check if we can send headers if we have headers to send
if (count($this->_headersRaw) || count($this->_headers) || (200 != $this->_httpResponseCode)) {
$this->canSendHeaders(true);
} elseif (200 == $this->_httpResponseCode) {
// Haven't changed the response code, and we have no headers
return $this;
}
$httpCodeSent = false;
foreach ($this->_headersRaw as $header) {
if (!$httpCodeSent && $this->_httpResponseCode) {
header($header, true, $this->_httpResponseCode);
$httpCodeSent = true;
} else {
header($header);
}
}
foreach ($this->_headers as $header) {
if (!$httpCodeSent && $this->_httpResponseCode) {
header($header['name'] . ': ' . $header['value'], $header['replace'], $this->_httpResponseCode);
$httpCodeSent = true;
} else {
header($header['name'] . ': ' . $header['value'], $header['replace']);
}
}
if (!$httpCodeSent) {
header('HTTP/1.1 ' . $this->_httpResponseCode);
$httpCodeSent = true;
}
return $this;
}
/**
* Set body content
*
* If $name is not passed, or is not a string, resets the entire body and
* sets the 'default' key to $content.
*
* If $name is a string, sets the named segment in the body array to
* $content.
*
* @param string $content
* @param null|string $name
* @return Zend_Controller_Response_Abstract
*/
public function setBody($content, $name = null)
{
if ((null === $name) || !is_string($name)) {
$this->_body = array('default' => (string) $content);
} else {
$this->_body[$name] = (string) $content;
}
return $this;
}
/**
* Append content to the body content
*
* @param string $content
* @param null|string $name
* @return Zend_Controller_Response_Abstract
*/
public function appendBody($content, $name = null)
{
if ((null === $name) || !is_string($name)) {
if (isset($this->_body['default'])) {
$this->_body['default'] .= (string) $content;
} else {
return $this->append('default', $content);
}
} elseif (isset($this->_body[$name])) {
$this->_body[$name] .= (string) $content;
} else {
return $this->append($name, $content);
}
return $this;
}
/**
* Clear body array
*
* With no arguments, clears the entire body array. Given a $name, clears
* just that named segment; if no segment matching $name exists, returns
* false to indicate an error.
*
* @param string $name Named segment to clear
* @return boolean
*/
public function clearBody($name = null)
{
if (null !== $name) {
$name = (string) $name;
if (isset($this->_body[$name])) {
unset($this->_body[$name]);
return true;
}
return false;
}
$this->_body = array();
return true;
}
/**
* Return the body content
*
* If $spec is false, returns the concatenated values of the body content
* array. If $spec is boolean true, returns the body content array. If
* $spec is a string and matches a named segment, returns the contents of
* that segment; otherwise, returns null.
*
* @param boolean $spec
* @return string|array|null
*/
public function getBody($spec = false)
{
if (false === $spec) {
ob_start();
$this->outputBody();
return ob_get_clean();
} elseif (true === $spec) {
return $this->_body;
} elseif (is_string($spec) && isset($this->_body[$spec])) {
return $this->_body[$spec];
}
return null;
}
/**
* Append a named body segment to the body content array
*
* If segment already exists, replaces with $content and places at end of
* array.
*
* @param string $name
* @param string $content
* @return Zend_Controller_Response_Abstract
*/
public function append($name, $content)
{
if (!is_string($name)) {
require_once 'Zend/Controller/Response/Exception.php';
throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")');
}
if (isset($this->_body[$name])) {
unset($this->_body[$name]);
}
$this->_body[$name] = (string) $content;
return $this;
}
/**
* Prepend a named body segment to the body content array
*
* If segment already exists, replaces with $content and places at top of
* array.
*
* @param string $name
* @param string $content
* @return void
*/
public function prepend($name, $content)
{
if (!is_string($name)) {
require_once 'Zend/Controller/Response/Exception.php';
throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")');
}
if (isset($this->_body[$name])) {
unset($this->_body[$name]);
}
$new = array($name => (string) $content);
$this->_body = $new + $this->_body;
return $this;
}
/**
* Insert a named segment into the body content array
*
* @param string $name
* @param string $content
* @param string $parent
* @param boolean $before Whether to insert the new segment before or
* after the parent. Defaults to false (after)
* @return Zend_Controller_Response_Abstract
*/
public function insert($name, $content, $parent = null, $before = false)
{
if (!is_string($name)) {
require_once 'Zend/Controller/Response/Exception.php';
throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")');
}
if ((null !== $parent) && !is_string($parent)) {
require_once 'Zend/Controller/Response/Exception.php';
throw new Zend_Controller_Response_Exception('Invalid body segment parent key ("' . gettype($parent) . '")');
}
if (isset($this->_body[$name])) {
unset($this->_body[$name]);
}
if ((null === $parent) || !isset($this->_body[$parent])) {
return $this->append($name, $content);
}
$ins = array($name => (string) $content);
$keys = array_keys($this->_body);
$loc = array_search($parent, $keys);
if (!$before) {
// Increment location if not inserting before
++$loc;
}
if (0 === $loc) {
// If location of key is 0, we're prepending
$this->_body = $ins + $this->_body;
} elseif ($loc >= (count($this->_body))) {
// If location of key is maximal, we're appending
$this->_body = $this->_body + $ins;
} else {
// Otherwise, insert at location specified
$pre = array_slice($this->_body, 0, $loc);
$post = array_slice($this->_body, $loc);
$this->_body = $pre + $ins + $post;
}
return $this;
}
/**
* Echo the body segments
*
* @return void
*/
public function outputBody()
{
$body = implode('', $this->_body);
echo $body;
}
/**
* Register an exception with the response
*
* @param Exception $e
* @return Zend_Controller_Response_Abstract
*/
public function setException(Exception $e)
{
$this->_exceptions[] = $e;
return $this;
}
/**
* Retrieve the exception stack
*
* @return array
*/
public function getException()
{
return $this->_exceptions;
}
/**
* Has an exception been registered with the response?
*
* @return boolean
*/
public function isException()
{
return !empty($this->_exceptions);
}
/**
* Does the response object contain an exception of a given type?
*
* @param string $type
* @return boolean
*/
public function hasExceptionOfType($type)
{
foreach ($this->_exceptions as $e) {
if ($e instanceof $type) {
return true;
}
}
return false;
}
/**
* Does the response object contain an exception with a given message?
*
* @param string $message
* @return boolean
*/
public function hasExceptionOfMessage($message)
{
foreach ($this->_exceptions as $e) {
if ($message == $e->getMessage()) {
return true;
}
}
return false;
}
/**
* Does the response object contain an exception with a given code?
*
* @param int $code
* @return boolean
*/
public function hasExceptionOfCode($code)
{
$code = (int) $code;
foreach ($this->_exceptions as $e) {
if ($code == $e->getCode()) {
return true;
}
}
return false;
}
/**
* Retrieve all exceptions of a given type
*
* @param string $type
* @return false|array
*/
public function getExceptionByType($type)
{
$exceptions = array();
foreach ($this->_exceptions as $e) {
if ($e instanceof $type) {
$exceptions[] = $e;
}
}
if (empty($exceptions)) {
$exceptions = false;
}
return $exceptions;
}
/**
* Retrieve all exceptions of a given message
*
* @param string $message
* @return false|array
*/
public function getExceptionByMessage($message)
{
$exceptions = array();
foreach ($this->_exceptions as $e) {
if ($message == $e->getMessage()) {
$exceptions[] = $e;
}
}
if (empty($exceptions)) {
$exceptions = false;
}
return $exceptions;
}
/**
* Retrieve all exceptions of a given code
*
* @param mixed $code
* @return void
*/
public function getExceptionByCode($code)
{
$code = (int) $code;
$exceptions = array();
foreach ($this->_exceptions as $e) {
if ($code == $e->getCode()) {
$exceptions[] = $e;
}
}
if (empty($exceptions)) {
$exceptions = false;
}
return $exceptions;
}
/**
* Whether or not to render exceptions (off by default)
*
* If called with no arguments or a null argument, returns the value of the
* flag; otherwise, sets it and returns the current value.
*
* @param boolean $flag Optional
* @return boolean
*/
public function renderExceptions($flag = null)
{
if (null !== $flag) {
$this->_renderExceptions = $flag ? true : false;
}
return $this->_renderExceptions;
}
/**
* Send the response, including all headers, rendering exceptions if so
* requested.
*
* @return void
*/
public function sendResponse()
{
$this->sendHeaders();
if ($this->isException() && $this->renderExceptions()) {
$exceptions = '';
foreach ($this->getException() as $e) {
$exceptions .= $e->__toString() . "\n";
}
echo $exceptions;
return;
}
$this->outputBody();
}
/**
* Magic __toString functionality
*
* Proxies to {@link sendResponse()} and returns response value as string
* using output buffering.
*
* @return string
*/
public function __toString()
{
ob_start();
$this->sendResponse();
return ob_get_clean();
}
}
PHP實戰Zend_Controller_Response_Http
PHP實戰
/** Zend_Controller_Response_Abstract */
require_once 'Zend/Controller/Response/Abstract.php';
/**
* Zend_Controller_Response_Http
*
* HTTP response for controllers
*
* @uses Zend_Controller_Response_Abstract
* @package Zend_Controller
* @subpackage Response
*/
class Zend_Controller_Response_Http extends Zend_Controller_Response_Abstract
{
}
PHP實戰常見使用用法
PHP實戰如果要發送響應輸出包括消息頭,使用sendResponse().
PHP實戰
$response->sendResponse();
PHP實戰Note: 默認地,前端控制器完成分發哀求后調用sendResponse();一般地,你不需要調用它.但是,如果你想處理響應或者用它來測試你可以使用Zend_Controller_Front::returnResponse(true)設置returnResponse 標志覆蓋默認行為:
PHP實戰
$front->returnResponse(true);
$response = $front->dispatch();
// do some more processing, such as logging...
// and then send the output:
$response->sendResponse();
PHP實戰在動作控制器中使用響應對象.把結果寫進響應對象,而不是直接渲染輸出和發送消息頭:
PHP實戰
// Within an action controller action:
// Set a header
$this->getResponse()
->setHeader('Content-Type', 'text/html')
->appendBody($content);
PHP實戰這樣做,可以在顯示內容之前,將所有消息頭一次發送.
PHP實戰Note: 如果使用動作控制器的 視圖集成(view integration),你不需要在相應對象中設置渲染的視圖腳本,因為Zend_Controller_Action::render() 默認完成了這些.
PHP實戰如果程序中發生了異常,檢查響應對象的isException() 標志,使用getException()獲取異常.此外,可以創建定制的響應對象重定向到錯誤頁面,記錄異常消息,漂亮的格式化異常消息等.
PHP實戰在前端控制器執行dispatch()后可以獲得響應對象,或者哀求前端控制器返回響應對象代替渲染輸出.
PHP實戰
// retrieve post-dispatch:
$front->dispatch();
$response = $front->getResponse();
if ($response->isException()) {
// log, mail, etc...
}
// Or, have the front controller dispatch() process return it
$front->returnResponse(true);
$response = $front->dispatch();
// do some processing...
// finally, echo the response
$response->sendResponse();
PHP實戰默認地,異常消息是不顯示的.可以通過調用renderExceptions()覆蓋默認設置.或者啟用前端控制器的throwExceptions():
PHP實戰
$response->renderExceptions(true);
$front->dispatch($request, $response);
// or:
$front->returnResponse(true);
$response = $front->dispatch();
$response->renderExceptions();
$response->sendResponse();
// or:
$front->throwExceptions(true);
$front->dispatch();
PHP實戰處理消息頭
PHP實戰如上文所述,響應對象的一項重要職責是收集和發出HTTP響應消息頭,相應地存在大量的辦法:
PHP實戰canSendHeaders() 用來判別消息頭是否已發送,該方法帶有一個可選的標志指示消息頭已發出時是否拋出異常.可以通過設置headersSentThrowsException 屬性為false來覆蓋默認設置.
setHeader($name, $value, $replace = false)用來設置單獨的消息頭.默認的不會替換已經存在的同名消息頭,但可以設置$replace 為true強制替換.
設置消息頭前,該方法先檢查canSendHeaders()看操作是否允許,并哀求拋出異常.
setRedirect($url, $code = 302) 設置HTTP定位消息頭準備重定向,如果提供HTTP狀態碼,重定向將會使用該狀態碼.
其內部調用setHeader()并使$replace 標志呈打開狀態確保只發送一次定位消息頭.
getHeaders() 返回一個消息頭數組,每個元素都是一個帶有'name'和'value'鍵的數組.
clearHeaders() 清除所有注冊的鍵值消息頭.
setRawHeader() 設置沒有鍵值對的原始消息頭,比如HTTP狀態消息頭.
getRawHeaders() 返回所有注冊的原始消息頭.
clearRawHeaders()清除所有的原始消息頭.
clearAllHeaders() 清除所有的消息頭,包括原始消息頭和鍵值消息頭.
PHP實戰除了上述方法,還有獲取和設置當前哀求HTTP響應碼的訪問器, setHttpResponseCode() 和 getHttpResponseCode().
PHP實戰命名片段
PHP實戰相應對象支持“命名片段”.允許你將消息體分割成不同的片段,并呈一定順序排列.因此輸出的是以特定次序返回的.在其內部,主體內容被存儲為一個數組,大量的拜訪器方法可以用來指示數組內位置和名稱.
PHP實戰舉例來說,你可以使用preDispatch() 鉤子來向響應對象中加入頁頭,然后在動作控制器中加入主體內容,最后在postDispatch()鉤子中加入頁腳.
PHP實戰
// Assume that this plugin class is registered with the front controller
class MyPlugin extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$response = $this->getResponse();
$view = new Zend_View();
$view->setBasePath('../views/scripts');
$response->prepend('header', $view->render('header.phtml'));
}
public function postDispatch(Zend_Controller_Request_Abstract $request)
{
$response = $this->getResponse();
$view = new Zend_View();
$view->setBasePath('../views/scripts');
$response->append('footer', $view->render('footer.phtml'));
}
}
// a sample action controller
class MyController extends Zend_Controller_Action
{
public function fooAction()
{
$this->render();
}
}
PHP實戰上面的例子中,調用/my/foo會使得最終響應對象中的內容呈現下面的結構:
PHP實戰
array(
'header' => ..., // header content
'default' => ..., // body content from MyController::fooAction()
'footer' => ... // footer content
);
PHP實戰渲染響應時,會依照數組中元素順序來渲染.
PHP實戰大量的辦法可以用來處理命名片段:
PHP實戰setBody() 和 appendBody() 都允許傳入一個$name參數,指示一個命名片段.如果提供了這個參數,將會覆蓋指定的命名片段,如果該片段不存在就創建一個.如果沒有傳入$name參數到setBody(),將會重置整個主體內容.如果沒有傳入$name參數到appendBody(),內容被附加到'default'命名片段.
prepend($name, $content) 將創建一個$name命名片段并放置在數組的開始位置.如果該片段存在,將首先移除.
append($name, $content) 將創建一個$name命名片段,并放置在數組的結尾位置. 如果該片段存在,將首先移除.
insert($name, $content, $parent = null, $before = false) 將創建一個$name命名片段.如果提供$parent參數,新的片段視$before的值決定放置在
$parent的前面或者后面.如果該片段存在,將首先移除.
clearBody($name = null) 如果$name參數提供,將刪除該片段,否則刪除全部.
getBody($spec = false) 如果$spec參數為一個片段名稱,將可以獲取到該字段.若$spec參數為false,將返回字符串格式的命名片段順序鏈.如果$spec參數為true,返回主體內容數組.
PHP實戰在響應對象中測試異常
PHP實戰如上文所述,默認的,分發過程中的異常發生會在響應對象中注冊.異常會注冊在一個堆中,允許你拋出所有異常--程序異常,分發異常,插件異常等.如果你要檢查或者記錄特定的異常,你可能想要使用響應對象的異常API:
PHP實戰setException(Exception $e) 注冊一個異常.
isException() 判斷該異常是否注冊.
getException() 返回整個異常堆.
hasExceptionOfType($type) 判斷特定類的異常是否在堆中.
hasExceptionOfMessage($message) 判斷帶有指定消息的異常是否在堆中.
hasExceptionOfCode($code) 判斷帶有指定代碼的異常是否在堆中.
getExceptionByType($type) 獲取堆中特定類的所有異常.如果沒有則返回false,否則返回數組.
getExceptionByMessage($message) 獲取堆中帶有特定消息的所有異常.如果沒有則返回false,否則返回數組.
getExceptionByCode($code) 獲取堆中帶有特定編碼的所有異常.如果沒有則返回false,否則返回數組.
renderExceptions($flag) 設置標志指示當發送響應時是否發送其中的異常.
PHP實戰自定義響應對象
PHP實戰響應對象的目的首先在于從大量的動作和插件中收集消息頭和內容,然后返回到客戶端;其次,響應對象也收集發生的任何異常,以處理或者返回這些異常,再或者對終端用戶暗藏它們.
PHP實戰響應的基類是Zend_Controller_Response_Abstract,創建的任何子類必須繼承這個類或它的衍生類.前面的章節中已經列出了大量可用的辦法.
PHP實戰自定義響應對象的原因包括基于哀求環境修改返回的內容的輸出方式(例如:在CLI和PHP-GTK哀求中不發送消息頭)增加返回存儲在命名片段中內容的最終視圖的功能等等.
PHP實戰更多關于zend相關內容感興趣的讀者可查看本站專題:《Zend FrameWork框架入門教程》、《php優秀開發框架總結》、《Yii框架入門及常用技巧總結》、《ThinkPHP入門教程》、《php面向對象程序設計入門教程》、《php+mysql數據庫操作入門教程》及《php常見數據庫操作技巧匯總》
PHP實戰希望本文所述對大家PHP程序設計有所贊助.
維易PHP培訓學院每天發布《PHP實戰:Zend Framework教程之響應對象的封裝Zend_Controller_Response實例詳解》等實戰技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培養人才。
轉載請注明本頁網址:
http://www.snjht.com/jiaocheng/7401.html