标签:yii

浏览模式: 标准 | 列表 | 图文
Best MVC Practices

Best MVC Practices

这是一篇来自Yii官方的文章,写在了1.1.6的new fetures里面,值得一看,最起码,你在看完后,下次被人面试的时候,问到什么 是MVC,你可以有回答的东西了。

不多说,上原文,内容也不复杂,所以我就不翻译了(其实是我翻译不来,将就着看看可以,可是要翻译成人人都能看得懂的内容就难了)。。。

原文来自于:http://www.yiiframework.com/doc/guide/1.1/en/basics.best-practices,目前官方也确实没有中文版。

Although Model-View-Controller (MVC) is known by nearly every Web developer, how to properly use MVC in real application development still eludes many people. The central idea behind MVC is code reusability and separation of concerns. In this section, we describe some general guidelines on how to better follow MVC when developing a Yii application.

To better explain these guidelines, we assume a Web application consists of several sub-applications, such as

  • front end: a public-facing website for normal end users;
  • back end: a website that exposes administrative functionality for managing the application. This is usually restricted to administrative staff;
  • console: an application consisting of console commands to be run in a terminal window or as scheduled jobs to support the whole application;
  • Web API: providing interfaces to third parties for integrating with the application.

The sub-applications may be implemented in terms of modules, or as a Yii application that shares some code with other sub-applications.

1. Model

Models represent the underlying data structure of a Web application. Models are often shared among different sub-applications of a Web application. For example, a LoginForm model may be used by both the front end and the back end of an application; a News model may be used by the console commands, Web APIs, and the front/back end of an application. Therefore, models

  • should contain properties to represent specific data;

  • should contain business logic (e.g. validation rules) to ensure the represented data fulfills the design requirement;

  • may contain code for manipulating data. For example, a SearchForm model, besides representing the search input data, may contain a search method to implement the actual search.

Sometimes, following the last rule above may make a model very fat, containing too much code in a single class. It may also make the model hard to maintain if the code it contains serves different purposes. For example, a News model may contain a method named getLatestNews which is only used by the front end; it may also contain a method named getDeletedNews which is only used by the back end. This may be fine for an application of small to medium size. For large applications, the following strategy may be used to make models more maintainable:

  • Define a NewsBase model class which only contains code shared by different sub-applications (e.g. front end, back end);

  • In each sub-application, define a News model by extending from NewsBase. Place all of the code that is specific to the sub-application in this News model.

So, if we were to employ this strategy in our above example, we would add a News model in the front end application that contains only the getLatestNews method, and we would add another News model in the back end application, which contains only the getDeletedNews method.

In general, models should not contain logic that deals directly with end users. More specifically, models

  • should not use $_GET, $_POST, or other similar variables that are directly tied to the end-user request. Remember that a model may be used by a totally different sub-application (e.g. unit test, Web API) that may not use these variables to represent user requests. These variables pertaining to the user request should be handled by the Controller.

  • should avoid embedding HTML or other presentational code. Because presentational code varies according to end user requirements (e.g. front end and back end may show the detail of a news in completely different formats), it is better taken care of by views.

2. View

Views are responsible for presenting models in the format that end users desire. In general, views

  • should mainly contain presentational code, such as HTML, and simple PHP code to traverse, format and render data;

  • should avoid containing code that performs explicit DB queries. Such code is better placed in models.

  • should avoid direct access to $_GET, $_POST, or other similar variables that represent the end user request. This is the controller's job. The view should be focused on the display and layout of the data provided to it by the controller and/or model, but not attempting to access request variables or the database directly.

  • may access properties and methods of controllers and models directly. However, this should be done only for the purpose of presentation.

Views can be reused in different ways:

  • Layout: common presentational areas (e.g. page header, footer) can be put in a layout view.

  • Partial views: use partial views (views that are not decorated by layouts) to reuse fragments of presentational code. For example, we use _form.php partial view to render the model input form that is used in both model creation and updating pages.

  • Widgets: if a lot of logic is needed to present a partial view, the partial view can be turned into a widget whose class file is the best place to contain this logic. For widgets that generate a lot of HTML markup, it is best to use view files specific to the widget to contain the markup.

  • Helper classes: in views we often need some code snippets to do tiny tasks such as formatting data or generating HTML tags. Rather than placing this code directly into the view files, a better approach is to place all of these code snippets in a view helper class. Then, just use the helper class in your view files. Yii provides an example of this approach. Yii has a powerful CHtml helper class that can produce commonly used HTML code. Helper classes may be put in an autoloadable directory so that they can be used without explicit class inclusion.

3. Controller

Controllers are the glue that binds models, views and other components together into a runnable application. Controllers are responsible for dealing directly with end user requests. Therefore, controllers

  • may access $_GET, $_POST and other PHP variables that represent user requests;

  • may create model instances and manage their life cycles. For example, in a typical model update action, the controller may first create the model instance; then populate the model with the user input from $_POST; after saving the model successfully, the controller may redirect the user browser to the model detail page. Note that the actual implementation of saving a model should be located in the model instead of the controller.

  • should avoid containing embedded SQL statements, which are better kept in models.

  • should avoid containing any HTML or any other presentational markup. This is better kept in views.

In a well-designed MVC application, controllers are often very thin, containing probably only a few dozen lines of code; while models are very fat, containing most of the code responsible for representing and manipulating the data. This is because the data structure and business logic represented by models is typically very specific to the particular application, and needs to be heavily customized to meet the specific application requirements; while controller logic often follows a similar pattern across applications and therefore may well be simplified by the underlying framework or the base classes.

Yii:relations update(self::STAT)

Yii:relations update(self::STAT)

今天在群里有朋友问我yii的relations的问题,结果贴出来的代码居然是类似这样的:

function relations(){
return array(
'xx'=>array(self::STAT,'aa','aa_id')
);
}

我当时直接了当的对他说,错了,怎么可能会有self::STAT呢?只有has_many,has_one,belongs_to,many_many,stat是错的。结果他回答说我OUT了,STAT是新加入的。。。

于是欣欣然跑到YII的官网查看了一下GUIDE,果然在 Relational Active Record,一节中发现了这个Statistical Query。

官方怎么说来着?第一句就让我大吃一惊:

Note: Statistical query has been supported since version 1.0.4.

奇怪,以前怎么没注意?

Besides the relational query described above, Yii also supports the so-called statistical query (or aggregational query). It refers to retrieving the aggregational information about the related objects, such as the number of comments for each post, the average rating for each product, etc. Statistical query can only be performed for objects related in HAS_MANY (e.g. a post has many comments) or MANY_MANY (e.g. a post belongs to many categories and a category has many posts).

Performing statistical query is very similar to performing relation query as we described before. We first need to declare the statistical query in the relations() method of CActiveRecord like we do with relational query.

class Post extends CActiveRecord
{
public function relations()
{
return array(
'commentCount'=>array(self::STAT, 'Comment', 'post_id'),
'categoryCount'=>array(self::STAT, 'Category', 'post_category(post_id, category_id)'),
);
}
}

In the above, we declare two statistical queries: commentCount calculates the number of comments belonging to a post, and categoryCount calculates the number of categories that a post belongs to. Note that the relationship between Post and Comment is HAS_MANY, while the relationship between Post and Category is MANY_MANY (with the joining table post_category). As we can see, the declaration is very similar to those relations we described in earlier subsections. The only difference is that the relation type is STAT here.

With the above declaration, we can retrieve the number of comments for a post using the expression $post->commentCount. When we access this property for the first time, a SQL statement will be executed implicitly to retrieve the corresponding result. As we already know, this is the so-called lazy loading approach. We can also use the eager loading approach if we need to determine the comment count for multiple posts:

$posts=Post::model()->with('commentCount', 'categoryCount')->findAll();

The above statement will execute three SQLs to bring back all posts together with their comment counts and category counts. Using the lazy loading approach, we would end up with 2*N+1 SQL queries if there are N posts.

By default, a statistical query will calculate the COUNT expression (and thus the comment count and category count in the above example). We can customize it by specifying additional options when we declare it in relations(). The available options are summarized as below.

  • select: the statistical expression. Defaults to COUNT(*), meaning the count of child objects.

  • defaultValue: the value to be assigned to those records that do not receive a statistical query result. For example, if a post does not have any comments, its commentCount would receive this value. The default value for this option is 0.

  • condition: the WHERE clause. It defaults to empty.

  • params: the parameters to be bound to the generated SQL statement. This should be given as an array of name-value pairs.

  • order: the ORDER BY clause. It defaults to empty.

  • group: the GROUP BY clause. It defaults to empty.

  • having: the HAVING clause. It defaults to empty.

关键的是看最后这几个条件,可以让你知道这些参数如何配置。。。
官方的GUIDE地址:http://www.yiiframework.com/doc/guide/1.1/en/database.arr

Yii的config配置的一些记录

Yii的config配置的一些记录

用YII开发的时候,由于会用到gii之类的工具,所以assets目录,仿佛就一定会需要存在了,但是assets这个目录名,会可能与我们自己的一些image,css之类的目录存在冲突(我是指目录太多了,不易管理,虽然这个目录似乎也不需要管理)
这时候,我们可以通过更改config来设置。
在config的components里加入:

'assetManager'=>array(
'basePath' => 'xxxxxx/xxx'
)

OK,我就这样把它与我存放image/css/js的目录合并在一起了。

模版,如果不想过多的折腾,我想,官方推荐的prado这样的伪PHP模版其实也不错,最起码常用的操作都封装了,也不用担心与其他的模版是否会存在这样那样的冲突,最起码他们的整合应该算是最好的。所以。。。加上:

'viewRenderer' => array(
'class' => 'CPradoViewRenderer',
),

不用过多的担心效率,它也是编译到runtime目录下执行的。。。

params,这个嘛。。。直接引用文件吧,其实在自动生成的代码里,就是这样配置的,直接引用文件有一个好处就是,你可以把系统的配置信息扔到一个文件里,然后让params来加载。就方便很多了。

开发笔记记录

开发笔记记录

在开发的时候会遇到很多问题,比如就象今天,某个项目中用了swfupload,是集成的uploadify(可能打错了)?其他时候都正常,就突然今天出了点问题,因为昨天改版上线,今天被人发现了大的BUG,那就是商品不能上传图片了。这个问题非常严重,因为影响了用户的正常使用,但是我在测试的时候却没有发现任何 问题,照样正常上传,一下子就感觉特郁闷。
最后,客户截图出来,显示security error,然后问了一下,用的是IE。哦。。。我用的是firefox。于是換成IE测试,果然出现这种情况。
找了很多原因,都没有发现问题在哪里,JS啥的都没有出错,原来是正常的,现在是只有FIREFOX正常,这究竟是什么 原因 呢?
排查了很久,而且到最后就差要单独拎出来重写了,意外之中突然发现,URL里有两个斜杠,比如当前的页面可能就是类似 这样:http://www.neatstudio.com//upload/image/?act=upload,在host后面有两个斜杠。难道是这个原因 ?于是把那个斜杠去掉,再测试,结果真的成功了。现在想想,好象很多人都会忽略这个问题,但我就真的发现了,分析了一下源码,发现还真有这可能,因为它源码里是的路径是类似:../js/upload/swfobject.js。在URL里有//的时候,它解析的时候,可能会有偏差(没有仔细看,昨天一夜没睡,发现问题后立马解决了它了),对于路径就进行了处理,比如rtrim()之类的,搞定,开心的回家了。。

然后说一点yii中遇到的问题,比如获取当前controller的ID,就是Yii::app()->controller->id,如果获取当前的action的ID,那就是Yii::app()->controller->action->id。也有人说是$this->actionId,我是没有成功过。。
当然,要记得,在init()方法时在,这些都是获取不到的,毕竟,人家还没有初始化,怎么可能有呢,于是我用的是Yii::app()->getRequest()->getPathInfo(),然后explode一下"/",弹出的最后一个就是action,前面的是controller,看了一下源码,官方获取,好象也是这样获取的。黑黑。。。。

好了,做完记录,睡觉了。。

Yii ClinkPager 郁闷

开发的时候,分页用了CLinkPager,然而在某一个页面的时候,page一直在变,但是下面的分类中的当前页永远在1上,不随着页码的变化而变化。
开始的时候以为是分页程序有问题,仔细对应了一下,发现分页的数据是正确的。
排查了半个多小时,突然想起,会不会$_GET['page']被unset了?
找了一下页面,最后在模版页里居然真的发现了unset($_GET['page']),当时我就傻眼了。
顺便再上一个option的onchange切换函数。很烂,只求先解决问题。。。。

function urlchange(field,value) {
var href= location.href;
var regex = new RegExp(field+"=[\-|0-9]{0,}");
if(href.indexOf(field)!=-1){
location.href = href.replace(regex,field+"="+value);
}else{
location.href = href + "&"+field+"="+value;
}
}

用法就是<select onchange="urlchange('page',this.value)"><option value='1'>第一页</option><option value='2'>第二页</option><option value='10'>第十页</option></select>

YUI PHP Loader Utility

YUI PHP Loader Utility

YUI是一个低调又强大的框架。你不能说它有什么,但它确实就存在着。。。
jQuery是Yii框架自带的一个工具,比如ZII里集成的全是JQuery,但YUI也是有着自己的工具,比如这个YUI PHP Load Ulility

The YUI PHP Loader Utility is a server-side utility that allows you to load specific YUI components and their dependencies into your page via PHP. YUI PHP Loader can operate as a holistic solution by loading all of your necessary YUI components, or it can be used to add one or more components to a page on which some YUI content already exists.

YUI also contains a client-side loader, which provides similar functionality from JavaScript.

YUI PHP Loader adds value in the following ways:

  1. Reliable, sorted loading of dependencies: YUI comprises more than two-dozen components, many of which work together to provide the best possible compromise between compartmentalization and code reuse. Because of this, YUI components often need to load with specific dependencies in a specific order. YUI PHP Loader understands which components depend on one another, and based on this knowledge it ensures that the right resources are loaded in the right order.
  2. Automatic use of rolled-up files. YUI PHP Loader knows about all of the built-in rollup files that ship with YUI — like the yahoo-dom-event.js file that contains the Yahoo Global Object, the Dom Collection, and the Event Utility, three components that are commonly used together. By automatically using rolled-up files when it makes sense to do so, the YUI PHP Loader helps you reduce HTTP requests and thereby keep your page as efficient as possible.

As you think about how you want to load YUI on the page, you may find it useful to refer to this overview of some of the most common loading strategies and their relative merits:

YUI的CSS其实也不错,现在细想想如果不是当年YAHOO那么强势的推PHP,如今的PHP也不会被这么多公司所能够接受吧。google是在强推python,虽然现在有着go语言,但python也正在被越来越多的公司所接受了,比如它的兼容性,它的运行速度。。。我还是比较喜欢的。。。。。。

Yii Rewrite中遇到的困惑

Yii Rewrite中遇到的困惑

用Yii开发的时候,总是会有涉及到rewrite,否则,网址里一堆 index.php?r=xxxx那就真的太长了。

关于rewrite,可以参考官方的guide,不过上面只有针对apache的htaccess配置。如果是nginx,你还可以搜索一下官方的wiki,有介绍nginx的配置的。只是官方上面用的方法是try_file,只针对高版本的。如果是低版本还是得用 if (!e $request_filename)这类的【更多设置请搜索google】我这里不详细说了。

今天在用官方推出的这一种rewrite功能【Parameterizing Hostnames 】的时候,出了点问题,该功能是:

Parameterizing Hostnames

Starting from version 1.0.11, it is also possible to include hostname into the rules for parsing and creating URLs. One may extract part of the hostname to be a GET parameter. For example, the URL http://admin.example.com/en/profile may be parsed into GET parameters user=admin and lang=en. On the other hand, rules with hostname may also be used to create URLs with paratermized hostnames.

In order to use parameterized hostnames, simply declare URL rules with host info, e.g.:

array(
'http://<user:\w+>.example.com/<lang:\w+>/profile' => 'user/profile',
)

The above example says that the first segment in the hostname should be treated as user parameter while the first segment in the path info should be lang parameter. The rule corresponds to the user/profile route.

Note that CUrlManager::showScriptName will not take effect when a URL is being created using a rule with parameterized hostname.

Also note that the rule with parameterized hostname should NOT contain the sub-folder if the application is under a sub-folder of the Web root. For example, if the application is under http://www.example.com/sandbox/blog, then we should still use the same URL rule as described above without the sub-folder sandbox/blog.

我在rules里写上了 "http://user.xxx.com/<_c:\w+>/<_a:\w+>"=>"user/<_c>/<_a>/"

然后发现访问 http://user.xxx.com/aaa/bbb/id/1的时候出现了404错误,找问题死活找不到在哪里。再转过头去看了很多其他的配置,发现都不正常。

然后,我尝试访问默认页,即没有参数的:http://user.xxx.com/default/index,咦,居然正常了。。但问题随之而来,其他链接都不正常,经过不停的测试测试再测试。最终发现,把""user/<_c>/<_a>/" 中最后一个 / 去掉就正常了。

那么,'<_m:\w+>/<_c:\w+>/<_a:\w+>' => '<_m>/<_c>/<_a>/' 和 '<_m:\w+>/<_c:\w+>/<_a:\w+>' => '<_m>/<_c>/<_a>' 的区别是什么呢?

仔细看了一下生成的链接,如果用第二种的时候,如果有多个参数,就转为 m/c/a?a=b&c=d 而如果用第一种就是生成 m/c/a/a/b/c/d 。这时候,系统可能会识别不出我这种用二级域名做rewrite的方式。于是,我去掉了最后一个“/”,一切都正常了。

期间,HM和Horadric.Cube多次对我帮助,告诉我不同的nginx参数的配置。灰常感谢。

Yii activerecord 中 index的用法

Yii activerecord 中 index的用法

很多时间,我们需要查询出来的结果按照指定的列进行索引,所以就会有了下面的代码

$lists = xxx::model('xx')->findAll(array('id'=>array(1,2,3,4,5)));//语句应该是不对的,表达我想in查询
然后
取出结果后
foreach($lists as $list){
$ls[$list->id]=$list;
}
这样有没有简化写法呢?因为我这样之后,发现。。。in查询变成了多条查询。
但是是lazy loading Xxxxx.xxx(relation中的关联)

有的,Yii中有类似的写法,那就是ar中的index(上文我刚说了on)

'index': the name of the column whose values should be used as keys of the array that stores related objects.

于是直接

$lists = xxx::model('xx')->findAll(array('index'=>'id'));

就OK了

Yii Relation记录

Yii Relation记录

Yii的relation中遇到的问题,记录一下。。。

目前我有两个表,表不是我设计的。所以我才特别郁闷。

商品表:字段:p_id,member_id

商铺表:字段:shop_id,member_id

其中p_id,shop_id都是主键,member_id是索引

考虑过这个原因,是最初设计的时候,每一个普通用户都可以发布商品,所以商品表没有记录shop_id。

可能也会考虑过,每一个用户会有多个商铺吧,所以shop表的member_id也不是唯一索引。

然后,我用Yii在做表关联,事实上,在我们改动程序的时候,我们已经把普通用户能够发布商品这个功能去掉了,因此,事实上对我们来说member_id,其实肯定是于shop表中的member_id有对应关系。

于是我在ShopProduct的relations这样写

/**
* @return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'member' => array(self::BELONGS_TO,'ShopMember','member_id','with'=>'extends','condition'=>"member.member_state = '1'"),
'shop' => array(self::BELONGS_TO , 'ShopInfo' , 'member_id','on'=>'t.member_id = shop.member_id' ),
);
}

是的,看上去好象不错,指定了foreignKey为member_id,同时指定了关联条件,即shop.member_id=product.member_id,然而世事总是那样的难以预料。。。。

在Yii的代码中,关于relation的on是这样写的:

'on': the ON clause. The condition specified here will be appended to the joining condition using the AND operator. This option has been available since version 1.0.2.

所以,上述的代码在SQL中显示出来就是 select * from product left outer join shop on (product.member_id = shop.shop_id and product.member_id = shop.member_id)//这个代码是伪代码,只是为了说明问题。

问了Yii群和hightman,他们告诉我解决方法都是将foreignKey字段设为空或者False,然后再指定ON。

于是,问题解决。多谢 Fising 和 HightMan

Yii使用EClientScript遇到的一点小问题

Yii使用EClientScript遇到的一点小问题

eclientscript是hightman开发的一个yii插件,用来对于script和css文件进行合并、压缩
当然在自己的项目中,我也为它加了不少的功能和组件,只是因为这是并非公用的东西,拿出来分享意义也不大。
比如,我定义了一个变量path,定义了几个全局的assetUrl,assetPath,然后可以指定目录生成压缩文件,把项目中关于商城、资讯、用户中心等的JS就可以彻底分开了(JS是无所谓,关键是CSS,因为它是认相对目录的)

好了,开始说我遇到的问题吧,目前我有一个JS,二级联动的JS,它是这样写的。

(function($){
$.fn.xxx = function(){}
})(jQuery);

是的,它没有什么问题,而且也挺不错,这样的写法可以防止项目中的$被污染,而使用$只在这一个包里被使用,单独引用的时候,一点问题没有。然而问题就发生在它被合并到一个文件后,上面的代码就无法执行了。。。后来直接改成

$.fn.xxx = function(){}

好吧,我偷懒了,但,将就点吧,没时间扑在上面。。。招人啊招人啊。。。
本文纯属工作记录,谢谢