命名格式转换

在做一些生成代码或生成配置时经常会遇到命名格式不一样的问题,比如有的地方要用驼峰命名(比如代码的类和遍历),有的地方要用下划线命名(比如数据库字段)。因此写了个小工具来快速将一组单词转换成特定格式的命名。

因为我的使用场景中没有太严苛的性能要求,所以里面用到了正则,并且应该也不是最优解,所以不建议在高并发的场景用(也没做过性能测试)。

在查规范的时候学习到了一些有意思的单词,很形象的形容了命名格式。
比如下划线命名是snake,纯大写的下划线命名(比如常量定义AAA_BBB_CCC)是screaming snake(尖叫蛇)。
大驼峰是pascal(这个其实不太理解为什么是帕斯卡)。
用横线命名(比如url、域名、docker服务名:aaa-bbb-ccc)是kebab(烤肉串)。

<?php
/**
 * Created by PhpStorm.
 * User: fyn
 * Date: 2018/10/9
 * Time: 10:40 AM
 */
namespace Fyn\Common;

class StringCase
{


    /**
     * snake_case
     * @param $string
     * @return string
     */
    public static function snakeCase($string) {
        $string = lcfirst($string);
        $string = preg_replace_callback('/[A-Z]+/', function ($text) {
            return '_'.strtolower($text[0]);
        },$string);
        $string = preg_replace('/[^a-zA-Z0-9]+/','_',$string);
        return $string;
    }

    /**
     * SCREAMING_SNAKE_CASE
     * @param $string
     * @return string
     */
    public static function screamingSnakeCase($string) {
        return strtoupper(self::snakeCase($string));
    }

    /**
     * kebab-case
     * @param $string
     * @return string
     */
    public static function kebabCase($string) {
        return str_replace('_','-',self::snakeCase($string));
    }

    /**
     * PascalCase
     * @param $string
     * @return string
     */
    public static function pascalCase($string) {
        $arr = preg_split('/[^a-zA-Z0-9]/', $string);
        $string = implode('',array_map('ucfirst',$arr));
        return $string;
    }

    /**
     * camelCase
     * @param $string
     * @return string
     */
    public static function camelCase($string) {
        return lcfirst(self::pascalCase($string));
    }

    /**
     * htmlcase
     * @param $string
     * @return string
     */
    public static function htmlCase($string) {
        return strtolower(preg_replace('/[^a-zA-Z0-9]+/','', $string));
    }
}

使用方法:

use Fyn\Common\StringCase;

echo StringCase::snakeCase("This_is A-test@text"),"\n";  //this_is_a_test_text

echo StringCase::kebabCase("This_is A-test@text"),"\n";  //this-is-a-test-text

echo StringCase::pascalCase("This_is A-test@text"),"\n"; //ThisIsATestText

echo StringCase::camelCase("This_is A-test@text"),"\n";  //thisIsATestText

php禁用xdebug的彩色html var_dump

这个功能有时会麻烦,直接看请求结果很乱,而且会忽略掉一些大的字段,可以通过这哥参数禁用

ini_set('xdebug.overload_var_dump', 0);
ini_set('html_errors', 0);

用PHP做gRPC的服务端

gRPC的官方文档中虽然没有给出PHP做Server的例子,但实际上grpc的扩展是支持的。

参考扩展中提供的几个类以及扩展内部的一些代码,写了一个简单的示例

这个示例只是证明PHP可以直接写gRPC服务,不代表可以在生产环境中这么用。主要原因有这么几点:

  1. 不借助pcntl或swoole的话是个单进程的服务,并发会阻塞
  2. 没做性能测试以及是否有内存泄漏的测试
  3. 而且直接PHP做Server的争议也比较大

完整示例代码(https://github.com/ssfyn/php-grpc-server-example

Server端的主要逻辑:

$this->server = new \Grpc\Server([]);
$this->server->addHttp2Port('0.0.0.0:50051');
$this->server->start();

while($request = $this->server->requestCall()){
    $method = $request->method;
    $call = $request->call;
    if ($method=='/dev.fyn.HelloWorld/SayHello') {
        //接收请求
        $recv = $call->startBatch([
            \Grpc\OP_RECV_MESSAGE => true
        ]);
        //将请求的数据转换为对象
        $request = new SayHelloRequest();
        $request->mergeFromString($recv->message);
        //调用业务代码
        $impl = new HelloWorldImpl();
        $response = $impl->sayHello($request);
        //处理返回值,即使没有metadata也要设置OP_SEND_INITIAL_METADATA,不然会一直阻塞在这里
        $call->startBatch([
            \Grpc\OP_SEND_INITIAL_METADATA => [],
            \Grpc\OP_SEND_MESSAGE => [
                'message'=>$response->serializeToString()
            ],
            \Grpc\OP_SEND_STATUS_FROM_SERVER => [
                'code'=>\Grpc\STATUS_OK,
                'details'=>'OK'
            ],
        ]);
    } else {
        $call->startBatch([
            \Grpc\OP_SEND_INITIAL_METADATA => [],
            \Grpc\OP_SEND_STATUS_FROM_SERVER => ['code'=>\Grpc\STATUS_NOT_FOUND,'details'=>'Not found'],
        ]);
    }
}

Mac安装PHP+GRPC

安装xcode命令行工具

xcode-select --install

安装brew

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

通过brew安装php5.6(或其它版本)

brew install php@5.6

通过brew安装php切换器

brew install brew-php-switcher

切换php版本

brew-php-switcher 5.6

通过pecl安装PHP的protobuf扩展(要编译,比较慢)

pecl install protobuf

通过pecl安装PHP的grpc扩展(要编译,比较慢)

pecl install grpc

PHP通过runkit覆盖一个方法

首先需要安装runkit扩展

这是原有类

namespace Fyn;

class Hello
{
    public function say(string $name): string {
        return sprintf('Hello %s', $name);
    }
}

通过runkit覆盖(必须先将类load进来才能覆盖)

if (class_exists(\Fyn\Hello:class)) {
    runkit_method_redefine(
        \Fyn\Hello:class,
        'say',
        '$msg',
        'return "Nihao {$msg}";'
    );
}

更多方法:http://php.net/manual/zh/book.runkit.php

不建议在生产环境中使用

Composer如何指定php的版本

这个问题主要出现在本地开发环境的php版本高于生产环境运行的php版本,或构建服务时构建环境的p hp版本高于运行环境的php版本。这时composer会引入一些版本过高的包。可以在composer.json中的config.platform中设置php版本。

{
    "require": {
    }
    "config": {
        "platform" :{
            "php": "5.6",
            "ext-grpc": "1.14",
            "ext-protobuf": "3.5"
        }
    }
}

同时也可以写一下用到的一些扩展,这样在install的时候就不回去检查是否真的装了这些扩展

PHP动态创建类的例子

1.实现一个stream_wrapper

class ClassStream {
    private $pos;
    private $stream;
    private $path;
    private static $_cache = [];
    public function stream_open($path, $mode, $options, &$opened_path) {
        $this->path = $path;
        $this->stream = self::$_cache[$path]?:'';
        $this->pos = 0;
        return true;
    }
    public function stream_read($count) {
        $ret = substr($this->stream, $this->pos, $count);
        $this->pos += strlen($ret);
        return $ret;
    }
    public function stream_write($data){
        $l=strlen($data);
        $this->stream =
            substr($this->stream, 0, $this->pos) .
            $data .
            substr($this->stream, $this->pos += $l);
        self::$_cache[$this->path] = $this->stream;
        return $l;
    }
    public function stream_eof() {
        return $this->pos >= strlen($this->stream);
    }
    public function stream_stat(){
        return true;
    }
    public function url_stat($path, $flags){
        if (!array_key_exists($path, self::$_cache)) {
            return false;
        }
        return [];
    }
}

stream_wrapper_register('class',ClassStream::class);

 

2.自动加载器

spl_autoload_register(function ($className){
    $path = 'class://' . $className;
    if(file_exists($path)) {
        require $path;
    }
});

 

3.动态创建一个类

$path = 'class://TestA';

$code = '
<?php
class TestA {
    public $b = 0;
}
';

file_put_contents($path, $code);

 

4.调用

$obj = new TestA();
var_dump($obj);