`
isiqi
  • 浏览: 16052756 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

第七天 模型与视图操作

阅读更多
Symfony回顾

现在我们的学习已经过去六天了,也许我们其中的一些人会认为到现在为止程序并不是十分的有用。这是因为一些人是通过可用的页面数量来评价一个程序是否有用的,而他们认为askeet只是显示一个问题列表,显示相关的答案以及处理用户会话。

我们并没有添加大量页面的原因是因为使用Symfony添加页面实在是太容易了。我们需要证明?好的。今天我们将会显示一个最后提问的问题列表,一个最后发表的答案列表,一个对某一个问题感兴趣的用户列表,用户的配置,并且我们会在每一个页面上添加一个浏览栏来访问这些特性。因为这些工作并不够一小时,我们同时会进行视图配置,并且会最终查看一下我们这周所完成的工作。准备好了?让我们开始吧。

重构

现在我们要使用一个与question/templates/_list.php相类似的页码控件添加一个页码列表。我们并不希望重复自己,所以我们要将这些页码代码放在一个自定义的帮助器中。帮助器是模板可以访问的一个PHP函数(就如同link_to()与format_date()帮助器)。

在askeet/apps/frontend/lib/helper中个GlobalHelper.php文件,并且添加下面的代码:

<?php

function pager_navigation($pager, $uri)
{
$navigation = '';

if ($pager->haveToPaginate())
{
$uri .= (preg_match('/\?/', $uri) ? '&' : '?').'page=';

// First and previous page
if ($pager->getPage() != 1)
{
$navigation .= link_to(image_tag('first.gif', 'align=absmiddle'), $uri.'1');
$navigation .= link_to(image_tag('previous.gif', 'align=absmiddle'), $uri.$pager->getPreviousPage()).'&nbsp;';
}

// Pages one by one
$links = array();
foreach ($pager->getLinks() as $page)
{
$links[] = link_to_unless($page == $pager->getPage(), $page, $uri.$page);
}
$navigation .= join('&nbsp;&nbsp;', $links);

// Next and last page
if ($pager->getPage() != $pager->getCurrentMaxLink())
{
$navigation .= '&nbsp;'.link_to(image_tag('next.gif', 'align=absmiddle'), $uri.$pager->getNextPage());
$navigation .= link_to(image_tag('last.gif', 'align=absmiddle'), $uri.$pager->getLastPage());
}

}

return $navigation;
}

这个页面浏览帮助器改进了我们前面所编写的代码:他可以使用任意的路由规则,不会为第一个页面显示'previous'链接,同样也不会为最后一个页面显示'next'链接。我们同时添加了四个图片(first.gif,previous.gif,next.gif,last.gif)来使链接显示更漂亮。我们可以在以后的工程中重用这个帮助器。

要在question/templates/_list.php片段中使用这个帮助器,调用方法如下:

<?php use_helper('Text', 'Global') ?>

<?php foreach($question_pager->getResults() as $question): ?>
<div class="question">
<div class="interested_block">
<?php include_partial('interested_user', array('question' => $question)) ?>
</div>

<h2><?php echo link_to($question->getTitle(), 'question/show?stripped_title='.$question->getStrippedTitle()) ?></h2>

<div class="question_body">
<?php echo truncate_text($question->getBody(), 200) ?>
</div>
</div>
<?php endforeach; ?>

<div id="question_pager">
<?php echo pager_navigation($question_pager, 'question/list') ?>
</div>

名字Global指向我们刚刚创建的GlobalHelper.php文件。

测试我们的工作是否正常:
http://askeet/frontend_dev.php/

最近问题列表

在question模块中,创建下面的动作:

public function executeRecent()
{
$this->question_pager = QuestionPeer::getRecentPager($this->getRequestParameter('page', 1));
}

这就如同以前一样的简单。我们认为获取最近问题的能力应是QuestionPeer类的一个方法。Peer类专注于返回指定类的对象列表,这在Symfony一书的模型一节进行详细的解释。但是getRecent()类方法还需要我们来创建。打开askeet/lib/model/QuestionPeer.php 类添加下面的代码:

public static function getRecentPager($page)
{
$pager = new sfPropelPager('Question', sfConfig::get('app_pager_homepage_max'));
$c = new Criteria();
$c->addDescendingOrderByColumn(self::CREATED_AT);
$pager->setCriteria($c);
$pager->setPage($page);
$pager->setPeerMethod('doSelectJoinUser');
$pager->init();

return $pager;
}

依据问题创建日期降序排列将会选择最近的问题。这个方法使用self而不是parent,是因为这是一个类方法,而不是一个对象方法。在这里我们使用doSelectJoinUser()方法,而不是简单的doSelect()方法,这是因为我们知道模板需要知道问题作者的详细信息。这就意味着首先请求问题列表,然后对于每一个问题请求得到相关的用户。当我们调用:
$question->getUser();
doSelectJoinUser()方法会在一个请求中完成所有的工作。

这里并没有请求发往数据库。joinUser允许我们将请求数量由1+问题数量减少到1。数据库会庆幸这样简单的优化。

Propel文档会为这个特性提供详细的解释。

最近问题列表模板看起来与在主页显示的问题列表十分相似。用下面的代码创建askeet/apps/frontend/module/question/templates/recentSuccess.php文件:

<h1>recent questions</h1>

<?php include_partial('list', array('question_pager' => $question_pager)) ?>

现在我们就可以理解为什么在第五天的学习中将问题列表重构到一个代码片段中。最后,我们需要在frontend/config/routing.yml文件中添加recent_question规则:

recent_questions:
url: /question/recent/:page
param: { module: question, action: recent, page: 1 }

但是需要求等一下:question/_list代码片段使用question/list路由规则来创建链接,所以他并不适用于最近问题列表。我们需要将路由规则作为一个参数传递到代码片段中,从而他可以为多个页面所重用。所以将recentSuccess.php的最后一行代码改为:

<?php include_partial('list', array('question_pager' => $question_pager, 'rule' => 'question/recent')) ?>

并且将_list.php代码片段的最后一行改为:

<div id="question_pager">
<?php echo pager_navigation($question_pager, $rule) ?>
</div>

不要忘记也要在modules/question/templates/listSuccess.php中添加路由参数:

<h1>popular questions</h1>

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

要显示最近的问题列表,我们可以在我们的浏览器地址栏中输入:
http://askeet/question/recent

最近答案列表

这与上面的相类似,所以我们就可以直接操作:

创建answer模块

$ symfony init-module frontend answer

创建recent动作:

public function executeRecent()
{
$this->answer_pager = AnswerPeer::getRecentPager($this->getRequestParameter('page', 1));
}

扩展AnswerPeer类:

public static function getRecentPager($page)
{
$pager = new sfPropelPager('Answer', sfConfig::get('app_pager_homepage_max'));
$c = new Criteria();
$c->addDescendingOrderByColumn(self::CREATED_AT);
$pager->setCriteria($c);
$pager->setPage($page);
$pager->setPeerMethod('doSelectJoinUser');
$pager->init();

return $pager;
}

创建新的recentSuccess.php模板:

<?php use_helper('Date', 'Global') ?>

<h1>recent answers</h1>

<div id="answers">
<?php foreach ($answer_pager->getResults() as $answer): ?>
<div class="answer">
<h2><?php echo link_to($answer->getQuestion()->getTitle(), 'question/show?stripped_title='.$answer->getQuestion()->getStrippedTitle()) ?></h2>
<?php echo count($answer->getRelevancys()) ?> points
posted by <?php echo link_to($answer->getUser(), 'user/show?id='.$answer->getUser()->getId()) ?>
on <?php echo format_date($answer->getCreatedAt(), 'p') ?>
<div>
<?php echo $answer->getBody() ?>
</div>
</div>
<?php endforeach ?>
</div>

<div id="question_pager">
<?php echo pager_navigation($answer_pager, 'answer/recent') ?>
</div>

在浏览器中测试:
http://askeet/answer/recent

现在我们已经习惯其用法了,不是吗?

用户配置

答案中的用户名字将会链接到我们所编写的user/show动作。这将是用户配置,而且他会显示最新的问题与答案,以及用户的详细信息。

所要做的第一件事就是要创建动作:

public function executeShow()
{
$this->subscriber = UserPeer::retrieveByPk($this->getRequestParameter('id', $this->getUser()->getSubscriberId()));
$this->forward404Unless($this->subscriber);

$this->interests = $this->subscriber->getInterestsJoinQuestion();
$this->answers = $this->subscriber->getAnswersJoinQuestion();
$this->questions = $this->subscriber->getQuestions();
}

->getInterestsJoinQuestion()与->getAnswersJoinQuestion()是User类的本地方法。我们可以查看askeet/lib/model/om/BaseUser.php类来查看其工作原理。

askeet/apps/frontend/modules/user/templates/showSuccess.php模块对我们来说应不是问题:

<h1><?php echo $subscriber ?>'s profile</h1>

<h2>Interests</h2>

<ul>
<?php foreach ($interests as $interest): $question = $interest->getQuestion() ?>
<li><?php echo link_to($question->getTitle(), 'question/show?stripped_title='.$question->getStrippedTitle()) ?></li>
<?php endforeach; ?>
</ul>

<h2>Contributions</h2>

<ul>
<?php foreach ($answers as $answer): $question = $answer->getQuestion() ?>
<li>
<?php echo link_to($question->getTitle(), 'question/show?stripped_title='.$question->getStrippedTitle()) ?><br />
<?php echo $answer->getBody() ?>
</li>
<?php endforeach; ?>
</ul>

<h2>Questions</h2>

<ul>
<?php foreach ($questions as $question): ?>
<li><?php echo link_to($question->getTitle(), 'question/show?stripped_title='.$question->getStrippedTitle()) ?></li>
<?php endforeach; ?>
</ul>

当然,我们希望可以限制由User对象的->getInterestsJoinQuestion(),->getAnswersJoinQuestion(),getQuestion()方法所返回的结果数量,以及排序方式。这可以通过覆写askeet/lib/model/User.php类文件中的相应方法来简单的做到,而我们在这里并讨论如何来做,但是今天的发布版本会包含相关的内容。

现在我们可以进行最终的测试了。让我们来看一下第一个用户所做的事情:

http://askeet/user/show/id/1

现在我们也可以由一个问题链接到一个用户配置。在question/templates/showSuccess.php以及question_body div开始处的question/templates/_list.php中添加下面的代码行:

<div>asked by <?php echo link_to($question->getUser(), 'user/show?id='.$question->getUser()->getId()) ?> on <?php echo format_date($question->getCreatedAt(), 'f') ?></div>

不要忘记在_list.php中声明Date帮助器。

添加浏览工具栏

我们将会改变全局的布局来添加一个侧边栏。这个侧边栏将会包含动态的内容,但是我们希望布局中可以设置其位置,他并不是每一个模板的一部分。另外,将侧边栏的代码放在模板中就意味着大量的重复,而正如我们所知的,我们并不会那样来做。

这就是为什么这个侧边栏是一个元素的原因。一个元素是一个动作的结果(例如,由模板执行所产生的HTML代码)。Symfony一书的视图一章解释了元素是什么,以及一个元素与一个片段的区别。

在布局中添加元素

打开全局布局(askeet/apps/frontend/templates/layout.php)。我们还记得这段代码吗?

<div id="content_bar">
<!-- Nothing for the moment -->
<div class="verticalalign"></div>
</div>

将其替换为下面的元素:

<?php include_component_slot('sidebar') ?>

正是如此。

定义进入元素的动作

我们决定使用比一个简单的元素更为强大的东西:元素槽。他是一个元素,但是其动作可以由通过调用者的动作进行修改,允许上下文内容。其视图配置(view.yml)定义了与一个元素槽相对应的动作:

default:
components:
sidebar: [sidebar, default]

在这个例子中,名为sidebar的元素槽声明为sidebar模块的默认动作的结果。

视图配置可以为整个程序进行定义(在askeet/apps/frontend/config目录下)或者是为特定的模块进行定义(在askeet/frontend/modules/mymodule/config/目录下)。对于我们的情况来说,我们会为整个程序进行定义,并且在必须的时候进行覆写来在侧边栏中提供内容相关的链接。

所以打开askeet/apps/frontend/config/view.yml并且添加我们在上面所显示的元素槽。我们会在Symfony一书的相关章节中了解到更多的关于视图配置的内容。

编写sidebar/default动作与模板

首先,我们会让symfony来初始化新的sidebar模块:

$ symfony init-module frontend sidebar

接下来,我们需要编写一个默认的元素。在askeet/apps/frontend/modules/sidebar/actions/目录下,将actions.class.php改为component.class.php,将其内容改为:

<?php

class sidebarComponents extends sfComponents
{
public function executeDefault()
{
}
}

一个元素视图对应一个模板,就如一个动作对应一个模板。所不同的只是名字的区别:一个元素视图的名字与片段相类似(以_开始),而不同于常规的模板(以Success结尾)。所以用下面的内容来创建一个askeet/apps/frontend/modules/sidebar/templates/_default.php片段(删除不会用到的indexSuccess.php):

<?php echo link_to('ask a new question', 'question/add') ?>

<ul>
<li><?php echo link_to('popular questions', 'question/list') ?></li>
<li><?php echo link_to('latest questions', 'question/recent') ?></li>
<li><?php echo link_to('latest answers', 'answer/recent') ?></li>
</ul>

如果现在我们试着在我们的askeet网站的每一个页面中进行浏览,那么我们就会得到一个错误。那是因为我们是在生产环境下浏览网站,而配置会进行缓存,并且在每次请求时并不会进行分析。我们修改了view.yml配置文件,但是生产环境下的动作并不会了解这些。他们会使用缓存的版本-不包含元素槽的配置。如果我们希望看到改变,我们要清除缓存,或者是在生产环境下浏览:

$ symfony clear-cache

或者:

http://askeet/frontend_dev.php/

浏览工具栏就会显示在每一个页面上。

更多的视图配置

现在让我们看一下apps/config/目录下的程序view.yml配置:

default:
http_metas:
content-type: text/html; charset=utf-8

metas:
title: symfony project
robots: index, follow
description: symfony project
keywords: symfony, project
language: en

stylesheets: [main, layout]

javascripts: []

has_layout: on
layout: layout

components:
sidebar: [sidebar, default]

metas部分包含整个网站的meta标记配置。title关键字定义了在标题栏或是浏览器窗口所显示的标题。title是非常重要的,因为如果网站是通过搜索索引查看的,那么标题将是用户看到的第一个内容。所以有必要将其改为更适合askeet网站的标题:

metas:
title: askeet! ask questions, find answers
robots: index, follow
description: askeet!, a symfony project built in 24 hours
keywords: symfony, project, askeet, php5, question, answer
language: en

刷新当前页面。如果我们没有看到任何改变,那是因为我们是在开发环境下,那么我们应清除缓存来得到正确的窗口标题。

查看我们的工作

一个通常的传统是当我们到达第七天时应停下来查看一下我们的工作。这是一个记录当一些事情,包括当前的数据模型与可用的动作的好机会。

到目前为止,我们的工程中可用的动作列表如下:

answer/
recent
question/
list
show
recent
sidebar/
default (component)
user/
show
login
logout
handleErrorLogin

模块同时包含下面的方法列表:

Anwser()
getRelevancyUpPercent()
getRelevancyDownPercent()
AnswerPeer::
getRecentPager()
Interest->
save()
Question->
setTitle()
QuestionPeer::
getQuestionFromTitle()
getHomepagePager()
getRecentPager()
Relevancy
save()
User->
__toString()
setPassword()

myUser->
signIn()
signOut()
getSubscriberId()
getSubscriber()
getNickName()

另外还有一个自定义的工具与一个自定义的验证器,位于askeet/apps/frontend/lib/目录下。

这七个小时显得并不坏,不是吗?

明天见
分享到:
评论

相关推荐

    立体模型的旋转变换 vc源代码 可画三视图

    编程思路:运用点、线、面三表结构,点表x[],y[],z[]存储各顶点坐标, s[]、e[]存储各线段起始及终止点在点表中的位置,m[8][8]中前几位存储线段码,第7位存储该面的线段数,第8位表示可见性。另外加字符响应函数:A...

    QT高级编程

    第7章 用qtconcurrent实现线程处理 7.1 在线程中执行函数 7.2 线程中的过滤和映射 第8章 用qthread实现线程处理 8.1 独立项的处理 8.2 共享项的处理 第9章 创建富文本编辑器 9.1 qtextdocument简介 9.2 创建自定义的...

    QT高级编程(中文完整高清版带书签).rar

    模型/视图委托 5.1 与数据类型相关的编辑器 5.2 与数据类型相关的委托 5.3 与模型相关的委托 第6章 模型/视图中的视图 6.1 QAbstractItemView子类 6.2 与模型相关的可视化视图 第7章 用QtConcurrent实现线程处理 7.1...

    使用UML和Rational Rose建立系统模型

    内容 第一章 综述 §1.1 UML背景 §1.2系统模型设计概要 第二章 统一建模语言UML概述 §2.1 UML简介 §2.1.1 UML的架构 §2.1.2 UML 的模型、视图、图与系统架构建模 §2.1.3标准建模...

    mplus 8 用户手册 Chapter10 多层次混合建模视图示例.pdf

    第 7 章:具有横截面数据视图示例的混合建模 第 8 章:采用纵向数据视图示例的混合建模 第 9 章:具有复杂调查数据视图示例的多层建模 第10章:多层次混合建模视图示例 第11章:缺少数据建模和贝叶斯估计视图...

    UML参考手册-UML入门资料

    第 2 章 模型的性质与目标 2 1 什么是模型 2 2 模型的用途 2 3 模型的层次 2 4 模型内容 2 5 模型说明了什么? 第二部分 基本概念 第 3 章 UML 初览 3 1 UML 视图 3 2 静态视图 3 3 用例视图 3 4 交互...

    ASP.NET的网页代码模型及生命周期

    第4章 ASP.NET的网页代码模型及生命周期 从本章开始,就进入了ASP.NET应用程序开发的世界。在了解了C#的结构,以及面向对象的概念后,就可以从面向对象的思想开发ASP.NET应用程序。在ASP.NET中,能够使用面向对象的...

    UML参考手册.doc

    第 2 章 模型的性质与目标 7 2.1 什么是模型 7 2.2 模型的用途 7 2.3 模型的层次 8 2.4 模型内容 10 2.5 模型说明了什么? 11 第二部分 基本概念 13 第 3 章 UML初览 14 3.1 UML视图 14 3.2 静态视图 15 ...

    Revit Structure 2011 中文用户手册

    第 7 章 打开 Revit 文件 第 8 章 保存 Revit 文件 第 9 章 标高和轴网 第 10 章 项目位置和方向 第 11 章 场地设计 第 12 章 概念设计环境 第 13 章结构建模 第 14 章钢筋 第 15 章墙 第 16 章门 第 17 章窗 第 18 ...

    UML参考手册.PDF

    第 2 章 模型的性质与目标 7 2.1 什么是模型 7 2.2 模型的用途 7 2.3 模型的层次 8 2.4 模型内容 10 2.5 模型说明了什么? 11 第二部分 基本概念 13 第 3 章 UML初览 14 3.1 UML视图 14 3.2 静态视图 15 3.3 用例...

    数据库系统导论(第七版)

    第7章 关系演算 140 7.1 引言 140 7.2 元组演算 141 7.3 举例 147 7.4 关系演算与关系代数的比较 149 7.5 计算能力 152 7.6 域演算 153 7.7 SQL语言 155 7.8 小结 162 练习 163 参考文献和简介 165 部分练习答案 167...

    doodle:Java中的模型-视图-控制器

    第7个按钮将打开JColorChooser,并允许用户选择其他颜色。 滑块用于控制笔划的宽度,最小大小为1,最大大小为10。 当前选择的颜色将显示在此面板的底部。帆布完全实现了工程图,每个笔划都有其自己的属性(颜色和...

    建立物业管理系统PDM模型-数据库系统分析与设计.doc

    报 告 课程名称: 数据库系统分析与设计 实验名称:建立物业管理系统PDM模型 院 (系): 专业班级: 姓 名: 学 号: 指导教师: 一、实验目的和要求 1、了解数据库物理数据模型(PDM)的基本概念(表、列、视图、...

    Django_中文教程.rar

    第七章:表单 第八章: 高级视图和URL配置 第九章:模版高级进阶 第十章: 模型高级进阶 第十一章:通用视图 第十二章: 部署Django 第十三章: 输出非HTML内容 第十四章: 会话、用户和注册 第十五章: ...

    python django建站教程

    第七章:表单 第八章 高级视图和URL配置 第九章:模版高级进阶 第十章 模型高级进阶 第十一章:通用视图 第十二章: 部署Django 第十三章: 输出非HTML内容 第十四章: 会话、用户和注册 第十五章:...

    iPhone开发基础教程PDF(含源代码)

     第7章 标签栏与选取器  第8章 表视图简介  第9章 导航控制器和表视图  第10章 应用程序设置和用户默认设置  第11章 基本数据持久性  第12章 使用Quartz和OpenGL绘图  第13章 轻击、触摸和手势  第14章 我在...

    QT5开发及实例

    本书源程序.rar 第10章Qt5网络与通信.pdf 第11章Qt5事件处理.pdf 第12章Qt5多线程.pdf 第13章Qt5数据库.pdf 第14章Qt5多国语言国际...第7章Qt5图形视图框架.pdf 第8章Qt5模型/视图结构.pdf 第9章Qt5文件及磁盘处理.pdf

    sap R/3 业务蓝图——理解业务过程参考模型2

    2.6.1 参考模型视图??汽车交易 25 2.6.2 组件模型??发生了什么事情 27 2.6.3 组织模型??谁执行这项任务 28 2.6.4 数据模型??需要什么 28 2.6.5 交互模型??公司的各个模型如何交互 31 2.7 小结 32 第3章 按需求...

    asp.mvc3.0中文教程

    七 ASP.NET MVC3 快速入门-第七节 在Movie(电影)模型与数据表中添加一个字段 48 7.1 在我们的Movie模型中添加一个Rating(电影等级)属性 48 7.3 当模型改变时自动重建数据库 51 7.4 修正票价字段的精度 57 八 ...

    领域驱动设计C# 2008实现问题.设计.解决方案

    第7章 提案请求 7.1 问题 7.2 设计 7.2.1 设计领域模型 7.2.2 设计提案请求聚合 7.2.3 定义聚合边界 7.2.4 设计仓储 7.2.5 编写单元测试 7.3 解决方案 7.3.1 提案请求类的私有字段和构造器 7.3.2 ...

Global site tag (gtag.js) - Google Analytics