是的,都说PHP写框架门槛低,我觉得这是个好事,框架的目的是更高效率的开发,门槛低为高效率提供了更多基础。有这么好的基础没有理由不自己也尝试写个框架,这里整理下自己要写框架的规划。
基于PHP5.3.X
这个都出来几年了,一些新特性的确能提高代码效率。
CLASS不是全部
OOP编程方式是非常可取的,一说OOP,直接对应的就是CLASS化,但是我们还需要独立的函数,PHP有命名空间,这些函数可以用命名空间包装起来,达到包化
CLASS风格封装配置和常量定义
虽然命名空间隔离了代码,但是隔离不了常量定义,比如
define('DEBUG',1);
又或者是一个全局的变量,个人认为作为一个框架不应该暴露出任何一个全局变量和常量定义。原因,如果应用项目够庞大,这种暴露无疑留下了冲突的危险
控制生命周期
一般认为PHP代码没有长声明周期的,这是常见的PHP加载方式造成的,可并不等于说就不需要考虑代码执行期间生成对象的生命周期了,要知道PHP也可以开发独立的服务器,具有长生命周期的代码。因此把需要具有长生命周期的对象用CLASS单件模式封装,其他变量都在某个入口函数中完成达到对象生命周期的控制
平面化CLASS
OOP的方式下,常常会看到CLASS一重有一重的继承,这样的好处就不说了,不过这让维护确实麻烦了,甚至好多基础类根本就是个空CLASS,好像仅仅是为了说明继承关系而产生的,为了OO而OO的代码。事实上我常常感觉通过CLASS继承并不能简单的通过维护基础类来解决新需求。所以平面化CLASS,减少CLASS继承的深度反而让事情会变得简单写,那要控制到多少深度呢?3层,不能超过这个数了。古语讲事不过三,这是人类社会早就总结出的哲学。原因道理不必深究,人家早就研究过了。
允许同一类别的CLASS具有不一致性接口
比如说数据库类操作,往往我们希望对数据库类操作提供完全一致性的接口,事实上由于数据库引擎的不同,差异性总是存在的,不值得为了这些差异性浪费脑细胞达到接口一致。20/80法则,有必要允许一些不一致性来节省生命,别担心应用层使用的麻烦,实际中这些差异性总是确定的。数据转移怎么办?哦,别扯这个了,数据库真的转移了,要改的东东那大了去了,就别计较这一点儿了。
同类函数具有相同的参数个数
举个例子来说明这个想法,数据有效性验证,用3验证来说明这个问题。
这是3个关于最小值,最大值有效性数据的验证
function min($val,$min){...} function max($val,$max){...} function range($val,$min,$max){...}
我要说什么呢?我要说的是参数的写法,我认为这样更好
function min($val,array $args){...} function max($val,array $args){...} function range($val,array $args){...}
$args是个Key-Value的数组,这样通过数据结构传递参数,达到验证类函数的参数都是2个,调用接口的一致性利于逻辑化代码。
好像还有些,先写到这里。呵呵,不能纸上谈兵,贴上一段输入验证的代码,我管这个叫过滤器
array( 'emailPattern'=>'/^[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/', 'emailFullPattern'=>'/^[^@]*<[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?>$/' ), /** * 消息默认设置 */ 'messageType'=>'无效参数', 'messageDefault'=>'未能通过"$_rule"规则', /** * 消息模板,当规则未通过的时候被使用 */ 'message'=>array( 'notEmpty'=>'非空', 'isEmpty'=>'必须空', 'enum'=>'取值之一', 'min'=>'最小值', 'max'=>'最大值', 'range'=>'值范围($min,$max)', 'length'=>'长度($min,$max)', 'email'=>'电子邮件格式', 'type'=>'要求类型"$type"', 'date'=>'日期', 'url'=>'网址' ) ); public static function extend($key,$val){ if(is_array(self::$conf[$key]) and is_array($val)) self::$conf[$key]=array_merge(self::$conf[$key],$val); else self::$conf[$key]=$val; } } /** * 带错误信息的过滤器代理 * 通过这是函数可以访问具体的规则器 * @param mixed $value,需要过滤的数据 * @param array $rules,过滤规则 * rules 是Key-Value的数组,Value也是Key-Value数组 * 例:array('min'=>array('min'=1)) * 特殊规则 allowNull 如果被定义将优先执行 * @return null 成功不返回任何值 string 失败返回错误提示信息 */ function proxy(&$value,array $rules) { if(!$rules or $rules['allowNull'] and is_null($value)) return; foreach($rules as $rule=>$args) { if($rule=='allowNull') continue; $func=__NAMESPACE__.'\\'.$rule; if(!function_exists($func)) J\ThrowException('未实现此功能',$rule,415,array('rule'=>$rule)); if(is_scalar($args)) $args=array($rule=>$args); $msg=$func(&$value,$args); if(true===$msg or null===$msg) continue; if(false===$msg) $msg=Config::$conf['message'][$rule]?:Config::$conf['messageDefault']; else $msg=Config::$conf['message'][$rule].$msg; $args['_value']=is_scalar($value)?$value:'value'; $args['_rule']=$rule; J\ThrowException(Config::$conf['messageType'],$msg,415,$args); } } /** * 范围检查 * 支持string数据类型和numeric类型 * @note $args[min] <= val >= $args[max] * @param mixed $val 等待验证的值 * @param array $args array(min=>foo,max=>foo) */ function range($val,array $args) { if(is_numeric($args['min'])) return is_numeric($val) and $val>=$args['min'] and $val<=$args['max']; elseif(is_string($args['min'])) return is_string($val) and $val>=$args['min'] and $val<=$args['max']; return false; } /** * numeric 类型的值范围规则 */ function min($val,array $args) { if(is_numeric($args['min'])) return is_numeric($val) and $val>=$args['min']; elseif(is_string($args['min'])) return is_string($val) and $val>=$args['min']; return false; } function max($val,array $args) { if(is_numeric($args['min'])) return is_numeric($val) and $val<=$args['max']; elseif(is_string($args['min'])) return is_string($val) and $val<=$args['max']; return false; } function enum($val,array $args=array('enum'=>array())) { if(is_scalar($val) and in_array($val,$args['enum'])) return; return '('.implode($args['enum'],',').')'; } function isEmpty($val) { return empty($val); } function notEmpty(&$val) { return !empty($val); } function length($val,array $args) { if(!is_scalar($val) or !isset($args['min']) and !isset($args['max'])) return false; $len=mb_strlen($val); if(isset($args['min']) and $len<$args['min']) return false; if(isset($args['max']) and $len>$args['max']) return false; } /** * 电子邮件格式校验 * @param array agrs,可以包含选项Key * allowName:允许类似 Yu HengChun格式 * checkMX:检验邮件服务器有效性 */ function email($val,$args){ $emailPattern=Config::$conf['constant']['emailPattern']; $emailFullPattern=Config::$conf['constant']['emailFullPattern']; $valid=is_string($val) && (preg_match($emailPattern,$val) || $args and $args['allowName'] and preg_match($emailFullPattern,$val)); if($valid and $args and $args['checkMX']){ $domain=rtrim(substr($val,strpos($val,'@')+1),'>'); if(function_exists('checkdnsrr')) $valid=checkdnsrr($domain,'MX'); if($valid && function_exists('fsockopen')){ $valid=fsockopen($domain,25); if($valid){ fclose($valid); $valid=true; } } } return $valid; } /** * 日期,可对数据进行有效性验证和数据处理 * @param array $args 数据处理 * format:参见 PHP::date 的格式 * timezone:原数据的时区 * totimezone:转换到时区时间 */ function date(&$val,array $args=array()) { if(!is_scalar($val)) return false; try { if($args['timezone']) $d=new \DateTime($val,new \DateTimeZone($args['timezone'])); else $d=new \DateTime($val); if($args['totimezone']) $d->setTimezone(new \DateTimeZone($args['totimezone'])); if($args['format']) { $val=$d->format($args['format']); }else { $val=$d->format('Y-m-d H:i:s'); } }catch(Exception $e){ return false; } } /** * 网址,支持scheme,host,path正则匹配验证 * @note 不支持scheme,host,path有关联的匹配 */ function url($val,array $args=array()) { $a=parse_url($val); if(!$a) return false; if(isset($args['scheme']) and is_scalar($args['scheme'])) return 'args[scheme] $scheme error'; if(is_array($args['scheme']) and !in_array($a['scheme'],$args['scheme'])) return 'scheme must be in ('.implode($args['scheme'],',').')'; if(isset($args['host']) and is_scalar($args['host'])) return 'args[host] $host error'; if(is_array($args['host'])){ if(!$a['host']) return 'host must be in ('.implode($args['host'],',').')'; $find=false; $h=$a['host']; foreach($args['host'] as $reg) { if(1==preg_match($reg,$h)){ $find=true; break; } } if(!$find) return 'host must be in ('.implode($args['host'],',').')'; } if(isset($args['path']) and is_scalar($args['path'])) return 'args[path] $path error'; if(is_array($args['path'])){ if(!$a['path']) return 'path must be in ('.implode($args['path'],',').')'; $find=false; $h=$a['path']; foreach($args['path'] as $reg) { if(1==preg_match($reg,$h)){ $find=true; break; } } if(!$find) return 'path must be in ('.implode($args['path'],',').')'; } }
也许你注意到了Config这个类,整个过滤器的组织方式使用命名空间封装独立函数,class Config封装配置和文本消息(这样可以方便的用于多语言翻译),所有过滤器具有一致的2参数接口,那个代理proxy函数是为了方便调用制作的。
被使用的JingYes.php
* @copyright Copyright © 2011 Yu HengChun * @license http://www.opensource.org/licenses/bsd-license.php */ namespace JingYes; /** * JingYes class风格配置 * 其它类里面也采用这种风格 */ final class Config { public static $conf=array( ); /** * 配置扩展 * @param string $key 键 * @param array $val 值 */ public static function extend($key,$val){ if(is_array(self::$conf[$key]) and is_array($val)) self::$conf[$key]=array_merge(self::$conf[$key],$val); else self::$conf[$key]=$val; } } if(!function_exists('JingYes\ThrowException')){ /** * 抛出异常信息 * @param string $type 错误类型信息 * @param string $message 错误细节信息模板 * @param integer $code 错误类型代码,采用http错误代码 * @param array $args 传递给 $message的参数,用于生成最终信息 */ function ThrowException($type,$message,$code=501,array $args=array()) { $type=Config::$conf['error'][$type]?:$type; $message=message($message,$args); throw new \Exception($type.':'.$message,$code); } } if(!function_exists('JingYes\message')){ /** * 对提示信息进行转换翻译 * @param string $message 错误细节信息模板 * @param array $args 传递给 $message的参数,用于生成最终信息 */ function message($message,array $args=array()) { $message=stripslashes($message); $message="\$message=\"$message\";"; extract($args); eval($message); return $message; } }