Symfony学习整理


Symfony学习整理

比较乱,自己看的,呵呵

一、schema.xml文件的建立:
1.如果已有数据库,可在config的propel.ini文件中连接好数据库,然后执行symfony propel-build-schema
生成schema.xml

2.手动写一个schema.xml文件
schema.xml的文法是相当简单的,在Propel 网站上有详细的说明,这是个XML,每一个tags 里有,

and tags.


   
   "1.0" encoding="UTF-8"?<
  
   
   "propel" defaultIdMethod="native" noxsd="true"<
     "Question"
   
   "integer""true""true""true"
   
   "integer"
   
   
    
    
    
    key foreignTable=
    
    "ask_user"<
          
    
    "user_id" foreign=
    
    "id"/<
        
        
    
    "title" type=
    
    "longvarchar" /<
        
    
    "body" type=
    
    "longvarchar" /<
      
    
    "created_at" type=
    
    "timestamp" /<
        
    
    "updated_at" type=
    
    "timestamp" /<
     
   
   
   
   
<>"ask_question" phpName=< "id" type= required= primaryKey= autoIncrement= /< "user_id" type= /<
"Answer" "integer""true""true""true" "integer" key foreignTable= "ask_question"< "question_id" foreign= "id"/< "user_id" type= "integer" /< key foreignTable= "ask_user"< "user_id" foreign= "id"/< "body" type= "longvarchar" /< "created_at" type= "timestamp" /<
<>"ask_answer" phpName=< "id" type= required= primaryKey= autoIncrement= /< "question_id" type= /<
"User" "integer""true""true""true" "varchar""50" "varchar""100" "varchar""100" "timestamp"
<>"ask_user" phpName=< "id" type= required= primaryKey= autoIncrement= /< "nickname" type= size= /< "first_name" type= size= /< "last_name" type= size= /< "created_at" type= /<
"Interest" "integer""true" key foreignTable= "ask_question"< "question_id" foreign= "id"/< "user_id" type= "integer" primaryKey= "true" /< key foreignTable= "ask_user"< "user_id" foreign= "id"/< "created_at" type= "timestamp" /<
<>"ask_interest" phpName=< "question_id" type= primaryKey= /<
"Relevancy" "integer""true" key foreignTable= "ask_answer"< "answer_id" foreign= "id"/< "user_id" type= "integer" primaryKey= "true" /< key foreignTable= "ask_user"< "user_id" foreign= "id"/< "score" type= "integer" /< "created_at" type= "timestamp" /<
<>"ask_relevancy" phpName=< "answer_id" type= primaryKey= /<



注意在这个文件里,数据库的名称是设成 propel ,是实际的数据库名。这是一个参数, 被用来连接symfony 框架的 Propel 层。真实的数据库名称是定义在databases.yml 文件里。

databases.yml文件内容
all: category:
all:
propel:
class: sfPropelDatabase
param:
phptype: mysql
host: localhost
database: database
username: youruser
password: yourpasswd
我使用的是
all:
propel:
class: sfPropelDatabase
param:
dsn: mysql://username:passwd@localhost/database


也可通过schema.xml文件可生成一个.sql文件,symfony propel-build-sql命令可生成data/sql/schema.sql文件


二、通过symfony propel-build-model

一些类别会产生在这个目录/lib/model/,它们是物件/关系映射(object-relational mapping)的产物。这些类别的好

处是我们可以用物件导向的方式而不是下SQL的INSERT/UPDATE指令来存取关联性资料库。Symfony使用Propel函式库来来

达到此目的。我们将叫这些物件为模型(model)。


三、一些小技巧
1、重指定默认主页
apps/frontend/config/routing.yml ,找到homepage:,修改它

homepage:
url: /
param: { module: question, action: list }


2、修改默认目录结构设定
apps/myapp/config/config.php

$sf_root_dir = sfConfig::get('sf_root_dir');
sfConfig::add(array(
'sf_web_dir_name' =< $sf_web_dir_name = 'www',
'sf_web_dir' =< $sf_root_dir.DIRECTORY_SEPARATOR.$sf_web_dir_name,
'sf_upload_dir' =<

$sf_root_dir.DIRECTORY_SEPARATOR.$sf_web_dir_name.DIRECTORY_SEPARATOR.sfConfig::get('sf_upload_dir_name'),
));


四、数据的读取
actions.class.php:

public function executeList ()
{
$this- }

listSuccess.php:


getId(), 'question/show?id='.$question- getTitle() ?<
getBody() ?<
getCreatedAt() ?<
getUpdatedAt() ?<

一步一步的讲解,到底做了什么:
1. 这个动作要求 Question 表传回符合空的WHERE条件(empty criteria)的记录(records)-就是传回所有记录
2. 把记录集(list)放进一个数组($questions),然后传入一个样板
3. 这样板一笔接一笔的把记录解析出来
4. 样板秀出每一笔记录的每一个字段(columns)
propel-build-model指令会用The →getId(), →getTitle(), →getBody(),etc. 这些方法,把 id,title, body, etc.

字段(fields) 的值(value)读取(retrieve)出来。这些是标准的 getters ,在每一个camelCased 字段名称前面(

prefix)加上 get - Propel 也提供标准的 setters ,前面(prefix)加上 set. Propel文件上也描述了对每一个类别

加上 accessors 。然后是神密的QuestionPeer::doSelect(new Criteria()) ,这也是标准的Propel要求(request)。


五、表单的验证
表单验证
用户可以回贴,但是,如果有谁提交空白的表单,数据库的质量将会受到殃及。避免此类情况发生,

在/apps/frontend/modules/comment/validate/目录下创建一个新的文件update.yml,加入:

methods:
post: [author, email, body]
get: [author, email, body]

fillin:
activate: on

names:
author:
required: Yes
required_msg: The name field cannot be left blank

email:
required: No
validators: emailValidator

body:
required: Yes
required_msg: The text field cannot be left blank

emailValidator:
class: sfEmailValidator
param:
email_error: The email address is not valid.
注意:复制时务必保留原来的格式,文件务虚以methods的m开头,不能有空格

启动fillin可使表单在验证失败的情况下,自动导入用户上一次输入的数据。names定义表单的验证规则。

在默认情况下,当验证失败,控制器(controller)将会把用户转updateError.php模板。较可取的做法是当验证失败时,

在同一页面显示表单和失败原因。打开modules/comment/actions/actions.class.php,加入函数:

public function handleError()
{
$this- }

打开modules/comment/templates/editSuccess.php模版,在第一行插入:

if ($sf_request-<hasErrors()): ?<
style="padding:10px;"<
Please correct the following errors and resubmit:


  • foreach($sf_request-<getErrors() as $error): ?<
    • echo $error?<
      endforeach ?<
      ="password"

"errors"

endif ?<


六、登陆和退出
建立登陆动作
打开/apps/frontend/modules/user/actions/action.class.php, 加入以下登录动作:

public function executeLogin()
{
$this-
return sfView::SUCCESS;
}

以上的动作把反向链接(referrer)保存为请求的一项属性。保存后此属性就能被相应的模板访问。我们把反向链接置于

tag中,当表单成功提交后,动作就能把页面转到反向链接。

return sfView::SUCCESS 把动作的结果传递到loginSuccess.php模板。由于返回的是SUCCESS,动作调出

loginSuccess.php这一模板。注意login是动作的名称。

在更深入讲解动作之前,我们先看一看模板。

新建loginSuccess.php模板
许多网上的信息交换是通过表单来达到的。Symfony提供了一套表单建立和管理的助手。

在/apps/frontend/modules/user/templates/目录下,建立模板loginSuccess.php:


     
      echo form_tag('user/login') ?<
 
  
     
     
class
class
echo input_hidden_tag('referer', $sf_request-<getAttribute('referer')) ?< echo submit_tag('sign in') ?<
="form-row"< echo input_tag('nickname', $sf_params-<get('nickname')) ?< ="form-row"< echo input_password_tag('password') ?<

以上模板引用了表单助手(form helpers)。这些助手可使表单撰写自动化。form_tag()助手建立一个默认动作为POST的

表单,且动作指向指定的参数。input_tag()助手返回一个tag其id属性的值为助手函数的第一个参数;而

的默认值设定为函数的第二个参数。更多关于表单助手请阅读相关章节。

到此的最关键点是以上表单提交与显示的动作是相同的(form_tag()的参数)。所以,让我们回到原来的动作。

处理表单提交
把login动作覆盖为以下代码:

public function executeLogin()
{
if ($this- {
// display the form
$this- }
else
{
// handle the form submission
$nickname = $this-

$c = new Criteria();
$c- $user = UserPeer::doSelectOne($c);

// nickname exists?
if ($user)
{
// password is OK?
if (true)
{
$this-
$this-

$this- $this-
// redirect to last page
return $this- '@homepage'));
}
}
}
}


login动作将用于显示和处理登陆表单。因此,它需要知道是被呼出的环境(context)。如果login动作不是在POST模式

下呼出的,它则是由链接呼出。假如login动作在POST模式下被访问,则说明了它是由表单提交呼出的。后者需要进行表

单处理。

login动作从请求中获取nickname参数,然后检查它是否以存在于数据库的User资料表中。

另一种验证是否登陆成功的方法:

验证文件
登录表单含有一个昵称,一个密码域。但是如果用户输入了不正确的数据怎么办?为了应对这种情况,

在/frontend/modules/user/validate目录下创建一个login.yml文件(login是将要被去验证的动作的名称),并加入以下

内容:

methods:
post: [nickname, password]

names:
nickname:
required: true
required_msg: your nickname is required
validators: nicknameValidator

password:
required: true
required_msg: your password is required

nicknameValidator:
class: sfStringValidator
param:
min: 5
min_error: nickname must be 5 or more characters
首先,在methods的报头为表单的方法定义了一个要被验证的表单域的列表(我们只定义了POST方法,因为GET只是用来演

示登录表单,并不需要验证)。 接下来,在names的报头, 针对每个表单域的检查需求被列了出来,同时还有其出错时相

应的错误信息。最后,由于昵称域需要特定的验证规则集合,我们在相应的报头下列出了详细的规则内容。在此例中,

sfStringValidator是一个symfony内建的验证器。

自定义验证器
你还记着昨天在login动作中检查一个输入昵称的存在吗?嗯,那个看起来像个表单验证。这段代码应该被取出来放到一

个自定义的验证器中。你认为这是复杂的?但他确实不是。编辑login.yml验证文件如下:

...
names:
nickname:
required: true
required_msg: your nickname is required
validators: [nicknameValidator, userValidator]
...
userValidator:
class: myLoginValidator
param:
password: password
login_error: this account does not exist or you entered a wrong password
我们只是添加了一个myLoginValidator类作为昵称表单域的新的验证器。这个验证器目前还不存在。但是我们知道它将需

要一个密码数据去完全证用户。因此密码数据将被用标签password作为参数来传递。

自定义验证器
现在是时间编写自定义的myLoginValidator了。你可以把它建立在任意一个可被module访问的lib/目录下(他们

是,askeet/lib/, askeet/apps/frontend/lib/, askeet/apps/frontend/modules/user/lib/)。现在,myLoginValidator

被用来作为一个应用程序范围内的验证器,所以myLoginValidator.class.php将被建立在askeet/apps/frontend/lib/目

录下:


class myLoginValidator extends sfValidator
{
public function initialize($context, $parameters = null)
{
// initialize parent
parent::initialize($context);

// set defaults
$this-
$this-

return true;
}

public function execute(&$value, &$error)
{
$password_param = $this-
$password = $this-

$login = $value;

// anonymous is not a real user
if ($login == 'anonymous')
{
$error = $this-
return false;
}

$c = new Criteria();
$c- $user = UserPeer::doSelectOne($c);

// nickname exists?
if ($user)
{
// password is OK?
if (sha1($user- {
$this-
$this-

$this- $this-
return true;
}
}

$error = $this-
return false;
}
}在登陆表单递交后,验证器被调用时,initialize()方法首先被调用。它初始化login_error消息(’Invalid Input’)

的默认值,并且把参数(那些在login.yml文件中报头param下的数据)合并到参数容器(Holder)对象中。

接下来,execute()方法被....执行。$password_param是在login.yml中password报头下的一个表单域名。他被当作一个

表单域名来读取申请变量中得值。因此$password包含了用户输入的密码值。$value是当前表单域的值——

myLoginValidator是为了昵称这个表单域被调用的。因此,$login中包含了用户输入的昵称。在最后,myLoginValidator

拥有了真正验证一个用户的所有必备数据。

接下来的代码是从login动作剥离出来的。但是另外,密码有效性的测试(在这之前总是真值)被实现了:用户输入密码的

哈希值(使用存在数据库中的salt来哈希)被用来和用户的哈希密码来比较。

如果登录和密码是正确的,验证器将返回真值并且表单的目标动作(executeLogin())将被执行。如果不是,他将返回

false并且handleErrorLogin()将被执行。

移除动作中的代码
现在所有的验证代码被放在了验证器中, 所以我们需要把它们从动作中除去。实际上,当动作被POST方法调用时,这就

意味着验证器已经验证了申请,因此用户是正确的。所以在这里唯一动作需要做的事情就是转回反向链接(referer)页:

public function executeLogin()
{
if ($this- {
// display the form
$this-
return sfView::SUCCESS;
}
else
{
// handle the form submission
// redirect to last page
return $this- '@homepage'));
}
}通过尝试用测试用户来登录,检查修改是否正确(在清空缓存之后,因为我们添加了新的验证类,他需要被自动重新载入

)。


在不久的将来我们会建立密码控制的机制。目前的动作只是简单地把用户的nickname和id属性储存于session中。referer

被预存于 tag 中,最终用户被转向到referer指定的地址。如果referer没有预存的话,用户则被转向到主页

(@homepage)。

注意以上的范例中有两种不同的属性:request属性和session属性。request属性($this→getRequest()→

setAttribute())只存在于请求范围,一旦被转向它将被销毁。而session属性($this→getUser()→setAttribute())

被保存直道用户期间(session)结束。要了解更多关于属性,请阅读Symfony book parameter holder这章。

赐予权限
用户可以登录我们的网站是一件非常好的事,但是他们不会仅仅是为了好玩才这样做的。创建登录动作的目的在于管理用

户权限:登录的用户能够发帖,声明对一个问题的兴趣, 评论一个问题,而其他非登录用户则只能够浏览。

验证一用户,你需要调用sfUser里的setAuthenticated()函数。除了鉴定,sfUser提供了凭证管理机制(→

addCredential())去细化用户访问权限控制。更多关于凭证。

以下代码鉴定用户,添加“subscriber”权限。

$this-
$this-

(session)属性中,用户同样被给于权限去访问受到保护的网站内容。在明天,我们将会看到如何限制验证后的用户访问

网站内容。


限制访问
如果你想对一个动作的访问进行限制,那么你只需要在模块的config/目录下添加一个secrity.yml文件,如下(现在先不

要这么做):

all:
is_secure: on
credentials: subscriber
这个模块中的动作只有当用户被验证过拥有一个subscriber资质之后才能被执行。 在askeet项目中,登录被用来发布新

问题,声明关于一个问题的兴趣和发表评论。所有其他的动作对于未登录用户是开放的。 因此去限制question/add动作(

还需要去编写),添加下面的security.yml文件在askeet/apps/frontend/modules/question/config/目录下:

add:
is_secure: on
credentials: subscriber

all:
is_secure: off


添加user/logout动作
注意→setAttribute()函数的最后一个参数 “subscriber” 定义命名空间(namespace),其优点在于让我们储存相同名

字的属性到不同的命名空间。另一优点是可让我们用一个名令快速清除命名空间里的所用属性:

public function executeLogout()
{
$this-
$this-

$this-

$this- '@homepage');
}

使运用命名空间可以使我们不用一个接一个的删除这两个属性。 这样少了一行代码, 要想着这变懒!

更新模板
原来的模板显示 “login” 链接即使用户已经登录。就做一下更正吧。打开

打开askeet/apps/frontend/templates/layout.php,把我们今天最初加到那段代码改为:

错误处理
如果用户输入了错误的数据将会发生些什么?写在login.yml文件中的条件将不会被满足,symfony的控制器将会把申请传

递给userAction类的handleErrorLogin()方法而不是form_tag参数中计划好的executeLogin()方法。如果这个方法不存在

, 默认的行为就会显示loginError.php模版,这是因为默认的handleError()方法返回:

public function handleError()
{
&bsp; return sfView::ERROR;
}这将要写一个全新的模版。 但是我们却喜欢再次显示登录表单,并把错误信息显示在出了问题的表单域附近。因此,在

这个例子中,让我们修改一下登录错误行为,让它去显示loginSuccess.php模版:

public function handleErrorLogin()
{
return sfView::SUCCESS;
}


密码存储

在我们的数据模型和测试数据中,并没有密码字段。这时我们应该定义一个。但是你知道在数据库中存储一个有着明显文

本的密码是一个非常不安全的想法。所以, 我们将存储一个密码的sha1哈希码并用一个随机的密码匙来哈希它。如果你

不熟悉’salt’过程,请查看密码骇客实践。

因此,打开schema.xml文件并给User表加入以下列:

使用propel-build-model命令重建Propel模型。你应该也把这两个字段加入数据库中。或者是用手工方式,或是是用

symfony的propel-build-sql命令产生出来的schema.sql语句来完成。现在打开/lib/model/User.php并加入setPassword

()方法:

public function setPassword($password)
{
$salt = md5(rand(100000, 999999).$this-
$this-
$this-
}这个函数模拟了一个密码的直接存储,同时它还存储了一个salt随机密码匙(一个哈希后的三十二个字符的随机字符串)

和哈希后的密码(一个40个字符的字符串)

七、分页
Question分页器

Symfony提供了sfPropelPager来达到分页的目的。sfPropelPager封装了对数据库的请求,从而只返回显示页所需的数据

。打个比方说,假如一分页器初始设定显示10个档案,sfPropelPager将会把数据库查询限制为10档案,利用偏差实现分

页(SQL里的LIMIT和OFFSET语句)。

修改question/list动作:

public function executeList ()
{
$this- }

修改此动作,目标是让模板访问到sfPropelPager对象,同时让问题由被关注的数目(number of interest)排列:

public function executeList ()
{
$pager = new sfPropelPager('Question', 2);
$c = new Criteria();
$c-
$pager-
$pager- $pager-
$pager-

$this- }

sfPropelPager在初始化时,设定了每页档案数量,以上范例为2档每页。→setPage()指定了显示页,至于哪页被显示则

由请求的参数决定。举个例说,假如请求参数是2,表示第二页,sfPropelPager将返回档案3-5。这个请求参数的默认值

是1,因此1-2档将返回如果此参数空缺。

译著:我认为如果请求参数是2,返回的应是3-4


使用自定义参数
在通常情况下,把常数置于配置文件是比较好的做法。就以以上的范例来说,每页档案数可存放到配置文件。把以上有

new sfPropelPager 这一行改为:

$pager = new sfPropelPager('Question', sfConfig::get('app_pager_homepage_max'));打开文件
/apps/frontend/config/app.yml,加入:

all:
pager:
homepage_max: 2

pager关键词被用来做命名空间, 这就是为什么它出现在参数名称中的原因。更多关于命名自定义参数的配置和规则请看

配置文件的说明

打开 listSuccess.php 模板,将

>?php foreach($questions as $question): ?<
修改为

>?php foreach($question_pager- 我们的网页将显示分页好的页面。

导航栏
目前的模板需植入网页导航栏 - 让用户能够前翻页和后翻页。在模板的最后加入以下代码:

>div id="question_pager"<
>?php if ($question_pager- >?php echo link_to('«', 'question/list?page=1') ?<
>?php echo link_to('<', 'question/list?page='.$question_pager-
>?php foreach ($question_pager- >?php echo link_to_unless($page == $question_pager- >?php endforeach; ?<

>?php echo link_to('>', 'question/list?page='.$question_pager- >?php echo link_to('»', 'question/list?page='.$question_pager- >?php endif; ?<
>/div<

这段代码利用了很多sfPropelPager对象的方法。 在→haveToPaginate()中,只有当要显示的结果超过了每页所允许的数量才返回真值。而→getPreviousPage(), →getNextPage() and →getLastPage()的作用和他们的名字是一样的。→getLinks()提供了有多少个页需要被显示的数组。→getCurrentMaxLink()返回了最后一个页码。

这个例子展示了一个简单的symfony链接助手(Helper): link_to_unless()。在给定的第一个参数是假的情况下,他将输出一个正常的link_to助手。如果为真,他将只显示被包含在一个简单的里面的文本而不显示链接。


八、修改URl

我们可以让我们的URL更加亲和用户和搜索引擎。就用贴的标题作为URL吧。

标题为URL存在一个问题 - 标题可含有特殊字符如空格。你可以转换字符,但在有空格的情况下会产生不美观的URL(例如

%20)。所以最好是在Post模型(model)中加入一个清理标题的函数。打开/lib/model/Post.php,添加函数:

public function getStrippedTitle()
{
$result = strtolower($this-

// strip all non word chars
$result = preg_replace('//W/', ' ', $result);

// replace all white space sections with a dash
$result = preg_replace('// /', '-', $result);

// trim dashes
$result = preg_replace('//-$/', '', $result);
$result = preg_replace('/^/-/', '', $result);

return $result;
}


现在你能为post模块建立一个permalink动作. 打开modules/post/actions/actions.class.php,加入函数:

public function executePermalink()
{
$posts = PostPeer::doSelect(new Criteria());
$title = $this-
foreach ($posts as $post)
{
if ($post- {
break;
}
}
$this-

$this-
$this- }

打开模版modules/post/templates/listSuccess.php, 把显示id的部分去掉,且把

getTitle() ?<

修改为:

getTitle(), '/'.$sf_last_module.'/permalink?title='.$post-

()) ?<

最后,打开/apps/frontend/config/routing.yml,加入:

list_of_posts:
url: /latest_posts
param: { module: post, action: list }

post:
url: /weblog/:title
param: { module: post, action: permalink }

打开网页测试新的URL。

九、Ajax应用

在布局中添加指示器
当异步请求正在处理的时候,一个AJAX网站的用户并没有任何线索关于他行为的处理和即将显示的结果。这就是为什么每个包含AJAX互动的网页应该显示一个行动指示器。 为了这个目的,在全局layout.php的上面添加:

"indicator" style="display: none"<
尽管默认是隐藏的,这个

将会被显示在AJAX请求正在处理的时候。这是个空的内容,但是main.css样式模版(存储在askeet/web/css/目录下)定义了它的形状和内容:

div#indicator
{
position: absolute;
width: 100px;
height: 40px;
left: 10px;
top: 10px;
z-index: 900;
background: url(/images/indicator.gif) no-repeat 0 0;
}

这就是为什么问题名称和兴趣区域的代码被重构到一个_interested_user.php片断中。再次打开这个片段,
>?php use_helper('User') ?<

>div class="interested_mark" id="mark_>?php echo $question- >?php echo $qestion- >/div<

>?php echo link_to_user_interested($sf_user, $question) ?<
这个链接做的不只是重新定向到另外一个网页。事实上,如果用户已经声明了他或她关于一个问题的兴趣,他或她是不应该能再次声明。并且,如果用户没有被授权。。。嗯,我们将会在以后看到这种情形。 这个链接被写入了一个助手函数。他被创建在askeet/apps/frontend/lib/helper/UserHelper.php中:

>?php

use_helper('Javascript');

function link_to_user_interested($user, $question)
{
if ($user-
{
$interested = InterestPeer::retrieveByPk($question- if ($interested)
{
// already interested
return 'interested!';
}
else
{
// didn't declare interest yet
return link_to_remote('interested?', array(
'url' =< 'user/interested?id='.$question-
'update' =< array('success' =< 'block_'.$question-
'loading' =< "Element.show('indicator')",
'complete' =< "Element.hide('indicator');".visual_effect('highlight', 'mark_'.$question-
));
}
}
else
{
return link_to('interested?', 'user/login');
}
}

?<
link_to_remote()函数是第一个AJAX交互组件:呼叫器。他声明了当用户点击链接时(此处:user/interested),哪个动作将被请求,那片区域将被动作的返回结果(此处:id_XX的元素)更新。两个事件处理器(loading和complete)被添加和连接到prototype javascript函数。Prototype库用简单的函数调用提供了非常容易的javascript工具在网页上应用可视化效果。他仅有的缺点是缺乏文档,但是源代码是直截了当的。 我们选择使用助手来替代片段是因为这个函数包括了比HTML更多的php代码。 不要忘记添加id—— id=”block_”到question/_list片段。 class="interested_block" id="block_getId() ?<"<
include_partial('interested_user', array('question' =< $question)) ?<
注意: 这个仅仅当你在web服务器配置中正确的定义了sf别名时才能工作。在第一天中有详细解释。 结果区域link_to_remote()javascript助手的update属性定义了结果区域。在这个例子里,user/interested动作将替换id为block_XX元素的内容。如果你有些迷惑,看一下在模版中片段整合将要显示的内容:
...
>div class="interested_block" id="block_>?php echo $question- >!-- between here --<
>?php use_helper('User') ?<
>div class="interested_mark" id="mark_>?php echo $question- >?php echo $question- >/div<
>?php echo link_to_user_interested($sf_user, $question) ?<
>!-- and there --<
>/div<
...
一个结果区域是在两个组件的部分。动作一旦执行就将替换这个内容。 第二个id(mark_XX)是非常直观的。link_to_remote助手的complete事件处理器加亮了点击过的兴趣的interested_mark 。。。在动作返回了一个增加过的兴趣数量之后。 服务器动作AJAX呼叫器定向到user/interested动作。这个动作必须为当前问题和用户创建一个新的interest表记录。这里是怎么用symfony实现它的方法:

public function executeInterested()
{
$this- $this-

$user = $this-

$interest = new Interest();
$interest-
$interest-
$interest-
}记住Interest对象的→save()方法被定义用来增加相关User表interested_user域的值。因此对当前问题有兴趣的用户数量将会在动作调用之后被神奇地在屏幕上增加。 此外,作为结果的interestedSuccess.php模版将会展示些什么呢? include_partial('question/interested_user', array('question' =< $question)) ? <它会再次展示question模块的_interested_user.php片段。这就是先把这个片段创建的好处。 我们同样也需要把这个模版的布局禁止掉(modules user config view.yml): interestedSuccess:
has_layout: off
添加一个页内登录表格我们前面讲过只有注册用户才能对一个问题声明兴趣。这就意味着如果一个非验证过用户点击一个“interested?”链接,首先显示的应该是登录页。 但是等一下。为什么一个用户必须用一个新加载的网页登录,而且失去了与他想声明兴趣的问题的联系。一个更好的办法是让一个登录表格动态的呈现在网页之中。这就是我们将要去做的: 在布局中添加一个隐藏的登录表格打开一个全局布局(在askeet/apps/forntend/templates/layout.php中),并且加入以下代码(在header和content div之间):
>?php use_helper('Javascript') ?<

>div id="login" style="display: none"<
>h2 /h2<

>?php echo link_to_function('cancel', visual_effect('blind_up', 'login', array('duration' =< 0.5))) ?<

>?php echo form_tag('user/login', 'id=loginform') ?<
nickname: >?php echo input_tag('nickname') ?<>br /<
password: >?php echo input_password_tag('password') ?<>br /<
>?php echo input_hidden_tag('referer', $sf_params- >?php echo submit_tag('login') ?<
>/form<
>/div<

再次,这个表格被设置成默认隐藏的。 referer隐藏标签包含了referer请求参数(如果它存在的话)或者是其它当前地址。 当一个非登录用户点击interested连接时显示登录表单你还记着我们之前些的那个User助手吗?我们现在来处理用户是没有授权的这种情况。再次打开askeet/lib/helper/UserHelper.php文件并修改以下代码: return link_to('interested?', 'user/login');为: return link_to_function('interested?', visual_effect('blind_down', 'login', array('duration' =< 0.5)));当用户是非登录时, 在“insterested?”上的连接将会触发一个prototype javascript效果(blind_down)。它将显示id为login的元素。这个元素也就是我们加在布局中的表单 登录用户user/login动作已经在第五天里实现了,并且在第六天里进行了重构。我们需要再次修改它吗?

public function executeLogin()
{
if ($this- {
// display the form
$this-
return sfView::SUCCESS;
}
else
{
// handle the form submission
// redirect to last page
return $this- '@homepage'));
}
}总得来说,不用了。它非常完美的实现了它的目的,回调地址的处理将把用户转向到当连接被点击时他们正在访问的地方。 现在测试AJAX功能。一个非注册用户将不用离开当前页就可以看到登录表单。如果昵称和密码被识别,网页将会被刷新并且用户将可以点击“interested?”这个他之前想访问的连接。

>?php if ($sf_user- >li<>?php echo link_to('sign out', 'user/logout') ?<>/li<
>li<>?php echo link_to($sf_user- /li<
>?php else: ?<
>li<>?php echo link_to('sign in/register', 'user/login') ?<>/li<
>?php endif ?<

/span>
="nickname"

【作者: Liberal】【访问统计:<script language="JavaScript" src="http://counter.blogchina.com/PageServlet?pageid=6419785&blogid=8561"></script>】【2007年08月18日 星期六 19:23】【注册】【打印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值