存档

‘PHP’ 分类的存档

swoole_process源码读解-构造析构函数

2018年5月16日 没有评论

php源码对大部分php程序员来说都是神秘的,因为php本身是一门神奇伟大的语音。它可以让没有什么编程基础的人可以编程,而且偏向于应用。而swoole是php中一个非常不错的扩展,它让php对异步编程更容易,对高并发做了很多的支持。而我想抽时间写一个针对swoole源码解读的文章,来让我们更深入的了解理解swoole,更好的在项目中使用它。

适合人群

那么这个系列文章比较适合哪些程序员哪?对php扩展开发有一定了解,有c/c++语言功底的。如果你对php扩展开发不是很了解,可以查看一下我以前写过php扩展开发的文章,或者看一下《PHP扩展开发及内核应用》这本书。

源码版本

php版本:7.1.11

swoole版本:2.1.2

下面将进入正题,我们将一起来查看swoole_process是如何实现的。

swoole_process构造函数

构造函数源码实现在swoole_process.c:239

static PHP_METHOD(swoole_process, __construct)
{
    zend_bool redirect_stdin_and_stdout = 0;
    long pipe_type = 2;
    zval *callback;
    
    //判断当前是不是命令行模式
    if (!SWOOLE_G(cli))
    {
        swoole_php_fatal_error(E_ERROR, "swoole_process only can be used in PHP CLI mode.");
        RETURN_FALSE;
    }

    //当前是否server中的master进程中
    if (SwooleG.serv && SwooleGS->start == 1 && swIsMaster())
    {
        swoole_php_fatal_error(E_ERROR, "swoole_process can't be used in master process.");
        RETURN_FALSE;
    }

    //解析参数,具体的请查看swoole官方手册中各参数含义
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|bl", &callback, &redirect_stdin_and_stdout, &pipe_type) == FAILURE)
    {
        RETURN_FALSE;
    }

    char *func_name = NULL;
    //检查传入的回调函数是否可以执行
    if (!sw_zend_is_callable(callback, 0, &func_name TSRMLS_CC))
    {
        swoole_php_fatal_error(E_ERROR, "function '%s' is not callable", func_name);
        efree(func_name);
        RETURN_FALSE;
    }
    efree(func_name);

    //分配process所需的内存
    swWorker *process = emalloc(sizeof(swWorker));
    bzero(process, sizeof(swWorker));

    int base = 1;
    //计算当前已启动process数量
    if (SwooleG.serv && SwooleGS->start)
    {
        base = SwooleG.serv->worker_num + SwooleG.task_worker_num + SwooleG.serv->user_worker_num;
    }
    if (php_swoole_worker_round_id == 0)
    {
        php_swoole_worker_round_id = base;
    }
    //设置当前process id,注意这里不是进程pid
    process->id = php_swoole_worker_round_id++;

    //输出重定向
    if (redirect_stdin_and_stdout)
    {
        process->redirect_stdin = 1;
        process->redirect_stdout = 1;
        process->redirect_stderr = 1;
        /**
         * Forced to use stream pipe
         */
        pipe_type = 1;
    }
    
    //使用管道,创建管道
    if (pipe_type > 0)
    {
        swPipe *_pipe = emalloc(sizeof(swWorker));
        int socket_type = pipe_type == 1 ? SOCK_STREAM : SOCK_DGRAM;
        if (swPipeUnsock_create(_pipe, 1, socket_type) < 0)
        {
            RETURN_FALSE;
        }

        process->pipe_object = _pipe;
        process->pipe_master = _pipe->getFd(_pipe, SW_PIPE_MASTER);
        process->pipe_worker = _pipe->getFd(_pipe, SW_PIPE_WORKER);
        process->pipe = process->pipe_master;
        //设置process对象的pipe属性
        zend_update_property_long(swoole_process_class_entry_ptr, getThis(), ZEND_STRL("pipe"), process->pipe_master TSRMLS_CC);
    }
    
    //将当前对象加入到swoole全局的对象管理当中
    swoole_set_object(getThis(), process);
    //设置process对象的callback属性
    zend_update_property(swoole_process_class_entry_ptr, getThis(), ZEND_STRL("callback"), callback TSRMLS_CC);
}

 

这里面有调用swoole_set_object函数,getThis函数获得当前对象指针,可以理解为php中常用的$this关键字,下面我们看一下swoole_set_object是如何实现的。

swoole_set_object在swoole.c:699

void swoole_set_object(zval *object, void *ptr)
{
    SWOOLE_GET_TSRMLS;
    int handle = sw_get_object_handle(object);
    assert(handle < SWOOLE_OBJECT_MAX);
    
    //是否需要为swoole_objects重新分配内存
    if (handle >= swoole_objects.size)
    {
        uint32_t old_size = swoole_objects.size;
        //新swoole_objects大小
        uint32_t new_size = swoole_get_new_size(old_size, handle TSRMLS_CC);

        void *old_ptr = swoole_objects.array;
        void *new_ptr = NULL;
        
        //重新分配内存
        new_ptr = realloc(old_ptr, sizeof(void*) * new_size);
        if (!new_ptr)
        {
            swoole_php_fatal_error(E_ERROR, "malloc(%d) failed.", (int )(new_size * sizeof(void *)));
            return;
        }
        bzero(new_ptr + (old_size * sizeof(void*)), (new_size - old_size) * sizeof(void*));
        swoole_objects.array = new_ptr;
        swoole_objects.size = new_size;
    }
    swoole_objects.array[handle] = ptr;
}

sw_get_object_handle是1个宏函数,最终展开结果是((*(object)).value.obj)->handle。其作用是获得当前对象的handle,也就是它的索引。

typedef struct _zval_struct     zval; //zend_types.h:84
typedef struct _zend_object     zend_object; //zend_types.h:89

struct _zend_object { //zend_types.h:277
	zend_refcounted_h gc;
	uint32_t          handle; // TODO: may be removed ???
	zend_class_entry *ce;
	const zend_object_handlers *handlers;
	HashTable        *properties;
	zval              properties_table[1];
};

typedef union _zend_value { //zend_types.h:101
	zend_long         lval;				/* long value */
	double            dval;				/* double value */
	zend_refcounted  *counted;
	zend_string      *str;
	zend_array       *arr;
	zend_object      *obj;
	zend_resource    *res;
	zend_reference   *ref;
	zend_ast_ref     *ast;
	zval             *zv;
	void             *ptr;
	zend_class_entry *ce;
	zend_function    *func;
	struct {
		uint32_t w1;
		uint32_t w2;
	} ww;
} zend_value;

struct _zval_struct {   //zend_types.h:121
	zend_value        value;			/* value */
	union {
		struct {
			ZEND_ENDIAN_LOHI_4(
				zend_uchar    type,			/* active type */
				zend_uchar    type_flags,
				zend_uchar    const_flags,
				zend_uchar    reserved)	    /* call info for EX(This) */
		} v;
		uint32_t type_info;
	} u1;
	union {
		uint32_t     next;                 /* hash collision chain */
		uint32_t     cache_slot;           /* literal cache slot */
		uint32_t     lineno;               /* line number (for ast nodes) */
		uint32_t     num_args;             /* arguments number for EX(This) */
		uint32_t     fe_pos;               /* foreach position */
		uint32_t     fe_iter_idx;          /* foreach iterator index */
		uint32_t     access_flags;         /* class constant access flags */
		uint32_t     property_guard;       /* single property guard */
		uint32_t     extra;                /* not further specified */
	} u2;
};

#define sw_get_object_handle(object)        Z_OBJ_HANDLE_P(object)
#define Z_OBJ_HANDLE_P(zval_p)      Z_OBJ_HANDLE(*(zval_p))
#define Z_OBJ_HANDLE(zval)          (Z_OBJ((zval)))->handle
#define Z_OBJ(zval)					(zval).value.obj

 析构函数

析构函数实现在swoole_process.c:318

static PHP_METHOD(swoole_process, __destruct)
{
    swWorker *process = swoole_get_object(getThis());
    swPipe *_pipe = process->pipe_object;
    if (_pipe)
    {
        _pipe->close(_pipe);
        efree(_pipe);
    }
    if (process->queue)
    {
        efree(process->queue);
    }
    efree(process);
}

析构函数主要判断是否使用管道和队列,然后关闭管道和释放管道队列占用的内存,然后释放进程占用内存。

swoole process父子进程使用队列通信

2018年5月11日 没有评论

最近在研究一点swoole一方面的东西,打算用swoole process做一个工具,而且想用里面的queue作为父子进程通信的方式。看了官方的文档,发现使用useQueue的时候,这个并没有给样例,在push里面看到了一个例子,但是发现它实现的方式和文档对useQueue的介绍有些不一致。文档里面说useQueue如果将mode设置为2时(默认就是2),所有创建的子进程都会从队列中争抢消息的。但是push中给的例子却是遍历所有worker,向队列里面插入消息。感觉这方面怪怪的,在swoole群里面问了一下,几个人在聊开车,对我的问题不知道太简单还是不屑一顾,没有一个人回答。求人不如求己,于是就去看了一下useQueue、push、pop相关的源码,发现了官方文档上面说的还是对的,push里面的例子却是有些问题,感觉官方文档的例子放这么久一个不合适的例子有点不应该,不过开源的东西,靠大家自发的贡献,我们也不能要求太多了。

下面是我自己写的一个使用消息队列并设置为非阻塞模式的例子,同时已经更新到swoole process useQueue文档的示例中去了。

<?php
$worker_num = 2;
$process_pool = [];

$process= null;
$pid = posix_getpid();

function sub_process(swoole_process $worker)
{
    sleep(1); //防止父进程还未往消息队列中加入内容直接退出
    echo "worker ".$worker->pid." started".PHP_EOL;
    while($msg = $worker->pop()){
        if ($msg === false) {
            break;
        }
        $sub_pid = $worker->pid;
        echo "[$sub_pid] msg : $msg".PHP_EOL;
        sleep(1);//这里的sleep模拟必须,否则可能1个worker就把所有信息全接受了
    }
    echo "worker ".$worker->pid." exit".PHP_EOL;
    $worker->exit(0);
}

$customMsgKey = 1;//默认为空,这个地方可以随便填的
$mod = 2 | swoole_process::IPC_NOWAIT;//这里设置队列为非阻塞模式

//创建worker进程
for($i=0;$i<$worker_num; $i++) {
    $process=new swoole_process('sub_process');
    $process->useQueue($customMsgKey, $mod);
    $process->start();
    $pid = $process->pid;
    $process_pool[$pid] = $process;
}

$messages = [
    "Hello World!",
    "Hello Cat!",
    "Hello King",
    "Hello Leon",
    "Hello Rose"
];
//由于所有进程是共享使用1个消息队列,所以只需向一个字进程发送消息即可
$process = current($process_pool);
foreach ($messages as $msg) {
    $process->push($msg);
}

swoole_process::wait();
swoole_process::wait();

echo "master exit".PHP_EOL;

如有问题请大家指正,useQueue文档地址

分类: PHP 标签:

lumen数据库时区设置

2018年5月10日 没有评论

前段时间写过一篇关于日期选择使用的问题博客,其实说起来也惭愧,用mysql的timestamp做日期我已经有2年多的使用经验了,以前一直不了解timestamp里面居然保存的居然是带时区的。今天就是记录一下laravel/lumen里面怎么设置连接时的时区问题。

那么是如何发现这个问题lumen/laravel里面需要设置数据库时区的哪?

最近公司使用lumen在做一下项目,下单的时间在app里面看着是正常的,但是我通过一些mysql工具连接时,发现时间却是+8小时以后的时间.

查看ORM源码,它的注释里面说日期类型的字符创会转化成“DateTime/Carbon”实例,然后再进一步处理是输出或者存入数据库。

我们可以通过在.env中设置”DB_TIMEZONE”来解决时区不一致的问题。

DB_TIMEZONE=+8:00

一般来说要保证我们设置DB_TIMEZONE和APP_TIMEZONE一致的,所以一般配置文件都是这样的。

APP_TIMEZONE=Asia/Shanghai
DB_TIMEZONE=+8:00

 

php不定参数函数

2017年12月21日 没有评论

php是可以像C语言一样使用不定参数函数的,系统函数常见的有sprintf、array_merge等,当然用户自定义的函数也可以实现。在 PHP 5.6 及以上的版本中,由 … 语法实现;在 PHP 5.5 及更早版本中,使用函数 func_num_args(),func_get_arg(),和 func_get_args() 。

当然5.6里面新增…这种方法相比以前的利用func_*_arg系列函数相关,参数的定制化和可控制性更强了。

首先先给出这两种方式不定参数函数的例子。

php5.6以前版本func_*_arg系列

function func2()
{
      echo "this is func2". PHP_EOL;
      $arg_num = func_num_args();
      $params = func_get_args();
      var_dump($arg_num, $params);
}
func2('luke', 'like', 'you');

php5.6以后…方式

function func1($author,...$params)
{
     echo "this is func1". PHP_EOL;
     var_dump($params);
}
func1('luke', 'like', 'you');

注意:这里的3个点是在参数名称前面的,所以如果你放到后面是会报错的。

分类: PHP 标签:

php-memcached扩展安装

2017年11月3日 没有评论

由于服务器上的php-memcached的序列化处理使用的是igbinary,而本地使用的是php默认的,在本地跑线上环境代码的时候就遇到问题了。具体是查看错误日志看到如下信息:

'ErrorException' with message 'Memcached::get(): could not unserialize value, no 
igbinary support'

查看memcached的igbinary support发现值是”no”,由于以前没自己动手搞过这块的东西。看了官方配置手册以后,以为只需要在配置文件中把”memcached.serializer”的配置设置为igbinary就可以了那。但是修改以后发现并没有作用。

接下来查看了一下扩展的编译参数,发现有个选项是”–enable-memcached-igbinary”,另外2个类似的参数分别是”–enable-memcached-json” 和”–enable-memcached-msgpack”.现在就找到了问题所在,需要重新重新编译memcached扩展以支持igbinary。

开始编译,编译参数如下:

./configure --enable-memcached --enable-memcached-igbinary --with-php-config=
/usr/local/bin/php-config

但是很不幸,并没有如期待的那样编译成功,遇到如下错误:

"./php_memcached.h:23:10: fatal error: 'Zend/zend_smart_str.h' file not found
#include "Zend/zend_smart_str.h""

搜索了整个电脑硬盘,也没有找到这个头文件。打开php-memcached在github上的项目地址,发现它在readme里面说3.x版本是支持php7的,而php5.2-5.6需要使用2.x版本,所以就从github上的release历史里面下载了php-memcached2.2版本,当然从php官方地址也是可以下载到相应的版本的。我特地下载了php7.1.11的源码查看了一下,果然有缺失的头文件。

接下来安装上面的编译参数再次进行编译,就成功了。

分类: Linux, PHP 标签:

php-fpm配置unixsock引发问题

2017年10月11日 没有评论

今天把博客从原来的阿里云迁移出来了,在新环境搭建环境时,nginx和php-fpm都已经配好的时候(其实还是有问题的),访问首页提示403错误。nginx和fpm的用户都配置为www-data,按道理说同一用户不应该没有访问文件的权限。以前nginx和fpm一直都是使用socket通信的方式,第一次使用unixsock方式配置,有点不知所措。

查看nginx的错误日志,发现最新的错误日志信息是类似这样的:

connect() to unix:/var/run/php-fpm.sock failed (13: Permission denied) while connecting to upstream

也就说出问题的地方是/var/run/php-fpm.sock文件,不是站点目录下文件的权限。在google上面搜索了一下,在stackoverflow上面发现一篇“nginx error connect to php5-fpm.sock failed (13: Permission denied)”,也是unixsock方式配置nginx和php-fpm的问题。问题里面给说的方案是需要设置一下unixsock中listen.user、listen.group、listen.mode这几个参数,按照默认的值将其前面的注释去掉即可,类似如下。


listen.owner = www-data
listen.group = www-data
listen.mode = 0660

然后重启php-fpm,重新访问页面,一切正常了。

分类: Linux, PHP 标签:

phar函数介绍

2016年11月30日 没有评论

php官方关于phar手册的地址,不过由于这个模块比较相对比较冷僻,所以没有人翻译手册,这里针对一些常用的函数,对手册做一些简明的翻译。

public Phar::__construct ( string $fname [, int $flags [, string $alias ]] )

构造函数,用来构建Phar对象。

$fname是一个指向一个phar归档文件的路径或者要创建的归档文件存放的路径,注意这个文件的扩展名必须包含“.phar”。

$flags传入给父类RecursiveDirectoryIteratord的标志。

$alias在调用流功能时应引用此Phar归档的别名.

错误和异常:被调用2次时,抛BadMethodCallException异常,当$fname指定的归档文件打不开时抛UnexpectedValueException异常。

 

public array Phar::buildFromDirectory ( string $base_dir [, string $regex ] )

从指定目录中的文件构造phar归档,此方法需要 将 php.ini 中的 phar.readonly 设为 0 以适合 Phar 对象. 否则, 将抛出PharException.用目录中的内容填充phar归档文件,第2个可选参数是一个正则表达式,用来过滤内容,匹配正则表达式的包含到归档文件中,否则排除。如果想要控制的更精细,试试Phar::buildFromIterator()。

$base_dir指定要填充到归档文件内容的目录路径(相对路径和绝对路径均可)。

$regex是一个可选正则参数用来过滤目录内的文件,只有匹配正则表达式的文件路径会被填充到归档文件中。

返回将文件的内部路径映射到文件系统上文件的完整路径的关联数组。

 

public void Phar::addFile ( string $file [, string $localname ] )

将一个文件从文件系统添加到 phar 档案中,此方法需要 将 php.ini 中的 phar.readonly 设为 0 以适合 Phar 对象. 否则, 将抛出PharException。通过这个方法,任何文件或者 URL 可以被添加到 phar 档案中。如果第二个可选参数 localname 被设定, 那么文件则会以该参数为名称保存到档案中,此外,file 参数会被作为路径保存在档案中。 URLs 必须提交 localname 参数,否则会抛出异常。 这个方法与 ZipArchive::addFile() 类似.

$file需要添加到 phar 档案的文件在磁盘上的完全(绝对)或者相对路径。

$localname文件保存到档案时的路径。

没有返回值,失败时会抛出异常。

 

public bool Phar::setDefaultStub ([ string $index [, string $webindex ]] )

用来将归档文件的加载器或者引导脚本设置为默认的加载器(几个英文单词吃不准,后面是原文 Used to set the PHP loader or bootstrap stub of a Phar archive to the default loader)。其实这个函数是Phar::createDefaultStub() 和Phar::setStub()的一个封装。

$index是一个归档文件内部的相对路径,如果在cli模式(命令行)下运行phar文件时执行的脚本。

$webindex是一个归档文件内部的相对路径,如果通过web浏览器访问phar归档文件时执行的脚本。

成功返回true,失败返回false。

 

public void Phar::compressFiles ( int $compression )

压缩当前phar归档中的文件。

$compression压缩时必须为Phar::GZ或 Phar::BZ2 ,或者使用 Phar::NONE 移除已有的压缩.

 

public bool Phar::extractTo ( string $pathto [, string|array $files [, bool $overwrite = false ]] )

将归档文件中的内容解压到指定目录。解压归档文件到硬盘时,会保留他们在归档文件中的权限。可选参数允许你控制那些文件提取到硬盘上或者当它们存在时是否覆盖它们。第2个可选参数可以是字符串或者数组,表示提取文件或目录的名字。默认的这个函数不会覆盖存在的文件,不过可以通过将第3个参数设置为true启用强制覆盖存在的文件。

$pathto解压提取到硬盘的路径。

$files可选参数指定提取归档文件或目录的名字,或者提取文件或目录的名字的数组。

$overwrite设置为true启用强制覆盖。

成功返回true,不过更好的办法是检查异常,如果没有异常抛出就是成功。

 

final public static bool Phar::loadPhar ( string $filename [, string $alias ] )

使用别名加载任意的phar归档文件。一般用来从外部来读取归档文件中的内容。最常用的就是给归档文件一个别名,随后就可以使用别名来引用归档文件,或者用于加载仅包含数据且不用于在PHP脚本中执行/包含的Phar存档。

$filename要打开的归档文件的相对或者绝对路径。

$alias设置一个用来引用归档文件的别名。许多phar档案在phar档案中指定一个显式别名,如果别名已经被制定过时,会抛出一个PharException异常。

成功时返回 TRUE, 或者在失败时返回 FALSE。

phar简介

2016年11月30日 没有评论

php5.3以后有一个非常不错的特性phar,有些类似java中的jar包,就是可以把php应用程序打包到一个单独的phar归档文件中。其实这个特性以前很早貌似就用,只不过最早是用php写的,所以性能上面不是特别好,后面官方用c扩展重新写一遍,文档可以查看Phar文档,不过目前90%的内容是英文的,下面我对介绍进行了一点简单翻译,当然英语水平一般,错误之处欢迎指正啊。

Phar简介

Phar扩展提供了一种将整个php应用放入一个单独文件被称为”phar”的归档,包更容易的发布和安装的解决方案。除了提供这种服务,phar扩展也提供了一种抽象化的文件格式的方法来创建和管理tar或者zip文件通过PharData类,很像Pdo提供了一种统一的接口来访问不同的数据库。和PDO不同之处,Pdo不能将数据在不同的数据库之间转换,Phar可以通过一行代码就可以在tar,zip和phar之间转换文件格式。查看Phar::convertToExecutable()示例。

phar是什么?phar归档文件最显著的特征是组合很多文件文件到一个单独的文件中。就像这样,一个phar文档提供了一种方式来发布一个完成的php应用程序在一个单独的文件中并且可以通过这个文件来运行它而无需解压到硬盘上。此外,phar归档文件也可以像其他php文件一样去被PHP执行,同时可以通过命令行和一台web服务器。Phar就像一种为PHP应用程序设计的拇指驱动器(U盘)。

Phar实现这些功能通过Stream Wrapper。一般来说,要使用一个在Phar归档中的php脚本文件,你可以用include。

函数介绍

可以参考另一篇关于phar函数手册的博客。

简单案例

这是一个比较简单的demo,希望通过这个能对phar有个比较初步的了解。

目录树如下:

|____build.php
|____config.json
|____extractPhar.php
|____lib
| |____class
| | |____empty.txt
| |____func.php
| |____stub.php
|____phar.phar
|____test_phar.php

build.php为生成phar归档文件的php脚本,config.json文件其实没什么用,extractPhar.php为解压phar到指定目录的脚本,lib目录下面就是测试用的库函数,phar.phar是用build.php生成的归档文件,test_phar.php是测试phar归档文件的脚本。下面我们一个一个的介绍这里面涉及到的脚本文件。

[build.php]

</pre>
<pre><?php</pre>
<pre>try{
    $phar = new Phar('phar.phar',0,'my.phar');
    $phar->buildFromDirectory(__DIR__.'/lib/');
    //$phar->buildFromDirectory(__DIR__.'/lib/',"/\.php/");
    $phar->addFile("config.json","config");
    $phar->setDefaultStub('stub.php','stub.php');
    $phar->compressFiles(Phar::GZ);
}catch (Exception $e){
    echo $e->getMessage();
}</pre>
<pre>

这里需要注意把php.ini 中的 phar.readonly 设为 0 以适合 Phar 对象. 否则, 将抛出PharException。

[func.php]

</pre>
<pre>function println($str){
    echo $str."\n";
}

function myFunc($input=__FILE__){
    println('Input File:'.$input);
    println('File Path:'.__DIR__);
}</pre>
<pre>

[stub.php]

</pre>
<pre><?php

require_once 'func.php';
myFunc(__FILE__);</pre>
<pre>

[test_phar.php]

</pre>
<pre><?php
include_once 'phar.phar';
include_once 'phar://my.phar/func.php';
myFunc(__FILE__);
$content = file_get_contents("phar://my.phar/config");
println($content);</pre>
<pre>

[extractPhar.php]

</pre>
<pre><?php
$phar = new Phar('phar.phar');
$phar->extractTo('./extract/');</pre>
<pre>

通过命令行脚本执行”php build.php”可以生成phar.phar归档文件。可以直接用”php phar.phar”执行phar归档文件,
它会执行”setDefaultStub”设置stub文件。extractPhar.php会将phar.phar归档文件中的内容解压到extract目录下面,
方面我们查看phar归档中的内容。
test_phar.php的执行结果如下:

Input File:phar:///Users/king/www/test/Phar/phar.phar/stub.php
File Path:phar:///Users/king/www/test/Phar/phar.phar
Input File:/Users/king/www/test/Phar/test_phar.php
File Path:phar:///Users/king/www/test/Phar/phar.phar
{
 "name" : "phar test project",
 "path" : "~/www/test/Phar"
}
extractPhar.php解压的extract目录如下:
|____class
| |____empty.txt
|____config
|____func.php
|____stub.php

php5.5新特性::class

2016年9月26日 没有评论

最近在了解laravel框架,发现它里面很多地方都使用了”类名::class”,这是php5.5以后新增的特性,功能有些类似get_class,不过get_class传入的是对象。

下面是官方手册中的描述和例子:

自 PHP 5.5 起,关键词 class 也可用于类名的解析。使用 ClassName::class 你可以获取一个字符串,包含了类 ClassName 的完全限定名称。这对使用了 命名空间 的类尤其有用。


<?php
namespace NS {
class ClassName {
}
echo ClassName::class;
}
?>

以上例程会输出:NS\ClassName.

我们可以拿它和get_class做一个比较:


<?php
NameSpace Test;

class Test{
}
var_dump(Test::class);
$t = new Test;
var_dump(get_class($t));

执行以后输出:

string(9) “Test\Test”
string(9) “Test\Test”

分类: PHP 标签:

KMP字符串比较算法(php版本)

2016年7月5日 没有评论

前段时间面试的时候,被一个公司的人问到过,今天正好在地铁上面看到了关于KMP算法的文章,作者写的很好,我这个算法渣都看懂了!于是就抽空写了这个PHP版本的kmp算法实现,写的不好的地方欢迎指正。

<?php
/**
 * kmp字符串比较算法
 */

/**
 * 获得所有前缀集合
 * @param string $string
 *
 * @return array
 */
function get_prefix_set($string){
    $len = strlen($string);
    if($len <= 1 ) return [];
    $prefixs = [];
    for($i=1;$i<$len;$i++){
        $prefixs[] = substr($string,0,$i);
    }
    return $prefixs;
}

/**
 * 获得所有后缀集合
 * @param string $string
 *
 * @return array
 */
function get_suffix_set($string){
    $len = strlen($string);
    if($len<=1) return [];
    $suffixs = [];
    for($i=1;$i<$len;$i++){
        $suffixs[] = substr($string,$i);
    }
    return $suffixs;
}

/**
 * 查找字符串$str2是否在$str1中出现
 * @param string $str1
 * @param string $str2
 *
 * @return bool
 */
function my_strstr($str1,$str2){
    $prefixSet = get_prefix_set($str2);
    $suffixSet = get_suffix_set($str2);
    $intersection = array_intersect($prefixSet,$suffixSet);
    $matchValue = 0;
    if(!empty($intersection)){
        foreach($intersection as $tmpstr){
            $len = strlen($tmpstr);
            if($len>$matchValue) $matchValue = $len;
        }
    }
    $str1_len = strlen($str1);
    $str2_len = strlen($str2);
    $endPos = $str1_len-$str2_len;
    if($endPos<0){
        return false;
    }
    for($i=0;$i<=$endPos;){
        $moveStep = 1;
        for($n=0;$n<$str2_len;$n++){
            if($str1[$i+$n] != $str2[$n]){//字符串不匹配时,跳出本次循环
                $moveStep = $n-$matchValue;
                $moveStep = $moveStep > 0 ? $moveStep : 1;
                break 1;
            }else if($str1[$i+$n] == $str2[$n] && $n==$str2_len-1){
                return true;
            }
        }
        $i +=$moveStep;
    }
    return false;
}

$result = my_strstr("abcabfcde","bfcd");
var_dump($result);

如果对KMP算法不是特别了解可以看一下《字符串匹配的KMP算法》