zoukankan      html  css  js  c++  java
  • 第三天-深入MVC体系结构

     在第二天的学习中,我们了解了如何基于一个关系数据模型构建一个对象模型,并且为这些对象中的一个对象生成了一个程序框架。顺便说一下,在前一天所生成的程序代码可以在askeet的SVN仓库得到:

    http://svn.askeet.com/

    我们在第三天的目标是要为这个网站定义一个漂亮的结构布局,将问题列表作为默认主页,显示对一个问题感兴趣的用户数量,以及为了进行数据测试由样本文本文件移居数据库。所需要做的内容并不是很多,但是有许多内容需要阅读和理解。

    为了阅读这一节教程,我们需要熟悉Symfony一书中所解释的工程,程序,模块以及动作等概念。

    MVC模型

    今天是我们第一次进入MVC体系结构的世界。这意味着什么呢?简单来说就是生成一个页面的代码依据其特性位于不同的文件中。

    如果代码独立于页面专注于数据的操作,他应位于Model中(大多数情况下在askeet/lib/model/目录中)。如果他专注于最终的显示,他应位于View中。在Symfony中,视图层依据于模型(例如askeet/apps/frontend/modules/question/templates/中)以及配置文件。最后,将所有这些联系在一起,并将网站逻辑转换为PHP代码的程序代码位于Controller,而在Symfony中,一个指定页面的控制器被称之为动作。我们可以在Symfony一书的MVC implementation in symfony一章中了解更多关于这个模型的内容。

    然而今天我们的程序只是做一些小的修改,我们将会操作许多不同的文件。不要感到麻烦,因为文件的组织与不同层的代码的分离很快就会变得明显并且有用。

    改变布局

    在设计模式中,一个动作所调用的模板内容集成到一个全局的模板或是布局中。换句话说,布局包含所有接口的不变部分,他修饰动作的结果。打开默认的布局(askeet/apps/frontend/templates/layout.php),将其改为下面的内容:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
     
    <?php echo include_http_metas() ?>
    <?php echo include_metas() ?>
     
    <?php echo include_title() ?>
     
    <link rel="shortcut icon" href="/favicon.ico" />
     
    </head>
    <body>
     
      <div id="header">
        <ul>
          <li><?php echo link_to('about', '@homepage') ?></li>
        </ul>
        <h1><?php echo link_to(image_tag('askeet_logo.gif', 'alt=askeet'), '@homepage') ?></h1>
      </div>
     
      <div id="content">
        <div id="content_main">
          <?php echo $sf_data->getRaw('sf_content') ?>
          <div class="verticalalign"></div>
        </div>
     
        <div id="content_bar">
          <!-- Nothing for the moment -->
          <div class="verticalalign"></div>
        </div>
      </div>
     
    </body>
    </html>

    我们试着尽可能保持标记的可理解,并且将所有的样式移到CSS样式表中。我们并不会在这里描述样式文件,因为CSS语法并不是这个教程的目的。我们可以在SVN仓库中下载这些样式文件。

    我们创建了两个样式文件(main.css,layout.css)。将他们拷贝到我们的askeet/web/css/目录下,并且编辑我们的frontend/config/view.yml文件来改变自动载入的样式文件:

    stylesheets:    [main, layout]

    此时的布局仍是轻量级的,我们将会在后来重新构建。模板中重要的一点就是<head>部分,这是自动生在的,而sf_content变量包含动作的结果。

    我们可以通过请求主页来检测修改是否可以正确显示--这一次是在开发环境中:
    http://askeet/frontend_dev.php/

    关于环境的说明

    如果我们想要了解http://askeet/frontend_dev.php/与http://askeet/之间的区别,那么我们需要看一下Symfony一书的配置一节。现在我们只需要知道他们指向同一个程序,但是却在不同的环境中。一个环境是一个唯一的配置,此时的框架的特性可以依据需要来决是否激活。

    在这种情况下,/frontend_dev.php/ URL指向开发环境,此时在每一次请求时都会解析整个配置,HTML缓存未激活,而调试工具都可用(包括窗口右上角的半透明的工具条)。而 / URL--等同于/index.php/--指向生产环境,此时配置是"编译"的,而为了加速页面的传输禁用了调试工具条。

    这两个PHP脚本--frontend_dev.php与index.php--称之为前端控制器,所有到程序的请求都是由他们来处理的。我们可以在askeet/web/目录下找到他们。事实上,index.php应命名为frontend_dev.php,但是因为frontend是我们创建的第一个程序,Symfony推测我们也许希望其作为默认程序,所以将其重命名为index.php,从而我们可以在仅请求/来在生产环境中查看我们的程序。如果我们希望了解更多的关于通常的MVC模型中前端控制器与控制器层,我们可以查看Symfony一书的控制器一章。

    一个良好的规则就是我们在开发环境中进行浏览,直到对所有的特性感到满意,然后切换到生产环境来检测其速度与良好的URL。

    记住,当我们添加新类或是改变配置文件后,要在生产环境中查看结果,应要先清除缓存。

    重新定义主页

    现在,当我们请求新网站的主页时,他显示一个'恭喜'页面。一个更好的主意是显示问题列表。要这样做,打开frontend程序的routing配置文件(askeet/apps/frontend/config/routing.yml),定位到homepage:部分,将其改变:

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

    在开发环境中刷新页面(http://askeet/frontend_dev.php),现在就会显示问题列表了。

    如果我们是一个很好奇的人,我们也许会查找包含’恭喜'信息的页面。而我们会很奇怪并不会在askeet目录下找到这个文件。事实上,默认的default/index动作的模板是在Symfony的数据目录中定义的,而且是独立于工程的。如是我们希望覆盖他,我们可以在我们自己的目录中创建一个默认的模块。

    路由系统所提供的功能我们将会在后面详细讨论,但是如果我们感兴趣,我们可以阅读Symfony一书的rounting一章。

    定义测试数据

    主页的列表显示仍然显得很空,除非我们添加我们自己的问题。当我们开发一个程序时,有一些测试数据是一个好主意。手动输入测试数据是相当痛苦的,这也就是为什么Symfony可以使用文件来移居数据库的原因。

    我们将在askeet/data/fixtures/目录下创建一个测试数据文件(这个目录需要创建)。使用下面的内容来创建一个名为test_data.yml的文件:

    User:
      anonymous:
        nickname:   anonymous
        first_name: Anonymous
        last_name:  Coward

      fabien:
        nickname:   fabpot
        first_name: Fabien
        last_name:  Potencier

      francois:
        nickname:   francoisz
        first_name: François
        last_name:  Zaninotto

    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 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.

      q3:
        title: How can I generate traffic to my blog?
        user_id: francois
        body:  |
          I have a very swell blog that talks
          about my class and mates and pets and favorite movies.

    Interest:
      i1: { user_id: fabien, question_id: q1 }
      i2: { user_id: francois, question_id: q1 }
      i3: { user_id: francois, question_id: q2 }
      i4: { user_id: fabien, question_id: q2 }

    首先,也许我们会认出这里的YAML语法。如果我们还不熟悉Symfony,那么也许我们还不知道在框架中YAML格式是配置文件最喜欢的格式。这并不是唯一的--如果我们还可以使用XML或是.ini文件,可以很容易的添加一个配置处理器允许Symfony读取他们。如果我们有时间和耐心,我们可以读一下Symfony一书中的实际配置一章中的关于YAML和Symfony配置的更多内容。而现在,如果我们并不熟悉YAML语法,那么我们需要立即开始,因为这个教程大量用了YAML语法格式。

    好了,现在回到测试数据文件。他定义了对象实体,并以内部名称来标识。这个标签对于链接相关对象而不必定义id是十分有用的。例如,创建的第一个对象为User类,并且标识为fabien。第一个问题标识为q1。这就很容易通过指定一个相关对象标签来创建一个类的对象:

    Interest:
      i1:
        user_id: fabien
        question_id: q1

    前面所给定的数据文件使用了短YAML语法来描述这些内容。我们可以在Symfony一书的数据文件一章了解到更多的关于数据迁移的内容。

    我们并不需要为created_at与updated_at列定义值,因为在默认情况下Symfony知道如何来填充这些域。

    创建一个批文件来迁移数据

    下一步就是实际的迁移数据库,而我们希望用一个可以在命令进行调用的PHP脚本来完成这些工作--一个批处理。

    批处理框架

    用下面的内容在askeet/batch/目录下创建一个名为load_data.php的文件:

    <?php
     
    define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
    define('SF_APP',         'frontend');
    define('SF_ENVIRONMENT', 'dev');
    define('SF_DEBUG',       true);
     
    require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');
     
    // initialize database manager
    $databaseManager = new sfDatabaseManager();
    $databaseManager->initialize();
     
    ?>

    这个脚本并没有做任何事情,或者说是几乎没有做任何事情:他定义了一个要进行配置的路径,程序以及环境,装入这个配置,并且初始化数据管理器。但是这已经很多了:这就意味着下面所编写的代码将会利用自动装入的类,自动连接到Propel对象以及Symfony根类。

    如果我们已经测试了Symfony的前端控制器(例如askeet/web/index.php),我们就会发现这些代码十分的熟悉。这是因为正如批请求一样,每一个web请求需要访问同样的对象与配置。

    数据导入

    现在已经准备好了批处理的框架,要在使其来完成一些事情了。批处理需要完成:
    1 读取YAML文件
    2 创建Propel对象实例
    3 在所链接的数据库的数据表中创建相关的记录

    这听起来很复杂,但是在Symfony中,由于sfPropelData对象,我们只需要两行代码就可以完成这些工作。在askeet/batch/load_data.php脚本最后的?>前添加下面的代码:
    data = new sfPropelData();
    $data->loadData(sfConfig::get('sf_data_dir').DIRECTORY_SEPARATOR.'fixtures');

    这就完成了。创建了一个sfPropelData对象,并且通知其将指定目录下的所有数据--我们的fixtures目录--装入到databases.yml配置文件中所定义的数据库中。

    这里的DIRECTORY_SEPARATOR常量用来兼容Windows与*nix平台。

    启动批处理

    最后,我们可以检测这些代码是否值得我们这样的论述,在命令行输入下面的命令:

    $ cd /home/sfprojects/askeet/batch
    $ php load_data.php

    通过刷新开发中的主页我们可以检测数据库中的这些修改:
    http://askeet/frontend_dev.php

    数据已成功装入。

    默认情况下,sfPropelData对象会在装入新的数据之前删除我们所有的数据。我们也可以在当前数据之后添加:

    $data = new sfPropelData();
    $data->setDeleteCurrentData(false);
    $data->loadData(sfConfig::get('sf_data_dir').DIRECTORY_SEPARATOR.'fixtures');

    在模块中访问数据

    当请求question模块的list动作时所显示的页面是executeList()(可以在askeet/frontend/modules/question/actions/action.class.php动作文件中找到)方法传递到askeet/apps/frontend/modules/question/templates/listSuccess.php模板中的结果。这是基于Symfony一书中控制器一章中所解释的名字转换。让我们看一下所执行的代码:

    actions.class.php:

    public function executeList ()
    {
      $this->questions = QuestionPeer::doSelect(new Criteria());
    }

    listSuccess.php:

    ...
    <?php foreach ($questions as $question): ?>
    <tr>
        <td><?php echo link_to($question->getId(), 'question/show?id='.$question->getId()) ?></td>
        <td><?php echo $question->getTitle() ?></td>
        <td><?php echo $question->getBody() ?></td>
        <td><?php echo $question->getCreatedAt() ?></td>
        <td><?php echo $question->getUpdatedAt() ?></td>
      </tr>
    <?php endforeach; ?>

    一步一步的,其所做的工作如下:

    1 动作请求满足一个空标准(criteria)的Question表的记录
    2 记录列表放在一个数组中($questions)传递到模板
    3 模板在动作所传递过来的问题中循环
    4 模板显示记录中每一列的值

    ->getId(),->getTitle(),->getBody()等方法是在Symfony的propel-build-model命令调用中生成的,用来获取id,title,body等数据域的值。这些是标准的获取方法,由在域名字前添加get前缀进行格式化,而Propel也提供了标准的设置方法,以set为前缀。Propel文档描述了为每一个类所创建的访问方法。

    而我们也许会感到迷惑的是QuestionPeer::doSelect(new Criteria())调用,他也是一个标准的Propel请求。Propel文档会详细的对其进行解释。

    如果我们并不能完全理解上面所编写的代码也不担心,几天后就会变得清晰了。

    修改question/list模板

    现在数据已经包含了对问题感兴趣的记录,这样就应该很容易的得到对一个问题感兴趣的用户。如是我们看一下askeet/lib/model/om/目录下由Propel所生成的BaseQuestion.php类,我们就会注意到有一个->getInterests()方法。Propel会检测到在Interest数据表定义中的question_id外键,从而推测以一个问题会有一些感兴趣的用户。这样就使其很容易通过修改listSuccess.php(askeet/frontend/modules/question/templates/)模板来显示我们所希望的内容。在这个处理中,我们会移除那些丑陋的表格,并且替换为我们漂亮的div格式:

    <?php use_helper('Text') ?>
     
    <h1>popular questions</h1>
     
    <?php foreach($questions as $question): ?>
      <div class="question">
        <div class="interested_block">
          <div class="interested_mark" id="interested_in_<?php echo $question->getId() ?>">
            <?php echo count($question->getInterests()) ?>
          </div>
        </div>
     
        <h2><?php echo link_to($question->getTitle(), 'question/show?id='.$question->getId()) ?></h2>
     
        <div class="question_body">
          <?php echo truncate_text($question->getBody(), 200) ?>
        </div>
      </div>
    <?php endforeach; ?>

    我们在这里可以看到与原始的listSuccess.php相同的foreach循环。link_to()与truncate_text()函数是由Symfony所提供的模板帮助器。第一创建了一个指向相同模块的另一个动作的超链接,而第二个将问题内容截短为200个字符。link_to()帮助器是自动装入的,但是我们要使用truncate_text()必须声明使用帮助器的Text组。

    现在我们可以刷新主而来测试我们的新模板:
    http://askeet/frontend_dev.php/

    感兴趣的用户数在每一个问题的附近显示。要得到上面的显示,下载main.css样式文件,并将其放置在askeet/web/css/目录中。

    清理

    propel-generate-crud命令会创建一些我们并不会需要的动作与模板。现在是清除他们的时候了。

    在askeet/apps/frontend/modules/question/actions/actions.class.php中要移除的动作:
        * executeIndex
        * executeEdit
        * executeUpdate
        * executeCreate
        * executeDelete

    在askeet/apps/frontend/modules/question/templates/目录中要移除的模板:
    editSuccess.php

    明天见

    今天是我们深入Model-View-Controller世界的伟大的一步:通过操作Propel对象模型的布局,模板,动作与对象,我们访问了一个MVC结构程序中所有层次。如果我们并不能完全理解这些层之间的桥梁也不必担心:这会一点点清晰起来的。

    今天打开许多文件,如果我们想要了解在一个工程中文件是如何组织的,可以查看Symfony一书的文件结构一章。

    明天将会是另一个伟大的一天:我们将会修改视图,设置一个更为复杂的路由规则,修改模块,深入数据操作以及在表之间进行链接。
  • 相关阅读:
    Android异步操作总结
    datatable1.9 与datatable1.10以数据差异
    ftk学习记录(形成全屏幕套件)
    linux处置服务Iptables
    Linux课程---9、安装RPM包(RPM的全称是什么)
    Linux课程---8、Linux启动流程
    Linux课程---7、shell技巧(获取帮助命令)
    Linux课程---6、别名管理和网络配置(Linux命令如何记)
    Linux课程---5、常用文件命令和目录命令(创建文件命令)
    英语发音规则---L字母
  • 原文地址:https://www.cnblogs.com/dyllove98/p/2462013.html
Copyright © 2011-2022 走看看