洛阳铲的日志

2010年11月8日

国外PHP开源论坛选型

Filed under: PHP — 标签:, , , , — 洛阳铲 @ 10:17

首先本文只适合于有PHP经验,准备或者有能力做二次开发的使用者,如果仅仅是简简单单的需要一个论坛,那么目前国内耳熟能详的DZ、PHPWIND足矣!

Name 备选? 备注
PHPBB V3.0.7 No 参见用户系统集成分析一文,铁定不是备选方案
bbPress No 论坛版的Wordpress,WP的同胞兄弟,看过wordpress代码的同学知道,这是个什么level,ugly very much!

SMF

No 太简单,无法扩展(也许可以改名为SiMple Fourm),只能hack

MyBB

YES 提供merge系统,以便从其他平台迁移。而且其无处不在的hook系统此处有更多的hook介绍),方便进行扩展开发。同时提供一些有趣的特征:日程安排基于用户的post和行为reputation机制会员自动升级机制(基于post、reputation或者注册时间)、mass mail、警告机制维护任务系统

SEO-Board

No 仅仅从安装配置过程来看,便是geek级别的论坛程序,扩展性无从考察

PunBB

无架构级别的hook,但提供扩展机制,并且官方提供很多扩展

FluxBB

中规中矩、基本的论坛功能都有,也提供有限的插件扩展机制

Vanilla

YES 基于MVC的框架(Garden),甚至Vanilla也只是此框架之下的一个APP,适合于技术强悍的团队使用,但是微薄化论坛UI,有些让人难以适应

Phorum

No 中规中矩、基本的论坛功能都有

IceBB

No 只能算个基本的论坛,无亮点可言,无开发资源

UseBB

No 只能算个基本的论坛,无亮点可言,无开发资源

XMB

No 轻型论坛,开发资源相当有限

上面这个表格展示了目前国外主流的开源php论坛程序,如果选择的话,无疑MyBB和Vanilla是最优选择。MyBB属于拿来即可用而且进来也有极大的扩展空间,而Vanilla属于拿来即可以开发,将来更有无限的扩展可能,但是技术要求要高很多。对于普通的团队而言,门槛低的MyBB也许是更好的选择

外部参考:

  1. MyBB Svn仓库地址:  http://svn.mybboard.net/mybb/branches/1.6-stable/
  2. MyBB中文社区:  http://www.mybbchina.net/
  3. Vanilla Git地址:  https://github.com/vanillaforums/Garden.git

2010年11月7日

用户系统集成分析

Filed under: PHP — 标签:, , , , — 洛阳铲 @ 22:30

系统不能是封闭的,否则死水一潭,还是诗里说的好,问渠那得清如许?唯有活水源头来!
先说说这个事情需要达成的目标:

尽可能少的修改核心代码的情况下实现开源论坛(以phpBB和phpWind来说)和其他第三方系统的
用户集成、发帖集成和等级积分集成

首先明确一下在上面提到的几个目标:

  1. 和哪些第三方系统集成?
    目前常用的第三方系统无非这几类: 网店、wiki、CMS、
    希望一个一个去集成他们,那绝对是痴人梦话。必须需找一个中立的系统。
    说道这个,康盛创想开了个好头,Ucenter的出现正好满足了这个目标。
    目前支持Ucenter的很多,ecshop、UCHome、DZ系、phpWind等等系统都对Ucneter提供支持。
    所以UCenter列为研究重点(没有知道UCenter是否开源,只说UCHome开源,不知道这在版权方面有没有什么隐患,知道的兄弟拍一下砖)。
    所以分别在这几类系统里面挑选一个系统来研究分析。
    网店系统当然就选ecshop、另外两类就选UCHome。 一来它们都开源,二来这两个系统为康盛系,最能代表康盛创想的官方思路。ecshop是康盛创想后来收购的,非康盛嫡系,但受康盛官方的Ucenter支持,这里面必然揉杂了ecshop本来的独立性和康盛的官方思路,和准备集成的系统是一个路子,正好可以搭车求思路。
  2. 集成哪些功能? 用户集成应该是需求量最大和紧迫性最高的一个,其余的则要次之。
  3. 尽量不要hack核心代码。如果官方版本升级之后怎么办? 打patch? 那绝对是个恶梦;从头开始再把项目进行一遍?恶梦2。所以不好hack核心代码,转而使用官方支持的技术手段,比如phpbb的hook系统等等。

 

ecshop用户集成分析

使用integrate抽象会员处理类,根据不同的系统使用不同的实现来完成集成,
include_once(ROOT_PATH . ‘includes/modules/integrates/’ . $GLOBALS[‘_CFG’][‘integrate_code’] . ‘.php’);
对于Ucenter,使用includes/modules/integrates/ucenter.php中的ucenter类, 来完成ecshop和ucenter双向注册。
具体流程为:
在ecshop的register.php调用lib_passport.php的register()来完成注册,register()是使用$GLOBALS[‘user’]->add_user()
来注册用户,$GLOBALS[‘user’]变量代理了所有的用户相关的操作,$GLOBALS[‘user’]是调用来自lib_common.php的&init_users()函数来实例化的用户对象,
$user =& init_users();
之后的所有用户操作都基于此对象。下面是具体实现:
//根据配置,加载integrate接口的实现文件,并初始化该接口类

  function &init_users() { 
    $set_modules = false; 
    static $cls = null; 
    if ($cls != null){ return $cls; } 
    include_once(ROOT_PATH . 'includes/modules/integrates/' . $GLOBALS['_CFG']['integrate_code'] . '.php'); 
    $cfg = unserialize($GLOBALS['_CFG']['integrate_config']); 
    $cls = new $GLOBALS['_CFG']['integrate_code']($cfg); return $cls; 
} 

使用的时候,直接使用$GLOBALS[‘user’]->user_method(), 进行调用。

比如 includes/lib_passport.php中用户注册可以用下面来完成:

$GLOBALS[‘user’]->add_user($username, $password, $email)

对于使用ucenter的ecshop,则使用includes/modules/integrates/ucenter.php来实现真正的注册。

在includes/modules/integrates/ucenter.php中:

$uid = uc_call("uc_user_register", array($username, $password, $email));

之后再做一次insert操作把用户信息也插入ecshop自己的用户表,完成ecshop和UCenter双向注册。

在这儿有两个设计思路:

  1. 使用modules/integrates/’ . $GLOBALS[‘_CFG’][‘integrate_code’] . ‘.php’ 来抽象了用户集成管理
  2. 在UCenter和ecshop中使用双用户表

Ucenter Home

对于Ucenter Home而言。直接使用uc_client库中的:uc_user_register进行用户注册。

所有用户信息保存在UCenter中。

因为uc_client对需要和ucenter集成的地方都很重要,所以做个简单分析:

前面提到的uc_user_register()函数位于uc_client/client.php中。

在22行,有一个定义:

define('UC_API_FUNC', UC_CONNECT == 'mysql' ? 'uc_api_mysql' : 'uc_api_post'); 

这就是Ucenter 接口开发手册中提到的两种调用方式。真正注册是下面的调用:

function uc_user_register($username, $password, $email, $questionid = '', 
$answer = '') { return call_user_func(UC_API_FUNC, 'user', 'register', 
array('username'=>$username, 'password'=>$password, 'email'=>$email, 
'questionid'=>$questionid, 'answer'=>$answer)); }

换句话说如果UC_CONNECT是’mysql’的时候,通过用uc_api_mysql操作ucenter的用户数据表完成用户注册,

否则使用uc_api_post来向Ucenter发送远程调用来完成注册。

具体的数据库注册细节或者post XML请求注册细节可以参考uc_client/client.php

phpBB集成思路

完美的方法是使用官方支持的方法。对于phpBB, 官方支持的其实就是hook系统。

据说老版本phpBB而言是有挂钩的的,但是对于最新的发行版3.0.7 PL1,hook系统提供有限的支持:

line 225 in common.php:

$phpbb_hook = new phpbb_hook(array('exit_handler', 'phpbb_user_session_handler', 'append_sid', array('template', 'display')));

即只提供exit_handler,phpbb_user_session_handler,append_sid,templates,display的hook。

对于用户注册,其实现位于 includes/ucp/ucp_register.php 的328行:

$user_id = user_add($user_row, $cp_data);

user_add是includes/functions_user.php的函数。无挂钩操作!

遗憾的是在用户注册这条路上,看到ucp_register::main中直接使用user_add来往数据库写注册用户的那一刻,偶心都凉了。

老实说真想去改user_add来增加钩子;但是如果以后钩子系统做改动或者phpBB出来3.4或者4.0。这些工作又要重新来过。

此条路不通!

说到这儿随便bs一下phpbb的代码,看着满屏的SQL语句,没有OO的意思,时不时就会冒出扔掉的冲动。


既然UCenter作为中心,那么何不利用Ucenter和外部系统进行通讯,实现系统间用户注册同步呢?

即关闭phpbb的用户注册功能,转而使用UCenter的用户注册模块,并且通过UCenter调用自行开发的PHPBB API进行用户注册。

 

PHPwind7.5 SP3用户注册分析

register.php第203行,通过L::loadClass(‘Register’)加载注册类register.class.php。使用PW_Register::execute进行注册操作。

在PW_Register::execute()中,使用uc_client/uc_client.php中的uc_user_register()来抽象用户注册(这个uc_user_register意外的和康盛创想的uc_client保持了目录结构和函数名称及调用格式惊人相似,这种相似一度迷惑我以为phpwind直接使用UC的客户端),uc_user_register 使用uc_data_request( ‘user’, ‘register’)来加载: uc_client/class_core.php实现MVC方式的进行注册,这后面的动作和UC非常类似, 只是写表操作(uc_client/model/user.php的add函数)发生在PHPWind自己的用户表上面。

uc_data_request中UC()->control(‘controle’)加载控制器控制器自己在构造函数中使用UC()->load( ‘model’ )加载需要使用的model。

即PHPWind对用户注册有抽象层,即可通过此处实现用户注册。

开发PHPWind的API的时候,发现用UC的Client库的时候到处出现已经声明过的函数错误,诸如:us_user_**系列 函数,好在php5.3之后支持命名空间,很容易的解决这个问题之后, 发现很多地方仍然有问题,深入debug发现,n多的常量居然也是同名,典型的莫过于UC_APPID 实在无语,PW_APPID也好过UC_APPID。 看来PHPWind的工程师要么是系统地学习了UC的代码,要么就是两家公司的开发人员浑然天成的心有灵犀!

因为上面这些分析的结果都是以UCenter为中心展开,所以有必要对UCenter做深入的分析。

UCenter的分析

UCenter的入口文件是index.php。在index.php中,根据$_REQUEST[‘release’]选择release/UC_CLIENT_RELEASE/版本。

同时根据传入的模块参数$m,选择app、frame、user、mail等等不同的模块。

如果没有则使用默认的版本:

if(empty($release)) { 
  define('RELEASE_ROOT', "release/20080429/"); 
} elseif(  intval($release) != UC_SERVER_RELEASE) { 
  define('RELEASE_ROOT', "release/$release/"); 
} else { 
  define('RELEASE_ROOT', ''); 
}
  
if(file_exists(UC_ROOT.RELEASE_ROOT.'model/base.php')) { 
  require UC_ROOT.RELEASE_ROOT.'model/base.php'; 
} else { 
  require UC_ROOT.'model/base.php'; 
}
if(in_array($m, array('app', 'frame', 'user', 'pm', 'pm_client', 'tag', 'feed', 'friend', 'domain', 'credit', 'mail', 'version'))) {

if(file_exists(UC_ROOT.RELEASE_ROOT."control/$m.php")) { 
  include UC_ROOT.RELEASE_ROOT."control/$m.php"; 
  } else { 
  include UC_ROOT."control/$m.php"; 
  }
  $classname = $m.'control'; 
  $control = new $classname(); 
  $method = 'on'.$a; 
if(method_exists($control, $method) && $a{0} != '_') { 
  $data = $control->$method(); 
  echo is_array($data) ? $control->serialize($data, 1) : $data; 
  exit; 
}elseif(method_exists($control, '_call')) {
  $data = $control->_call('on'.$a, ''); 
  echo is_array($data) ? $control->serialize($data, 1) : $data; 
  exit; 
} else { 
  exit('Action not found!'); 
  }}

使用MVC模式,根据m参数加载控制器$m.php。$m.’control’调用其父类base::load方法加载模型。

function load($model, $base = NULL, $release = '') { 
  $base = $base ? $base : $this; 
  if(empty($_ENV[$model])) { 
  $release = !$release ? RELEASE_ROOT : $release; 
  if(file_exists(UC_ROOT.$release."model/$model.php")) { 
  require_once UC_ROOT.$release."model/$model.php"; 
  } else {
  require_once UC_ROOT."model/$model.php"; 
  } 
  eval('$_ENV[$model] = new '.$model.'model($base);'); 
  } 
  return $_ENV[$model]; 

不得不说UCenter的这个设计思路是相当灵巧的:

  1. 客户端可以指定Ucenter兼容的版本,服务器如果无法提供,也会有一个默认的版本: model/base.php 这个是UCenter服务端的默认版本
  2. 让我们扩展UCenter成为了可能,比如现在最新的UCHome的uc client是使用’20090121’版本(define(‘UC_CLIENT_RELEASE’, ‘20090121’);),

    而ecshop中的UCclient是使用’20081212’版本( define(‘UC_CLIENT_RELEASE’, ‘20081212’); )

    而服务器只有两个版本一个是主版本,另外一个是位于release下面的20080429。 既没有UCHome的20090121也没有ecshop的20081212.

    所以其实使用的版本是主版本model和control两个目录中的代码。 换句话说,如果我们提供一个自己的20081212或者20090121 release,

    那么我们就扩展了现在的UCenter。

  3. control+model典型的MVC模式。在API通讯中V可以忽略不计。更好的消息是在基础控制器中负责加载模型的load()函数,它也工具release

    选择不同的模型,这就是说: 不仅可以扩展控制器、还可以扩展模型。当然对于集成这个任务而言简单的扩展模型就可以实现目标

如何整合?

对于PHPBB,整合系统代码功能分析:

  1. 给phpBB增加API,将phpBB功能以服务的形式提供出来。最主要的是暴露给提供UCenter中自定义的模型使用,

    之所以选择API,而不是直接操作数据库,是因为在以后的pbpBB升级过程中可以更加平滑。同时不仅仅可以给UCenter使用,也可以给别的第三方使用
  2. 关闭phpBB的注册功能

另外,深入的分析和试用,发现phpBB不大适合国情,因为:

  1. UI需要修改,默认的字体在e文先很漂亮,但不适用于咱们的方块字。
  2. SEO不原生支持,虽然可以通过插件支持,但是一旦升级,seo的插件不一定赶得上,则意味着之前的url统统都白搭
  3. 代码开源,但是架构封闭,就拿用户注册那一部分的分析就可以窥豹一斑。

鉴于此,增加phpWind作为一号候补,不选择Dz,完全是因为看不到代码,没有真相,而且phpWind提供类似UCHome的个人中心也十分容易和其他系统集成,而且本身提供API(只是没有正式公布)而ecshop支持和phpwind集成,这样一来论坛、个人中心和网店可以无缝集成。由于此时 phpWind8还是beta版本,而且只有gbk没有utf-8版本,放弃了初期以php8作为分析整合对象的目标,转而还是以php7.5 SP3为目标。 不过因为和UCHome重复的原因,这儿需要做些选择到底是:phpWind还是UCHome?

在可以预计的将来,独立的第三方应用会越来越多,所以还是以平台中立为重点,基于此,倾向于UCHome,而phpWind只是UC中的论坛app。

在新版本的DZ X中,似乎削弱了这种UC平台的中心地位,同时把很多UCHome的功能嵌入到了论坛中去,这似乎是在向PW学习。

老实讲,我并不看重这种思路,原因很简单:KISS原则,做最简单的自己, 做最专业的自己。DZ也好PW也好最实用的还是论坛功能,其他

功能圈子、群组之类的并不是你的特长,也不需要你很特长,如果把自己包装成面面俱到的多面手,到头来就会变成个四不象:什么功能都有,

但是什么功能都不专业。反而提供一个通用的接口或者平台,与其他系统完美的交互,构造自己在网络平台的中心位置,这才是重中之重!

也许看起来功能很简单、单一,其实在周围已经繁衍出无数的外围产品,

2007年11月22日

Apache乱码问题

Filed under: Apache,PHP — HackGou @ 00:09

很多时候,浏览器并不按照指定的编码来显示页面。 导致乱码。其实这和http头中的设置有很大关系:
Content-Type: text/html; charset=utf-8
当http头中有这一行的时候,浏览器总是按照这个头指示的编码来显示页面内容,
而忽略Meta tag中的设置。如果Meta和Content-Type 的charset一致时,一切都是正常的,
而一旦不一样,那就会出现问题。
而apache本身是可以通过AddDefaultCarset XXX来来设置 http头中的默认字符编码,
同时当apache和php在一起的时候,还有php.ini的一个设置会影响这个http头的默认编码:
default_charset = “utf-8”
而这个default_charset 的设置会覆盖apache的AddDefaultCarset配置,
当然也可以在每个php里面手动调用
header(“content-type:text/html; charset=xxx”)
来覆盖default_charset 的值,这么看来一共有四个地方 会对php的执行结果产生影响: 优先级别从高到低:
php的header(“content-type:text/html; charset=xxx”)函数
php.ini里面的default_charset 设置
httpd.conf 中的AddDefaultCarset设置
最后才是html代码中的:META tag
其实最简单的办法是把php.ini中的default_charset和httpd.conf中的AddDefaultCarset置空。 通过Meta tag来指定编码,header只是临时性的改变编码的最后关口。

2007年01月20日

p spell和recode居然会让php5挂掉?

Filed under: Apache,FreeBSD,PHP,port,server — blog @ 12:54

从port安装的php5和extension中,pspell和recode两个扩展居然会导致php 挂掉,
有些不可思意,也许是 这两个扩展需要额外的lib没有安装,引发经典的库依赖问题??
;extension=pspell.so
;extension=recode.so
还好,这两个扩展根本用不到,注释之
不能从根本上解决问题,那让问题根本不会出现,未尝不是一个根本上解决问题的法子 :P

2006年10月13日

Seagull中的makeUrl解析

Filed under: PHP,Seagull — HackGou @ 01:39

makeUrl原型位于Output:

makeUrl($action = ”, $mgr = ”, $mod = ”, $aList = array(), $params = ”, $idx = 0)

makeUrl由Output调用别名函数

SGL_Url::makeLink SGL_Url::makeLink($action, $mgr, $mod, $aList, $params, $idx, $this)
实现.
 而SGL_Url::makeLink则是从$conf[‘site’][‘outputUrlHandler’]的实例中调用
makeLink(tion, $mgr, $mod, $aList, $params, $idx, $output)完成真正的url生成。
默认情况下$conf[‘site’][‘outputUrlHandler’]的值为SGL_UrlParser_SefStrategy。
它是从SGL_UrlParserStrategy中继承而来。解析makeLink即可解释默认的makeUrl所发生的动作,
当然也可以自己继承SGL_UrlParserStrategy,来实现自己独特风格的makeUrl函数。
makeLink($action, $mgr, $mod, $aList, $params, $idx) $action、$action, $mgr, $mod,从名字就可以看出来。 关键是后面$aList, $params, $idx三个参数。 makeLinnk首先将$params中的参数使用’||’拆分成’参数名|值’对的形式,比如#image_id|imageId||a|2||b|[a,1,2,3-4,,4] 将拆分成 image_id|iamgeId和 a|2和 b|[1,2,3-4,4] 两个’参数名|值’对。
然后分别将这些值对使用’|’拆成$qsParamName和$listKey循环带入$aList的每个元素进行如下检查:

if ($aList不为空且$aList最后一个单元为数组
    或者$aList是数组且最后一个单元为对象
    或者$aList单元数目不为零。
    或者$aList等于0 )
    { if( $aList[$idx][$listKey]存在且$listKey不为空){
             $qsParamValue = $aList[$idx][$listKey]
    }elseif( $aList[$idx]存在且$listKey为空 ) { 
             $qsParamValue = $idx;
    }else{
             if($listKey包含'[‘) {
                    #$listKey为hash 
                    $aElems = array_filter(preg_split(‘/[^a-z_0-9]/i’, $listKey), ‘strlen’); 
                    #用非数字字母和下划线拆分不含空元素的数组 
                    if (!($aList) && is_a($output, ‘SGL_Output’)) { 
                        #如果$aList空且$output为SGL_Output的实例 
                        // variable is of type  $output->org[‘organisation_id’] = ‘foo’; 
                        $qsParamValue = $output>{$aElems[0]}[$aElems[1]]; 
                        //$qsParamValue = $output->a[1] ; 
                    } else { 
                        $qsParamValue = $aList[$idx][$aElems[0]][$aElems[1]]; 
                        //$qsParamValue = $aList[$idx][a][1] ; 
                    } 
                }elseif( is_a($output, ‘SGL_Output’) && 
                            !empty($listKey) && 
                            isset($output->{$listKey})){ 
                    $qsParamValue = $output->{$listKey}
                     # 此例中$qsParamName为image_id时 $qsParamValue = $output->imageId 
                }else{ 
                    $qsParamValue = $listKey # 否则直接 a = 2 
                }
         }
}else{ 
    $qs .= ‘/’ . $qsParamName . ‘/’ . $aList[$idx]->$listKey;
}

由此可以看出$aList,$idx和不同格式的$params可以变化出非常多的参数取值,
下面是 $params = qsParamName|imageId||qsParamName||2||qsParamName|[a,1,2,3-4,,4] 时,一些有可能的结果,
这些结果很大程度上取决于$aList,$idx和两个参数的取值:

$qsParamName = $aList[$idx][$imageId];
$qsParamName = $idx;
$qsParamName = $output->a[1];
$qsParamName = $aList[$idx][a][1];
$qsParamName = $output->imageId;
$qsParamName = 2;

这其实是一个取值链,举个例子:

{makeUrl(#add#,#favorite#,#favorite#,##,#image_id|imageId||a|2#)}

的结果为:

/favorite/action/add/image_id/60/a/2/
{makeUrl(#add#,#favorite#,#favorite#,##,#image_id|imageId||a|2||qsParamName|[imageName,1,2,3-4,,4]#,)}

的结果为:

/favorite/action/add/image_id/60/a/2/qsParamName/$output->imageName[1]

的结果为:

makeUrl(#add#,#favorite#,#favorite#,##,#image_id|imageId||a|2||qsParamName|imageName#)

的结果为:

/favorite/action/add/image_id/60/a/2/qsParamName/60.jpg/

Older Posts »

Powered by WordPress