echo func(); //func was called exit; //忽略我的存在了吗 function func(){ return 'func was called'; }上面这个例子中,exit并没有影响到它后面的函数定义代码。也就是说函数定义在代码任何位置都是可以的。但是如果在exit语句后面使用include或者require进行文件包含的方式进行函数定义是无效的。而且不管是include还是require,如果要使用被包含文件里面的类或者函数的话,一定要在使用之前进行包含动作,顺序不能反。
2、字符串比较时的隐患
var_dump( md5('240610708') == md5('QNKCDZO') );//bool(true); var_dump( md5('aabg7XSs') == md5('aabC9RqS')); //bool(true); var_dump( sha1('aaroZmOk') == sha1('aaK1STfY')); //bool(true); var_dump( sha1('aaO8zKZF') == sha1('aa3OFF9m')); //bool(true); var_dump( '0010e2' == '1e3'); //bool(true); var_dump( '0x1234Ab' == '1193131'); //bool(true); var_dump( '0xABCdef' == ' 0xABCdef'); //bool(true);我们都知道等于与全等于的区别,等于(==)运算符会自动进行类型转换,比如 '0x1234Ab'这个字符串用在等于(==)运算当中,系统会认为它是一个十六进制表示的数值, 所以它等于十进制的1193131。像一些加密函数,比如上面例子中的md5, sha1这些加密之后的字符串有可能出现0x之类的进制前导符号,导致进行比较时不能按字符串原样比较,所以只要把上面的例子中的等于(==)运算符改成全等于(===)就不会有问题了。所说PHP7将不再视0x开头的字符串为十六进制了。
3、调用远程接口时注意的一点细节
$ret = curl_excute($ch); $ret = json_decode($ret); if($ret->code==0){ //远程接口约定code为0时表示调用成功。 //进行成功操作 }else{ //进行失败操作 }很多接口不知道为什么定义状态码code等于0时表示成功,对于弱类型语言而言,null==0也是成立的。如果接口正常返回,那上面代码会没有问题,但是如果出现网络问题导致接口返回的是null,那么code==0也会成立(你也不能用全等于啊,谁知道接口返回的0是字符串类型还是数值类型),这就与事实相悖了。所以保险的做法是在条件判断时,加个isset($ret->code)的并且关系条件。
4、switch语句中用的比较是等于(==),而不是全等于。这点与javascript中的switch语句是不同的。
5、注意组合运算符的计算顺序
$a=0; $a += (++$a) + (++$a);//$a最终的值是5,不是4. /* $a += $b + $c 相当于 $a = ($b + $c) + $a 而不是 $a = $a + ($b + $c) */
6、print结构
echo '5'.(print '2')+3; //output 254;
首先print 2,输出 2
因为print会返回1,所以接着运行echo '5'. 1 + 3;//54, 注意1和'.'之间有个空格,否则会语法报错。
两次输出连在一起就是254.
7、json_encode编码对象的注意点
json_encode除了可以对数组进行编码之外,还可以传一个对象,如果是对象的话,对象里面的私有属性、const常量、方法等是会被忽略的。
8、chmod($filename, $mode)方法为什么有时候达不到效果
chmod作用是更改给定文件的权限,但是它有以下几点需要注意:
a)第二个参数必须是八进制,比如0777,而不能是类似777或者“u+rwx,go+rwx"这些。(4-read, 2-write, 1-execute)
b)如果开启了安全模式,那么目标文件的所有者和脚本的执行者必须是同一用户,否则无法修改。
9、字符串也可以这样引用单个字符(类似数组下标):
$str = "abcd";
echo $str[0];//out "a" 或者
echo $str{0};// out "a"
但是字符串常量不行
const MY_STR='abcd';
echo MY_STR[0];//错误的
字符串甚至可以被list()解构,但是到PHP7之后不再有此特性,需要使用str_split().
10、魔术变量__CLASS__要注意的点:
__CLASS__保存是其所在类的类名,它是静态绑定的,只与它出现的地方有关,继承关系并不会影响到它的值,如下代码:
class A{
function __construct(){
echo __CLASS__; //此处__CLASS__出现在类A里面,所以它的值永远是“A",即使在A的子类B里面也是不会变的。
//echo get_class($this); //如果想得到真正运行时的类名的话,用get_class($this)函数获得
}
static function show(){
echo __CLASS__; //静态方法同样也是如此,如果要得到运行时的类名,用get_called_class(),不过此方法需要php
//的版本在5.3.0以上,低版本需要自行实现,网上有这样的方法。
}
}
class B extends A{
function showMe(){
echo __CLASS__; //此外因为__CLASS__在类B里面,所以保存的值是B
}
}
$b = new B(); // output A
$b->showMe(); // output B
B::show(); // output A
11、include与require的区别
include 包含的目标文件不存在时是抛出警告,而require是抛出致命错误并终止程序的执行。除此之外两者的特性都是一样。
12、注意一些常用的安全性措施
a) 敏感目录防止web访问,比如用来存放用户上传身份证的目录应该禁止访问。
b) 使用mysqli的预编绎语句来防止sql注入。
c) 注意防xss,用户输入的文字最好是用htmlspecialchars函数编码一下;利用jq渲染文本时尽量使用$().text()方法而不是$().html()方法,特别是从URL后面取GET参数进去渲染时,更要警惕。尽量避免随意种植cookie.
d) 预防CSRF,每个与后台交互的请求都带上一个由后台生成的token参数,该参数生成时保存在session中,用来对比请求中带来的token是否一致。
e) 严格控制上传文件的类型,上传文件的类型不能使用上传文件数组中的MIME值,而应该查看此文件的后缀名,并且保存文件时进行重命名以防止某些文件名出现解析漏洞问题。
f) 对用户密码进行更高层次的hash加密,目前的md5还是很容易通过碰撞破解出来。
g) 验证码比对完成之后要清理掉,如果不清理,那么用户只要输对第一次,后面都可以重复输同样值进行验证。还有一点,一定要判断用户提交的验证码是不是空值。
h) call_user_func(_array)要小心处理用户输入的参数值。
13、类中的static属性和static方法都是可以直接用类名访问的,同时static方法其实也是可以用实例化对象访问的(ClassName::method() 或者 $object::method()都是可以的),静态属性亦如此。 另外注意的是,static方法中不能出现$this关键字。
14、私有变量真的不能被外部访问到吗?事实可能并不总是如此。同属一个类的实例在方法体中是可以访问私有属性的。
class Foo{
private $var;
public function lookInto(Foo $obj){
$obj->var = 'abc'; //works
echo $obj->var; //works
}
}
同样的,protected也是可以用类似的方式进行访问:
class Foo{
protected $hi = 'hi';
protected function sayHi(){
echo $this->hi;
}
}
class Bar extends Foo{
public static function callSayHi(Foo $foo){
$foo->hi = 'Hi, foo'; //access the protected property of $foo
$foo->sayHi();// output Hi, foo
}
}
所以属性的可见性是针对于类空间而言,而不是实例。此特性为实现装饰者模式又提供了更多灵活控制的手段,但同时注意到,随意修改protected属性的值也许会给下游调用者造成不可预测的数据异常问题。
15、spl_autoload_register中没必要使用require_once和include_once, 使用require和include就可以了,毕竟没有once的校验在性能上还是有点优势。
16、如何调用grandParent类中的方法?
class GrandPa{
protected function call(){}
}
class Pa extends GrandParent{
protected function call(){}
}
class Son extends Pa{
protected function call(){
parent::call();// In Pa class
$grand = get_parent_class(get_parent_class($this));
$grand::call(); // In GrandPa class
}
}
17、不依赖clone,通过对象实例也能派生对象.
$a = new SomeClass;
$b = new $a; //$b是一个全新的实例,除了同样来自SomeClass之外,跟$a没有任何关系。
18、Trait虽然不可以实例化,但可以实现静态方法调用:
trait TFoo{
public static function doIt(){}
}
TFoo::doIt(); // works.
19、Trait的静态属性在调用它的实例中是独立的:
class Base{
public static $hi = "In Base";
}
class AofBase extends Base{}
class BofBase extends Base{}
AofBase::$hi和BofBase::$hi是同一个静态变量。
如果引入的是trait, 则不然:
trai T{
public static $hi = "ABC";
}
class A{
use T;
}
class B{
use T;
}
B::$hi = '123';
echo A::$hi; //ABC
echo B::$hi; //123
20、匿名类生成的对象具有共同的类空间:
function getAnoObj(){
return new class{
public static $p = [];
};
}
$a = getAnoObj();
$a::$p[] = 'a';
$b = getAnoObj();
$b::$p[] = 'b';
var_dump($b::$p); // array('a', 'b');
21、关于数组函数的一些小技巧:
a) 去除数组中的空值:
$out = array_filter(array_map('trim', [' a', "", 0, null, "b "])); // ['a', 'b'] , 注意array_filter会保留键名。
b) foreach中展开数组元素:foreach ([['a','b'], ['1', '2']] as list($a, $b)) {...}
c) 提取二维数组中的一列作为键(如果元素数量达到百万级建议使用foreach手动构造):
$out = array_column([['id' => 1, 'name'=>'Jil', 'age'=>21], ['id' => 2, 'name'=>'Bob', 'age'=>11]], null, 'id');
//array_column第二个参数表示提取哪一列,如果为null,将提取所有列;第三个参数表示用哪一列作为新数组的键。
22、clone可以实现多个对象共享一个属性,并且可以规避掉构造方法的调用。
class MyRef{
public $ref;
public function __construct(){
$this->ref = &$this->ref; //将属性自身的引用保存到属性中
}
}
$a = new MyRef; // 构造方法仅在$a初始化的时候被调用
$a->ref = 1;
$b = clone $a;
$b->ref = 2;
echo $a->ref; // output: 2
23、forward_static_call 与 call_user_func 虽然都可以用来调用用户函数,但是前者只能用在类方法中,用以实现后期静态绑定的功能,比较如下示例:
class Beer {
const NAME = 'Beer';
public static function printed(){
echo static::NAME . PHP_EOL;
}
}
class Ale extends Beer {
const NAME = 'Ale';
public static function printed(){
forward_static_call(array('parent','printed')); // Ale
call_user_func(array('parent','printed')); // Ale
forward_static_call(array('Beer','printed')); // Ale
call_user_func(array('Beer','printed')); // Beer
}
}
Ale::printed();
24、file_exists的陷阱:如果一个文件开始是存在的,然后被外部动作删除(非unlink()调用, 因为unlink()自带清cache机制 ),因为cache的缘故,file_exists在文件删除之后依然会返回True. 包括is_file(), is_dir()都会受缓存影响。
25、匿名函数的递归调用:
$sum = function ($v) use (&$sum){ //注意一定要用引用传递
if ($v == 0 ) return $v;
return $v + $sum($v-1);
};
26、is_callable的特性:
a) 能感知context, 也就是说在类方法内部检测本类的私有方法返回true, 在外部返回false. 而method_exists对私有方法也返回true.
b) 一个类如果定义了__call方法,那么传入任何不存在的方法名都返回true. 而method_exists返回的是false.
c) 一个类如果定义了__invoke方法,那么检测对象实例的时候会返回true.
d) is_callable会触发__autoload机制,所以性能上会有点瑕疵。
27、foreach中的引用陷阱:
$arr = [
1,
2,
3,
];
foreach ($arr as &$row) {
}
//unset($row);
foreach ($arr as $row){
echo $row, "<br>";
}
/*output:
1
2
2
*/
记得loop1结束后一定要unset($row)。发生此问题的原因是由于:
a)foreach语句中的变量范围是全局的。
b)$row作为引用变量时,在循环结束后,仍然是一个引用变量,并且指向了$arr的最后一个元素,即 $row==>$arr[2],
后续任何对$row的赋值,都会改变$arr[2]的值。