对于PHPer来说,OOP是必不可少的开发思维,但是对于PHP类和对象的底层实现你了解多少呢? 本着知其然、知其所以然的思路,让我们一起上去寻找答案吧~
类的底层实现可以看成是变量、函数等的知识集合,我们之前讲过。所以想要更深入了解的朋友最好查看我之前介绍变量和函数的文章
类数据结构
不管是普通类、具体类还是socket,都是用统一的结构体存储的,但是中间代码生成的时候,会被添加到全局类列表中。其实也是在这类名将用于确定该类是否已存在。 如果存在则添加失败。
struct _zend_class_entry { char type; // 和函数一样,类被拆分为两种类型:ZEND_INTERNAL_CLASS 内部类型和ZEND_USER_CLASS 用户自定义类型 char *name;// 类名称 zend_uint name_length; // 即sizeof(name) - 1 struct _zend_class_entry *parent; // 继承的父类 int refcount; // 引用数 zend_bool constants_updated; zend_uint ce_flags; //类的类型,在编译阶段被区分是普通类,接口,抽象类 HashTable function_table; // 静态类方法和普通类方法存放集合 HashTable default_properties; // 默认属性存放集合 HashTable properties_info; // 属性信息存放集合 HashTable default_static_members;// 类本身所具有的静态变量存放集合 HashTable *static_members; // type == ZEND_USER_CLASS时,取&default_static_members; // type == ZEND_INTERAL_CLASS时,设为NULL HashTable constants_table; // 常量存放集合 struct _zend_function_entry *builtin_functions;// 方法定义入口 /* 魔术方法 */ //所有魔术方法单独存放,初始化时被设置为null union _zend_function *constructor; union _zend_function *destructor; union _zend_function *clone; union _zend_function *__get; union _zend_function *__set; union _zend_function *__unset; union _zend_function *__isset; union _zend_function *__call; union _zend_function *__tostring; union _zend_function *serialize_func; union _zend_function *unserialize_func; zend_class_iterator_funcs iterator_funcs;// 迭代 /* 类句柄 */ zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC); zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object, intby_ref TSRMLS_DC); /* 类声明的接口 */ int(*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type TSRMLS_DC); /* 序列化回调函数指针 */ int(*serialize)(zval *object, unsignedchar**buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC); int(*unserialize)(zval **object, zend_class_entry *ce, constunsignedchar*buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC); zend_class_entry **interfaces; // 类实现的接口 zend_uint num_interfaces; // 类实现的接口数 char *filename; // 类的存放文件地址 绝对地址 zend_uint line_start; // 类定义的开始行 zend_uint line_end; // 类定义的结束行 char *doc_comment; zend_uint doc_comment_len; struct _zend_module_entry *module; // 类所在的模块入口:EG(current_module) };
从前面的代码可以看出php 类,类的成员变量和成员方法都存储在各自的结构体中,并且该结构体的数据结构和之前讲解的变量和函数的数据结构是一模一样的,只不过编译后的成员变量和成员方法存储在类结构中
对象创建
我们都知道对象是new的,从底层来看,对象生成有3步
第一步:根据类名查找全局类列表中是否存在该类,如果存在则获取存储类的变量
步骤2:判断该类是否为普通类(非表征类或socket); 如果是普通类,则分配显存给要创建的对象存储的zval容器,并设置容器类型为IS_OBJECT
第三步:进行对象初始化操作,并将对象添加到全局对象列表(对象池)中
附上对象的数据结构:
typedef struct _zend_object { zend_class_entry *ce; //对象的类结构 HashTable *properties; //对象属性 HashTable *guards; /* protects from __get/__set ... recursion */ } zend_object;
获取和设置成员变量
获取成员变量:
第一步,获取对象的属性,从对象的属性中查找是否存在与名称对应的属性。 如果有返回结果,如果不存在则转第二步
第二步,如果有get魔术,调用该方法获取变量,如果不存在则报错
设置成员变量:
第一步,获取对象的属性php 类,从对象的属性中检查是否存在与名称对应的属性。 如果存在且现有值与要设置的值相同,则不进行任何操作; 否则,将执行变参数操作。 如果不存在,则转第二步
第二步,如果_set魔术存在,则调用该方法设置变量,如果不存在,则转到第三步
第三步,如果该成员变量从未被设置过,则直接将该变量添加到该对象的properties数组所在的HashTable中。
总结
到昨天为止,我们差不多已经讲完了php的底层原理。 其实这段时间,很多朋友告诉我,现在已经逐渐开始使用php7了,而你现在讲解的内容还是php5,会不会过时了? 虽然我对php5的解释也是为了解释php7,但是php7实际上是php5的扩展。 了解了php5之后,再了解php7就会更容易了。 而php是从php5逐步建立起来的,我们需要了解php5的内容。 不过从明天开始,我们就要开始从底层开始比较php7和php5的区别了,敬请期待~
加密已经成为语言编程中的重要步骤之一,尽管不是时候。 为了保护独立开发成果,越来越多的程序员将加密融入到自己的开发项目中。 PHP是日常生活中使用的编程语言之一。 我们来看看PHP的加密方式。
1.MD5加密
stringmd5(字符串$str[,布尔$raw_output=false])
范围
str - 原始字符串。
raw_output - 如果可选的 raw_output 设置为 TRUE,则 MD5 消息摘要将以 16 字节宽的原始二进制补码格式返回。
这是不可逆加密,执行以下代码
$密码='123456';
echomd5($密码);
结果是 e10adc3949ba59abbe56e057f20f883e
2、地穴加密
stringcrypt(字符串$str[,字符串$salt])
crypt() 返回基于标准 UNIX DES 算法或系统上可用的其他替代算法的哈希字符串。
范围
str - 要散列的字符串。
salt - 可选的盐字符串。 如果未提供,算法行为将由不同的算法实现决定,并可能导致不可预测的结果。
这也是不可逆加密php解密,执行以下代码
代码如下所示:
$密码='123456';
$salt="test";//只取前两项
echocrypt($密码,$盐);
得到的结果是teMGKvBPcptKo
使用手动盐的反例如下:
代码如下所示:
$password=crypt('mypassword');//手动生成salt值
/* 您应该使用 crypt() 的完整结果作为密码校准的盐,以防止使用不同的哈希算法引起的问题。 (如上所述,基于标准 DES 的密码哈希使用 2 字符盐,基于 MD5 的哈希使用 12 字符盐。) */
if(crypt('mypassword', $password)==$password){
echo "密码已验证!";
执行结果是输出Passwordverified!
使用不同哈希类型的 crypt() 的示例如下:
代码如下所示:
如果(CRYPT_STD_DES==1){
echo '标准DES:'.crypt('rasmuslerdorf','rl').“n”;
如果(CRYPT_EXT_DES==1){
echo '扩展DES:'.crypt('rasmuslerdorf','_J9..rasm').“n”;
如果(CRYPT_MD5==1){
echo 'MD5:'.crypt('rasmuslerdorf','$1$rasmusle$').“n”;
如果(CRYPT_BLOWFISH==1){
echo 'Blowfish:'.crypt('rasmuslerdorf', '$2a$07$usesomesillystringforsalt$').“n”;
如果(CRYPT_SHA256==1){
echo 'SHA-256:'.crypt('rasmuslerdorf', '$5$rounds=5000$usesomesillystringforsalt$').“n”;
如果(CRYPT_SHA512==1){
echo 'SHA-512:'.crypt('rasmuslerdorf', '$6$rounds=5000$usesomesillystringforsalt$').“n”;
结果如下
标准DES:rl.3StKT.4T8M
扩展DES:_J9..rasmBYk8r9AiWNc
MD5:$1$rasmusle$rISCgZzpwk3UhDidwXvin0
河豚:$2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi
SHA-256:$5$rounds=5000$usesomesillystri$KqJWpanXZHKq2BOB43TSaYhEWsQ1Lr5QNyPCDH/Tp.6
SHA-512:$6$回合=5000$usesomesillystri$D4IrlXatmP7rx3P3InaxBeooomnAihCKRVQP22JZ6EY47Wc6BkroIuUUBOov1i.S5KPgErtP/EN5mcO.ChWQW21
在 crypt() 函数支持多个哈希的系统上,根据相应类型是否可用,以下常量设置为 0 或 1:
CRYPT_STD_DES – 基于标准 DES 算法的哈希,使用“./0-9A-Za-z”字符集中的两个字符作为盐。 在 salt 中使用非法字符将导致 crypt() 失败。
CRYPT_EXT_DES – 基于 DES 算法的扩展哈希。 其盐值是一个 9 个字符的字符串,由逗号后跟 4 字节循环计数和 4 字节盐值组成。 它们被编码为可再现的字符,每个字符 6 位,最低有效位在前。 0 到 63 被编码为“./0-9A-Za-z”。 在 salt 中使用非法字符将导致 crypt() 失败。
CRYPT_MD5 – MD5 哈希使用以 $1$ 开头的 12 字符字符串盐。
CRYPT_BLOWFISH – Blowfish 算法使用以下盐值:“$2a$”、两位数的成本参数“$”以及由“./0-9A-Za-z”中的字符组成的 64 位字符串。 在 salt 中使用此范围之外的字符将导致 crypt() 返回空字符串。 两位数的cost参数是以2为底的周期数的对数,其范围是04-31。 如果超过这个范围,crypt()将会失败。
CRYPT_SHA256 – SHA-256 算法使用以 $5$ 开头的 16 个字符的字符串盐值进行哈希处理。 如果salt值字符串以“rounds=$”开头,则N的数值将用于指定要执行的哈希轮数,很像Blowfish算法的成本参数。 默认循环次数为 5000,最小值为 1000,最大值为 999,999,999。 超出此范围的 N 将转换为最接近的值。
CRYPT_SHA512 – SHA-512 算法使用以 $6$ 开头的 16 个字符的字符串盐值进行哈希处理。 如果salt值字符串以“rounds=$”开头php解密,则N的数值将用于指定要执行的哈希轮数,很像Blowfish算法的成本参数。 默认循环次数为 5000,最小值为 1000,最大值为 999,999,999。 超出此范围的 N 将转换为最接近的值。
3.Sha1加密
stringsha1(字符串$str[,布尔$raw_output=false])
范围
str - 输入字符串。
raw_output - 如果可选的 raw_output 参数设置为 TRUE,则 sha1 摘要将以原始格式返回,宽度为 20 个字符,否则返回值为宽度为 40 个字符的十六进制数字。
这也是一种不可逆加密,执行以下代码:
$密码='123456';
echosha1($密码);
得到的结果是7c4a8d09ca3762af61e59520943dc26494f8941b
以上几种看起来都是不可逆加密,查字典也能揭晓。 下面的地址提供了显示里面加密结果的函数。
4. URL编码加解密技术
stringurlencode(字符串$str)
该函数用于对字符串进行编码并在 URL 的请求部分中使用它,也用于将变量传递到下一页。
返回一个字符串,其中包含除 -_ 之外的所有非字母数字字符。 替换为百分号 (%),后跟两位十六进制数字,空格编码为减号 (+)。 这种编码方式与WWW表单POST数据的编码方式相同,也与application/x-www-form-urlencoded的媒体类型编码方式相同。 由于历史原因,此编码与 RFC1738 编码的不同之处在于编码空间为减号 (+)。
stringurldecode(字符串$str)
解码给定编码字符串中的任何 %##。 减号 ('+') 被解码为空格字符。
这是可逆加密,使用urlencode方法进行加密,使用urldecode方法泄密,执行以下代码:
$url='';
$encodeUrl=urlencode($url);
echo $encodeUrl."n";//如果网页上显示则将n改为
echoldecode($encodeUrl);
得到的结果如下
http%3A%2F%2F%2FCraryPrimitiveMan%2F
基于RFC3986的URL加密方法如下:
代码如下所示:
函数 myUrlEncode($string){
$entities=array('%21','%2A','%27','%28','%29','%3B','%3A','%40','%26', “%3D”、“%2B”、“%24”、“%2C”、“%2F”、“%3F”、“%25”、“%23”、“%5B”、“%5D”) ;
$replacements=array('!','*',"'", "(", ")", ";", ":", "@", "&", "=", "+", " $", ",", "/", "?", "%", "#", "[", "]");
返回 str_replace($entities, $replacements, urlencode($string));
5、Base64信息编码与加密
stringbase64_encode(字符串$数据)
使用base64对数据进行编码。
这种编码的设计使得二进制补码数据可以通过非纯 8 位传输层(例如电子文本消息的正文)进行传输。
Base64 编码的数据比原始数据多占用约 33% 的空间。
stringbase64_decode(字符串$data[,bool$strict=false])
解码 Base64 编码的数据。
范围
数据——编码数据。
strict - 如果输入数据超过 base64 字母表,则返回 FALSE。
执行以下代码:
代码如下所示:
$name='CraryPrimitiveMan';
$encodeName=base64_encode($name);
echo $encodeName."n";
echobase64_decode($encodeName);
结果如下
代码如下所示:
Q3JhcnlQcmltaXRpdmVNYW4=
疯狂的原始人
推荐phpass
使用 phpass 0.3 进行测试,这是一种标准的哈希形式,用于在将用户密码存储到数据库之前保护用户密码。 许多常用的哈希算法(例如 md5 甚至 sha1)对于密码存储来说都是不安全的,因为黑客可以使用这些算法轻松破解密码。
对密码进行哈希处理的最安全方法是使用 bcrypt 算法。 开源 phpass 库在一个方便的类中提供了此功能。
代码如下所示:
6. 自定义数字加解密算法
主要使用按位运算符对数字进行加密和解密。
(一)实施过程
(2)结果检验
例如,输入“123456789”进行测试,您将得到加密后的结果。
然后点击“解密”即可得到密文结果。