0%

PHP常用

不定时维护

字符串分割成数组

$name = 'post.name.value';
echo json_encode(explode('.', $name));
// ["post","name","value"]
list($method, $name) = explode('.', $name, 2);
var_dump($method, $name);
// string(4) "post"
// string(10) "name.value"

foreach 的值,不能为 null

把字符串解析成日期

int strtotime ( string$time [, int$now ] )

其值相对于 now 参数给出的时间,如果没有提供此参数则用系统当前时间。

string date ( string$format [, int$timestamp ] )

返回将整数 timestamp 按照给定的格式字串而产生的字符串。
如果没有给出时间戳则使用本地当前时间。换句话说,timestamp 是可选的,默认值为 time()。

本周本月本年

<?php
// 本周
echo date('Y-m-d', strtotime('monday this week')); // 错误写法 strtotime('-1 monday') 解释为上一个星期一,如果当天为星期一则错误
echo date('Y-m-d', strtotime('sunday this week')); // 错误写法 strtotime('+1 sunday')
// 本月
echo date('Y-m') . '-01';
echo date('Y-m-t');
// 本年
echo date('Y') . '-01-01';
echo date('Y') . '-12' . date('-t', strtotime(date('Y') . '-12-01'));

json_encode 中文被转码

5.4 以后使用 JSON_UNESCAPED_UNICODE

json_encode($arr, JSON_UNESCAPED_UNICODE);

使用 and or 作逻辑运算

如果没有设置$name,就把$name 设置为 2

isset($name) or $name = 2;

如果设置了$name,就把$name 设置为 2

isset($name) and $name = 2;

转义字符串

htmlspecialchars

htmlspecialchars ( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") [, bool $double_encode = TRUE ]]] ) : string
字符 替换后
& (& 符号) &
“ (双引号) ",除非设置了 ENT_NOQUOTES
‘ (单引号) 设置了 ENT_QUOTES 后, ' (如果是 ENT_HTML401) ,或者 ' (如果是 ENT_XML1、 ENT_XHTML 或 ENT_HTML5)。
< (小于) <
> (大于) >

大写小写转换

  • 将字符串转换为小写形式

strtolower()

  • 将字符串转换为大写形式

strtoupper()

  • 将字符串的第一个字符转换为大写形式

ucfirst()

  • 将字符串中每一个单词的首字母转换为大写形式

ucwords()

大于小于等于不等于

EQ EQUAL 等于
NQ NOT EQUAL 不等于
GT GREATER THAN 大于
LT LESS THAN 小于
GE GREATER THAN OR EQUAL 大于等于
LE LESS THAN OR EQUAL 小于等于

修改字符编码到 UTF-8

mb_convert_encoding

mb_convert_encoding ( string $str , string $to_encoding [, mixed $from_encoding = mb_internal_encoding() ] ) : string

将 string 类型 str 的字符编码从可选的 from_encoding 转换到 to_encoding。
例子:

mb_convert_encoding($str, 'UTF-8');

//动态修改所有字符集到UTF-8
//修改字符集
$encode = mb_detect_encoding($str, array('ASCII', 'UTF-8', 'GB2312', 'GBK', 'BIG5'));
if ($encode != 'UTF-8') {
$str = iconv($encode, 'UTF-8', $str);
}

进一取整

向上取整

echo ceil(164.1); // 165

向下取整

echo floor(3.14159); // 3
echo floor(3.64159); // 3

使用 array_slice 分页

array_slice($data, $start, $limit);

转换换行符为 html 可识别

自动转换 \n<br />

nl2br($str);

php 收不到 authorization header

apache 环境下,默认不能获取 Authorization 信息,需要配置 apache/config/httpd.conf 文件,加上以下内容即可

<IfModule mod_rewrite.c>
SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0
</IfModule>

php7 的改进和优化

详见:https://www.php.net/manual/zh/migration70.php

  • PHP 7 引入了严格模式开关

PHP 是一个弱类型的语言,不过在 PHP 7 中支持变量类型的定义,引入了一个开关指令declare(strict_type=1);。这个指令一旦开启,就会强制当前文件下的程序遵循严格的函数传参类型和返回类型。不开启 strict_type,PHP 将会尝试转换成要求的类型;开启之后,PHP 不再做类型转换,类型不匹配就会抛出错误。

要使用严格模式,一个 declare 声明指令必须放在文件的顶部。这意味着严格声明标量是基于文件可配的。这个指令不仅影响参数的类型声明,还影响函数的返回值声明。

  • PHP 7 改进了错误处理机制

另外,在 PHP 7 中,很多致命错误以及可恢复的致命错误都被转换为异常来处理了。这些异常继承自 Error 类,此类实现了 Throwable 接口(所有异常都实现了这个基础接口)。

这也意味着,当发生错误的时候,以前代码中的一些错误处理的代码将无法被触发。因为在 PHP 7 版本中,已经使用抛出异常的错误处理机制了。(如果代码中没有捕获 Error 异常,就会引发致命错误)。

  • PHP 7 优化了 Zval

在 2013 年的时候,惠新宸和 Dmitry(PHP 语言内核开发者之一)就曾经在 PHP 5.5 的版本上做过一个 JIT(Just In Time,即时编译,一种软件优化技术)的尝试。

PHP 5.5 原来的执行流程是将 PHP 代码通过词法和语法分析编译成 opcode 字节码,然后 Zend 引擎读取这些 opcode 指令,逐条解析执行。他们在 opcode 环节后又引入了类型推断(TypeInf),然后通过 JIT 生成 ByteCodes 再执行。

采用这种技术优化,PHP 的效率在实际项目中并没有取得明显的提升,于是他们重新设计了 PHP 的底层语言结构。Zval 是存储 PHP 中变量的载体,是一个 C 语言实现的结构体(struct),PHP 5 的 Zval 在内存中占据 24 个字节,而在 PHP 7 中优化后的 Zval 只占 16 个字节,这样变量的存储变得非常简单和高效。

  • PHP 7 优化了数组

PHP 7 优化了数组的 HashTable 实现,PHP 5 的数组存储形式是一个支持双向链表的 HashTable,不仅支持通过数组的 key 来做 hash 映射访问元素,也能通过 foreach 以访问双向链表的方式遍历数组元素。

当我们通过 key 值访问一个元素内容的时候,有时需要 3 次的指针跳跃才能找对需要的内容。最重要的一点是,这些数组元素的存储是分散在各个不同的内存区域的,在 CPU 读取的时候,因为它们很可能不在同一级缓存中,导致 CPU 不得不到下级缓存甚至内存区域查找,从而引起 CPU 缓存命中下降,进而增加更多的耗时。

优化后的 Zend Array 最大的特点是整块的数组元素和 hash 映射表全部连接在一起,被分配在同一块内存中。如果是遍历一个整型的简单类型数组,效率会非常快,因为数组元素(Bucket)本身是连续分配在同一块内存里的,并且数组元素的 Zval 会把整型元素存储在内部,也不再有指针外链,全部数据都存储在当前内存区域内。

当然,最重要的是它能够避免 CPU 缓存命中率下降。

  • PHP 7 改进了函数调用

PHP 7 还改进了函数的调用机制,通过优化参数传递的环节减少了一些指令,提高执行效率。

hashTable

详见:https://gywbd.github.io/posts/2014/12/php7-new-hashtable-implementation.html
Hashtable 的概念实际上非常简单:字符串的 先会被传递给一个 hash 函数(hashing function,中文也翻译为散列函数),然后这个函数会返回一个整数(我们把它叫做 hash 值),而这个整数就是”通常”的数组的索引(hashTable 的索引是键的散列)。

问题是对于两个不同的字符串,调用 hash 函数会得到同一个 hash 值,而现实情况是任意字符串都可以作为键,所以键会有无数个,而数组的大小必须是提前设定好的,因为 hash 值必须小于数组索引的最大值,所以可以生成的 hash 值必须是有限的。这样用有限的 hash 值表示无限的键,必然会导致冲突。我们把两个不同的键的 hash 值是一样的情况称为冲突,任何 Hashtable 算法都必须提供某种机制解决这种冲突。

有两种主要的处理冲突的方法:

  • 开放定址法:当冲突发生的时候,冲突的元素会被保存到一个不同的索引中;
  • 链接法:所有拥有相同的 hash 值的元素,它们都会被保存到一个链表中。PHP 使用的就是第二种方法。

老 hashTable

bucket
typedef struct bucket {
ulong h;
uint nKeyLength;
void *pData;
void *pDataPtr;
struct bucket *pListNext;
struct bucket *pListLast;
struct bucket *pNext;
struct bucket *pLast;
char *arKey;
} Bucket;
  • pNext:指向下一个 bucket 的指针
  • pLast:指向前一个 bucket 的指针
  • pListNext:记录插入顺序的双向链表,前向指针存储在 pListNext
  • pListLast:记录插入顺序的双向链表,后向指针存储在 pListLast 中,还有一个指向列表开头 ( pListHead) 和列表结尾(pListLast)的指针,如下图

doubly_linked_hashtable.svg)ordered_hashtable.svg

  • pData 是指向存储值的指针,存储的为值的副本,且与 bucket 分开分配内存。但如果存储的值为一个指针时,重新分配内存显然很低效(因为指针内存占用极小,但分配内存的过程耗费资源),此时 PHP 会将指针放入 pDataPtr 成员中(而不是单独分配的内存中),pData 然后指向 pDataPtr,即 pData = &pDataPtr

总结:

  • 冲突处理双向链表中的元素被称为”buckets”。每一个 bucket 都是单独分配的(内存分配总是低效的,单独分配意味着内存地址并非连续,又会降低 CPU 缓存的效率(随机读取慢于连续读取),双向链表均不要求内存的连续,内存分配每次还额外需要分配 8/16(32 位/64 位)个字节冗余)
  • 键和值都是保存在这些 buckets 中,值会存放 zval 结构,这些结构也是分开单独分配内存的,它们的大小是 16 字节(32 位系统)或 24 个字节(64 位系统),由于键值都保存在 buckets 中,需在每个 bucket 中保存一个指向 zval 结构的指针
  • 冲突处理双向链表的旁边有另外一个双向链表,用于保存数组中元素的插入顺序,见上方pListNext pListLast
  • 双向链表中的每个 bucket 需要 4 个指针用于链表的连接,这会带来 16/32(32 位/64 位)个字节的开销,遍历这种链表也不利于缓存(cache-unfriendly)操作(遍历非连续地址随机读取)
HashTable
typedef struct _hashtable {
uint nTableSize;
uint nTableMask;
uint nNumOfElements;
ulong nNextFreeElement;
Bucket *pInternalPointer;
Bucket *pListHead;
Bucket *pListTail;
Bucket **arBuckets;
dtor_func_t pDestructor;
zend_bool persistent;
unsigned char nApplyCount;
zend_bool bApplyProtection;
#if ZEND_DEBUG
int inconsistent;
#endif
} HashTable;
  • arBuckets:包含链接的存储桶列表并由键的散列索引的数组
  • nTableMask:存储称为表掩码的 nTableSize - 1 的值
  • nTableSize:哈希表大小

总结:

  • nTableSize 始终是 2 的幂,如果哈希表(arBuckets 数组)中有 12 个元素,则实际哈希表大小(nTableSize)将为 16,bucket 中 h 为幂大小,即哈希表大小为 16 时 h=4。当 nTableSize 始终是 2 的幂时,可以通过位运算 h & (nTableSize - 1) 取余计算落到哪个桶中,而不是 h % nTableSize 的低效的方式
  • arBuckets 数组会自动增长,但删除元素时它不会缩小,例如将 1000000 个元素插入哈希表,然后再次删除所有元素,则 nTableSize 仍然是 1048576

新 hashTable

新 zval
struct _zval_struct {
zend_value value;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type,
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved
)
} v;
uint32_t type_info;
} u1;
union {
uint32_t var_flags;
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* literal cache slot */
uint32_t lineno; /* line number (for ast nodes) */
} u2;
};
  • value:zend_value 联合体有 8 个字节,它可以保存任何类型的值,包括整数、字符串、数组等。具体保存什么取决于 zval 的类型。
  • type_info:占 4 字节,它包含变量的真正类型(类似于 IS_STRING、IS_ARRAY),以及一系列的标志位,用于提供跟类型相关的信息。例如,如果 zval 保存的是一个对象,那么这些类型标志位会说明它是一个非常量(non-constant)、可引用计数(refcounted)、可垃圾回收(garbage-collectible)、不可复制(non-copying)的类型。
  • 最后一部分占有 4 个字节,通常情况下不会被用到(它只是用于填充内存,如果不存在的话,编译器也会自动实现)(对于这一点完全不了解的同学,可以自行搜索内存对齐)。然而,在某些特殊情况下,这些空间也会被用于存放一些额外的信息。例如,AST(抽象语法树)的节点使用它来存放行号,VM(虚拟机)常量使用它来存放缓冲槽的索引,以及 Hashtable 使用它来保存冲突处理链上的下一个元素——这一部分才是我们要重点关注的。

总结:
新 zval 相比 php5 没有 refcount 字段。这是因为新的 zval 将不会被单独分配,它会被直接嵌入到任何需要存放它的地方(例如,一个 hashtable bucket 中)。所以 zvals 将不再需要使用引用计数(refcounting),复杂数据类型例如字符串、数组、对象和资源(resources)仍需要使用。所以新的 zval 的设计将引用计数(包括跟垃圾回收相关的信息)从 zval 转移到了数组/对象/等中。这种方式有很多优点:

  • 保存简单的值(例如,boolean、integer 或者 float)的 zval 将不再需要额外分配内存。所以避免内存分配的头部冗余(allocation header overhead),以及减少不必要的内存分配和内存释放,可以提高缓存的局部性,从而提高性能。
  • 保存简单的值的 zval 不需要保存 refcount 和 GC 的根缓冲区。
  • 避免两次引用计数。例如,以前的对象即使用了 zval 的引用计数,又使用额外的对象的引用计数,对于支持按对象传递的语义而言,这是必须的。
  • 现在所有的复杂的值都内嵌一个引用计数,它们可以不依赖于 zval 的机制而进行共享。特别是字符串现在也有可能共享。这对于 hashtable 的实现也很重要,因为这样就不用再拷贝非 interned 字符串的键了。
Bucket
typedef struct _Bucket {
zend_ulong h;
zend_string *key;
zval val;
} Bucket;
  • h:一个 hash 值,整数键会保存在字段 h 中(整数键的 hash 值和整数键是一样的),在这种情况下 key 字段的值将是 NULL
  • key:一个字符串键
  • val:一个 zval 值,zval 是直接嵌入到 bucket 结构体里面,就没有必要单独为它分配内存,这样就不会因为内存分配而产生冗余信息,从而减少内存的浪费
HashTable
typedef struct _HashTable {
uint32_t nTableSize;
uint32_t nTableMask;
uint32_t nNumUsed;
uint32_t nNumOfElements;
zend_long nNextFreeElement;
Bucket *arData;
uint32_t *arHash;
dtor_func_t pDestructor;
uint32_t nInternalPointer;
union {
struct {
ZEND_ENDIAN_LOHI_3(
zend_uchar flags,
zend_uchar nApplyCount,
uint16_t reserve)
} v;
uint32_t flags;
} u;
} HashTable;

总结:

  • arData 数组保存了所有的 buckets(也就是数组的元素),这个数组被分配的内存大小为 2 的幂次方,它被保存在 nTableSize 这个字段(最小值为 8)
  • 数组中实际保存的元素的个数被保存在 nNumOfElements 这个字段
  • arData 这个数组直接包含 Bucket 结构
  • 老的 Hashtable 的实现是使用一个指针数组(Bucket **arBuckets)来保存分开分配的 buckets,这意味着需要更多的分配和释放操作(alloc/frees),需要为冗余信息及额外的指针分配内存
请我喝杯咖啡吧 Coffee time !