[原创中文翻译]symfony askeet24:第七天,模型和视图的处理。
October 4, 2007 – 1:06 am[欢迎转载,转载请注名出处http://symfony.net.cn。本文英文版权归symfony官方网站所有]
Prefactoring
开始
So, we are going to add paginated lists with pagination controls similar to the ones in question/templates/_list.php. We don’t like to repeat ourselves, so we will extract the pagination code from this partial into a custom helper. A helper is a PHP function made accessible to the templates (just like the link_to() and format_date() helpers).
我们要加上页数列表,用上分页控制,这有点类似question/templates/_list.php模板。我们不要重复代码,所以提取分页代码到自定义helper。helper是PHP方法,能连接到模板(就像link_to()和format_data() helper)。
Create a GlobalHelper.php in askeet/apps/frontend/lib/helper and add in:
创建GlobalHelper.php在askeet/apps/frontend/lib/helper,加上下面代码:
<?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()).' ';
}
// Pages one by one
$links = array();
foreach ($pager->getLinks() as $page)
{
$links[] = link_to_unless($page == $pager->getPage(), $page, $uri.$page);
}
$navigation .= join(' ', $links);
// Next and last page
if ($pager->getPage() != $pager->getCurrentMaxLink())
{
$navigation .= ' '.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;
}
The pagination navigation helper improves the code we previously wrote: it can use any routing rule, doesn’t display the ‘previous’ links for the first page nor the ‘next’ links for the last page. We also added four new images (first.gif, previous.gif, next.gif and last.gif) to make the links look prettier. Grab them from the askeet SVN repository. You will probaby reuse this helper in the future for your own projects.
pagination navigation helper提高了我们前面写的代码质量:可以使用任何路由规则,第一页不显示“previous”链接,最后一页不显示“next”链接。再增加四个新图片(first.gif、previous.gif、next.gif和last.gif)让分页链接更好看些。图片从askeet SVN里获得。你以后可以把这些用在你自己的项目中。
To use this helper in the question/templates/_list.php fragment, call the helper function as follows:
使用question/templates/_list.php代码片断的helper,调用helper函数如下这样:
[译者,2007年10月17日:在今天校译时,我发现似乎askeet的教材近期被修改过,首先当我看到下面的代码时我就已经怀疑代码是不是被改过了,如果你仔细看过第五天我写得文章,你会注意到<?php use_helpers(’Text’) ?>和<?php use_helper(’Text’) ?>是不一样的。那么看看symfony-project.com上的askeet教材你会发现不同之处。下面我把原来的英文文章加粗,把最近更新的英文文章变成斜体,你就会看到不同了。]
*********************
这是原来的代码和文章: (加粗)
<?php use_helpers(’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>
Notice the addition of the ’s’ in the use_helpers() call at the beginning, since we now need more than one helper. The name Global refers to the GlobalHelper.php file we just created.
注意开始use_helpers()附加的“s”,因为我们现在需要更多的helper。Global提到了我们刚创建GlobalHelper.php文件。
***********************************
***********************************
这是现在symfony-project.com上的代码和文章:(斜体)
<?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>
The name Global refers to the GlobalHelper.php file we just created.
Global提到了我们刚才创建的文件GlobalHelper.php。
***********************************
[译者,2007年10月17日:经过实际调试,现在symfony-project.com上的教材所示可以使用,根据第五天教材分析,如果使用use_helpers()方法,就会报错。]
Check that everything works as before by requesting:
在用前检查所有的:
http://askeet/frontend_dev.php/
List of the recent questions
列出最近的问题
In the question module, create a new action recent:
在问题模块,创建新动作recent:
public function executeRecent()
{
$this->question_pager = QuestionPeer::getRecentPager($this->getRequestParameter('page', 1));
}
That’s as simple as that. We consider that the ability to grab the latest questions should be a method of the QuestionPeer class. The -Peer classes are dedicated to return lists of objects of a given class - this is explained in detail in the model chapter of the symfony book. But the getRecent() class method still has to be created. Open the askeet/lib/model/QuestionPeer.php class and add in:
很简单吧。获取最近的问题是QuestionPeer类的一个方法在起作用。-Peer类专注于返回给出的对象列表——symfony book的model章节有详细介绍。但是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;
}
The creation date descending order criteria will select the latest questions. This method uses self instead of parent because it is a class function, not an object function. The reason why we do a doSelectJoinUser() here instead of a simple doSelect() is because we know that the template will need the details of the question’s author. That would mean a first request for the list of questions, plus one request per question to get the related user. The doSelectJoinUser() method does all that in only one request: when we ask
在数据库里倒序查询就取出了最近的问题。函数调用自己不继承父类,因为这是类方法,不是对象方法。我们为什么在这里用doSelectJoinUser()而不是简单的doSelect()呢,是因为我们知道模板需要用到和问题有关用户的细节信息。这代表着对问题列表的第一个请求,加上对每个问题的请求获得相关用户信息。doSelectJoinUser()方法接受一个请求就做好了所有的事情:当我们发出请求:
$question->getUser();
…there is no request sent to the database. The joinUser allows us to reduce the number of requests from 1 + the number of questions to only 1. The database will thank us for this easy optimization.
… …没有请求发送到数据库。joinUser允许我们降低request数量从1+到question数量1。数据库会喜欢我们这种简单优化。
The Propel documentation will give you all the explanations about this great feature.
Propel文档会告诉你所有关于这个巨大特性的信息。
The template of the list of recent questions will look a lot like the list of questions displayed in the homepage. Create the askeet/apps/frontend/module/question/templates/recentSuccess.php with:
列出最近问题的模板看起来非常类似问题列表首页。创建askeet/apps/frontend/module/question/templates/recentSuccess.php:
<h1>recent questions</h1>
<?php include_partial(’list’, array(’question_pager’ => $question_pager)) ?>
You now understand why we refactored the question list into a fragment during day five. Finally, you need to add a recent_questions rule in the frontend/config/routing.yml configuration file, as exposed during day four:
现在你该明白为什么我们在第五天教材里把重新装饰问题列表写入代码片断了吧。最终,你需要添加recent_question规则在frontend/config/routing.yml定义文件,像第四天的那个:
recent_questions:
url: /recent/:page
param: { module: question, action: recent, page: 1 }
But wait: the question/_list fragment creates links with the routing rule question/list, so using it will not work for the recent questions list. We need to have the routing rule passed as a parameter to the fragment so that it can be reused for various pagers. So change the final line of recentSuccess.php to:
但等等:question/_list代码片断靠question/list路由规则创建链接,所以使用它无法让最近问题列表工作。我们需要有路由规则把参数传递到代码片断,那么许多页都可以重复使用了。把recentSuccess.php的最后几行改成:
<?php include_partial('list', array('question_pager' => $question_pager, 'rule' => 'question/recent')) ?>
and also change the final lines of the _list.php fragment to:
_list.php也需要在最后几行改一下:
<div id="question_pager">
<?php echo pager_navigation($question_pager, $rule) ?>
</div>
Don’t forget to also add the rule parameter in the call to the _list fragment in modules/question/templates/listSuccess.php.
别忘了把_list代码片断的规则参数写到module/question/templates/listSuccess.php。
<h1>popular questions</h1>
<?php echo include_partial(’list’, array(’question_pager’ => $question_pager, ‘rule’ => ‘question/list’)) ?>
Clear the cache (the configuration was modified), and that’s it.
清除缓存(配置文件被修改了),这就是了。
To display the list of latest questions, type in your browser URL bar:
为了显示最近的问题,在浏览器输入URL:
List of the recent answers
列出最近的answer
It is pretty much the same thing as above, so we will be quite straightforward on this one:
和上面基本一样,所以我们会很快完成:
Create an answer module:
创建答案模块:
$ symfony init-module frontend answer
Create a new action recent:
创建新recent动作:
public function executeRecent()
{
$this->answer_pager = AnswerPeer::getRecentPager($this->getRequestParameter('page', 1));
}
Extend the AnswerPeer class:
扩展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;
}
Create a new recentSuccess.php template:
创建新recentSuccess.php模板:
<?php use_helpers('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>
Test it in your browser:
在浏览器测试:
You are getting used to it, aren’t you?
很习惯了,是不是?
Those who paid attention to the day 4 probably recognized the chunk of code used to show the details of an answer. Since this code is used in at least two places, we will refactor it and create an _answer.php partial, to be used both in question/show and answer/recent. Details are to be found in the askeet SVN repository.
仔细回忆第四天教程,认出来这些是用来显示答案细节的垃圾代码了么?因为代码至少在两个地方使用了,我们会重新修饰并创建_answer.php模板片断,即在question/show又在answer/recent中使用。文件都可以在askeet SVN中找到。
User profile
用户资料
The user name in a an answer will link to a user/show action yet to be written. This will be the user profile, and it will display the latest questions and answers contributed, as well as a few details about the user.
答案的作者名字有一个链接到已经写好的user/show动作。这将是用户profile,列出最近的问题和答案,当然也有一点用户信息。
The first thing to do is to create the action:
第一件事是创建动作:
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();
}
The ->getInterestsJoinQuestion() and ->getAnswersJoinQuestion() methods are native methods of the User class. You can inspect the askeet/lib/model/om/BaseUser.php class to see how they work.
->getInterestsJoinQueston()和->getAnswerJoinQuestion()方法是User类的方法。你可以检查askeet/lib/model/om/BaseUser.php类看看怎么工作的。
The askeet/apps/frontend/modules/user/templates/showSuccess.php template should not give you any problem:
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>
Of course, you could wish to limit the number of result returned by each of the ->getInterestsJoinQuestion(), ->getAnswersJoinQuestion() and getQuestion() methods of the User object, as well as the sorting order. It is simply done by overriding these methods in the askeet/lib/model/User.php class file, and we won’t disclose here how to do it - but today’s release will include it.
当然,你会希望限制每一个User对象的->getInterestsJoinQuestion(),->getAnswersJoinQuestion()和getQuestion()方法返回的结果数量,和排序显示一样。askeet/lib/model/User.php类文件会被覆盖来实现,我们在这里不讲解怎么做——但是今天的发行版本会包含这个。
It is time for the final test. Let’s see what the first user did:
最后该做测试了。看看第一个用户都干了什么:
[后面的ID你根据数据库里用户前面的id自己换就是了。]
Now we can also link to a user profile from a question. Add the following line to question/templates/showSuccess.php and question/templates/_list.php at the beginning of the question_body div:
现在我们从一个问题链接到用户资料了。给question/templates/showSuccess.php和question/templates/_list.php开头question_body层加上几行代码:
<div>asked by <?php echo link_to($question->getUser(), ‘user/show?id=’.$question->getUser()->getId()) ?> on <?php echo format_date($question->getCreatedAt(), ‘f’) ?></div>
Don’t forget to declare the use of the Date helper in _list.php.
别忘了声明Data helper在_list.php。
Add a navigation bar
加导航条
We will change the global layout to add a lateral bar. This bar will contain dynamic content, but as we want to settle its position in the layout, it can’t be part of each template. In addition, putting the code of the bar in the template would mean repeating it a lot, and you know we don’t like to do that.
修改全局布局加一个侧边条。这个条包含动态内容,但是正如我们想定位它在布局的位置,所以我们不会把这个变成任何模板的一部分。附加一条,把这类代码写到模板里意味着要重复写很多代码,你知道我们不想这么干。
That’s why the bar will be a component. A component is the result of an action (i.e. the HTML code resulting from the template execution) made available in a variable. The view chapter of the symfony book explains what a component is, and the differences between a component and a fragment.
这类代码是一个组件。组件是动作的结果(也就是说,HTML代码是模板执行后的结果)。symfony book的view章节解释了什么是组件,组件和代码片断的区别。
Add the component in the layout
给布局增加组件
Open the global layout (askeet/apps/frontend/templates/layout.php). Do you remember this part of code:
打开全局布局(askeet/apps/frontend/templates/layout.php)。你还记得这部分代码吗:
<div id="content_bar">
<!-- Nothing for the moment -->
<div class="verticalalign"></div>
</div>
Replace the comment by
把注释那行换成换成
<?php include_component_slot('sidebar') ?>
And that’s it.
好了。
Define what action goes into the component
定义动作到组件
We decided to use something a little more powerful than a simple component: a component slot. It is a component whose action can be modified according to the caller action - allowing contextual content. It’s the view configuration (written in a view.yml file) that defines which action corresponds to a component slot:
我们决定用点更有力的组件来代替简单的组件:这就是组件槽。根据caller action来决定这个元件中谁的动作可以被修改。这是view配置文件(写入了view.yml文件),对应的动作定义在组件槽里:
default:
components:
sidebar: [sidebar, default]
In this example, the component slot named sidebar is declared to be the result of the default action of the sidebar module.
如上例,组件槽命名为sidebar,在sidebar模块的default动作中被声明为结果。
The view configuration can be defined for the whole application (in the askeet/apps/frontend/config/ directory) or specifically for a module (in a askeet/apps/frontend/modules/mymodule/config/ directory). For our case, we will define it for the whole application, and override it when necessary to provide context-specific links in the sidebar.
view配置文件可以定义整个程序(askeet/apps/frontend/config/下),或者详细得针对一个模块(在askeet/apps/frontend/module/mymodule/config/下)。例如,我们会定义整个程序,必要时在sidebar重写提供context-specific链接。
So open the askeet/apps/frontend/config/view.yml and add in the component slot configuration shown above. You will find more information about the view configuration in the related chapter of the symfony book.
那么打开askeet/apps/frontend/config/view.yml并且增加上面的组件槽配置文件。在symfony book的view章节有更多信息。
Write the sidebar/default action and template
写sidebar/default的动作和模板
First, we will let symfony initialize the new sidebar module:
首先,我们需要让symfony初始化新的sidebar模块:
$ symfony init-module frontend sidebar
Next, we need to write a default component. In the askeet/apps/frontend/modules/sidebar/actions/ directory, rename actions.class.php into components.class.php, and change its content by:
接下来,我们写个默认组件。在askeet/apps/frontend/modules/sidebar/actions/下,重命名actions.class.php为components.class.php,改变内容为:
<?php
//
class sidebarComponents extends sfComponents
{
public function executeDefault()
{
}
}
A component view is a template, just like for an action. The difference is in the naming: A component view is named like a fragment (starting with _) rather than like a regular template (ending with Success). So create a askeet/apps/frontend/modules/sidebar/templates/_default.php fragment (and erase the indexSuccess.php that will not be used) with the following content:
组件视图是模板,就像一个动作。不同点在于命名:组件视图的命名像个代码片断(以_开头),更像规则模板(以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>
If you try to navigate in any page of your askeet website now, you might get an error. That’s because you are navigating the site in the production environment, where the configuration is cached and not parsed at each request. We modified the view.yml configuration file, but the actions in the production environment don’t see it. They use the cached version - the one that doesn’t contain the component slot configuration. If you want to see the changes, either clear the cache or navigate in the development environment:
如果想在你的askeet站点访问任何页,你会得到错误信息。因为你在发布模式下访问站点,配置信息被缓存并且无法解析每一个request。我们修改view.yml配置文件,但是发布模式下的动作不认识它。他们使用cache version——不包括组件槽配置文件。如果你想看看变化,要么清除缓存然后再发布模式下查看,要么在开发模式下查看:
$ symfony clear-cache
or
或者
http://askeet/frontend_dev.php/
The navigation bar is correctly displayed on every page
这个导航条正确显示在了每一个页面上。
This is a general effect of the production environment configuration. So you need to remember to use the development environment during the development phase (when you change the configuration a lot), and clear the cache when you navigate in the production environment after each change in the configuration.
发布模式总体上来说会影响配置。所以你需要记得在开发时期使用开发环境(当你改变配置很多时),如果使用发布模式就要记得清除缓存。
A little more view configuration
view配置更多的内容
While we are at it, let’s have a look at the application view.yml configuration file in apps/config/:
在这里,在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]
The metas section contains a configuration for the meta tags of the whole site. The title key also defines the title that is displayed in the title bar of the browser window. This title is very important, because it is the first thing that a user sees of the site if it is found by a search index. It is therefore necessary to change it to something more adapted to the askeet site:
metas段包含可以在整个网站使用的meta标签的配置文件。title定义了title,在浏览器的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
Refresh the current page. If you don’t see any change, that’s because you are in the production environment, and you should clear the cache first, to get the proper window title:
刷新页面。如果你没看到任何改变,因为你在发布环境中,你首先需要清理缓存:
In addition to providing a default title for your project pages, symfony creates a default robots.txt and favicon.ico in the web root directory (askeet/web/). Don’t forget to change them also!
附加给你的项目页提供了一个默认的title,symfony在askeet/web/下创建了一个默认的robots.txt和favicon.ico图标。别忘了去改。
You might need to change the title for each page of your site. You can do that by defining a custom view.yml configuration for each module, but that would only let you give static titles. Alternatively, you can use a dynamic value from an action with the ->setTitle() method, as described in the view configuration chapter:
你应该给每一个页面都改一下title。你可以给每一个模块自定义view.yml配置文件,但这样就是静态title。非此即彼,你可以用->setTitle()方法从动作中获得动态值,就像view章节说的:
$this->getResponse()->setTitle($title);
Look at what we have done
看看我们都干了什么
It is a general tradition to stop and look at what you’ve done when you reach the seventh day. That’s a good opportunity to document a few things, including the current data model and the available actions.
到了第七天,总的来说我们需要停下来看看我们都干了什么。这是整理文档的好时机,包括当前数据模型和可用的动作。
As a matter of fact, you should document your code while you write it, for instance using PHP doc-style comments for each method. The thing with a symfony project is that the names used in the methods or functions often serve as an explanation of their purpose and use. The methods are kept short, and so are very readable. Most of the time, the templates only use foreach and if statements that are pretty self-explanatory. That’s why the code you will find in the askeet SVN repository doesn’t contain much documentation - plus the fact that we’ve already written seven hours worth of explanations about the work we’ve done!
事实上,你可以在写代码时就写好文档,例如用PHP doc。symfony里方法的名字就可以解释他们的功能。方法代码被保持很短,很易读。大部分时间,模板仅用foreach和if结构而且容易读懂。这也是askeet SVN没有包含文档的原因——再加上事实上我们已经写了七个小时了我们在干什么的文字!
Now let’s have a look at the updated entity relationship diagram:
现在看看更新的关系数据库图:
The list of available actions is the following:
下面列出可用的动作:
answer/
recent
question/
list
show
recent
sidebar/
default (component)
user/
show
login
logout
handleErrorLogin
The model also contains the following methods:
模型包含的方法:
Anwser()
getRelevancyUpPercent()
getRelevancyDownPercent()
AnswerPeer::
getRecentPager()
Interest->
save()
Question->
setTitle()
QuestionPeer::
getQuestionFromTitle()
getHomepagePager()
getRecentPager()
Relevancy
save()
User->
__toString()
setPassword()
myUser->
signIn()
signOut()
getSubscriberId()
getSubscriber()
getNickName()
…plus a custom tools class and a custom validator, placed in the askeet/apps/frontend/lib/ directory.
… …附加的自定义类和自定义验证,在askeet/apps/frontend/lib/下。
That’s not bad for seven hours, is it?
做了七个小时还不算坏,是吧?
[译者,2007年10月17日下午4点:建议把symfony-book的view章节好好看看再做本章。组件,代码片断和槽的区别需要好好理解。另外,本章讲解了如何整理程序文档,可供参考!]







2 Responses to “[原创中文翻译]symfony askeet24:第七天,模型和视图的处理。”
When will the work be finished?
By 石凯旭 on Apr 11, 2008
It has already been finished,but we should make it better.
By hluan on Apr 11, 2008