第 2 步:初始化应用数据库和模型
下一步是初始化应用数据库。所以,我们要创建一个新的MySQL表来保存产品信息,如下所示:
以下是引用片段:
mysql> CREATE TABLE IF NOT EXISTS products (
-> id int(11) NOT NULL AUTO_INCREMENT,
-> title varchar(200) NOT NULL,
-> shortdesc text NOT NULL,
-> price float NOT NULL,
-> quantity int(11) NOT NULL,
-> PRIMARY KEY (id)
-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在这个表中填入一些示例记录以便开始开发,如下所示:
以下是引用片段:
mysql> INSERT INTO products (id, title, shortdesc, price, quantity) VALUES(1,
-> 'Ride Along Fire Engine', 'This red fire engine is ideal for toddlers who
-> want to travel independently. Comes with flashing lights and beeping horn.',
-> 69.99, 11);
Query OK, 1 row affected (0.08 sec)
mysql> INSERT INTO products (id, title, shortdesc, price, quantity) VALUES(2,
-> 'Wind-Up Crocodile Bath Toy', 'This wind-up toy is the perfect companion
-> for hours of bathtub fun.', 7.99, 67);
Query OK, 1 row affected (0.08 sec)
第 3 步:配置应用的名称空间
最后一步是为Zend Framework自动加载配置名称空间。这个步骤将在需要时实现自动加载特定应用类到应用中。在这里,我假设应用的名称空间为Example,而特定应用类(如SOAP服务类)将存储在$PROJECT/library/Example/中。所以,要修改应用配置文件$PROJECT/application/configs/application.ini并添加下面一行到文件中:
以下是引用片段:
autoloaderNamespaces[] = "Example_"
您现在已经完成了创建一个SOAP服务的所有准备工作!
查询数据
因为这是一个示例应用,我将尽量保持简单,并只在默认模块的IndexController上创建一个处理SOAP请求的动作;然而,在实际中,您可能希望使用一个单独的控制器处理SOAP请求。编辑文件$PROJECT/application/controllers/IndexController.php,然后添加新的动作,如 清单 3 所示:
清单 3. soapAction() 的定义
以下是引用片段:
<?php
class IndexController extends Zend_Controller_Action
{
public function soapAction()
{
// disable layouts and renderers
$this->getHelper('viewRenderer')->setNoRender(true);
// initialize server and set URI
$server = new Zend_Soap_Server(null,
array('uri' => 'http://example.localhost/index/soap'));
// set SOAP service class
$server->setClass('Example_Manager');
// handle request
$server->handle();
}
}
清单 3 传递一个null值到对象构造函数的第一个参数,以非WSDL模式初始化了一个新的Zend_Soap_Server对象。如果以非WSDL模式创建服务器,我们必须指定服务器URI;在 清单 3 中,这是在作为第二个参数传递给构造函数的选项数组中指定的。
接下来,服务器对象的setClass()函数用于将一个服务类附加到服务器上。这个类实现了SOAP服务的可用函数;这个服务器将在SOAP请求的响应中自动调用这些函数。如果您喜欢,您也可以使用addFunction()和loadFunctions()函数将用户自定义函数附加到服务器上,而不需要使用 setClass()函数附加整个类。
正如之前所提到的,Zend_Soap_Server类并没有提供它自己的SOAP服务器实现;它只是封装了 PHP 的内置SOAP扩展。因此,一旦所有先决条件都准备好后,清单 3 中的handle()函数会负责初始化内置的PHP SoapServer对象,将它传递给请求对象,并调用该对象的handle() 函数处理SOAP请求。
虽然所有这些都做好了,但是这还远远不够,因为服务类还没有定义。接下来我们使用 清单 4 中的代码创建这个类定义,将创建的类定义保存到 $PROJECT/library/Example/Manager.php:
清单 4. 带有get*()函数的服务对象定义
以下是引用片段:
<?php
class Example_Manager {
/**
* Returns list of all products in database
*
* @return array
*/
public function getProducts()
{
$db = Zend_Registry::get('Zend_Db');
$sql = "SELECT * FROM products";
return $db->fetchAll($sql);
}
/**
* Returns specified product in database
*
* @param integer $id
* @return array|Exception
*/
public function getProduct($id)
{
if (!Zend_Validate::is($id, 'Int')) {
throw new Example_Exception('Invalid input');
}
$db = Zend_Registry::get('Zend_Db');
$sql = "SELECT * FROM products WHERE id = '$id'";
$result = $db->fetchAll($sql);
if (count($result) != 1) {
throw new Exception('Invalid product ID: ' . $id);
}
return $result;
}
}
?>
清单 4 创建了一个单独的服务类,它包含两个函数。getProducts()函数使用Zend_Db查询表中所有的产品记录,然后将它们作为一个数组返回,而 getProduct() 函数则接收一个产品标识符,然后只返回特定的一条记录。然后SOAP服务器将这个方法的返回值转换成一个 SOAP 响应数据包,并将它返回给发送请求的客户端。清单8包含一个响应数据包的例子:
如果您还在疑惑Zend_Db是在哪里初始化的,我可以告诉您它是在应用启动加载器中初始化的,即$PROJECT/application/Bootstrap.php。这个 Bootstrap.php包含了一个 _initDatabase()函数,它创建Zend_Db适配器,并将它注册到应用注册表中。清单 5 显示这部分代码:
清单 5. 数据库适配器初始化
以下是引用片段:
<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initDatabase()
{
$db = new Zend_Db_Adapter_Pdo_Mysql(array(
'host' => 'localhost',
'username' => 'user',
'password' => 'pass',
'dbname' => 'example'
));
Zend_Registry::set('Zend_Db', $db);
}
}
为了看到实际结果,要创建一个SOAP客户端(清单 6),然后使用它连接SOAP服务,并请求getProducts()函数。
清单 6. 一个示例SOAP客户端
以下是引用片段:
<?php
// load Zend libraries
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Soap_Client');
// initialize SOAP client
$options = array(
'location' => 'http://example.localhost/index/soap',
'uri' => 'http://example.localhost/index/soap'
);
try {
$client = new Zend_Soap_Client(null, $options);
$result = $client->getProducts();
print_r($result);
} catch (SoapFault $s) {
die('ERROR: [' . $s->faultcode . '] ' . $s->faultstring);
} catch (Exception $e) {
die('ERROR: ' . $e->getMessage());
}
?>
SOAP客户端将会产生一个请求数据包(清单 7)。
清单 7. getProducts()的一个示例SOAP请求
以下是引用片段:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"
xmlns:ns1="http://example.localhost/index/soap"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:enc="http://www.w3.org/2003/05/soap-encoding">
<env:Body>
<ns1:getProducts env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"/>
</env:Body>
</env:Envelope>
这个服务器产生一个使用SOAP编码的响应(清单 8)。
清单 8. getProducts() 函数的一个示例SOAP响应
以下是引用片段:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"
xmlns:ns1="http://example.localhost/index/soap"
xmlns:ns2="http://xml.apache.org/xml-soap"
xmlns:enc="http://www.w3.org/2003/05/soap-encoding"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<env:Body xmlns:rpc="http://www.w3.org/2003/05/soap-rpc">
<ns1:getProductsResponse
env:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<rpc:result>return</rpc:result>
<return enc:itemType="ns2:Map" enc:arraySize="2" xsi:type="enc:Array">
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">id</key>
<value xsi:type="xsd:string">1</value>
</item>
<item>
<key xsi:type="xsd:string">title</key>
<value xsi:type="xsd:string">Ride Along Fire Engine</value>
</item>
<item>
<key xsi:type="xsd:string">shortdesc</key>
<value xsi:type="xsd:string">This red fire engine is ideal
for toddlers who want to travel independently.
Comes with flashing lights and beeping horn.</value>
</item>
<item>
<key xsi:type="xsd:string">price</key>
<value xsi:type="xsd:string">69.99</value>
</item>
<item>
<key xsi:type="xsd:string">quantity</key>
<value xsi:type="xsd:string">11</value>
</item>
</item>
...
</return>
</ns1:getProductsResponse>
</env:Body>
</env:Envelope>
然后SOAP客户端会将这个响应转换成一个原生的PHP数组,它可以被进一步处理或检查,如 图 2 所示。

图 2. 被转换成一个原生PHP数组的SOAP请求结果