`PHP`标签下的文章

PHP

引用符号'&'在foreach循环中的惊喜

工作中总会遇到一些奇奇怪怪的由前人所写下的不可置疑的代码,如果你仅仅跟着眼前所见的代码去理解他人的思路,Well You have fell into a terrible situation.

贴一段示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
$students = [
['name'=>'Jackson','age'=>15],
['name'=>'Jerry','age'=>14],
['name'=>'Amy','age'=>14]
];
$classes = [
'CL001'=>['name'=>'Language','classTime'=>30],
'CL002'=>['name'=>'PE','classTime'=>13]
];

/*
* 循环$students,为其添加一个属性'name-age'
* 这里使用了&$v以获得数组内部元素的引用,使的可以在循环体内修改元素内容
*/
foreach ($students as $k=>&$v){
$v['name-age'] = $v['name'] . '-' .$v['age'];
}

$classString = ''; //定义一个空串
/*
* 循环$classes,将所有课程ID和名称联结成串
*/
foreach ($classes as $k=>$v){
$classString .= 'ClassID:'.$k.'-'.$v['name'].PHP_EOL;
}
echo $classString;
var_dump($students); //Think here!

你也许会回答这样一个输出结果:

阅读剩下更多

默认配图
PHP

PHP加解密算法使用openssl替换mcrypt扩展

PHP版本从7.2开始不再支持mcrypt扩展,所以我们需要使用OpenSSl对其进行替换。本文仅列出部分算法的替换示例,所以不在本文出现的算法或模式需要自行尝试,顺水推舟。

本文替换案例:

  1. MCRYPT_RIJNDAEL_128 | MCRYPT_MODE_ECB => AES-128-ECB
  2. MCRYPT_DES | MCRYPT_MODE_CBC => DES-CBC
  3. MCRYPT_RIJNDAEL_128 | MCRYPT_MODE_CBC => AES-128-CBC
  4. MCRYPT_XTEA | MCRYPT_MODE_CBC

在使用 MCRYPT_RIJNDAEL_128 的地方,如果秘钥长度分别为16、24、32,则加密算法用 AES-128-ECB、AES-192-ECB、AES-256-ECB,BlockSize为16、24、32。

首先列出需要用到的数据填充方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function ZeroPadding($str, $block = 16) {
$pad = $block - (strlen($str) % $block);
if($pad == $block) return $str;
return $str.str_repeat(chr(0),$pad);
}
function ZeroUnPadding($str) {
return rtrim($str, "\0");
}


function PKCS7Padding($str, $block_size) {
$padding_char = $block_size - (strlen($str) % $block_size);
$padding_str = str_repeat(chr($padding_char),$padding_char);
return $str.$padding_str;
}
function PKCS7UnPadding($str) {
$char=substr($str,-1,1);
$num=ord($char);
if($num>0 && $num <= strlen($str)) {
$str = substr($str, 0, -1 * $num);
}
return $str;
}

阅读剩下更多

默认配图
学习笔记

Go-Server/Client以及PHP-Client之间的GRPC初次尝试

写这篇笔记的目的

为了应对后续开发生涯中可能遇到的种种情况以及分布式计算的趋势(讲白了就是后续对工作会很有帮助)。如若总是依赖http-api/restful编写并提供外部调用接口,当接口数量不断上升,文档内容不断增加,这给设计者和使用者都带来非常不好的体验,而RPC在这体就现出了非常大的优势。我将自己的理解和体会以及学习的过程记录在这里,以便今后遇到问题能够从这儿获得些许的线索以及提供一个参考给同道中人。

前提

不妨思考这样一个情形:作为接口设计者,我早已经定义好的接口的请求方式(RESTFul)和返回结构(json),但是每个接口我还需要另外维护一个文档来说明各个接口的用法(请求参数)和解释返回的结果(字段描述)。而对于接口的调用者而言,不但要去文档中查找自己需要的接口并阅读说明,在实际调用中,还要以防接口提供者返回非既定结构的结果而导致的报错。

介绍

Remote Procedure Call(远程过程调用),简称RPC。它可以使得调用远程服务接口如同调用本地方法一样简单。虽然实质还是通过网络通信,但是相比http请求api的网络开销还是极小的,其原因简单来说,HTTP协议每次请求都需要建立TCP连接,就会涉及3次握手的网络开销问题以及冗余报文,而rpc直接使用TCP多路复用(gRPC基于HTTP/2)无需重复建立连接。就文档方面而言,编写一份开发文档足矣,因为接口的定义由接口定义语言(IDL)来完成,而阅读IDL便可理解所有接口,并且通过编译器可以将IDL编译成不同的语言实现源码(gRPC通过protoc编译器将protobuf编译)。其他优点例如:注册、监控、发布等的这里不做论述。

Go-Server

GRPC的官方资源:go get google.golang.org/grpc

GRPC的镜像资源:https://github.com/grpc/grpc

官方的相关示列可以在grpc/examples/中找到。

定义服务

使用protocol buffers去定义gRPC service和方法 request以及 response 的类型。

新增并编辑文件:something.proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
syntax = "proto3";

// 声明包/作用域
package something;

// 定义一个服务,名为Wiwider
service Wiwider {
rpc FindUser (UserRequest) returns (UserReply) {} // 声明一个方法
rpc FindUsers (UserRequest) returns (UsersReply) {}
}

// 定义一个请求消息
message UserRequest {
string name = 1;
}

// 定义一个响应消息
message UserReply {
int64 id = 1;
string name = 2;
int32 age = 3;
sexes sex = 4;

enum sexes {
Male = 0;
Female = 1;
}
}

// 定义一个包含消息的消息
message UsersReply {
repeated UserReply users = 1;
}

生成代码

Go-代码

1
protoc --go_out=plugins=grpc:. something.proto

运行这个命令后,会在当前目录生成一个something.pb.go文件,内容包含:

  • 所有用于填充,序列化和获取我们请求和响应消息类型的 protocol buffer 代码
  • 一个为客户端调用定义在Wiwider服务的方法的接口类型(或者 存根 )
  • 一个为服务器使用定义在Wiwider服务的方法去实现的接口类型(或者 存根 )

PHP-代码

1
protoc --proto_path=./ --php_out=./ --grpc_out=./ --plugin=protoc-gen-grpc=/Users/anthony/git/grpc/bins/opt/grpc_php_plugin ./something.proto

其中/Users/anthony/git/grpc/bins/opt/grpc_php_plugin对应从git中获取的<grpc-git-path>/bins/opt/grpc_php_plugin

运行这个命令后,会在当前目录生成如下:

  • GPBMetadata/something.php
  • Something/UserReply/sexes.php
  • Something/UserReply.php
  • Something/UserReply_sexes.php
  • Something/UserRequest.php
  • Something/UsersReply.php
  • Something/WiwiderClient.php

创建服务器

首先我们需要实现服务定义的服务接口:

something/something.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package something

import (
"Wiwide/cmi"
"context"
)
type UserServer struct {

}

func (s *UserServer) FindUser(ctx context.Context, in *UserRequest) (*UserReply, error) {
name := in.GetName()
rs, err := cmi.Db("local").Table("users").Where("name", name).First() // 从数据库查询
if err != nil || len(rs) == 0 {
return &UserReply{},err
}
rId := rs["id"].(int64)
rName := rs["name"].(string)
rAge := int32(rs["age"].(int64))
rSex := UserReplySexes(rs["sex"].(int64))
rep := &UserReply{Id:rId,Name:rName,Age:rAge,Sex:rSex}
return rep, nil
}

func (s *UserServer) FindUsers(ctx context.Context, in *UserRequest) (*UsersReply, error) {
name := in.GetName()
rs, err := cmi.Db("local").Table("users").Where("name","like","%"+name+"%").Get() // 从数据库查询
if err != nil {
return &UsersReply{}, err
}
list := make([]*UserReply, 0)
for _, v := range rs {
rId := v["id"].(int64)
rName := v["name"].(string)
rAge := int32(v["age"].(int64))
rSex := UserReplySexes(v["sex"].(int64))
list = append(list, &UserReply{Id:rId,Name:rName,Age:rAge,Sex:rSex})
}
rep := &UsersReply{Users: list}
return rep, nil
}

然后运行一个gRPC服务器,注册我们的服务并监听来自客户端的请求:

server.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"Wiwide/rpc/something"
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"log"
"net"
)

var (
port = ":50051"
)
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
something.RegisterWiwiderServer(s, &something.UserServer{})
reflection.Register(s)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}

运行服务器

简单执行命令:go run server.go,然后等待客户端请求。

创建客户端

Go-Client

创建文件client.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
"Wiwide/rpc/something"
"context"
"google.golang.org/grpc"
"log"
"os"
"time"
)

const(
address = "localhost:50051"
defaultName = "World"
)

func main(){
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()

name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
s := something.NewWiwiderClient(conn)
u, err := s.FindUser(ctx, &something.UserRequest{Name:name})
if err != nil {
log.Fatalf("could not Find: %v", err)
}
us, err := s.FindUsers(ctx, &something.UserRequest{Name:name})
if err != nil {
log.Fatalf("could not Find users: %v", err)
}
log.Printf("Find user: %v", u)
log.Printf("Find users: %v", us)
}

PHP-Client

PHP首先要安装grpc的php扩展,下载地址:http://pecl.php.net/package/gRPC

直接使用phpize安装:

1
2
3
4
5
6
7
8
tar -zxf grpc-1.17.0.tgz
cd grpc-1.17.0
phpize
./configure --with-php-config=/usr/local/php/bin/php-config
make
make install
# 在php.ini中增加
extension = grpc.so

执行php -m | grep grpc 应该会输出”grpc”,就代表成功了。

创建客户端文件,这里需要用到composer获取两个包,其中composer.json内容为:

1
2
3
4
5
6
7
8
{
"name": "grpc/grpc-demo",
"description": "gRPC example for PHP",
"require": {
"grpc/grpc": "^v1.3.0",
"google/protobuf": "^v3.3.0"
}
}

something.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
require dirname(__FILE__).'/vendor/autoload.php';

include_once dirname(__FILE__).'/Something/UserReply.php';
include_once dirname(__FILE__).'/Something/UsersReply.php';
include_once dirname(__FILE__).'/Something/UserRequest.php';
include_once dirname(__FILE__).'/Something/WiwiderClient.php';
include_once dirname(__FILE__).'/GPBMetadata/Something.php';

$client = new Something\WiwiderClient('127.0.0.1:50051', ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$req = new Something\UserRequest();
$name = !empty($argv[1]) ? $argv[1] : 'world';
$req->setName($name);
list($reply, $status) = $client->FindUser($req)->wait();
var_dump($reply->getId(), $reply->getName(), $reply->getAge(), $reply->getSex(), $status);

list($reply, $status) = $client->FindUsers($req)->wait();
$uers = $reply->getUsers();
$users = array();
foreach ($uers as $key => $value) {
array_push($users, array('id'=>$value->getId(),'name'=>$value->getName(),'age'=>$value->getAge(),'sex'=>$value->getSex()));
}
var_dump($users);

运行客户端

Go-Client

我们先编译一下:go build client.go,输出可执行文件client,然后直接运行:

  • ./client Anthony
  • ./client ny

分别输出如下:

1
2
2018/12/02 13:40:15 Find user: id:1 name:"Anthony" age:24 sex:Female 
2018/12/02 13:40:15 Find users: users:<id:1 name:"Anthony" age:24 sex:Female >
1
2
2018/12/02 13:40:25 Find user: 
2018/12/02 13:40:25 Find users: users:<id:1 name:"Anthony" age:24 sex:Female > users:<id:4 name:"funny" age:15 sex:2 >

PHP-Client

直接执行PHP文件:php something.php Anthonyphp something.php ny,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int(1)
string(7) "Anthony"
int(24)
int(1)
object(stdClass)#8 (3) {
["metadata"]=>
array(0) {
}
["code"]=>
int(0)
["details"]=>
string(0) ""
}
array(1) {
[0]=>
array(4) {
["id"]=>
int(1)
["name"]=>
string(7) "Anthony"
["age"]=>
int(24)
["sex"]=>
int(1)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int(0)
string(0) ""
int(0)
int(0)
object(stdClass)#8 (3) {
["metadata"]=>
array(0) {
}
["code"]=>
int(0)
["details"]=>
string(0) ""
}
array(2) {
[0]=>
array(4) {
["id"]=>
int(1)
["name"]=>
string(7) "Anthony"
["age"]=>
int(24)
["sex"]=>
int(1)
}
[1]=>
array(4) {
["id"]=>
int(4)
["name"]=>
string(5) "funny"
["age"]=>
int(15)
["sex"]=>
int(2)
}
}

结束

本次笔记接近结尾了,内容很简单,主要记录使用grpc的大体流程。为后续的测试做一点点的准备。

参考资料

阅读剩下更多

默认配图
学习笔记

判断IP地址是否内网IP

当你的服务同时开放于公网和内网,子服务却仅允许内网访问,那么则会涉及到IP白名单的功能。但是如果名单太多或者服务进行了迁移,那么维护起来会相当的麻烦,最简单的就是判断访问的来源IP是否内网IP地址,从而直接屏蔽掉公网IP。

首先,我们认识了3类私有地址:

A类:10.0.0.0-10.255.255.255

B类:172.16.0.0-172.31.255.255

C类:192.168.0.0-192.168.255.255

还有一个本机地址:127.0.0.1

他们就是我们平时所谓的内网IP地址。

方法一: PHP自带函数

filter_var($IP, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)

如果$IP是内网IP则会返回false,否则返回ip字符串。

1
2
3
4
5
6
7
$IP = get_client_ip(); //该方法自己实现,返回客户端的IP地址
$is_publicIP = filter_var($IP, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
if($is_publicIP){
echo '公网IP';
}else{
echo '内网IP';
}

方法二: 适合有网络基础的胖友阅读

关于IP地址的知识,这里就不讲太多了,下面的方法就是根据IP地址的定义和网段的划分等专业知识进行判定的。懂的就自然懂啦!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function isLocal($ip){
$long=ip2long($ip);
$data=array(
24=>'10.255.255.255',
20=>'172.31.255.255',
16=>'192.168.255.255'
);
foreach($data as $k=>$v){
if($long >> $k === ip2long($v)>>$k){
return true;
}
}
return false;
}

方法三:正则匹配

更加不用解释了。

1
2
3
4
<?php
function isLocal($ip){
return preg_match('%^127\.|10\.|192\.168|172\.(1[6-9]|2|3[01])%',$ip);
}

参考:

  1. http://php.net/manual/zh/function.filter-var.php
  2. https://www.51-n.com/t-4360-1-1.html
  3. https://blog.csdn.net/z_qifa/article/details/75497577

阅读剩下更多

默认配图
PHP

Pazzle on array_column

  在PHP中内置的对数组操作的方法(函数)有一个叫做’array_column’的非常实用,它可以用作返回一个二维数组的指定列。

  先介绍一下它!

  好比这样一个用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$students = array(
array(
'id'=>1,
'name'=>'Jerry'
),
array(
'id'=>3,
'name'=>'Tom'
),
array(
'id'=>6,
'name'=>'Lily'
),
array(
'id'=>11,
'name'=>'Bob'
),
);
$student_names = array_column($students,'name');
var_dump($student_names);

输出结果:

1
2
3
4
5
array (size=4)
0 => string 'Jerry' (length=5)
1 => string 'Tom' (length=3)
2 => string 'Lily' (length=4)
3 => string 'Bob' (length=3)

  这样就得到了一个所有名字的数组,想必对于一个PHPER来说并不陌生。但是今天要讲的重点是array_column的另外一个用法,如果给它第三个参数,那么它将以第三个参数对应的数组中的值作为返回数组的键,用例如下:

阅读剩下更多

默认配图
学习笔记

完全源码编译LNMP环境

环境:

软件:

依赖软件:

阅读剩下更多

默认配图
PHP

PHP时区默认为Europe/zurich且修改php.ini无效的困惑

为何我的date(“Y/m/d H:i:s”)相差了7个小时?

环境:MacOS X EI Capitan / PHP5.6.25

  今天在校验Crontab的计划执行时,利用PHP输出执行时间却发现输出的时间和当地事件相差了7个小时,对!是7不是8,并不是默认的UTC,呵呵了~

  好吧,一贯思路,我去把php.ini中的date.timezone修改成PRC。嗯,肯定正常了吧。

  要能正常我也不会写本文了^_~!

  不论是修改成PRC还是Asia/Shanghai又或者UTC,全都未然。   


  1. apache输出phpinfo(),查看date区域它的Default Timezone还是没变。
    Phpinfo的date区域
  2. 在控制台查看php -i | grep zone也还是同样没变
    控制台输出timezone
  3. 核对加载的php.ini文件是否正确并且配置信息格式是否正确

  4. 控制台使用php -a交互模式,调用ini_get('date.timezone'),检查配置的INI是否生效

  全都没有任何问题啊!!!为什么结果却出乎意料呢??百度,谷歌我都问遍了,大家没人出现我这样的情况啊。正当我发愁的时候,小伙伴过来给我支招了。

阅读剩下更多

默认配图
学习笔记

我是如何将mac OS X(10.11.6)的PHP版本升级到5.6.29的


由于项目和学习的需要,本机曾经升级到了5.5.38已经不够折腾了,所以时候升级一波5.6了(没必要到7的时候,我是不会去升级的)。
首先直接去搜索引擎找便捷的方法呗,直接就选中了一款来自“简书”的文章,因为它就简单的一条命令:

1
curl -s http://php-osx.liip.ch/install.sh | bash -s 5.6

看到这个地址,我心里就放心了!因为上一个版本也是liip的。
我首先看了一下这个install.sh的内容,确定没有什么危险,里面其实就是检测一下设备的系统版本和cpu类型是否可以升级。
环境检测没问题就去下载了一个叫做“packager.tgz”的压缩包,然后解压,运行里面的pythony脚本。

1
2
#TYPE = 5.6-10.10
sudo packager.py install $TYPE-frontenddev

阅读剩下更多

默认配图
算法

实现寻找两个字符串的最大公子串的方法

昨天在做某兔的校招笔试题的时候遇到的题目,就这一个编程题,然而当时却没有拿下,把它和字符串匹配中的子串包含给弄混了,哎!
废话少说,上代码!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
function MaxSubCommonStr($str1,$str2){
$a = str_split($str1); //字符串分割成数组
$b = str_split($str2);
$len1 = strlen($str1); //字符串的长度
$len2 = strlen($str2);
$maxlen = 0; //最大计数器
for($i=0;$i<$len1;$i++){
for($j=0;$j<$len2;$j++){
if($a[$i] == $b[$j]){ //找到第一个相等的字符
$as = $i; //拷贝字符串1的起点
$bs = $j; //拷贝字符串2的起点
$count = 1; //有一个相等的字符了
while ($as + 1 < $len1 && $bs + 1 < $len2 && $a[++$as] == $b[++$bs])
$count++; //往后比较,每匹配一个计数+1,直到其中一个查完或者出现不相等
if($count > $maxlen){ //当本次计数长度大于最大记录时
$maxlen = $count; //更新最大计数长度
$start1 = $i; //更新本次比较的字符串1起点
$start2 = $j; //更新本次比较的字符串2起点
}
}
}
}
return substr($str1,$start1,$maxlen); //直接返回字符串1,从$start1起点往后$maxlen最大匹配长度个数的子串
}

$str1 = 'abcdefgabc';
$str2 = 'defghijabc';
echo MaxSubCommonStr($str1,$str2);
?>

没什么含金量,只是写出来练练手,思路照搬过来的。

阅读剩下更多

默认配图
算法

PHP常用算法的方法实现(冒泡、选择、插入、快排、二分查找)

以前说起写算法,基本上都是拿C语言来写,因为用C可以更清楚的理解各种排序算法和数据结构。今天遍换成使用PHP语言来写几个常用的算法。
这次要写的算法包括:

  • 冒泡排序
  • 插入排序
  • 选择排序(直接)
  • 快速排序
  • 二分查找

冒泡:

<?php
$arr = array(4,3,5,6,8,0,10,15,11);
echo implode(' ',$arr);
//冒泡排序 最坏 平均O(N^2) 最好O(N)
function BubbleSort($arr){
	$length = count($arr);
	if($length <= 1){
		return $arr;
	}
	for($i=0;$i<$length;$i++){
		for($j=0;$j<$length-$i-1;$j++){
			if($arr[$j] > $arr[$j+1]){
				$tmp = $arr[$j];
				$arr[$j] = $arr[$j+1];
				$arr[$j+1] = $tmp;
			}
		}
	}
	return $arr;
}
echo "\nBubbleSort:\n";
echo implode(' ',BubbleSort($arr))."\n";
?>

阅读剩下更多

默认配图
返回顶部