PHP Tutorials

变量引用

  • $ref =& $a : $ref是一个引用, ‘ref’会被增加到符号表, 但不会新建一个zval, 而是$ref$a指向同一个zval, zval::is_ref变为1, zval::refcount变为2
  • 在函数内引用全局变量:
$var1 = "hello world";
$var2 = "";
function f(){
// 相当于$var1 =& GLOBALS['var1'], 函数内新建一个本地的引用$var1指向全局变量
global $var1,$var2;
$var2 =& var1; // 本地的引用$var2重新被指向
}
f();
var_dump($var2); // var2仍然是空字串

下面的代码不会改变$bar的值:

function f(&ref) { // 新建本地引用变量$ref, 指向$bar
$ref =& GLOBALS['xxx']; // $ref被重新指向
}
f($bar);

unset一个引用, 只是断开了引用和实际变量的连接(php manual原话, 实际做了两件事情: 1从符号表删除这个引用变量的名字,2引用指向的zval的引用计数-1,当引用计数变为0时, zbal::is_ref也变为0), 引用指向的实际变量并不被销毁:

function f(){
global $var; // 相当于$var & = $GLOBALS['var']
unset $var;
}
// 这里的$var不受影响

引用计数: zval结构和引用计数 对一个变量$a使用unset, refcount会减1,

$a = "old string"; // 符号表里新增一个'a', 内存新建一个zval结构, 此时zal::is_ref是0,引用计数是1
$c = $b = $a; // 符号表新增b和c, 但为了效率没有给b和c新建zval, 而是指向同一个zval
xdebug_debug_zval( 'a' ); // a符号对应的zval的引用计数=3, is_ref变成1
$c = "new string"; // 发生写时复制, 这时候才给c新建一个zval
xdebug_debug_zval( 'a' ); // a引用计数=2
xdebug_debug_zval( 'c' ); // c是一个新的zval,c引用计数为1
unset( $b, $c ); // 从符号表删除b和c, b和c指向的zval各自引用计数-1, 当引用计数减为1时is_ref变为0, 当引用计数减为0时被gc
xdebug_debug_zval( 'a' ); // 计数=1
xdebug_debug_zval( 'c' ); // 已从符号表删除

变量与常量

  • 在函数内调用全局变量时需要用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风格正则

格式化输出

echo, print(), printf(), print_r(), var_dump():

  • echoprint都是语言结构, 有无括号均可使用, echo和print都不是函数所以没有返回值, 所以不要把echo当作if的判断条件;
  • echo后面可以是一般的变量(包括类的成员), 但不能是array和object;
  • printf是函数所以有返回值, 和C语言的printf基本一样.
  • print_r()var_dump()可以更详细的打印出类型和值;
echo "1", "2", "3"; // echo可以一次输出多个字符
echo ("$var"); // echo 也可以带括号
echo ("1", "2", "3"); // error! echo不是函数
echo "$_GET['action']"; // error!
echo "${_GET['action']}"; // $符号后加个大括号就正确了

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有如下几个转换规则:

    1. 如果key是可以转换成integer的(例如字符型的”8”,布尔型的true), 那么key会被转换为integer;
    2. 当key是float型, 其小数部分会被舍去, 自动转换为integer;
    3. 定义数组时, 多个元素使用同一个key, 这些元素的值会被最后一个覆盖, 比如arr = array(3.1 => '3.1', '3' => 'three');, 最终数组只有一个元素k,v = 3,'three';
    4. 使用[]符号访问数组成员时, 也符合以上的转换规则, 例如arr[3.1]和arr[‘3’]都会被默认转换为arr[3];

数组引用(references)

$worker = array("Fred","Willma");
$Clone = $worker; // worker的一份拷贝
$Ref =& $worker; // 不产生新的拷贝, 仅指向同一个对象

$worker[1] = 37; // $Clone发生写时复制
var_dump($Clone); // 值没有改变
var_dump($Ref); // 值改变了

原数组赋值null:

$worker = null;
var_dump($Clone); // 正常访问
var_dump($Ref); // NULL

删除原数组:

unset($worker); // 删除原来的数组
var_dump($Clone); // 正常访问
var_dump($Ref); // 正常访问

结论

  • Obj无论用= 还是=&, 修改其中一个都会影响另一个, 因为Obj的赋值是按引用传递的.
  • 但是Array和String属于基本类型, 使用=的时候是值传递, 改变一个另一个不受影响, 只有=&按引用传递时才会改变另一个.
  • unset一个引用只会断开引用和实际变量的连接, 并不会影响实际变量

问题

引用计数和写时复制

PHP的类型转换

php中的强制转换和c的语法类似, 强转为string类型还可以使用strval()函数;

$fst = (string) $foo; // c风格转换
echo strval(pow(2,50)); // 利用函数转换

转换为对象

  • 如果将一个对象转换成对象, 它将不会有任何变化.
  • 数组转换成对象将使键名成为属性名并具有相对应的值.
  • 如果基本类型的值被转换成对象, 将会创建一个内置类 stdClass 的实例. 如果该值为 NULL, 则新的实例为空. 名为 scalar 的成员变量将包含该值
  1. 基本类型转化为对象
$obj = (object) 'abc';  // 字符串转化为对象
echo $obj->scalar; // outputs 'abc'
  1. 数组转换为对象:
$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的===== :

    1. 字符串: 作为PHP的基本类型, 字符串==是比较值而非引用, 所以=====以及strcmp()都可以用来比较字符串;
    2. 数组==比较: 两个数组的key和value都相等则为true, 顺序可以不同, 类型不必严格相等;
    3. 数组===比较: 两个数组必须顺序一致, 并且key和value的类型也严格相等, 也可以使用array_diff()函数;
    4. 对象==比较: 成员的值相等, 并且两个对象是同一个class类型;
    5. 对象===比较: 必须指向同一个对象
  • 不同类型间的比较 :

    • 上面的情况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)才是真

流程控制

函数(方法)

  • 类对象作为参数/返回值: 都是按照引用的方式传递参数.
  • 函数返回引用:
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同时拥有constfinal关键字, const修饰常量和类成员常量, finalabstract都可以修饰抽象类和函数

上代码: 参考(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'); // 返回数组
if(!count($methods_arr)) { ... }
$vars_arr = get_class_vars('Class'); // 注意只返回有默认值的属性, 数组
// 用foreach(...)遍历类的属性
  • 对象检验: 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

// 文件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_createcurl_exec实现 具体参考代码WP_Http_Streams::request()WP_Http_Curl::request()
  • 安全问题: fsockopenpfsockopen, 后者是打开一个持续的连接, 在php.ini的选项disable_functions增加”fsockopen” 和allow_url_fopen=Off
  • IDC为什么禁用fsockopen、pfsockopen函数
  • 异步请求: curl_multi

php.ini配置

错误捕获

  • error_reporting=E_ALL & ~E_DEPRECATED & ~E_STRICT :
  • display_errors=On : 是否将错误信息作为输出的一部分显示到屏幕
  • error_log="D:\xampp\php\logs\php_error_log" : 设置脚本错误将被记录到的文件

文件编码

参考十步解决Php Utf-8编码:

  1. php文件本身必须是UTF-8编码. 不像Java会生成class文件, 避免这个问题
  2. php要输出头:header("Content-Type: text/html; charset=UTF-8")
  3. meta标签无所谓, 有header所有浏览器就会按header来解析
  4. 所有外围都得用UTF8, 包括数据库. *.js, *.css(CSS影响倒不大)
  5. php本身不是Unicode的, 所有substr之类的函数需改为mb_substr(需要装mbstring扩展);或者用 iconv functions转码

性能

参考 (http://www.imooc.com/video/4169)

  • apache benchmark
  • php解析-> 解释器
  • 内置函数的性能, 实现
  • 少用魔法函数
  • 禁用@错误抑制, @是如何实现的?
  • 及时unset()不使用的, Google: unset会有无法注销变量的情况
  • 正则对性能的影响
  • 数组的查找, map
  • IO: 磁盘, 数据库, 内存, 网络
  • 网络请求并行: curl_multi_*,

调试

  1. 使用echo,print,var_dump等在页面上显示错误, 需要修改php.ini的display_errors等选项, 参考, 额外的:debug_backtrace()
  2. 输出到文件: file_put_contents('debug.log',"msg...",FILE_APPEND) , 或者error_log($str, 3, 'errors.log'), 用法参考
  3. 线上环境调试: phpstrom+xdebug + chrome(debug helper) or firefox (easy xdebug)
  4. zend studio + zend debugger , 或者在 NetBeans IDE PHP 编辑器中调试 PHP 源代码

参考