前言:
其实语言并不重要,关键是开发思想,递增式的学习,会更加有收获;下面我将从分析一个电商项目进行整理一下自己所学的知识点,学会欣赏别人的代码!!!
1.框架意识
2.MVC设计思想
3.电商项目的分析
1.框架意识:
其实我们一直都在接触框架,框架不是一件什么神奇的东西,首先:
- 如果没有Apache这个服务器作为我们的底层框架,我们PHP的发送到服务器端的代码远远不止这么少。
- 我们习以为常的_GET[]、_SESSION[]这些机制,其实也是封装好的东西,php预处理也是一种框架,只是不用我们去理会。
下面介绍的这个项目,在PHP 层已经做得很好了,只是在VIEW层没有怎么处理(因为VIEW层现在技术可以达到模板语言,而不是传标签,解决IF语句,类似于node.js)
2.MVC设计思想:
本次分析的电商项目特点:
抽象,单入口:
index:php?p=xxx&c=xxx & a=xxx;
与我之前架构的:
直接,多入口:(多个功能多个文件)
2.1单入口的分析:
相比于之前的架构方式:将PHP文件下的模块功能分开
Notes:
现在流行的Restful规范(TP5等)并不是简单的但入口而是对URL进行了语义化,路径分析(路由)
3.电商项目的分析:
Notes:
由于该项目的路径地址是是根目录因此文件夹需要均放在外面
3.1.index.php:
//项目调试开关
define('APP_DEBUG',true);
//引入框架基础类
require './framework/Framework.class.php';
//运行项目
Framework::run();
项目调试开关,true的时候是我们会将错误信息打印出来,在正常给用户运行的时候,这里是关掉的。
framework.class.php里的方法
private static function init(){
//开启调试时,显示错误报告
if(APP_DEBUG){
ini_set('display_errors',1);
error_reporting(E_ALL);
}else{
ini_set('display_errors',0);
error_reporting(0);
}
3.2.framework.class.php
//启动项目
public static function run(){
self::init(); //初始化
self::registerAutoLoad(); //注册自动加载
self::extend(); //扩展功能
self::dispatch(); //请求分发
}
类的静态方法和静态属性可以不用实例化对象直接使用(使用的方式是 类名::静态方法名 )
静态方法在读到这个类或者引入这个类文件的时候,就已经实例化并存放到内存中了,非静态类则需要new一下。
静态类在内存中即使有多个实例,静态的属性也只有一份。
::”这个叫范围解析操作符,又名域运算符 ‘- >’符号是“插入式解引用操作符” 用于类中,访问类里的函数或对象,比如:
<?php class Test { function do_test() { echo "Doing foo."; } } $bar = new Test; $bar->do_test(); ?>
PHP中=>和->以及::的用法实例可以参照这一篇文章
http://www.php.cn/php-weizijiaocheng-378631.html
3.2.1 路径配置:
//设置常量供项目内使用
define('DS', DIRECTORY_SEPARATOR); //路径分隔符
define('ROOT', getcwd().DS); //项目根目录
define('APP_PATH', ROOT.'app'.DS); //应用目录
define('FRAMEWORK_PATH', ROOT.'framework'.DS); //框架目录
define('LIBRARY_PATH', FRAMEWORK_PATH.'library'.DS);//类库目录
define('PUBLIC_PATH', ROOT.'public'.DS); //公开目录
define('COMMON_PATH', APP_PATH.'common'.DS); //公共目录
DIRECTORY_SEPARATOR 这个是PHP预处理的变量,相当于输出路径分隔符,为什么要这样操作呢?
因为在LINUX上,路径是‘/’
而实际上多多数的网站我们会将他们配置到LINUX服务器上,所以这一点非常重要
3.3.3 注册自动加载:
//注册自动加载
private static function registerAutoLoad(){
spl_autoload_register(function($class_name){
$class_name = ucwords($class_name); //自动转换类名首字母大写
if(strpos($class_name, 'Controller')){
$target = CONTROLLER_PATH."$class_name.class.php";
if(is_file($target)){
require $target;
}else{
E('您的访问参数有误!');
}
}elseif(strpos($class_name, 'Model')){
require MODEL_PATH."$class_name.class.php";
}else{
require LIBRARY_PATH."$class_name.class.php";
}
});
}
spl_autoload_register(function($class_name)
如不是有这个方法的帮助下,PHP 的面向对象将会变得法场头疼,自动加载注册类,
E 函数:
//遇到致命错误,输出错误信息并停止运行
function E($msg){
header('content-type:text/html;charset=utf-8');
die('<pre>'.htmlspecialchars($msg).'</pre>');
}
3.3.4 获取请求参数:
//获取请求参数
private static function getParams(){
//获取URL参数
$p = I('p','get','string','home');
$c = I('c','get','string','index');
$a = I('a','get','string','index');
//获取pathinfo
$pathinfo = self::getPathinfo();
//定义常量表示 URL Rewrite 开关
define('URL_REWRITE', (bool)$pathinfo);
//开启URL Rewrite时,从pathinfo获取参数
if(URL_REWRITE){
$params = self::getPathinfoParams($pathinfo);
//进行路由规则匹配
$start = 0; //取出参数的起始下标
if(isset($params[0]) && ($roule=C('URL_MAP_RULES')) && isset($roule[$params[0]])){
list($p,$c,$a) = explode('/', $roule[$params[0]]);
$start = (count($params)%2 > 0) ? 1 : 0; //根据参数个数,自动取出后面的参数
}
//将pathinfo参数保存到$_GET中
for($len=count($params);$start<$len;$start+=2){
if(isset($params[$start])){
$_GET[$params[$start]] = isset($params[$start+1]) ? $params[$start+1] : '';
}
}
}
//正则验证参数,要求以字母开头,后面跟0~20个 字母/数字/下划线 字符
foreach(array($p,$c,$a) as $v) {
preg_match('/^[A-Za-z]\w{0,20}$/',$v) || E('请求参数包含特殊字符!');
}
return array($p,$c,$a);
}
最关键是弄懂这个I方法:
//接收变量
//变量名,方法,数据类型,默认值
function I($var,$method='post',$type='text',$def=''){
switch($method){
case 'get': $method = &$_GET; break;
case 'post': $method = &$_POST; break;
case 'cookie': $method = &$_COOKIE; break;
case 'server': $method = &$_SERVER; break;
}
$value = isset($method[$var]) ? $method[$var] : $def;
switch($type){
//字符串类型
case 'string': //字符串 不进行过滤
$value = is_string($value) ? $value : '';
break;
case 'text': //字符串 进行HTML转义
$value = is_string($value) ? trim(htmlspecialchars($value)) : '';
break;
case 'int': //整数
$value = (int)$value;
break;
case 'id': //无符号整数
$value = max((int)$value,0);
break;
case 'float': //浮点数
$value = (float)$value;
break;
case 'bool': //布尔型
$value = (bool)$value;
break;
case 'array': //数组型
$value = is_array($value) ? $value : array();
break;
}
return $value;
}
举个栗子,如果I(' ','get','string','index');
那么我们将会直接进入主页面,但是同时也不会将路径暴露给用户
//从pathinfo中获取参数,进行路由匹配
private static function getPathinfoParams($pathinfo){
//过滤URL后缀
$suffix = C('URL_SUFFIX');
$len = strlen($suffix);
if(substr($pathinfo,-$len)==$suffix){
$pathinfo = substr($pathinfo, 0, strlen($pathinfo)-$len);
}
//将pathinfo转换为数组
return explode('/', trim($pathinfo, '/'));
}
Notes:
$len = strlen($suffix);
if(substr($pathinfo,-$len)==$suffix){
$pathinfo = substr($pathinfo, 0, strlen($pathinfo)-$len);
}
substr($pathinfo,-$len)==$suffix
//配置文件操作
function C($name,$value=null){
static $config = null; //保存项目中的设置
//函数首次被调用时载入配置文件
if(!$config){
$config = require COMMON_PATH.'config.php';
}
//省略value参数表示获取配置项,否则修改配置项
if(is_null($value)){
return isset($config[$name]) ? $config[$name] : '';
}else{
$config[$name] = $value;
}
}
3.3.5 难点却很实用的会话存储:
SESSION_DB
//扩展功能
private static function extend(){
//配置时区
date_default_timezone_set(C('DEFAULT_TIMEZONE'));
//设置HttpOnly
C('PHPSESSID_HTTPONLY') && ini_set('session.cookie_httponly', 1);
//Session入库
C('SESSION_DB') && new SessionDB();
//配置 mbstring 扩展内置字符集
mb_internal_encoding('UTF-8');
}
<?php
//Session数据库转储
class SessionDB {
/*
-- 数据库方式Session驱动
create table `prefix_session` (
`id` varchar(255) not null unique key,
`expire` int(11) not null,
`data` blob
);
*/
protected $lifeTime = '';
protected $table = '';
protected $db = null;
public function __construct(){
session_set_save_handler(
array(&$this, 'open'),
array(&$this, 'close'),
array(&$this, 'read'),
array(&$this, 'write'),
array(&$this, 'destroy'),
array(&$this, 'gc')
);
}
public function open($savePath, $sessName) {
$this->lifeTime = C('SESSION_EXPIRE') ? C('SESSION_EXPIRE') : ini_get('session.gc_maxlifetime');
$this->table = C('DB_PREFIX') . 'session';
$this->db = M();
return true;
}
public function close() {
$this->gc($this->lifeTime);
return true;
}
public function read($sessID) {
$sql = "select `data` from `$this->table` where `id`= :id and `expire` > :expire";
$data = array('id' => $sessID,'expire' => time());
$rst = $this->db->data($data)->fetchRow($sql);
return isset($rst['data']) ? $rst['data'] : '';
}
public function write($sessID, $sessData) {
$sql = "replace into `$this->table` (`id`,`expire`,`data`) values (:id, :expire, :data)";
$data = array('id'=>$sessID,'expire'=>time() + $this->lifeTime,'data'=>$sessData);
$this->db->data($data)->query($sql);
return true;
}
public function destroy($sessID) {
$sql = "delete from `$this->table` where `id`=:id";
$data = array('id'=>$sessID);
$this->db->data($data)->query($sql);
return true;
}
public function gc($sessMaxLifeTime) {
$sql = "delete from `$this->table` where `expire` < :expire";
$data = array('expire'=>time());
$this->db->data($data)->query($sql);
return true;
}
}
Session 数据木库转存需要按照PHP接口规范
Notes:
php会话存储默认在服务器内存中,但是一旦服务器关闭就消失:
应用场景有:
1.自动登录功能
2.电商项目的购物车:本次案例重点讲解的是电商的购物车信息存储的问题