​​​​ php 记录 | 苏生不惑的博客

php 记录

php sqlite

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# -- 查询当前数据库信息
sqlite> .database

# -- 查询当前数据库下的所有表的信息
# 可选参数 ? table name , 如果加上这个参数, 会按照输入的表面去显示, 支持 LIKE 方式查找
sqlite> .tables
sqlite> .tables %persion%

# -- 创建一个表
sqlite> CREATE TABLE persions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name text,
age int);

# -- 删除一个表
sqlite> drop table persions;

# -- 修改表名
sqlite> ALTER TABLE persions RENAME TO persions2;
# 查看修改结果
sqlite> .tables
# 再改回去,不要影响后面的命令
sqlite> ALTER TABLE persions2 RENAME TO persions;

# -- 查看表结构
sqlite> pragma table_info ('persions');

# -- 修改字段, SQLite 不能像主流数据库那样直接修改字段, 只能添加一个新的字段
# 如果必须要修改,可以先将表重命名一个其他的,再将数据插入即可
# 添加新字段
sqlite> ALTER TABLE persions ADD COLUMN address text;

# -- 添加数据, 规则和 MYSQL 很类似
# 方式1
sqlite> INSERT INTO persions(name,age,address) VALUES ('张三',25,'北京');
# 方式2 不推荐,如果使用这种方式, 就要字段和表的字段数量完全相同
sqlite> INSERT INTO persions VALUES (2,'李四',30,'天津');
# 方式3 一般不会用,向表中插入默认值,如果没有指定默认值,就写入 NULL
sqlite> INSERT INTO persions DEFAULT VALUES;

# -- 更新数据
sqlite> UPDATE persions SET name='李四',age=22,address='上海' WHERE id=3;

# -- 查询数据
sqlite> SELECT id,name,age,address FROM persions;

# -- 删除数据
sqlite> DELETE FROM persions WHERE id=3;
// 创建一个专门处理 SQLite 的 PDO类
class Sqlite
{
protected $pdo;

public function __construct($pdo = null)
{
// 创建 PDO_SQLITE DSN
if ($pdo) {
$this->pdo = $pdo;
} else {
try {
$this->pdo = new PDO('sqlite:/home/bro/www/temp/test.sq3');
} catch (PDOException $e) {
echo '数据库连接失败: ' . $e->getMessage();
}
}

// 测试的时候将错误提示打开,否则出错了不清楚怎么回事
$this->pdo->setAttribute($this->pdo::ATTR_ERRMODE, $this->pdo::ERRMODE_EXCEPTION);
}

/**
* 查询所有数据
*/
public function fetchAll($sql)
{

return $this->pdo->query($sql)->fetchAll();
}

/**
* 插入一条数据
*/
public function insertOneData($sql)
{
return $this->pdo->prepare($sql)->execute();

}
}

/**
* 定义一个打印结果的函数
*/
function p($val, $title = null)
{
if($title) {
echo "<br><br><h3>{$title}</h3><hr>";
}
echo '<pre>';
var_dump($val);
echo '</pre><hr>';
}

$sqlite = new Sqlite; //new 一个Sqlite对象

// 查询所有数据https://broqiang.com/posts/linux-php-sqlite
$sqlQueryAll = "select * from persions";

$allData = $sqlite->fetchAll($sqlQueryAll);

p($allData,'所有查询结果');

// 插入一条数据
$sqlInsertOneData = sprintf("INSERT INTO persions(name,age,address) VALUES ('%s',%d,'%s');",'张三',25,'北京');

p($sqlite->insertOneData($sqlInsertOneData) ? '插入成功' : '插入失败','插入一条数据');

S.O.I.L.D 之单一职责

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
<?php

namespace Acme\Reporting;

use Auth;
use DB;
use Exception;

class SalesReporter
{
/**
* 获取某个时间段的销售总额
*
* @param $startDate
* @param $endDate
*
* @return string
*/
public function between($startDate, $endDate) : string
{
if (! Auth::check()) {
throw new Exception('Authentication required for reporting');
}

$sales = $this->queryDBForSalesBetween($startDate, $endDate);
return $this->format($sales);
}

/**
* 查询销售报表数据
*
* @param $startDate
* @param $endDate
*
* @return float
*/
protected function queryDBForSalesBetween($startDate, $endDate) : float
{
return DB::table('sales')->whereBetween('created_at', [$startDate, $endDate])->sum('charge') / 100;
}

/**
* 数据展示
*
* @param $sales
* @return string
*/
protected function format($sales) : string
{
return "<h1>Sales: $sales</h1>";
}
}
测试

$report = new Acme\Reporting\SalesReporter();
$report->between(
now()->subDays(10),
now()
);
该例子明显违反了单一职责:

授权方式发生变化时,如 API 授权,需要改动该类
当数据库层发生变化时候,如使用 Redis 时,需要改动该类
当展示方式发生变化时,需要改动该类
正面示例

对于上述的例子,应当作出如下的改动:

不需要关心用户授权,用户授权与本类的职责无关
数据层应当剥离出来
展示层应当剥离出来
<?php

// 展示层接口
interface SalesOutputInterface {
public function output();
}

// 展示层实现
class HtmlOutput implements SalesOutputInterface {
public function output($sales)
{
echo "<h1>{$sales}</h1>";
}
}

// 数据层
class SalesRepository {
public function between()
{
return DB::table('sales')->whereBetween('create_at', [$startDate, $endDate])->sum('charge') / 100;
}
}

// 职责类
class SalsReporter {

public $repo;

public function __construct($repo)
{
$this->repo = $repo;
}

public function between($startDate, $endDate, SalesOutputInterface $formater)
{
$sales = $this->repo->between($startDate, $endDate);
$formater->output($sales);
}
}
测试

$report = new SalsReporter(new SalesRepository);
$report->between(
now->subDays(10),
now(),
new HtmlOutput
);
结合 Laravel 的依赖注入,可以进一步简化https://learnku.com/articles/27923#topnav

class SalsReporter {

public $repo;

public function __construct(SalesRepository $repo)
{
$this->repo = $repo;
}

public function between($startDate, $endDate, SalesOutputInterface $formater)
{
$sales = $this->repo->between($startDate, $endDate);
$formater->output($sales);
}
}
https://learnku.com/articles/27953

与微博内容分析相关的正则表达式

1
2
3
4
5
6
微博表情\[[\u4e00-\u9fa5A-Za-z]{1,8}\]
微博昵称@[\u4e00-\u9fa5A-Z0-9a-z_-]{2,30}
微博话题#[^@<>#"&'\r\n\t]{1,49}#
微博短链接#https{0,1}://t.cn/[A-Z0-9a-z]{6,8}[/]{0,1}#
网址长链接(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]
https://www.playpi.org/2018121101.html

Warning: count(): Parameter must be an array or an object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PHP7.2中这样写https://segmentfault.com/a/1190000017268206

<?php
echo count(null);
会报以下错误:

Warning: count(): Parameter must be an array or an object that implements Countable in
但在PHP7.1以下
会返回0
所以这次就坑了自己最终又从PHP7.2降回7.1 php artisan -V 查看Laravel版本

laravel 5.45.6的日志是有区别的
所以需要在config下添加一个logging.php的配置文件,代码请复制里面
https://github.com/laravel/laravel/blob/develop/config/logging.php
http://wangyapeng.me/2019/01/13/upgrad-laravel-5.5-to-5.7-log/
然后在.env下添加LOG_CHANNEL=stack这样日志就不会报错了
在 .env 删除 APP_DEBUG_LEVEL,新增 LOG_CHANNEL=stack
在 config/app.php 内删除 log 和 log_level 两个字段

无限级分类

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
php artisan make:migration create_category_table --create=category
//https://segmentfault.com/a/1190000010359094
Schema::create('categorys', function (Blueprint $table) {
$table->increments('id');
$table->integer('parent_id');
$table->string('code');
$table->string('name');
$table->string('path');
$table->timestamps();
});
php artisan migrate

php artisan make: model Category -m
use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
public function childCategory() {
return $this->hasMany('App\Category', 'parent_id', 'id');
}

public function allChildrenCategorys()
{
return $this->childCategory()->with('allChildrenCategorys');
}
}
$categorys = App/Category::with('allChildrenCategorys')->first();
$categorys->allChildrenCategorys;
$categorys->allChildrenCategorys->first()->allChildrenCategorys;
$arr = [];
array_walk_recursive($categories,function ($v, $k) use(&$arr) {
if($k == 'id')
$arr[] = $v;
});

本周一的时间戳

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
strtotime('monday');
因为这样会导致在非周一的时候计算到下一个周一,
https://www.h57.pw/2018/11/27/calculate-the-timestamp-of-this-monday/
所以我们要在计算之前先判定一下当前是周几,如果是周一就采用 'monday' 非周一的时候采用'previous monday'
if(date('w') == 1) {
$time = strtotime('monday');
} else {
$time = strtotime('previous monday');
}

老外认为“this monday”是下周一,“last monday”是本周一
直接"-2 monday"就好了,或者"monday last week"
date('w')得到当前周几,由于周一至周六分别是1-6,周日是0。当值为0的时候,上周一是13天前。其余就是date('w')+6天前。

$days = date('w')==0?13:date('w')+6;

echo date('Y-m-d',time()-$days*86400);
>>> date('Ymd His',strtotime('-1 monday'))
=> "20190429 000000"
>>> date('Ymd His',strtotime('-1 monday 2019-05-06'))
=> "20190429 000000"
>>> date('Ymd His',strtotime(' monday 2019-05-06'))
=> "20190506 000000"
echo '<br>上周:<br>';
echo date("Y-m-d H:i:s",mktime(0, 0 , 0,date("m"),date("d")-date("w")+1-7,date("Y"))),"\n";
echo date("Y-m-d H:i:s",mktime(23,59,59,date("m"),date("d")-date("w")+7-7,date("Y"))),"\n";
echo '<br>本周:<br>';
echo date("Y-m-d H:i:s",mktime(0, 0 , 0,date("m"),date("d")-date("w")+1,date("Y"))),"\n";
echo date("Y-m-d H:i:s",mktime(23,59,59,date("m"),date("d")-date("w")+7,date("Y"))),"\n";

echo '<br>上月:<br>';
echo date("Y-m-d H:i:s",mktime(0, 0 , 0,date("m")-1,1,date("Y"))),"\n";
echo date("Y-m-d H:i:s",mktime(23,59,59,date("m") ,0,date("Y"))),"\n";
echo '<br>本月:<br>';
echo date("Y-m-d H:i:s",mktime(0, 0 , 0,date("m"),1,date("Y"))),"\n";
echo date("Y-m-d H:i:s",mktime(23,59,59,date("m"),date("t"),date("Y"))),"\n";

$getMonthDays = date("t",mktime(0, 0 , 0,date('n')+(date('n')-1)%3,1,date("Y")));//本季度未最后一月天数
echo '<br>本季度:<br>';
echo date('Y-m-d H:i:s', mktime(0, 0, 0,date('n')-(date('n')-1)%3,1,date('Y'))),"\n";
echo date('Y-m-d H:i:s', mktime(23,59,59,date('n')+(date('n')-1)%3,$getMonthDays,date('Y'))),"\n";

/**
*
* 获取指定年月的开始和结束时间戳
*
* @param int $year 年份
* @param int $month 月份
* @return array(开始时间,结束时间)
*/
function getMonthBeginAndEnd($year = 0, $month = 0) {
$year = $year ? $year : date('Y');
$month = $month ? $month : date('m');
$d = date('t', strtotime($year . '-' . $month));

return ['begin' => strtotime($year . '-' . $month), 'end' => mktime(23, 59, 59, $month, $d, $year)];
}


/**
* 获取指定时间戳所在的月份的开始时间戳和结束时间戳
*http://www.phpernote.com/php-function/1439.html
* @param int $timestamp
* @return array(开始时间,结束时间)
*/
function getMonthBeginAndEnd($timestamp = 0) {
$timestamp = $timestamp ? $timestamp : time();

$year = date('Y', $timestamp);
$month = date('m', $timestamp);
$d = date('t', strtotime($year . '-' . $month));

return ['begin' => strtotime($year . '-' . $month), 'end' => mktime(23, 59, 59, $month, $d, $year)];
}

广度优先搜索

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 广度搜索
*
* 你的朋友关系,以及朋友的朋友的关系,查看你的朋友或者朋友的朋友是不是包含 m 结尾的名字
*/


// 需要检索的数组
$graph = [];
$graph['you'] = ['alice', 'bob', 'claire'];
$graph['bob'] = ['anuj', 'peggy'];
$graph['alice'] = ['peggy'];
$graph['claire'] = ['thom', 'jonny'];
$graph['anuj'] = [];
$graph['peggy'] = [];
$graph['thom'] = [];
$graph['jonny'] = [];

// 搜索过的数组
$searchedItem = [];
// 待检索的数组
$waitSearchArray = [];
// 将第一层的关系加入到等待搜索的数组
$waitSearchArray = array_merge($waitSearchArray, $graph['you']);

// 将结果元素赋值为 false
$resultName = false;

//需要查找的字符
$findChar = 'z';

// 如果等待搜索的数组不为空就循环查找
while ($waitSearchArray) {
// 从队列头部弹出一个元素
$name = array_shift($waitSearchArray);
// 如果待检查的元素在已经搜索过的数组中,就跳过,这个是用来防止循环检查的
if (in_array($name, $searchedItem)) {
continue;
}
// 获取最后一个字符
$lastChar = substr($name, strlen($name) - 1, 1);
// 如果最后一个字符是 m,就说明找到了,把结果赋值,然后跳出循环
if ($lastChar == $findChar) {
$resultName = $name;
break;
}

// 到这里了说明没有找到,那么把这个人的名字,放入到已经搜索过的数组中
$searchedItem[] = $name;
// 然后再把这个名字的朋友关系加入到待搜索的数组中
$waitSearchArray = array_merge($waitSearchArray, $graph[$name]);
}

https://www.h57.pw/2017/11/14/breadthfirst-search-php-implementation/
var_dump($resultName);

归并排序

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
42
43
44
45
46
47
48
49
50
51
52
53
function sortArr($arr) {
if (count($arr) < 2) {
return $arr;
}
$mid = count($arr) / 2;
$arr1 = array_slice($arr, 0, $mid);
$arr2 = array_slice($arr, $mid, count($arr));
$arr1 = sortArr($arr1);
$arr2 = sortArr($arr2);

return mergeArr($arr1, $arr2);
}

function mergeArr($arr1, $arr2) {

if (!is_array($arr1)) {
$arr1[] = $arr1;
}
if (!is_array($arr2)) {
$arr2[] = $arr2;
}

$i =0;
$j = 0;
$arr1Length = count($arr1);
$arr2Length = count($arr2);
$returnArr = [];
while($i < $arr1Length && $j < $arr2Length) {
if($arr1[$i] > $arr2[$j]) {
$returnArr[] = $arr2[$j];
$j++;
} else {
$returnArr[] = $arr1[$i];
$i++;
}
}
for($tmp = $i; $tmp < $arr1Length; $tmp++) {
$returnArr[] = $arr1[$tmp];
}
for($tmp = $j; $tmp < $arr2Length; $tmp++) {
$returnArr[] = $arr2[$tmp];
}
return $returnArr;
}

$arr = [1,3,2,5,7,9,3,1];


$sortableArr = sortArr($arr);
//https://www.h57.pw/2016/09/25/php-merge-sort/
foreach ($sortableArr as $a) {
print_r($a . "\n");
}

正则表达式实现 @某人

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (preg_match_all (‘#@\w+#u’, ‘@张全蛋 含泪质检 @三星 Note7 被炸飞,听说 @炸机 跟 @啤酒 更配哦!’, $matches)) {
var_export($matches);
}
// 输出
array (
0 =>
array (
0 => ‘@张全蛋’,
1 => ‘@三星 Note7’,
2 => ‘@炸机’,
3 => ‘@啤酒’,
),
)
正则表达式 #@\w+#u 中:
#是分隔符.
u 是修饰符,表示 Unicode.
\w 是元字符,在 ASCII 下等价于 [A-Za-z0-9_], 在 Unicode 下表示字符 (包括汉字) 和数字和下划线.
+ 是量词,表示 1 个或多个,等价于 {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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
* 归并排序
* @param $arr
* @return array
*/
function mergeSort($arr)
{
$arrCount = count($arr);
if ($arrCount < 2) {
return $arr;
}
$mid = ceil($arrCount / 2); // 向上取整一下
$leftArr = array_slice($arr, 0, $mid);
$rightArr = array_slice($arr, $mid, $arrCount - $mid);
return mergeArr(mergeSort($leftArr), mergeSort($rightArr));
}

/**
* 最终合并数组的方法
* @param $leftArr
* @param $rightArr
* @return array
*/
function mergeArr($leftArr, $rightArr)
{
$i = 0;
$j = 0;
$returnArr = [];
while ($i < count($leftArr) && $j < count($rightArr)) {
if ($leftArr[$i] < $rightArr[$j]) {
$returnArr[] = $leftArr[$i];
$i++;
} else if ($leftArr[$i] > $rightArr[$j]) {
$returnArr[] = $rightArr[$j];
$j++;
} else {
$returnArr[] = $leftArr[$i];
$returnArr[] = $rightArr[$j];
$i++;
$j++;
}
}
for ($temp = $i; $temp < count($leftArr); $temp++) {
$returnArr[] = $leftArr[$temp];
}
for ($temp = $j; $temp < count($rightArr); $temp++) {
$returnArr[] = $rightArr[$temp];
}
return $returnArr;
}

/**
* 快速排序https://www.h57.pw/2017/04/10/thinking-about-divide-and-conquer-by-sorting-fast-merging/
* @param $arr
* @return array
*/
function quickSort($arr)
{
if (count($arr) < 2) {
return $arr;
}
$flag = $arr[0];
$lessArr = [];
$largeArr = [];
$flagArr = [];
$flagArr[] = $flag;

for ($i = 1; $i < count($arr); $i++) {
if ($arr[$i] < $flag) {
$lessArr[] = $arr[$i];
} else if ($arr[$i] > $flag) {
$largeArr[] = $arr[$i];
} else {
$flagArr[] = $arr[$i];
}
}

return array_merge(quickSort($lessArr), $flagArr, quickSort($largeArr));
}

Jwt前后端分离

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
42
43
44
45
46
47
48
vi composer.json 
"require": {
"tymon/jwt-auth": "1.0.*"
},
php artisan jwt:secret
https://www.njphper.com/posts/cd8c881c.html
class User extends Model implements AuthenticatableContract, AuthorizableContract, JWTSubject
{
use Authenticatable, Authorizable;

/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email',
];

/**
* The attributes excluded from the model's JSON form.
*
* @var array
*/
protected $hidden = [
'password',
];

/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}

/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
}

递归

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
& 引用赋值
function doloop1(&$i = 1)
{
print_r($i);
$i++;
if ($i <= 10) {
doloop1($i);
}
}
doloop1();
static 静态变量https://learnku.com/articles/28252#topnav
function doloop2()
{
static $i = 1;
print_r($i);
$i++;
if ($i <= 10) {
doloop2();
}
}
doloop2();
global 全局变量
$i = 1;
function doloop3()
{
global $i;
echo $i;
$i++;
if ($i <= 10) {
doloop3();
}
}
doloop3();
$data = [
['id' => 1, 'title' => 'Electronics', 'parent_id' => 0],
['id' => 2, 'title' => 'Laptops & PC', 'parent_id' => 1],
['id' => 3, 'title' => 'Laptops', 'parent_id' => 2],
['id' => 4, 'title' => 'PC', 'parent_id' => 2],
['id' => 5, 'title' => 'Cameras & photo', 'parent_id' => 1],
['id' => 6, 'title' => 'Camera', 'parent_id' => 5],
['id' => 7, 'title' => 'Phones & Accessories', 'parent_id' => 1],
['id' => 8, 'title' => 'Smartphones', 'parent_id' => 7],
['id' => 9, 'title' => 'Android', 'parent_id' => 8],
['id' => 10, 'title' => 'iOS', 'parent_id' => 8],
['id' => 11, 'title' => 'Other Smartphones', 'parent_id' => 8],
['id' => 12, 'title' => 'Batteries', 'parent_id' => 7],
['id' => 13, 'title' => 'Headsets', 'parent_id' => 7],
['id' => 14, 'title' => 'Screen Protectors', 'parent_id' => 7],
];
获取无限极分类

/**
* 值引用获取无限极分类树
*
* @param array $data
* [[[@return](https://learnku.com/users/31554)](https://learnku.com/users/31554)](https://learnku.com/users/31554) array
*/
function make_tree($data)
{
$refer = array();
$tree = array();
foreach($data as $k => $v){
$refer[$v['id']] = & $data[$k]; //创建主键的数组引用
}

foreach($data as $k => $v){
$parent_id = $v['parent_id']; //获取当前分类的父级id
if($parent_id == 0){
$tree[] = & $data[$k]; //顶级栏目
}else{
if(isset($refer[$parent_id])){
$refer[$parent_id]['children'][] = & $data[$k]; //如果存在父级栏目,则添加进父级栏目的子栏目数组中
}
}
}

return $tree;
}

/**
* 递归获取无限极分类树
*
* @param array $data
* @param int $parent_id
* @param int $level
* [[[@return](https://learnku.com/users/31554)](https://learnku.com/users/31554)](https://learnku.com/users/31554) array
*/
function make_tree2($data = [], $parent_id = 0, $level = 0)
{
$tree = [];
if ($data && is_array($data)) {
foreach ($data as $v) {
if ($v['parent_id'] == $parent_id) {
$tree[] = [
'id' => $v['id'],
'level' => $level,
'title' => $v['title'],
'parent_id' => $v['parent_id'],
'children' => make_tree2($data, $v['id'], $level + 1),
];
}
}
}
return $tree;
}

Laravel中chunk方法分块处理数据的坑

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
42
43
44
45
46
47
48
49
50
public function chunk($count, callable $callback)
{
// 我理解的是类似于limit,offset 实现数据分页查询
$results = $this->forPage($page = 1, $count)->get();

while (count($results) > 0) {
// On each chunk result set, we will pass them to the callback and then let the
// developer take care of everything within the callback, which allows us to
// keep the memory low for spinning through large result sets for working.
// 如果用户回调中,更新的字段与查询的字段是一个条件,就会出现这样的问题
if (call_user_func($callback, $results) === false) {
return false;
}

$page++;

$results = $this->forPage($page, $count)->get();
}

return true;
}

public function handle()
{
// 1.先去查询需要更新的数据量
$count = DB::table('table')
->where('status', '=', 0)
->count();
echo "需要推送的数量:$count\r\n";
while ($count) {
// 2.然后limit去查数据
$data= DB::table(self::OPEN_API_WUBA_TEST_CLUE)
->where('is_push', '=', self::IS_PUSH_FAILED)
->limit(100)
->get();
! empty($data) && $this->processData($data);
// 3.批次处理完数据,再去查询下 需要更新的数据,直到更新完毕退出循环
$count = DB::table('table')
->where('status', '=', 0)
->count();
echo "剩余推送的记录数:$count\r\n";
}
exit("数据全部推送完毕.\r\n");

}

private function processData($data)
{
// TODO(xiaoyi):处理数据业务逻辑https://segmentfault.com/a/1190000015284897
}

代码细节

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
42
43
44
45
46
47
48
49
一个数组对象成员,你知道怎么写吗?
/**
* @var Ads[]
*/
public $adsList = [];
类的魔术方法调用的注释,你知道怎么写吗?https://yq.aliyun.com/articles/691719?spm=a2c4e.11153959.0.0.1a265ee3ENEDVQ
/**
* @link http://manual.phpdoc.org/HTMLframesConverter/default/
*
* @method static int search(string $query, $limit = 10, $offset = 0)
*/
class SearchServiceProxy
{
public static function __callStatic($method, $arguments)
{
if (!method_exists("SearchService", $method)) {
throw new \LogicException(__CLASS__ . "::" . $method . " not found");
}

try {
$data = call_user_func_array(["SearchService", $method], $arguments);
} catch (\Exception $e) {
error_log($e->getMessage());
return false;
}

return $data;
}
}

class SearchService
{

/**
* @param string $query
* @param int $limit
* @param int $offset
*
* @return array
* @deprecated 方法废弃
*/
public static function search(string $query, $limit = 10, $offset = 0)
{
return [
["id" => 1, "aaa"],
["id" => 2, "bbb"],
];
}
}

PHP 7.1 中使用 openssl 取代 mcrypt

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
/**
* 对明文进行加密
* @param string $text 需要加密的明文
* @return string 加密后的密文
*/
public function encrypt($text, $appid)
{
try {
/**
*原来代码
*/
$iv = substr($this->key, 0, 16);
$encrypted = openssl_encrypt($text,'AES-256-CBC',$this->key,OPENSSL_ZERO_PADDING,$iv);
return array(ErrorCode::$OK, $encrypted);
} catch (Exception $e) {
//print $e;
return array(ErrorCode::$EncryptAESError, null);
}
}
/**
* 对密文进行解密https://blog.wpjam.com/m/php-7-1-openssl/
* @param string $encrypted 需要解密的密文
* @return string 解密得到的明文
*/
public function decrypt($encrypted, $appid)
{
try {
$iv = substr($this->key, 0, 16);
$decrypted = openssl_decrypt($encrypted,'AES-256-CBC',$this->key,OPENSSL_ZERO_PADDING,$iv);
} catch (Exception $e) {
return array(ErrorCode::$DecryptAESError, null);
}
/**
*原来代码
*/
}

static 效率优化

1
2
3
4
5
6
7
function get_some_var(){
static $var;
if(!isset($var)){
$var = complex_calculation();
}
return $var;
}

解码 JSONP

1
2
3
4
5
6
function jsonp_decode($jsonp, $assoc = false) {
if($jsonp[0] !== '[' && $jsonp[0] !== '{') {
$jsonp = substr($jsonp, strpos($jsonp, '('));
}
return json_decode(trim($jsonp,'();'), $assoc);
}

合并多维数组中的子数组

1
2
$merged = call_user_func_array('array_merge', $result);
$merged = array_merge(...$result);

simplexml_load_string 的 parser error 问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
首先使用函数 libxml_use_internal_errors() 关闭 XML 错误,然后使用 libxml_get_errors() 获取相关的错误进行自定义处理。

libxml_use_internal_errors(true);
$sxe = simplexml_load_string("<?xml version='1.0'><broken><xml></broken>");
if (false === $sxe) {
echo "加载 XML 错误\n";
foreach(libxml_get_errors() as $error) {
echo "\t", $error->message;
}
}加载 XML 错误

Blank needed here
parsing XML declaration: '?>' expected
Opening and ending tag mismatch: xml line 1 and broken
Premature end of data in tag broken line 1

如何移除控制字符

1
2
3
function wpjam_strip_control_characters($str){
return preg_replace('/[\x00-\x1F\x7F-\x9F]/u', '', $str);
}

获取 Linux 服务器的 uptime

1
2
3
4
5
6
7
8
9
10
11
$uptime = trim(shell_exec('uptime'));
// output is 04:47:32 up 187 days, 5:03, 1 user, load average: 0.55, 0.55, 0.54

$uptime = explode(',', $uptime);
$uptime = explode(' ', $uptime[0]);

$uptime = $uptime[2].' '.$uptime[3]; // 187 days
$uptime = trim(file_get_contents('/proc/uptime'));
$uptime = explode(' ', $uptime);

echo $uptime[0]; //uptime in seconds

用Memcahced 的时候,请不要把过期时间设置成超过30天

1
2
3
4
过期时间是一个 Unix 时间戳,也可以是一个从现在算起的以秒为单位的数字。
那么怎么判断是 Unix 时间戳还是一个从现在算起的以秒为单位的数字呢?小于 60×60×24×3030天时间的秒数),就算是从现在算起的以秒为单位的数字。如果大于服务端会将其作为一个真实的Unix时间戳来处理而不是自当前时间的偏移。
如果过期时间被设置为0(默认),此元素永不过期(但是它可能由于服务端为了给其他新的元素分配空间而被删除)。
所以如果真的要设置一个 key 的过期时间为一年后,其值应该设置为: time()+60×60×24×365

json_decode 无法解析

1
2
3
4
5
include 'JSON.php';//https://github.com/pear/Services_JSON
$json = new Services_JSON();
$data = $json->decode($str);
$json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
$data = $json->decode($str);

cURL 报错 error 60 SSL certificate problem

1
2
3
4
5
6
将 cacert.pem 文件保存在可到达的目标中。
然后,在 php.ini 文件中,向下滚动到找到 [curl] 的位置。
您应该看到注释掉了 CURLOPT_CAINFO 选项。 取消注释并将其指向 cacert.pem 文件。 你应该有这样的一行:


curl.cainfo =“证书路径\cacert.pem”

按周取时间段

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
$start = '1478863624';
$end = '1480505248';
function getweek($start, $end)
{
$ret = array();
$i = 0;
while($start <= $end){
$ret[$i]['start'] = date('Y-m-d',$start);
$tmp = strtotime("+6 days",$start);
if($end <= $tmp)
$ret[$i]['end'] = date('Y-m-d',$end);
else
$ret[$i]['end'] = date('Y-m-d',$tmp);
$i++;
$start = strtotime("+1 day",$tmp);
}
return $ret;
}


Array
(
[0] => Array
(
[start] => 2016-11-11
[end] => 2016-11-17
)

[1] => Array
(
[start] => 2016-11-18
[end] => 2016-11-24
)

[2] => Array
(
[start] => 2016-11-25
[end] => 2016-11-30
)

PHP 代码安全

1
https://learnku.com/articles/28505

匿名函数

1
2
3
4
5
6
7
8
9
$var = "https://6619.io";

(function() use ($var) {
// echo $var;
})();

(function($param) {
// echo $param;
})($var);

比较字符串

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
42
43
44
45
46
47
48
49
50
51
52
53
54
列出目录https://www.restran.net/2016/09/26/php-security-notes/

scandir('/site')
show_source('flag.php');
highlight_file('flag.php');
var_dump(file('flag.php'));
print_r(file('flag.php'));
如果 GET 参数中设置 name[]=a,那么 $_GET['name'] = [a],php 会把 []=a 当成数组传入, $_GET 会自动对参数调用 urldecode。

$_POST 同样存在此漏洞,提交的表单数据,user[]=admin,$_POST['user'] 得到的是 ['admin'] 是一个数组。
sha1([]) === false
md5([]) === false

$password = "ffifdyop";
$sql = "SELECT * FROM admin WHERE pass = '".md5($password,true)."'"
b"SELECT * FROM admin WHERE pass = ''or'6É]™é!r,ùíb\x1C'"

'0.999999999999999999999' == 1
true in PHP 4.3.0 - 5.6.x
# false in 7.0.0+
'0e0' == '0x0'
'0xABC' == '0xabc'
'0xABCdef' == '0xabcDEF'
'000000e1' == '0x000000'
'0xABFe1' == '0xABFE1'
'0xe' == '0Xe'
'0xABCDEF' == '11259375'
'0xABCDEF123' == '46118400291'
'0x1234AB' == '1193131'
'0x1234Ab' == '1193131'

# true in PHP 4.3.0 - 4.3.9, 5.2.1 - 5.6.x
# false in PHP 4.3.10 - 4.4.9, 5.0.3 - 5.2.0, 7.0.0+
'0xABCdef' == ' 0xabcDEF'
'1e1' == '0xa'
'0xe' == ' 0Xe'
'0x123' == ' 0x123'

# true in PHP 4.3.10 - 4.4.9, 5.0.3 - 5.2.0
# false in PHP 4.3.0 - 4.3.9, 5.0.0 - 5.0.2, 5.2.1 - 5.6.26, 7.0.0+
'0e0' == '0x0a'

# true in PHP 4.3.0 - 4.3.9, 5.0.0 - 5.0.2
# false in PHP 4.3.10 - 4.4.9, 5.0.3 - 5.6.26, 7.0.0+
'0xe' == ' 0Xe.'
var_dump(is_numeric("\01")); // false

typeid=1’ union select.. 也能通过 in_array 的验证


if (in_array($_GET('typeid'], array(1, 2, 3, 4))) {
$sql="select …. where typeid=".$_GET['typeid']";
echo $sql;
}

md5

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
两个 md5 一样的字符串


from binascii import unhexlify
from hashlib import md5
from future.moves.urllib.parse import quote

input1 = 'Oded Goldreich\nOded Goldreich\nOded Goldreich\nOded Go' + unhexlify(
'd8050d0019bb9318924caa96dce35cb835b349e144e98c50c22cf461244a4064bf1afaecc5820d428ad38d6bec89a5ad51e29063dd79b16cf67c12978647f5af123de3acf844085cd025b956')

print(quote(input1))
print md5(input1).hexdigest()

input2 = 'Neal Koblitz\nNeal Koblitz\nNeal Koblitz\nNeal Koblitz\n' + unhexlify('75b80e0035f3d2c909af1baddce35cb835b349e144e88c50c22cf461244a40e4bf1afaecc5820d428ad38d6bec89a5ad51e29063dd79b16cf6fc11978647f5af123de3acf84408dcd025b956')
print md5(input2).hexdigest()
print(quote(input2))
from array import array
from hashlib import md5
input1 = array('I', [0x6165300e,0x87a79a55,0xf7c60bd0,0x34febd0b,0x6503cf04,0x854f709e,0xfb0fc034,0x874c9c65,0x2f94cc40,0x15a12deb,0x5c15f4a3,0x490786bb,0x6d658673,0xa4341f7d,0x8fd75920,0xefd18d5a])
input2 = array('I', [x^y for x,y in zip(input1, [0, 0, 0, 0, 0, 1<<10, 0, 0, 0, 0, 1<<31, 0, 0, 0, 0, 0])])
print(input1 == input2) # False
print(md5(input1).hexdigest()) # cee9a457e790cf20d4bdaa6d69f01e41
print(md5(input2).hexdigest()) # cee9a457e790cf20d4bdaa6d69f01e41

// '0e5093234' 为 0,'0eabc3234' 不为 0

// true
'0e509367213418206700842008763514' == '0e481036490867661113260034900752'
// true
'0e481036490867661113260034900752' == '0'

// false
var_dump('0' == '0e1abcd');
// true
var_dump(0 == '0e1abcd');

var_dump(md5('240610708') == md5('QNKCDZO'));
var_dump(md5('aabg7XSs') == md5('aabC9RqS'));
var_dump(sha1('aaroZmOk') == sha1('aaK1STfY'));
var_dump(sha1('aaO8zKZF') == sha1('aa3OFF9m'));
找出 0e 开头的 hash 碰撞,可以用如下代码



$salt = 'vunp';
$hash = '0e612198634316944013585621061115';

for ($i=1; $i<100000000000; $i++) {
if (md5($salt . $i) == $hash) {
echo $i;
break;
}
}

echo ' done';
0e 开头,md5后面全是数字的
240610708: 0e462097431906509019562988736854
QLTHNDT: 0e405967825401955372549139051580
QNKCDZO: 0e830400451993494058024219903391
PJNPDWY: 0e291529052894702774557631701704
NWWKITQ: 0e763082070976038347657360817689
NOOPCJF: 0e818888003657176127862245791911
MMHUWUV: 0e701732711630150438129209816536
MAUXXQC: 0e478478466848439040434801845361
IHKFRNS: 0e256160682445802696926137988570
GZECLQZ: 0e537612333747236407713628225676
GGHMVOE: 0e362766013028313274586933780773
GEGHBXL: 0e248776895502908863709684713578
EEIZDOI: 0e782601363539291779881938479162
DYAXWCA: 0e424759758842488633464374063001
DQWRASX: 0e742373665639232907775599582643
BRTKUJZ: 00e57640477961333848717747276704
ABJIHVY: 0e755264355178451322893275696586
aaaXXAYW: 0e540853622400160407992788832284
aabg7XSs: 0e087386482136013740957780965295
aabC9RqS: 0e041022518165728065344349536299
sha1
10932435112: 0e07766915004133176347055865026311692244
aaroZmOk: 0e66507019969427134894567494305185566735
aaK1STfY: 0e76658526655756207688271159624026011393
aaO8zKZF: 0e89257456677279068558073954252716165668
aa3OFF9m: 0e36977786278517984959260394024281014729

crc32
6586: 0e817678

pre_match 在匹配的时候会消耗较大的资源,并且默认存在贪婪匹配,如果传入一个超长的字符串,会导致 pre_match 消耗大量资源从而导致 php 超时,后面的 php 语句就不会执行。payload:


$code="xdsec###AAAAAAAAAAAAAAAAAAA(超多个A)";
preg_match("/(\d+)\.(\d+)\.(\d+)\.(\d+)/", $code));

方法多次调用

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
<?php 
https://segmentfault.com/a/1190000007210948
class MyDate
{
public static function getCurrentDate()
{
static $current_date = '';

if (!$current_date) {
echo 'only run one time';
usleep(200000);
$current_date = date('Y-m-d H:i:s');
}

return $current_date;
}

public static function getCurrentDate2()
{
usleep(200000);
echo 'run everytime';
return date('Y-m-d H:i:s');
}
}

$start = microtime(true);
$i = 5;
while ($i--) {
MyDate::getCurrentDate();
}
echo microtime(true) - $start; //200ms

$start = microtime(true);
$i = 5;
while ($i--) {
MyDate::getCurrentDate2();
}
echo microtime(true) - $start; //1s

越权漏洞

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
水平越权就是同等角色下的用户,不但能够访问和操作自己私有的数据,还能访问其他人私有的数据,其根本是基于数据的访问权限。
# 1 删除前鉴权处理
public function destory($id)
{
$payment = Payment::find($id);
if ($payment->user_id != $this->currentUser->id) {
return ...
}
$payment->delete();
}

# 2 参入id查询删除
public function destory($id)
{
Payment::whereUserId($this->currentUser->id)->whereId($id)->delete();
}

# 3 模型关联查询
class User extends Model
{
public function payments()
{
return $this->hasMany('App\Payment');
}
}

class PaymentController extends Controller
{
public function destory($id)
{
$this->currentUser->payments()->whereId($id)->delete();
}
}
在 findPassword 里面再次验证完成邮箱校验的账户是否为当前找回密码的账号

class UserController extends Controller
{

public function check($data)
{
if (checkEmail($data['email'], $data['code'])) {
return true;
}
...
}

public function findPassword($data)
{
if (checkEmail($data['email'], $data['code'])) {
$user = User::whereEmail($data['email'])->first();
$user->password = $data['new_password'];
$user->save();
}
...
}
}

https://learnku.com/articles/28505#topnav

限制分页条目

1
2
3
4
5
6
7
8
恶意请求者请求把 pagesize 输入 5000,10000 等甚至更大的数,会给数据库带来一定的压力,localhost/api/articles?pageid=0&pagesize=10000

//用框架自带的分页方法
public function index()
{
$builder = Article::with('category:id,name')->orderBy('id', 'desc')->paginate(8);
return response()->json(['status' => true, 'count' => $builder->total(), 'articles' => $builder->items()]);
}

XSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
<?php
$searchQuery = $_GET['q'];
/* some search magic here */
?>
<h1>You searched for: <?php echo $searchQuery; ?></h1>

</body>
因为我们把用户的内容直接打印出来,不经过任何过滤,非法用户可以拼接 URL: search.php?q=%3Cscript%3Ealert(1)%3B%3C%2Fscript%3E
PHP 渲染出来的内容如下,可以看到 Javascript 代码会被直接执行:

<body>
<h1>You searched for: <script>alert(1);</script></h1>
<p>We found: Absolutely nothing because this is a demo</p>
</body>

CSRF

1
2
3
4
5
6
7
例如网站上有用户可以用来注销账户的链接。

<a href="http://your-website.com/delete-account">销毁账户</a>
如果某个用户评论:

<img src=”http://your-website.com/delete-account”> wow
用户将在查看此评论的时候删除他们的账号。

No input file specified

1
2
3
4
5
6
7
2019/05/23 12:31:44 [error] 5085#5085: *1 FastCGI sent in stderr: "PHP message: PHP Warning:  Unknown: open_basedir restriction in effect. File(/home/vagrant/Code/haopai-git/public/index.php) is not within the allowed path(s): (/www/wwwroot/dev.guooo.top/:/tmp/:/proc/) in Unknown on line 0
PHP message: PHP Warning: Unknown: failed to open stream: Operation not permitted in Unknown on line 0
Unable to open primary script: /home/vagrant/Code/haopai-git/public/index.php (Operation not permitted)" while reading response header from upstream, client: 192.168.10.1, server: hp.hopa.cc, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/var/run/php/php7.2-fpm.sock:", host: "hp.hopa.cc"
看到上面的错误,我也去网上找,网上主要是说 ngnix 里面配置 fastcgi_param 的问题,但是我觉得不对吧,因为别的项目都没有问题,单单就这个项目有问题。不知怎么了,我就注意到 "/www/wwwroot/" 这个东西,我电脑里就不应该有这个文件夹啊,于是我就去项目里查找这个字符串,最后在 .user.ini 这个文件找到了,当我看到这个文件的时候我就意识到是他的问题,我把这个文件里面的配置删了。文件内容如下:

open_basedir=***********
https://learnku.com/articles/28858

一个简单的composer包

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
42
43
44
45
46
47
48
49
vi https://github.com/jianyan74/php-excel/blob/master/composer.json
{
"name": "jianyan74/php-excel",
"description": "php excel 导入导出",
"keywords": ["excel", "csv", "xlsx", "xls", "html", "jianyan74"],
"license": "MIT",
"authors": [
{
"name": "jianyan74"
}
],
"type": "extension",
"require": {
"php": ">=7.0",
"phpoffice/phpspreadsheet": "^1.3"
},
"autoload": {
"psr-4": {
"jianyan\\excel\\": "./src"
}
}
}

vi
<?php
namespace jianyan\excel;
use Exception;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Html;
use PhpOffice\PhpSpreadsheet\Writer\Xls;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Writer\Csv;
/**
* 导出导入Excel
*
* Class Excel
* @package jianyan\excel
* @author jianyan74 <751393839@qq.com>
*/
class Excel
{

}

composer require jianyan74/php-excel

use jianyan\excel\Excel;
Excel::exportData($list, $header)

jwt记录

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
JWT 由三部分组成:头部、数据体、签名 / 加密

这三部分以 . (英文句号) 连接,注意这三部分顺序是固定的,即 header.payload.signature 如下示例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Base64url 编码是 Base64 的一种针对 URL 的特定变种。因为 = 、+、/ 这个三个字符在 URL 中是有特定含义的,所以 Base64url 分别将 = 直接忽略,+ 替换成 -,/ 替换成 _
Header 部分:

{
"alg": "HS256",
"typ": "JWT"
}
Payload 部分:

{
"sub": "demo",
"name": "xfly",
"admin": true
}
根据 Header 部分的 alg 属性我们可以知道该 JWT 符合 JWS 中的规范,且签名算法是 HS256 也就是 HMAC SHA-256 算法,那么我们就可以根据如下公式计算最后的签名部分:

HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
其中的密钥是保证签名安全性的关键,所以必须保存好,在本例中密钥是 123456。因为有这个密钥的存在,所以即便调用方偷偷的修改了前两部分的内容,在验证环节就会出现签名不一致的情况,所以保证了安全性。
如果有效期设置过长,意味着这个 Token 泄漏后可以被长期利用,危害较大,所以一般我们都会设置一个较短的有效期。由于有效期较短,意味着需要经常进行重新授权的操作。
假设在用户操作过程中升级 / 变更了某些权限,势必需要刷新以更新数据。
要解决这个问题,需要在服务端部署额外逻辑,常见的做法是增加刷新机制和黑名单机制,通过 Refresh Token 刷新 JWT,将需要废弃的 Token 加入到黑名单。
简单科普下非对称式加密算法:有两个密钥,一个公开密钥和一个私有密钥,私钥参与加密,公钥用于解密,巧妙之处是解密只能用公钥来解,即便是加密用的密钥也无法对密文进行解密。你可以看到加密和解密需要两个不同的密钥,故称之为非对称加密。
https://learnku.com/articles/28909#reply91329

PHP-FPM 创建慢日志

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
CGI: 是一个协议,规定了 Web 服务器和后端语言的交互。但是性能差点,每个请求都会 fork 一个新的进程。\
FastCGI: 也是一个协议,是 CGI 的升级版,可以在一个进程内处理多个请求 \
FPM:FastCGI 进程管理器,是一个实现了 FastCGI 协议的工具 \
PHP-FPM: 是一个 PHP 的进程管理器,专门给 PHP 使用的 FPM 工具 \

Nginx 并不是直接和 PHP 进行通信的,而是通过 PHP-FPM。Nginx 不仅仅是一个强大的 Web 服务器,也是一个强大的代理服务器,提供了很多请求协议的代理。比如 Http 协议还有 FastCgi 协议等。

当请求进入到 Nginx 中,Nginx 提供了一个 FastCgi 模块 来把 Http 请求映射为对应的 Fastcgi 请求。该模块提供了 fastcgi_param 指定来完成映射关系。它的主要作用就是把 Nginx 中的变量翻译成 PHP 中能够理解的变量。 一般该文件是在 Nginx 的安装目录下,我的内容如下:

/etc/nginx # cat /etc/nginx/fastcgi_params

fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;

.php 结尾的请求都交给 fastcgi 模块处理,然后把处理后的请求发送给 PHP-FPM,然后 PHP-FPM 把请求交给 worker 进程,worker 进程加载 PHP 解析器运行 PHP 处理结果。 其中 fastcgi_pass unix:/var/run/php/php7.1-fpm.sock; 这一行用来指定 fpm 的地址

Nginx 和 PHP 的通信流程大概如下。

客户端发送请求到 Nginx
加载 nginx.conf 文件,把所有 .php 结尾的请求特殊处理
加载 FastCGI 模块,完成请求参数的解析映射,生成 FastCGI 请求
然后通过 fastcgi_pass 参数把 FastCGI 请求发送给 PHP-FPM 处理
PHP-FPM 收到请求,分配给空闲 worker 子进程
worker 子进程加载 PHP 解析器等 完成 PHP 执行获取结果
PHP-FPM 是一种 master/worker 进程架构。首先会启动一个 master 主进程,主要功能用来完成 PHP 环境的初始化,事件监听,子进程状态管理等等。然后会启动若干 worker 子进程来处理 PHP 请求
测试 php-fpm 配置内容是否正确 使用 -t 参数, 还可以通过加 -c 指定 php.ini 文件,通过 -y 指定 php-fpm.conf
find / -name php-fpm7
/etc/init.d/php-fpm7
/etc/logrotate.d/php-fpm7
/usr/sbin/php-fpm7

/usr/sbin/php-fpm7 -c /usr/local/php/etc/php.ini -y /usr/local/php/etc/php-fpm.conf -t

去看 PHP-FPM 的错误日志。在 php-fpm.conf 中增加 error_log = /xx/xx/php-fpm.error.log, 然后重启 PHP-FPM,在访问测试连接请求,发现果然有报错

[17-May-2019 10:04:50] NOTICE: fpm is running, pid 12
[17-May-2019 10:04:50] NOTICE: ready to handle connections
[17-May-2019 10:05:04] ERROR: failed to ptrace(ATTACH) child 22: Operation not permitted (1)
[17-May-2019 10:05:04] WARNING: [pool www123] child 22, script '/app/www/public/index.php' (request: "GET /index.php") executing too slow (2.317563 sec), logging
显示子进程权限不够,ptrace 调用失败! master 进程为了监控子进程需要调用 ptrace 来实现对子进程监控和追踪,但是调用 ptrace 却失败了 https://learnku.com/articles/28683

正则表达式之难点

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
断言 ?<= 这个是对需要匹配的目标左边的(前面)的进行断言,断定它前面会出现的 但是不会被匹配到。如:
$subject = 'I am Lancer, Please say hello Lancer';

//目标: 我要把hello 后面的Lancer 改为 '!' .
$pattern = '/(?<=hello )Lancer/';
$result = preg_replace($pattern, '', $subject);
echo $result; //I am Lancer, Please say hello !
这样就成功咯~
?=,与上面的位置刚好相反,这个是对需要匹配的目标右边的(后面)的进行断言,断定它后面会出现的 但是不会被匹配到。如:
$subject = 'I love you! I love her too!';

//目标:不能爱这么多, 把第二个 'love' 改为 'hate'
$pattern = '/love(?= her)/';
$result = preg_replace($pattern, 'hate', $subject);
echo $result; //'I love you! I hate her too!'
?<!这个是需要对匹配左边的(前面的)进行断言,不过它是非,找到不是这个的。还是拿第一个例子来说:
$subject = 'I am Lancer, Please say hello Lancer';

//目标: 我还是要把hello 后面的Lancer 改为 '!' 该怎么做
$pattern = '/(?<!am )Lancer/'; //找到‘Lancer’前面不是'am '的'Lancer'
$result = preg_replace($pattern, '', $subject);
echo $result; //I am Lancer, Please say hello !
?!还是一样的秘方,还是一样的味道~
$subject = 'I love you! I love her too!';

//目标:不能爱这么多, 把第二个 'love' 改为 'hate'
$pattern = '/love(?! you)/';
$result = preg_replace($pattern, 'hate', $subject);
echo $result; //'I love you! I hate her too!'
总结:这个断言,作用主要在,对于很多同样的目标,可是我只要其中的一个,或者多个的时候,那么就可以根据它的前面和后面,进行断言,来区分他们找到自己想要匹配的目标。
捕获 先来说一下, 什么叫捕获。就是匹配之后,会根据你正则表达式中的()来进行分组。一一捕获。打个比方:
//为了显示方便,写了个show函数
function show($str)
{
if (empty($str)) {
echo null;
} elseif (is_array($str) || is_object($str)) {
echo '<pre>';
print_r($str);
echo '</pre>';
} else {
echo $str;
}
}
//--------------------------------------------------------------------
$subject = '12323abcdea1233';
$pattern = '/(a)(b)(c)(d)(e)/';
preg_match_all($pattern, $subject, $matches);
show($matches);>>> $matches
=> [
[
"abcde",
],
[
"a",
],
[
"b",
],
[
"c",
],
[
"d",
],
[
"e",
],
]
//这个答案,大家应该都知道吧。索引为0的是整个match的内容,接着的
//就是捕获的每一个()分组的内容。我们还可以这样来写:

$subject = '123abcabc123';
$pattern = '/(a)(b)(c)(\1)(\2)(\3)/';
preg_match_all($pattern, $subject, $matches);
show($matches);//??
先看答案:
Array
(
[0] => Array
(
[0] => abcabc
)

[1] => Array
(
[0] => a
)

[2] => Array
(
[0] => b
)

[3] => Array
(
[0] => c
)

[4] => Array
(
[0] => a
)

[5] => Array
(
[0] => b
)

[6] => Array
(
[0] => c
)

)
//你可能会有疑问, 咦,,, 怎么(\1)和(a), (\2)和(b),(\3)和(c) 在正则里是一样的呢?
//其实 (a)就是指的第一组, 然后后面就可以用(\1)来表示。(b),(c)也一样。
有人可能就会问了, 那你写这个的作用又是什么呢 ? 获取这些括号里的干啥。。 我只要第一个索引的匹配就够了呀。

但是, 你考虑到了替换这个因素没? 如果我替换的时候需要()的东西呢? 这个时候,我们就可以用到捕获到的()的东西来穿插。
不知道有人好奇过没,为什么用那些TP框架,Laravel框架, 或者smarty 在模版里写的{{$msg}}为什么也能输出呢? 其实就是用了正则替换~ 看代码:

$msg = "正则捕获";
$subject = '<p>{{$msg}}</p>';
$pattern = '/\{\{(.*/)\}\}/'; //因为正则里也有'{' 和'}'所以需要用‘\’转义
$result = preg_replace($pattern, '<?php echo $1; ?>', $subject);
show($result); // <p><?php echo $msg;?></p>
//成功修改~
上面说的是捕获, 但是我可能不想捕获怎么办? 那么就可以用(?:) 在前面加上?:即可。注意, 这个不会影响匹配 只会影响捕获。 如:

$subject = 'abc';
$pattern = '/(a)(?:b)(c)/';
preg_match_all($pattern, $subject, $matches);
show($matches);
//结果:
Array
(
[0] => Array
(
[0] => abc
)

[1] => Array
(
[0] => a
)

[2] => Array
(
[0] => c
)
)
//看 匹配的结果让然是'abc' 不过没有捕获到 'b'
https://coffeephp.com/articles/5

S.O.I.L.D 之接口隔离

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
42
43
44
45
46
47
https://learnku.com/articles/29130
interface WorkableInterface
{
public function work();
}

interface ManageableInterface
{
public function beManaged();
}
class HumanWorker implements WorkableInterface, SleepableInterface, ManageableInterface
{
public function work()
{
return 'human working.';
}
public function sleep()
{
return 'human sleeping';
}
public function beManaged()
{
$this->work();
$this->sleep();
}
}

class AndroidWorker implements WorkableInterface, ManageableInterface
{
public function work()
{
return 'android working.';
}
public function beManaged()
{
$this->work();
}
}
保持 Captain 类的封闭性

class Captain
{
public function manage(ManageableInterface $worker)
{
$worker->beManaged();
}
}

array_map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$pieces = [];
foreach($whole as $item)
{
$pieces[] = $item['foo'];
}
return $pieces;
return array_map(
function ($item) { return $item['foo']; },
$whole
);

但需要遍历 Key => Value 形式的关联数组,该怎么操作呢?

return array_map(
function callback($k, $v) { ... },
array_keys($array),
$array
);
https://wi1dcard.cn/posts/php-array-map-instead-of-foreach/

intval() 与 (int)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$int = intval('0123', 8); // == 83
$test_int = 12;
$test_float = 12.8;
$test_string = "12";

echo (int) $test_int; // == 12
echo (int) $test_float; // == 12
echo (int) $test_string; // == 12

echo intval($test_int, 8); // == 12
echo intval($test_float, 8) // == 12
echo intval($test_string, 8); // == 10
intval() 会将类型已经是 int 或 float 的数据当作「无需转换」而直接返回!

所以如上,$test_int 和 $test_float 并没有按照八进制转换,使用时一定需要注意避免踩坑。

json_encode() 序列化非公开属性

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
class Foo implements \JsonSerializable
{
public $fooProperty;

protected $barProperty;

public function jsonSerialize()
{
return [
'fooProperty' => $this->fooProperty,
'barProperty' => $this->barProperty,
];
}
}
class Foo implements \JsonSerializable
{
public $fooProperty;

protected $barProperty;

public function jsonSerialize()
{
return get_object_vars($this);
}
}https://wi1dcard.cn/posts/json-encode-non-public-properties/

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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
$catList = [
'1' => ['id' => 1, 'name' => '颜色', 'parent_id' => 0],
'2' => ['id' => 2, 'name' => '规格', 'parent_id' => 0],
'3' => ['id' => 3, 'name' => '白色', 'parent_id' => 1],
'4' => ['id' => 4, 'name' => '黑色', 'parent_id' => 1],
'5' => ['id' => 5, 'name' => '大', 'parent_id' => 2],
'6' => ['id' => 6, 'name' => '小', 'parent_id' => 2],
'7' => ['id' => 7, 'name' => '黄色', 'parent_id' => 1],
];
$treeData = [];
foreach ($catList as $item) {
if (isset($catList[$item['parent_id']]) && !empty($catList[$item['parent_id']])) {
// 子分类
$catList[$item['parent_id']]['children'][] = &$catList[$item['id']];
} else {
// 一级分类
$treeData[] = &$catList[$item['id']];
}
}
=> [
[
"id" => 1,
"name" => "颜色",
"parent_id" => 0,
"children" => [
[
"id" => 3,
"name" => "白色",
"parent_id" => 1,
],
[
"id" => 4,
"name" => "黑色",
"parent_id" => 1,
],
[
"id" => 7,
"name" => "黄色",
"parent_id" => 1,
],
],
],
[
"id" => 2,
"name" => "规格",
"parent_id" => 0,
"children" => [
[
"id" => 5,
"name" => "大",
"parent_id" => 2,
],
[
"id" => 6,
"name" => "小",
"parent_id" => 2,
],
],
],
]

封装代码

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
if (IS_POST) {
$like = '';
if (isset($_POST['username'])) {
$username = $_POST['username'];
$like .= "username like '%" . $username . "%' and ";
}

if (isset($_POST['phone'])) {
$phone = $_POST['phone'];
$like .= "phone like '%" . $phone . "%' and";

}

if ($_POST['is_auth']) {
$isAuth = $_POST['is_auth'];
$like .= "is_auth like '%" . $isAuth . "%' and";

}

if ($_POST['sex']) {
$sex = $_POST['sex'];
$like .= "sex like '%" . $sex . "%' and";
}


if ($_POST['time']) {
$time = $_POST['time'];
$like .= "time like '%" . $time . "%' and";
}


if ($_POST['wallet']) {
$wallet = $_POST['wallet'];
$like .= "wallet like '%" . $wallet . "%' and";

}

$like = rtrim($like, 'and');

$sql = "SELECT * FROM `user` WHERE {$like}";


} else {
return view('user');
}
function request($param = null)
{
return new Request($param);
}

class Request
{
public function __construct(string $param = null)
{
return isset($_POST[$param]) ? $_POST[$param] : false;
}

public function all()
{
return $_POST;
}
}


class User
{
public $request = [
'username',
'phone',
'is_auth',
'sex',
'time',
'wallet'
];

public function index()
{
if (IS_POST) {
$like = '';

foreach (request()->all() as $key => $value) {
if (in_array($key, $this->request) && request($key)) {
$like .= sprintf("%s like %s and", $key, $value);
//$like[] = sprintf("%s like %s", $key, $value);
}
}

//$sql = implode(" AND ", $like);
$like = rtrim($like, 'and');

$sql = "SELECT * FROM `user` WHERE {$like}";

} else {
return view('user');
}
}
}
https://github.com/CrazyCodes/Blog/blob/master/%E6%88%91%E4%B8%8EJetbrains%E7%9A%84%E8%BF%99%E4%BA%9B%E5%B9%B4.md
https://segmentfault.com/a/1190000019274366

无限极分类树

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
array(
1 => array(
'id' => 1,
'name' => '中华人民共和国',
'parent_id' => 0,
'level' => 'country',
),
2 => array(
'id' => 2,
'name' => '北京市',
'parent_id' => 1,
'level' => 'province',
),
20 => array(
'id' => 20,
'name' => '天津市',
'parent_id' => 1,
'level' => 'province',
),
38 => array(
'id' => 38,
'name' => '河北省',
'parent_id' => 1,
'level' => 'province',
),
218 => array(
'id' => 218,
'name' => '山西省',
'parent_id' => 1,
'level' => 'province',
),
349 => array(
'id' => 349,
'name' => '内蒙古自治区',
'parent_id' => 1,
'level' => 'province',
),
465 => array(
'id' => 465,
'name' => '辽宁省',
'parent_id' => 1,
'level' => 'province',
),
...
);
优化前

/**
* 获取以父级 ID 为 $parent_id 为根节点的树型结构数组
*
* @param array $arr
* @param int $level 树型当前层
* @param int $parent_id 父级id
* @param int $floor 树型总层数
* @return array
*/
public static function getList(&$arr, $level = 0, $parent_id = 1, $floor = 3)
{
if ($level != 0) {
$empty = $arr[$parent_id];
$empty['list'] = [];
$emptyPointer = &$empty['list'];
} else {
$empty = [];
$emptyPointer = &$empty;
}
if ($level < $floor) {
$ok = false;
foreach ($arr as $index => &$item) {
if ($item['parent_id'] == $parent_id) {
$data = self::getList($arr, $level + 1, $index);
array_push($emptyPointer, $data);
$ok = true;
}
if ($ok && $item['parent_id'] != $parent_id) {
break;
}
}
}

return $empty;
}
优化后https://wi1dcard.cn/posts/php-fastest-create-tree-from-list/

public static function getList($catList)
{
$treeData = [];
foreach ($catList as &$item) {
$parent_id = $item['parent_id'];
if (isset($catList[$parent_id]) && !empty($catList[$parent_id])) {
$catList[$parent_id]['list'][] = &$catList[$item['id']];
} else {
$treeData[] = &$catList[$item['id']];
}
}
unset($item);
return $treeData[0]['list']; // 根节点只有中华人民共和国,所以直接返回中国的所有子节点
}

代理与反向代理、负载均衡和缓存

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
42
43
44
45
代理(正向代理,目标服务器不知道谁在访问)

位于客户端和目标服务器之间,起到一个中转的作用。其实就是客户端想访问目标服务器,但是因为某些原因不能够直接访问,则把请求和目标服务器发给代理服务器,代理服务器再去请求目标服务器,把返回的响应结果返回给客户端。


反向代理(用户实际并不知道最终服务器,只是访问一个反向代理服务器而已)

客户端会把反向代理服务器当成目标服务器,向反向代理服务器发送请求后,反向代理服务器再请求内部的后端服务器,把得到的响应结果返回给客户端。
server{
listen 80;
server_name test.test;
#将本机接收到的test.test的请求全部转发到另外一台服务器192.168.78.128
location /{
proxy_pass http://192.168.78.128;
#下面是其他辅助指令
proxy_set_header Host $host; #更改来自客户端的请求头信息
proxy_set_header X-Real_IP $remote_addr; #用户真实访问ip
proxy_connect_timeout 2; #配置nginx与后端服务器建立连接的超时时间
proxy_read_timeout 2; #配置nginx向后端发出read请求的等待响应超时时间
proxy_send_timeout 2; #配置nginx向后端服务器发出write请求的等待响应超时时间
proxy_redirect http://www.baidu.com; #用于修改后端服务器返回的响应头中的Location和Refresh
}
}


用户不直接访问后端服务器,而是访问负载均衡服务器,由负载均衡服务器再次转发到后端服务器。如果这个时候有一台后端服务器挂掉了,那么负载均衡服务器会剔除掉它,将后续请求都转发到好的那台,这样就不影响网站的正常运行
server{
listen 80;
server_name test.test;
location / {
proxy_pass http://web_server; #反向代理
}
}
#配置负载均衡服务器组
upstream web_server {
server 192.168.78.128;
server 192.168.78.129;
}

#配置负载均衡服务器组
upstream web_server {
server 192.168.78.128 weight=1;
server 192.168.78.129 weight=3;
}
这里面的权值总和为一个循环,这里以 4 次为一个循环,那么就是每四次请求中,三次会被分派到 129 这个服务器,一次分配到 128,但是具体三次并不会顺序执行,而是按照算法分散执行。https://learnku.com/articles/26686 https://learnku.com/articles/29231#reply92298

Swoole,Redis list实现简单消息推送

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
swoole.php

<?php
$server = new swoole_websocket_server("0.0.0.0", 9502);

$server->on('workerStart', function ($server, $workerId) {
$redis = new Swoole\Coroutine\Redis();
$redis->connect('127.0.0.1', 6379);
while (true) {
// brpop 第二个参数 50 表示超时(阻塞等待)时间, blpop 同理,详情建议读文档,对应的 redis 操作是 rpush/lpush key content
if (($message = $redis->brpop('message', 50)) === null) {
continue;
}
// var_dump($message); 结果为数组
foreach ($server->connections as $fd) {
$server->push($fd, 'redis 的 ' . $message[0] . ' 队列发送消息:' . $message[1]);
}
}
});

$server->on('open', function ($server, $request) {
$server->push($request->fd, "hello;\n");
});

$server->on('message', function (swoole_websocket_server $server, $request) {
$server->push($request->fd, "hello");
});

$server->on('close', function ($server, $fd) {
echo "client-{$fd} is closed\n";
$server->close($fd);
});

$server->start();
//https://alpha2016.github.io/2019/02/16/PHP,Swoole,Redis-list%E5%AE%9E%E7%8E%B0%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/

Redis list队列异常崩溃

1
2
3
用 Redis list 做消息队列,在取出消息的时候 Redis 宕机,程序上的业务逻辑没执行,怎样处理?先不去考虑 Redis 的异常处理及恢复,一般主从哨兵机制很难崩溃,暂时考虑程序端如何处理这种

直接使用 Redis 官方的 RPOPLPUSH / BRPOPLPUSH source destination 命令,在读取消息的同时,将读取到的消息内容放到目标队列,然后当前进程进行消费数据,消费成功,则 lrem destination 1 key 在目标队列中删除刚才的消息内容。

从0-n之间取k个不重复的数

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
使用 array 系列函数的方法

function getRandomN($max, $num) {
$count = 0;
$return = array();
while ($count < $num) {
$return[] = mt_rand(0, $max);
$return = array_flip(array_flip($return));
$count = count($return);
}
shuffle($return);
return $return;
}

return getRandomN(20, 10);

// output:[7,14,6,12,3,4,15,0,16,10]
不使用 array 系列函数的方法

function getRandomN($num, $n){
$startArray = range(0, $num);
$resultArray = [];
for($i = 0; $i < $n; $i++)
{
$random = mt_rand(0, $num - $i);
$resultArray[$i] = $startArray[$random];
$startArray[$random] = $startArray[$num - $i - 1];
}

return $resultArray;
}

return getRandomN(20, 10);

// output:[7,14,6,12,3,4,15,0,16,10]
https://alpha2016.github.io/2019/04/09/PHP%E4%BB%8E0-n%E4%B9%8B%E9%97%B4%E9%9A%8F%E6%9C%BA%E5%8F%96k%E4%B8%AA%E4%B8%8D%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0/

匿名函数

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
//声明一个狗对象
class Dog
{
public $name = '旺财';
}

//声明一个猫对象
class Cat
{
public $name = '喵喵';
}

//声明一个匿名函数,用来输出名字
$sayName = function() {
echo $this->name;
};

//用bind赋值上面那个匿名函数,并指定this指向狗对象,返回新的闭包函数
$sayDogName = Closure::bind($sayName, new Dog);

//调用新的闭包函数,输出 “旺财”
$sayDogName();
//指定类的作用域为 Dog 对象
$sayDogName = Closure::bind($sayName, new Dog, 'Dog');

//输出 “旺财”
$sayDogName();

数组整合合并

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

$a=[
[
"id"=> 1,
"name"=> "std_name_0"
],
[
"id"=>2,
"name"=> "std_name_1"
],
[
"id"=> 3,
"name"=>"std_name_2"
],
[
"id"=> 4,
"name"=> "std_name_3"
],
[
"id"=> 5,
"name"=> "std_name_4"
]

]
$b=[
[
"id"=> 2,
"order_count"=>0
],
[
"id"=> 3,
"order_count"=> 2
],
[
"id"=>4,
"order_count"=> 4
],
[
"id"=> 5,
"order_count"=>6
],
[
"id"=> 6,
"order_count"=> 8
],
[
"id"=> 7,
"order_count"=> 10
],
[
"id"=>8,
"order_count"=> 12
]
]
$users_info=[];
foreach ($a as $val){
$users_info[$val['id']]['id'] = $val['id'];
$users_info[$val['id']]['name'] = $val['name'];
$users_info[$val['id']]['order_count'] = 0;
}
foreach ($b as $val){
$users_info[$val['id']]['id'] = $val['id'];
$users_info[$val['id']]['order_count'] = $val['order_count'];
$users_info[$val['id']]['name'] = $users_info[$val['id']]['name']??'';
}
>>> $users_info
=> [
1 => [
"id" => 1,
"name" => "std_name_0",
"order_count" => 0,
],
2 => [
"id" => 2,
"name" => "std_name_1",
"order_count" => 0,
],
3 => [
"id" => 3,
"name" => "std_name_2",
"order_count" => 2,
],
4 => [
"id" => 4,
"name" => "std_name_3",
"order_count" => 4,
],
https://www.guaosi.com/2018/12/14/complex-two-dimensional-arrays/

循环中查询数据库

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
42
43
44
45
46
47
48
49
50
51
foreach ( $product as $p) {
//如果存在则更新
if ( $this->count($p) ) {
$this->update($p);
} else {
$this->insert($p);
}
}
$product = [
[
'product_id'=>1,
'pnid'=>1,
'pvid'=>10
],
[
'product_id'=>1,
'pnid'=>3,
'pvid'=>30
],
[
'product_id'=>1,
'pnid'=>12,
'pvid'=>21
],
[
'product_id'=>1,
'pnid'=>31,
'pvid'=>33
],
];

//获取product_id为1的数据
$oldData = $this->get(1);

/**
* 这里会把数据组成以pnid为键的数组
*/
$oldKeyData = array_column($oldData,NULL,'pnid');

$update = [];
$insert = [];

foreach ( $product as $p) {
if ( isset($oldKeyData[$p['pnid']]) ) {
$updatep[] = $p;
} eles {
$insert[] = $p;
}
}

//进行更新和删除https://learnku.com/articles/29764

json排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
array:5 [
6 => "中国"
5 => "美国"
4 => "日本"
1 => "俄罗斯"
2 => "英国"
]
return Response::send(0,'成功!',$data);
data {…}
1 俄罗斯
2 英国
4 日本
5 美国
6 中国
每个数字 key 前面加个空格字符串即可。

数组排序

1
array_multisort(array_column($array,'sort'),SORT_ASC,$array);

图片上传添加自动裁剪

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
composer require intervention/image

Intervention\Image\ImageServiceProvider::class,
'Image' => Intervention\Image\Facades\Image::class,
php artisan vendor:publish --provider="Intervention\Image\ImageServiceProviderLaravel5"


public function image_upload(Request $req)
{
try {
if ($req->isMethod('POST')) {
$file = $req->file('file');
// 图片宽度参数 可以写死也可传值接收 关闭 false
$max_width = false;
if ($file->isValid()) {
$realPath = $file->getRealPath ();// 临时文件路径

$entension = $file->getClientOriginalExtension();//上传文件后缀名
$time = date('Ym');
$newName = md5(uniqid(microtime(true), true)) . date('YmdHis') . '.' . $entension;
$path = $file->move(base_path() . '/public/upload/image/' . $time, $newName);
$filepath = '/upload/image/' . $time . '/' . $newName;

// 图片物理路径 特别重要 如果路径则无法识别写入图片
$img = public_path().'/'.$filepath;

// 图片剪裁逻辑 如果限制了图片宽度且不为gif格式,就进行裁剪
if ($max_width && $entension != 'gif') {
// 此类中封装的函数,用于裁剪图片
$this->reduceSize($img, $max_width);
}

return response()->json(['status' => 0, 'msg' => '上传成功', 'filepath' => $filepath]);
}
}
} catch (Exception $e) {
return $this->doFailure($e);
};
}

// 图片按宽度剪裁
public function reduceSize($img, $max_width)
{
// 先实例化,传参是文件的磁盘物理路径

$image = Image::make($img);

// 进行大小调整的操作
$image->resize($max_width, null, function ($constraint) {

// 设定宽度是 $max_width,高度等比例双方缩放
$constraint->aspectRatio();

// 防止裁图时图片尺寸变大
$constraint->upsize();
});

// 对图片修改后进行保存
$image->save();
}`

快速接入 GitHub 登陆

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/**
* 发送请求(也可以直接使用guzzle)
*https://learnku.com/articles/29160
* @param string $url 请求地址
* @param array $data 请求数据
* @param array $headers 请求头
* @return string|array
*/
function sendRequest($url, $data = [], $headers = [])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
if (!empty($data)) {
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
$response = curl_exec($ch) ? curl_multi_getcontent($ch) : '';
curl_close($ch);
return $response;
}

// 如果用户同意登陆, github 就会返回到 callback.php 并携带一个code参数
// 此时只需要使用这个 code 去获取 access_token, 然后在使用 access_token 获取用户信息
$url = "https://github.com/login/oauth/access_token";
$app_id = "your github oauth app client_id";
$app_secret = "your github oauth app client_secret";

// 组合请求参数
$code = $_GET['code'];
$params = [
'client_id' => $app_id,
'client_secret' => $app_secret,
'code' => $code,
];

// 发送请求并获取响应信息
$response = sendRequest($url, $params, ['Accept: application/json']);
$response = json_decode($response, true);

// 如果有响应信息, 说明请求成功
if (!empty($response['access_token'])) {
// 请求成功,使用 access_token 获取用户信息
$access_token = $response['access_token'];
$url = "https://api.github.com/user";

// 发送请求,调取github API 获取用户信息
$userInfo = sendRequest($url,[],[
"Accept: application/json",
"User-Agent: ilearn", // 此处(ilearn)是填写应用名称 或者 github用户名
"Authorization:token {$access_token}"
]);

exit($userInfo);
}

// 如果登陆失败就打印错误信息
echo "<p>登陆失败</p></pre>";
var_dump($response);
exit("</pre>");

https://github.com/overtrue/laravel-socialite

使用命令 composer require huoshaotuzi/sociate 安装,现在支持 QQ、微博、微信公众号、百度、Github 登录,以后有时间还会继续扩展更多的登录。https://learnku.com/laravel/t/24449

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
25
26
27
28
29
30
31
32
33
34
35
36
SQL 注入
$username = $_GET['username'];
$query = $pdo->prepare('SELECT * FROM users WHERE username = :username');
$query->execute(['username' => $username]);
$data = $query->fetch();

xss
$searchQuery = htmlentities($searchQuery, ENT_QUOTES);
XSRF/CSRF
最常用的防御方法是生成一个 CSRF 令牌加密安全字符串,一般称其为 Token,并将 Token 存储于 Cookie 或者 Session 中。

每次你在网页构造表单时,将 Token 令牌放在表单中的隐藏字段,表单请求服务器以后会根据用户的 Cookie 或者 Session 里的 Token 令牌比对,校验成功才给予通过

密码哈希

//user signup
$password = $_POST['password'];
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);

//login
$password = $_POST['password'];
$hash = '1234'; //load this value from your db

if(password_verify($password, $hash)) {
echo 'Password is valid!';
} else {
echo 'Invalid password.';
}
密码哈希并不是密码加密。哈希(Hash)是将目标文本转换成具有相同长度的、不可逆的杂凑字符串(或叫做消息摘要),而加密(Encrypt)是将目标文本转换成具有不同长度的、可逆的密文。显然他们之间最大的区别是可逆性,在储存密码时,我们要的就是哈希这种不可逆的属性。

命令注入
$targetIp = escapeshellarg($_GET['ip']);
$output = shell_exec("ping -c 5 $targetIp");

像登录这样的敏感表单应该有一个严格的速率限制,以防止暴力攻击。保存每个用户在过去几分钟内失败的登录尝试次数,如果该速率超过你定义的阈值,则拒绝进一步登录尝试,直到冷却期结束。还可通过电子邮件通知用户登录失败,以便他们知道自己的账户被成为目标。
https://learnku.com/php/t/24930#reply94655

内置 Web 服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
php -S localhost:8000 -t public/
php -S localhost:8000 router.php
/**
* 对URL进行解析,并获取请求的文件名
*/
$uri = urldecode(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH));

/**
* 判断是否存在该文件,如果不存在,则直接继续加载入口文件
*/
if ($uri !== "/" && file_exists(__DIR__ . "$uri")) {
return false;
}

/**
* 加载入口文件
*/
require_once "./index.php";
https://learnku.com/articles/28768#reply94663

phpexcel

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
https://github.com/PHPOffice/PhpSpreadsheet

composer require phpoffice/phpspreadsheet
require 'vendor/autoload.php';

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', 'Hello World !');

$writer = new Xlsx($spreadsheet);
$writer->save('hello world.xlsx');
php -S localhost:8000 -t vendor/phpoffice/phpspreadsheet/samples
// 文件路径
$inputFileName = './sampleData/example1.xls';

$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($inputFileName);
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls();
只要读取数据,不要格式时,实例读取器中 readDataOnly 属性,如下

$inputFileType = 'Xls';
$inputFileName = './sampleData/example1.xls';

$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType);
/** 只要数据 **/
$reader->setReadDataOnly(true);

$spreadsheet = $reader->load($inputFileName);

多个文件合并为一个对象

$inputFileType = 'Csv';
$inputFileNames = [
'./sampleData/example1.csv',
'./sampleData/example2.csv'
'./sampleData/example3.csv'
];

$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType);

/** 拿到第一个 **/
$inputFileName = array_shift($inputFileNames);

$spreadsheet = $reader->load($inputFileName);

$spreadsheet->getActiveSheet()
->setTitle(pathinfo($inputFileName,PATHINFO_BASENAME));

/** 循环读取 **/
foreach($inputFileNames as $sheet => $inputFileName) {
/** 重新设置工作表索引 **/
$reader->setSheetIndex($sheet+1);
/** 把文件当做一个新的工作表载入 **/
$reader->loadIntoExisting($inputFileName,$spreadsheet);
/** 设置工作表标题 **/
$spreadsheet->getActiveSheet()
->setTitle(pathinfo($inputFileName,PATHINFO_BASENAME));
}

$spreadsheet = PhpOffice\PhpSpreadsheet\IOFactory::load("new.xls");

$data = $spreadsheet
->getSheet(0) // 指定第一个工作表为当前
->toArray(); // 转为数组

// 或者得到全部工作表的数据数组
$cells=array();
// 工作表对象有迭代器实现
foreach ( $spreadsheet->getWorksheetIterator() as $data ) {
$cells = $data->toArray();
}
https://learnku.com/articles/30048

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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
$processNum = 4;
$timeStart = time();
$tasks = range(1, 20);

$jobs = [];
foreach ($tasks as $task) {
//这里讲返回的结果对4进行取模,存入4个数组,然后4个进程分别读取不同的数据进行处理
$jobs[$task % $processNum][] = $task;
}

$mainPid = posix_getpid();
echo "主进程:" . $mainPid . PHP_EOL;

for ($i = 0; $i < $processNum; $i++) {
$pid = pcntl_fork();
if ($pid == -1) {
//错误处理:创建子进程失败时返回-1.
die('could not fork');
} elseif ($pid) {
//父进程会得到子进程号,所以这里是父进程执行的逻辑
} else { // 子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
$content = $jobs[$i];
$childStart = time();
foreach ($content as $v2) {
sleep(1); // 子进程执行的逻辑
}
$childEnd = time();
$childDiff = $childEnd - $childStart;
echo "#" . posix_getpid() . "执行完毕,用时:" . $childDiff . "秒" . PHP_EOL;
exit(); // 子进程执行完后必须退出,否则会循环的创建进程...
}
}

//这里挂起主进程,等待子进程全部退出后再退出主进程
while ($processNum > 0) {
if ( pcntl_wait($status) > 0) {
$processNum--;
echo "#" . $pid . "退出" . PHP_EOL;
}
}

$timeEnd = time();
$diff = $timeEnd - $timeStart;
echo '共计用时:' . $diff . '秒';https://learnku.com/laravel/t/30134

php 进程

1
2
3
4
5
6
7
8
9
10
11
12
后台运行
if ($pid=pcntl_fork ()) exit (0);// 是父进程,结束父进程,子进程继续
脱离控制终端,登录会话和进程组
posix_setsid ();// 子进程升级组长进程,脱离原来的会话 / 终端
禁止进程重新打开控制终端
if ($pid=pcntl_fork ()) exit (0);// 结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
关闭打开的文件描述符
fclose (STDIN),fclose (STDOUT),fclose (STDERR)// 关闭标准输入输出与错误显示。
改变当前工作目录
chdir("/")
重设文件创建掩模
umask (0);// 防止继承父级遗留下来的掩模

码模拟实现队列

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
define('MAXSIZE',5);//队列空间大小
/**
* @param $queue 队列存储空间
* @param $front 队列队头游标
* @param $near 队列队尾游标
* @param $data 进队的数据
* @return int
*/
function enQueue(&$queue,$front,$near,$data)
{
if (($near+1)%MAXSIZE==$front){
throw new RuntimeException("队空间已满");
}
$queue[$near%MAXSIZE] = $data;
$near+=1;
return $near;
}

/**
* 出队操作
* @param $queue
* @param $front
* @param $near
* @return int
*/
function deQueue(&$queue,$front,$near)
{
if ($front==$near%MAXSIZE){
throw new RuntimeException("空队列");
}
$data = $queue[$front];
echo "出队元素:".$data.PHP_EOL;
$front = ($front+1)%MAXSIZE;
return $front;
}
$a = [];//队列 使用数组模拟
$front = $near = 0;//游标初始化
$near = enQueue($a,$front,$near,1);
$near = enQueue($a,$front,$near,2);
$near = enQueue($a,$front,$near,3);

$front = deQueue($a,$front,$near);
$front = deQueue($a,$front,$near);
$front = deQueue($a,$front,$near);
//$front = deQueue($a,$front,$near);

$near = enQueue($a,$front,$near,100);
$front = deQueue($a,$front,$near);

$near = enQueue($a,$front,$near,1);
$near = enQueue($a,$front,$near,2);
$near = enQueue($a,$front,$near,3);
$near = enQueue($a,$front,$near,4);
$front = deQueue($a,$front,$near);
$front = deQueue($a,$front,$near);
$front = deQueue($a,$front,$near);
$front = deQueue($a,$front,$near);
队列实现了先进先出 FIFO 的功能,在许多应用中如队列系统,排队买票等都应用到了,比如我们用的 redis 缓存系统中的列数据类型 List,它便能完成队列的功能
https://learnku.com/articles/30430

夏令时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//判断美国那个时间段是否为夏令时
function is_dst($timestamp)
{
$timezone = date('e'); //获取当前使用的时区
date_default_timezone_set('US/Pacific-New'); //强制设置时区
$dst = date('I',$timestamp); //判断是否夏令时
date_default_timezone_set($timezone); //还原时区
return $dst; //返回结果
}
https://gist.github.com/flowerains/6694812
//获取当月第一天和最后一天
private function getthemonth($date)
{
$firstday = date('Y-m-01', strtotime($date));
$lastday = date('Y-m-d', strtotime("$firstday +1 month -1 day"));
return array($firstday, $lastday);
}

去除emoji

1
2
3
4
5
6
7
8
9
10
11
12
13
function removeEmoji($text) {
$clean_text = "";
// Match Emoticons
$regexEmoticons = '/[\x{1F600}-\x{1F64F}]/u';
$clean_text = preg_replace($regexEmoticons, '', $text);
// Match Miscellaneous Symbols and Pictographs
$regexSymbols = '/[\x{1F300}-\x{1F5FF}]/u';
$clean_text = preg_replace($regexSymbols, '', $clean_text);
// Match Transport And Map Symbols
$regexTransport = '/[\x{1F680}-\x{1F6FF}]/u';
$clean_text = preg_replace($regexTransport, '', $clean_text);
return $clean_text;
}

imagick等比缩放图片

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
42
/** 
* 使用imagick 等比缩放图片
* @param string $source_img 源图片地址
* @param string $target_img 缩放后图片地址
* @param int $with 缩放后图片宽度
* @param int $height 缩放后图片高度
*/
public function imagick($source_img,$target_img,$with,$height){
if(is_file($source_img)){ //判断源图片是否存在
$im = new Imagick();
}else{
exit;
}
$result = $im->readImage($source_img);
$srcWH = $im->getImageGeometry(); //获取源图片宽和高
//图片等比例缩放宽和高设置 ,根据宽度设置等比缩放
if($srcWH['width']>$with){
$srcW['width'] = $with;
$srcH['height'] = $srcW['width']/$srcWH['width']*$srcWH['height'];
}else{
$srcW['width'] = $srcWH['width'];
$srcH['height'] = $srcWH['height'];
}

//按照比例进行缩放
$im->thumbnailImage( $srcW['width'], $srcH['height'], true );

// 按照缩略图大小创建一个有颜色的图片
$new_img= new Imagick();
$new_img->newImage( $srcW['width'], $srcH['height'], 'white', 'jpg' ); //pink,black

//合并图片
$new_img->compositeImage( $im, imagick::COMPOSITE_OVER, 0, 0);
//生成图片
$new_img->setImageFileName($target_img);
$new_img->writeImage();
//输出图片
header( "Content-Type: image/jpg" );
echo $new_img;
}
}
https://gist.github.com/flowerains/beaab90b1bfc01054de3

redis支持php的序列化和反序列化

1
2
3
4
5
6
7
8
$redis = new Redis();
$redis->connect('127.0.0.1', 6379, 1);
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
$key = 'this_is_test';
$value = ['15','12',15,'alex'];
$name = $redis->rPush($key,$value);
$result = $redis->lpop($key);
var_dump($result);

一致性hash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 整数hash
function intHash($key)
{
$md5 = substr(md5($key), 0, 8);
$seed = 31;
$hash = 0;
for ($i = 0;$i < 8; ++$i) {
$hash = $hash * $seed + ord(md5($i));
++$i;
}
return $hash & 0x7FFFFFFF;
}
$value = intHash('wangchuang') % 2;
echo $value;

抽奖的经典算法

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
function get_rand($proArr) { 
$result = '';

//概率数组的总概率精度
$proSum = array_sum($proArr);

//概率数组循环
foreach ($proArr as $key => $proCur) {
$randNum = mt_rand(1, $proSum);
if ($randNum <= $proCur) {
$result = $key;
break;
} else {
$proSum -= $proCur;
}
}
unset ($proArr);

return $result;
}
$prize_arr = array(
'0' => array('id'=>1,'prize'=>'平板电脑','v'=>1),
'1' => array('id'=>2,'prize'=>'数码相机','v'=>5),
'2' => array('id'=>3,'prize'=>'音箱设备','v'=>10),
'3' => array('id'=>4,'prize'=>'4G优盘','v'=>12),
'4' => array('id'=>5,'prize'=>'10Q币','v'=>22),
'5' => array('id'=>6,'prize'=>'下次没准就能中哦','v'=>50),
);
foreach ($prize_arr as $key => $val) {
$arr[$val['id']] = $val['v'];
}

$rid = get_rand($arr); //根据概率获取奖项id

$res['yes'] = $prize_arr[$rid-1]['prize']; //中奖项
unset($prize_arr[$rid-1]); //将中奖项从数组中剔除,剩下未中奖项
shuffle($prize_arr); //打乱数组顺序
for($i=0;$i<count($prize_arr);$i++){
$pr[] = $prize_arr[$i]['prize'];
}
$res['no'] = $pr;
echo json_encode($res);
https://gist.github.com/flowerains/1e2e57906d496206a097

$proArr = ['1'=>65,'2'=>20,'3'=>5,'4'=>5,'5'=>5];
$rank = get_rand($proArr);
$i = 0;
//统计次数
$num1 = 0;
$num2 = 0;
$num3 = 0;
$num4 = 0;
$num5 = 0;
$arr = array();
do{
$rank = get_rand($proArr);
switch ($rank) {
case 1:
$num1++;
break;
case 2:
$num2++;
break;
case 3:
$num3++;
break;
case 4:
$num4++;
break;
case 5:
$num5++;
break;
}
$arr[] = $rank;
$i++;
}while($i < 100000);
$value1 = number_format($num1/count($arr),4)*100;
$value2 = number_format($num2/count($arr),4)*100;
$value3 = number_format($num3/count($arr),4)*100;
$value4 = number_format($num4/count($arr),4)*100;
$value5 = number_format($num5/count($arr),4)*100;
echo "1的中奖率%$value1\n";
echo "2的中奖率%$value2\n";
echo "3的中奖率%$value3\n";
echo "4的中奖率%$value4\n";
echo "5的中奖率%$value5\n";

本月第一天

1
2
3
4
5
6
7
8
9
//上一周的周一周末 
$start=date("Y-m-d",strtotime("2016-11-07"."-1 week Monday"));
echo date("Y-m-d",strtotime("$start +6 day"));
//下一周的周一周末
$start=date("Y-m-d",strtotime("2016-11-07"."+1 week Monday"));  
echo date("Y-m-d",strtotime("$start +6 day"));  
//本月的第一天,最后一天  
$start=date('Y-m-01', strtotime(date("Y-m-d")));  
echo date('Y-m-d', strtotime("$start +1 month -1 day"));

多维数组转一维数组

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
42
43
44
<?php

$a=array(
0=>array(
0=>"a",
1=>array(
0=>'b'
)
),
1=>array(
0=>'c',
1=>'d',
2=>array(
0=>array(
"ddd"=>array(
0=>'e',
"a"=>'f',
1=>'g'
)
)
)
),
'w'=>'h'
);
//获取未知维度数组最里面的值,换成一维数组https://3v4l.org/Mrjr1
function get_array_value($data){
$result=array();
array_walk_recursive($data,function($value) use (&$result){
array_push($result,$value);
});
return $result;
}
print_r(get_array_value($a));
Array
(
[0] => a
[1] => b
[2] => c
[3] => d
[4] => e
[5] => f
[6] => g
[7] => h
)

面向对象的三大特性

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
封装,继承,多态 面向对象的五大原则

单一职责原则,开放封闭原则,里氏替换原则,依赖倒置原则,接口隔离原则


/**
* 输出一个字符串
* 装饰器动态添加功能
* Class EchoText
*/
class EchoText
{
protected $decorator = [];

public function Index()
{
//调用装饰器前置操作
$this->beforeEcho();
echo "你好,我是装饰器。";
//调用装饰器后置操作
$this->afterEcho();
}

//增加装饰器
public function addDecorator(Decorator $decorator)
{
$this->decorator[] = $decorator;
}

//执行装饰器前置操作 先进先出原则
protected function beforeEcho()
{
foreach ($this->decorator as $decorator)
$decorator->before();
}

//执行装饰器后置操作 先进后出原则
protected function afterEcho()
{
$tmp = array_reverse($this->decorator);
foreach ($tmp as $decorator)
$decorator->after();
}
}

/**
* 装饰器接口
* Class Decorator
*/
interface Decorator
{
public function before();

public function after();
}

/**
* 颜色装饰器实现
* Class ColorDecorator
*/
class ColorDecorator implements Decorator
{
protected $color;

public function __construct($color)
{
$this->color = $color;
}

public function before()
{
echo "<dis style='color: {$this->color}'>";
}

public function after()
{
echo "</div>";
}
}

/**
* 字体大小装饰器实现
* Class SizeDecorator
*/
class SizeDecorator implements Decorator
{
protected $size;

public function __construct($size)
{
$this->size = $size;
}

public function before()
{
echo "<dis style='font-size: {$this->size}px'>**";
}

public function after()
{
echo "</div>";
}
}

//实例化输出类
$echo = new EchoText();
//增加装饰器
$echo->addDecorator(new ColorDecorator('red'));
//增加装饰器
$echo->addDecorator(new SizeDecorator('22'));
//输出
$echo->Index();
interface employee
{
public function working();
}

class teacher implements employee//具体应该依赖与抽象
{
public function working(){
echo 'teaching...';
}
}

class coder implements employee
{
public function working(){
echo 'coding...';
}
}

class workA//例子1
{
public function work(){
$teacher = new teacher;
$teacher->working();
}
}

class workB//例子2
{
private $e;
public function set(employee $e){
$this->e = $e;
}

public function work(){
$this->e->working();
}
}

$worka = new workA;//workA 依赖于 teacher 类 不符合依赖倒置原则
$worka->work();
$workb = new workB;//workB 不依赖与某个类 既可以注入 teacher 也可以 注入 coder
$workb->set(new teacher());
$workb->work();
https://learnku.com/articles/30034#267039

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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
使用队列,额外起一个进程处理队列,并发请求都放到队列中,由额外进程串行处理,并发问题就不存在了,但是要额外进程支持以及处理延迟严重,本文不先不讨论这种方法。
利用数据库事务特征,做原子更新,此方法需要依赖数据库的事务特性。
借助文件排他锁,在处理下单请求的时候,用 flock 锁定一个文件,成功拿到锁的才能处理订单
<?php
$http = new swoole_http_server("0.0.0.0", 9509); // 监听 9509

$http->set(array(
'reactor_num' => 2, //reactor thread num
'worker_num' => 4 //worker process num
));

$http->on('request', function (swoole_http_request $request, swoole_http_response $response) {
$uniqid = uniqid('uid-', TRUE); // 模拟唯一用户ID
$redis = new Redis();
$redis->connect('127.0.0.1', 6379); // 连接 redis

$redis->watch('rest_count'); // 监测 rest_count 是否被其它的进程更改

$rest_count = intval($redis->get("rest_count")); // 模拟唯一订单ID
if ($rest_count > 0){
$value = "{$rest_count}-{$uniqid}"; // 表示当前订单,被当前用户抢到了

// do something ... 主要是模拟用户抢到单后可能要进行的一些密集运算
$rand = rand(100, 1000000);
$sum = 0;
for ($i = 0; $i < $rand; $i++) {$sum += $i;}

// redis 事务
$redis->multi();
$redis->lPush('uniqids', $value);
$redis->decr('rest_count');
$replies = $redis->exec(); // 执行以上 redis 事务

// 如果 rest_count 的值被其它的并发进程更改了,以上事务将回滚
if (!$replies) {
echo "订单 {$value} 回滚" . PHP_EOL;
}
}
$redis->unwatch();
});

$http->start();
使用 ab 测试https://learnku.com/articles/29686

$ ab -t 20 -c 10 http://192.168.1.104:9509/

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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 编写一个简单daemon程序https://learnku.com/articles/30595
$pid = pcntl_fork();

switch($pid) {
case 0:
// 2.子进程逻辑
$mypid = posix_getpid();
//echo "Son pid is $mypid\n";

// 3.升级组长进程
if (!$sid = posix_setsid()) {
exit("Set sid error\n");
}

// 4.防止组长进程再次控制终端
if (-1 == pcntl_fork()) {
exit("Fork error\n");
}

// 5.标准输入/输出/错误
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);

// 6.改变目录
chdir("/");

// 7.重设掩码
umask(0);
break;

case -1:
// fork err
exit("Fork error\n");
break;

default:
// 1.父进程逻辑
$mypid = posix_getpid();
//echo "Parent pid is $mypid\n";
exit();
break;
}

for ($i = 0; $i < 10000; $i ++) {
file_put_contents("/tmp/wutao.log", "i=$i\n", FILE_APPEND);
}

sleep(10);

JSON_encode () 精度缺失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  $info = 26.54;
$res = json_encode($info);
var_dump($res);
运行结果

string(18) "26.539999999999999"

serialize_precision = 16
>>> ini_get('serialize_precision')
=> "17"
>>> ini_set('serialize_precision',16)
=> "17"
>>> json_encode($info);
=> "26.54"

生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function getRange ($max = 10) {
for ($i = 1; $i < $max; $i++) {
$injected = yield $i;

if ($injected === 'stop') return;
}
}
$generator = getRange(PHP_INT_MAX);
传递参数到生成器中


foreach ($generator as $range) {
if ($range === 10000) {
$generator->send('stop');
}

echo "Dataset {$range} <br>";
}
注意: 在生成器中使用 return ,会跳出生成器。
https://learnku.com/php/t/28336

字符串分割

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
$str = '#本科 & 硕士 @博士 - 教授';
$limit1 = array('#', '&', '@', '-');
$limit2 = array('$#$', '$&$', '$@$', '$-$');
$str2 = str_replace($limit1, $limit2, $str);
$arr = array();
$arr1 = array_values(array_filter(explode('$', $str2)));
foreach ($arr1 as $k => $v) {
if ($k % 2 == 0) {
$arr[] = array($v, $arr1[$k + 1]);
}
}
print_r($arr); https://learnku.com/index.php/laravel/t/30777
$string = '#本科 & 硕士 @博士 - 教授';
$matches = [];
preg_match_all('/([#|&|@|\-])([^#|&|@|\-]*)/', $string, $matches);

$signArr = array_map('trim', $matches[1]);
$titleArr = array_map('trim', $matches[2]);
# 在这以下的代码你可以自由发挥了
$data = [];
$minCount = min(count($signArr), $titleArr);
for ($i=0; $i<$minCount; $i++)
{
$data[] = [
$signArr[$i],
$titleArr[$i],
];
}

闭包和匿名函数

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
42
function callFunc1(Closure $closure) {
$closure();
}

function callFunc2(Callable $callback) {
$callback();
}

function xy() {
echo 'Hello, World!';
}
$function = function() {
echo 'Hello, World!';
};

callFunc1("xy"); // Catchable fatal error: Argument 1 passed to callFunc1() must be an instance of Closure, string given
callFunc2("xy"); // Hello, World!

callFunc1($function); // Hello, World!
callFunc2($function); // Hello, World!

//从php7.1开始可以使用以下代码转换
callFunc1(Closure::fromCallable("xy"))
其中 callable 参数可以为

is_callable('functionName');
is_callable([$foo, 'bar']);//$foo->bar()
is_callable(['foo','bar']);//foo::bar()
is_callable(function(){});//closure

public function subscribeSellOrder(SellOrderProcess $sellOrderProcess)
{
$closure = self::makeClosure($sellOrderProcess, 'sellOrdersProcess');
$this->redis->subscribe(RtdataConst::SELL_CHANNEL_NAME, $closure);
}

protected static function makeClosure($obj, $method)
{
return Closure::fromCallable([$obj, $method]);
}

https://learnku.com/index.php/articles/30767

curl绑定host

1
2
3
4
5
xxx.cn 测试的host为172.16.252.2
$url = "http://172.16.252.2/api/test";

$client = new Client(['emitter' => new \App\Services\GuzzleEmitter('test')]);
$response = $client->get($url,array("timeout"=>3,"connect_timeout"=>2,'headers'=>['host'=>'xxx.cn']));

错误和异常

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
https://learnku.com/articles/25813
错误并不能被 try catch 捕捉
Parse error 解析错误 Fatal error 致命错误 Warning 警告 Notice 注意
error_report 设置错误的报告级别error_reporting(0);

set_error_handler 设置用户自定义的错误处理函数
set_error_handler('myErrorFun'); //把php原始的错误处理机制,变成我们的myErrorFun函数处理
function myErrorFun($errno, $message, $file, $line)
{
echo '错误码是:'.$errno.'</br>';
echo '错误的信息是'.$message.'</br>';
echo '错误的文件是:'.$file.'</br>';
echo '错误的行数是'.$line;
}

echo $a; //a是未定义的变量
错误是由于 php 脚本自身的问题导致的和逻辑无关,异常则是由于逻辑问题导致的
在 php7 之前错误不能用 try catch 捕获,异常可以
try {
throw new Exception("大江东去,浪淘尽,千古风流人物"); //抛出异常
异常必须要手动抛出才行否则捕获不到 手动抛出异常是 php 比较鸡肋的地方,异常必须要手动抛出,才能捕获到,但是既然已经知道哪里会发生异常,直接规避不就可以了吗?或者重写逻辑,这可能是 php 比较特殊的地方吧
} catch (Exception $e) { //系统内置的异常处理类
echo $e->getMessage(); //获取异常信息
}
自定义的异常处理函数,用来处理没有用 try catch 捕获的异常,如果你抛出的异常无人捕获,那么就会进入该方法
set_exception_handler('myexception');
function myexception($exception)
{
var_dump($exception->getMessage());
}

throw new Exception("测试一下自定义异常处理函");

装饰器

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
封装,继承,多态

什么是封装?

把客观的事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的类进行信息的隐藏。简单的说就是:封装使对象的设计者与对象的使用者分开,使用者只要知道对象可以做什么就可以了,不需要知道具体是怎么实现的。封装可以有助于提高类和系统的安全性。

什么是继承?

继承指的是建立一个新的派生类,从一个或多个先前定义的类中继承数据和函数,可以重新定义或加进新数据和函数,从而建立了类的层次或等级。

什么是多态?

多态性指的是: 同一操作作用与不同类的实例,将产生不同的执行结果,即不同类的对象收到相同的消息时,将得到不同的结果。
/**
* 输出一个字符串
* 装饰器动态添加功能
* Class EchoText
*/
class EchoText
{
protected $decorator = [];

public function Index()
{
//调用装饰器前置操作
$this->beforeEcho();
echo "你好,我是装饰器。";
//调用装饰器后置操作
$this->afterEcho();
}

//增加装饰器
public function addDecorator(Decorator $decorator)
{
$this->decorator[] = $decorator;
}

//执行装饰器前置操作 先进先出原则
protected function beforeEcho()
{
foreach ($this->decorator as $decorator)
$decorator->before();
}

//执行装饰器后置操作 先进后出原则
protected function afterEcho()
{
$tmp = array_reverse($this->decorator);
foreach ($tmp as $decorator)
$decorator->after();
}
}

/**
* 装饰器接口
* Class Decorator
*/
interface Decorator
{
public function before();

public function after();
}

/**
* 颜色装饰器实现
* Class ColorDecorator
*/
class ColorDecorator implements Decorator
{
protected $color;

public function __construct($color)
{
$this->color = $color;
}

public function before()
{
echo "<dis style='color: {$this->color}'>";
}

public function after()
{
echo "</div>";
}
}

/**
* 字体大小装饰器实现
* Class SizeDecorator
*/
class SizeDecorator implements Decorator
{
protected $size;

public function __construct($size)
{
$this->size = $size;
}

public function before()
{
echo "<dis style='font-size: {$this->size}px'>**";
}

public function after()
{
echo "</div>";
}
}

//实例化输出类
$echo = new EchoText();
//增加装饰器
$echo->addDecorator(new ColorDecorator('red'));
//增加装饰器
$echo->addDecorator(new SizeDecorator('22'));
//输出https://learnku.com/articles/30034
$echo->Index();

理解 cookie、session、token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
session这对服务器说是一个巨大的开销 , 严重的限制了服务器扩展能力, 比如说我用两个机器组成了一个集群, 小 F 通过机器 A 登录了系统, 那 session id 会保存在机器 A 上, 假设小 F 的下一次请求被转发到机器 B 怎么办?机器 B 可没有小 F 的 session id 啊。有时候会采用一点小伎俩:session sticky , 就是让小 F 的请求一直粘连在机器 A 上, 但是这也不管用, 要是机器 A 挂掉了, 还得转到机器 B 去。那只好做 session 的复制了, 把 session id 在两个机器之间搬来搬去, 快累死了。
小 F 已经登录了系统, 我给他发一个令牌 token, 里边包含了小 F 的 user id, 下一次小 F 再次通过 Http 请求访问我的时候, 把这个 token 通过 Http header 带过来不就可以了。*

不过这和 session id 没有本质区别啊, 任何人都可以可以伪造, 所以我得想点儿办法, 让别人伪造不了。

那就对数据做一个签名吧, 比如说我用 HMAC-SHA256 算法,加上一个只有我才知道的密钥, 对数据做一个签名, 把这个签名和数据一起作为 token, 由于密钥别人不知道, 就无法伪造 token 了。

基于 Token 的身份验证的过程如下:

用户通过用户名和密码发送请求。
程序验证。
程序返回一个签名的 token 给客户端。
客户端储存 token, 并且每次用于每次发送请求。
服务端验证 token 并返回数据。
https://learnku.com/articles/30051

分割字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function mb_str_split($string): array
{
if (is_string($string)) {
return preg_split('/(?<!^)(?!$)/u', $string);
}

return [];
}
>>> mb_str_split('我是谁')
=> [
"我",
"是",
"谁",
]

计算身份证的最后一位验证码

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
/**
* @functionName 计算身份证的最后一位验证码
* @description 根据国家标准GB 11643-1999,前提必须是18位的证件号
*https://github.com/alicfeng/identity-card
* @param string $idBody 证件号码的前17位数字
*
* @return bool|mixed
*/
public static function calculateCode($idBody)
{
if (17 != strlen($idBody)) {
return false;
}

//加权因子
$factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
//校验码对应值
$code = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
$checksum = 0;

foreach (range(0, strlen($idBody) - 1) as $index => $item) {
$checksum += substr($idBody, $index, 1) * $factor[$index];
}

return $code[$checksum % 11];
}

简单工厂模式

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
使用简单工厂模式重新实现达到解耦的目的

将加减乘除解耦,每一个运算用单独的类去写,再用工厂模式实例化出合适的对象,通过多态返回计算器结果,后面如果再加一个平方根运算可以新增一个平方根的类,如果需要修改加法运算,那就只需要修改 OperationAdd 类。


class Operation
{
public $num1;
public $num2;
}

class OperationAdd extends Operation
{
public function getResult()
{
$result = $this->num1 + $this->num2;
return $result;
}
}

class OperationSub extends Operation
{
public function getResult()
{
$result = $this->num1 - $this->num2;
return $result;
}
}

class OperationMul extends Operation
{
public function getResult()
{
$result = $this->num1 * $this->num2;
return $result;
}
}

class OperationDiv extends Operation
{
public function getResult()
{

if ($this->num2 != 0) {
$result = $this->num1 / $this->num2;
} else {
$result = "除数不能为0";
}
return $result;
}
}

class OperationFactory
{
public static function createOperate($symbol)
{
switch ($symbol) {//多态
case '+':
$result = new OperationAdd();
break;
case '-':
$result = new OperationSub();
break;
case '*':
$result = new OperationMul();
break;
case '/':
$result = new OperationDiv();
break;
default:
$result = NULL;
}
return $result;
}
}

$operationFactory = OperationFactory::createOperate('+');
$operationFactory->num1 = 21;
$operationFactory->num2 = 11;

echo $operationFactory->getResult();//32 https://learnku.com/articles/32491

array_reduce 多值化一

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
array_reduce 函数内部实现机制,更类似于如下代码
function array_reduce2($array, $callback, $initial=null){
$acc = $initial;
foreach($array as $a)
$acc = $callback($acc, $a);
return $acc;
}

array_reduce2([2,4],function($carry,$x){
return $carry+$x;
}); // 6
将一个序列函数的嵌套调用组合为 array_reduce 产生的新函数调用
function compose(...$functions){
return array_reduce(
$functions,
function($carry,$function){
return function($x)use($carry,$function){
return $function($carry($x));
};
},
function($x){
return $x;
}
);
}
效果
$compose = compose(
// 加 2
function ($x) {
return $x + 2;
},
// 乘 4
function ($x) {
return $x * 4;
}
);

$res = $compose(3); // (3+2)*4 = 20 https://learnku.com/articles/32417

过滤空白字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$s="  空白字符";
>>> strlen($s)
=> 18
说明前2个字符长度为6,是全角空格
>>> json_encode([' '])
=> "["\u3000"]"
>>> preg_replace("/\s+/u", "",$s)
=> "空白字符"
>>> preg_replace("/\s+/", "",$s)
=> "  空白字符"
>>> preg_replace("/\x{3000}+/u",'',$s)
=> "空白字符"
>>> trim($s,' ')
=> "空白字符"

curl 上传图片

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
42
43
<form action="http://myServerURL" method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="Submit">
</form>

$fields = [
'image' => new \CurlFile(realpath($_FILES['img']['tmp_name']) )
];
curl_setopt($resource, CURLOPT_POSTFIELDS, $fields);
https://stackoverflow.com/questions/21905942/posting-raw-image-data-as-multipart-form-data-in-curl
https://www.cnblogs.com/vanwee/p/10341298.html
function upload($file, $multipart = true) {
$cookie = ''; // 微博cookie
$url = 'http://picupload.service.weibo.com/interface/pic_upload.php'
.'?mime=image%2Fjpeg&data=base64&url=0&markpos=1&logo=&nick=0&marks=1&app=miniblog';
if($multipart) {
$url .= '&cb=http://weibo.com/aj/static/upimgback.html?_wv=5&callback=STK_ijax_'.time();
if (class_exists('CURLFile')) { // php 5.5
$post['pic1'] = new CURLFile(realpath($file));
} else {
$post['pic1'] = '@'.realpath($file);
}
} else {
$post['b64_data'] = base64_encode(file_get_contents($file));
}
// Curl提交 https://cloud.tencent.com/developer/article/1147512
$ch = curl_init($url);
curl_setopt_array($ch, array(
CURLOPT_POST => true,
CURLOPT_VERBOSE => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => array("Cookie: $cookie"),
CURLOPT_POSTFIELDS => $post,
));
$output = curl_exec($ch);
curl_close($ch);
// 正则表达式提取返回结果中的json数据
preg_match('/({.*)/i', $output, $match);
if(!isset($match[1])) return '';
return $match[1];
}
upload('mypic.jpg', true); // multipart方式上传
upload('http://www.mysite.cn/mypic.jpg', false); // 非multipart方式(base64)上传

快手短视频无水印

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
composer require guzzlehttp/guzzle
https://github.com/leixiaokou/kuaishou
require __DIR__.'/vendor/autoload.php';
$url = $_GET['url'] ?? '';
$headers = [
'Connection' => 'keep-alive',
'User-Agent'=>'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16D57 Version/12.0 Safari/604.1'
];
$client = new \GuzzleHttp\Client(['timeout' => 2, 'headers' => $headers, 'http_errors' => false,]);
$data['headers'] = $headers;
$jar = new \GuzzleHttp\Cookie\CookieJar;
$data['cookies'] = $jar;
$response = $client->request('GET', $url, $data);

$body = $response->getBody();
if ($body instanceof \GuzzleHttp\Psr7\Stream) {
$body = $body->getContents();
}
$result = htmlspecialchars_decode($body);
$pattern = '#"srcNoMark":"(.*?)"#';
preg_match($pattern, $result, $match);
$data = [
'video_src' => '',
'cover_image' => '',
];
if (empty($match[1])) {
return false;
}
$data['video_src'] = $match[1];
$pattern = '#"poster":"(.*?)"#';
preg_match($pattern, $result, $match);
if (!empty($match[1])) {
$data['cover_image'] = $match[1];
}
echo json_encode($data);
简单易用的获取无水印短视频播放地址,比如抖音无水印,快手无水印,微视无水印接口服务
https://v.taokeduo.cn/api?uid=应用uid&url=短视频平台的分享链接
例如:https://v.taokeduo.cn/api?uid=06217e0cdb27d0200e3a4690f8907fd2&url=https://v.douyin.com/vc4bUC/

根据另外一个数组去重

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
42

function uniqueArray($arr, $plugins)
{
if (!$arr){
return [];
}
$tmp = [];//相同的mid放一起
foreach ($arr as $key => $v) {
if (isset($tmp[$v['mid']])) {
$tmp[$v['mid']][] = $v;
} else {
$tmp[$v['mid']] = [$v];
}
}
$data = [];
foreach ($tmp as $key => &$value) {
$has = 0;
foreach ($value as $k => $v) {
if ($v['is_top'] == 1) {//置顶的优先取
$data[] = $v;
$has = 1;
break;
}
}
if (!$has) {
foreach ($value as $k => &$v) {
$v['sort'] = array_search($v['plugin'], $plugins);//获取在另外一个数组的顺序
}
$value = arraySort($value,'sort',SORT_ASC);
$data[] = $value[0];//获取去重后的第一个
}
}
return $data;
}
function arraySort($array, $key, $sort = SORT_DESC) {
$keysValue = [];
foreach ($array as $k => $v) {
$keysValue[$k] = $v[$key];
}
array_multisort($keysValue, $sort, $array);
return $array;
}

获取字符串内的链接

1
preg_match_all('/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i',strip_tags($content),$m);

TCP/IP 协议

PHP 设计模式

php错误和异常

PHP 操作 Redis

Redis 基础

PHP地理位置

用 PHP 写一个微波炉

Laravel 开发的一款文档管理系统

PHP 开发量化交易的工具,模拟交易所 API 数据

再谈PHP错误与异常处理

MemCached 编译安装部署

TCP 的连接建立与关闭状态及数据传输通信过程

PhpSpreadsheet 小教程https://github.com/PHPOffice/PhpSpreadsheet

理解 cookie、session、token

如何快速接入 GitHub 登陆

获取国内财经门户网股票数据composer包https://learnku.com/laravel/t/30040

面向对象的三大特性

PHP 经典面试问题解决过程:上台阶问题

Tideways、xhprof 和 xhgui 打造 PHP 非侵入式监控平台

文档协作翻译插件

Slim 框架使用orm

PHP 基础算题之动态规划leetcode

PC 端微信扫码支付全过程 — easywechat + Laravel 5.8

php函数列表

PHP 面向对象设计的五个基准原则

扫二维码登录过程

使用 TUS 协议来实现大文件的断点续传

防爬虫机器人盗文

Nginx之旅-环境确认及Nginx的安装

省市县数据扩展包

代理与反向代理、负载均衡和缓存

多版本 PHP 共存phpbrew

单文件 PHP 缩略图库

命令注入绕过姿势

PHP论坛

搭建jetbrains激活服务器

phpstorm的奇技淫巧http://phpstorm.tips/tips

PHP设计模式

开发者知识体系

JWT 完整使用详解

PHP笔记

CTF 中的 PHP 知识汇总

聊聊服务稳定性保障这些事

个人开发者在线收款的服务

加密货币市场实时价格脚本

GameBoy开发

An example of all the Imagick functions

静态扫描为你的PHP项目上线保驾护航

静态扫描为你的PHP项目上线保驾护航

PHP技术进阶

2019 PHP 安全指南

php之道

2核1G内存的服务器能承载多少人访问

深入理解IEEE754的64位双精度

自动拉取github上的仓库

EasyDingTalk

尝试 leetcode 题目

PHP常用函数整理

笔记分享 | 简书2GitHub

微信支付单文件版

无限级分类、多级菜单、省份城市

用PHP玩转进程之二 — 多进程PHPServer

unid是一个可以生成唯一ID的php扩展

秒杀系统的设计

使用 Vim 搭建 PHP 开发环境

docker 构建 php 开发环境

Debug bar for PHP

过滤敏感词汇

COOKIE和SESSION机制详解

webupload 实现分片上传视频