Yii框架灵活的扩展受到公司的青睐,所以,项目中使用了yii,为了兼容原来的系统,依然选择了yii1.1的版本。
这里不讲yii的特性,主要说一说使用yii时对curl的再次封装。
先看看yii的配置文件,在main.php中将curl配置为Components。
'components' => array(
// Curl库 调用:Yii::app()->curl
'curl' => array(
'class' => 'ext.curl.Curl',
'options' => array(
CURLOPT_HTTPHEADER => array('Expect:'),
CURLOPT_HEADER => '',
),
),
再看看extensions/curl/Curl.php文件的内容,既然要做为Components配置,那么就要集成YII的核心组件类CComponents:
CURL的请求有几个变量进行说明:
- ch:curl初始化生成的句柄
- options:请求参数选项
- url:请求访问url
- response:请求响应
初始化:
init()函数封装了原curl的curl_init()函数,将类自身的数组变量options与配置文件中的options合并,并设置了请求协议、请求方式、头部消息等数据包请求参数选项,在请求事件完成后,关闭curl句柄。这里面用到了一个闭包的知识,具体查看php手册。
public function init()
{
try {
$this->_ch = curl_init();
$options = is_array($this->options) ? ($this->options + $this->_config) : $this->_config;
$this->setOptions($options);
$ch = $this->_ch;
// close curl on exit
@Yii::app()->onEndRequest = function() use(&$ch){
curl_close($ch);
};
} catch (Exception $e) {
throw new CException('Curl not installed');
}
}
再来看看初始化使用到的一个设置参数选项的函数setOptions($options) 函数:
这个函数就是把参数绑定包curl句柄ch上,一般情况使用单个绑定,这里利用php5.5.0中新增的一个批量设置参数函数curl_setopt_array($ch,$options) 。
curl_setopt($ch, CURLOPT_HTTPHEADER, $header); //设置http头
curl_setopt($ch, CURLOPT_ENCODING, gzip ); //设置为客户端支持gzip压缩
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30 ); //设置连接等待时间
curl_setopt($ch, CURLOPT_URL, $url );
上面这是单个参数绑定方法。
public function setOptions($options = array())
{
curl_setopt_array($this->_ch, $options);
return $this;
}
封装的时候考虑单个绑定:
public function setOption($option, $value)
{
curl_setopt($this->_ch, $option, $value);
return $this;
}
再来看看执行访问的函数exec($url):
<strong><span style="color:#FF6666;">private</span></strong> function exec($url)
{
$this->setOption(CURLOPT_URL, $url);
$this->response = curl_exec($this->_ch);
if (!curl_errno($this->_ch)) {
if (isset($this->options[CURLOPT_HEADER]))
if ($this->options[CURLOPT_HEADER]) {
$header_size = curl_getinfo($this->_ch, CURLINFO_HEADER_SIZE);
return substr($this->response, $header_size);
}
return $this->response;
} else {
throw new CException(curl_error($this->_ch));
return false;
}
}
exec 函数中的参数就是访问的url,这时候就调用setOption单个绑定参数,调用curl的系统函数curl_exec($ch)执行请求访问url,将结果赋值给类的私有成员变量response,并对其做了过滤处理(curl_errno,curl_getinfo)。
curl_errno: returnstring|boolean Returns escaped string or FALSE on failure.
curl_getinfo: returnstring the error message or '' (the empty string) if no* error occurred.
以下就是具体的四种请求+多维数组和文件上传请求:
public function get($url, $params = array())
{
$this->setOption(CURLOPT_HTTPGET, true);
return $this->exec($this->buildUrl($url, $params));
}
/**
* 内部接口post请求专用
*
* 支持多维数组 和 文件上传
*
*/
public function httpPostRequest($url,$data = array()){
$this->setOption(CURLOPT_POST, true);
$this->setOption(CURLOPT_POSTFIELDS, http_build_query($data));
return $this->exec($url);
}
/**
* Curl 原生post请求发送方式
*
* 用于需要post无key参数、post文件等
*/
public function post($url, $data = array())
{
$this->setOption(CURLOPT_POST, true);
$this->setOption(CURLOPT_POSTFIELDS, $data);
return $this->exec($url);
}
public function put($url, $data, $params = array())
{
// write to memory/temp
$f = fopen('php://temp', 'rw+');
fwrite($f, $data);
rewind($f);
$this->setOption(CURLOPT_PUT, true);
$this->setOption(CURLOPT_INFILE, $f);
$this->setOption(CURLOPT_INFILESIZE, strlen($data));
return $this->exec($this->buildUrl($url, $params));
}
public function delete($url, $params = array())
{
$this->setOption(CURLOPT_RETURNTRANSFER, true);
$this->setOption(CURLOPT_CUSTOMREQUEST, 'DELETE');
return $this->exec($this->buildUrl($url, $params));
}
上面四种请求中,用到了url,封装的目的是为了通用,所以,url需要依据重新生成:
public function buildUrl($url, $data = array())
{
$parsed = parse_url($url);
isset($parsed['query']) ? parse_str($parsed['query'], $parsed['query']) : $parsed['query'] = array();
$params = isset($parsed['query']) ? array_merge($parsed['query'], $data) : $data;
$parsed['query'] = ($params) ? '?' . http_build_query($params) : '';
if (!isset($parsed['path'])) {
$parsed['path']='/';
}
$parsed['port'] = isset($parsed['port'])?':'.$parsed['port']:'';
return $parsed['scheme'].'://'.$parsed['host'].$parsed['port'].$parsed['path'].$parsed['query'];
}
其实,这个函数写的很有意思:首先把原来的url利用parse_url解析成parsed数组,数组的key可能包括:schema,host,port,path,query等。isset($parsed['query']) ? parse_str($parsed['query'], $parsed['query']) : $parsed['query'] = array();这句是先判断是否存在query这样的key,query是什么呢?query是get的参数,如果存在,则把parsed['query']解析成数组,如果未设置,则赋值为空数组。$params = isset($parsed['query']) ? array_merge($parsed['query'], $data) : $data;这句,判断parsed['query']是否存在,如果存在,则与参数data合并,如果不存在,则把data赋值给params。最后,再把各个参数组合成完整的url。
http_build_query()这个函数会把数组解析成字符串,并且用"&"连接起来。
最后看看其他几个有用的函数:
public function setHeaders($header = array())
{
if ($this->isAssoc($header)) {
$out = array();
foreach ($header as $k => $v) {
$out[] = $k .': '.$v;
}
$header = $out;
}
$this->setOption(CURLOPT_HTTPHEADER, $header);
return $this;
}
public function getHeaders()
{
$headers = array();
$header_text = substr($this->response, 0, strpos($this->response, "\r\n\r\n"));
foreach (explode("\r\n", $header_text) as $i => $line) {
if ($i === 0) {
$headers['http_code'] = $line;
} else {
list ($key, $value) = explode(': ', $line);
$headers[$key] = $value;
}
}
return $headers;
}
private function isAssoc($arr)
{
return array_keys($arr) !== range(0, count($arr) - 1);
}
public function getError()
{
return curl_error($this->_ch);
}
public function getInfo()
{
return curl_getinfo($this->_ch);
}
public function getStatus()
{
return curl_getinfo($this->_ch, CURLINFO_HTTP_CODE);
}
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
下面是完整的代码:
<?php
/**
* Curl wrapper for Yii
*
*/
class Curl extends CComponent
{
private $_ch;
private $response;
// config from config.php
public $options;
// default config
private $_config = array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HEADER => false,
CURLOPT_VERBOSE => true,
CURLOPT_AUTOREFERER => true,
CURLOPT_CONNECTTIMEOUT => 30,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'
);
private function exec($url)
{
$this->setOption(CURLOPT_URL, $url);
$this->response = curl_exec($this->_ch);
if (!curl_errno($this->_ch)) {
if (isset($this->options[CURLOPT_HEADER]))
if ($this->options[CURLOPT_HEADER]) {
$header_size = curl_getinfo($this->_ch, CURLINFO_HEADER_SIZE);
return substr($this->response, $header_size);
}
return $this->response;
} else {
throw new CException(curl_error($this->_ch));
return false;
}
}
public function get($url, $params = array())
{
$this->setOption(CURLOPT_HTTPGET, true);
return $this->exec($this->buildUrl($url, $params));
}
/**
*
*
* 支持多维数组 和 文件上传
*
*/
public function httpPostRequest($url,$data = array()){
$this->setOption(CURLOPT_POST, true);
$this->setOption(CURLOPT_POSTFIELDS, http_build_query($data));
return $this->exec($url);
}
/**
* Curl 原生post请求发送方式
*
* 用于需要post无key参数、post文件等
*/
public function post($url, $data = array())
{
$this->setOption(CURLOPT_POST, true);
$this->setOption(CURLOPT_POSTFIELDS, $data);
return $this->exec($url);
}
public function put($url, $data, $params = array())
{
// write to memory/temp
$f = fopen('php://temp', 'rw+');
fwrite($f, $data);
rewind($f);
$this->setOption(CURLOPT_PUT, true);
$this->setOption(CURLOPT_INFILE, $f);
$this->setOption(CURLOPT_INFILESIZE, strlen($data));
return $this->exec($this->buildUrl($url, $params));
}
public function delete($url, $params = array())
{
$this->setOption(CURLOPT_RETURNTRANSFER, true);
$this->setOption(CURLOPT_CUSTOMREQUEST, 'DELETE');
return $this->exec($this->buildUrl($url, $params));
}
public function buildUrl($url, $data = array())
{
$parsed = parse_url($url);
isset($parsed['query']) ? parse_str($parsed['query'], $parsed['query']) : $parsed['query'] = array();
$params = isset($parsed['query']) ? array_merge($parsed['query'], $data) : $data;
$parsed['query'] = ($params) ? '?' . http_build_query($params) : '';
if (!isset($parsed['path'])) {
$parsed['path']='/';
}
$parsed['port'] = isset($parsed['port'])?':'.$parsed['port']:'';
return $parsed['scheme'].'://'.$parsed['host'].$parsed['port'].$parsed['path'].$parsed['query'];
}
public function setOptions($options = array())
{
curl_setopt_array($this->_ch, $options);
return $this;
}
public function setOption($option, $value)
{
curl_setopt($this->_ch, $option, $value);
return $this;
}
public function setHeaders($header = array())
{
if ($this->isAssoc($header)) {
$out = array();
foreach ($header as $k => $v) {
$out[] = $k .': '.$v;
}
$header = $out;
}
$this->setOption(CURLOPT_HTTPHEADER, $header);
return $this;
}
private function isAssoc($arr)
{
return array_keys($arr) !== range(0, count($arr) - 1);
}
public function getError()
{
return curl_error($this->_ch);
}
public function getInfo()
{
return curl_getinfo($this->_ch);
}
public function getStatus()
{
return curl_getinfo($this->_ch, CURLINFO_HTTP_CODE);
}
// initialize curl
public function init()
{
try {
$this->_ch = curl_init();
$options = is_array($this->options) ? ($this->options + $this->_config) : $this->_config;
$this->setOptions($options);
$ch = $this->_ch;
// close curl on exit
@Yii::app()->onEndRequest = function() use(&$ch){
curl_close($ch);
};
} catch (Exception $e) {
throw new CException('Curl not installed');
}
}
public function getHeaders()
{
$headers = array();
$header_text = substr($this->response, 0, strpos($this->response, "\r\n\r\n"));
foreach (explode("\r\n", $header_text) as $i => $line) {
if ($i === 0) {
$headers['http_code'] = $line;
} else {
list ($key, $value) = explode(': ', $line);
$headers[$key] = $value;
}
}
return $headers;
}
}