基于swoole框架进行二次封装,php高性能业务框架编写思路

swoole已经是php高性能服务器事实标准,可以参考这个博客使用swoole开发业务框架

项目地址:https://github.com/neatlife/pframework

欢迎star,欢迎pr

⁃   框架执行的核心流程图如下(右键可查看大图):

  1. 通用组件尽量遵守psr进行实现,以提高对三方组件的兼容性
  2. 事件驱动

全局变量适配

swoole是从命令行启动,常驻进程运行,web请求处理依赖的全局变量比如 \(_SERVER, \)_GET, \(_POST, \)_FILES等不会随着每次请求的改变填上对应的值,swoole把这个每次请求的变量放在了Swoole\Http\Request对象中

Read more   2019/7/23 posted in  SWOOLE PHP

dotenv 2到3版本设计模式重构实战分析

老大说了这个dotenv的重构比较适合用来学习,进行了分析,并分享了出来

github项目地址: https://github.com/vlucas/phpdotenv

dotenv是一个php写的从文件中加载环境变量的库,库本身文件数量较少,比较容易阅读。

核心api变动

2.x实例化方式

$dotenv = new Dotenv\Dotenv(__DIR__, 'myconfig');
$dotenv->load();

3.x实例化方式

$dotenv = Dotenv\Dotenv::create(__DIR__, 'myconfig');
$dotenv->load();

静态工厂方法设计模式应用

这个3.x版本create方法创建对象使用的是工厂方法设计模式

核心实例化dotenv对象由手动调用构造方法重构成了静态工厂方法,这个改变是因为这个dotenv加载.env文件需要判断env文件、envFactory对象是否传递,而这个判断是个if else的逻辑,根据构造方法不宜写逻辑定理,如果不使用静态工厂方法进行重构,那么在new DotEnv之前,必须手动判断.env是否存在。在多次实例化这个Dotenv对象时,必然造成代码重复。

通过工厂方法封装创建对象的逻辑,以避免创建对象的代码重复,是非常常见的一种设计模式的最佳实践

Read more   2019/7/21 posted in  PHP 设计模式

xaop源码分析

项目地址:https://github.com/liqiongfan/xaop

思路: hook掉php执行函数的方法(zend_execute_ex),然后使用call_user_function调用自定义的回调函数

文件和作用

Xaop操作类的所有方法:kernel/xaop.c

Xaop操作类会使用的宏,比如解析和检查方法的参数:kernel/xaop.h

扩展调用php函数的函数:kernel/helper.c 和 kernel/helper.h

替换php的zend_execution_ex: kernel/exec.c 和 kernel/exec.c

解析用户编写的annotation的相关逻辑:kernel/parsing.c 和 kernel/parsing.h

全局变量定义在: php_xaop.h

全局工具类zend_class_entry的声明*annotation_ce, *doc_ce, *xaop_ce:kernel/classes.c

检查参数是否合法

#define CHECK_PARAM() do {\
    if ( Z_TYPE_P(class_name) != IS_STRING && Z_TYPE_P(class_name) != IS_NULL ) { \
        php_error_docref(NULL, E_ERROR, "First argument need to be a valid class name or NULL");\
        return ;\
    }\
    if ( ZSTR_LEN(function_name) && '*' == ZSTR_VAL(function_name)[0] ) { \
        php_error_docref(NULL, E_ERROR, "Function name mustn't be `*`.");\
        return ;\
    }\
    if ( !zend_is_callable(aop, IS_CALLABLE_CHECK_NO_ACCESS, NULL) ) {\
        php_error_docref(NULL, E_ERROR, "Third argument is expected to be a valid callback");\
        return ;\
    }\
} while(0)
Read more   2019/6/20 posted in  源码分析 PHP

在mac os中使用phpbrew管理本地php版本

虽然mac下的mamp pro非常好用,并且提供gui操作界面,但是是收费软件,还比较贵,目前使用phpbrew作为mamp pro的替代品,除了没有gui界面,对php的版本管理不输mamp pro

安装phpbrew

先使用homebrew安装依赖

# xcode-select --install
brew install automake autoconf curl pcre bison re2c mhash libtool icu4c gettext jpeg openssl libxml2 mcrypt gmp libevent
brew link icu4c
brew link --force openssl
brew link --force libxml2

安装phpbrew

curl -L -O https://github.com/phpbrew/phpbrew/raw/master/phpbrew
chmod +x phpbrew

mv phpbrew /usr/local/bin/phpbrew
Read more   2019/6/11 posted in  PHP

在spring boot中三分钟上手阿里云日志服务Log Service

虽然可以自行搭建elk等日志系统,但是日志系统特别耗费系统资源,相对于阿里云提供日志服务,自行搭建的机器成本和运维成本较高,一般可以直接选择使用阿里云的日志服务

先来看一张阿里云日志服务的架构图

从上面这张图可以看到这个阿里云的日志服务不仅支持多个数据源,同时在日志服务的基础上衍生出不少数据二次加工的功能,比如MacCompute、Spark Streaming等

创建案例项目

Read more   2019/5/27 posted in  三分钟系列 Spring Boot PHP

使用gdb调试工具上手调试php和swoole源码

swoole作为php的核心项目,php和swoole都具有一定的研究价值,由于是c语言编写的项目,要上手进行调试,那么最好用的调试工具就是gdb了

这个gdb调试工具功能强大,支持的选项也是非常多,下面就总结出来常用的命令

编译源码添加调试信息

在编译时把调试信息编译进生成的二进制文件中,需要给gcc编译器加上-g参数,比如编译php的Makefile中是这样的

加上-g参数就已经有调试信息了,-O0是关闭gcc的优化,这个gcc优化后会在调试时丢失一部分调试信息,所以一般建议关闭

这样在gdb里面就可以随时查看当前执行到源码的那个地方了,能够显著提升调试的效率

查看源码

Read more   2019/5/18 posted in  GDB PHP SWOOLE 源码分析

面试题-字符串的处理 近义词正反双向替换

试题

$words = array(
'我'=>'你',

'不错'=>'很好',

'cc'=>'XXX',

);

$str = '你和我今天很好,XXX,是不是不错';

替换后结果 我和你今天不错,cc,是不是很好

(近义词可能是任意词语,给定的数组的键替换成值,值替换成键)

解决方案

参考代码:

<?php

$words = array(
    '我'=>'你',
    '不错'=>'很好',
    'cc'=>'XXX',
);
$str = '你和我今天很好,XXX,是不是不错';

$words = array_merge($words, array_flip($words));
$regex = "/" . implode('|', array_map(function($word) {
    $word = "(" . str_replace(['"'], [''], json_encode($word)) . ")";
    $word = preg_replace("/\\\u(.{4})/", "\x{\$1}", $word);
    return $word;
}, array_keys($words))) . "/u";

echo preg_replace_callback($regex, function($matches) use ($words) {
    $keyword = end($matches);
    return $words[$keyword];
}, $str);
2017/8/9 posted in  面试题 PHP

socket和socket通信与socket和websocket通信的区别

  1. socket和socket通信的数据没有经过编码,所以在socket服务端无需对接收的数据解码,和对返回的数据编码
  2. socket和socket建立连接时就使用的socket协议进行通信,而websocket建立连接时使用的HTTP协议,必须提升通信协议为websocket
  3. socket和socket通信没有握手过程,socket和websocket时,socket必须给websocket返回握手响应

附socket与socket通信参考代码

server

<?php

$host = '0.0.0.0';
$port = 12345;

$master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)      or die("socket_create()函数调用失败。");
socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1)     or die("socket_option() failed");// 设置重用
socket_bind($master, $host, $port)                          or die("socket_bind() 函数调用失败。");
socket_listen($master, 20);
$sockets = [];
$sockets[] = $master;
while(true) {
    $activeSockets = $sockets;
    $write = $except = null;
    $status = socket_select($activeSockets, $write, $except, null);
    if ($status === 0) {
        continue;
    }
    if ($status === false) {
        echo socket_last_error() . "\r\n";
        continue;
    }
    if (in_array($master, $activeSockets)) {
        $newSocket = socket_accept($master);
        if (!$newSocket) {
            continue;
        }
        $sockets[] = $newSocket;
        $activeSockets[] = $newSocket;
        $index = array_search($master, $activeSockets);
        unset($activeSockets[$index]);
    }
    foreach($activeSockets as $activeSocket) {
        $socketId = (int) $activeSocket;
        $requestDataLength = socket_recv($activeSocket, $requestData, 2048, 0);
        if ($requestDataLength == 0) {
            socket_close($activeSocket);
            $index = array_search($activeSocket, $sockets);
            unset($sockets[$index]);
            continue;
        }
        $response = "";
        $data = json_decode($requestData, true);
        if ($data) {
            switch($data['operator']) {
                case 'add':
                    $response = $data['firstNum'] + $data['secondNum'];
                    break;
                case 'sub':
                    $response = $data['firstNum'] - $data['secondNum'];
                    break;
                case 'multi':
                    $response = $data['firstNum'] * $data['secondNum'];
                    break;
                case 'div':
                    $response = $data['firstNum'] / $data['secondNum'];
                    break;
            }
        } else {
            $response = $requestData;
        }
        socket_write($activeSocket, $response, strlen($response));
    }

};

client

<?php

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$result = socket_connect($socket, '127.0.0.1', '12345');
$raw = json_encode([
    'firstNum' => 10,
    'secondNum' => 20,
    'operator' => 'add',
]);
socket_write($socket, $raw, strlen($raw));
$result = socket_read($socket, 2048);
echo $result;
socket_close($socket);
2017/6/19 posted in  PHP

图片16:9缩放

function crop_image($image) {
    list($w_i, $h_i, $type) = getimagesize($image);
 
    $w_o = $w_i;
    $h_o = 9 * $w_o / 16;
 
    if ($h_i < $h_o) {
        $h_o = $h_i;
        $w_o = 16 * $h_o / 9;
    }
 
    $x_o = $w_i - $w_o;
    $y_o = $h_i - $h_o;
 
    $types = array("", "gif", "jpeg", "png");
    $ext = $types[$type];
    if ($ext) {
      $func = 'imagecreatefrom'.$ext;
      $img_i = $func($image);
    } else {
      echo 'Incorrect image';
      return false;
    }
    if ($x_o + $w_o > $w_i) $w_o = $w_i - $x_o;
    if ($y_o + $h_o > $h_i) $h_o = $h_i - $y_o;
    $img_o = imagecreatetruecolor($w_o, $h_o);
    imagecopy($img_o, $img_i, 0, 0, $x_o/2, $y_o/2, $w_o, $h_o);
    $func = 'image'.$ext;
    return $func($img_o, $image);  
}
 
crop_image("screen.png");

改良后的代码(解决获取图片宽高错误)

function crop_image($image) {
    $img = imagecreatefromstring(file_get_contents($image));
    $exif = @exif_read_data($image);
    if( ! empty($exif['Orientation'])) {
        switch($exif['Orientation']) {
            case 8:
                $img = imagerotate($img, 90, 0);
                break;
            case 3:
                $img = imagerotate($img, 180, 0);
                break;
            case 6:
                $img = imagerotate($img, -90, 0);
                break;
        }
        list($_, $_, $type) = getimagesize($image);
        $types = array("", "gif", "jpeg", "png");
        $ext = $types[$type];
        $func = 'image'.$ext;
        $func($img, $image);
    }
    list($w_i, $h_i, $type) = getimagesize($image);
 
    $w_o = $w_i;
    $h_o = 9 * $w_o / 16;
 
    if ($h_i < $h_o) {
        $h_o = $h_i;
        $w_o = 16 * $h_o / 9;
    }
 
    $x_o = $w_i - $w_o;
    $y_o = $h_i - $h_o;
 
    $types = array("", "gif", "jpeg", "png");
    $ext = $types[$type];
    if ($ext) {
      $func = 'imagecreatefrom'.$ext;
      $img_i = $func($image);
    } else {
      echo 'Incorrect image';
      return false;
    }
    if ($x_o + $w_o > $w_i) $w_o = $w_i - $x_o;
    if ($y_o + $h_o > $h_i) $h_o = $h_i - $y_o;
    $img_o = imagecreatetruecolor($w_o, $h_o);
    imagecopy($img_o, $img_i, 0, 0, $x_o/2, $y_o/2, $w_o, $h_o);
    $func = 'image'.$ext;
    return $func($img_o, $image);
}
 
crop_image("1.jpg");
2017/4/15 posted in  PHP

面试题-在页面上显示一张图片

项目需求

在页面上显示一张图片,此图片是由多个部分组成:背景,小岛和海鸥。根据传入参数的不同,小岛和海鸥在背景中的位置将可以变化。

<?php

class pic
{
    private $_url;

    public function __get($name)
    {
        return $this->$name;
    }

    public function __set($name,$value)
    {
        return $this->$name = $value;
    }
}
interface layer
{
    public function loadPic(pic $pic);
}

class background implements layer
{
    private $_z = 0;
    private $_pic;
    private $_width;
    private $_height;
    private $_html;

    public function loadPic(pic $pic)
    {
        $this->_pic = $pic;
    }

    public function __get($name)
    {
        return $this->$name;
    }

    public function __set($name,$value)
    {
        return $this->$name = $value;
    }

    public function generateHtml()
    {
        $this->_html = "<img src='{$this->_pic->_url}' width='{$this->_width}' height='{$this->_height}' style='z-index: {$this->_z};'>";
        return $this->_html;
    }
}

abstract class onLayer implements layer
{
    private $_x;
    private $_y;
    private $_z;
    private $_pic;
    private $_width;
    private $_height;
    private $_html;
    private $_downLayer;

    public function loadPic(pic $pic)
    {
        $this->_pic = $pic;
    }

    public function __get($name)
    {
        return $this->$name;
    }

    public function __set($name,$value)
    {
        return $this->$name = $value;
    }

    abstract public function on(layer $newLayer);

    public function generateHtml()
    {
        $this->_html = $this->_downLayer->_html."<img src='{$this->_pic->_url}' width='{$this->_width}' height='{$this->_height}' style='z-index: {$this->_z}; position: absolute; left: {$this->_x}px; top: {$this->_y}px;'>";
        return $this->_html;
    }
}

class island extends onLayer
{
    public function on(layer $downLayer)
    {
        $this->_width = 100;
        $this->_height = 50;
        $this->_downLayer = $downLayer;
        $this->_z = $downLayer->_z + 20;
    }
}

class bird extends onLayer
{
    public function on(layer $downLayer)
    {
        $this->_x = 20;
        $this->_y = 30;
        $this->_width = 20;
        $this->_height = 20;
        $this->_downLayer = $downLayer;
        $this->_z = $downLayer->_z + 21;
    }
}

$inland_x = isset($_REQUEST['_x']) ? $_REQUEST['_x'] : 0;
$inland_y = isset($_REQUEST['_y']) ? $_REQUEST['_y'] : 0;

No.1

开始写代码,显示由背景、小岛和海鸥三个图片组合而成的背景图

/*
背景图片为: sea.jpg
岛的图片为: island.jpg
鸟的图片为: bird.jpg
*/
$pic = new pic();
$pic->_url = 'sea.jpg';
$background = new background();
$background->loadPic($pic);
$background->_width = '400';
$background->_height = '300';
$background->generateHtml();


$pic1 = new pic();
$pic1->_url = 'island.jpg';
$island = new island();
$island->loadPic($pic1);
$island->_x = $inland_x;
$island->_y = $inland_y;
$island->on($background);
$island->generateHtml();

$pic2 = new pic();
$pic2->_url = 'bird.jpg';
$bird = new bird();
$bird->loadPic($pic2);
$bird->on($island);
$bird->_x = $inland_x;
$bird->_y = $inland_y;


$seaPic = $bird->generateHtml();
echo $seaPic;
//end_code

?>

<form action="#" method="post" style="">
    <input name="_x" type="text">
    <input name="_y" type="text">
    <input type="submit">
</form>

三张素材图

2017/3/15 posted in  面试题 PHP

FFMPEG获取视频播放时长

function video_info($file,$ffmpeg) {
    ob_start();
    passthru(sprintf($ffmpeg.' -i "%s" 2>&1', $file));
    $info = ob_get_contents();
    ob_end_clean();
  // 通过使用输出缓冲,获取到ffmpeg所有输出的内容。
   $ret = array();
    // Duration: 01:24:12.73, start: 0.000000, bitrate: 456 kb/s
    if (preg_match("/Duration: (.*?), start: (.*?), bitrate: (\d*) kb\/s/", $info, $match)) {
        $ret['duration'] = $match[1]; // 提取出播放时间
        $da = explode(':', $match[1]); 
        $ret['seconds'] = $da[0] * 3600 + $da[1] * 60 + $da[2]; // 转换为秒
        $ret['start'] = $match[2]; // 开始时间
        $ret['bitrate'] = $match[3]; // bitrate 码率 单位 kb
    }

    // Stream #0.1: Video: rv40, yuv420p, 512x384, 355 kb/s, 12.05 fps, 12 tbr, 1k tbn, 12 tbc
    if (preg_match("/Video: (.*?), (.*?), (.*?)[,\s]/", $info, $match)) {
        $ret['vcodec'] = $match[1]; // 编码格式
        $ret['vformat'] = $match[2]; // 视频格式 
        $ret['resolution'] = $match[3]; // 分辨率
        $a = explode('x', $match[3]);
        $ret['width'] = $a[0];
        $ret['height'] = $a[1];
    }

    // Stream #0.0: Audio: cook, 44100 Hz, stereo, s16, 96 kb/s
    if (preg_match("/Audio: (\w*), (\d*) Hz/", $info, $match)) {
        $ret['acodec'] = $match[1];       // 音频编码
        $ret['asamplerate'] = $match[2];  // 音频采样频率
    }

    if (isset($ret['seconds']) && isset($ret['start'])) {
        $ret['play_time'] = $ret['seconds'] + $ret['start']; // 实际播放时间
    }

    $ret['size'] = filesize($file); // 文件大小
    return $ret;
}
2017/3/7 posted in  PHP

PHP分段下载文件

function download($file)
{
    $fp = @fopen($file, 'rb');
    $size   = filesize($file); // File size
    $length = $size;           // Content length
    $start  = 0;               // Start byte
    $end    = $size - 1;       // End byte
    header('Content-type: octet-stream');
    header("Accept-Ranges: 0-$length");
    if (isset($_SERVER['HTTP_RANGE'])) {
        $cStart = $start;
        $cEnd   = $end;
        list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
        if (strpos($range, ',') !== false) {
            header('HTTP/1.1 416 Requested Range Not Satisfiable');
            header("Content-Range: bytes $start-$end/$size");
            exit;
        }
        if ($range == '-') {
            $cStart = $size - substr($range, 1);
        }else{
            $range  = explode('-', $range);
            $cStart = $range[0];
            $cEnd   = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
        }
        $cEnd = ($cEnd > $end) ? $end : $cEnd;
        if ($cStart > $cEnd || $cStart > $size - 1 || $cEnd >= $size) {
            header('HTTP/1.1 416 Requested Range Not Satisfiable');
            header("Content-Range: bytes $start-$end/$size");
            exit;
        }
        $start  = $cStart;
        $end    = $cEnd;
        $length = $end - $start + 1;
        fseek($fp, $start);
        header('HTTP/1.1 206 Partial Content');
    }
    header("Content-Range: bytes $start-$end/$size");
    header("Content-Length: " . $length);
    $buffer = 1024 * 8;
    while(!feof($fp) && ($p = ftell($fp)) <= $end) {
        if ($p + $buffer > $end) {
            $buffer = $end - $p + 1;
        }
        set_time_limit(0);
        echo fread($fp, $buffer);
        flush();
    }
    fclose($fp);
    exit();
}
2017/2/23 posted in  PHP