symfony表单与页面实现技巧


Posted in PHP onJanuary 26, 2015

本文实例讲述了symfony表单与页面实现技巧。分享给大家供大家参考。具体如下:

symfony开发很简洁,但是功能的数量仍然很缺乏。现在是时候进行一些askeet站点与用户之间的交互了。而HTML交互的根本--除了起链接--就是表单了。

这里我们的目标是允许用户登陆,并在主页的问题列表中进行翻阅。这对于开发而言是很快的,并且可以让我们回忆起前面的内容。

登陆表单

在测试数据中存在用户,但是程序却没有办法来进行验证。下面我们要在程序的每一个页面添加一个登陆表单。打开全局的布局文件askeet/apps/frontend/templates/layout.php,并且在到about的连接之前添加下面的代码行:

<li><?php echo link_to('sign in', 'user/login') ?></li>

当前的布局将这些链接放在web调试工具栏之后。要看到这些链接,点击'Sf'图标折叠起调试工具栏就可以看到了。

现在需要创建user模块。而question模块是在第二天生成的,这一次我们只是叫symfony来创建模块框架,而我们将会自己来编写这些代码。

$ symfony init-module frontend user

这个框架包含一个默认的index动作与一个indexSuccess.php模板。删除他们,因为我们并不需要他们。

创建user/login动作

在user/actions/action.class.php文件中,添加下面的登陆动作:
public function executeLogin()

{

  $this->getRequest()->setAttribute('referer', $this->getRequest()->getReferer());

 

  return sfView::SUCCESS;

}

这个动作将referer保存在请求属性中。然后这个属性可为模块所用存放在一个隐藏区域中,从而这个表单的目的动作可以在成功登陆后重定向到原始的referer。

语句return sfView::SUCCESS将动作执行结果传递到loginSuccess.php模块。这条语句是在一个不包含返回语句的动作中实现的,这也就是一个动作的默认模块被称之为actionnameSuccess.php的原因。

在动作上开始更多的工作之前,我们先来看一下模块。

创建loginSuccess.php模块

web上的许多人机交互使用表单,而Symfony通过提供一个form帮助器集合来组织表单的创建与管理。

在askeet/apps/frontend/modules/user/templates/目录下,创建下面的loginSuccess.php模块:

<?php echo form_tag('user/login') ?>

 

  <fieldset>

 

  <div class="form-row">

    <label for="nickname">nickname:</label>

    <?php echo input_tag('nickname', $sf_params->get('nickname')) ?>

  </div>

 

  <div class="form-row">

    <label for="password">password:</label>

    <?php echo input_password_tag('password') ?>

  </div>

 

  </fieldset>

 

  <?php echo input_hidden_tag('referer', $sf_request->getAttribute('referer')) ?>

  <?php echo submit_tag('sign in') ?>

 

</form>

这个模块是我们第一次使用表单帮助器。这些Symfony函数可以帮助我们自动化编写表单标签。form_tag()打开一从此标签,使用POST作为默认的动作,并且指向作为参数传递的动作。input_tag()帮助器产生一个<input>标签,并且依据所传递的第一个参数自动添加一个id属性;而默认值则是由第二个参数得到。我们可以在Symfony一书的相关章节查找到更多的关于表单帮助器与他们所产生的HTML代码的内容。

这里的实质是当表单提交时则会调用这个动作。所以我们返回来看一下这个动作。

处理表单提交

用下面的代码来替换我们刚才所编写的登陆动作:

public function executeLogin()

{

  if ($this->getRequest()->getMethod() != sfRequest::POST)

  {

    // display the form

    $this->getRequest()->setAttribute('referer', $this->getRequest()->getReferer());

  }

  else

  {

    // handle the form submission

    $nickname = $this->getRequestParameter('nickname');

 

    $c = new Criteria();

    $c->add(UserPeer::NICKNAME, $nickname);

    $user = UserPeer::doSelectOne($c);

 

    // nickname exists?

    if ($user)

    {

      // password is OK?

      if (true)

      {

        $this->getUser()->setAuthenticated(true);

        $this->getUser()->addCredential('subscriber');

 

        $this->getUser()->setAttribute('subscriber_id', $user->getId(), 'subscriber');

        $this->getUser()->setAttribute('nickname', $user->getNickname(), 'subscriber');

 

        // redirect to last page

        return $this->redirect($this->getRequestParameter('referer', '@homepage'));

      }

    }

  }

}

登陆动作可以同时用来显示登陆表单并且进行处理。相应的,他必须知道所调用的环境。如果这个动作并没有在POST模式下调用(因为是由一个链接来请求的):而这正是我们在前面所讨论的情况。如果是在POST模式下请求的,那么则会由表单调用这个动作并进行相应的处理。

这个动作会由请求参数得到nickname域的值,并且查询User表来查看在数据库是否存在此用户。

将来一个密码控制将会为用户分配凭证。但是现在,这个动作所做的只是在一个会话属性中存储用户的id与nickname属性。最后,这个动作重定向到表单中隐藏中的原始referer域,这是作为一个请求参数传递的。如果这个域是空的,则会使用默认值。

这里我们需要注意这个例子中两种类型的属性集合之间的区别:request attributes($this->getRequest()->setAttribute())是为模板所保存的,而且只要答案发送到referer则会被忘记。session attributes($this->getUser()->setAttribute())是在整个用户会话生命期被保存的,而且在将来其他的动作也可以访问他们。如果我们希望了解更多的关于属性的内容,我们可以查看Symfony一书的参数保存器一节。

分配权限

用户可以登陆进askeet网站是一件好事,但是用户并不仅是因为好玩而登陆。发表一个新问题,对某一个问题表示兴趣,评价一个评论都需要登陆。而其他的动作将会向非登陆用户开放。

要将一个用户设置为经过验证的,我们需要调用sfUser对象的->setAuthenticated()方法。这个对象同时提供了一个证书机制(->addCredential()),来通过配置限制访问。Symfony一书的用户证书一节对此进行了详细的解释。

这就是下面两行的目的:

$this->getContext()->getUser()->setAuthenticated(true);

$this->getContext()->getUser()->addCredential('subscriber');

当nickname被识别后,不仅用户数据被存放在会话属性中,而且这个用户也会被分配网站限制部分的访问权限。在明天我们将会看到如何限制验证用户的程序访问。

添加user/logout动作

关于->setAttribute()方法还有最后一个窍门:最后一个参数(上面例子中的subscriber)定义了属性存放的名字空间。一个名字空间不仅允许一个在另一个名字空间存在的名字指定给一个属性,而且可以使用一个命令快速移除所有这些属性:

public function executeLogout()

{

  $this->getUser()->setAuthenticated(false);

  $this->getUser()->clearCredentials();

 

  $this->getUser()->getAttributeHolder()->removeNamespace('subscriber');

 

  $this->redirect('@homepage');

}

使用名字空间可以省去我们一个一个移除这些属性的麻烦:这只是一行语句。

更新布局

当前这个布局即使用户已经登陆仍然显示一个'login'链接。让我们来修正这一点。在askeet/apps/frontend/templates/layout.php文件中,修改我们在今天的指南开始时所修改的代码:

<?php if ($sf_user->isAuthenticated()): ?>

  <li><?php echo link_to('sign out', 'user/logout') ?></li>

  <li><?php echo link_to($sf_user->getAttribute('nickname', '', 'subscriber').' profile', 'user/profile') ?></li>

<?php else: ?>

  <li><?php echo link_to('sign in/register', 'user/login') ?></li>

<?php endif ?>

现在是时候进行测试了,我们可以显示程序的任何一页,点击'login'链接,输入一个可用的昵称('anonymous'为例)并且进行验证。如果窗口顶部的'login'变为'sign out',则我们所做的一切都是正确的。最后,试着注销来查看'login'链接是否再次出现。

问题组织

随着数以千计的Symfony爱好者访问askeet网站,在主页上显示的问题就会逐渐变多。为了避免变慢的请求速度,问题列的随意翻阅就成为必须解决的问题。

Symfony为这一目的提供了一个对象:sfPropelPager。他会封装到数据的请求,从而只会查询当前页面所显示的记录。例如,如果一个页面初始化时每页只显示10个问题,则到数据的请求只会限制为10个结果,并且会设置偏移来在页面中进行匹配。

修改question/list动作

在前面的练习中,我们看到了问题模块的显示动作:

public function executeList ()

{

  $this->questions = QuestionPeer::doSelect(new Criteria());

}

我们将会修改这个动作来向模板传递一个sfPropelPager而不是传递一个数组。同时,我们会依据感兴趣的数量来对问题进行排序:

public function executeList ()

{

  $pager = new sfPropelPager('Question', 2);

  $c = new Criteria();

  $c->addDescendingOrderByColumn(QuestionPeer::INTERESTED_USERS);

  $pager->setCriteria($c);

  $pager->setPage($this->getRequestParameter('page', 1));

  $pager->setPeerMethod('doSelectJoinUser');

  $pager->init();

 

  $this->question_pager = $pager;

}

sfPropelPager对象的初始化指明了他包含哪个对象类,以及在一个页面中可以放置的对象的最大数目(在这个例子中为2)。->setPage()方法使用一个请求参数来设置当前页面。例如,如果这个页面参数的值为2,sfPropelPager将会返回3到5的结果。页面请求参数的值变为1,则页面默认会返回1到2的结果。我们可以在Symfony一书的页面章节中了解到关于sfPropelPager对象及其方法的更多信息。

使用一个默认参数

将常量放在我们所使用的配置文件中是一个好主意。例如,每页的结果(在这个例子为2)可以由一个在我们自定义的程序配置中的参数来代替。用下面的代码来改变上面的sfPropelPager行:

..

  $pager = new sfPropelPager('Question', sfConfig::get('app_pager_homepage_max'));

这里的pager关键字是作为名字空间使用的,这也就是为什么在参数名字中出现的原因。我们可以在Symfony一书的配置一节中查看到更多的关于自定义配置与命名自定义参数规则的更多的内容。

修改listSuccess.php模板

在listSuccess.php模板中,将下面的代码行:

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

替换为

<?php foreach($question_pager->getResults() as $question): ?>

从而页面显示存储在页面中的结果列表。

添加页面浏览

在这个模板中还需要做另外一件事:页面浏览。现在,模板所做的只是显示前两个问题,但是我们应添加到下一个页面的功能,以及回到前一个页面的功能。要完成添加这些功能,我们需要在模板后面添加下面的代码:

<div id="question_pager">

<?php if ($question_pager->haveToPaginate()): ?>

  <?php echo link_to('«', 'question/list?page=1') ?>

  <?php echo link_to('<', 'question/list?page='.$question_pager->getPreviousPage()) ?>

 

  <?php foreach ($question_pager->getLinks() as $page): ?>

    <?php echo link_to_unless($page == $question_pager->getPage(), $page, 'question/list?page='.$page) ?>

    <?php echo ($page != $question_pager->getCurrentMaxLink()) ? '-' : '' ?>

  <?php endforeach; ?>

 

  <?php echo link_to('>', 'question/list?page='.$question_pager->getNextPage()) ?>

  <?php echo link_to('»', 'question/list?page='.$question_pager->getLastPage()) ?>

<?php endif; ?>

</div>

这段代码利用了sfPropelPager对象的各种方法,以及->haveToPaginate(),这个函数只有在请求的结果数目超过了页面尺寸时才会返回真;而->getPreviousPage(),->getNextPage(),->getLastPage()都具有明显示的意义;->getLinks()函数提供了一个页面号的数组;而->getCurrentMaxLink()函数返回最后的页面号。

这个例子同时显示了一个Symfony链接帮助器:link_to_unless()会在作为第一个参数的测试为假的情况下输出一个常规link_to(),否则会输出一个非链接的文本,并使用简单的<span>包装。

我们测试这个页面了吗?我们应进行测试。直到我们用我们自己的眼睛来验证,这个修改才算结束。要进行测试,打开在第三天所创建的测试数据文件,并且为要显示的页面浏览添加一些问题。重新运行导入数据批处理文件,然后再一次请求主页。

为子页添加路由规则

默认情况下,页面规则如下:

http://askeet/frontend_dev.php/question/list/page/XX

现在我们利用路由规则使用这些页面更易于理解:

http://askeet/frontend_dev.php/index/XX

打开apps/frontend/config/routing.yml文件并且在顶部添加下面内容:

popular_questions: 

  url:   /index/:page 

  param: { module: question, action: list }

并且为登陆页面添加另外的路由规则:

login: 

  url:   /login 

  param: { module: user, action: login }

重构

模型

question/list动作执行与模型相关的代码,这也就是我们为什么要将这些代码移动到模块中的原因。用下面的代码来代替question/list动作:

public function executeList () 

{ 

  $this->question_pager = QuestionPeer::getHomepagePager($this->getRequestParameter('page', 1)); 

}

并且在lib/model中的QuestionPeer.php类中添加下面的方法:

public static function getHomepagePager($page)

{

  $pager = new sfPropelPager('Question', sfConfig::get('app_pager_homepage_max'));

  $c = new Criteria();

  $c->addDescendingOrderByColumn(self::INTERESTED_USERS);

  $pager->setCriteria($c);

  $pager->setPage($page);

  $pager->setPeerMethod('doSelectJoinUser');

  $pager->init();

 

  return $pager;

}

同样的想法也适用于我们昨天编写的question/show动作:Propel对象由其剥离的标题取回问题的用法应属于这个模块。所以用下面的代码来变更question/show动作代码:

public function executeShow()

{

  $this->question = QuestionPeer::getQuestionFromTitle($this->getRequestParameter('stripped_title'));

 

  $this->forward404Unless($this->question);

}

在QuestionPeer.php文件中添加下面的代码:

public static function getQuestionFromTitle($title)

{

  $c = new Criteria();

  $c->add(QuestionPeer::STRIPPED_TITLE, $title);

 

  return self::doSelectOne($c); 

}

模板

在question/templates/listSuccess.php中显示的问题列表在将来的某些地方还会用到。所以我们将显示问题列表的模板代码放在一个_list.php片段中,并且用下面的简单代码来代替listSuccess.php的内容:

<h1>popular question</h1>
<?php echo include_partial('list',array('question_pager'=>$question_pager)) ?>

希望本文所述对大家的symfony框架程序设计有所帮助。

PHP 相关文章推荐
综合图片计数器
Oct 09 PHP
基于在生产环境中使用php性能测试工具xhprof的详解
Jun 03 PHP
php连接Access数据库错误及解决方法
Jun 20 PHP
用Zend Studio+PHPnow+Zend Debugger搭建PHP服务器调试环境步骤
Jan 19 PHP
php中利用explode函数分割字符串到数组
Feb 08 PHP
php中session过期时间设置及session回收机制介绍
May 05 PHP
学习PHP session的传递方式
Jun 15 PHP
基于PHP实现短信验证码接口(容联运通讯)
Sep 06 PHP
PHP实现的权重算法示例【可用于游戏根据权限来随机物品】
Feb 15 PHP
php curl操作API接口类完整示例
May 21 PHP
PHP实现字母数字混合验证码功能
Jul 11 PHP
浅谈laravel aliases别名的原理
Oct 24 PHP
php使用cookie保存登录用户名的方法
Jan 26 #PHP
Symfony页面的基本创建实例详解
Jan 26 #PHP
PHP中使用imagick生成PSD文件缩略图教程
Jan 26 #PHP
PHP中使用imagick实现把PDF转成图片
Jan 26 #PHP
PHP中使用Imagick操作PSD文件实例
Jan 26 #PHP
PHP实现将浏览历史页面网址保存到cookie的方法
Jan 26 #PHP
php ImageMagick windows下安装教程
Jan 26 #PHP
You might like
php中实现可以返回多个值的函数实例
2015/03/21 PHP
WordPress主题中添加文章列表页页码导航的PHP代码实例
2015/12/22 PHP
简单谈谈PHP中strlen 函数
2016/02/27 PHP
thinkPHP连接sqlite3数据库的实现方法(附Thinkphp代码生成器下载)
2016/05/27 PHP
tp5递归 无限级分类详解
2019/10/18 PHP
JS模拟的QQ面板上的多级可展开的菜单
2009/10/10 Javascript
javascript 获取url参数和script标签中获取url参数函数代码
2010/01/22 Javascript
jquery获取css中的选择器(实例讲解)
2013/12/02 Javascript
Javascript中3种实现继承的方法和代码实例
2014/08/12 Javascript
面向切面编程(AOP)的理解
2015/05/01 Javascript
JS实现焦点图轮播效果的方法详解
2016/12/19 Javascript
使用JavaScriptCore实现OC和JS交互详解
2017/03/28 Javascript
详谈AngularJs 控制器、数据绑定、作用域
2017/07/09 Javascript
20170918 前端开发周报之JS前端开发必看
2017/09/18 Javascript
微信小程序之页面跳转和参数传递的实现
2017/09/29 Javascript
深入Node TCP模块的理解
2019/03/13 Javascript
浅谈Vue页面级缓存解决方案feb-alive(上)
2019/04/14 Javascript
[02:02]2018DOTA2亚洲邀请赛Mineski赛前采访
2018/04/04 DOTA
[01:12:40]DOTA2-DPC中国联赛 正赛 DLG vs XG BO3 第三场 1月25日
2021/03/11 DOTA
python的urllib模块显示下载进度示例
2014/01/17 Python
Scrapy的简单使用教程
2017/10/24 Python
Python实现的朴素贝叶斯分类器示例
2018/01/06 Python
详解Python循环作用域与闭包
2019/03/21 Python
HTML5新特性之语义化标签
2017/10/31 HTML / CSS
YSL圣罗兰美妆英国官网:Yves Saint Laurent Beauty UK
2019/08/03 全球购物
您在慕尼黑的跑步商店:Lauf-bar
2019/10/11 全球购物
技校学生个人职业生涯规划范文
2014/03/03 职场文书
办公室副主任职责范本
2014/03/08 职场文书
安全生产承诺书范文
2014/05/22 职场文书
2014年社区工会工作总结
2014/12/18 职场文书
研讨会通知
2015/04/27 职场文书
幼儿园门卫安全责任书
2015/05/08 职场文书
2016年3月份红领巾广播稿
2015/12/21 职场文书
代码解析React中setState同步和异步问题
2021/06/03 Javascript
postman中form-data、x-www-form-urlencoded、raw、binary的区别介绍
2022/01/18 HTML / CSS
PostgreSQL 插入INSERT、删除DELETE、更新UPDATE、事务transaction
2022/04/12 PostgreSQL