标签归档:PHP

深入理解PHP之引用一

深入理解PHP之引用一

作者Derick Rethans

译者:Martin Pan<http://www.phppan.com/>

PHP是弱语言,其变量处理的过程是不可见的。

你是否曾经很想知道在变量拷贝的时候,PHP引擎做了什么?

你是否曾经很想知道一个函数是如何以引用的方式返回一个变量?

如果是这样,请您接着向下看。

每门计算机语言都需要一些容器来保存变量数据。在一些语言当中,变量都有特定的类型,如字符串,数组,对象等等。比如C和Pascal就属于这种。而PHP则没有这样的类型。在PHP中,一个变量在某一行是字符串,可能到下一行就变成了数字。变量可以经常在不同的类型间轻易的转化,甚至是自动的转换。PHP之所以成为一个简单并且强大的语言,很大一部分的原因是它拥有弱类型的变量。但是有些时候这也会带来一些有趣的问题。

在PHP内部,变量是存储在一个叫做zval的容器中。它不仅仅包含变量的值,也包含变量的类型。Python和PHP类似,也有一个标签标记变量类型。变量容器中包含一些Zend引擎用来区分是否引用的字段。同时它也包含这个值的引用计数。

变量存储在一个相当于关联数组的符号表中。这个数组以变量名为key,并且指向包含了这些变量的容器。如图1所示:

php-variables-01

引用计数

PHP试着在变量拷贝(如 $a = $b )的时候变得聪明些。“=”也称为赋值操作符。当进行赋值操作时,Zend引擎不会创建一个新的变量窗口,而是增大变量窗口的 refcount 字段,你可以想象一下,当这个变量是一个巨大的字符串或一个巨大 的数组时,这将节约多少的内存。如图2所示,

php-variables-02

第一步: 变量a,包含文本”this is”。默认情况下,引用计数等于1

第二步:将变量$a赋值给$b和$c。这里没有新的变量容器生成,仅仅是每次在变量赋值操作时将refcount加1。因为这里执行了两次赋值操作,所以refcount最后会变成3。

现在,也许你很想知道当变量$c改变时将发生什么。根据refcount的值的不同,它会有两种不同的处理方式。如果refcount等于1,这个变量容器将更新它的值(也许同时会更新它的类型)。如果refcount大于1,将创建一个包含了新值(和类型)的变量容器。如图2所示的第三步,$a变量所在的变量容器的refcount值被减去一,现在refcount的值是2,而新创建的容器的refcount的值为1。当对一个变量使用unset函数时,这个变量所在的容器的refcount值将减去一,如图第4步所示。如果refcount的值少于1,Zend引擎将翻译这个变量容器,如图第5步所示。

传递变量给函数(Passing Variables to Functions)

除了所有脚本共用的全局符号表以外,每个用户定义的函数在调用时都会创建一个属于自己的符号表,用来存放它自己的变量。当一个函数被调用后,Zend引擎就会创建一个这样的符号表,当这个函数返回时这个函数表就会被释放。一个函数要么通过return语句返回,要么因为函数结束而返回(译者注:无返回的函数默认会返回NULL)。如图3所示:

php-variables-03

图3详细介绍了变量是如何传递给函数的。

第一步,我们将”thisis”赋给变量$a,然后我们将这个变量传递do_something()函数的$s变量。

第二步,你可以看到这与变量赋值的操作是一样的(与我们在前一小节提到的$b = $a类似),只是其存储在不同的符号表(函数符号表),并且引用计数加2,而不是加1。原因是函数栈也包含了这个变量容器的引用。

第三步,当我们赋新值给变量$s,原变量容器的refcount减1,并且创建一个包含了新值的变量容器。

第四步,我们通过return语句返回一个变量。返回的变量从全局符号表中获取一个实体并将其refcount的值增加1.当函数结束时,函数的符号表将被销毁。在销毁的过程中,Zend引擎将遍历符号表中的每个变量,并将其refcount的值减少。当变量容器的refount的值变为0,这个变量容器将会被销毁。如你所见,由于 PHP的引用计数机制,变量容器不是以拷贝的方式从函数返回。如果变量$s在第三步时没有被修改,则变量$a和$b将一直指向相同的变量容器(这个容器的refcount为2)。在这种情况下,语句$a = “this is”将不会创建变量容器的副本。

英文原文地址:http://derickrethans.nl/files/phparch-php-variables-article.pdf

以上只是第一部分的内容,原来看懂很容易,翻译出来相当难。

PHP的Socket编程

PHP的Socket编程

计算机进程可以使用socket和其他进程通信,通过socket,其他进程的位置是透明的。这些进程可以在同一台计算机上也可以在不同的计算机上。

在PHP中,socket是以扩展的方式加载的,如果无法使用socket相关函数,请确认是否有打开此扩展。
下面我们以一个面向连接的客户端和服务器的简单实现说明一些函数的使用,在此之后,简单介绍在PHP的内部是如何实现这些函数的。

【客户端实现】
如下所示代码为客户端的实现代码:

set_time_limit(0);
 
$host = "127.0.0.1";
$port = 2046;
 
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)or die("Could not create	socket\n"); // 创建一个Socket
 
$connection = socket_connect($socket, $host, $port) or die("Could not connet server\n");    //  连接
 
socket_write($socket, "time") or die("Write failed\n"); // 数据传送 向服务器发送消息
 
while ($buffer = socket_read($socket, 1024, PHP_NORMAL_READ)) {
    echo("Data sent was: time\nResponse was:" . $buffer . "\n");
}
 
socket_close($socket);

客户端首先创建一个socket,并且连接服务器。向服务器发送一个time的消息,等待服务器返回信息,读取服务器的信息并输出。

【服务器实现】

 
set_time_limit(0);
 
$host = "127.0.0.1";
$port = 2046;
 
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("Could not create	socket\n"); // 创建一个Socket
 
$result = socket_bind($socket, $host, $port) or die("Could not bind tosocket\n"); //绑定Socket到端口
 
$result = socket_listen($socket, 3) or die("Could not set up socket listener\n"); // 开始监听连接
 
$spawn = socket_accept($socket) or die("Could not accept incoming connection\n"); // 处理通信
 
$input = socket_read($spawn, 1024) or die("Could not read input\n"); // 数据传送 获得客户端的输入
 
$input = trim($input);
echo 'input:', $input, "\n";
 
if ($input == 'time') {
    $output = date("Y-m-d H:i:s"). "\n"; //处理客户端输入并返回结果
}else{
    $output = "input error \n"; //处理客户端输入并返回结果
}
 
echo "output:", $output, "\n";
 
//	数据传送 向客户端写入返回结果
socket_write($spawn, $output, strlen($output)) or die("Could not write output\n");	
 
// 关闭sockets
socket_close($spawn);
socket_close($socket);

服务器端创建一个socket,并且绑定端口,监听连接,读取客户端的数据,根据客户端的输入返回不同的值,最后写入数据到客户端。关闭socket。
只是这个服务器不能接受多个连接并且只完成一个操作
【PHP内部源码说明】
从PHP内部源码来看,PHP提供的socket编程是在socket,bind,listen等函数外添加了一个层,让其更加简单和方便调用。但是一些业务逻辑的程序还是需要程序员自己去实现。
下面我们以socket_create的源码实现来说明PHP的内部实现。
前面我们有说到php的socket是以扩展的方式实现的。在源码的ext目录,我们找到sockets目录。这个目录存放了PHP对于socket的实现。直接搜索PHP_FUNCTION(socket_create),在sockets.c文件中找到了此函数的实现。如下所示代码:

/* {{{ proto resource socket_create(int domain, int type, int protocol) U
   Creates an endpoint for communication in the domain specified by domain, of type specified by type */
PHP_FUNCTION(socket_create)
{
        long            arg1, arg2, arg3;
        php_socket      *php_sock = (php_socket*)emalloc(sizeof(php_socket));
 
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lll", &arg1, &arg2, &arg3) == FAILURE) {
                efree(php_sock);
                return;
        }
 
        if (arg1 != AF_UNIX
#if HAVE_IPV6
                && arg1 != AF_INET6
#endif
                && arg1 != AF_INET) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid socket domain [%ld] specified for argument 1, assuming AF_INET", arg1);
                arg1 = AF_INET;
        }
 
        if (arg2 > 10) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid socket type [%ld] specified for argument 2, assuming SOCK_STREAM", arg2);
                arg2 = SOCK_STREAM;
        }
 
        php_sock->bsd_socket = socket(arg1, arg2, arg3);
        php_sock->type = arg1;
 
        if (IS_INVALID_SOCKET(php_sock)) {
                SOCKETS_G(last_error) = errno;
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create socket [%d]: %s", errno, php_strerror(errno TSRMLS_CC));
                efree(php_sock);
                RETURN_FALSE;
        }
 
        php_sock->error = 0;
        php_sock->blocking = 1;
                                                                                                                                           1257,1-8      61%
        ZEND_REGISTER_RESOURCE(return_value, php_sock, le_socket);
}
/* }}} */

从整个函数的实现看,程序基本上是一个错误和异常处理。PHP本身引入了php_socket结构体,其创建时调用socket函数实现。

PS:关于PHP的网络编程,我们会在TIPI系列文章中作详细的说明。
【socket函数】
函数名 描述
socket_accept() 接受一个Socket连接
socket_bind() 把socket绑定在一个IP地址和端口上
socket_clear_error() 清除socket的错误或最后的错误代码
socket_close() 关闭一个socket资源
socket_connect() 开始一个socket连接
socket_create_listen() 在指定端口打开一个socket监听
socket_create_pair() 产生一对没有差别的socket到一个数组里
socket_create() 产生一个socket,相当于产生一个socket的数据结构
socket_get_option() 获取socket选项
socket_getpeername() 获取远程类似主机的ip地址
socket_getsockname() 获取本地socket的ip地址
socket_iovec_add() 添加一个新的向量到一个分散/聚合的数组
socket_iovec_alloc() 这个函数创建一个能够发送接收读写的iovec数据结构
socket_iovec_delete() 删除一个已分配的iovec
socket_iovec_fetch() 返回指定的iovec资源的数据
socket_iovec_free() 释放一个iovec资源
socket_iovec_set() 设置iovec的数据新值
socket_last_error() 获取当前socket的最后错误代码
socket_listen() 监听由指定socket的所有连接
socket_read() 读取指定长度的数据
socket_readv() 读取从分散/聚合数组过来的数据
socket_recv() 从socket里结束数据到缓存
socket_recvfrom() 接受数据从指定的socket,如果没有指定则默认当前socket
socket_recvmsg() 从iovec里接受消息
socket_select() 多路选择
socket_send() 这个函数发送数据到已连接的socket
socket_sendmsg() 发送消息到socket
socket_sendto() 发送消息到指定地址的socket
socket_set_block() 在socket里设置为块模式
socket_set_nonblock() socket里设置为非块模式
socket_set_option() 设置socket选项
socket_shutdown() 这个函数允许你关闭读、写、或指定的socket
socket_strerror() 返回指定错误号的周详错误
socket_write() 写数据到socket缓存
socket_writev() 写数据到分散/聚合数组

思考PHP语言四:接口和抽象类

思考PHP语言四:接口和抽象类
【概述】
在写PHP的日子里,我们多是按照需求完成相关功能,对于一些设计的工作较少的接触,也许是PHP的历史遗留问题或者其它,对于PHP的接口与抽象类使用得较少,但是对于一个支持面向对象的语言,接口与抽象是两个非常重要的内容。这里我们介绍接口与抽象类的一些基础知识,思考一些关于面向对象的东西。
【接口】
接口是一些方法特征的集合,这里的方法没有实现,只有声明。如果一个类继承了某个接口,则需要实现这个接口的所有方法。接口除了声明方法外,还可以定义常量。如下所示:

1
2
3
4
interface IFoo {
	const CONST_VAR = 'martin';
	public function method($str);
}

接口中方法的声明需要包含方法的名称,参数。不用包含参数类型和返还类型(PHP的函数本来就不用定义参数类型和返还类型)。
接口的方法只能是public,这说明接口本身包含的对外开放的意义在访问控制中体现出来了,并且当我们实现接口的方法时,也不能修改方法的访问控制权限。

接口是可以继承的,接口可以继承接口(和类继承一样,使用extends关键字),类可以继承接口(我们称之为接口继承,用implements关键字)

实现同一个接口的两个类可能功能完全不同,但是他们有相同的方法以及方法参数,并且都是公开的,从而他们可以提供相类似的服务,从而具有相同的接口。

接口的可插入性
在一个拥有多个层次结构的类组织中,如果要给某个处于中间位置的类添加父类,如果我们没有接口,则我们需要修改这个类的所有父类,打乱整个类的层次结构。当有了接口后,任何一个类都可以实现一个接口,此时,接口不会影响父类,但是会影响所有的子类,此类将必须实现这个接口的所有方法(抽象类可以不用实现所有方法,只是将实现下移到子类),子类则可以自动从此类继承实现后的方法。这就是接口的可插入性。

接口通常被用来声明一个新的类型,并且会作为一个类组织结构的起点,特别是当某个实体属于多个类型时,此时接口的作用就体现出来了。另外,理想情况下(现实中没发现过)一个类只应该实现接口或抽象类所声明的方法,纯理想呵。

【抽象类】
抽象类是类的一种,通过在类定义前添加abstract关键字实现。如下示例:

1
2
3
abstract class AbstarctFoo {
	abstract public function method();
}

抽象类不能实例化,一般作为类组织结构中枝节点或根节点存在。
抽象类提供一个类型的部分实现,抽象类可以有实例变量,可以有构造方法,可以有抽象方法,同时也可以有具体的方法实现。抽象类的构造方法可以被子类调用,只是需要显式调用。
我们在做设计时需要注意的是一般不要从具体类继承,具体类是用来实例化的。
在类的组织结构中,一般抽象类是枝节点,具体类是叶节点,在设计结构时应该尽量将公共代码放到作为父类的抽象类中,这样可以提高代码的复用性,与公共代码的移动相反,数据应该尽量放到具体类。
在继承过程中,子类的责任是扩展父类,而不是置换或取消掉父类的职责,当有取消或置换父类的职责的情况发生时,此时可能你的设计有问题了,需要考虑他们是否是is-a的关系?
抽象类和接口有相同的方法时
在PHP中,如果一个类继承了一个抽象类并且实现了一个接口,如果此时这个抽象类和接口中有相同名称的方法,则此时对于接口的实现会报错。与此相同,当一个抽象类实现一个接口时,如果接口已经声明了方法A,则在抽象方法中将不能再次声明此方法。