Author Archive
Seagull源码剖析(2)
“渲染”的实现
我们知道,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出来。
Seagull源码剖析(1)
程序流程
seagull程序的执行起点是:AppController::run(),该还是在index.php中被调用,是整个程序执行的中心。run()函数首先调用的是AppController::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:
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。
$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“渲染”出来:
$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是真正的“渲染”引擎。
Effective PHP . part 2 : 深入探讨,防止用户重复登陆
这是个很古老的问题了,但是解决起来并不是很容易。
最直观的想法是:通过检验session来判断。当用户登陆时,设定一个session变量,如果又有新的用户登陆,则根据这个已经设定了的session变量来判断这个用户是否已经登陆了。
这个解决方法的最大问题在于session的清除是有一定的时限的,也就是说,当用户已经断线的时候,session变量可能还没有被清除。此时如果这个用户再次登陆的话,他会发现自己怎么都不能登陆了,直到session变量被清楚为止。
第二个想法则更进一步,既然问题出在没有及时清除session变量上面,那么我们有没有办法在用户登出(注销)的时候就清除与之相关的session变量呢?如果用户是老老实实登出,然后关闭浏览器的话,这个当然很好办,只需在用户登出时添加小段清除session变量的代码即可。不过据我所知,99%的用户大概都只会把浏览器一关了之。直接关掉浏览器而没有登出的最大问题就是,我们写的清除代码在这种情况下就无能为力了。当然我们还是可以想些办法的,比如在on_load这样的客户端事件中向服务器发送清除session变量的请求。服务器处理该请求,并清除session变量。
这已经是一个比较接近完美的答案了。但是。。当然还有个但是。。在某种情况下,这个方案仍然不能很好的工作。我指的是,如果用户在关闭浏览器之前就已经断线了的话,我们的清除代码就又一次不起作用了。
换个思路吧。既然我们的目的是要防止用户重复登陆,那么,如果我们能在用户登陆的时候,判断该用户是否仍然在线,如果在线,我们不是简单的拒绝该用户登陆,而是反过来把先前已经在线的用户踢出去,这样也就达到了我们的目的了。唯一的区别在于,我们已经不必在意这个先前在线的用户是真的在线呢,还是先前已经掉线但是其session变量还没有清除。在这里,这两种情况没什么差别。使用这种同一用户在不通地方登陆后,将先登陆的用户踢出的方案的一个最广为人知的例子就是腾讯的QQ。
好了,下面公布代码:
1)创建如下表格
`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存储在自己的数据库中,便于清除
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)用户登陆时
$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)在需要检测是否当前用户已经被踢的页面内加上下面的代码
$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,基本过程和代码就是这样啦,不算太复杂。
Pear primer . part 3 : FTP处理
以删除ftp服务器上的文件为例:
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();
名字
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
商业模式
1)如新浪、网易一样做广告
2)像google一样做广告
3)像阿里巴巴一样做电子商务
4)像新浪网易或者大量的影视网站一样做短信
5)像百度一样做搜索排名
6)像ebay一样收用户使用费
7)像hao123一样卖掉
8)像e都市一样卖虚拟地图上的广告
9)web2.0在聚集了人气之后,下一步怎么走?
SecureCRT+Linux服务器
SecureCRT+Linux的SSH,可以实现基本的Linux服务器的远程管理功能。
1)在Linux服务器上运行ssh服务。
2)使用SecureCRT连接Linux服务器就可以得到一个shell。
3)往服务器上发送文件可以使用rz命令,SecureCRT会打开一个对话框让你选择需要上传的文件。
4)从服务器上下载文件使用sz命令,参数是需要下载的文件名。
5)下载一个目录可以先用 "tar -zcvf 文件名 要下载的目录 " 将目录打包再下载。
将blog系统转换为WordPress
以前一直用plog(LifeType),但是plog的bug实在让我难以忍受。所以下定决心将blog系统迁移到wordpress。
迁移原有plog文章到wp的步骤如下:
1)在plog管理平台内,将“最近文章数目” 改成你blog内的文章总数;
2)在plog前台,打开RSS2.0导出的xml文件,将其另存;
3)在wp的管理界面内,选择Import->RSS,选择刚才导出的xml文件;
4)导入成功。
Tips: 用vim格式化你的php代码
.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



