分类目录归档:PHP

SimpleXML函数库在豆瓣API调用中的应用

SimpleXML是PHP5提供的一套用于处理XML的函数库,它通过simplexml_load_file和simplexml_load_string两个函数实现将XML对象转换为simplexml对象(SimpleXMLElement),前者用于处理XML文档,后者用于处理符合XML标准的字符串,程序员可以通过simplexml对象直接读写XML内容。

相比普通XML格式文件,调用豆瓣API返回的结果中有两个地方需要特殊处理:

1. 节点名称中含命名空间。例如<db:attribute />、<db:tag /> 、<gd:rating />等节点分别包含在db和gd的命名空间中,命名空间在XML文件会有定义。对于包含在命名空间的节点名的处理,SimpleXML函数库提供了children函数。通常children函数是用来获得SimpleXMLElement对象的子节点的值,其时,children函数还接受namespace的参数,用来获取当前节点内部指定命名空间中的子节点的值。因此,通过带namespace参数的children函数可以解析上述的<db:attribute />、<db:tag />和<gd:rating />子节点。

2. 节点名称内部包含属性值。例如<db:tag count="63706" name="青春" />,tag节点的内部属性count的值为“63706”,内部属性name的值为“青春”。内部属性的名称和值在一些场合下非常有用,甚至必需。SimpleXML函数库提供了attributes函数来解析节点的内部属性。对一个节点调用attributes函数,会返回一个关联数组,键为内部属性名,值为内部属性值,例如上述的tag节点会被解析为数组:Array ( [count] => 63706 [name] => 青春 ) )。

以我最近比较喜欢的电影『那些年,我们一起追的女孩』为例,这部电影相关信息的API地址为http://api.douban.com/movie/subject/4920528(缓存页面),通过SimpleXML函数库解析返回结果的PHP代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
header("Content-type: text/html; charset=utf-8");//中文支持。
$url = 'http://api.douban.com/movie/subject/4920528';//影片对应的API地址。
$xml =  simplexml_load_file($url); //解析XML文件形式的返回结果。
 
print "id: " . $xml->id . "<br>";//影片ID,URL。
print "title: ".$xml->title . "<br>";//标题。
print "author: ".$xml->author->name . "<br>";//导演。
print "summary: " . $xml->summary . "<br>";//故事梗概。
 
//调用children函数获取db命名空间下的节点数组。
$db_xml = $xml->children('http://www.douban.com/xmlns/');
//var_dump($db_xml); //db命名空间只包含attribute节点和tag节点。
foreach($db_xml->attribute as $obj){//遍历所有<db:attribute />节点。
	print "db:attribute: ";
	$attrname = $obj->attributes();//调用attributes()函数获取内部属性数组。
	foreach($attrname as $key=>$value){//遍历当前节点的所有内部属性。
		print $key . "=>" . $value . " ";
	}
	print $obj . "<br>";
}
//<db:tag />节点的解析同<db:attribute />。省略。
 
//获取gd命名空间下的节点的方法同db命名空间。省略。
?>

上述程序的运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
id: http://api.douban.com/movie/subject/4920528
title: 那些年,我们一起追的女孩
author: 九把刀 Giddens
summary: 柯景腾(柯震东 饰)、老曹(敖犬 饰)、勃起(鄢胜宇 饰)、该边(蔡昌宪 饰),
、阿和(赦绍文 饰),从国中到高中,一直是不离不弃的死党。他们都对班花沈佳宜(陈妍希 饰)
有着一种纠结的感情。一方面,他们瞧不起这种只会用功读书的女生,另一方面他们又为她的美好
气质倾倒。因为学习成绩较差,柯景腾被老师安排坐在沈佳宜前面。因为他的一次英雄救美,她开始
用强制的方式帮他补习功课。此事令其他兄弟羡慕嫉妒恨,但是大家都未说破。毕业后,柯景腾和
沈佳宜在各自大学保持恋人般的联系。直到他举办比武大会,事情才出现了变化…… 本片根据导演
兼编剧九把刀在2007年的自传体小说改编。柯震东凭借本片获第48届金马奖最佳新演员。©豆瓣
db:attribute: name=>director 九把刀
db:attribute: lang=>zh_CN name=>aka 那些年,我们一起追的女孩
db:attribute: name=>movie_type 剧情
db:attribute: name=>movie_type 喜剧
db:attribute: name=>movie_type 爱情
db:attribute: name=>website www.appleofmyeye.com.tw
db:attribute: name=>movie_duration 110分钟(港澳台)
db:attribute: name=>movie_duration 100分钟(中国大陆)
db:attribute: name=>year 2011
db:attribute: name=>writer 九把刀
db:attribute: name=>language 汉语普通话
db:attribute: name=>imdb http://www.imdb.com/title/tt2036416/
db:attribute: name=>aka You Are the Apple of My Eye
db:attribute: name=>aka 나사년, 아문일기추적녀해
db:attribute: name=>aka あの頃、君を追いかけた
db:attribute: name=>aka 那些年,我們一起追的女孩
db:attribute: name=>aka 那些年,我们一起追的女孩
db:attribute: name=>title 那些年,我们一起追的女孩
db:attribute: name=>country 台湾
db:attribute: name=>pubdate 2011-08-19(台湾)
db:attribute: name=>pubdate 2011-10-20(香港)
db:attribute: name=>pubdate 2012-01-06(中国大陆)
db:attribute: name=>cast 柯震东
db:attribute: name=>cast 陈妍希
db:attribute: name=>cast 敖犬
db:attribute: name=>cast 郝邵文
db:attribute: name=>cast 蔡昌宪
db:attribute: name=>cast 鄢胜宇
db:attribute: name=>cast 弯弯
db:attribute: name=>cast 邱彦翔

更多的SimpleXML函数库用法可参见官方文档

--EOF--

短网址API调用实例(PHP实现)

短网址是随着微博的流行而出现,目前市面上的短网址服务林林总总不计其数,评价一个短网址服务好坏的最重要标准是其服务的稳定性。试想,短网址服务做的工作就是为原先的长网址加一层映射关系,而这种映射关系是握在短网址服务提供商手上的,如果这个环节出了问题,导致映射关系丢失,那也就意味着再也无法找回原先的长网址。

以上差不多都是废话。

国内外比较有实力的短网址提供商如goo.gl(Google)、dwz.cn(百度)、is.gd等都提供了API的方式供外部生成短网址,该方式只需模拟一个HTTP请求,以长网址作为参数向服务器发送该请求,如果请求成功,服务器就会返回缩短以后的短网址。

有些API要求将HTTP请求以POST方式提交,例如goo.gl和dwz.cn(demo),这时可以采用基于PHP的cURL库来完成HTTP请求的发送和接收。以goo.gl为例(更多细节可参考『Google URL Shortener API』):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$apiKey = 'APIKEY'; //google短网址所需。
//API key从http://code.google.com/apis/console/得到
$postData = array('longUrl' => "http://fengchj.com", 'key' => $apiKey);
$jsonData = json_encode($postData);
$co = curl_init();
curl_setopt($co, CURLOPT_URL, 'https://www.googleapis.com/urlshortener/v1/url');
curl_setopt($co, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($co, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($co, CURLOPT_HEADER, 0);
curl_setopt($co, CURLOPT_HTTPHEADER, array('Content-type:application/json'));
curl_setopt($co, CURLOPT_POST, 1);
curl_setopt($co, CURLOPT_POSTFIELDS, $jsonData);
$strResponse = curl_exec($co);
curl_close($co);
$arrResponse = json_decode($strResponse );
print $arrResponse->id; //得到http://goo.gl/xxxx类型的短网址。

有些短网址服务API接受以GET方式提交的长网址,例如is.gd,用户只需将长网址作为参数向提供商给出的URL发送请求即可,如:

1
print file_get_contents("http://is.gd/create.php?format=simple&url=http://fengchj.com");

实际上这里也可采用cURL库进行GET方式HTTP请求的封装,但是由于GET方式的幂等特性,用file_get_contents来请求给定URL页面更为方便。相对于cURL库,file_get_contents方式的缺点在于无法对HTTP请求过程进行错误处理。

调用外部API生成短网址的效果可参见http://fengchj.com/?page_id=1619

--EOF--

UCHome二次开发小结

这几个周末,算上中秋假期,断断续续地把这个基于UCHome的小项目的后台工作开发完毕,感觉还可以,大致摸清了基于UCHome进行二次开发的套路。以下是一点非正式的总结。

UCHome全称UCenter Home,是一款用PHP+MySQL开发的社会化Web2.0框架,它和Discuz!一样都是康盛公司的产品,不过从它的官网看最新的2.0版还是去年11月份的,似乎康盛不打算继续更新下去了。

虽然PHP软件通过include和require实际上执行的代码都是在同一个页面里,但是UCHome框架从逻辑上还是可以分层的,它分为三层:表现层、业务层、持久层。这种分层方法并不严谨,只是大致上有点意思。

1、表现层:UCHome的表现层采用模板技术。所有页面元素都是写在各自的htm静态页面里的,等系统启动后,才会按照这些htm页面来生成可执行的动态PHP页面,以“活动”页面为例,活动的页面包含在template目录的各个主题下面,主要包含space_event_list.htm、space_event_view.htm、cp_event.htm等页面,它们分别对应着活动列表、活动详情和发布活动页面。静态htm页面中是通过一些自定义的模板解析来控制的,这些指令包括if、loop和eval,if进行条件判断,loop用于遍历循环,eval用户执行php语句,它们在使用是要包含在“<!--{”和“}-->”符号中间,关于模板解析指令的语法可以参考这篇官方文档。当用户第一次访问这个页面时,框架会根据相应模板在data目录下的tpl_cache目录里生成php页面,这些php文件才是真正的执行体。tpl_cache目录是缓存目录,里面的文件可以任意删除。在开发的时,如果修改了模板页面,如果要即时在浏览器中看到效果,那么必须先删除这个目录中的文件才行,相当于清空缓存操作,让框架重新生成这个页面。

2、业务层:业务层代码都写在data目录下,这里面有页面展示前和页面表单提交后的业务逻辑处理。cp前缀的表示发布新动态(活动, 博客, 相册, 投票, etc),cp_event.php就是发起新活动,cp_blog.php就是发表新博客。space前缀的文件是各种动态对应的处理逻辑,例如,space_event.php页面会被包含在space_event_list.htm和space_event_view.htm页面里,活动页面上提交的表单都会在space_event.php里处理,总之,它充当着一个控制器的角色,用户点击页面显示哪些内容都是在这里面控制。业务层同表现层通过几个常用的数组结构进行数据交互,例如有$space数组和$_SGLOBAL数组,后者估计是全站最重要的数据结构了,存储了大量信息,这里可以看到它的完整结构。

3、持久层:持久层我想到了function_common.php,这个文件也在source目录中。除了很多持久层的数据库操作可以写在这里外,这个文件中还包含了很多通用函数,可供全站调用。UCHome对数据库操作进行了简单的封装,数据库操作都被封装进了$_SGLOBAL['db']对象中。进行select操作可以这样:

1
2
3
4
$query = $_SGLOBAL['db']->query("SELECT * FROM ".tname('blog'));
while ($blog = $_SGLOBAL['db']->fetch_array($query)) {
	$bloglist[] = $blog;
}

$_SGLOBAL['db']->query(SQL)语句返回的是一个结果集,需要用一个while循环加$_SGLOBAL['db']->fetch_array()将它们取出来再返回,这里tname('tablename')也是一个函数,用于取出这个名字对应的数据库中的表名。查找是这样,增删则不必这样麻烦,只要将SQL语句写在$_SGLOBAL['db']->query()的参数中即可。框架本身也提供了inserttable()和updatetable()两个函数,用于向表中添加数据和更新数据,不过我没用到。

以上就是我这些日子断断续续了解的UCHome二次开发知识,有不当之处,欢迎指正,总结了错的东西误导大家就不好了。建议真要基于这个框架开发的同学去Discuz!的技术文库上好好看看,免得代码写得不规范,导致他们升级框架时给二次开发者带来无穷无尽的痛苦。

--EOF--

根据访问者IP获取当地天气(PHP实现)(非小偷程序)

网上用于获取天气的程序不少,做得比较好的有GeoIP Weather,它从GeoLite City(免费版下载)获得地址信息,天气预报数据源来自The Weather Channel,而且它也是开源的,有兴趣的朋友可以下载源代码来看看。

开始进入正题,一般而言在网站中放置地天气预报Widget不必做得像GeoIP Weather这么复杂,完全可以用其他方法替代。比较简单的方法是写一个小偷程序,从中国天气网QQSina等天气预报数据源抓取数据,这种方法实现较简单,只是特别不靠谱,当目标网站UI发生点细微的变化可能就得修改源程序。其实用小偷程序实现也是无奈,现在很少有好的天气预报服务提供商来提供Web Service接口,让大家无偿调用了。我花了老半天,理出个以下这么一个相对折中的办法,用非小偷程序实现根据访问者IP获取当地天气的功能。

程序基本思路:
1、根据来访IP获得地址信息。这里仍然通过现有的Web Service来实现,毕竟只是个Widget,没必要自己维护一个IP归属地的数据库。对比之后发现腾讯的这个服务不错:http://fw.qq.com/ipaddress,它会返回这样的信息:

1
var IPData = new Array("60.12.143.169","","浙江省","杭州市");

用php的file_get_content()函数获得这个字符串后,进行解析,得到城市名:杭州。(参考程序)

2、将汉语城市名转化成拼音。这个步骤是由于我选用Google的天气预报服务所致,Google的天气预报API只接受城市的英文名。(参考程序)

3、调用Google的天气预报API(汉字字符编码采用GB2312)。这个API的返回结果是一个xml文件,里面已包含当前的天气状况和近5天的天气预报。所以我们只需要解析这个xml文件就完成目标了。(参考程序)

4、客户调用代码后返回结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
杭州天气:
日期:2011-09-12
状况:多云
温度:29
图标:/ig/images/weather/mostly_cloudy.gif
风况:风向: 东、风速:3 米/秒
将来天气:
周一23-31可能有暴风雨
/ig/images/weather/chance_of_storm.gif
周二24-31晴
/ig/images/weather/sunny.gif
周三25-33以晴为主
/ig/images/weather/mostly_sunny.gif

完整程序详见附件(右键->另存为...,将.txt改为.php):附件。本程序主体部分来自互联网(见参考程序链接),本人为整合只做过一小部分修改。

以上步骤仅仅是提供一个可实现的思路。如果调用接口不同,第2步关于汉字与拼音的转换完全可以省略,这样程序自然也就能简洁很多了。Google的这个天气服务API是用在它的iGoogle个性化首页里的一个天气预报portlet,因此相对来说还是比较稳定的。此外,这个程序在查询外国城市天气时就有bug,第2步Pinyin()函数的功能是讲汉语转成对应的拼音,比如,东京,Google API接受的是Tokyo,而不是dongjing。

再补充一点,如果需求很简单,只需显示当前的天气情况,那么可以考虑腾讯提供的另一个接口:

1
2
3
4
5
6
<script type="text/javascript" 
        src="http://minisite.qq.com/js/j.minisite.weather.js"></script>
<div id="Wealth"></div>
<script type="text/javascript"> 
        MiniSite.Weather.print("Wealth");
</script>

显示效果会向下边这样:

1
杭州 16℃~29℃ 晴

--EOF--

新浪微博php sdk中$o->getRequestToken()返回值为空的解决方案

从新浪微博下载的官方php sdk,跑在本地服务器没问题,上载到虚拟主机上就出现如题所述的index.php页面里$o->getRequestToken()返回值为空。跟踪getRequestToken函数中

1
$request = $this->oAuthRequest($this->requestTokenURL(), 'GET', $parameters);

发现$request还是为空,继续查看oAuthRequest函数,oAuthRequest函数主要调用一个http()方法,该方法用于向新浪微博的授权中心发一个http请求,如果请求成功,会返回一个oauth_token。新浪微博的php sdk采用curl库抓取网页获得所需的返回值。用echo在http方法的前前后后打印了不少变量值,对比本地服务器的输出信息,很容易将问题定位在

1
$response = curl_exec($ci);

本地执行完这一句$response是有值返回的,而在虚拟主机上是没有值返回的。用var_dump(curl_error($ci))打印curl_exec()的错误信息。提示不能连接host,问题已经明确,只要确保虚拟主机能连接到request的请求地址

1
http://api.t.sina.com.cn/oauth/request_token

那么就能得到oauth_token,从而完成oAuth认证的第一步。

以上是$keys = $o->getRequestToken()返回空值的根本原因。而我去网上找过资料以后反而走了不少弯路。
首先,有人说碰到这个原因是虚拟主机上无法解析地址http://api.t.sina.com.cn/oauth/request_token。于是我将weibooauth.php中所有api.t.sina.com.cn都换成了它的ip地址(通过ping得到)。现在想想,其实这个方法是错误的,虽然ping能得到api.t.sina.com.cn的IP地址,但是却不能保证请求授权的地址就是http://IP地址/oauth/request_token。改后发现仍然调用失败,于是只能看weibooauth.php中的源代码,才有了之前的分析,把问题定位在curl_exec()函数,因为这个函数权限比较大,安全系数低,通常虚拟主机提供商会将它初始化为disable_functions,在服务器端用phpinfo()查了一下,果然curl_exec()在它的禁止函数列表中,于是找到客服,请求能不能放开这个函数,客服态度很好,几分钟就放开了curl_exec()执行权限。事实上,只要到了这一步,如果我当初没把api.t.sina.com.cn改成ip地址,那么所有问题就都解决了。

给出问题的解决方案:
1.确保主机能访问得到http://api.t.sina.com.cn/oauth/request_token。
2.确保主机上有curl_exec()的执行权限。

--EOF--