攻城记:Thinkphp框架的项目规划总结和踩坑经验
一、项目模块规划
1、项目分为PC端、移动端、和PC管理端,分为对应目录为 /Application/Home,/Application/Mobile,/Application/Admin;
对应入口文件为 index.php, mobile.php,admin.php,入口文件中设定绑定模块;
ThinkPHP配置>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> index.php // 应用入口文件 // 检测PHP环境 if(version_compare(PHP_VERSION,'5.3.0','<')) die('require PHP > 5.3.0 !'); // 开启调试模式 建议开发阶段开启 部署阶段注释或者设为false define('APP_DEBUG',true); // 定义应用目录 define('APP_PATH','./Application/'); // 绑定模块 index.php define('BIND_MODULE','Home'); // 引入ThinkPHP入口文件 require './ThinkPHP/ThinkPHP.php'; // 亲^_^ 后面不需要任何代码了 就是如此简单 //-------------------------------------------------- admin.php // 应用入口文件 // 检测PHP环境 if(version_compare(PHP_VERSION,'5.3.0','<')) die('require PHP > 5.3.0 !'); // 开启调试模式 建议开发阶段开启 部署阶段注释或者设为false define('APP_DEBUG',true); // 定义应用目录 define('APP_PATH','./Application/'); // 绑定模块 define('BIND_MODULE','Admin'); // 引入ThinkPHP入口文件 require './ThinkPHP/ThinkPHP.php'; // 亲^_^ 后面不需要任何代码了 就是如此简单 //-------------------------------------------------- mobile.php // 应用入口文件 // 检测PHP环境 if(version_compare(PHP_VERSION,'5.3.0','<')) die('require PHP > 5.3.0 !'); // 开启调试模式 建议开发阶段开启 部署阶段注释或者设为false define('APP_DEBUG',true); // 定义应用目录 define('APP_PATH','./Application/'); // 绑定模块 define('BIND_MODULE','Mobile'); // 引入ThinkPHP入口文件 require './ThinkPHP/ThinkPHP.php'; // 亲^_^ 后面不需要任何代码了 就是如此简单 //--------------------------------------------------
2、访问的URL为 “域名+项目文件夹名+入口文件+控制器+方法”,如“localhost/myprj/index.php/Index/index”;
3、服务器配置域名绑定到项目文件夹,省略项目文件名,服务器上URL为“www.myprj.com/index.php/Index/index”;
<VirtualHost *:80> ServerName myprj.com RedirectMatch permanent ^/(.*) http://www.myprj.com/$1 </VirtualHost> <VirtualHost *:80> ServerName www.myprj.com DocumentRoot "/usr/local/apache/htdocs/myprj" </VirtualHost>
4、对于三个模块的关系,我规划的是 PC端为父类,移动端和管理端均继承于PC端;
二、配置和目录规划
1、配置文件 /Application/Common/Conf/config.php为公共配置文件,用于配置数据库信息、模板后缀名、自动开启Session、URL模式等全项目公用的配置信息;
2、/Application/Home(或Mobile或Admin)/Conf/config.php为模块配置文件,一般用于配置CSS、JS、图片目录,如下
<?php return array( //'配置项'=>'配置值' 'TMPL_PARSE_STRING'=>array( '__CSS__' => __ROOT__.'/Public/home/css', '__JS__' => __ROOT__.'/Public/home/js', '__IMG__' => __ROOT__.'/Public/home/image', '__PCSS__' => __ROOT__.'/Public/pub/css', '__PJS__' => __ROOT__.'/Public/pub/js', '__PIMG__' => __ROOT__.'/Public/pub/image', ) );
备注1:在CSS中引用图片使用相对路径,如 body { background
: url("../image/bgimage.png") }
备注2:模板在包含文件时要使用<include file="..." />标签,使用<?php include '...'; ?>等原生PHP函数会导致包含文件中的__APP__、__JS__ 等预定义不被渲染;(框架BUG)
备注3:模板在包含公共模板文件时使用<include file="Index/header"/>,对应的公共模板文件路径为 /View/Index/header.php ,此方法不经过控制器,所以不需要定义对应的方法,如果是其它控制器也不需要定义相对应的控制器。
3、/Application/Common/Common/function.php为公共函数文件,用于保存公共函数,如 密码加密函数、表单过滤函数 等,这个文件会被自动调用不需要手动 require;
备注:为移植第三方接口(如微信支付、支付宝支付、OAuth登录)修改工作较少,我把这些第三方DEMO放到了 /Application/Common/Common 目录下,在function.php中编写函数调用相关的接口类和函数。
4、设定模板文件的后缀名为php,因为一些IDE对html后缀的文件不能智能优化显示其中的php代码,比如Dreamweaver和Notepad++。
5、建议配置URL伪静态后缀设为空(默认为html),以免在编程中生成带参数的URL时出现异常的情况。(框架BUG)
6、如果TP3.2.3,作数据库配置兼容处理(设计缺陷?)
//TP3.2.3兼容处理:列名返回时区分大小写,原默认配置是全部为小写 'DB_PARAMS'=>array(\PDO::ATTR_CASE => \PDO::CASE_NATURAL),
三、MVC划分
1、由于项目并不复杂,TP中提供了可不必定义的Model类,而如果定义Model类会在多模块的继承中增加复杂度,所以项目中均无定义Model类;可以看看一些开源项目中,不少Controller的方法只是对Model调用了一个方法然后ajax返回,非常冗余;
2、控制器分为两大类,一类是专门负责模板渲染(assign和display),这里称为模板控制器;另一类是负责数据库操作和处理,这里称为数据控制器;
3、为便于对于模板的统一控制,仅 Index 控制器为模板控制器;由于PC版有用户中心一系列的模板,所以 UserCenter也是模板控制器;
4、原则上所有的数据库操作不允许存在于模板控制器(如 Index控制器)中,应该写在相应对象的数据控制器中;
5、同理原则上模板赋值(assign)和模板渲染(display)不允许存在于数据控制器中
6、Ajax返回写在数据控制器中,对于同时支持被其它控制器和Ajax操作的方法,使用 $isReturn=FALSE 可选参数来决定输出数据还是函数返回数据;
四、编程规范
1、文件、类、方法、函数命名规范参考Thinkphp官方规范
2、HTML/CSS、JS(jQuery)和PHP规范参考 这个链接>>
3、MySQL设计规范参考 这个链接>>
五、Thinkphp框架专用命名规范--团队内部规范
1、类实例化成对象变量的命名
控制器命名的规则是 $+类名首字母小写+字母C(表示控制器),即使只使用其中的一个方法也不要使用类中的方法名作为对象的名称。
控制器命名的规则是 $+类名首字母小写+字母M(表示模型),特别的空模型使用 $m,因为变量应该小写字母开头 。
$usrC = A('Usr'); $productC = A('Product'); $memberM = M('member'); $m = M(); 或者直接使用 M()->方法();
备注:实例化出来的类实例也是变量,变量名称就要以小写字母开头;
2、数据变量的命名
虽然PHP的变量类型有好多,但在数据显示方面,就基本上可以归纳为 字符串族 、一维数组族、多维数组族 这三种。
字符串族:整型、符点型、字符串,这一族可以直接使用 echo 或者类似Smarty的{$key} 等直接输出;
一维数组族:这种一般是查询数据库得出来的只有一行数据(通常需要类似 $userInfo = $userInfoArr[0] 的小处理一下),这种一般是 assign 到模板然后用类似 {$userInfo['name']} 这种方式输出;
多维数组族:这种一般是查询数据库得出来的多行数据,变量命名以Arr为后缀,如 $productArr = $productC->getIndexPro(); ,这种一般在模板用 foreach for 等循环遍历出来;
六、部署:Linux下目录权限设置及大小写BUG
1)缓存目录
项目/Application/Runtime/ 及其内目录设置 777 权限
chmod 777 -R ./Application/Runtime/
如果仍不能正常生成缓存文件,检查是否硬盘已满。若系统为centos7,则要关闭 selinux 防火墙。
2)上传目录
项目/upload/ 设置 777 权限,注意目录如果没有可执行权限会导致 上传时报类似“目录不存在”这样的错误。
chmod 777 ./upload/
上传目录内的所有文件都要设置成不可执行权限,这个似乎Linux没有相关的配置,是在Apache或者.htaccess里面配置成不可执行PHP的,下面是.htaccess方式
#禁止上传目录 upload 的PHP执行权限,包括大小写的PHP后缀 <FilesMatch "(?i:\.php)$"> Deny from all </FilesMatch>
3)项目应用目录
所有的PHP访问应该都应该从入口文件进入,CSS/JS/图片等可以不必经过入口文件。那么就应该屏蔽整个代码项目的文件的直接访问,而不只是TP官方文档所说的只是保护模板文件,所以直接在 项目/Application/ 目录下放置一个 .htaccess 文件,写上下面的内容
#项目目录屏蔽所有没经过入口文件,直接URL访问的 <FilesMatch "(.*)"> Deny from all </FilesMatch>
4)关闭调试模式
把服务器上的index.php、admin.php等入口文件注释掉 define('APP_DEBUG',true); 即关闭调试模式,注意不要再上传到SVN,本地开发仍然使用调试模式。关闭调试模式要在TP的配置文件 项目/Application/Common/Config/config.php 里加上(框架BUG)
'URL_CASE_INSENSITIVE' => FALSE, //调试时是false的//部署时是true会导致Linux下模板渲染文件名全部转换为小写字母而出错!!
5)缓存清理
关闭调试模式后,会生成配置缓存文件。每次更改配置文件都要删除 项目/Application/Runtime/common~runtime.php 文件才能使新配置生效;(文档BUG)
更改配置后页面显示不正常,要清理页面缓存,清空 项目/Application/Runtime/Cache 目录里面的文件;
注意不能把 项目/Application/Runtime 整个目录删除,它不会自动生成,会导致无法生成各种缓存而使程序无法正常执行;(框架BUG)
管理后台可以加入一个“清理缓存”的按钮
//清理缓存 function clearCache(){ $cacheDir = $_SERVER['DOCUMENT_ROOT'].__ROOT__.'/Application/Runtime' ; // var_dump( $cacheDir ); $ok = deldir( $cacheDir ); // var_dump($ok); //增加回缓存目录,并设置权限为777 mkdir( $cacheDir ); chmod( $cacheDir, 0777); echo $ok; } //删除整个目录 function deldir($dir) { //先删除目录下的文件: $dh=opendir($dir); while ($file=readdir($dh)) { if($file!="." && $file!="..") { $fullpath=$dir."/".$file; if(!is_dir($fullpath)) { unlink($fullpath); //var_dump($fullpath); } else { deldir($fullpath); } } } closedir($dh); //删除当前文件夹: if(rmdir($dir)) { return true; } else { return false; } }
七、URL优化和重写
服务器上部署还可以启用TP的“REWRITE模式”,同时apache配置相应的域名对相应的入口文件,如 www.prj.com 到 index.php ,m.prj.com 到 mobile.php ,admin.prj.com 到 admin.php ,URL进一步缩写省去入口文件,如“www.myprj.com/Index/index”。
1)Apache配置,不同的域名设置不同的首页文件
<VirtualHost *:80>
DocumentRoot "D:\wamp\www\batsing"
ServerName www.batsing.com
DirectoryIndex index.php
</VirtualHost>
<VirtualHost *:80>
DocumentRoot "D:\wamp\www\batsing"
ServerName m.batsing.com
DirectoryIndex mobile.php
</VirtualHost>
<VirtualHost *:80>
DocumentRoot "D:\wamp\www\batsing"
ServerName admin.batsing.com
DirectoryIndex admin.php
</VirtualHost>
2) .htaccess文件配置(Apache专用,先开启rewrite_module)
<IfModule mod_rewrite.c> Options +FollowSymlinks RewriteEngine On #设置用户端的重写规则,入口文件index.php,隐藏index.php RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{HTTP_HOST} ^www.batsing.com$ [NC] RewriteRule ^(.*)$ index.php?/$1 [QSA,PT,L] #设置移动端的重写规则,入口文件mobile.php,隐藏mobile.php RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{HTTP_HOST} ^m.batsing.com$ [NC] RewriteRule ^(.*)$ mobile.php?/$1 [QSA,PT,L] #设置管理端的重写规则,入口文件admin.php,隐藏admin.php RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{HTTP_HOST} ^admin.batsing.com$ [NC] RewriteRule ^(.*)$ admin.php?/$1 [QSA,PT,L] #404页重定向,框架外 ErrorDocument 404 /notfound.html #测试,指定浏览器 重定向URL (自动从www重定向到mobile) # RewriteCond %{HTTP_HOST} ^www.batsing.com$ [NC] # RewriteCond %{HTTP_USER_AGENT} "Mobile" [NC] #含Mobile字眼的浏览器(包括微信、UC移动、QQ移动、Safari移动、小米原生) # RewriteRule ^(.*)$ http://m.batsing.com/ [R=301,NC,L] #这一段放到httpd.conf去了,不用每次都读取.htaccess文件
#注意,如果apache 与 PHP 是以fast-cgi的方式运行,
#那么 RewriteRule ^(.*)$ index.php?/$1 [QSA,PT,L] 需要修改为 RewriteRule ^(.*)$ index.php [L,E=PATH_INFO:$1]
#否则会出现 No input file specified. 的错误。
</IfModule>
3) ThinkPHP的项目公共配置文件 /Application/Common/Config/config.php 增加一行开启URL访问模式为 2,默认为模式1
'URL_MODEL' => 2, // URL访问模式,可选参数0、1、2、3
注解:设置URL模式是为了让系统生成的链接(如__APP__,{:U('xxx')} 等)不再包含index.php这一串,即使不修改thinkphp的url模式,也可以通过不带index.php的方式访问网页。
4)本地测试办法,修改hosts文件,配置相关的域名到本地
127.0.0.1 www.batsing.com
127.0.0.1 m.batsing.com
127.0.0.1 admin.batsing.com
1+2.)Nginx的配置URL重写
server { listen 80; server_name www.batsing.com ; root "D:\wamp\www\batsing"; location / { index index.php; #域名对应的首页(对应Apache的DirectoryIndex) #autoindex on; if (!-e $request_filename){ #index.php 缩写,与上面apache的.htaccess的功能一样 rewrite ^/(.*)$ /index.php?/$1; } } location ~ \.php(.*)$ { #开启了pathinfo的fastcgi模式的PHP fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_split_path_info ^((?U).+\.php)(/?.+)$; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; include fastcgi_params; } }
上面配置中的 if 和左括号间要有空格,否则报错无法启动nginx
!注意:开启了pathinfo功能的fastcgi模式的php存在文件类型错误解析漏洞 。注意上传目录和静态资源目录(css/js)的安全
八、服务器环境和本地环境不同配置
服务上关闭调试模式,本地开启调试模式。所以服务器上只会加载 config.php ,而本地还会加载 debug.php并替代config.php中的配置项。总结所写的配置如下:
config.php <?php return array( //'配置项'=>'配置值' 'DB_TYPE'=>'mysqli', // 数据库类型 'DB_HOST'=>'localhost', // 服务器地址 'DB_NAME'=>'dbname', // 数据库名 'DB_USER'=>'dbuser1', // 用户名 'DB_PWD'=>'dbpwd1', // 密码 'DB_PORT'=>3306, // 端口 'DB_PREFIX'=>'', // 数据库表前缀 'DB_CHARSET'=>'utf8', // 数据库字符集 'TMPL_TEMPLATE_SUFFIX' => '.php', //模板后缀名为php 'URL_HTML_SUFFIX' => '', //伪静态success、error、redirect、U()生成的URL后缀为空 'URL_MODEL' => 2, // URL访问模式,可选参数0、1、2、3 'URL_CASE_INSENSITIVE' => FALSE, //调试时是false的//部署时是true会导致Linux下模板渲染文件名全部转换为小写字母而出错!! //TP3.2.3兼容处理:列名返回时区分大小写,原默认配置是全部为小写 'DB_PARAMS'=>array(\PDO::ATTR_CASE => \PDO::CASE_NATURAL), ); debug.php <?php //数据库配置信息 return array( //'配置项'=>'配置值' 'DB_USER'=>'root', // 用户名 'DB_PWD'=>'localdbpwd', // 密码 'URL_MODEL' => 1, // URL访问模式,默认1,本地无配置域名 'SHOW_PAGE_TRACE'=>true, //开启页面Trace );
九、手机浏览器自动从PC版跳转到移动版
参考我的这篇博文 Apache配置必配基础>>
十、一些shell脚本
2)上传代码后设置目录的可读权限;
3)清空缓存目录;
4)所有非上传目录、缓存目录、日志目录 一键加锁不可写,以及一键解锁为可写;
十一、其它一些BUG
1)success、error方法跳转不正常,比如 UserController里面的 login方法成功后跳转到 IndexController里面的 center方法,使用 $this->success('登录成功', 'Index/center'); 是会生成 Index/center ,浏览器就识别跳转到了 /User/Index/center,导致404,而不是 /Index/center 的。解决方法是改成 $this->success('登录成功', U('Index/center'));
原因是这两个方法是直接在提示页面生成 <a href="Index/center">正在跳转</a> 这样的HTML,这样则会使跳转错误。而后面一种则是生成一个完整(但不含域名)的URL。
2)调用方法时页面渲染不符合预期,像下面,本来预期 do() 方法进入if条件调用 doA() 后会渲染 doA 页面,但结果却是渲染 do 页面(没有do页面则报错)。查看源码可以发现它的渲染规则是,渲染的页面默认是跟请求URL所对应的控制器和Action的。
<?php class IndexController { function do(){ if( ... ){ $this->doA(); return; }else{ ... } } function doA(){ ... $this->display(); //渲染 doA 页面 } }
3)引入的CSS和JS不会经过模板渲染。所有在CSS、JS中都无法使用 __APP__、{:U('Ctrl/method')} 等方法。CSS引用图片建议使用相对路径,如 background: url("./img/xxx.jpg");
4)统一过滤的 I() 方法用的函数 htmlspecialchars 没有转义单引号 ' ,可能会造成 XSS 漏洞,最好自己定义一个更加强大、安全的过滤函数。如:
htmlspecialchars(trim($data), ENT_QUOTES)
5)GET或POST中的参数名为m、c、a 时,都会出现路由错误,其URL模式为普通模式时这是可以理解的,但是其它的URL模式都有这样的问题,而且连POST也会这样处理,只能说是BUG了。可以修改配置如下一般能达到避免效果
//修改URL中的获取变量的名字 'VAR_MODULE' => '__m__', // 模块获取变量 'VAR_CONTROLLER' => '__c__', // 控制器获取变量 'VAR_ACTION' => '__a__', // 操作获取变量 'VAR_PATHINFO' => '__s__', // 操作获取变量
∞、静态化
利用URL重写规则,判断静态文件是否存在,存在则直接显示,否则定向到TP框架中处理;
覆盖重写TP中的display()方法,让其除了生成页面外,还生成静态页面;
需要静态化的页面在显示如用户名等通用信息时使用ajax获取;
具体配置和方法以后贴出,敬请期待。