变量引用
$ref =& $a
:$ref
是一个引用, ‘ref’会被增加到符号表, 但不会新建一个zval, 而是$ref
和$a
指向同一个zval,zval::is_ref
变为1,zval::refcount
变为2- 在函数内引用全局变量:
$var1 = "hello world"; |
下面的代码不会改变$bar的值:
function f(&ref) { // 新建本地引用变量$ref, 指向$bar |
unset一个引用, 只是断开了引用和实际变量的连接(php manual原话, 实际做了两件事情: 1从符号表删除这个引用变量的名字,2引用指向的zval的引用计数-1,当引用计数变为0时, zbal::is_ref也变为0), 引用指向的实际变量并不被销毁:
function f(){ |
引用计数: zval结构和引用计数 对一个变量$a使用unset, refcount会减1,
$a = "old string"; // 符号表里新增一个'a', 内存新建一个zval结构, 此时zal::is_ref是0,引用计数是1 |
变量与常量
- 在函数内调用全局变量时需要用global关键字声明:
global $x,$y;
, 注意这里的global
是声明而不是定义. - 全局变量: 在函数内
global $a
和$GLOBALS['a']
的区别:global $var;
是$var = $GLOBALS['var'];
的简写。所以unset($var)
不会影响外面的global. 如果想要在函数中销毁全局变量可以用unset($GLABOL['var'])
- Wordpress的全局变量$wpdb的定义:
require_wp_db()
常量定义:
define("CONST_STRING1", "Hello world.");
const CONST_STRING2 = 'Hi world'; // 在PHP5.3之后可以用这种方法.如果常量名是动态的, 也可以用函数
constant("常量名")
来获取常量的值.- 用
get_defined_constants()
可以获得所有已定义的常量列表. - empty, isset, is_null ,is_object, is_array, is_string, is_resource, 参考PHP type comparison tables
empty()
: 大致相当于!isset($var) || is_null($var) || !$var
, 详见 empty函数说明和 (http://stackoverflow.com/questions/4559925/why-check-both-isset-and-empty)isset($a)
: 当变量a被赋值, 并且不是null, 那么isset($a)
为真.- 如果在函数中
unset
一个全局变量, 那么仅仅在这个函数中全局变量被销毁了, 在此函数外面仍旧可以使用这个全局变量. 如果想要在函数中销毁全局变量, 需要用unset(GLOBALS['ha'])
来销毁; is_resource()
可以判断fopen
返回的文件句柄;
基本数据类型
- PHP的基本类型有6种: boolean, integer, float, string, 复合类型: array, object;
- 在PHP中, 数组和字符串都属于”基本类型”, 基本类型的赋值/函数传递都是传值的;
- (1) boolean: 整数(0)/浮点数(0.0)/对象(NULL)都被视作false, 可以使用
boolean is_bool($x)
判断是否是boolean型; - (2) integer: 类似C/C++, 0900, 0x900分别用来表示八进制, 十六进制.
- (3) float: PHP的浮点数和C/C++中的双精度浮点数范围一样, 都是8字节.
- (4) string: 可以使用
==
号比较两个字符串是否相同, 用boolean is_string($x)
判断变量是否是字符串. PHP使用[ ]
或大括号访问字符串的单个字符, 例如$str{0}
或$str{$i}
- 除了单引号和双引号的字符串, PHP还支持nowdoc和heredoc来支持长字符串, nowdoc类似单引号, heredoc类似双引号:
$nowdoc = <<< 'END' //结束符可以自定义
...
END; // 遇到结束符表示一段结束, 不要忘记分号
$heredoc = <<< END // 结束符不带单引号
...
END; // heredoc无法解析$
字符串
字符串处理函数
- 参考: (https://php.net/ref.strings)
- 字符串空格、大小写:
trim
,ltrim
,rtrim
,strtolower
,strtoupper
; - 字符串截取:
string substr($str,$start,$len)
, - 查找字符串的首次出现:
string strstr(string,search)
, 如果只是想测试a是b的子串,请使用速度更快耗费内存更少的 strpos() 函数。 - 字符串分解:
list($a,$b,$c) = explode(',', $str)
; - 字符串合并:
$str_imp = implode(',', $array)
将数组的元素合并成一个字符串;
Web相关字符串处理函数
- HTML实体:
htmlentities
区别htmlspecialchars
, (http://www.w3school.com.cn/php/func_string_htmlentities.aspx) - 去除html标签
string strip_tags($htmstr)
, 去除<p>,<i>
等 - 获取HTML mate属性:
$arr = get_meta_tags($htmstr)
- 获取HTTP头:
array get_headers($url)
- 将当前的 QUERY_STRING解析到数组:
void parse_str(string,array)
, 将url的get参数解析到array,- 这个函数的输出受到
magic_quotes_gpc
的影响.
- 这个函数的输出受到
- 与上面的
parse_str()
相反, 将array组装为get查询字符串http_build_query($array)
- 解析URL的要素:
array parse_url($url)
, 返回包含host,port,user 等 - 对URL进行编码:
urlencode($url)
: 将非字母字符转换为”%数字”, 空格编码为”+”, 对应解码为urldecode($url)
- 对URL进行编码:
rawurlencode($url)
: 与上面的区别是空格编码为”%20”, 对应解码是rawurldecode($url)
- 问题: 为什么要对URL进行编码? 哪些需要编码? (http://www.blogjava.net/donghang73/archive/2011/08/10/356208.html)
- 更多URL函数参考(http://php.net/manual/zh/book.url.php)
- [注1] addslashes() / stripslashes() : (http://php.net/manual/zh/function.addslashes.php)
比较字符串
- 在PHP中字符串是基本类型, 所以
==
是比较值, 而不是像Java比较字符串引用的指向, 另外int类型的1和”1”用==
比较是相等的; - 如果想严格比较字符串, 包括类型比较, 则使用
===
; strcmp($1,$2)
,strcasecmp($1,$2)
Perl风格正则
boolean preg_match($pattern, $string, $array_match)
: 第三参数match也可以没有, match[0]存储完整的$string, match[1]存储…
正则表达式快速参考(https://msdn.microsoft.com/zh-cn/library/az24scfc(v=vs.110).aspx)
格式化输出
echo, print(), printf(), print_r(), var_dump():
echo
和print
都是语言结构, 有无括号均可使用, echo和print都不是函数所以没有返回值, 所以不要把echo当作if的判断条件;echo
后面可以是一般的变量(包括类的成员), 但不能是array和object;printf
是函数所以有返回值, 和C语言的printf基本一样.print_r()
和var_dump()
可以更详细的打印出类型和值;
echo "1", "2", "3"; // echo可以一次输出多个字符 |
PHP的数组
- 测试一个对象是否是数组:
is_array($x)
- 获取数组大小:
sizeof($arr)
和count($arr)
; - 生成连续数组:
range()
例如:$arr = range(1,100)
或者$arr = range('a', 'z')
; - 为数组元素赋值:
array_pad($arr,n,val)
, 注意array_pad()
返回的是一个新数组, 在原数组$arr基础上,生成一个带有n个初值为val的数组; 从数组中析取多个值:
$arr = array(1,2);
list($a,$b,$c) = $arr; // 变量a,b,c的值分别为1,2,null获取子数组:
// array_slice()返回是一个新数组, 当然也可以可以用list析取值:
list($a,$b) = array_slice($arr, 1, 3); //返回从$arr[1]开始, 包括arr[1]的3个元素检查数组里是否存在Key:
if(array_key_exists('name', $array))
, 能否用if($array['name'])
判断?- 检查数组里是否存在Value:
if(in_array("abc",$array))
, 第三个参数可选,true或false表示是否判断类型; - 迭代器(iterator), PHP有一些函数来移动迭代器, 并返回元素:
current()
,reset()
,next()
,prev()
,key()
,- 迭代器
each()
返回当前的k和v, 并指向下一个元素:while(list($k, $v) = each($array))
;
- 迭代器
- 数组排序:
sort()
,rsort()
, - 翻转数组:
array_reverse($arr)
, - 合并数组:
array_merge($arr1,$arr2)
: 对于数字索引的数组, 如果第二个数组有 Key 重复的元素, 这个元素会被分配一个新的索引; 对于字符串索引的数组则覆盖前面的值;- 思考: 如果两个数组一个是”数字索引”另一个是”字符串索引”, merge的结果是怎样的?
- 数组相加:
$arr1 + $arr2
: 相同索引的元素会合并, 和merge的区别是, 如果有索引相同的元素, 加号是保留第一个数组的元素; - 比较数组:
$arr_diff = array_diff($arr1,$arr2)
, 返回值是$arr1
和$arr2
的交集, 并且对于数组的value是用===
比较的,所以$arr1
和$arr2
分别有int(1)和”1”也要被算作不同; - 数组作为LIFO堆栈后进先出 :
array_push($arr,$elem)
和array_pop()
更多数组函数参考(http://php.net/manual/zh/ref.array.php)
PHP数组的key可以为integer(甚至是负数)和string, value可以为任意类型; key有如下几个转换规则:
- 如果key是可以转换成integer的(例如字符型的”8”,布尔型的true), 那么key会被转换为integer;
- 当key是float型, 其小数部分会被舍去, 自动转换为integer;
- 定义数组时, 多个元素使用同一个key, 这些元素的值会被最后一个覆盖, 比如
arr = array(3.1 => '3.1', '3' => 'three');
, 最终数组只有一个元素k,v = 3,'three'
; - 使用[]符号访问数组成员时, 也符合以上的转换规则, 例如arr[3.1]和arr[‘3’]都会被默认转换为arr[3];
数组引用(references)
$worker = array("Fred","Willma"); |
原数组赋值null:$worker = null;
var_dump($Clone); // 正常访问
var_dump($Ref); // NULL
删除原数组:unset($worker); // 删除原来的数组
var_dump($Clone); // 正常访问
var_dump($Ref); // 正常访问
结论
- Obj无论用
=
还是=&
, 修改其中一个都会影响另一个, 因为Obj的赋值是按引用传递的. - 但是Array和String属于基本类型, 使用
=
的时候是值传递, 改变一个另一个不受影响, 只有=&
按引用传递时才会改变另一个. unset
一个引用只会断开引用和实际变量的连接, 并不会影响实际变量
问题
- 使用
unset
和$a=null
的区别是? (http://stackoverflow.com/questions/584960/whats-better-at-freeing-memory-with-php-unset-or-var-null)$a=null
立即释放内存, 但没有把$a
从符号表里删除;unset($a)
不会立即释放而是在适当时候释放内存(类似Java), 但会立即把$a
从符号表删除, 此时再用if($a==null)
会得到一个”未定义变量”的错误.array_key_exists('var_name', $var)
引用计数和写时复制
- 引用计数 : (http://php.net/manual/zh/features.gc.refcounting-basics.php)
- 写时复制 : (http://www.php-internals.com/book/?p=chapt06/06-06-copy-on-write)
PHP的类型转换
php中的强制转换和c的语法类似, 强转为string类型还可以使用strval()函数;$fst = (string) $foo; // c风格转换
echo strval(pow(2,50)); // 利用函数转换
转换为对象
- 如果将一个对象转换成对象, 它将不会有任何变化.
- 数组转换成对象将使键名成为属性名并具有相对应的值.
- 如果基本类型的值被转换成对象, 将会创建一个内置类
stdClass
的实例. 如果该值为 NULL, 则新的实例为空. 名为scalar
的成员变量将包含该值
基本类型转化为对象
$obj = (object) 'abc'; // 字符串转化为对象
echo $obj->scalar; // outputs 'abc'数组转换为对象:
$arr = array("One" => 1, "Two" => 2);
$obj = (object)$arr;
echo $obj->One;
echo $obj->Two;
转换为数组
对于任意 integer, float, string, boolean 和 resource 类型, 如果将一个值转换为数组, 将得到一个仅有一个元素的数组, 其下标为 0, 该元素即为此标量的值.
如果一个 object 类型转换为 array, 结果为一个数组, 其单元为该对象的属性. 键名将为成员变量名, 不过有几点例外:
1. 整数属性不可访问;私有变量前会加上类名作前缀;
2. 保护变量前会加上一个 `*` 做前缀. 这些前缀的前后都各有一个 NULL 字符. 这会导致一些不可预知的行为:
PHP各种类型的比较
PHP里的比较运算符==
,>
,<
不区分类型, 只比较”值”, 如果比较符号两边不是同一类型(但是能转换为同一类型)也可以比较. 比如(int)1和(string)”1”用==
比较是相等的;
如果要做严格的类型比较, 需要用全等于===
, 当类型和值都相同时表达式才是true.
PHP的
==
和===
:- 字符串: 作为PHP的基本类型, 字符串
==
是比较值而非引用, 所以==
和===
以及strcmp()
都可以用来比较字符串; - 数组
==
比较: 两个数组的key和value都相等则为true, 顺序可以不同, 类型不必严格相等; - 数组
===
比较: 两个数组必须顺序一致, 并且key和value的类型也严格相等, 也可以使用array_diff()
函数; - 对象
==
比较: 成员的值相等, 并且两个对象是同一个class类型; - 对象
===
比较: 必须指向同一个对象
- 字符串: 作为PHP的基本类型, 字符串
不同类型间的比较 :
- 上面的情况1要注意: 当其中一个变量不确定类型时(来自用户的输入), 用
==
比较是危险的, 比如if(0=='pwd123')
是真, 所以strcmp和===才是妥当的字符串比较. - int/string/array/obj 同boolean比较? # int(非0), 非空的string(‘false’和’true’), 非空array, 同true做
==
比较都是真 - int/string/array/obj 同null比较? # int(0),空字符串string(‘’),空数组array() 与null做
==
比较都是真, if(‘’ == null)是真 - 空字符串
$b=''
和null用==
比较返回真, 用===
比较返回假; 只有$b=null
之后,if($b === null)
才是真
- 上面的情况1要注意: 当其中一个变量不确定类型时(来自用户的输入), 用
流程控制
- if-else嵌入到html: (http://stackoverflow.com/questions/722379/can-html-be-embedded-inside-php-if-statement)
函数(方法)
- 类对象作为参数/返回值: 都是按照引用的方式传递参数.
函数返回引用:
function &returns_reference() // 函数名前要带一个&符号
{
return $someref; // 没有&
}
$newref =& returns_reference(); // 注意这里也需要&符号函数按引用传参:
function foo(&$var)
{
$var++;
}
$a=5;
foo($a); // $a is 6 here
面向对象
- “魔术方法” :
__sleep()
,__wakeup()
,__invoke()
, 这个函数实际是如何使用的? - “后期静态绑定”: 在继承中使用
self::
或者__CLASS__
, 取决于函数的定义是由基类or派生类, 如果用static::
代替self::
- C++具有abstract方法和类, 但没有interface; Java有abstract方法, 但没有抽象类, Java用interface替代抽象类; 但PHP同时具有以上;
- C++用const可以修饰方法和属性, Java没有const关键字而用final修饰常量, 同时final还可以修饰属性(常量),方法和类(不可继承), 形参(同C++的const);
- PHP同时拥有
const
和final
关键字,const
修饰常量和类成员常量,final
和abstract
都可以修饰抽象类和函数
上代码: 参考(http://learnxinyminutes.com/docs/zh-cn/php-cn/)class MyClass extends MyAbstractClass implements InterfaceTwo
{
const MY_CONST = 'value'; // 常量
static $staticVar = 'static';
public $property = 'public'; // 在这里可以初始化
public $instanceProp; // 类成员要在构造函数里初始化
protected $prot = 'protected'; // 当前类和子类可访问
private $priv = 'private'; // 仅当前类可访问
// 构造可以有不同的参数列表, 类似于C++的构造函数重载
public function __construct($instanceProp) {
parent::__construct(); // 要手动调用父类的构造
$this->instanceProp = $instanceProp;
}
// 不可被改写的方法
final function youCannotOverrideMe()
{
}
}
// 命名空间:
// 类会被默认的放在全局命名空间中,可以被一个\来显式调用
$cls = new \MyClass();
// 为一个文件设置一个命名空间
namespace My\Namespace;
class MyClass
{ ... }
// 你也可以为命名空间起一个别名
namespace My\Other\Namespace;
use My\Namespace as SomeOtherNamespace;
$cls = new SomeOtherNamespace\MyClass();
类自省
- 类检验:
class_exists('className')
,get_class_methods('className')
,get_class_vars('className')
,get_parent_class('className')
, for example:
$methods_arr = get_class_methods('ChangyanHandler'); // 返回数组 |
对象检验:
is_object($obj)
,get_class($obj)
,bool method_exists(obj, method_name)
,get_object_vars($obj)
, for example:$class_name = get_class($obj); // 由类实例获取类名
$vars_arr = get_object_vars($obj); // 只返回有默认值的属性, 数组if($obj instanceof ChangyanHandler)
串行化
$str = serialize($mixed)
串行化对象或者数组, $mixed = unserialize($str)
从字符串恢复对象或者数组, 在串行化和反串行化操作一个object时, 有两个钩子函数:
- `__sleep()` : 在`serialize()`之前调用, 在这个函数里你需要关闭已达开的资源, 并返回array, 包含需要串行化的成员名字;
- `__wakeup()` : 在`unserialize()`之后调用, 在这个函数里你需要重新打开资源;
模块化
引用其他文件:
include 'globals.php'
:require 'globals.php'
: 如果不能被导入时,会抛出错误
异常
@TODO
MySQL
[注1] 对输入进行安全处理:
addslashes()
函数对_COOKIE[]
,_GET[]
,_POST[]
的输入进行去引号以及斜线进行转义处理, 注意,magic_quotes_gpc
设置为On的时候默认对上面的输入自动进行addslashes()
, 不要在该值为On的时候重复调用addslashes
, 稳妥的做法是:if (get_magic_quotes_gpc()) {
$lastname = stripslashes($_POST['lastname']); //得到原始的输入
}属性
magic_quotes_gpc
自从5.4起被移除, 不再推荐使用对于5.4之前的版本也要注意, magic_quotes_gpc只对GET/SET/COOKIE有效, 但没有转义
$_SERVER[]
建议使用 DBMS 指定的转义函数(比如 MySQLi 是
mysqli_real_escape_string()
, 但是如果你使用的 DBMS 没有一个转义函数才选择使用使用addslashes()
;- Mysqli和PDO都提供了预处理语句
prepare()
和bind_param()
, 使用预处理语句的优点? PDO参数化查询 - 对于非array或obj的输入, 可以用
intval()
转换. - PDO vs. MySQLi 选择哪一个?
Web
- 全局数组:
_COOKIE[]
,_GET[]
,_POST[]
,_FILES
,_SERVER[]
,_ENV[]
: - 数组
$_SERVER[]
包括的全局变量有:- REQUEST_METHOD: 访问页面时的请求方法.例如: “GET”. “HEAD”. “POST”. “PUT”.
- REQUEST_TIME: 请求开始时的时间戳.
- QUERY_STRING: 查询(query)的字符串(URL中第一个问号?之后的内容).
- DOCUMENT_ROOT: 当前运行脚本所在的文档根目录.在服务器配置文件中定义.
- HTTP_ACCEPT: 当前请求的Accept头信息的内容.
- HTTP_ACCEPT_CHARSET: 当前请求的Accept-Charset头信息的内容.
- SERVER_PROTOCOL: 请求页面时通信协议的和版本, 例如:Http/1.0;
- REQUEST_METHOD: 访问页面时的请求方法.例如:”GET”. “HEAD”. “POST”. “PUT”.
- 获取SSL状态
if($_SERVER['HTTPS'] != 'on') { .. }
Http Header
- HTTP响应头的设置: 在用
header()
前不能有任何html输出header('Location: http://segmentfault.com/'); // 重定向
header("HTTP/1.0 404 Not Found"); // 返回404
header('Content-type: application/pdf'); // 页面将输出pdf
header('Content-Type: text/html; charset=utf-8'); // 设置输出html和编码
在任何输出之前, 应该使用header()
Cookie:
跨域种Cookie// 文件a.com/setcookie.php
header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');
setcookie("TestCookie", $value, time()+3600, "/rasmus/", "example.com", 1); // 1小时, "example.com/rasmus"及其子目录有效, Https Only
// 文件b.com/b_setcookie.php
<script src="http://a.com/setcookie.php"></script>
//访问b.com/b_setcookie.php 能设置a.com的cookie
Session
- session的实现: 在当前域下种”PHPSESSIONID”的cookie, 这个cookie的值就是服务器端对应session的文件名, 可以使用
session_save_path()
获取设置session存储路径. - session_start()都做了什么?
- session的同步: NFS, Memcached
- PHP Session可能会引起并发问题
发送Http请求
- Wordpress 支持两种, stream和curl方式, 分别使用
stream_context_create
和curl_exec
实现 具体参考代码WP_Http_Streams::request()
和WP_Http_Curl::request()
- 安全问题:
fsockopen
和pfsockopen
, 后者是打开一个持续的连接, 在php.ini的选项disable_functions
增加”fsockopen” 和allow_url_fopen=Off
- IDC为什么禁用fsockopen、pfsockopen函数
- 异步请求:
curl_multi
php.ini配置
- 获取php.ini的设置:
$val = ini_get('magic_quotes_gpc')
; - php.ini 配置选项列表: (http://php.net/manual/zh/ini.list.php)
- php.ini 安全最佳实践: expose_php, display_errors, log_errors/error_log (http://www.cyberciti.biz/tips/php-security-best-practices-tutorial.html)
错误捕获
error_reporting=E_ALL & ~E_DEPRECATED & ~E_STRICT
:display_errors=On
: 是否将错误信息作为输出的一部分显示到屏幕error_log="D:\xampp\php\logs\php_error_log"
: 设置脚本错误将被记录到的文件
文件编码
- php文件本身必须是UTF-8编码. 不像Java会生成class文件, 避免这个问题
- php要输出头:
header("Content-Type: text/html; charset=UTF-8")
- meta标签无所谓, 有header所有浏览器就会按header来解析
- 所有外围都得用UTF8, 包括数据库. *.js, *.css(CSS影响倒不大)
- php本身不是Unicode的, 所有substr之类的函数需改为
mb_substr
(需要装mbstring扩展);或者用 iconv functions转码
性能
参考 (http://www.imooc.com/video/4169)
- apache benchmark
- php解析-> 解释器
- 内置函数的性能, 实现
- 少用魔法函数
- 禁用@错误抑制, @是如何实现的?
- 及时
unset()
不使用的, Google: unset会有无法注销变量的情况 - 正则对性能的影响
- 数组的查找, map
- IO: 磁盘, 数据库, 内存, 网络
- 网络请求并行:
curl_multi_*
,
调试
- 使用
echo
,print
,var_dump
等在页面上显示错误, 需要修改php.ini的display_errors
等选项, 参考, 额外的:debug_backtrace() - 输出到文件:
file_put_contents('debug.log',"msg...",FILE_APPEND)
, 或者error_log($str, 3, 'errors.log')
, 用法参考 - 线上环境调试: phpstrom+xdebug + chrome(debug helper) or firefox (easy xdebug)
- zend studio + zend debugger , 或者在 NetBeans IDE PHP 编辑器中调试 PHP 源代码