PHPStorm在windows上优雅使用gitbash

gitbash是windows上开发的一个神器终端工具,在执行一些类Linux命令时得心应手。

phpstorm的内置terminal是windows内置的cmd,不适合php开发,于是需要把cmd换成gitbash

直接替换后,会弹出新窗口,需要配置参数,如下面

"C:\git\bin\bash.exe" --login -i

PHP7.4.0一些新特性

2019年11月28日,php.net发布了php7.4.0,根据php.net网站公开信息,博主来翻译一下本次更新的一些特性

强类型的类变量(Typed properties)

类属性在定义的时候,可以设置它的类型

class User {
    public int $id;
    public string $name;
}

上面的例子可以强制$user->id 指定为 integer 类型,$user->name 指定为 string 类型

箭头函数(Arrow functions)

箭头函数提供了一个简练的返回值的语法

$factor = 10;
$nums = array_map(fn($n) => $n * $factor, [1, 2, 3, 4]);
// $nums = array(10, 20, 30, 40);

PS: => 右边仅仅支持一句话语句,不支持多个分号的语句

子类可以更改父类方法返回类型(Limited return type covariance and argument type contravariance)

class A {}
class B extends A {}

class Producer {
    public function method(): A {}
}
class ChildProducer extends Producer {
    public function method(): B {}
}

仅仅使用自动加载的时候,才支持此特性。在单个文件中,只有非循环类型引用是可用的,因为所有类在被使用之前引入到工作空间

空合并赋值运算符(Null coalescing assignment operator)

$array['key'] ??= computeDefault();
// 约等于下面的语句
if (!isset($array['key'])) {
    $array['key'] = computeDefault();
}

优雅合并数组(Unpacking inside arrays)

$parts = ['apple', 'pear'];
$fruits = ['banana', 'orange', ...$parts, 'watermelon'];
// $fruits = ['banana', 'orange', 'apple', 'pear', 'watermelon'];
// 等于下面的操作,保留了数组元素的顺序
$fruits = array_merage(['banana', 'orange'], $parts, ['watermelon']);

数字类型文字分隔符(Numeric literal separator)

6.674_083e-11; // float 浮点型
299_792_458;   // decimal 十进制
0xCAFE_F00D;   // hexadecimal 十六进制
0b0101_1111;   // binary 二进制

数字文本可以在数字之间包含下划线。这点博主有点懵逼,等博主用7.4.0跑一下看看什么结果

弱引用(Weak references)

弱引用允许码农保留对某个对象的引用,该对象不会阻止该对象被销毁。

允许从__toString()中抛出异常(Allow exceptions from __toString())

现在允许从 __toString() 方法中抛出异常。之前这么做,php会报一个致命错误(fatal error)。字符串转换方法(__toString())中现有的可恢复致命错误已转换为错误异常。换句话说:__toString方法中可以随便抛异常,上层可以捕获这异常并可以进行相应处理,旧版php不能这么做

CURL

libcurl在版本>=7.56.0中,CURLFile 支持 stream wrappers 和空白文件名(plain file names)

过滤器(Filter)

FILTER_VALIDATE_FLOAT 过滤器支持最小范围(min_range)和最大范围(max_range)参数选项, FILTER_VALIDATE_INT也是如此

FFI扩展

FFI是一个新的PHP扩展(extension),它可以简单地调用C语言类库的原生方法、使用原生变量、创建或访问数据结构

GD扩展

添加了IMG_FILTER_SCATTER 图像过滤器常量以对图像应用散射过滤器。

哈希(Hash)

增加了依赖于Castagnoli’s 多项式的CRC32哈希算法。这个CRC32用于存储系统中,例如iSCSI、SCTP、Btrfs、ext4。

多字节字符串(Multibyte String)

增加了mb_str_split()函数,它提供了和str_split()相同的功能,但是操作的是代码点(code point),而不是字节(Bytes)

代码点(code point)是指与一个编码表中的某个字符对应的代码值。UTF-16编码采用不同长度的编码表示所有Unicode代码点,每个16位二进制表示一个代码单元(code unit)。基本字符的范围为[U+0000~U+FFFF],辅助字符,即上面提到的增补字符,其两个代码单元的范围分别为[U+D800~U+DBFF]和[U+DC00~U+DFFF]。这样很容易就能知道一个代码单元是一个基本字符的编码还是一个辅助字符的第一或第二部分。

OPcache

支持预加载代码

正则(Regular Expressions (Perl-Compatible))

preg_replace_callback()preg_replace_callback_array()函数添加了flags参数, 这个参数支持PREG_OFFSET_CAPTUREPREG_UNMATCHED_AS_NULL常量。这会影响传递给回调函数的匹配内容的数组格式。

PDO

用户名和密码现在可以指定为mysql、mssql、sybase、dblib、firebird和oci驱动程序的PDO DSN的一部分。以前,只支持pgsql驱动程序。如果在构造函数和DSN中都指定了用户名/密码,则构造函数优先。

现在可以在SQL查询中转义问号,以避免它们被解释为参数占位符。使用 ?? 向数据库发送单个问号,例如使用PostgreSQL JSON key 存在 (?) 操作符。

PDO_OCI

现在可以使用PDOStatement::getColumnMeta()方法了

PDO_SQLite

PDOStatement::getAttribute(PDO::SQLITE_ATTR_READONLY_STATEMENT) 可以检查是否是只读状态,而不用修改数据库内容

PDO::errorInfo() and PDOStatement::errorInfo()中,PDO::setAttribute(PDO::SQLITE_ATTR_EXTENDED_RESULT_CODES, true) 启用了SQLite3 扩展结果码(result code)

尚未完成….

Ubnt EdgeRouter X如何设置支持ipv6上网

目前各家主流家庭宽带运营商已经开启ipv4/ipv6双栈接入能力。

博主在很久前被种草 2018-03-22 购买了Ubnt EdgeRouter X路由器

今天测试了一位网友的配置,终于可以使用原生ipv4/ipv6双栈网络接入,网络环境:江苏扬州电信家庭宽带PPPoE拨号

使用ssh客户端工具,博主在windows上面使用的是Xshell,进入shell环境

ssh ubnt:ubnt@192.168.1.1
# 这是我的ipv6部分以及相应防火墙的设置,er-x sfp,应该和er-x差不多,eth0是wan口,eth1-eth4为lan口,switch0,供参考:


configure
# Configure the PPPoE for IPv6(eth0):
set interfaces ethernet eth0 pppoe 0 ipv6 enable
set interfaces ethernet eth0 pppoe 0 ipv6 address autoconf
set interfaces ethernet eth0 pppoe 0 ipv6 dup-addr-detect-transmits 1
set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 0 prefix-length /60
set interfaces ethernet eth0 pppoe 0 dhcpv6-pd rapid-commit enable
set interfaces ethernet eth0 pppoe 0 dhcpv6-pd prefix-only

# Enable IPv6 SLAAC on the LAN(switch0):
set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 0 interface switch0 host-address ::1
set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 0 interface switch0 prefix-id :0
set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 0 interface switch0 service slaac

# Enable IPv6 on switch0:
set interfaces switch switch0 ipv6 dup-addr-detect-transmits 1
set interfaces switch switch0 ipv6 router-advert cur-hop-limit 64
set interfaces switch switch0 ipv6 router-advert link-mtu 0
set interfaces switch switch0 ipv6 router-advert managed-flag false
set interfaces switch switch0 ipv6 router-advert max-interval 600
set interfaces switch switch0 ipv6 router-advert other-config-flag false
set interfaces switch switch0 ipv6 router-advert prefix '::/64' autonomous-flag true
set interfaces switch switch0 ipv6 router-advert prefix '::/64' on-link-flag true
set interfaces switch switch0 ipv6 router-advert prefix '::/64' valid-lifetime 2592000
set interfaces switch switch0 ipv6 router-advert reachable-time 0
set interfaces switch switch0 ipv6 router-advert retrans-timer 0
set interfaces switch switch0 ipv6 router-advert send-advert true

# create a policy for WAN->Router:
set firewall ipv6-name WANv6_LOCAL default-action drop
set firewall ipv6-name WANv6_LOCAL description 'Local network traffic'
set firewall ipv6-name WANv6_LOCAL enable-default-log
set firewall ipv6-name WANv6_LOCAL rule 10 action accept
set firewall ipv6-name WANv6_LOCAL rule 10 description 'Allow established/related sessions'
set firewall ipv6-name WANv6_LOCAL rule 10 state established enable
set firewall ipv6-name WANv6_LOCAL rule 10 state related enable
set firewall ipv6-name WANv6_LOCAL rule 20 action drop
set firewall ipv6-name WANv6_LOCAL rule 20 description 'Drop invalid state'
set firewall ipv6-name WANv6_LOCAL rule 20 state invalid enable
set firewall ipv6-name WANv6_LOCAL rule 30 action accept
set firewall ipv6-name WANv6_LOCAL rule 30 description 'Allow IPv6 icmp'
set firewall ipv6-name WANv6_LOCAL rule 30 protocol icmpv6
set firewall ipv6-name WANv6_LOCAL rule 40 action accept
set firewall ipv6-name WANv6_LOCAL rule 40 description 'allow dhcpv6'
set firewall ipv6-name WANv6_LOCAL rule 40 destination port 546
set firewall ipv6-name WANv6_LOCAL rule 40 protocol udp
set firewall ipv6-name WANv6_LOCAL rule 40 source port 547
# create a policy for WAN->LAN Clients:
set firewall ipv6-name WANv6_IN default-action drop
set firewall ipv6-name WANv6_IN description 'WAN inbound traffic to the router'
set firewall ipv6-name WANv6_IN enable-default-log
set firewall ipv6-name WANv6_IN rule 10 action accept
set firewall ipv6-name WANv6_IN rule 10 description 'Allow established/related sessions'
set firewall ipv6-name WANv6_IN rule 10 state established enable
set firewall ipv6-name WANv6_IN rule 10 state related enable
set firewall ipv6-name WANv6_IN rule 20 action drop
set firewall ipv6-name WANv6_IN rule 20 description 'Drop invalid state'
set firewall ipv6-name WANv6_IN rule 20 state invalid enable
set firewall ipv6-name WANv6_IN rule 30 action accept
set firewall ipv6-name WANv6_IN rule 30 description 'Allow IPv6 icmp'
set firewall ipv6-name WANv6_IN rule 30 protocol icmpv6
set firewall ipv6-name WANv6_IN rule 40 action accept
set firewall ipv6-name WANv6_IN rule 40 description 'allow dhcpv6'
set firewall ipv6-name WANv6_IN rule 40 destination port 546
set firewall ipv6-name WANv6_IN rule 40 protocol udp
set firewall ipv6-name WANv6_IN rule 40 source port 547
set firewall ipv6-receive-redirects disable
set firewall ipv6-src-route disable
set interfaces ethernet eth0 pppoe 0 firewall in ipv6-name WANv6_IN
set interfaces ethernet eth0 pppoe 0 firewall local ipv6-name WANv6_LOCAL

commit
save
exit
# 重启路由器
reboot

等待路由器5分钟重启完毕,重启网卡以重新获取ip地址,可以看到如下信息

打开当前博客任意页面,查看右侧小工具,检测当前ip地址是否是网卡分配的对应地址

参考资料: [网络] ER-X如何设置支持ipv6上网?

微信小程序-接口wx.uploadFile 上传文件后返回不是JSON对象

最近接触到微信小程序接口开发工作,前端小伙伴提醒我上传文件接口返回的是文字,而不是json对象,如下图:

上面一个红框是普通接口返回的内容,下图是微信上传图片返回的内容。很显然普通接口自动反序列化成了json对象,wx.upload方法没有自动反序列化。所以用一种周全的方法解决此问题

wx.uploadFile({
      url: url,
      filePath: audioFile,
      name: 'audioFile',//这里是上传音频文件类型,按照需求填写
      header: {
        "Content-Type": "multipart/form-data",
        'accept': 'application/json',
      },
      formData: {
        'token': token,  //其他额外的formdata,按需求来
      },
      success: function (res) {
        let data = res.data;
        // 为什么这么这么写,看下文
        if ('object' !== typeof data) {
          //坑一:与wx.request不同,wx.uploadFile返回的是[字符串],需要自己转为JSON格式
          //如果不转换,直接用点运算符是获取不到后台返回的值的
          let data = JSON.parse(data)
          let status = data.status;
        } else {
          let status = res.status;
        }
        // todo 写你的业务代码了
      }
})

推荐大家写的时候加上'object' !== typeof data,防止微信在今后版本修复这个bug,导致你的代码会报错,推荐大家这么写

参考资料:http://www.majianwei.com/archives/8732

利用Docker搭建Elasticsearch和Kibana版本7.4.2(Mac)

最近公司技术部门发福利,给技术部小伙伴买了一些技术教程视频和书籍,考虑到App使用全文搜索功能,购买了Elasticsearch入门视频。

这篇文章记录一下使用Docker搭建本机测试用elasticsearch和网页图形工具kibana

当前elasticsearch和kibana的最新版本是7.4.2,使用命令拉取最新两个镜像:

docker pull elasticsearch:7.4.2
docker pull kibana:7.4.2

花费半小时拉取完毕之后(网速看脸,有时候快有时候慢),两个官方镜像就已经准备好了。

首先创建一下elasticsearch和kibana专用的docker网络:

docker network create --subnet=172.20.0.0/16 es

创建elasticsearch运行容器:

docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -v /Users/charles/docker/elasticsearch/data:/usr/share/elasticsearch/data --network es --ip 172.20.0.2 elasticsearch:7.4.2

上述命令中 /Users/charles/docker/elasticsearch/data 是elasticsearch存放索引数据的文件夹,换成你觉得方便的地方。设置固定ip方便设置kibana

防止遇到一些未知的错误,我建议你把上述命令中的-d参数去掉,不使用daemon方式运行,直接以前台运行,看到下图则表明elasticsearch运行正常,再加上-d作为后台服务运行

打开http://127.0.0.1:9200/测试一下是否运行正常

看到类似上图一样的JSON串说明elasticsearch运行正常。上图使用了Chrome的格式化JSON串插件,科学上网后点击此链接安装

接下来安装kibana客户端工具,使用命令获取elasticserach容器的虚拟内网ip地址

docker inspect elasticsearch | grep IPAddress
配图仅供参考

我的docker上显示的是 172.20.0.2,根据此IP地址启动kibana容器

docker run --network es --name kibana -e ELASTICSEARCH_URL=http://172.20.0.2:9200 -p 5601:5601 -d kibana:7.4.2

执行好此命令后,可以在浏览器打开http://127.0.0.1:5601

如果页面提示:Kibana server is not ready yet,稍等片刻再重试

见到上图,则说明kibana已经正常运行了。

可以使用命令,让elasticsearch、kibana在docker运行时自动启动,参考链接

docker update --restart=always elasticsearch
docker update --restart=always kibana

尽情享用elasticsearch吧

Xshell不能使用退格、删除键的解决方案

xshell使用中,若是敲错字母了的,按退键删除敲错的字母,却正在屏幕表现出了“^H”,无法正常删除,再按删除键,却表现出“^[[3~”

修改办法:当前连接属性–>终端 –>键盘 把delete 和backspace序列改为 ASCII 127即可。

Laravel/Lumen漂亮的解决主从库延迟问题

5.5及以上版本:

// 修改config/database.php文件
// 增加sticky配置项为true
'mysql' => [
	'driver' => 'mysql',
	'host' => env('DB_HOST', '127.0.0.1'),
	'port' => env('DB_PORT', '3306'),
	'database' => env('DB_DATABASE', 'forge'),
	'username' => env('DB_USERNAME', 'forge'),
	'password' => env('DB_PASSWORD', ''),
	'unix_socket' => env('DB_SOCKET', ''),
	'charset' => 'utf8mb4',
	'collation' => 'utf8mb4_general_ci',
	'prefix' => '',
	'prefix_indexes' => true,
	'strict' => true,
	'engine' => null,
	'sticky' => true,
],

5.4及以下版本:

app/Providers/AppServiceProvider增加boot方法,监听每条执行的SQL,如果发现DML语句(INSERT,UPDATE,DELETE)时,则清空从库连接的pdo(readPdo),这样由于Laravel底层安全机制,会默认使用主库连接。这样没有使用事务时,从根本上解决执行DML语句后,再执行DQL(SELECT)语句的延迟问题。

// Laravel/Lumen Connection源码,当readPdo为null时,默认使用主库pdo实例。
public function getReadPdo()
{
     if ($this->transactions >= 1) {
            return $this->getPdo();
     }
      return $this->readPdo ?: $this->pdo;
}

Laravel/Lumen 5.1 实现代码

public function boot()
{
    \DB::listen(function ($sql, $bindings, $time, $connection) {
          $sql = ltrim($sql);
          if (stripos($sql, 'insert') === 0
                   || stripos($sql, 'update') === 0
                    || stripos($sql, 'delete') === 0
           ) {
                 //清空从库连接, 自动使用主库连接
                 \DB::connection($connection)->setReadPdo(null);
           }
     });
}

Laravel/Lumen 5.2 实现代码

public function boot()
{
     \DB::listen(function (QueryExecuted $executed) {
                $executed->sql = ltrim($executed->sql);
                if (stripos($executed->sql, 'insert') === 0
                || stripos($executed->sql, 'update') === 0
                || stripos($executed->sql, 'delete') === 0
               ) {
                     \DB::connection($executed->connection)->setReadPdo(null);//清空从连接,会自动使用主连接
            }
       });
}

参考资料: http://blog.sina.com.cn/s/blog_9bbafb790102win1.html

微信网页授权的两种返回结果

微信公众号网页授权的两种方式,snsapi_base、snsapi_userinfo

snsapi_base基础信息静默授权,不会打扰到用户,授权返回内容是:

{
	"id": "og11w0hu...",
	"name": null,
	"nickname": null,
	"avatar": null,
	"email": null,
	"original": {
		"access_token": "27_T...",
		"expires_in": 7200,
		"refresh_token": "EQ...",
		"openid": "og1",
		"scope": "snsapi_base"
	},
	"token": "27_TDiwj62xKsng...",
	"provider": "WeChat"
}

snsapi_userinfo用户公开信息授权,第一次需要用户确认,授权返回内容是:

{
	"id": "og11w0hu...",
	"name": "Charles",
	"nickname": "Charles",
	"avatar": "http:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/PiajxSqBRaEJW4jPlAdDLJeG67fhnotIzBgL0tZjJBYnGLvG2TDPv0rAc0X7ZcZV03D4qsTicDPXTj7ibDkqbnYdw\/132",
	"email": null,
	"original": {
		"openid": "og1...",
		"nickname": "Charles",
		"sex": 1,
		"language": "zh_CN",
		"city": "扬州",
		"province": "江苏",
		"country": "中国",
		"headimgurl": "http:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/PiajxSqBRaEJW4jPlAdDLJeG67fhnotIzBgL0tZjJBYnGLvG2TDPv0rAc0X7ZcZV03D4qsTicDPXTj7ibDkqbnYdw\/132",
		"privilege": []
	},
	"token": "27_CFj...",
	"provider": "WeChat"
}

本文json串使用bejson工具格式化

laravel之Eloquent之Relation 枢纽表关联关系hasManyThrough

使用laravel的eloquent写两个表的关联关系是日常操作,hasOne,hasMany,belongsTo

会遇到有枢纽表(中间表)来做多对多关系用的,hasManyThrough这个方法几个参数比较难记下来

现有三个模型User、City、UserCity,对应表users、cities、user_cities

通过UserCity枢纽表关联User和City关系

现在在User表中写上City的关联关系

class User extends Model
{
    public function cities(): Relation
    {
        return $this->hasManyThrough(
            // 参数1 目标表类名
            City::class,
            // 参数2 枢纽表类名
            UserCity::class,
            // 参数3 枢纽表中和当前表关联的字段名
            'user_id',
            // 参数4 枢纽表中和目标表关联的字段名
            'city_id',
            // 参数5 当前表中和枢纽表关联的字段名,一般是主键
            'id',
            // 参数6 目标表中和枢纽表关联的字段名,一般是主键
            'id');
    }
}

prestissimo给composer加速

要求

  • composer >=1.0.0 (includes dev-master)
  • PHP >=5.3, (suggest >=5.5, because curl_share_init)
  • ext-curl

安装

$ composer global require hirak/prestissimo

卸载

$ composer global remove hirak/prestissimo

基准测试效果

288s -> 26s

$ composer create-project laravel/laravel laravel1 --no-progress --profile --prefer-dist