Blog

  • 重构“任氏有无轩”——第三天

    今天继续加深书籍详细信息页面的构造。

    在G+上95对我的进展发了一个评论:

    再加上点自动抓取网上共享章节的功能

    对这个要求,我只能说我只能实现一点点。我将在详细信息页面中构造一个显示豆瓣对应书籍的信息的部分。

    另外,我要实现一个功能是在这个页面中编辑书籍tags的功能。

    先看实现的界面:

    detail

    先讲在页面中编辑书籍tags。

    这个其实很简单,在控制器中创建一个动作tagaddAction,用来处理“增加更多TAG”按钮的POST动作:

    public function tagaddAction(Request $req)
    {
        $q=$req->request->all();
        $q['newtags'];
        $id=$q['id'];
        $this->processTagAdd($tags, $id);
        $url=$this->get('router')->generate('book_detail', array('id'=>$id));
        return $this->redirect($url);
    }
    private function processTagAdd($tags, $id)
    {
        $em=$this->getDoctrine()-gt;getEntityManager();
        $existing=$em->getRepository('trrsywxBundle:BookBook')->getTagsByBookId($id);
        $existing=$this->convertTagsToArray($existing);
        $book=$em->getRepository('trrsywxBundle:BookBook')->findOneBy(array('id'=>$id));
        $tags=trim($tags);
        $tagslist=  explode(' ', $tags);
        foreach ($tagslist as $tag)
        {
            if(!in_array($tag, $existing)) //This is a new tag
            {
                $booktaglist=new trrsywxBundleEntityBookTaglist();
                $booktaglist->setId($book);
                $booktaglist->setTag($tag);
                $em->persist($booktaglist);
            }
            $em->flush();
        }
    }

    这里有几个需要讲一讲的地方:

    • 在控制器中生成一个url,然后重定向。这也是一般表单post后所需要进行的最后一步。
    • 在processTagAdd中,需要注意$booktaglist->setId()的参数不是$book.id,而是$book本身。所以,虽然我传递进去的是$id,但是之前不得不用一个Repository函数来找出这本书。
    • 注意一个新的taglist是怎样创建,赋值,持续化的。最后,将所有持续化后的对象一次性flush到数据库中去。
    • 用in_array来判断要新加的tag是否已经存在于原来的taglist里,同时要将taglist对象中的tag提取出来变成一个数组——因为它本来是个对象数组。这个是由一个辅助函数convertTagsToArray完成的。

    =====我是分割线=====

    接下来讲一讲获得豆瓣的信息。

    我用到了Snoopy这个库,关于如何在Symfony 2中加入第三方库的说明,参见这篇文章

    获得豆瓣信息的核心代码如下:

    private function getDoubanRemote($isbn)
    {
            $bad_isbn = 'bad isbn';
            $url = "http://api.douban.com/book/subject/isbn/$isbn?alt=json";
            $s = new SnoopySnoopy();
            $s->agent = 'RSYWX.net - http://www.rsywx.net';
            $s->read_timeout = 1;
            //$s->user = 'taylor.ren@gmail.com';
            //$s->pass = 'xxxxxx';
            $s->fetch($url);
            $res = $s->results;
            if ($res == $bad_isbn) // My isbn is not found in douban
                return false;
            else
                return json_decode($res, true);
    }
    public function getDouban($isbn)
    {
            $ret = $this->createDummyReturn();
            $res = $this->getDoubanRemote($isbn);
            if (!$res) // The above call not successful
            {
                return $ret;
            } else
            {
                if (array_key_exists('summary', $res))
                    $summary = $res['summary']['$t'];
                else
                    $summary = '(豆瓣还没有简介)';
                $ret['summary'] = $summary;
                $ret['alternate'] = $res['link'][1]['@href'];
                if (array_key_exists('db:tag', $res))
                    $tags = $res['db:tag'];
                else
                    $tags[] = '豆瓣没有给出任何TAG';
                $ret['tags'] = array(); //Clear up the dummy tag 'not found';
                foreach ($tags as $t)
                {
                    $ret['tags'][] = $t['@name'];
                }
                $rating = $res['gd:rating']['@average'];
                if ($rating == 0)
                    $rating = '(还没有评分)';
                $ret['rating'] = $rating;
            }
            return $ret;
    }

    其实没有什么技术含量,主要是从豆瓣API返回的数组中提取我用得着的数据而已。

    到此,书籍详细页面已经完成。我接下来要做的是书籍列表部分。这个部分需要用到分页。可耻的是,Symfony 2没有内置分页部件,我需要自己来编写。这个会是一个挑战。

    【本文收录于[go4pro.org]】

  • 重构“任氏有无轩”——第二天

    重构进入第二天——熟悉我风格的人都知道,这不是真正的第二天。

    今天主要进行书籍详细信息页面的创建,以及对书籍封面的处理。

    页面的创建继续使用Bootstrap来完成,用到的元素包括Hero Unit,Table等。我这里不进行详细的展开。

    相较之前的页面,我准备在新的页面中加入书籍的封面。这里的一个问题是:不是所有我收藏的书我都扫描了封面——以后也许会的,但是目前的话,我觉得用一个类似缺省封面的设计是比较好的。

    缺省封面当然不能将每本书的信息都录入,因此需要动态生成。同样的,即使有书籍本身的封面,我也要生成一些版权信息。所幸的事,这段代码大部分已经在我之前的编程中实现,因此本次只是进行修改并配合Symfony 2的框架。

    首先创建一个路由,这个路由只有一个功能,就是将形如:/bookseller/cover/id_title_author.png这样的请求映射给恰当的控制器,并由控制器来完成缺省封面的动态生成,然后返回一个png图形的句柄,再由浏览器进行渲染:

    cover:
        pattern: books/cover/{id}_{title}_{author}.png
        defaults: {_controller: trrsywxBundle:Default:cover}

    然后修改模板文件,引用到<img ...="..." />的地方都要改写:

    <img src="{{path("cover", {'id':book.id, 'author':book.author, 'title':book.title}) }}" alt="{{book.title}}的封面" title="{{book.title}}的封面"/>

    最后,当然是控制器的编写,主要是用到PHP内置的GD库的函数,我这里不展开。

    我将default.jpg和某本书对应的真正封面(如果有)放在一个目录里,如果找到和书籍对应的封面(根据文件名判断),那么显示这个封面并加上版权信息;如果没有对应的封面,那么显示缺省封面,加上书名、作者以及版权信息等。

    以下是效果:

    default cover

    这样做,当然有副作用:每个图片都需要动态调入,会有额外的开销。

    【本文收录于[go4pro.org]】

  • 重构“任氏有无轩”——第一天

    按照惯例,这个“第一天”其实是假的:在我而言,利用零零碎碎的时间完成了“第一天”——即一个比较专注于开发这个项目的人可以在一天,甚至半天时间内完成——的内容。

    对于Symfony 2框架,我就不准备多介绍了。一言以蔽之,这是一个全面而强大的PHP框架。

    这次重构有两个重要的目的:

    首先是熟悉Symfony 2框架(我之前的开发是基于Symfony 1.4版本),其次是学习一些新的东西(比如Bootstrap)。

    闲话不说,开始!

    我的站点根目录在g:/www/rsywx2,然后Symfony框架会安装在Symfony目录下,这个目录也是我们日常工作的目录,以下的命令,如果不特别之处,都是在这个目录下运行。 另外,Apache的配置中,要将DocumentRoot设置到g:/www/rsywx2/Symfony/web下。 我下载的是所谓没有Vendor的文件包,所以解压后需要运行一个命令来安装各种预设的包:

    php binvendors install这个命令需要git的支持。它会解析deps和deps.lock文件,安装各种基本的包。

    安装完毕后,目录结构如下:

    symfony2-tree

    我这里不展开描述各个目录的功能。在以后的编程中会不断提到,大家自然就会不断熟悉。

    此时,我们可以用http://localhost/app_dev.php来浏览一个示范界面。这个界面我们会很快就替换掉的。

    下一步就是开始进行第一个Bundle的创建。Symfony 2中用Bundle来描述一个应用,这个Bundle中包含了Controller,Entity,Repository,Resource,Tests等内容。简单描述如下:

    • Controller:MVC中的C部分,所有的程序逻辑都将在这个目录中的文件里实现。
    • Entity:反应了从PHP类到数据库表格之间的映射关系,并且加入了大量的getter/setter,以及Relationship的描述。
    • Resource:主要是两部分。一部分是所有的View,可以简单的理解为Web页面,Symfony 2推荐使用Twig引擎来描述页面。这个引擎也是由Symfony 2的开发者开发的。另一部分是一些config。如果你像我一样,习惯根据数据库来创建ORM,那么这里会有数据库中各个表的描述——它和Entity不同,在我的用例中,Entity是要根据这个来创建的。
    • Tests:可以用来放置测试文件。

    创建Bundle是向导式的,输入如下命令即可:

    php appconsole generate:bundle

    你需要输入一些Bundle的基本信息,Symfony会为你创建对应的一个Bundle。注意:所有的Bundle都要以Bundle结束,可以加入namespace的分割。

    创建好的一个Bundle结构如上述。

    下来就是设置数据库,修改app/config/parameter.ini文件,设置数据库、数据库类型、用户、密码等信息。当然,在我的用例中,我是先创建好数据库、表格的,所以数据库、表格已经全部存在了,我们需要导入这些信息:

    php appconsole doctrine:mapping:importphp appconsole doctrine:generate:entities

    import的时候需要指定一下格式,可以用yml,xml,php等格式。第一次运行第二个命令时,需要指定一下path,即生成的Entities要放在哪里,一般只要指定path=/src即可。这样,生成的Entity的PHP文件会放在你创建的Bundle的Entity目录下。 至此,数据库建模已经全部完成。

    MVC模块中,M部分是提供数据的。我们当然可以在Controller中直接查询,但是为了更好的复用模块,我们可以创建一个Repository,将所有数据库相关的操作全部放在里面。但是,由于Doctrine本身有自己的Repository,我们必须指定我们自己的Repository来替换某个Entity缺省的Repository:

    //修改srctrrsywxBundleResourcesconfigdoctrineBookReview.orm.xml文件
    //这里需要加以必要的修改以匹配你import进来的文件和你的namespace和Bundle名
    
    <entity name=trrsywxBundleEntityBookReview repository-class=trrsywxBundleRepositoryBookRepository table=book_review>

    重新运行一下doctrine:generate:entities,如果之前没有Repository目录,就会创建这个目录,并在目录下创建一个BookRepository文件。

    再简单讲一下Twig模板引擎。

    Twig很简单。要么用{{…}}表示输出,要么用{%…%}表示控制结构。同时它支持嵌套、继承,甚至可以在模板中嵌入一个控制器。

    最后简单讲一下Bootstrap。它简明、优雅,适合那些程序员来写界面。

    第一天的成果大致如下,请勿见笑:

    symfony2-day1-index

    【本文收录于[go4pro.org]

  • 倪匡《卫斯理》系列

    从5月19日开始,到今天(8月5号)为止,终于完成了倪匡《卫斯理》系列的阅读和整理。

    有兴趣的可以移步我的Wiki专题。 进行这个整理有很多出发点。 一来是断断续续看过很多篇;二来是觉得网络上的版本多有错漏,看了之后觉得骨鲠在喉,说不出的不痛快。

    我不敢说我的整理就是“善本”,但是至少有了改进,希望如果有人愿意,可以继续改进。

    放几张封面给大家看看。

  • 老彼得在加日记(101)(补)(最后一篇)

    这篇将是老彼得在加日记的最后一篇,因为他3号左右就要离开加拿大,离开他短暂生活了100多天的homestay,告别那些小朋友,回到苏州了。

    Welcome Back!

  • 老彼得在加日记(100)

    今天是老彼得在加拿大的100天!!!!撒花!!!庆祝!!!

    祝你安好!

  • 老彼得在加日记(99)

    今天和老彼得的交流集中在“乐高玩具”怎么带回来?

    我当时也没有多想,直接给的建议是要么拆成小段,然后带回来;要么再搞个包/袋子,直接装进去。

    他后来自己想了个比较折中的方法:拆成小块,装个袋子,放在随身行李中。

    我回家后突然想到,其实更好的做法也许是我让他自己提议。

    嗯,这个应该在以后多注意。

    What do you think? What do you have in mind? How do you wanna handle it?

    是我应该多问的问题。

  • 老彼得在加日记(98)

    今天和老彼得通了两次电话。一次是提醒他可以开始打包了;第二次是他打回给我说打包完毕!!!

    他也有担心:一双凉拖可能放不进去了;有没有可能超重?

    我给他的提议是:那些在国内很便宜又很容易搞到的东西,如果到了最后必须舍弃的话,不妨舍弃;但是那些有意义的、能帮助他回忆国外生活的(比如课本、作业等)不妨带回来。

    就是这样。

    祝你安好!

  • 老彼得在加日记(97)

    由于老彼得的iPod没有找到,所以视频暂停。Hoho……

    祝你安好!