存档在 ‘PHP’ 分类

跨域获取json数据

2010年7月19日
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Test Jsonp</title>
	<script type="text/javascript">
        	function jsonpCallback(result)
        	{
			alert(result.msg);
        	}
    	</script>
	<script type="text/javascript" src="http://crossdomain.com/jsonServerResponse?jsonp=jsonpCallback"></script>
</head>
<body>
</body>
</html>

Ecshop二次开发之自定义库文件和模板数据调用

2010年7月14日

Smarty的标签非常多,也比较繁琐,所以Ecshop使用了精简版的Smarty,其中用到的Smarty模板语法:

{$keywords} 输出模板变量
{$array.test.abc} 输出二维数组
{$lang.remark_package} 语言包$lang[remark_package]
{insert_scripts files='common.js'} 引入js文件
<!-- {foreach from=$property_group item=property} -->xxxx<!-- {/foreach} --> 循环
<!-- {if $package_goods_list} -->xxxx<!-- {/if} -->  判断
<!-- #BeginLibraryItem "/library/goods_tags.lbi" --><!-- #EndLibraryItem -->  引入goods_tags.lbi库文件,相当于PHP的include
{*内容*}  html注释,只在模板上显示,页面上会被删除掉.

Ecshop二次开发的数据调用,主要动用到以下两个目录下的文件
dwt(模板文件)路径: themes\default
lbi(库文件)路径: themes\default\library
举个例子就很清楚了,是我从网上找来的,由于转载得很严重,所以不知道真正出处。。
我们在themes\default\library下新建一个名为newest_comments.lbi的库文件,写入以下内容:

if(!function_exists("get_new_comments")){
function get_new_comments($num)
{
   $sql = 'SELECT * FROM '. $GLOBALS['ecs']-&gt;table('comment') .
            ' WHERE status = 1 AND parent_id = 0 and comment_type=0 '.
            ' ORDER BY add_time DESC';
  if ($num &gt; 0)
  {
   $sql .= ' LIMIT ' . $num;
  }
  //echo $sql;
  $res = $GLOBALS['db']-&gt;getAll($sql);
  $comments = array();
  foreach ($res AS $idx =&gt; $row)
  {
   $comments[$idx]['add_time']       = $comments[$idx]['add_time']       = local_date
($GLOBALS['_CFG']['time_format'], $row['add_time']);
   $comments[$idx]['user_name']       = $row['user_name'];
   $comments[$idx]['content']       = $row['content'];
   $comments[$idx]['id_value']       = $row['id_value'];
  }
  return $comments;
}
}
$this-&gt;assign('new_comments',get_new_comments(10)); // 10条最新评论
?&gt;
 
<!--数据调用-最新评论开始 -->
 
 
<div class="comments">
<!--{foreach from=$new_comments item=comment}-->
 
<div class="t_l f_l"><a href="goods.php?id={$comment.id_value}" target="_blank">
    {$comment.content|truncate:15:""}</a></div>
 
 
<div class="d_r f_r">时间:{$comment.add_time}</div>
 
 
<!--{/foreach}--></div>

这样一个库文件就做好了。我们发现,库文件可以使用PHP、Smarty标签和HTML混写的写法,这无疑大大降低了我们二次开发的难度。这个库文件的作用,就是取出数据库中的10条最新评论,并且循环输出到模板。
接下来我们就要在模板中调用这个库文件,我们打开首页的模板themes\default\index.dwt(这里的dwt文件和页面都是一一对应,如商品页是goods.dwt,品牌页是brand.dwt)。我们在首页模板适当的地方加入:

<!-- #BeginLibraryItem "/library/newest_comments.lbi" --> @@@这里即使修改了也没反应@@@<!-- #EndLibraryItem -->

这样就实现了自定义库文件的编写和调用。
细心的你可能会发现,在Ecshop的dwt模板文件中,调用库文件标签<– #BeginLibraryItem “/library/和<– #EndLibraryItem –>之间存在着一些和对应的lbi库文件中重复的内容。你可以把它们当作是注释,用Dreamweaver等编辑器编辑模板时候的需要看见的注释。真正的库文件内容还需要到相应的库文件中去修改。好吧 ,就是这些,简单吧。

[Ecshop]让Smarty不用再写assign

2010年7月14日

由于工作缘故,开始研究起Ecshop的二次开发。从今天起会陆续写几篇关于Ecshop二次开发的文章和大家分享。

Ecshop使用的是Smarty模板引擎——一种被很多人称为“过时”的模板引擎技术(当然,我也是这么认为的)。Smarty作为一种模板引擎技术,使用它的目的是为了实现MVC。而仅仅通过模板技术实现的MVC,还不能成为真正意义上的MVC。Smarty现在也不是PHP官方推荐的模板技术了。不过在官方的框架Zend Framework中,还是保留了对Smarty的支持。

Ecshop的源码中使用了自己精简的Smarty,功能上基本能满足需求。Ecshop仍然是采用面向过程的编码方式,和Discuz类似。而Discuz的中使用模板变量前是不用assign的。那么在Ecshop中像下面的代码我们能不能也省了呢

    $smarty->assign('image_width',  $_CFG['image_width']);
    $smarty->assign('image_height', $_CFG['image_height']);
    $smarty->assign('helps',        get_shop_help()); // 网店帮助
    $smarty->assign('id',           $goods_id);
    $smarty->assign('type',         0);
    $smarty->assign('cfg',          $_CFG);
    $smarty->assign('promotion',       get_promotion_info($goods_id));//促销信息
    $smarty->assign('promotion_info', get_promotion_info());

当然是可以的。我们在Smarty类中的display方法开头加入以下这几句

unset($GLOBALS['_ENV'],$GLOBALS['HTTP_ENV_VARS'],$GLOBALS['HTTP_SERVER_VARS'],$GLOBALS['HTTP_POST_VARS'],$GLOBALS['HTTP_GET_VARS'],$GLOBALS['HTTP_COOKIE_VARS'],$GLOBALS['HTTP_POST_FILES'],$GLOBALS['HTTP_COOKIE_VARS']); //先清空这几个全局变量
$this->assign($GLOBALS); //把全局变量数组"注入模板"

这样,以后在二次开发中我们就不用写那么多繁琐的$smart->assign 了

Thinkphp2.0中关联模型中的一个bug

2010年5月10日

今天天气真好啊。。
在最近一个新项目的开发中,我采用了thinkphp作为开发框架,需求是围绕discuz和uchome进行的二次开发。
我在Conf/config.php配置文件中设置的默认表前缀是”a_”,但由于需求需要,我需要从论坛的”cdb_activitities” 和”cdb_threads”两张表中进行关联查询。按照tp开发手册上所说,我自定义了一下两张表模型的表前缀,并加上关联规则:

/**
 * Activities模型
 *
 * @author xhowhy
 */
class ActivitiesModel extends RelationModel {
    //put your code here
    protected $tablePrefix = 'cdb_';
    public $_link = array(
	'threads'=>array(
	    'mapping_type'=>HAS_ONE,
	    'class_name'     =>'threads',
	    'foreign_key'=>'tid'
       )
    );
}
/**
 * Description of ThreadsModelclass
 *
 * @author Administrator
 */
class ThreadsModel extends RelationModel {
    //put your code here
    protected $tablePrefix = 'cdb_';
}

可关联后进行查询,发现class_name返回的是bool(false)。我很奇怪,就算是没有被关联的数据,那返回的也应该是null,怎么会是bool(false)。后来经过查看tp源码中的Thinkphp/Lib/Think/Core/Model/RelationModel.class.php文件,终于在170行找到原因:

                        // 获取关联模型对象
                        $model = M($mappingClass);

可以看出,在生成关联模型对象的时候,源码中采用了M函数,而在Thinkphp/common/function.php中定义的M函数的作用是“M函数用于实例化一个没有模型文件的Model”。很显然,根据上下文,这里应该使用D函数。因为就算是关联模型,也不一定没有模型文件,同样有可能是自定义表前缀、甚至是跨库操作。
所以代码应该改成:

                        // 获取关联模型对象
                        $model = D($mappingClass);

问题成功解决了。

跟踪客户需求变更和虚惊一场

2010年4月10日

本来题目里说的是两件事的,合在一篇里写,足以见证这一周的tough。

先说第二件事,其实和第一件也有关系。根据客户的需求,修改了index.html页面。本应该上传到/templates下,却一不小心上传到网站根目录/,把客户原本的flash首页给覆盖了。这下心想over了,俊哥那应该没有存底的,只好自己做一个吧,embed标签还是会的。

刚用编辑器建立一个新的html文件,要先写上keywords和description,就用google百度了一下客户的网站,看到了一句短短的描述。下意识的点了链接,发现点开的竟然不是index.php而是index.html的flash首页!Oh~搜狗浏览器威武~看来是搜狗把页面和域名都缓存了缘故。查看源代码,ctrl+c,Ctrl+v,Ctrl+s。。

(我们也可以通过查看网页快照找回从前的页面源代码,前提是你的站点被收录了)

吸取教训:1、以后静态首页扩展名用.htm,模板文件用.html或者.php,避免覆盖;2、或者首页模板文件不叫index.html而叫做default、Homepage;3、不管怎样,以后上传文件多加小心,合理给文件命名,最大程度避免杯具的再次发生。

说第一件事。

去年做的单子,客户是自动控制行业的一家公司。最近,原本负责他们公司网络的网管员(也是负责他们公司网站建设的人)走人了。新上任的这一位网管貌似有点想法,一直在联系我,前后提出了一些更改方案,主要的修改是把产品分类从三级增加到5级,我按他的要求做了。完了之后,说还是不完善,又提出让我过去当面沟通。

见面之后,我很诧异他竟然是个男的,电话里我一直以为是个御姐~o(∩_∩)o 哈哈。交流过程中,我发现他们走人的那位似乎工作交接没有做到位,这位新的网管简直是完全不知所以然,连个后台地址用的都是测试时候用的虚拟主机四级域名(年前就弃用了)。。

我向他展示了新后台的使用方法后,他倒是气定神闲提出了很多前台页面的修改需求,还不忘讽刺他那位前同事接手的这件事(网站建设)没做好。我当即表示,一开始需求就是你们客户提出的,另外我们对你们行业也不甚了解,自然要按照你们客户的需求来做。现在你觉得没有满足你的需求,那也正是说明当初你根本没弄清楚自己的需求,责任不在于我们开发人员。可以说,现在的情况是你们需求产生变动,不是我们当初没把你们需求做好。

他哪里知道,他提出的几点修改需求,足以让我让我和俊哥把产品分类推到重做,况且我之前花在产品分类和其他一些地方上的修改已经很多了。

我和俊哥说了,他说没事,需求变动大就要他们加钱。谁知过两天之后,客户又打来电话说,我给你个网站地址,你就照着上面的来做。。看了看  说行

前后两天的努力,总算按客户的意思做好了,前台页改得面目全非,动用到了数据表结构变更,url更是重新设计,分类列表的递归输出。。不过还好,最后终于上线了。

这次的事让我体会到跟踪客户需求变更的重要性,因为有时候,用户只是简单的一句话,对于项目的调整来说工作量是非常巨大的。。在项目的初期,我们应该先调研,帮客户弄清需求,并考虑将来可能遇到的需求变动;在适当的时候,我们可以让客户知道需求变更的代价。

通过这个项目,也开始有点体会到“随着 Web 应用程序变得越来越复杂,简单的 CRUD 操作已经无法满足对于复杂应用程序的开发要求。”接下来,要好好学习一下软件工程,并把面向对象的程序设计和实现运用到新项目当中去。。

回顾一下曾经开发过的 PHP 应用,大部分开发者都会发现这些应用中,数据的创建、读取、更新和删除操作是重复最多次的操作。但是不管我们如何简化这些 CRUD(创建、读取、更新、删除)操作,面对客户不断变化的需求,应用程序的内在结构总是逐渐变得凌乱。

而造成这种情况的根本原因就是我们没有正确使用面向对象的技术来设计和实现这些应用程序。由于业务逻辑固有的复杂性被所谓的 CRUD 快速开发能力所掩盖。本应是内聚的业务逻辑却拆散为一个个 CRUD 操作,分散到了应用程序的不同部分。如此一来,业务逻辑的变化必然会导致应用程序内部产生连锁反应式的改动,应用程序内在结构的逐渐腐朽变成了不可逆转的趋势。

不知道会不会怪我。。

2010年4月4日

这一周竟然一篇日志都没写。。懈怠了懈怠了。唉

昨晚用Thinkphp搞了个后台,剽窃了 pbdigg的模板 ,不知道会不会怪我 ,唉

Thinkphp设计得很好,很多地方都替开发者考虑得周全了。。比如第二张图中的?m=admin上两张图:

他会提示:

系统发生错误

您可以选择 [ 重试 ] [ 返回 ] 或者 [ 回到首页 ]

[ 错误信息 ]

非法操作admin

[ TRACE ]

[10-04-04 11:38:04] () Action->__call(admin, Array)
[10-04-04 11:38:04] () LogAction->admin()
[10-04-04 11:38:04] F:\wwwroot\libra\lib\ThinkPHP\Lib\Think\Core\App.class.php (415) call_user_func(Array)
[10-04-04 11:38:04] F:\wwwroot\libra\lib\ThinkPHP\Lib\Think\Core\App.class.php (439) App::exec()
[10-04-04 11:38:04] F:\wwwroot\libra\admincp.php (11) App::run()

真好。。

让PHP支持像jQuery那样的链式操作

2010年3月19日

群里有人讨论这个问题,我的想法是在方法里返回this指针(指向对象本身),通过对私有成员$sql的赋值,来获得最终的执行语句,并返回相应结果。

Class Database{
    private $sql;
...
    public function where($condition){
        //此处省略n行...
        return this;
    }
    public function limit($limit){
        //此处省略n行...
        return this;
    }
    public function execute(){
        return this->query($sql);
    }
    public function select(){
        //此处省略n行...
        return this;
    }
}
//调用时:
$DB = new Database();
$DB->where($condition)->limit(20)->select();

用Session实现在线聊天室

2010年2月27日

Longbill同学那里看到的,这方法用得很巧,但可能不适用于国内某些虚拟主机环境下。

原理就是在session_start()之前,调用session_id(“xxxx”),手工指定session_id的值,这样以来,发送到客户端的PHPSESSID的cookie的值就一样了,这样每个客户端的session_id被手工指定为同一个。

Xxtea加密算法的PHP实现 – 支持数组加密解密

2010年2月17日

——XXTEA 算法很安全,而且非常快速,非常适合应用于 Web 开发中。
今天在国内的一个前辈博客当中看到XXTEA的各种实现方法,有python/ruby/javascript等。原文参见http://www.coolcode.org/?action=show&id=128 而我打算用这个算法来加密一些Cookie数据,因此进行了小部分修改,增加了加密数组的功能(原理很简单 大家看代码),代码如下:

/**
 * @author      Ma Bingyao <andot@ujn.edu.cn>
 * @copyright   Ma Bingyao
 * @package     XXTEA encryption arithmetic library
 * @version     1.5
 * @lastupdate   2006-12-17
 * @link        http://www.coolcode.cn/?p=128
 */
Class Xxtea {
 
	public function encrypt($s,$key){
                is_array($s) && $s = serialize($s);
		return str_replace(array('+','/','='), array('-','_','.'), base64_encode($this->xxtea_encrypt($s, $key)));
	}
 
	public function decrypt($e,$key,$isArray=false){                
		$c = str_replace(array('-','_','.'), array('+','/','='), $e);
                if($isArray === false){
                    return $this->xxtea_decrypt(base64_decode($c), $key);
                }else{
                    return unserialize($this->xxtea_decrypt(base64_decode($c), $key));
                }
 
	}
 
	private function long2str($v, $w) {
		$len = count($v);
		$n = ($len - 1) << 2;
		if ($w) {
			$m = $v[$len - 1];
			if (($m < $n - 3) || ($m > $n)) return false;
			$n = $m;
		}
		$s = array();
		for ($i = 0; $i < $len; $i++) {
			$s[$i] = pack("V", $v[$i]);
		}
		if ($w) {
			return substr(join('', $s), 0, $n);
		} else {
			return join('', $s);
		}
	}
	private function str2long($s, $w) {
		$v = unpack("V*", $s. str_repeat("\0", (4 - strlen($s) % 4) & 3));
		$v = array_values($v);
		if ($w) {
			$v[count($v)] = strlen($s);
		}
		return $v;
	}
	private function int32($n) {
		while ($n >= 2147483648) $n -= 4294967296;
		while ($n <= -2147483649) $n += 4294967296;
		return (int)$n;
	}
	private function xxtea_encrypt($str, $key) {
		if ($str == "") {
			return "";
		}
		$v = $this->str2long($str, true);
		$k = $this->str2long($key, false);
		if (count($k) < 4) {
			for ($i = count($k); $i < 4; $i++) {
				$k[$i] = 0;
			}
		}
		$n = count($v) - 1;
		$z = $v[$n];
		$y = $v[0];
		$delta = 0x9E3779B9;
		$q = floor(6 + 52 / ($n + 1));
		$sum = 0;
 
		while (0 < $q--) {
			$sum = $this->int32($sum + $delta);
			$e = $sum >> 2 & 3;
			for ($p = 0; $p < $n; $p++) {
				$y = $v[$p + 1];
				$mx = $this->int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ $this->int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
				$z = $v[$p] = $this->int32($v[$p] + $mx);
			}
			$y = $v[0];
			$mx = $this->int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ $this->int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
			$z = $v[$n] = $this->int32($v[$n] + $mx);
		}
		return $this->long2str($v, false);
	}
	private function xxtea_decrypt($str, $key) {
		if ($str == "") {
			return "";
		}
		$v = $this->str2long($str, false);
		$k = $this->str2long($key, false);
		if (count($k) < 4) {
			for ($i = count($k); $i < 4; $i++) {
				$k[$i] = 0;
			}
		}
		$n = count($v) - 1;
 
		$z = $v[$n];
		$y = $v[0];
		$delta = 0x9E3779B9;
		$q = floor(6 + 52 / ($n + 1));
		$sum = $this->int32($q * $delta);
 
		while ($sum != 0) {
			$e = $sum >> 2 & 3;
			for ($p = $n; $p > 0; $p--) {
				$z = $v[$p - 1];
				$mx = $this->int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ $this->int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
				$y = $v[$p] = $this->int32($v[$p] - $mx);
			}
			$z = $v[$n];
			$mx = $this->int32((($z >> 5 & 0x07ffffff) ^ $y << 2) + (($y >> 3 & 0x1fffffff) ^ $z << 4)) ^ $this->int32(($sum ^ $y) + ($k[$p & 3 ^ $e] ^ $z));
			$y = $v[0] = $this->int32($v[0] - $mx);
			$sum = $this->int32($sum - $delta);
		}
		return $this->long2str($v, true);
	}
}

PHP常用缓存技术详解(二)

2010年2月15日

继续说缓存,从缓存对象来看,第一种缓存技术缓存了页面片段,难免造成缓存的对象比较臃肿,于是便有人把程序中的变量(也许经过序列化),通过fwrite()或者file_put_contents()写入到文件中,同样通过设定过期时间来确保缓存的有效性,此外还引人了主动缓存和被动缓存的概念(如discuz所见)。此方法避免使用ob系列函数,提升了部分效率,特别是在虚拟主机下。其实现方法如下,转自一国产框架:

/**
 * Punny - The most easy-to-use PHP MVC framework
 * http://punny.skiyo.cn/
 *
 * Copyright (c) 2009 Jessica(董立强)
 * Licensed under the MIT license.
 *
 * @author Jessica<cndingo@qq.com>
 * @version $Id: FileCache.class.php 267 2010-01-19 07:21:08Z jessica.dlq $
 */
class FileCache {
 
	/**
	 * 缓存目录
	 *
	 * @var string
	 * @access private
	 */
	private $dir;
	/**
	 * 构造器
	 *
	 * @access public
	 */
	public function __construct() {
		$this->dir = 'cache/'; //缓存目录
		@chmod($this->dir, 0777);
		if(!is_writable($this->dir)) {
			throw new Exception('缓存文件夹' . $this->dir . '不可写!');
		}
	}
 
	/**
	 * 设置一个缓存变量
	 *
	 * @param String $key    缓存Key
	 * @param mixed $value   缓存内容
	 * @param int $expire    缓存时间(秒)
	 * @return boolean       是否缓存成功
	 * @access public
	 * @abstract
	 */
    public function set($key, $value, $expire = 60) {
		$file = $this->dir . md5($key) . '.cache';
		if(file_put_contents($file , serialize($value), LOCK_EX)) {
			@touch($file, time() + $expire);
			return true;
		} else {
			return false;
		}
 
	}
 
	/**
	 * 获取一个已经缓存的变量
	 *
	 * @param String $key  缓存Key
	 * @return mixed       缓存内容
	 * @access public
	 */
	public function get($key) {
		$file = $this->dir . md5($key) . '.cache';
		if(is_file($file)) {
			if(time() <= filemtime($file)) {
				return unserialize(file_get_contents($file));
			} else {
				@unlink($file);
				//删除缓存
				return false;
			}
		} else {
			//没有找到缓存
			return false;
		}
	}
 
	/**
	 * 删除一个已经缓存的变量
	 *
	 * @param  $key
	 * @return boolean       是否删除成功
	 * @access public
	 */
	public function del($key) {
		$file = $this->dir . md5($key) . '.cache';
		return @unlink($file);
	}
 
	/**
	 * 删除全部缓存变量
	 *
	 * @return boolean       是否删除成功
	 * @access public
	 */
	public function delAll() {
		$files = scandir($this->dir);
		$files = array_diff($files, array('.', '..'));
		foreach($files as $file) {
			@unlink($file);
		}
		return true;
	}
 
	/**
	 * 检测是否存在对应的缓存
	 *
	 * @param string $key   缓存Key
	 * @return boolean      是否存在key
	 * @access public
	 */
	public function has($key) {
		return (is_file($this->dir . md5($key) . '.cache') === NULL ? false : true);
	}
}