标签 - PHP内核

PHP内核    2019-04-25 16:30:15    14    0    0

在维基百科中,对命名空间的定义是: 命名空间(英语:Namespace)表示标识符(identifier)的上下文(context)。一个标识符可在多个命名空间中定义, 它在不同命名空间中的含义是互不相干的。在编程语言中,命名空间是一种特殊的作用域,它包含了处于该作用域内的标识符, 且本身也用一个标识符来表示,这样便将一系列在逻辑上相关的标识符用一个标识符组织了起来。 函数和类的作用域可被视作隐式命名空间,它们和可见性、可访问性和对象生命周期不可分割的联系在一起。

命名空间可以看作是一种封装事物的方法,同时也可以看作是组织代码结构的一种形式,在很多语言中都可以见到这种抽象概念和组织形式。 在PHP中,命名空间用来解决在编写类库或应用程序时创建可重用的代码如类或函数时碰到的两类问题:

  1. 用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。
  2. 为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性。

PHP从5.3.0版本开始支持命名空间特性。看一个定义和使用命名空间的示例:

01<?php
02namespace tipi;
03class Exception {
04    public static $var 'think in php internal';
05}
06  
07const E_ALL = "E_ALL IN Tipi";
08  
09function strlen(){
10    echo 'strlen in tipi';
11}
12  
13echo Exception::$var;
14echo strlen(Exception::$var);
15?>

如上所示,定义了命名空间tipi,在这个命名空间内定义了一个Exception类,一个E_ALL常量和一个函数strlen。 这些类、常量和函数PHP默认已经实现。假如没有这个命名空间,声明这些类、常量或函数时会报函数重复声明或类重复声明的错误, 并且常量的定义也不会成功。

从PHP语言来看,命名空间通过 namespace 关键字定义,在命名空间内,可以包括任何合法的PHP代码,但是它的影响范围仅限于类、常量和函数。 从语法上来讲,PHP支持在一个文件中定义多个命名空间,但是不推荐这种代码组织方式。 当需要将全局的非命名空间中的代码与命名空间中的

PHP内核    2019-04-25 16:29:50    14    0    0

这一节主要描述与对象属性有关的东西。有关如何对它进行定义的操作我们已经在上一章中描述过了,这里不再叙述,只讲对其的操作。

读取对象的属性

1ZEND_API zval *zend_read_property(zend_class_entry *scope, zval *object, char *name, int name_length, zend_bool silent TSRMLS_DC);
2 
3ZEND_API zval *zend_read_static_property(zend_class_entry *scope, char*name, int name_length, zend_bool silent TSRMLS_DC);

zend_read_property函数用于读取对象的属性,而zend_read_static_property则用于读取静态属性。可以看出,静态属性是直接保存在类上的,用具体的对象无关。

silent参数:

  • 0: 如果属性不存在,则抛出一个notice错误。
  • 1: 如果属性不存在,不报错。

如果所查的属性不存在,那么此函数将返回IS_NULL类型的zval。

更新对象的属性:

1ZEND_API void zend_update_property(zend_class_entry *scope, zval *object, char *name, int name_length, zval *value TSRMLS_DC);
2ZEND_API int zend_update_static_property(zend_class_entry *scope, char*name, int name_length, zval *value TSRMLS_DC);

zend_update_property用来更新对象的属性,zend_update_static_property用来更新类的静态属性。如果对象或者类中没有相关的属性,函数将自动的添加上。

读写对象与类属性的实例

假设我们已经在扩展中定义好下面的类:

01class baby
02{
03    public $age;
04    public static $area;
05     
06    public function __construct($age$area)
07    {
08   
PHP内核    2019-04-25 16:29:25    10    0    0

为了操作一个对象,我们需要先获取这个对象的实例,而这有肯定会涉及调用对象的构造方法。首先我们先了解下一个object在PHP内核中到底是如何实现的。

01typedef struct _zend_object_value {
02    zend_object_handle handle;
03    zend_object_handlers *handlers;
04} zend_object_value;
05 
06//此外再回顾一下zval的值value的结构。
07typedef union _zvalue_value {
08    long lval;                  /* long value */
09    double dval;                /* double value */
10    struct {
11        char *val;
12        int len;
13    } str;
14    HashTable *ht;              /* hash table value */
15    zend_object_value obj;
16} zvalue_value;

如果我们有一个zval *tmp,那么tmp->value.obj来访问到最终保存对象实例的zend_object_value结构体,它包含两个成员:

  • zend_object_handle handle:最终实现是一个unsigned int值,Zend会把每个对象放进数组里,这个handle就是此实例的索引。所以我们在把对象当作参数传递时,只不过是传递的handle罢了,这样对性能有利,同时也是对象的引用机制的原理。
  • zend_object_handlers *handlers:这个里面是一组函数指针,我们可以通过它来对象进行一些操作,比如:添加引用、获取属性等。此结构体在Zend/zend_object_handlers.h里定义。

下面我给出这个类的PHP语言实现,让我们在扩展中实现它,并生成它。

01<?php
02class baby
03{
04    public function __construct()
05    {
06        echo "a new baby!\n";
07    }  
08     
09    p
PHP内核    2019-04-25 16:29:00    19    0    0

对象是我们可以进行研究的任何事物,世间万物都可以看作对象。它不仅可以表示我们可以看到的具体事物, 也可以表示那些我们看不见的事件等。对象是一个实体,它具有状态,一般我们用变量来表示, 同时它也可以具有操作行为,一般用方法来表示,对象就是对象状态和对象行为的集合体。

在之前我们很多次的说到类,对于对象来说,具有相同或相似性质的对象的抽象就是类。 因此,对象的抽象是类,类的具体化就是对象,我们常常也说对象是类的实例。 从对象的表现形式来看,它和一般的数据类型在形式上十分相似,但是它们在本质是不同的。 对象拥有方法,对象间的通信是通过方法调用,以一种消息传递的方式进行。 而我们常说的面向对象编程(OOP)使得对象具有交互能力的主要模型就是消息传递模型。 对象是消息传递的主体,它可以接收,也可以拒绝外界发来的消息。

这一小节,我们从源码结构来看看PHP实现对象的方法以及其消息传递的方式。

对象的结构

对象在PHP中是使用一种zend_object_value的结构体来存储。

1typedef struct _zend_object_value {
2    zend_object_handle handle;
3        //  unsigned int类型,EG(objects_store).object_buckets的索引
4    zend_object_handlers *handlers;
5} zend_object_value;

PHP内核会将所有的对象存放在一个对象列表容器中,这个列表容器是保存在EG(objects_store)里的一个全局变量。 上面的handle字段就是这个列表中object_buckets的索引。当我们需要在PHP中存储对象的时候, PHP内核会根据handle索引从对象列表中获取相对应的对象。而获取的对象有其独立的结构,如下代码所示:

1typedef struct _zend_object {
2    zend_class_entry *ce;
3    HashTable *properties;
4    HashTable *guards; /* protects from __get/__set ... recursion */
5} zend_object;

ce是存储该对象的类结构,properties是一个HashTable,用来存放对象

PHP内核    2019-04-25 16:28:33    11    0    0

在面向对象语言中,都会内置一些语言内置提供的基本功能类,比如JavaScript中的Array,Number等类, PHP中也有很多这种类,比如Directory,stdClass,Exception等类,同时一些标准扩展比如PDO等扩展中也会定义一些类, PHP中类是不允许重复定义的,所以在编写代码时不允许定义已经存在的类。

同时PHP中有一些特殊的类:self,static和parent,相信读者对这self和parent都比较熟悉了,而static特殊类是PHP5.3才引入的。

PHP中的static关键字非常多义:

  • 在函数体内的修饰变量的static关键字用于定义静态局部变量。
  • 用于修饰类成员函数和成员变量时用于声明静态成员。
  • (PHP5.3)在作用域解析符(::)前又表示静态延迟绑定的特殊类。

这个关键字修饰的意义都表示"静态",在PHP手册中提到self, parent和static这几个关键字,但实际上除了static是关键字以外,其他两个均不是关键字, 在手册的关键字列表中也没有这两个关键字, 要验证这一点很简单:

1<?php
2var_dump(self); // ->  string(4) "self"
3?>

上面的代码并没有报错,如果你把error_reporting(E_ALL)打开,就能看到实际是什么情况了: 运行这段代码会出现“ Notice: Use of undefined constant self - assumed 'self'“, 也就是说PHP把self当成一个普通常量了,尝试未定义的常量会把产量本身当成一个字符串, 例如上例的”self",不过同时会出一个NOTICE,这就是说self这个标示符并没有什么特殊的。

1<?php
2define('self'"stdClass");
3echo self; // stdClass
4?>

不同语言中的关键字的意义会有些区别,Wikipedia上的解释是: 具有特殊含义的标示符或者单词,从这个意义上说$this也算是一个关键字,但在PHP的关键字列表中并没有。 PHP的关键字和C/C++一样属于保留字(关键字),关键字用于表示特定的语法形式,例如函数定义,流程控制等结构。 这些关键字有他们的特定的使用场景,而上面提到的self和parent并没有这样的限制。

self,parent,static类

前面

PHP内核    2019-04-25 16:27:29    12    0    0

PHP中有一些特殊的函数和方法,这些函数和方法相比普通方法的特殊之处在于:用户代码通常不会主动调用, 而是在特定的时机会被PHP自动调用。在PHP中通常以"__"打头的方法都作为魔术方法, 所以通常不要定义以"__"开头的函数或方法。 例如:__autoload()函数, 通常我们不会手动调用这个函数, 而如果在代码中访问某个未定义的方法, 如过已经定义了__autoload()函数,此时PHP将会尝试调用__autoload()函数, 例如在类的定义中如果定义了__construct()方法, 在初始化类的实例时将会调用这个方法, 同理还有__destuct()方法, 详细内容请参考PHP手册。

魔术函数和魔术方法

前面提到魔术函数和魔术方法的特殊之处在于这些方法(在这里把函数和方法统称方法)的调用时机是在某些特定的场景才会被触发, 这些方法可以理解为一些事件监听方法, 在事件触发时才会执行。

根据前面的介绍, 魔术方法就是在类的某些场景下触发的一些监听方法。这些方法需要在类定义中进行定义, 在存储上魔术方法自然存储于类中, 而类在PHP内部是一个_zend_class_entry结构体,与普通方法一样, 只不过这些类不是存储在类的函数表, 而是直接存储在类结构体中:

  • _zend_class_entry结构体中的存储位置不同;
  • 由ZendVM自动分情境进行调用;
  • 不是必须的,按需定义,自动调用

从以上三个方面可以发现,关于魔术变量的关键理解,主要集中在两个方面:一,定义在哪里; 二,如何判断其存在并进行调用。

首先,魔术变量的存储在_zend_class_entry中的代码如下:

01struct _zend_class_entry {
02    ...
03    //构造方法 __construct
04    union _zend_function *constructor;
05    //析构方法 __destruct
06    union _zend_function *destructor;
07    //克隆方法 __clone
08    union _zend_function *clone;
09    union _zend_function *__get;
10    union _zend_function *__set;
11    union _zend_fu
PHP内核    2019-04-25 16:26:30    4    0    0

继承

继承是一种关联类的层次模型,它可以建立类之间的关系,并实现代码重用,方便系统扩展。 继承提供了一种明确表述共性的方法,是一个新类从现有的类中派生的过程。 继承产生的新类继承了原始类的特性,新类称为原始类的派生类(或子类), 而原始类称为新类的基类(或父类)。派生类可以从基类那里继承方法和变量, 并且新类可以重载或增加新的方法,使之满足自己的定制化的需要。

PHP中使用extends关键字来进行类的继承,一个类只能继承一个父类。 被继承的成员方法和成员变量可以使用同名的方法或变量重写,如果需要访问父类的成员方法或变量可以 使用特殊类parent来进行。

PHP内核将类的继承实现放在了"编译阶段",因此使用VLD生成中间代码时会发现并没有关于继承的相关信息。 通过对extends关键字的词法分析和语法分析,在Zend/zend_complie.c文件中 找到继承实现的编译函数zend_do_inheritance()。其调用顺序如下: [zend_do_early_binding] --> [do_bind_inherited_class()] --> [zend_do_inheritance()]

01ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent_ce TSRMLS_DC)
02{
03    //  ...省略  报错处理 接口不能从类继承,final类不能继承
04  
05    //  ...省略 序列化函数和反序列化函数 如果当前类没有,则取父类的
06  
07    /* Inherit interfaces */
08    zend_do_inherit_interfaces(ce, parent_ce TSRMLS_CC);
09  
10    /* Inherit properties */
11    zend_hash_merge(&ce->default_properties, &parent_ce->default_properties, (void (*)(void *)) zval_add_ref, NULL, sizeof(zval *), 0);
12    if (parent_ce->type != ce->type) {
13    
PHP内核    2019-04-25 16:25:42    8    0    0

面向对象的三大特性(封装、继承、多态),其中封装是一个非常重要的特性。封装隐藏了对象内部的细节和实现, 使对象能够集中而完整的描述并对应一个具体的事物, 只提供对外的访问接口,这样可以在不改变接口的前提下改变实现细节,而且能使对象自我完备。 除此之外,封装还可以增强安全性和简化编程。 在面向对象的语言中一般是通过访问控制来实现封装的特性。 PHP提供了public、protected及private三个层次访问控制。这和其他面向对象的语言中对应的关键字语义一样。 这几个关键字都用于修饰类的成员:

  • private 用于禁止除类本身以外(包括继承也属于非类本身)对成员的访问,用于隐藏类的内部数据和实现。
  • protectd 用于禁止除本类以及继承该类的类以外的任何访问。同样用于封装类的实现,同时给予类一定的扩展能力, 因为子类还是可以访问到这些成员。
  • public 最好理解,被public修饰的成员可以被任意的访问。

如果没有设置访问控制关键字,则类的成员方法和成员变量会被设置成默认的 public。

这三个关键字在语法解析时分别对应三种访问控制的标记:

1member_modifier:
2    T_PUBLIC                { Z_LVAL($$.u.constant) = ZEND_ACC_PUBLIC; }
3|   T_PROTECTED             { Z_LVAL($$.u.constant) = ZEND_ACC_PROTECTED; }
4|   T_PRIVATE               { Z_LVAL($$.u.constant) = ZEND_ACC_PRIVATE; }

这三种访问控制的标记是PHP内核中定义的三个常量,在Zend/zend_complic.h中,其定义如下:

1#define ZEND_ACC_PUBLIC     0x100
2#define ZEND_ACC_PROTECTED  0x200
3#define ZEND_ACC_PRIVATE    0x400
4#define ZEND_ACC_PPP_MASK  (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE)

我们经常使用16进制的数字表标示状态,例如上面的访问控制常量, 0x100使用二进制表示

PHP内核    2019-04-25 16:25:18    30    0    0

在这一节中,我们正式的定义一个类。首先我给出PHP语言的实现:

01<?php
02class myclass
03{
04    public $public_var;
05    private $private_var;
06    protected $protected_var;
07     
08    public static $static_var;
09     
10    public function __construct()
11    {
12        echo "我是__construct方法\n";
13    }
14     
15    public function public_method()
16    {
17        echo "我是public类型的方法\n";
18    }
19     
20    public function private_method()
21    {
22        echo "我是private类型的方法\n";
23    }
24     
25    public function protected_method()
26    {
27        echo "我是protected类型的方法\n";
28    }
29     
30    public static function static_var()
31    {
32        echo "我是static类型的方法\n";
33    }
34}
35?>

定义类对应的zend_class_entry

定义类的第一步,便是先定义好这个类的zend_class_entry,这一步操作是在MINIT阶段完成的。

01static zend_function_entry myclass_method[]=
02{NULL,NULL,NULL};
03 
04PHP_MINIT_FUNCTION(test)
05{
06    zend_class_entry ce;
07    INIT_CLASS_ENTRY(ce,"myclass",myclass_method);
08    zend_register_internal_class(&ce TSRMLS_CC);
09    return SUCCESS;
10}
11 
12//这就是最简单的一个类,没有属性没有
PHP内核    2019-04-25 16:24:40    9    0    0

zend_class_entry是内核中定义的一个结构体,是内核实现PHP语言中类与对象的一个非常基础、关键的结构类型。他就相当于我们定义的类的原型。

如果我们想获得一个名字为myclass的类该怎么做呢?首先我们定义一个zend_class_entry变量,并为它设置名字,最后注册到runtime中去。

01zend_class_entry *myclass_ce;
02 
03 
04static zend_function_entry myclass_method[] = {
05    { NULL, NULL, NULL }
06};
07 
08ZEND_MINIT_FUNCTION(sample3)
09{
10    zend_class_entry ce;
11     
12    //"myclass"是这个类的名称。
13    INIT_CLASS_ENTRY(ce, "myclass",myclass_method);
14    myclass_ce = zend_register_internal_class(&ce TSRMLS_CC);
15    return SUCCESS;
16}

这样我们便定义了一个类myclass,而且我们可以正常的在PHP语言中使用它,比如:

1<?php
2$obj new myclass();
3?>

我们上面还定义了一个myclass_ce指针,他是干什么用的呢?当我们在扩展中对这个类进行操作,比如生成实例的时候,会使用到它,它的作用就类似与打开文件的操作句柄。

2/9