Spyc PHP yaml config 载入读取类介绍

最近在复盘项目私有化部署的一些细节,当初为了让项目当中配置收敛可配置化,将配置存于yaml config文件当中,代码层读取yaml配置文件,然后界面上实现一个修改yaml配置文件功能,以实现项目参数配置化。

读取yaml配置文件,使用的是网上现成的方案,Spyc Class for PHP。

Spyc Class is a simple YAML Loader/dumper class for PHP. 是一款纯PHP编写的YAML加载/转储程序,给定一个YAML文件,Spyc将返回一个array。

源码下载:git://www.github.com/mustangostang/spyc.git

  • 读取简单示例:
<?php
require_once "spyc.php";
$data = Spyc::YAMLLoad('config.yaml');
?>

读取完之后,data的值为yaml config的结构化数组

  • 写入简单示例:
<?php
$file = "config.yaml";
$yaml_content = Spyc::YAMLDump($configs_exists);   //config_exists是用YAMLLoad读取回来的结构化数组
$file = fopen($file_name, 'w') or die("无法打开yaml文件!");;
fwrite($file, $yaml_content);
?>
  • 当然上面只是个简单的用法,实际项目实践中可以将它封装的更好的一点,比如完善读接口,修改yamlconfig配置的写节点都封装一下。传入xxx.yyy.zzz 自动读取yaml config xxx.yyy.zzz的值

php5升级php7 mysql类函数替换

php7中mysql函数不支持,需要替换成mysqli,由于用了框架,框架可配置数据库driver, 所以mysql替换的工作量并没有那么多,但是仍然还有200多处是裸写原生的mysql方法.在外面包一层,全局重载mysql_xxx方法,是个不错的选择

思路源于github:https://github.com/e-sites/php-mysql-mysqli-wrapper/blob/master/mysql.php

if((float) phpversion() >= 7.0 && !extension_loaded('mysql') && !function_exists('mysql_connect')) {
  if(!extension_loaded('mysqli')) {             
       trigger_error('The extension   "MySQLi" is not available', E_USER_ERROR);    
  }

class MysqlAdapter {     
      const MYSQL_CLIENT_COMPRESS = MYSQLI_CLIENT_COMPRESS;     
      const MYSQL_CLIENT_IGNORE_SPACE = MYSQLI_CLIENT_IGNORE_SPACE;        
      const MYSQL_CLIENT_INTERACTIVE = MYSQLI_CLIENT_INTERACTIVE;     
      const MYSQL_CLIENT_SSL = MYSQLI_CLIENT_SSL;     
      const MYSQL_ASSOC = MYSQLI_ASSOC;     
      const MYSQL_NUM = MYSQLI_NUM;     
      const MYSQL_BOTH = MYSQLI_BOTH;     
      protected static $link;     

      public static function get_link_identifier(mysqli $mysqli = null) {         
            if(!$mysqli) {             
                $mysqli = self::$link;         
            }         
            return $mysqli;     
      }     

      public static function set_link_identifier(mysqli $mysqli = null) {         
            self::$link = $mysqli;     
      } 
} 

/** Open a connection to a MySQL Server  
  * @param $server  
  * @param $username  
  * @param $password  
  * @return mysqli|null  
*/ 
      function mysql_connect($server, $username, $password, $new_link = false, $client_flags = 0) {
          $host = $port = null;     
          list($host, $port) = explode(':', $server);     
          $link = mysqli_connect($host, $username, $password, null, $port);     
          MysqlAdapter::set_link_identifier($link);     
          return $link; 
      }
      
      ......
}

用上面这种方案,业务侧代码可以不用改,既节省了工作量,又能兼容php5.如果是php5,mysql_xxx类方法存在,则不会进到重载逻辑.

php5.6 升级php7最佳实践

最近项目在做php7的升级,之所以升级,出于以下两个方面

  • php7的性能

PHP 升级到 7.2 之后,效率直接提高了 2-3 倍,有开启 OPCCache 的 PHP 7,有测试,比 PHP 5.6 都快近 10 倍,这个差距是非常明显的。
另外,从SEO的角度来看,搜索引擎对网站速度的要求是非常看重的,如果你的网站不能以较快的速度打开的话,搜索引擎自然是有可能给你的降权的。
PHP 7 是跨时代的版本,但是 PHP 7 在 2019 年之后就将不再维护了,PHP 7.1 和 7.2 分别升级了一些特性之后,性能也进行一定的提升,因此建议一步到位 PHP 7.2

  • php7的安全支持

PHP 官网更新了版本支持列表,自2019年1月1日起,PHP 5.6 已不再受官方支持,使用 PHP 5.6 版本的网站将不再收到安全漏洞或错误更新。此外,PHP 7.0 也已于2018年12月1日结束安全支持。

基于以上两个方面,所以打算升级php7.2,由此积累了一些升级php7的经验,该工作大体个体力活,并没有想象中的那么难,即便项目代码量庞大,个人认为只是个时间问题。下面总结下php5.6升级到php7.2时遇到的一些问题:

  • Object类不能再被作为类名

Cannot use ‘Object’ as class name as it is reserved

解决方案:将所有Object类名全局搜出来替换成xxxObject, 比如我使用的项目是使用的框架是cake, 所以我将Object类改为了CakeObject。

  • Declaration of should be compatible with that…… warnings

这个Warning主要是由于父子类同名方法,参数个数以及参数的默认值不一致,改造方法主要是对齐参数个数和参数的默认值

https://stackoverflow.com/questions/36079651/silence-declaration-should-be-compatible-warnings-in-php-7

  • $object =& new Controller();

要将=& new 类名()改成 = new 类名();

  • 非循环逻辑中使用break返回,需要改成return
function add_test($condition, $key, $value){
//   if (ALL_VALUE == $value) { break; }  //这样使用在php7中是不允许的
     if (ALL_VALUE == $value) { return; } //需要改成return 
     .....
}
  • 函数废弃情况
  1. split 函数改成 explode函数
  2. md5(xx) % yy 改成算术值求模
  3. count($string) 求字符长度改成strlen($string)
  4. ereg_replace 函数替换成 preg_replace函数
  5. mysql_real_escape_string函数要替换成mysqli_escape_string
  6. call_user_method_array 替换成 call_user_func_array
  7. mysql函数替换成mysqli 【php.ini中,只有extension=mysqli,而不再有extension=mysql这个扩展】
  8. ereg函数的弃用,这个可以换成preg_match
  9. spliti 函数的弃用,这个可以换成explode
  10. HTTP_RAW_POST_DATA 这个全局变量被弃用了.要改成file_get_contents
  11. end(explode(xxx))  这个写法要拆成两句 $a = explode(xxx); end($a);
  12. preg_replace 修正符e的弃用,可以用preg_replace_callback替换

第1点需要注意的是:原split 的第一个参数是pattern 正则,比如split(‘\|’, $string), 如果替换成 explode, 正确的写法:

explode("|", $string);

第5点:mysql_real_escape_string 和 mysqli_escape_string 也有区别:

mysqli_escape_string(connection,escapestring); //第一个参数是Mysql连接句柄,必填 🙁  所以改造时,要想办法传入connection

 mysql_real_escape_string(string,connection)  //connection可选,非必填,只需要string第一个参数即可
  • foreach 不建议使用引用传递,正常的修改值还是能修改到的:
foreach($array as &v) {

}

//如果要改$array数组,要想办法用$array[$key]['xxxx'] = 'yyy';

差异主要存在:

1. 如果在循环的时候是引用的方式,对数组的修改会影响循环行为。不过PHP7版本优化了很多场景下面位置的维护。比如在循环时往数组中追加元素。在循环中追加的元素,也会进入循环

2. foreach()循环对数组内部指针不再起作用,在PHP7之前,当数组通过foreach迭代时,数组指针会移动。现在开始,不再如此 ,如果是current函数和next函数

其他PHP7 与 PHP5之间的差异,可以参考:

http://www.php7.site/tw/book/php7/foreach-changes-24.html

不重新编译php, 单独编译添加扩展

文章来源: https://blog.csdn.net/qmhball/article/details/79015888

之前给php安装一个LDAP扩展,查了下如何在不重新编译php的基础上,添加l扩展的方法。

问题

php自身提供了很多扩展,比如curl,gmp, mbstring等等。我们在编译安装php时未必安装了所有扩展。那么在安装完php后,如果想单独安装某个php自身的扩展怎么办呢?

我们以curl扩展模块的安装为例说明如何单独添加扩展。

步骤
1.进入php源码的扩展目录。本例中我的源码对应目录如下:

/data/src/php-7.0.14/ext 


2.进入模块目录,我们安装的是curl扩展,所以进入curl目录。此时,我们所在的目录是

/data/src/php-7.0.14/ext/curl


3.执行phpize。注意,如果你的机器上有多个php版本,一定要执行你准备安装扩展的php版本对应的phpize。比如,我的机器上同时有php7和php5.3.3。我当前准备为php7安装curl扩展。所以执行php7对应的phpize

/usr/local/php70/bin/phpize

结果类似:

/usr/local/php70/bin/phpize

结果类似:
Configuring for:
PHP Api Version:         20151012
Zend Module Api No:      20151012
Zend Extension Api No:   320151012   

4.执行

./configure --with-php-config=/usr/local/php70/bin/php-config

PS:如果你的机器上只有一个php版本,大多时候不需要特别使用–with-php-config指明相应的php-config所在位置。

5.执行make,之后会在./modules/目录下生成curl.so文件

6.执行make install

结果类似:
Installing shared extensions:     /usr/local/php70/lib/php/20151012/


执行结果告诉你将curl.so安装到了哪个目录。

一般情况,如果你正确的指定了php-config或者你的机器只有一个php版本,so都会被正确安装到相应php的extension_dir。如果执行make install后,extension_dir目录下没有相应的so,可以手动将第5步中生成的so文件cp过去。

要查看具体extension_dir,可以执行如下命令。

php -i| grep extension_dir

结果类似
extension_dir => /usr/local/php70/lib/php/20151012/ => /usr/local/php70/lib/php/20151012/ 

7.在php.ini中增加

extension=curl.so

如果不知道php.ini的具体位置,可以通过如下命令查看

php --ini

8.执行如下命令,查看是否安装成功

php -m | grep curl

如果你看到了curl, 恭喜你,安装成功!

注意:如果你是使用fpm,apache等方式执行php。在修改相应的php.ini后,还需要重新启动fpm或apache,新的扩展才会生效。

PHP-CGI 详解

CGI程序本身只能解析请求、返回结果,不会进程管理,所以有一个致命的缺点,那就是每处理一个请求都需要fork一个全新的进程,随着Web的兴起,高并发越来越成为常态,这样低效的方式明显不能满足需求(每一次web请求都会有启动和退出进程,也就是最为人诟病的fork-and-execute模式,这样一在大规模并发下,就死翘翘了)。就这样,FastCGI诞生了,CGI程序很快就退出了历史的舞台。

FastCGI (Fast CGI)
FastCGI,顾名思义就是更快的CGI程序,用来提高CGI程序性能,它允许在一个进程内处理多个请求,而不是一个请求处理完毕就直接结束进程,性能上有了很大的提高。

  • 提高性能?那么CGI程序的性能问题在哪呢?

PHP解析器会解析php.ini文件,初始化执行环境,就是这里了。标准的CGI程序对每个请求都会执行这些步骤(不闲累啊!启动进程很累的说!),所以处理每个请求的时间会比较长。这明显不合理嘛!首先,FastCGI会先启一个master进程,解析配置文件,初始化执行环境,然后再启动多个worker进程。当请求过来时,master会传递给一个worker,然后立即可以接受下一个请求。
这样就避免了重复的劳动,效率自然是高。而且当worker不够用时,master可以根据配置预先启动几个worker等着。当然空闲worker太多时,也会停掉一些,这样就提高了性能,也节约了资源。这就是FastCGI的对进程的管理。

PS:也有一些能够调度PHP-CGI进程的程序,比如说由lighthttpd分离出来的spawn-fcgi。好了,PHP-FPM也是这么个东东,在长时间的发展后,逐渐得到了大家的认可(要知道前几年大家可是抱怨PHP-FPM稳定性太差的),也越来越流行。

PHP-FPM(FastCGI Process Manager)

它是FastCGI协议的一个实现,任何实现了FastCGI协议的服务器都能够与之通信。FPM之于标准的FastCGI程序,也提供了一些增强功能,具体可以参考官方文档:PHP: FPM Installation

  • FPM是一个PHP进程管理器,包含master和worker两种进程。

master进程只有一个,负责监听端口,接收来自服务器的请求,而worker进程则一般有多个(具体数量根据实际需要配置),每个进程内部都嵌入了一个PHP解释器,是PHP代码真正执行的地方,下面是我本机上FPM的进程情况:1个master进程,2个worker进程。

  • 从FPM接收到请求,到处理完毕,其具体的流程如下:
  1. FPM的master进程接收到请求。
  2. master进程根据配置指派特定的worker进程进行请求处理,如果没有可用进程,返回错误,这也是我们配合Nginx遇到502错误比较多的原因
  3. worker进程处理请求,如果超时,返回504错误
  4. 请求处理结束,返回结果。
  • FPM从接收到处理请求的流程就是这样了,那么Nginx又是如何发送请求给FPM的呢?

我们知道,Nginx不仅仅是一个Web服务器,也是一个功能强大的Proxy服务器,除了进行http请求的代理,也可以进行许多其他协议请求的代理,包括本文与FPM相关的FastCGI协议。为了能够使Nginx理解FastCGI协议Nginx提供了FastCGI模块来将http请求映射为对应的FastCGI请求
Nginx的FastCGI模块提供了fastcgi_param指令来主要处理这些映射关系,下面是Nginx的一个配置文件实例,其主要完成的工作是将Nginx中的变量翻译成PHP中能够理解的变量。

除此之外,非常重要的就是fastcgi_pass指令了,这个指令用于指定FPM进程监听的地址,Nginx会把所有的PHP请求翻译成FastCGI请求之后再发送到这个地址。下面一个简单的可以工作的Nginx配置文件:

在这个配置文件中,我们新建了一个虚拟主机,监听80端口,项目根目录为 /usr/local/web/myproject/public。然后我们通过location指令,将所有的以.php结尾的请求都交给FastCGI模块处理,从而把所有的PHP请求都交给了FPM处理,从而完成Nginx到FPM的闭环。如此以来,Nginx与FPM通信的整个流程应该比较清晰了。

  • 修改了php.ini配置文件后,使用PHP-FPM为什么能平滑重启?

修改php.ini之后,PHP-CGI进程是没办法平滑重启的。PHP-FPM对此的处理机制是新的worker进程用新的配置,已经存在的worker进程处理完手上的活就可以歇着了,通过这种机制来平滑过渡。

Opache详解

Opcache的前生是Optimizer+, 它是PHP官方公司Zend开发的一款闭源但可以免费使用的PHP优化加速组件。
Optimizer+将PHP代码预编译生成的脚本文件Opcode缓存在共享内存中供以后反复使用,从而避免了从磁盘读取代码再次编译的时间消耗。
同时,它还应用了一些代码优化模式,使得代码执行更快,从而加速PHP的执行。

request请求(nginx, apache等)-> Zend引擎读取.php文件-> 扫描其词典和表达式(词法分析)->解析文件->创建要执行的计算机代码(称为Opcode)->最后执行Opcode->response返回
每一次请求PHP脚本都会执行一遍以上步骤,如果PHP源代码没有变化,那么Opcode也不会变化,显然没必要每次都重新生成Opcode, 结合在Web中无所不在的缓存机制,我们可以把Opcode缓存下来,以后直接访问缓存的Opcode岂不是更快,启用Opcode缓存之后的流程图如下所示:

Opcode cache 的目的是避免重复编译,减少CPU和内存开销。
PS: 当解释器完成对脚本代码的分析后,便将它们生成可以直接运行的中间代码,也称为操作码(Operate Code, opcode), 动态内容的性能瓶颈不在cpu和内存,而在I/O操作,比如数据库查询带来的I/O开销,那么opcache的性能提升非常有限,但总归是好的。
Opcode原理
Opcode 是一种PHP脚本编译后的中间语言,就像Java的ByteCode, 或者.NET的MSL。举个例子,比如你写个php代码:

<?php   
echo “Hello World”;  
 $a = 1 + 1;   
echo $a;
?>

PHP(PHP引擎Zend)执行这段代码会经过如下4个步骤:
1.Scanning , 将PHP代码转换为语言片段(Tokens)
2.Parsing, 将Tokens转换成简单而有意义的表达式
3.Compilation,  将表达式编译Opcodes
4.Execution, 顺次执行Opcodes, 每次一条,从而实现PHP脚本功能。
每次请求来,就不需要重复执行前面三步,从而大幅提高PHP执行速度。


Opcode是php语言里供zend引擎执行的一种中间代码,类似java中的字节码,或者python中的字节码对象pycodeobject.