Author Archive

Seagull源码剖析(2)

Posted by 妖刀 18 February, 2006 (0) Comment

“渲染”的实现

我们知道,seagull的dispatch是在SGL_MainProcess内完成的。在SGL_MainProcess的process函数的最后那部分是实现seagull的显示,也就是“渲染”的,让我们来看看它是怎么实现的。

首先,根据用户设置的模板引擎,载入相应的RendererStrategy(也就是具体实现模板引擎的代码)。

$templateEngine = ucfirst($conf['site']['templateEngine']);
$rendererClass = 'SGL_Html'.$templateEngine.'RendererStrategy';
$rendererFile = 'Html'.$templateEngine.'RendererStrategy.php';

然后,生成一个RendererStrategy类,并将其交给新生成的一个View类。

$view = new SGL_HtmlView($output, new $rendererClass());

$rendererClass()在构造的时候,会获得模板的必要信息,比如模板路径等。

然后就调用 $view->render(); 该函数会转而调用$rendererClass 的render()函数。

让我们看看HtmlFlexyRendererStrategy类的render()到底做了些什么。

function render(/*SGL_View*/ &$view)
{
//  invoke html view specific post-process tasks
$view->postProcess($view);

//  suppress error notices in templates
SGL::setNoticeBehaviour(SGL_NOTICES_DISABLED);

//  prepare flexy object
require_once 'HTML/Template/Flexy.php';
$flexy = $this->initEngine($view->data);

$ok = $flexy->compile($view->data->masterTemplate);

//  if some Flexy 'elements' exist in the output object, send them as
//  2nd arg to Flexy::bufferedOutputObject()
$elements = (   isset($view->data->flexyElements)
&& is_array($view->data->flexyElements))
? $view->data->flexyElements
: array();

$data = $flexy->bufferedOutputObject($view->data, $elements);

SGL::setNoticeBehaviour(SGL_NOTICES_ENABLED);

$c = &SGL_Config::singleton();
$conf = $c->getAll();
if ($conf['site']['outputBuffering']) {
ob_end_flush();
}
return $data;
}

它首先回调view类的postProcess()函数,在那里,view类会做一些“渲染”前的准备工作:

$process =  new SGL_Process_BuildOutputData(
new SGL_Process_SetupWysiwyg(
new SGL_Process_GetPerformanceInfo(
new SGL_Process_SetupGui(
new SGL_Process_SetupNavigation(
new SGL_Process_SetupBlocks()
)))));

准备完毕,HtmlFlexyRendererStrategy->render()要真正完成模板的显示工作了,是由Compile那一行代码来实现的。这里要注意的是,render()函数compile 的模板文件是masterTemplate,而不是模块中指定的template文件。以default模块为例,masterTemplate文件是master.html,而我们要显示的模板文件是home.html,那么这个home.html是什么时候被compile的呢?答案就在masterTemplate文件内。打开master.html你会看到,里面有一行“{outputBody()}”,这会调用outputBody(),该函数中,才把template文件compile出来。

Categories : 技术 / 设计 Tags :

Seagull源码剖析(1)

Posted by 妖刀 17 February, 2006 (0) Comment

程序流程

seagull程序的执行起点是:AppController::run(),该还是在index.php中被调用,是整个程序执行的中心。run()函数首先调用的是AppController::init(),完成各种常量设置、全局变量设置等工作。

function init()
{
SGL_AppController::setupMinimumEnv();

$autoLoad = (file_exists(SGL_VAR_DIR  . '/INSTALL_COMPLETE.php'))
? true
: false;
$c = &SGL_Config::singleton($autoLoad);

$init = new SGL_TaskRunner();
$init->addData($c->getAll());
$init->addTask(new SGL_Task_SetupConstantsFinish());
$init->addTask(new SGL_Task_ModifyIniSettings());
$init->addTask(new SGL_Task_SetBaseUrl());
$init->addTask(new SGL_Task_SetGlobals());
$init->addTask(new SGL_Task_RegisterTrustedIPs());
$init->addTask(new SGL_Task_EnsureBC());
$init->main();
define('SGL_INITIALISED', true);
}

function setupMinimumEnv()
{
$init = new SGL_TaskRunner();
$init->addTask(new SGL_Task_SetupPaths());
$init->addTask(new SGL_Task_SetupConstantsStart());
$init->main();
}

初始化完成后,run()函数将请求对象($_POST、$_GET)存入$input变量内(这是一个SGL_Registry对象),然后使用Process机制运行真正的dispatch:

$process =  new SGL_Process_Init(
new SGL_Process_SetupErrorHandling(
new SGL_Process_SetupORM(
new SGL_Process_StripMagicQuotes(
new SGL_Process_DiscoverClientOs(
new SGL_Process_ResolveManager(
new SGL_Process_CreateSession(
new SGL_Process_SetupLangSupport(
new SGL_Process_SetupPerms(
new SGL_Process_AuthenticateRequest(
new SGL_Process_BuildHeaders(
new SGL_Process_SetupLocale(
new SGL_Process_DetectDebug(
new SGL_Process_DetectBlackListing(
new SGL_MainProcess()
))))))))))))));

$process->process($input);

这里是一个 Decorate模式(稍后分析),最后的SGL_MainProcess()是一个dispatcher,负责将请求分派到正确的module下面的action。

SGL::logMessage(null, PEAR_LOG_DEBUG);

$req = $input->getRequest();
$mgr = $input->get('manager');
$conf = $mgr->conf;
$mgr->validate($req, $input);

$output = & new SGL_Output();
$input->aggregate($output);

//  process data if valid
if ($mgr->isValid()) {
$mgr->process($input, $output);
}

$mgr->display($output);

这里就是实现了seagull的validate()、process()和display()机制的地方。

接下来要做的就是使用合适的模板引擎将html“渲染”出来:

//  build view
$templateEngine = ucfirst($conf['site']['templateEngine']);
$rendererClass = 'SGL_Html'.$templateEngine.'RendererStrategy';
$rendererFile = 'Html'.$templateEngine.'RendererStrategy.php';
if (file_exists(SGL_LIB_DIR .'/SGL/'. $rendererFile)) {
require_once SGL_LIB_DIR .'/SGL/'. $rendererFile;
} else {
PEAR::raiseError('Could not find renderer',
SGL_ERROR_NOFILE, PEAR_ERROR_DIE);
}
if (SGL::runningFromCLI()) {
$view = new SGL_CliView($output, new $rendererClass());
} else {
$view = new SGL_HtmlView($output, new $rendererClass());
}
echo $view->render();

seagull支持不止一种模板引擎,所以这里使用了Strategy模式,lib/SGL/目录下面的HtmlFlexyRendererStrategy.php、HtmlSavant2RendererStrategy.php和HtmlSmartyRendererStrategy.php是真正的“渲染”引擎。

Categories : 技术 / 设计 Tags :

Effective PHP . part 2 : 深入探讨,防止用户重复登陆

Posted by 妖刀 20 January, 2006 (0) Comment

这是个很古老的问题了,但是解决起来并不是很容易。

最直观的想法是:通过检验session来判断。当用户登陆时,设定一个session变量,如果又有新的用户登陆,则根据这个已经设定了的session变量来判断这个用户是否已经登陆了。

这个解决方法的最大问题在于session的清除是有一定的时限的,也就是说,当用户已经断线的时候,session变量可能还没有被清除。此时如果这个用户再次登陆的话,他会发现自己怎么都不能登陆了,直到session变量被清楚为止。

第二个想法则更进一步,既然问题出在没有及时清除session变量上面,那么我们有没有办法在用户登出(注销)的时候就清除与之相关的session变量呢?如果用户是老老实实登出,然后关闭浏览器的话,这个当然很好办,只需在用户登出时添加小段清除session变量的代码即可。不过据我所知,99%的用户大概都只会把浏览器一关了之。直接关掉浏览器而没有登出的最大问题就是,我们写的清除代码在这种情况下就无能为力了。当然我们还是可以想些办法的,比如在on_load这样的客户端事件中向服务器发送清除session变量的请求。服务器处理该请求,并清除session变量。

这已经是一个比较接近完美的答案了。但是。。当然还有个但是。。在某种情况下,这个方案仍然不能很好的工作。我指的是,如果用户在关闭浏览器之前就已经断线了的话,我们的清除代码就又一次不起作用了。

换个思路吧。既然我们的目的是要防止用户重复登陆,那么,如果我们能在用户登陆的时候,判断该用户是否仍然在线,如果在线,我们不是简单的拒绝该用户登陆,而是反过来把先前已经在线的用户踢出去,这样也就达到了我们的目的了。唯一的区别在于,我们已经不必在意这个先前在线的用户是真的在线呢,还是先前已经掉线但是其session变量还没有清除。在这里,这两种情况没什么差别。使用这种同一用户在不通地方登陆后,将先登陆的用户踢出的方案的一个最广为人知的例子就是腾讯的QQ。

好了,下面公布代码:

1)创建如下表格

CREATE TABLE `sessions` (
`SessionID` varchar(255) NOT NULL default '',
`LastUpdated` datetime NOT NULL default '0000-00-00 00:00:00',
`DataValue` text,
`is_kicked` tinyint(4) NOT NULL default '0',
PRIMARY KEY  (`SessionID`),
KEY `LastUpdated` (`LastUpdated`)
)

2)下面代码是用于使用自定义的session处理函数的,我们将session存储在自己的数据库中,便于清除

$aTime = 1800;

function sessao_open($aSavaPath, $aSessionName)
{
global $aTime;
sessao_gc( $aTime );
return True;
}
function sessao_close()
{
return True;
}
function sessao_read( $aKey )
{
$query = "SELECT DataValue FROM sessions WHERE SessionID='$aKey'";
$busca = mysql_query($query);
if(mysql_num_rows($busca) == 1)
{
$r = mysql_fetch_array($busca);
return $r['DataValue'];
}
}
function sessao_write( $aKey, $aVal )
{
$aVal = addslashes( $aVal );

$query = "SELECT DataValue FROM sessions WHERE SessionID='$aKey'";
$busca = mysql_query($query);
if(mysql_num_rows($busca) == 1)
{
$query = "UPDATE sessions SET DataValue = '$aVal', LastUpdated = NOW() WHERE SessionID = '$aKey'";
mysql_query($query);
return True;
}
else
{
$query = "INSERT INTO sessions (SessionID, LastUpdated, DataValue) VALUES ('$aKey', NOW(), '$aVal')";
mysql_query($query);
return True;
}
}
function sessao_destroy( $aKey )
{
$query = "DELETE FROM sessions WHERE SessionID = '$aKey'";
mysql_query($query);
return True;
}
function sessao_gc( $aMaxLifeTime )
{
$query = "DELETE FROM sessions WHERE UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(LastUpdated) > $aMaxLifeTime";
mysql_query($query);
return True;
}
session_set_save_handler("sessao_open", "sessao_close", "sessao_read", "sessao_write", "sessao_destroy", "sessao_gc");

3)用户登陆时

$sql_session = "select session_id from member where member_id = '".$row['member_id']."'";
$result_session = myquery($sql_session);

// 如果该用户已经登陆了,我们断开先前的。
if( @mysql_num_rows($result_session) > 0)
{
$row_session = @mysql_fetch_array($result_session);

if(session_id() != $row_session['session_id'])
{
$sql_se = "update sessions set is_kicked='1' where SessionID='".$row_session['session_id']."'";
myquery($sql_se);
}
}

$_SESSION['login'] = 'member';
$_SESSION['member_id'] = $row['member_id'];
$_SESSION['member_username'] = $row['member_username'];
$_SESSION['member_name'] = $row['member_name'];
// 讲用户的session id记录,方便后面清除session变量
$sql = "update member set session_id='".session_id()."' where member_id='".$row['member_id']."'";
myquery($sql);

4)在需要检测是否当前用户已经被踢的页面内加上下面的代码

// put this file after session_db.php
$sess_id = session_id();
$sql = "select * from sessions where SessionID='$sess_id' and is_kicked=1";
$result = myquery($sql);
if(@mysql_num_rows($result) > 0 && $_SESSION['login'] == 'member')
{
echo "<script>";
echo "alert(\"此帐号已在其他地方登陆,您被迫下线。如果发现有可疑盗用,请联系我们!\");";
echo "</script>";

$_SESSION['login'] = '';
$_SESSION['member_id'] = '';
$_SESSION['member_username'] = '';
$_SESSION['member_name'] = '';

session_destroy();
}

OK,基本过程和代码就是这样啦,不算太复杂。

Categories : 技术 / 设计 Tags :

Pear primer . part 3 : FTP处理

Posted by 妖刀 20 January, 2006 (0) Comment

以删除ftp服务器上的文件为例:

$oldpath = ini_get("include_path");
ini_set("include_path","../PEAR".PATH_SEPARATOR.$oldpath);
require_once 'Net/FTP.php';

$ftp = new Net_FTP();
$ftp->connect('ftp_server', 21);
$ftp->login('ftp_username', 'ftp_password');
$ftp->cd('foldername');
$ftp->rm('foldername or filename', true);
$ftp->disconnect();

Categories : 技术 / 设计 Tags :

名字

Posted by 妖刀 20 January, 2006 (0) Comment

1)外来词汇

Yahoo、Oracle、Lotus

2)缩写

SUN、SONY、SAP、Intel、microsoft、hotmail、365kit

3)跟创始人相关,或者创始人所喜爱的某件物品

Apple、redhat、Adobe、Cisco

4)公司的产品或者专注的领域

microsoft、Xeror

5)单词变形

Writely、Flickr、google、Xeror

6)生活中的物件

豆瓣、土豆

7)神话、传说、童话

alibaba

Categories : 片语 / 情绪 Tags :

商业模式

Posted by 妖刀 20 January, 2006 (0) Comment

1)如新浪、网易一样做广告

2)像google一样做广告

3)像阿里巴巴一样做电子商务

4)像新浪网易或者大量的影视网站一样做短信

5)像百度一样做搜索排名

6)像ebay一样收用户使用费

7)像hao123一样卖掉

8)像e都市一样卖虚拟地图上的广告

9)web2.0在聚集了人气之后,下一步怎么走?

Categories : 技术 / 设计 Tags :

“玫瑰还是二手的好”

Posted by 妖刀 17 January, 2006 (0) Comment

听二手玫瑰吧

听二手玫瑰吧

Categories : 电影 / 音乐 Tags :

SecureCRT+Linux服务器

Posted by 妖刀 17 January, 2006 (0) Comment

SecureCRT+Linux的SSH,可以实现基本的Linux服务器的远程管理功能。

1)在Linux服务器上运行ssh服务。

2)使用SecureCRT连接Linux服务器就可以得到一个shell。

3)往服务器上发送文件可以使用rz命令,SecureCRT会打开一个对话框让你选择需要上传的文件。

4)从服务器上下载文件使用sz命令,参数是需要下载的文件名。

5)下载一个目录可以先用 "tar -zcvf 文件名 要下载的目录 " 将目录打包再下载。

Categories : 技术 / 设计 Tags :

将blog系统转换为WordPress

Posted by 妖刀 16 January, 2006 (0) Comment

以前一直用plog(LifeType),但是plog的bug实在让我难以忍受。所以下定决心将blog系统迁移到wordpress。

迁移原有plog文章到wp的步骤如下:

1)在plog管理平台内,将“最近文章数目” 改成你blog内的文章总数;

2)在plog前台,打开RSS2.0导出的xml文件,将其另存;

3)在wp的管理界面内,选择Import->RSS,选择刚才导出的xml文件;

4)导入成功。

Categories : 技术 / 设计 Tags :

Tips: 用vim格式化你的php代码

Posted by 妖刀 14 January, 2006 (0) Comment

.left{float:left; margin-right:10px;} vim有自己的php indent脚本,但是这个脚本存在不少问题,你可以在vim的主页找到correct的php indent文件(http://www.vim.org)。

但是这个脚本只对php语言的代码起作用,对html代码是不会起格式化的作用。如果要对一个php和html混合代码的文件进行格式化的话,可以使用下面的方法:

.bcode { display: block; overflow: auto; font-size: 10pt; white-space: pre; background-color: #EEE; border: 1px solid #CCC; padding: 5px; width: 90%; } :set ft=html
gg=G
:set ft=php
gg=G

Categories : 技术 / 设计 Tags :