• Symfony第九天--局部改进


     回顾

    在我们第八天的学习中,我们很容易的为askeet添加了AJAX交互。现在程序已经相当有用了,但是还需要大量的小修改。在问题体中应允许丰富的文本,而主键不应出现在URI中。在Symfony中修正这些问题并不难,今天将是我们练习我们所学东西的一个好机会,并且可以检测我们是否已经知道如何操作MVC结构的所有层。

    在question与answer上允许丰富的文本格式

    Markdown

    question与answer现在只允许接受普通文本。要允许基本的格式,粗体,斜体,超链接,图片,等,我们将会使用一个外部库,而不是重新发明轮子。

    如果我们阅读过文本格式的Symfony文档,那么你也许知道我们是Markdown的fans。Markdown是一个文本到HTML的转换工具,以及一个文本格式化语法。例如,与Wiki或是论坛语法相比,Markdown的最大好处就是普通的markdown文本文件仍然具有可读性:

    Test Markdown text
    ------------------

    This is a **very simple** example of [Markdown][1].
    The best thing about markdown is its _auto-escape_ feature for code chunks:

        <a href="http://www.symfony-project.com">link to symfony</a>

    >The `<` and `>` are properly escaped as `&lt;` and `&gt;`,
    >and are not interpreted by any browser

    [1]: http://daringfireball.net/projects/markdown/   "Markdown"

    Markdown的渲染结果如下:

    Test Markdown text

    This is a very simple example of Markdown. The best thing about markdown is its auto-escape feature for code chunks:

     <a href="http://www.symfony-project.com">link to symfony</a>

        The < and > are properly escaped as &lt; and &gt;, and are not interpreted by any browser

    Markdown库

    尽管最初是使用Perl语言编写的,但是现在Markdown已经可以作为一个PHP库来下载了。而这正是我们要使用的。下载markdown.php文件,并将其放置在askeet工程的lib目录下(askeet/lib)。就是这样:如果在我们的文件中首先进行请求,那么现在askeet程序的所有类都可以访问这个库了:

    require_once('markdown.php');

    我们可以在每次要显示消息体时调用Markdown转换器,但是这会给我们的服务器造成沉重的负载。我们会在问题创建时将文本体转换为HTML,并且在Question表中存储内容的HTML版本。也许我们已经熟悉这样了,所以模块扩展并不惊奇。

    扩展模块

    首先在schema.xml中的Question表部分添加一列:

    column name="html_body" type="longvarchar" />

    然后,生成模块并且更新数据库:

    $ symfony propel-build-model
    $ symfony propel-build-sql
    $ symfony propel-insert-sql

    覆盖setBody方法

    当调用Question类的->setBody()方法时,html_body列必须使用文本内容的Markdown版本进行更新。打开askeet/lib/model/Question.php模块文本,并且编写下面的函数:

    public function setBody($v)
    {
      parent::setBody($v);
     
      require_once('markdown.php');
     
      // strip all HTML tags
      $v = htmlentities($v, ENT_QUOTES, 'UTF-8');
     
      $this->setHtmlBody(markdown($v));
    }

    在设置HTML内容之前使用htmlentities()函数可能保护askeet免受跨站点脚本(cross-site-scripting XSS)攻击,因为所有的<script>标记都会转义。

    更新测试数据

    我们会在问题的测试数据中添加一些Markdown格式(askeet/data/fixtures/test_data.yml),来检测转换可以正常工作:

    Question:
      q1:
        title: What shall I do tonight with my girlfriend?
        user_id: fabien
        body:  |
          We shall meet in front of the __Dunkin'Donuts__ before dinner,
          and I haven't the slightest idea of what I can do with her.
          She's not interested in _programming_, _space opera movies_ nor _insects_.
          She's kinda cute, so I __really__ need to find something
          that will keep her to my side for another evening.

      q2:
        title: What can I offer to my step mother?
        user_id: anonymous
        body:  |
          My stepmother has everything a stepmother is usually offered
          (watch, vacuum cleaner, earrings, [del.icio.us](http://del.icio.us) account).
          Her birthday comes next week, I am broke, and I know that
          if I don't offer her something *sweet*, my girlfriend
          won't look at me in the eyes for another month.

    我们现在可以重新装入数据库:

    $ php batch/load_data.php

    修改模板

    question模块的showSuccess.php模板可以进行简单的修改:

    ...
    <div class="question_body">
      <?php echo $question->getHtmlBody() ?>
    </div>
    ...

    list模板片段(_list.php)也会显示内容,但是却是截短的形式:

    <div class="question_body">
      <?php echo truncate_text(strip_tags($question->getHtmlBody()), 200) ?>
    </div>

    现在可以测试所有的修改了:显示我们修改的三个页面,并且观察由测试数据所显示的格式化文本:

    http://askeet/question/list
    http://askeet/recent
    http://askeet/question/show/stripped_title/what-shall-i-do-tonight-with-my-girlfriend

    同样的修改也适用于Answer内容:在模块中创建一个html_body列,覆盖->setBody()方法,question/show所显示的another时使用的是->getHtmlBody()方法,而不是->getBody()方法。因这些代码与上面的完全相同,所以在这里我们不再进行描述,但是我们可以在SVN代码中看到修改。

    隐藏所有的id

    在Symfony中另一个好的习惯就是尽量避免将主键作为参数传递。这是因为我们的主键是自动增加的,这样就为破坏都提供了太多的关于数据库记录的信息。另外,这样显示的URI并没有任何意义,而且也不适用于搜索引擎。

    例如,以用户配置页面为例。现在,他使用用户id作为参数。但是如果我们确保nickname是唯一的,那么他也可以是请求的参数。让我们进行修改。

    更改动作

    编辑user/show动作:

    public function executeShow()
    {
      $this->subscriber = UserPeer::retrieveByNickname($this->getRequestParameter('nickname'));
      $this->forward404Unless($this->subscriber);
     
      $this->interests = $this->subscriber->getInterestsJoinQuestion();
      $this->answers   = $this->subscriber->getAnswersJoinQuestion();
      $this->questions = $this->subscriber->getQuestions();
    }

    更改模块

    在askeet/lib/model/目录下的UserPeer中添加下面的方法:

    public static function retrieveByNickname($nickname)
    {
      $c = new Criteria();
      $c->add(self::NICKNAME, $nickname);
     
      return self::doSelectOne($c);
    }

    更改模板

    现在到用户配置的显示链接必须关注用户的nickname而不是其id。

    在question/showSuccess.php,question/_list.php模板中,将下面的代码:

    <?php echo link_to($question->getUser(), 'user/show?id='.$question->getUserId()) ?>

    替换为:

    <?php echo link_to($question->getUser(), 'user/show?nickname='.$question->getUser()->getNickname()) ?>

    对answer/_answer.php模板也要做同样的修改。

    添加路由规则

    在这个动作的转发配置中添加一条新的规则,从而url模式会显示一个nickname请求参数:

    user_profile:
      url:   /user/:nickname
      param: { module: user, action: show }

    在执行Symfony的clear-cache命令之后,我们要做的最后一件事就是要测试我们的修改。

    转发(routing)

    除了今天的内容之外,我们到现在所编写的许多动作都使用默认的转发规则,从而模块的名字与动作会显示在浏览器的地址栏中。我们已经了解了如何进行修正,所以让我们为所有的动作定义URL模式。编辑askeet/apps/frontend/config/routing.yml:

    # question
    question:
      url:   /question/:stripped_title
      param: { module: question, action: show }

    popular_questions:
      url:   /index/:page
      param: { module: question, action: list, page: 1 }

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

    add_question:
      url:   /add_question
      param: { module: question, action: add }

    # answer
    recent_answers:
      url:   /recent/answers/:page
      param: { module: answer, action: recent, page: 1 }

    # user
    login:
      url:   /login
      param: { module: user, action: login }

    logout:
      url:   /logout
      param: { module: user, action: logout }

    user_profile:
      url:   /user/:nickname
      param: { module: user, action: show }

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

    default_symfony:
      url:   /symfony/:action/*
      param: { module: default }

    default_index:
      url:   /:module
      param: { action: index }

    default:
      url:   /:module/:action/*

    如果我们在生产环境中进行浏览,那么强烈建议在测试这些配置修改之前清除缓存。

    Symfony转发的一个好习惯就是在一个link_to()帮助器中使用规则名,而不是module/action。不仅因为其速度快(转发引擎并不需要分析转发配置来查找合适的规则),还因为他允许我们在规则名之后修改动作。Symfony一书的转发一章进行详细的解释。

    <?php link_to('@user_profile?id='.$user->getId()) ?>
    // is better than
    <?php link_to('user/show?id='.$user->getId()) ?>

    Askeet遵循Symfony的好习惯,所以我们今天所下载的代码在链接帮助器中只有规则名。将所有模板中的action/module替换为@rule以及自定义帮助器并不是一件有趣的事情,所以关于转发的最后一条建议就是:当我们创建动作时编写转发规则,并且从开始就在链接帮助器中使用规则名。

    明天见
  • 相关阅读:
    接口的幂等性原则
    SpringBoot热部署-解决方案
    @Resource 与 @Service注解的区别
    软件概要设计做什么,怎么做
    First Show
    Glide源码解析一,初始化
    android使用giflib加载gif
    android的APT技术
    RxJava的concat操作符
    RxJava基本使用
  • 原文地址:https://www.cnblogs.com/dyllove98/p/2462007.html
Copyright © 2020-2023  润新知