​​​​ laravel 笔记 | 苏生不惑的博客

laravel 笔记

事件的使用Laravel 启动流程分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
php artisan make:event UserRegistered

php artisan make:listener SendWelcomeMail --event=UserRegistered

php artisan make:listener UpdateReferrer --event=UserRegistered
protected $listen = [
UserRegistered::class => [
SendWelcomeMail::class,
UpdateReferrer::class,
]
];
https://learnku.com/articles/26152
class User extends Model
{
public static function boot()
{
parent::boot();

static::created(function (User $user) {
\event(new UserRegistered($user));
});
}
}

cron timeout

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
crontab定时启动任务,flock保证互斥,timeout设置超时以及报警脚本
flock -xn /tmp/lock /path/to/php /path/to/file
让我们再来重放一下故障场景:假如上一分钟的 A 请求还没退出,下一分钟的 B 请求也启动了,那么 B 请求会发现 A 请求还没有释放锁,于是它不会执行。
假如因为某些无法预知的原因,导致脚本不能正常结束请求,进而导致不能正常释放锁,那么后续所有其它的 CD 等请求也都无法执行了,如何避免?答案是 timeout,它实现了超时控制机制:
https://huoding.com/2016/12/12/573 https://juejin.im/post/5bb4fc0de51d450e597b6d51
timeout -s SIGINT 100 flock -xn /tmp/lock /path/to/php /path/to/file
让我们再来重放一下故障场景:假如上一分钟的 A 请求还没退出,下一分钟的 B 请求也启动了,那么 B 请求会发现 A 的请求还没有释放锁,于是它不会执行,不过下下分钟的 C 请求肯定能执行,因为在这之前,A 请求已经因为超时被 timeout 干掉了。

# 超时发送-9信号,超时执行后面的脚本输出failed
timeout -s 9 5 sleep 20 || echo 'failed'
# 未超时执行后面的脚本输出success
timeout -s 9 10 sleep 5 && echo 'success'
需要注意的点

timeout 正常结束的返回码是0
timeout 超时kill结束的返回码是124

假设6分钟执行一次且互斥非阻塞,超时10分钟且超时执行报警脚本,则最终crontab文件如下
*/6 * * * * cd /data/projec && flock -xn ./task.lock -c 'timeout -9 600 sh task.sh || sh alarm.sh'
需要注意的点
如果task.sh启动了子进程进行处理,则需要在task.sh的末尾加上wait命令等待全部子进程完成才结束,否则timeout无效
cd /data/project
nohub python3 main.py >main.log 2>&1 &
wait
https://juejin.im/post/5bb4fc0de51d450e597b6d51

杀死过期进程

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
    public function fire()
{
try {
$pids = $this->getPids(["php", "shell_lock"]);
foreach ($pids as $pid) {
$startTime = $this->getProcessStartTime($pid);
$this->ifKill($startTime, $pid);
}
} catch (Exception $e) {
Log::error("[" . ClientIp::getIp() . "][LONG TIME KILLER]: " . $e->getMessage() . "\r\n" . $e->getTraceAsString());
}
}

protected function getPids($arr)
{
$command = "ps aux | grep -v grep ";
foreach ($arr as $word) {
$command .= "| grep {$word}";
}
$command .= " | awk '{print $2}'";
$result = shell_exec($command);
$result = trim($result);
if (empty($result)) {
return false;
}
return explode("\n", $result);
}

protected function getProcessStartTime($pid)
{
/*ps -p 18994 -o lstart
STARTED
Thu Mar 21 10:25:38 2019
*/
$command = "ps -p {$pid} -o lstart";
$result = shell_exec($command);
$result = trim(str_replace("STARTED", "", $result));
if (empty($result)) {
return false;
}
$ctime = \Carbon\Carbon::createFromFormat("D M j H:i:s Y", $result);
return $ctime;
}

protected function ifKill(Carbon $ctime, $pid)
{
if ($ctime->lt(\Carbon\Carbon::now()->addHours(-1))) {
$cmd = file_get_contents("/proc/{$pid}/cmdline");
$logString = Carbon::now()->toDateTimeString() . "]Killing pid {$pid} started at " . $ctime->toDateTimeString() . " lasts for more than 1 hour, start script is `" . $cmd . "`";
\Log::info($logString);
shell_exec("kill -9 {$pid}");
}
}

for pid in $(ps aux|grep 'artisan ' | grep -v grep | awk '{print $2}'); do kill -9 $pid; done
ps -ef | pgrep -f "search" | xargs kill -9

每个文章的前10条评论一同查询出来

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
$posts = Post::paginate(15);

$postIds = $posts->pluck('id')->all();

//找出符合条件的 comments ,同时定义 @post, @rank 变量,这里没有用 all,get 等函数,此时并不会执行 SQL 语句。
$sub = Comment::whereIn('post_id',$postIds)->select(DB::raw('*,@post := NULL ,@rank := 0'))->orderBy('post_id');

//把上面构造的 sql 查询作为子表进行查询,根据 post_id 进行分区的同时 @rank 变量不断+1
$sub2 = DB::table( DB::raw("({$sub->toSql()}) as b") )
->mergeBindings($sub->getQuery())
->select(DB::raw('b.*,IF (
@post = b.post_id ,@rank :=@rank + 1 ,@rank := 1
) AS rank,
@post := b.post_id'));

//取出符合条件的前10条comment
$commentIds = DB::table( DB::raw("({$sub2->toSql()}) as c") )
->mergeBindings($sub2)
->where('rank','<',11)->select('c.id')->pluck('id')->toArray();

$comments = Comment::whereIn('id',$commentIds)->get();

$posts = $posts->each(function ($item, $key) use ($comments) {
$item->comments = $comments->where('post_id',$item->id);
});
会产生三条sql https://learnku.com/articles/20315
select * from `posts` limit 15 offset 0;

select `c`.`id` from (select b.*,IF (
@post = b.post_id ,@rank :=@rank + 1 ,@rank := 1
) AS rank,
@post := b.post_id from (select *,@post := NULL ,@rank := 0 from `comments` where `post_id` in ('2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16') order by `post_id` asc) as b) as c where `rank` < '11';

select * from `comments` where `id` in ('180', '589', '590', '3736');

Laravel 集合

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
https://learnku.com/laravel/t/26110

$users = User::all();
$users->contains('name', 'Chasity Tillman');
//true

$collection = collect(['name' => 'John', 'age' => 23]);
$collection->contains('Jane');
//false

$collection = collect([1, 2, 3, 4, 5]);
$collection->contains(function ($key, $value) {
return $value <= 5;
//true
});
$users = User::all();
$youngsters = $users->filter(function ($value, $key) {
return $value->age < 35;
});

$youngsters->all();
// 所有年龄小于 35 的用户
$movies = collect([
[
'name' => 'Back To The Future',
'releases' => [1985, 1989, 1990]
],
[
'name' => 'Fast and Furious',
'releases' => [2001, 2003, 2006, 2009, 2011, 2013, 2015, 2017]
],
[
'name' => 'Speed',
'releases' => [1994]
]
]);

$mostReleases = $movies->sortByDesc(function ($movie, $key) {
return count($movie['releases']);
});

$mostReleases->toArray();
//列出以上映总数降序排序的电影

dd($mostReleases->values()->toArray());
/*
列出以上映总数降序排序的电影并重置键值
*/
$movies = collect([
['name' => 'Back To the Future', 'genre' => 'scifi', 'rating' => 8],
['name' => 'The Matrix', 'genre' => 'fantasy', 'rating' => 9],
['name' => 'The Croods', 'genre' => 'animation', 'rating' => 8],
['name' => 'Zootopia', 'genre' => 'animation', 'rating' => 4],
['name' => 'The Jungle Book', 'genre' => 'fantasy', 'rating' => 5],
]);

$genre = $movies->groupBy('genre');
/*
[
"scifi" => [
["name" => "Back To the Future", "genre" => "scifi", "rating" => 8,],
],
"fantasy" => [
["name" => "The Matrix", "genre" => "fantasy", "rating" => 9,],
["name" => "The Jungle Book", "genre" => "fantasy", "rating" => 5, ],
],
"animation" => [
["name" => "The Croods", "genre" => "animation", "rating" => 8,],
["name" => "Zootopia", "genre" => "animation", "rating" => 4, ],
],
]
*/

$rating = $movies->groupBy(function ($movie, $key) {
return $movie['rating'];
});

/*
[
8 => [
["name" => "Back To the Future", "genre" => "scifi", "rating" => 8,],
["name" => "The Croods", "genre" => "animation", "rating" => 8,],
],
9 => [
["name" => "The Matrix", "genre" => "fantasy", "rating" => 9,],
],
4 => [
["name" => "Zootopia","genre" => "animation", "rating" => 4,],
],
5 => [
["name" => "The Jungle Book","genre" => "fantasy","rating" => 5,],
],
]
*/
}
$list = collect([
'Albert', 'Ben', 'Charles', 'Dan', 'Eric', 'Xavier', 'Yuri', 'Zane'
]);

//获取前两个名字
$firstTwo = $list->take(2);
//['Albert', 'Ben']

//获取最后两个名字
$lastTwo = $list->take(-2);
//['Yuri', 'Zane']
$list = collect([
'Albert', 'Ben', 'Charles', 'Dan', 'Eric', 'Xavier', 'Yuri', 'Zane'
]);

$chunks = $list->chunk(3);
$chunks->toArray();
/*
[
["Albert", "Ben", "Charles",],
[3 => "Dan", 4 => "Eric", 5 => "Xavier",],
[6 => "Yuri", 7 => "Zane",],
]
*/
$names = collect([
'Albert', 'Ben', 'Charles', 'Dan', 'Eric', 'Xavier', 'Yuri', 'Zane'
]);

$names->transform(function ($name, $key) {
return strlen($name);
});

$names->toArray();
//[6, 3, 7, 3, 4, 6, 4, 4,]
$names = collect([
'Albert', 'Ben', 'Charles', 'Dan', 'Eric', 'Xavier', 'Yuri', 'Zane'
]);

$names->transform(function ($name, $key) {
return strlen($name);
});

$names->toArray();
//[6, 3, 7, 3, 4, 6, 4, 4,]

队列执行失败反复执行

1
2
3
4
5
6
7
由于报错或者什么原因队列执行失败,但是队列的 attempts一直为1,可以手动throw
try{

}catch (\Throwable $e) {
throw new \Exception("queue fail");
}
php artisan xx --tries=2

实现Schemaless

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
mysql> CREATE TABLE users (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
created_at timestamp null,
updated_at timestamp null,
data JSON NOT NULL,
PRIMARY KEY(id)
);

mysql> ALTER TABLE users add name VARCHAR(100) AS
(JSON_UNQUOTE(JSON_EXTRACT(data, '$.name'))) AFTER id;

mysql> ALTER TABLE users add address VARCHAR(100) AS
(JSON_UNQUOTE(JSON_EXTRACT(data, '$.address'))) AFTER name;

mysql> ALTER TABLE users add level INT UNSIGNED AS
(JSON_EXTRACT(data, '$.level')) AFTER name;
然后是核心代码 Schemaless.php,以 trait 的方式实现:https://huoding.com/2017/01/14/590

<?php

namespace App;

trait Schemaless
{
public function getDirty()
{
$dirty = collect(parent::getDirty());

$keys = $dirty->keys()->map(function($key) {
if (in_array($key, $this->virtual)) {
$key = $this->getDataColumn() . '->' . $key;
}

return $key;
});

return $keys->combine($dirty)->all();
}

public function save(array $options = [])
{
if (!$this->exists) {
$attributes = collect($this->getAttributes());

$virtual = $attributes->only($this->virtual);

$attributes = $attributes->diffKeys($virtual)->merge([
$this->getDataColumn() => json_encode($virtual->all()),
]);

$this->setRawAttributes($attributes->all());
}

return parent::save($options);
}

public function getDataColumn()
{
static $column;

if ($column === null) {
$column = defined('static::DATA') ? static::DATA : 'data';
}

return $column;
}
}
接着是 Model 实现 User.php,里面激活了 schemaless,并设置了虚拟字段:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
use Schemaless;

protected $virtual = ['name', 'address', 'level'];

protected $hidden = ['data'];
}
最后是 Controller 实现 UsersController.php,里面演示了如何创建和修改:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\User;

class UsersController extends Controller
{
public function __construct(User $user)
{
$this->user = $user;
}

public function store()
{
$user = $this->user;

$user->name = '老王';
$user->address = '东北';
$user->level = 1;
$user->save();
}

public function update()
{
$user = $this->user->find(1);

$user->address = '北京';
$user->save();
}
}

PhpSpreadsheet 导出图片到 Excel

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
public function export($data)
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
//设置sheet的名字 两种方法 https://learnku.com/articles/26965
$sheet->setTitle('phpspreadsheet——demo');
$spreadsheet->getActiveSheet()->setTitle('Hello');
//设置第一行小标题
$k = 1;
$sheet->setCellValue('A' . $k, '问题');
$sheet->setCellValue('B' . $k, '选项');
$sheet->setCellValue('C' . $k, '答案');
$sheet->setCellValue('D' . $k, '图片');

// 设置个表格宽度
$spreadsheet->getActiveSheet()->getColumnDimension('A')->setWidth(16);
$spreadsheet->getActiveSheet()->getColumnDimension('B')->setWidth(80);
$spreadsheet->getActiveSheet()->getColumnDimension('C')->setWidth(15);
$spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(20);

// 垂直居中
$spreadsheet->getActiveSheet()->getStyle('A')->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER);
$spreadsheet->getActiveSheet()->getStyle('B')->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER);
$spreadsheet->getActiveSheet()->getStyle('C')->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER);
$spreadsheet->getActiveSheet()->getStyle('D')->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER);

$info = $data;
// 设置A单元格的宽度 同理设置每个
$spreadsheet->getActiveSheet()->getColumnDimension('A')->setWidth(20);
// 设置第三行的高度
$spreadsheet->getActiveSheet()->getRowDimension('3')->setRowHeight(50);
// A1水平居中
$styleArray = [
'alignment' => [
'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
],
];
$sheet->getStyle('A1')->applyFromArray($styleArray);
// 将A3到D4合并成一个单元格
$spreadsheet->getActiveSheet()->mergeCells('A3:D4');
// 拆分合并单元格
$spreadsheet->getActiveSheet()->unmergeCells('A3:D4');
// 将A2到D8表格边框 改变为红色
$styleArray = [
'borders' => [
'outline' => [
'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THICK,
'color' => ['argb' => 'FFFF0000'],
],
],
];
// $sheet->getStyle('A2:E8')->applyFromArray($styleArray);
// 设置超链接
// $sheet->setCellValue('D6', 'www.baidu.com');
// $spreadsheet->getActiveSheet()->setCellValue('E6', 'www.baidu.com');
// 循环赋值
$k = 2;
foreach ($info as $key => $value) {
$sheet->setCellValue('A' . $k, $value['question']);
$sheet->setCellValue('B' . $k, $value['question_options']);
$sheet->setCellValue('C' . $k, $value['answer']);

$img = self::curlGet($value['img']);
$dir = public_path('/temp/image/');
$file_info = pathinfo($value['img']);
if (!empty($file_info['basename'])) { //过滤非文件类型
$basename = $file_info['basename'];
is_dir($dir) OR mkdir($dir, 0777, true); //进行检测文件是否存在
file_put_contents($dir . $basename, $img);

$drawing[$k] = new Drawing();
$drawing[$k]->setName('Logo');
$drawing[$k]->setDescription('Logo');
$drawing[$k]->setPath($dir . $basename);
$drawing[$k]->setWidth(80);
$drawing[$k]->setHeight(80);
$drawing[$k]->setCoordinates('D'.$k);
$drawing[$k]->setOffsetX(12);
$drawing[$k]->setOffsetY(12);
$drawing[$k]->setWorksheet($spreadsheet->getActiveSheet());
} else {
$sheet->setCellValue('D' . $k, '');
}
$sheet->getRowDimension($k)->setRowHeight(80);
$k++;
}
$file_name = date('Y-m-d', time()) . rand(1000, 9999);
// 第一种保存方式
/*$writer = new Xlsx($spreadsheet);
//保存的路径可自行设置
$file_name = '../'.$file_name . ".xlsx";
$writer->save($file_name);*/
// 第二种直接页面上显示下载
$file_name = $file_name . ".xls";
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="' . $file_name . '"');
header('Cache-Control: max-age=0');
$writer = IOFactory::createWriter($spreadsheet, 'Xls');
// 注意createWriter($spreadsheet, 'Xls') 第二个参数首字母必须大写
$writer->save('php://output');
}

public function getClient(){
$client = new Client();
return $client;
}

public static function curlGet($url)
{
$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); // 这个是重点 请求https。
$data = curl_exec($ch);
curl_close($ch);
return $data;
}

Laravel 集合的 “when” 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$hosts = [
['name' => 'Eric Barnes', 'location' => 'USA', 'is_active' => 0],
['name' => 'Jack Fruh', 'location' => 'USA', 'is_active' => 0],
['name' => 'Jacob Bennett', 'location' => 'USA', 'is_active' => 1],
['name' => 'Michael Dyrynda', 'location' => 'AU', 'is_active' => 1],
];
以往,如果在一个查询语句上,还要有进一步的过滤条件,你可能需要这样写:

$inUsa = collect($hosts)->where('location', 'USA');

if (request('retired')) {
$inUsa = $inUsa->filter(function($employee){
return ! $employee['is_active'];
});
}
通过 when 方法,你就可以在一个链式查询中完成所有的筛选过滤:https://learnku.com/laravel/t/26331

$inUsa = collect($hosts)
->where('location', 'USA')
->when(request('retired'), function($collection) {
return $collection->reject(function($employee){
return $employee['is_active'];
});
});

Jwtauth 自定义认证头信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
项目使用的 tymon/jwt-auth 包作为 token 的认证,过程中需要迁移项目,因为之前公司的 token 头部使用自定义的,并且他们还修改了包的头信息。就是下面头部信息。
https://learnku.com/articles/26976
class AuthHeaders implements ParserContract
{
// 下面这两处就是被修改的
protected $header = 'authorization';

protected $prefix = 'bearer';
}
迁移项目过程了,因为拉取了新的包,所以还要去动包的信息,这是极其不合理的行为。所以就在包中尝试找到了更好的解决办法。如果在项目迁移过程中遇到了类似的问题该如何去做呢?这里只提供了我能想到的解决办法。需要在 AppServiceProvider 中加入该方法就可以了。

protected function setAuthHeader()
{
$chain = $this->app['tymon.jwt.parser']->getChain();

$chain[0] = $chain[0]->setHeaderPrefix('项目的 token 前缀')->setHeaderName('项目的头信息 key');

$this->app['tymon.jwt.parser']->setChain($chain);
}

ajax 实现跨域

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
$.ajax({
async: true,
url: "http://172.16.112.3/api.php",
type: "GET",
dataType: "jsonp", // 返回的数据类型,设置为JSONP方式
jsonp: 'callback', //指定一个查询参数名称来覆盖默认的 jsonp 回调参数名 callback
jsonpCallback: 'handleResponse', //设置回调函数名
success: function (response, status, xhr) {
console.log('状态为:' + status + ',状态是:' + xhr.statusText);
console.log(response);
}
});
原生 js 实现跨域

window.onload = function () {
function jsonp(obj) {
//定义一个处理Jsonp返回数据的回调函数
window["callback"] = function (object) {
obj.success(object);
}
var script = document.createElement("script");
//组合请求URL
script.src = obj.url + "?callback=callback";
for (key in obj.data) {
script.src += "&" + key + "=" + obj.data[key];
}
//将创建的新节点添加到BOM树上
document.getElementsByTagName("body")[0].appendChild(script);
}
jsonp({
url: "http://172.16.112.3/api.php",
success: function (obj) {
console.log(obj);
}
});
}

PDO 防止 sql 注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
setAttribute() 这一行是强制性的,它会告诉 PDO 禁用模拟预处理语句,并使用 real parepared statements 。这可以确保 SQL 语句和相应的值在传递到 mysql 服务器之前是不会被 PHP 解析的(禁止了所有可能的恶意 SQL 注入攻击)。虽然你可以配置文件中设置 字符集的属性 (charset=utf8),但是需要格外注意的是,老版本的 PHP( < 5.3.6)在 DSN 中是忽略字符参数的。
我们来看一段完整的代码使用实例:https://learnku.com/articles/27000#topnav

$user=$_POST['user']; $pass=$_POST['pass'];
$dbh = new \PDO("mysql:host=localhost; dbname=zz", "root", "root");
$dbh->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
//禁用prepared statements的仿真效果
// $dbh->exec ("set names 'utf8'");
$sql="select * from test where user=? and pass=?";
$stmt = $dbh->prepare($sql);
$exeres = $stmt->execute(array($user, $pass));
if ($exeres) {
//while条件为真时,输出$row,
while
($row = $stmt->fetch(\PDO::FETCH_ASSOC)){
print_r($row);die();
} //失败输出登录失败
print_r("登录失败");die();
}
当调用 prepare () 时,查询语句已经发送给了数据库服务器,此时只有占位符?发送过去,没有用户提交的数据;当调用到 execute () 时,用户提交过来的值才会传送给数据库,他们是分开传送的,两者独立的,SQL 攻击者没有一点机会。

Eloquent 关系中使用 orderBy()

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
public function productsByName()
{
return $this->hasMany(Product::class)->orderBy('name');
}
$products = Product::whereDate('created_at', '2018-01-31')->get();
$products = Product::whereMonth('created_at', '12')->get();
$products = Product::whereDay('created_at', '31')->get();
$products = Product::whereYear('created_at', date('Y'))->get();
$products = Product::whereTime('created_at', '=', '14:13:58')->get();
Route::group(['prefix' => 'account', 'as' => 'account.'], function() {
Route::get('login', 'AccountController@login');
Route::get('register', 'AccountController@register');
Route::group(['middleware' => 'auth'], function() {
Route::get('edit', 'AccountController@edit');
});
});
恢复多个软删除https://learnku.com/articles/26673

如果记录使用了软删除,那么你就可以一次恢复多条软删除记录。

Post::withTrashed()->where('author_id', 1)->restore();
// Author -> hasMany(Book::class)
$authors = Author::has('books', '>', 5)->get();
$users = User::all(['id', 'name', 'email']);
在执行 Eloqument 查询后,你可以使用 map() 来修改行。

$users = User::where('role_id', 1)->get()->map(function (User $user) {
$user->some_column = some_function($user);
return $user;
});
当一个关系被调用时,如果它不存在,则会出现致命的错误,例如 $post->user->name ,可以使用 withDefault() 来避免。

/** 获取文章作者 */
public function user()
{
return $this->belongsTo('App\User')->withDefault();
}
$users = App\Book::with('author:id,name')->get();

开闭原则

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
 Controller 类,只接受用户输入,返回输出,不需要具体处理背后的事情。当需要表单验证的时候,注入相应的 Request 类。当需要数据操作时,注入相应的 Repository 或 Service 或 Factory。

  Model 类,将修改器,访问器定义在 trait 然后 use 进来。数据操作逻辑分离成 Repository。

  Helper.php 全局函数,将同类型的 helper 分离成单独的文件,使其高内聚。然后在 Helper.php 中引入
所有具体的类都需要实现 Interface 中定义的方法。
在 Factory 中实例化具体的类。
在 Controller 中使用 Factory。

首先创建一个 PaymentInterface,任何支付方式都必须实现此接口中定义的方法。

interface PaymentInterface {
public function pay();
}
接着,创建两个实现 PaymentInterface 的具体类。

class Alipay implements PaymentInterface {
public function pay() {
// 支付宝支付具体代码
}
}
class Wechat implements PaymentInterface {
public function pay() {
// 微信支付具体代码
}
}
然后,创建一个支付工厂,此工厂用来实例化具体的支付类

class PeymentFactory
{
public function init($payType) {
switch ($payType) {
case 'Alipay':
return new Alipay();
case 'Wechat':
return new Wechat();
}
throw new Exception('未知支付方式');
}
}
最后,在控制器中注入此 Factory

public function pay (Request $request, PaymentFactory $paymentFactory) {
$payment = $paymentFactory->init($request->payType);
$payment->pay();
}

内存占用高,速度就会提高

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
class a{
function __construct(){
echo 'class a __construct'."\n";
}

function a1(){
echo 'a1'."\n";
}

function a2(){
echo 'a2'."\n";
}

function a3(){
echo 'a3'."\n";
}
}
//https://learnku.com/laravel/t/27033
class b{
protected $cache;

function __construct(){
echo 'class b __construct'."\n";
}

function b1($tags='tags'){
return new a();
}

function b2($tags='tags'){
static $cache=[];
if(isset($cache[$tags])) return $cache[$tags];
return $cache[$tags]=new a();
}

function b3($tags='tags'){
if(isset($this->cache[$tags])) return $this->cache[$tags];
return $this->cache[$tags]=new a();
}
}

/*内存计算*/
$start = memory_get_usage();
$b=new b();
$b->b1()->a1();
$b->b1()->a2();
$b->b1()->a3();
/*内存计算*/
$end = memory_get_usage();
echo ($end-$start)."\n";

/*内存计算*/
$start = memory_get_usage();
$b=new b();
$b->b2()->a1();
$b->b2()->a2();
$b->b2()->a3();
/*内存计算*/
$end = memory_get_usage();
echo ($end-$start)."\n";

/*内存计算*/
$start = memory_get_usage();
$b=new b();
$b->b3()->a1();
$b->b3()->a2();
$b->b3()->a3();
/*内存计算*/
$end = memory_get_usage();
echo ($end-$start)."\n";
内存占用高,速度就会提高,反之,就速度就会降低,上面那个 b1 和 b2 后面应该加一个 unset ($b), 内存计算才准确吧
这应该就是连接数据库的时候为什么推荐使用短链接而不是长链接的原因吧,长连接占用内存高,资源多吧,不即时释放

调用第二个 b1 的时候,第一个 b1 创建出来的 a 就被销毁了啊,就不占内存了啊。
这题还有一个目的,在同一个进程下想减少 new 的次数,结果画蛇添足了

关联模型字段取别名查询不出数据

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
https://learnku.com/articles/22489#topnav
laravel 自带的 ORM 是个神器,针对这种有关系的数据,完全可以使用关系模型,既简单又实用,由于这里只有一个数据表,关系也存在于同一张表,所以可以直接使用自关联,将两个关系定义在同一个 Model 里面:

......
public function parent() {
return $this->hasOne($this, 'id', 'parent_id');
}

public function children() {
return $this->hasMany($this, 'parent_id', 'id');
}
关系定义里面的参数也可以这样写:

......
public function parent() {
return $this->hasOne(get_class($this), $this->getKeyName(), 'parent_id');
}

public function children() {
return $this->hasMany(get_class($this), 'parent_id', $this->getKeyName());
}
查询包含子菜单的数据

return Admin_menu::with(['children' => function($query){
$query->select('id', 'title', 'parent_id');
}])
->select('id', 'title', 'parent_id')
->get();
where in (array)这里的 array 是依赖主键的名称的,在关联查询的时候,已经定义了 id = [3,4,5,6...],但是我们最后给 id 取了别名,变成 MaindId,所以找不到名为 id 的数组。
如果真是这样,我们试着再给它加上 id,让它能够找到名为 id 的数组
\DB::connection()->enableQueryLog(); // 开启查询日志
$menus = Admin_menu::with(['children' => function($query){
$query->select('id', 'title', 'parent_id');
}])
->select('id', 'id as MainId', 'title', 'parent_id')
->get();
foreach (\DB::getQueryLog() as $sql) {
dump($sql['query']);
}
依赖关联主键 localKey 的查询,不能缺少相应的字段,也就是说 select 应该包含对应 localKey,如果要取别名应该额外添加,形如:

select('id', 'id as MainId', 'title', 'parent_id', 'parent_id as extraId')
public function test() {
$menus = Admin_menu::with(['parent' => function($query){
$query->select('id', 'title', 'parent_id');
}])
->select('id', 'title', 'parent_id')
->get();
return $this->transformer($menus);
}

protected function transformer($items) {
$data = [];
foreach ($items ?? [] as $item) {
$data[] = [
'mainId' => $item->id,
'title' => $item->title,
'parent_id' => $item->parent_id,
'children' => $item->children,
];
}
return $data;
}

敏感词过滤算法

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
//
https://juejin.im/post/5cb56372f265da037d4fa133
class TreeMap
{
public $data; // 节点字符
public $children = []; // 存放子节点引用(因为有任意个子节点,所以靠数组来存储)
public $isEndingChar = false; // 是否是字符串结束字符

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

class TrieTree
{
/**
* 敏感词数组
*
* @var array
* @author qpf
*/
public $trieTreeMap = array();

public function __construct()
{
$this->trieTreeMap = new TreeMap('/');
}

/**
* 获取敏感词Map
*
* @return array
* @author qpf
*/
public function getTreeMap()
{
return $this->trieTreeMap;
}

/**
* 添加敏感词
*
* @param array $txtWords
* @author qpf
*/
public function addWords(array $wordsList)
{
foreach ($wordsList as $words) {
$trieTreeMap = $this->trieTreeMap;
$len = mb_strlen($words);
for ($i = 0; $i < $len; $i++) {
$word = mb_substr($words, $i, 1);
if(!isset($trieTreeMap->children[$word])){
$newNode = new TreeMap($word);
$trieTreeMap->children[$word] = $newNode;
}
$trieTreeMap = $trieTreeMap->children[$word];
}
$trieTreeMap->isEndingChar = true;
}
}

/**
* 查找对应敏感词
*
* @param string $txt
* @return array
* @author qpf
*/
public function search($txt)
{
$wordsList = array();
$txtLength = mb_strlen($txt);
for ($i = 0; $i < $txtLength; $i++) {
$wordLength = $this->checkWord($txt, $i, $txtLength);
if($wordLength > 0) {
echo $wordLength;
$words = mb_substr($txt, $i, $wordLength);
$wordsList[] = $words;
$i += $wordLength - 1;
}
}
return $wordsList;
}

/**
* 敏感词检测
*
* @param $txt
* @param $beginIndex
* @param $length
* @return int
*/
private function checkWord($txt, $beginIndex, $length)
{
$flag = false;
$wordLength = 0;
$trieTree = $this->trieTreeMap; //获取敏感词树
for ($i = $beginIndex; $i < $length; $i++) {
$word = mb_substr($txt, $i, 1); //检验单个字
if (!isset($trieTree->children[$word])) { //如果树中不存在,结束
break;
}
//如果存在
$wordLength++;
$trieTree = $trieTree->children[$word];
if ($trieTree->isEndingChar === true) {
$flag = true;
break;
}
}
if($beginIndex > 0) {
$flag || $wordLength = 0; //如果$flag == false 赋值$wordLenth为0
}
return $wordLength;
}

}

$data = ['白粉', '白粉人', '白粉人嫩','不该大'];
$wordObj = new TrieTree();
$wordObj->addWords($data);

$txt = "白粉啊,白粉人,我不该大啊";
$words = $wordObj->search($txt);
var_dump($words);die;

curl https

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
curl请求https,需要设置ipv4访问,ipv6的话会导致解析不了域名,php代码如下:
//请求https需要用ipv4
$curl->setOption(CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
/**
* curl POST
*
* @param string url
* @param array 数据
* @param int 请求超时时间
* @param bool HTTPS时是否进行严格认证
* @return string
*/
function curlPost($url, $data = array(), $timeout = 30, $CA = true){

$cacert = getcwd() . '/cacert.pem'; //CA根证书https://segmentfault.com/a/1190000005856334
$SSL = substr($url, 0, 8) == "https://" ? true : false;

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout-2);
if ($SSL && $CA) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); // 只信任CA颁布的证书
curl_setopt($ch, CURLOPT_CAINFO, $cacert); // CA根证书(用来验证的网站证书是否是CA颁布)
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 检查证书中是否设置域名,并且是否与提供的主机名匹配
} else if ($SSL && !$CA) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 信任任何证书
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1); // 检查证书中是否设置域名
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); //避免data数据过长问题
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
//curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); //data with URLEncode

$ret = curl_exec($ch);
//var_dump(curl_error($ch)); //查看报错信息

curl_close($ch);
return $ret;
}

Laravel-Excel3.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
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
composer require maatwebsite/excel
app/app 注册服务及门面:

'providers' => [
Maatwebsite\Excel\ExcelServiceProvider::class,
]
'aliases' => [
'Excel' => Maatwebsite\Excel\Facades\Excel::class,
]
php artisan vendor:publish

创建 app/exports/export.php

<?php
namespace App\Exports;

use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings; //设置标题
use Maatwebsite\Excel\Concerns\ShouldAutoSize; //自动单元格尺寸
use PhpOffice\PhpSpreadsheet\Style\NumberFormat; //设置单元格数据格式
use Maatwebsite\Excel\Concerns\WithColumnFormatting; //设置列格式
use Maatwebsite\Excel\Concerns\WithStrictNullComparison; //为空时零填充

class InvoicesExport implements FromCollection,WithHeadings,WithStrictNullComparison,WithColumnFormatting,ShouldAutoSize
{
protected $data;

protected $header;
/*
* Excel类的构造函数
*/
public function __construct($data, $header)
{
$this->data = $data;
$this->header = $header;
}

//导出数据逻辑
public function collection()
{
return $this->data;
}
//首行标题
public function headings(): array
{
return $this->header;
}
//设置列格式
public function columnFormats(): array
{
return [
//'E' => NumberFormat::FORMAT_DATE_XLSX14,
];
}
}
控制器中使用:

public function export(){
$data = Post::get();
$header = [ 'ID',
'姓名',
'年龄',
'性别',
'创建时间',
'修改时间'];
return \Excel::download(new InvoicesExport($data, $header), "测试导出.xls");
}
创建 app/Exports/Import.php

<?php
namespace App\Exports;

use Maatwebsite\Excel\Concerns\ToArray;

class Import implements ToArray
{
//重新父类实现
public function array(array $array){

return $array;
}

}
控制器使用:https://learnku.com/articles/26940

public function import(Request $request){
$file = $request->file('file');
$data = \Excel::toArray(new Import(), $file);
dd($data);
}

数据填充功能生成中文测试数据

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
$factory->define(App\Product::class, function (Faker\Generator $faker) {
return [
'user_id' => 1,
'name' => $faker->name,
'mobile' => $faker->phoneNumber,
'province' => $faker->state,
'city' => $faker->city,
'area' => $faker->area,
'address' => $faker->streetAddress,
'postcode' => $faker->postcode,
];
});
$factory->define(App\Address::class, function () {
$faker = Faker\Factory::create('zh_CN');//在 config\app.php 文件中加入 faker_locale => 'zh_CN' 就可以实现了
//https://tianyong90.com/2019/03/10/shi-yong-laravel-shu-ju-tian-chong-gong-neng-sheng-cheng-zhong-wen-ce-shi-shu-ju/
return [
'user_id' => 1,
'name' => $faker->name,
'mobile' => $faker->phoneNumber,
'province' => $faker->state,
'city' => $faker->city,
'area' => $faker->area,
'address' => $faker->streetAddress,
'postcode' => $faker->postcode,
];
});

intervention/image 坑

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
// 记录开始时间
$startTimestamp = microtime(true);

$url = 'http://wx.qlogo.cn/mmopen/XxT9TiaJ1ibf06TNRCMjQADS4opDHvQLguLZHpqkRlvuJYZicvJW4iaOalPsKIs0kpZ3F6864ZzibyObYiaucUQSrdp4pFTNDyIpxw/0';

$avatar = \Image::make($url);

// 记录结束时间
$endTimestamp = microtime(true);

info($startTimestamp);
info($endTimestamp);
info($endTimestamp - $startTimestamp);
$startTimestamp = microtime(true);

$client = new \GuzzleHttp\Client();

$url = 'http://wx.qlogo.cn/mmopen/XxT9TiaJ1ibf06TNRCMjQADS4opDHvQLguLZHpqkRlvuJYZicvJW4iaOalPsKIs0kpZ3F6864ZzibyObYiaucUQSrdp4pFTNDyIpxw/0';

$avatarResponse = $client->get($url);

$avatar = \Image::make($avatarResponse->getBody()->getContents());

$endTimestamp = microtime(true);

info($startTimestamp);
info($endTimestamp);
info($endTimestamp - $startTimestamp);
先使用 GuzzleHttp 获取头像,再使用 Image::make($data) 创建头像。

puppeteer 采集异步加载的网页内容

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
$ composer require spatie/browsershot
$ npm i puppeteer --save
https://tianyong90.com/2019/03/10/laravel-zhong-shi-yong-puppeteer-cai-ji-yi-bu-jia-zai-de-wang-ye-nei-rong/#安装
use Spatie\Browsershot\Browsershot;

public function getBodyHtml()
{
$newsUrl = 'https://m.toutiao.com/i6546884151050502660/';

$html = Browsershot::url($newsUrl)
->windowSize(480, 800)
->userAgent('Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Mobile Safari/537.36')
->mobile()
->touch()
->bodyHtml();

\Log::info($html);
}
use Spatie\Browsershot\Browsershot;

public function getBodyHtml()
{
$newsUrl = 'https://m.toutiao.com/i6546884151050502660/';

Browsershot::url($newsUrl)
->windowSize(480, 800)
->userAgent('Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Mobile Safari/537.36')
->mobile()
->touch()
->setDelay(1000)
->save(public_path('images/toutiao.jpg'));
}

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
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
158
1,北京英国标准时间时差是8小时。
2,英国伦敦使用夏令时(夏时制)时时差是7小时。
北京时间为东八区的区时UTC+8,英国伦敦为零时区的区时UTC+0,两者时差为8小时;
当英国为夏时制时(提前一小时),时区变为UTC+1,两者时差为7小时。
英国夏令时的区间:从3月最后一个星期日到10月最后一个星期日。
参考:http://baike.baidu.com/view/37429.htm
时差的计算方法:两个时区标准时间(即时区数)相减就是时差,时区的数值大的时间早。比如中国是东八区(+8),美国东部是西五区(-5),两地的时差是13小时,北京比纽约要早13个小时;如果是美国实行夏令时的时期,相差12小时。
英国与北京时间的时差是8个小时,但其中英国属于夏令时国家,如果是夏令时(每年3月最后一个星期日至10月最后一个星期日)期间,中国和英国的时差是7个小时。

1884年在华盛顿召开的一次国际子午线会议上,规定将全球划分为24个时区(东、西各12个时区),且同时规定了英国为为本初子午线,即零度经线。东1-12区,西1-12区,每个时区横跨经度15度,时间正好是1小时。最后的东、西第12区各跨经度7.5度,以东、西经180度为界。
中国首都北属于东八区,所以对比英国的零度经线,两者之间相差了8个小时。但因为英国实行夏令时(每年3月最后一个星期日至10月最后一个星期日),则英国在天亮早的夏季人为将时间调快一小时可以使人早起早睡,减少照明量。那么英国与北京时间之间时差是7个小时。

//获取美国冬夏时令 //判断当前是否为夏令时,为真返回1,否则为0
function getAmericanSeason($market){
$localzone = date("e");
//date_default_timezone_set('US/Pacific-New');
$timezone = in_array($market, array('LME', 'LIFFE')) ? 'Europe/London' : 'US/Pacific-New';
$season = date("I");
date_default_timezone_set($localzone);
return $season;
}
//判断美国那个时间段是否为夏令时https://gist.github.com/flowerains
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; //返回结果
}
print gmdate("Y-m-d\TH:i:s\Z");
$date_utc = new \DateTime("now", new \DateTimeZone("UTC"));
https://stackoverflow.com/questions/8655515/get-utc-time-in-php/18808833
echo $date_utc->format(\DateTime::RFC850); # Saturday, 18-Apr-15 03:23:46 UTC
echo gmdate ("l d F Y H:i:s")." GMT"; //输出: Wednesday 09 April 2014 03:53:36 GMT
GMT格林威治时间和本地的时间是有时差的, 我们知道php指定时区后用date() 函数获取的是本地时间, 如果想获取标准的GMT 格林威治时间就要用gmdate()函数了
/**
* Converts a local Unix timestamp to GMT
*
* @param int Unix timestamp
* @return int
*/
function local_to_gmt($time = '')
{
if ($time === '')
{
$time = time();
}

return mktime(
gmdate('G', $time),
gmdate('i', $time),
gmdate('s', $time),
gmdate('n', $time),
gmdate('j', $time),
gmdate('Y', $time)
);
}

date_default_timezone_set('Asia/Calcutta');

$current_date = date("Y/m/d g:i A");

$ist_date = DateTime::createFromFormat(
'"Y/m/d g:i A"',
$current_date,
new DateTimeZone('Asia/Calcutta')
);

$utc_date = clone $ist_date;
$utc_date->setTimeZone(new DateTimeZone('UTC'));

echo 'UTC: ' . $utc_date->format('Y-m-d g:i A');
$time = gmmktime();
echo date("Y-m-d H:i:s", $time);
date_default_timezone_set("UTC");
date("Y-m-d H:i:s", time() - date("Z"))
date_default_timezone_set('Australia/Brisbane');
echo gmdate('c');///2018-01-12T16:10:11+00:00
最好是一直使用 UTC 时间。服务器使用,自己开发默认也是,然后存入数据库也是,这样的话把数据显示给用户看的话转换为适当时区的日期和时间就行了。
时间戳是指格林尼治时间(GMT)19700101000000秒到当前时间的总秒数。https://www.php.net/manual/en/timezones.php
function st_date($format,$timestamp = false) {
$timestamp = is_numberic($timestamp) ? $timestamp : time();
return gmdate($format,$timestamp + 8*3600);
}
这样就可以保证这段代码无论在哪里,都可以输出东八区的时间。
$triggerOn = '04/01/2013 03:08 PM';
$user_tz = 'America/Los_Angeles';

echo $triggerOn; // echoes 04/01/2013 03:08 PM

$schedule_date = new DateTime($triggerOn, new DateTimeZone($user_tz) );
$schedule_date->setTimeZone(new DateTimeZone('UTC'));
$triggerOn = $schedule_date->format('Y-m-d H:i:s');

echo $triggerOn; // echoes 2013-04-01 22:08:00

格林威治时间(Greenwich Mean Time, GMT)。

通用协调时间(Universal Time Coordinated, UTC)。UTC的表示方式为:年(y)、月(m)、日(d)、时(h)、分(min)、秒(s),均用数字表示。若以「世界标准时间」的角度来说,UTC比GMT来得更加精准: *UTC = GMT +/- 0.9* 。

本地时间(locale time):很显然,本地时间跟时区(timezone)有关: *本地时间 = UTC + 时区* 。例如,中国北京标准时间(忽略GMT和UTC的差异): *CST = GMT + 8 = UTC + 8* 。

Unix时间戳(Unix timestamp),又称 Unix时间(Unix time) 或 POSIX时间(POSIX time) ,它从格林威治时间1970年01月01日00时00分00秒起至现在的总秒数。

$usersNow = new DateTime('now', new DateTimeZone("+8"));//php5.5+
echo $usersNow->format(DateTime::RFC3339);//2019-04-18T17:16:12+08:00
$usersNow = new DateTime('now', new DateTimeZone('+0300'));
$original = new DateTime("now", new DateTimeZone('UTC'));
$timezoneName = timezone_name_from_abbr("", 3*3600, false);
$modified = $original->setTimezone(new DateTimezone($timezoneName));

$date = date_create('2000-01-01', timezone_open('Pacific/Nauru'));
echo date_format($date, 'Y-m-d H:i:sP') . "\n";

date_timezone_set($date, timezone_open('Pacific/Chatham'));
echo date_format($date, 'Y-m-d H:i:sP') . "\n";
为什么要时间戳?因为从0开始运行的秒数永远相等,即使出现润秒,也并不影响时间戳。
php在使用date函数的时候,会依照所在时区去进行计算。date()进行输出的时间,就是我们所说的本地时间。
可变:date()不可变:time()、gmdate()
// 方法1date('Y-m-d H:i:s',strtotime(gmdate('Y-m-d H:i:s').' +8 hours'));
// 方法2(推荐)gmdate('Y-m-d H:i:s',time() + 8*3600);
$gmt_date = date('Y-m-d H:i:s',time() - date('Z'));
$local_time = gmdate('Y-m-d H:i:s',time() + date('Z'));
但这和$local_time = date('Y-m-d H:i:s');没有任何结果上的区别。
只保存timestamp,也就是time(),它的值是固定的,不随着时区的调整而改变,即使更换了服务器,它的误差也很小,所以有利于今后将程序分发部署到不同的服务器上面。

function toTimeZone($src, $from_tz = 'America/Denver', $to_tz = 'Asia/Shanghai', $fm = 'Y-m-d H:i:s') {
$datetime = new DateTime($src, new DateTimeZone($from_tz));
$datetime->setTimezone(new DateTimeZone($to_tz));
return $datetime->format($fm);
}
$time_zone = "GMT+8";
$time = time();
$date = date_create(date("Y-m-d H:i", $time), timezone_open('UTC'));
$date = date_timezone_set($date, timezone_open($time_zone));
$date = date_format($date, 'Y-m-d H:i');

echo date("Y-m-d H:i:s");
// date() 返回的是: 当前(这一刻 time()函数执行/返回时) GMT标准时间 的"本地化时间" 的自定义格式时间
// date()跟php系统设置的时区有关!
echo gmdate("Y-m-d H:i:s");
// gmdate() 返回的是: 当前(这一刻 time()函数执行/返回时) GMT标准时间 的自定义格式时间
// gmdate() 跟你现在所处的位置无关, 跟php系统设置的时区无关!

也就说, date()和gmdate()的区别, 仅仅在于 处理的时间 是不同的!

INSERT INTO `table` (id, time) VALUES(NULL, UNIX_TIMESTAMP());

// or http://cn.voidcc.com/question/p-fvgnwppc-bc.html

$time = time();

$query = "INSERT INTO `table` (id, time) VALUES(NULL, $time);
如果你想从数据库中选择它作为一个DATETIME,你可以这样做:

SELECT *, FROM_UNIXTIME(time) as dt FROM `table` WHERE 1
产生的dt栏会格式为yyyy-MM-dd hh:mm:ss。

You have new mail in /var/spool/mail/root

1
2
3
4
5
6
7
8
9
10
tac /var/spool/mail/root |more
PHP Fatal error: require_once(): Failed opening required '../Redis/RedisManager.php' (include_path='.:/usr/local/lib/php
/:/usr/share/pear:/usr/share/php') in /cron/test.php on line 8

crotnab -e
*/5 * * * 1-6 cd /cron/;/usr/local/bin/php /cron/test.php -t 15

tail -f /var/log/cron
Apr 18 18:00:02 xk-6-240-a8 CROND[23941]: (root) CMD (/usr/lib64/sa/sa1 1 1)
Apr 18 18:00:02 xk-6-240-a8 CROND[23942]: (root) CMD (cd /cron/;/usr/local/bin/php /cron/test.php -t 15

对二维数组进行排序

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
/**
* 对二维数组进行按字段排序
* @param array $array 要排序的二维数组
* @param bool $orderby 根据该字段(二维数组单个元素中的键名)排序
* @param string $order 排序方式,asc:升序;desc:降序(默认)
* @param string $children 子元素字段(键名),当元素含有该字段时,进行递归排序
* @return array
*/
function array_orderby(&$array,$orderby = null,$order = 'desc',$children = false) {
if($orderby == null)
return $array;
$key_value = $new_array = array();

foreach($array as $k => $v) {
$key_value[$k] = $v[$orderby];
}

if($order == 'asc') {
asort($key_value);
}
else {
arsort($key_value);
}
reset($key_value);

foreach($key_value as $k => $v) {
$new_array[$k] = $array[$k];
// 如果有children
if($children && isset($new_array[$k][$children])) {
$new_array[$k][$children] = array_sort($new_array[$k][$children]);
}
}

$new_array = array_values($new_array); // 使键名从0开始升序排列
$array = $new_array;
return $new_array;
}
$a = array(
array('name' => 'Kenvi','age' => 18),
array('name' => 'Nance','age' => 21),
.....
);
array_orderby($a,'age','asc'); // 或者$a = array_orderby($a,'age','asc');
$b = array(
array(
'id' => 12,'count' => 36,
'children' => array(
array('id' => 76,'count' => 40),
array('id' => 98,'count' => 100)
)
),
array('id' => 19,'count' => 87),
......
);
array_orderby($b,'count','desc','children');
https://www.tangshuang.net/2077.html

mysql orderby limit 翻页数据重复

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
SELECT `post_title`,`post_date` FROM post WHERE `post_status`='publish' ORDER BY view_count desc LIMIT 5,5
使用上述SQL查询的时候,很有可能出现和LIMIT 0,5相同的某条记录,而如果使用:

SELECT * FROM post WHERE `post_status`='publish' ORDER BY view_count desc LIMIT 5,5
则不会出现重复的情况。但是,由于post表的字段很多,我仅仅希望用这两个字段,不想把post_content也查出来。为了解决这个情况,我在ORDER BY后面使用了两个排序条件来解决这个问题。

SELECT `post_title`,`post_date` FROM post WHERE `post_status`='publish' ORDER BY view_count desc,ID asc LIMIT 5,5
按理来说,mysql的排序默认情况下是以主键ID作为排序条件的,也就是说,如果在view_count相等的情况下,主键ID作为默认的排序条件,不需要我们多此一举加ID asc。但是事实就是,mysql再order by和limit混用的时候,出现了排序的混乱情况。其后的机理我尚不得而知,在阅读这篇文章后,好像有所领悟,下面做一下猜测。

这篇文章的解释是:https://www.tangshuang.net/1827.html

在MySQL 5.6的版本上,优化器在遇到order by limit语句的时候,做了一个优化,即使用了priority queue。……

使用 priority queue 的目的,就是在不能使用索引有序性的时候,如果要排序,并且使用了limit n,那么只需要在排序的过程中,保留n条记录即可,这样虽然不能解决所有记录都需要排序的开销,但是只需要 sort buffer 少量的内存就可以完成排序。

之所以5.6出现了第二页数据重复的问题,是因为 priority queue 使用了堆排序的排序方法,而堆排序是一个不稳定的排序方法,也就是相同的值可能排序出来的结果和读出来的数据顺序不一致。

5.5 没有这个优化,所以也就不会出现这个问题。
也就是说,mysql5.5是不存在本文提到的问题的,5.6版本之后才出现了这种情况。

我们再看下mysql解释sql语言时的执行顺序:

(7) SELECT
(8) DISTINCT <select_list>
(1) FROM <left_table>
(3) <join_type> JOIN <right_table>
(2) ON <join_condition>
(4) WHERE <where_condition>
(5) GROUP BY <group_by_list>
(6) HAVING <having_condition>
(9) ORDER BY <order_by_condition>
(10) LIMIT <limit_number>
在我们本文的案例sql中,执行顺序依次为form... where... select... order by... limit...

由于上述priority queue的原因,在完成select之后,所有记录是以堆排序的方法排列的,在进行order by时,仅把view_count值大的往前移动。但由于limit的因素,排序过程中只需要保留到5条记录即可,view_count并不具备索引有序性,所以当第二页数据要展示时,mysql见到哪一条就拿哪一条,因此,当排序值相同的时候,第一次排序是随意排的,第二次再执行该sql的时候,其结果应该和第一次结果一样。

消息队列处理高并发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
当用户点击按钮提交购买请求时,并不直接马上执行购买请求动作,而是将请求动作存入消息列队,用户的请求到此结束,而在服务器后台,从消息列队中逐一取出请求记录,再进行数据库操作,完成处理之后,将处理结果返回给用户。由于消息队列的存取是先进先出(和栈相反),因此,所有处理将按请求顺序进行处理。由于处理是在后台一个一个完成,因此不会对服务器和数据库造成巨大压力。


$redis = new Redis();
$redis->connect('127.0.0.1',6379);
try {
$redis->LPUSH('click',$user_id);
}
catch(Exception $e) {
echo $e->getMessage();
}
$redis = new Redis();
$redis->pconnect('127.0.0.1',6379);
while(1) {
try{
$value = $redis->LPOP('click');
/**
利用$value进行逻辑和数据处理
*/
}
catch(Exception $e) {
echo $e->getMessage();
}
}

获取这两个字符串前面的相同部分

1
2
3
4
5
6
7
8
9
10
11
异或运算是一种非常特殊的运算,简单的说就是“如果两个值相同,返回0|false,如果两个值不同,则返回1|true”。
function samestrin($str1, $str2) {
$pos = strspn($str1 ^ $str2, "\0");
if($pos) {
return substr($str1, 0, $pos);
}
return null;
}https://www.tangshuang.net/82.html
上述代码中,关键部分已经用红色表示出来了。一般情况下,当你对两个字符串进行异或操作的时候,相同的字符的异或结果是null(“\0”),所以我们只要找出第一个非null(“\0”)字符就可以了。如此优雅的操作,得益于计算机领域的异或运算,如若没有这种算法,我们可能需要写一大堆代码来进行匹配对比。
samestrin('ab','abd')
=> "ab"

树形结构算法

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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/**
* 构建层级(树状)数组
* @param array $array 要进行处理的一维数组,经过该函数处理后,该数组自动转为树状数组
* @param string $pid 父级ID的字段名
* @return array|bool
*/
function array_tree(&$array, $pid = 'pid') {
// 子元素计数器
function array_children_count($array,$pid) {
$counter = array();
foreach($array as $item) {
$count = isset($counter[$item[$pid]]) ? $counter[$item[$pid]] : 0;
$count ++;
$counter[$item[$pid]] = $count;
}
return $counter;
}

// 把元素插入到对应的父元素children字段
function array_child_append($parent,$pid,$child) {
foreach($parent as &$item) {
if($item['id'] == $pid) {
if(!isset($item['children'])) $item['children'] = array();
$item['children'][] = $child;
}
}
return $parent;
}
// 开始程序
$counter = array_children_count($array,$pid);

// 如果顶级元素为0个,那么直接返回false
if($counter[0] == 0)
return false;

// 准备顶级元素
$tree = array();

// 位移
while(isset($counter[0]) && $counter[0] > 0) { // 如果顶级栏目的子元素计数器仍然大于0,那么仍然往下执行循环
$temp = array_shift($array);
if(isset($counter[$temp['id']]) && $counter[$temp['id']] > 0) { // 如果数组的第一个元素的子元素个数大于0,那么把该元素放置到数组的末端
array_push($array,$temp);
}
else { // 相反,如果该数组的第一个元素没有子元素,那么把该元素移动到其父元素的children字段中,同时,该元素从原数组中被删除
if($temp[$pid] == 0)
$tree[] = $temp;
else
$array = array_child_append($array,$temp[$pid],$temp);
}
$counter = array_children_count($array,$pid);
}
$array = $tree;
return $tree;
}
$arr = array(
array('id' => 53,'pid' => 0),
array('id' => 64,'pid' => 53),
array('id' => 70,'pid' => 42)
);
//$r=array_tree($arr,'pid');print_r($r);

$demo1 = array(
1 => array('id' => 1,'title' => 'title1','pid' => 0),
2 => array('id' => 2,'title' => 'title2','pid' => 1),
3 => array('id' => 3,'title' => 'title3','pid' => 1),
4 => array('id' => 4,'title' => 'title4','pid' => 2),
5 => array('id' => 5,'title' => 'title5','pid' => 2),
6 => array('id' => 6,'title' => 'title6','pid' => 0)
);
$demo1 = array_tree($demo1);// 或者直接使用array_tree($demo1);,无需返回值https://www.tangshuang.net/2073.html https://3v4l.org/kngT8
print_r($demo1);
Array
(
[0] => Array
(
[name] => Nance
[age] => 21
)

[1] => Array
(
[name] => Kenvi
[age] => 18
)

)
Array
(
[0] => Array
(
[id] => 6
[title] => title6
[pid] => 0
)

[1] => Array
(
[id] => 1
[title] => title1
[pid] => 0
[children] => Array
(
[0] => Array
(
[id] => 3
[title] => title3
[pid] => 1
)

[1] => Array
(
[id] => 2
[title] => title2
[pid] => 1
[children] => Array
(
[0] => Array
(
[id] => 4
[title] => title4
[pid] => 2
)

[1] => Array
(
[id] => 5
[title] => title5
[pid] => 2
function array_tree($array) {
$result = array();
$tmp = array();
foreach($array as $item) {
if($item['parent_id'] == 0) {
$i = count($result);
$result[$i] = $item;
$id = $item['id'];
$tmp[$id] = &$result[$i];//这一句保证了当无论$tmp[$id]还是$result[$i]发生变化,都会让另外一个值同时发生变化。而 $tmp[$parent_id]['children'][$i] = $item; 实际上导致$result[$parent_id]发生了变化。
}
else {
$id = $item['id'];
$parent_id = $item['parent_id'];
$parent = $tmp[$parent_id];
$i = count($parent['children']);
$tmp[$parent_id]['children'][$i] = $item;
$tmp[$id] = &$tmp[$parent_id]['children'][$i];
}
}
return $result;
}
当$b = &$a时,$b和$a同时引用同一个内容(指针指向同一块内存),无论$a或$b谁发生变化,这个内容都会发生变化,进而呈现为$a和$b保持同步的变化。
https://www.tangshuang.net/1701.html
print_r(array_tree($demo1));
Array
(
[0] => Array
(
[id] => 1
[title] => title1
[parent_id] => 0
[children] => Array
(
[0] => Array
(
[id] => 2
[title] => title2
[parent_id] => 1
[children] => Array
(
[0] => Array
(
[id] => 4
[title] => title4
[parent_id] => 2
)

[1] => Array
(
[id] => 5
[title] => title5
[parent_id] => 2
)

)

)

[1] => Array
(
[id] => 3
[title] => title3
[parent_id] => 1
)

)

)

[1] => Array
(
[id] => 6
[title] => title6
[parent_id] => 0
)

)
$tmp[$id] = &$tmp[$parent_id]['children'][$i];这一句起到了关键性作用。加上这一句之后,我们再来跑一遍array('id' => 23,'parent_id' => 12,...)这个元素。

[2]=> array('id' => 12,'parent_id' => 5,...)

$tmp[5]['children'][0] = array('id' => 12,'parent_id' => 5,...) // 重新从①开始演示

$result[0]['children'][0] = array('id' => 12,'parent_id' => 5,...) // 内部结果

$tmp[12] = &$tmp[5]['children'][0] // 第二个&出现了

[5]=> array('id' => 23,'parent_id' => 12,...)

$tmp[12]['children'][0] = array('id' => 23,'parent_id' => 12,...)

由于第二个&引用的原因,实际上发生了:

$tmp[5]['children'][0]['children'][0] = array('id' => 23,'parent_id' => 12,...) // 内部结果

$result[0]['children'][0]['children'][0] = array('id' => 23,'parent_id' => 12,...) // 内部结果
这个时候你可能已经看出了端倪。父子关系变成了 5 > 12 > 23,而这组关系,全部存储在了id=5的这个顶级元素中,以多重的children元素实现了父子孙结构。

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
https://github.com/tangshuang/php-events-async-callback https://www.tangshuang.net/2086.html
$pid = pcntl_fork();
if($pid > 0){ //父进程代码
exit(0);
}
elseif($pid == 0) { //子进程代码
exit(0);
}
你会发现httpd或php-fpm的进程会增加,也就是说,pcntl其实是通过重新开启php进程,并通过其他机制实现进程之间的通信的。
proc_open,popen也是利用httpd来实现多进程

$proc=proc_open("echo foo", array(
array("pipe","r"),
array("pipe","w"),
array("pipe","w")
), $pipes);
print stream_get_contents($pipes[1]);

require 'Events.php';
require 'EventListener.class.php';

$EventListener = new EventListener('timeout');
$EventListener->add('timeout','setTimeout');

function setTimeout() {
sleep(10);
file_put_contents('./log.txt','延时回调成功,现在时间:'.date('Y-m-d H:i:s'),FILE_APPEND);
}

// 你自己的代码流程https://github.com/tangshuang/php-events-async-callback

file_put_contents('./log.txt','延时操作开始,现在时间:'.date('Y-m-d H:i:s')."\r",LOCK_EX);
$EventListener->run('timeout');

shell重启服务

1
2
3
4
5
6
7
8
9
10
11
在shell脚本中对服务进行判断,如果nginx或者httpd宕机了,就执行一条重启命令:

#!/bin/bash
#nginx
ps -ef | grep nginx |grep -v grep > /dev/null
if [ $? != 0 ];then
service nginx restart > /dev/nullfi
#httpd
ps -ef | grep httpd |grep -v grep > /dev/null
if [ $? != 0 ];then
service httpd restart > /dev/nullfi

Laravel IoC 服务容器类管理工具

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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。

抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。
class MySQLConnection
{

/**
* 数据库连接
*/
public function connect()
{
var_dump(‘MYSQL Connection’);
}

}

class PasswordReminder
{
/**
* @var MySQLConnection
*/
private $dbConnection;

public function __construct(MySQLConnection $dbConnection)
{
$this->dbConnection = $dbConnection;
}
}
大家常常会有一个误解,那就是依赖反转就只是依赖注入的另一种说法。但其实二者是不同的。在上面的代码示例中,尽管在 PasswordReminder 类中注入了 MySQLConnection 类,但它还是依赖于 MySQLConnection 类。

然而,高层次模块 PasswordReminder 是不应该依赖于低层次模块 MySQLConnection 的。

如果我们想要把 MySQLConnection 改成 MongoDBConnection,那我们就还得手动修改 PasswordReminder 类构造函数里的依赖。

interface ConnectionInterface
{
public function connect();
}

class DbConnection implements ConnectionInterface
{
/**
* 数据库连接
*/
public function connect()
{
var_dump(‘MYSQL Connection’);
}
}

class PasswordReminder
{
/**
* @var DBConnection
*/
private $dbConnection;
public function __construct(ConnectionInterface $dbConnection)
{
$this->dbConnection = $dbConnection;
}
}
IoC 容器简单地理解为就是一个容器,里面装的是类的依赖。
namespace App\Repositories;

interface OrderRepositoryInterface
{
public function getAll();
}
namespace App\Repositories;

class DbOrderRepository implements OrderRepositoryInterface
{

function getAll()
{
return 'Getting all from mysql';
}
}
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests;
use App\Repositories\OrderRepositoryInterface;

class OrdersController extends Controller
{
protected $order;

function __construct(OrderRepositoryInterface $order)
{
$this->order = $order;
}

public function index()
{
dd($this->order->getAll());
return View::make(orders.index);
}
}
Route::resource('orders', 'OrdersController');
App::bind('App\Repositories\OrderRepositoryInterface', 'App\Repositories\DbOrderRepository');
定义一个容器类:

class SimpleContainer
{
protected static $container = [];

public static function bind($name, Callable $resolver)
{
static::$container[$name] = $resolver;
}

public static function make($name)
{
if(isset(static::$container[$name])){
$resolver = static::$container[$name] ;
return $resolver();
}

throw new Exception("Binding does not exist in containeer");

}
}
class LogToDatabase
{
public function execute($message)
{
var_dump('log the message to a database :'.$message);
}
}

class UsersController {

protected $logger;

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

public function show()
{
$user = 'JohnDoe';
$this->logger->execute($user);
}
}
绑定依赖:

SimpleContainer::bind('Foo', function()
{
return new UsersController(new LogToDatabase);
});

$foo = SimpleContainer::make('Foo');

print_r($foo->show());

Laravel 的服务容器源码:vendor/laravel/framwork/src/Illuminate/Container/Container.php

public function bind($abstract, $concrete = null, $shared = false)
{
$abstract = $this->normalize($abstract);

$concrete = $this->normalize($concrete);

if (is_array($abstract)) {
list($abstract, $alias) = $this->extractAlias($abstract);

$this->alias($abstract, $alias);
}

$this->dropStaleInstances($abstract);

if (is_null($concrete)) {
$concrete = $abstract;
}

if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}

$this->bindings[$abstract] = compact('concrete', 'shared');

if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}

public function make($abstract, array $parameters = [])
{
$abstract = $this->getAlias($this->normalize($abstract));

if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}

$concrete = $this->getConcrete($abstract);

if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete, $parameters);
} else {
$object = $this->make($concrete, $parameters);
}

foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}

if ($this->isShared($abstract)) {
$this->instances[$abstract] = $object;
}

$this->fireResolvingCallbacks($abstract, $object);

$this->resolved[$abstract] = true;

return $object;
}

public function build($concrete, array $parameters = [])
{

if ($concrete instanceof Closure) {
return $concrete($this, $parameters);
}

$reflector = new ReflectionClass($concrete);

if (! $reflector->isInstantiable()) {
if (! empty($this->buildStack)) {
$previous = implode(', ', $this->buildStack);
$message = "Target [$concrete] is not instantiable while building [$previous].";
} else {
$message = "Target [$concrete] is not instantiable.";
}

throw new BindingResolutionException($message);
}

$this->buildStack[] = $concrete;

$constructor = $reflector->getConstructor();

if (is_null($constructor)) {
array_pop($this->buildStack);

return new $concrete;
}

$dependencies = $constructor->getParameters();
$parameters = $this->keyParametersByArgument(
$dependencies, $parameters
);

$instances = $this->getDependencies($dependencies,$parameters);

array_pop($this->buildStack);

return $reflector->newInstanceArgs($instances);
}
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
$api = new HelpSpot\API(new HttpClient);

$this->app->instance('HelpSpot\API', $api);
https://learnku.com/laravel/t/26922 https://github.com/nahidulhasan/laravel-service-container
Ioc 是一个简单的组件,可以更加方便地解析依赖项。你可以将对象形容为容器,并且每次解析类时,都会自动注入依赖项。
$auth = new SimpleAuth( new FileSessionStorage() );
因为 Application 类继承自 Container 类,所以你可以通过 App 门面来访问容器。

App::bind( 'FileSessionStorage', function(){
return new FileSessionStorage;
});
https://learnku.com/laravel/t/26721

App:bind( 'SessionStorage', 'MysqlSessionStorage' );//SessionStorage是接口,MysqlSessionStorage和FileSessionStorage继承ta,调用的时候注入SessionStorage $session
如果我们想要切换我们的存储服务,我们只要变更一下这个绑定。App:bind( 'SessionStorage', 'FileSessionStorage ' );

laravel面试哪些问题 v2ex

laravel函数

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
//在给定值后返回字符串的其余部分。
function after($subject, $search)
{
dump(explode($search, $subject, 2),array_reverse(explode($search, $subject, 2)));
//array:2 [▼
0 => ""
1 => "试laravel函数"
]
array:2 [▼
0 => "试laravel函数"
1 => ""
]
return $search === '' ? $subject : array_reverse(explode($search, $subject, 2))[0];
}
$s = '测试laravel函数';echo after($s,'测');//试laravel函数
function ascii($value, $language = 'en')
{
$languageSpecific = languageSpecificCharsArray($language);

if (! is_null($languageSpecific)) {
$value = str_replace($languageSpecific[0], $languageSpecific[1], $value);
}

foreach ( charsArray() as $key => $val) {
$value = str_replace($val, $key, $value);//循环将数组内的字符串替换
}

return preg_replace('/[^\x20-\x7E]/u', '', $value);
}
function languageSpecificCharsArray($language)
{
static $languageSpecific;

if (! isset($languageSpecific)) {
$languageSpecific = [
'bg' => [
['х', 'Х', 'щ', 'Щ', 'ъ', 'Ъ', 'ь', 'Ь'],
['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y'],
],
'de' => [
['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü'],
['ae', 'oe', 'ue', 'AE', 'OE', 'UE'],
],
];
}

return $languageSpecific[$language] ?? null;
}
function charsArray()
{
static $charsArray;

if (isset($charsArray)) {
return $charsArray;
}

return $charsArray = [
'0' => ['°', '₀', '۰', '0'],
'1' => ['¹', '₁', '۱', '1'],
'2' => ['²', '₂', '۲', '2'],
'3' => ['³', '₃', '۳', '3'],
'4' => ['⁴', '₄', '۴', '٤', '4'],
'5' => ['⁵', '₅', '۵', '٥', '5'],
'6' => ['⁶', '₆', '۶', '٦', '6'],
'7' => ['⁷', '₇', '۷', '7'],
'8' => ['⁸', '₈', '۸', '8'],
'9' => ['⁹', '₉', '۹', '9'],
}
将utf - 8值转换为ASCII
ascii($s);//laravel
//在给定值之前获取字符串的部分。
function before($subject, $search)
{
return $search === '' ? $subject : explode($search, $subject)[0];
}
//将一个值转换为一个大写的大写字母
function studly($value)
{
$key = $value;

if (isset(static::$studlyCache[$key])) {
return static::$studlyCache[$key];
}

$value = ucwords(str_replace(['-', '_'], ' ', $value));

return static::$studlyCache[$key] = str_replace(' ', '', $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
添加中间件. LowerCaseRoutes

<?php

namespace App\Http\Middleware;

use Closure;
use \Illuminate\Support\Facades\Redirect;

/**
* 把大写uri转换为小写.已经实现路由不区分大小写,如果有大写,会 301 跳转到小写的地址.

https://learnku.com/laravel/t/27783
* Class LowerCaseRoutes
* @package App\Http\Middleware
*/
class LowerCaseRoutes
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (preg_match('/([A-Z]+)/', $request->path(), $match)) {
$newRoute = str_replace($request->path(), strtolower($request->path()), $request->fullUrl());
return Redirect::to($newRoute, 301);
}

return $next($request);
}
}

数组扁平化

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
$data = [
'testArray' => [
'string1',
'string2',
[
'string1',
'string2',
],
],
'teststring'
];
echo "<pre>";https://learnku.com/articles/24318

print_r(iterator_to_array(
new RecursiveIteratorIterator(
new RecursiveArrayIterator($data)
),
false // use_keys:默认true
)
);

/*Array
(
[0] => string1
[1] => string2
[2] => string1
[3] => string2
[4] => teststring
)*/
collect ($data)->flatten ()->all ()

phpexcel科学计数法

1
2
3
4
5
6
7
8
9
10
拼一个 \t
设置单元格格式为文本格式https://learnku.com/articles/23696

$card_no_style = [
'numberFormat' => [
'formatCode' => NumberFormat::FORMAT_TEXT
]

];
$obj_sheet->getStyle(Coordinate::stringFromColumnIndex($column) . "1" . ":" . Coordinate::stringFromColumnIndex($column) . count($data))->applyFromArray($card_no_style);

PHPExcel 在 PHP7.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
'break' not in the 'loop' or 'switch' context

PHPExcel\Calculation\Functions.php  LINE: 581

if (($value === NULL) || (is_float($value)) || (is_int($value))) {
return 1;
} elseif(is_bool($value)) {
return 4;
} elseif(is_array($value)) {
return 64;
break;//这是581行
} elseif(is_string($value)) {
// Errors
if ((strlen($value) > 0) && ($value{0} == '#')) {
return 16;
}
return 2;
}

return 跳出当前函数或者循环直接返回 break 并会执行而报错https://learnku.com/articles/26675

方案 1: 将 581 行的 break 注释掉

方案 2 : 将 PHPExcel 升级到 1.81 版本以上

方案三:使用 PHPExcel 的升级产品 PhpSpreadsheet

方案 1 修改源码 不推荐
方案 2PHPExcel 已不再维护 故不推荐
对于框架版本国过老 或经多手的项目 建议使用 方案 1,2

方案 3 为 PHPExcel 的替代品升级产品 而且在维护 新项目推荐方案 3

PHPExcel 1.8.1 地址 https://github.com/PHPOffice/PHPExcel/commit/372c7cbb695a6f6f1e62649381aeaa37e7e70b32

PhpSpreadsheet 安装 如下

文档地址:https://phpspreadsheet.readthedocs.io/en/latest/#getting-started

GitHub 下载:https://github.com/PHPOffice/PhpSpreadsheet

composer 安装:composer require phpoffice/phpspreadsheet

PHPExcel 与 PhpSpreadsheet?

PhpSpreadsheet 是 PHPExcel 的下一个版本。它打破了兼容性,大大提高了代码库质量(命名空间,PSR 合规性,最新 PHP 语言功能的使用等)。

因为所有的努力都转移到了 PhpSpreadsheet,所以不再维护 PHPExcel。PHPExcel,补丁和新功能的所有贡献都应该以 PhpSpreadsheet master 分支为目标。

foreach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$data=array('a','b','c');
foreach($data as $key=>$value){
$value=&$data[$key];
}
问题1:程序执行时,每一次循环结束后变量$data的值是什么?请解释。
问题2:程序执行完后,变量$data的值是什么?请解释。
//第一次遍历 结果还是 【a,b,c】
//第二次遍历
$data[0]='a';//第一次遍历的时候$data[0]=a
$val=&$data[0];//这边已经把$data[0]引用指向$val 第一次遍历以后
$val=b //现在$val 一旦变了 那么$data[0]也就改变了 所以变成$data[0]=b
$data=[b,b,c];//最后结果变成了
//第三次遍历
$data[1]=b;//第二次遍历的时候$data[1]变成了b
$val=&$data[1];//第二次遍历的时候$val的引用变成了$data[1]
$val=c;//所以这边$val变成了c 那么$data[1]也就变成了c
$data=['b','c','c']//最后结果变成了
//最后的$data的结果https://learnku.com/articles/26374
【b,c,c】
list($b, $a) = array($a, $b);

tap

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
$items = [
['name' => 'David Charleston', 'member' => 1, 'active' => 1],
['name' => 'Blain Charleston', 'member' => 0, 'active' => 0],
['name' => 'Megan Tarash', 'member' => 1, 'active' => 1],
['name' => 'Jonathan Phaedrus', 'member' => 1, 'active' => 1],
['name' => 'Paul Jackson', 'member' => 0, 'active' => 1]
];
先将数组转为集合,再进行数据的过滤,最后在两个不同的节点调用 tap 方法,返回打印结果:

return collect($items)
->where('active', 1)
->tap(function($collection){
return var_dump($collection->pluck('name'));
})
->where('member', 1)
->tap(function($collection){
return var_dump($collection->pluck('name'));
});
第一次调用 tap 方法输出结果:https://learnku.com/laravel/t/26361

David Charleston, Megan Tarash, Jonathan Phaedrus, Paul Jackson
第二次调用 tap 方法输出结果:

David Charleston, Megan Tarash, Jonathan Phaedrus
Tap vs Pipe

Laravel 也提供了另一个类似 tap 的集合操作方法 -- pipe,两者在集合调用上很类似,却有一个主要的区别:

通过调用 tap 方法不会改变原集合的结果,而 pipe 方法会根据返回值修改元集合的结果。示例如下:

return collect($items)
->where('active', 1)
->pipe(function ($collection) {
return $collection->push(['name' => 'John Doe']);
});
// David Charleston, Megan Tarash, Jonathan Phaedrus, Paul Jackson, John Doe

artisan日志 root 权限解决

1
2
3
4
5
6
7
8
9
10
11
12
13
bootstrap/app.php 中写入以下配置: 
https://learnku.com/articles/26444
$app->configureMonologUsing(function(Monolog\Logger $monolog) {
$filename = storage_path('logs/laravel-'.php_sapi_name().'.log');
$handler = new Monolog\Handler\RotatingFileHandler($filename);
$monolog->pushHandler($handler);
});
正确的做法应该是用 www 用户执行 artisan 命令,因为不光是日志,还有可能有缓存、 storage 等文件可能被 root 用户创建而导致 www 用户无权操作。
脚本写在 crontab 中的,如果我想用 www 用户执行 artisan 命令,我应该怎么写 crontab 呢 crontab -e -u www
php cli 或者 fpm 都是用 www 用户去执行 就不会出现你这个问题的
不想使用单独的用户配置 crontab,也可以这么干,比如:配置在 root 账号下

su www -s /bin/bash -c “php artisan schedule:run”

英文字符占 0.5 个,中文字符占 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
https://learnku.com/articles/4956/following-my-unique-needs-of-the-english-characters-accounted-for-05-chinese-characters-accounted-for-1#topnav
$double = str_word_count(preg_replace('([a-zA-Z0-9_])', '', $value));

// 计算单字节.
preg_match_all('/[a-zA-Z0-9_]/', $value, $single);
$single = count($single[0]) / 2;

// 多子节长度.
$double = mb_strlen(preg_replace('([a-zA-Z0-9_])', '', $value));
function the_fucking_length_function($str)
{
return strlen(mb_convert_encoding($str, 'GB2312')) / 2;
}
$length = (strlen($value) + mb_strlen($value)) / 2;
//这个方法是好的,缺点就只有一个,就是精度问题,因为 utf-8 中只有两万多个汉字是 3 字节,还有六万多的不常用汉字是 4 字节的,附带 emoji 之类的就
function mbstrlen($str) {
return ceil((strlen($str) + mb_strlen($str, "UTF8")) / 2);
}
function str_display_len (string $str)
{
preg_match_all('/[a-zA-Z0-9_]/', $str, $single);

return count($single[0]) / 2 + mb_strlen(preg_replace('([a-zA-Z0-9_])', '', $str));
}
https://www.php.net/manual/zh/function.mb-strwidth.php

bas64简历

1
https://learnku.com/laravel/t/25907

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
//https://learnku.com/articles/26769
$arr = [
[
'id'=>1,
'score'=>10,
],
[
'id'=>2,
'score'=>30,
],
[
'id'=>3,
'score'=>50,
],
[
'id'=>4,
'score'=>50,
]
]
你想从小到大,取出前三名,如果第四名和第三名也相同,也取出来

先按 score 排序

array_multisort(array_column($arr,'score'),SORT_ASC,$arr);
前三名

$results = array_slice($arr, 0, 3, true);
然后前三名的最后一名和之后的比较

$length = count($arr);
$resultLength = count($results);

//$resultLength 比 $length小说明$arr 的还有元素,否则它们俩就相等了。然后比较key和key+1的值。
极限是所有的人都和第三名相同,自行判断是否需要所有人都胜出,来限制 $resultLength 的长度

while($resultLength<$length &&$results[$resultLength-1]['score']==$arr[$resultLength]['score']){
array_push($results,$arr[$resultLength]);
$resultLength = count($results);
}
最后如果要判断某一用户是否胜出

$ifWinner = 0;
if(in_array($id,array_column($results,'id'))){
$ifWinner = 1;
}

多个 Laravel 应用 queue 队列执行时会互串

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
app/Libriries/helper.php ,可以写在你任何想放的地方

/**
* 多个 laravel 项目,如果 --queue 相同会互串
* 这里统一加个当前项目名的前缀来区分
* @param $name
* @return string
*/
function queue_name($name){
$name_trans = [];
foreach(explode(',',$name) as $k => $v){
$name_trans[] = str_slug(config('app.name') . ' ' . $v, '_');
}
return implode(',',$name_trans);
}
修改每个 Job 的 $queue 添加 queue_name ()

namespace App\Jobs;
// 省略...
class ApiBehavior implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

public function __construct()
{
// 添加这一行
$this->queue = queue_name('你的队列名');
}
}
// 分发队列时,照常分发即可
ApiBehavior::dispatch()
// 或者这种分发时指定,可以省略第二步。
ApiBehavior::dispatch()->onQueue(quque_name('你的队列名'))
新建监听队列命令,包装一层 quque:work,以使用自定义队列名

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class QueueWorkListen extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'queue:work-listen
{connection? : The name of the queue connection to work}
{--queue= : The names of the queues to work}
{--daemon : Run the worker in daemon mode (Deprecated)}
{--once : Only process the next job on the queue}
{--delay=0 : The number of seconds to delay failed jobs}
{--force : Force the worker to run even in maintenance mode}
{--memory=128 : The memory limit in megabytes}
{--sleep=3 : Number of seconds to sleep when no job is available}
{--timeout=60 : The number of seconds a child process can run}
{--tries=0 : Number of times to attempt a job before logging it failed}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Start processing jobs on the queue as a daemon';

/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}

/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->call('queue:work',[
'connection'=> $this->argument('connection'),
'--queue' => queue_name($this->option('queue') ?: 'default'),
'--daemon' => $this->option('daemon'),
'--once' => $this->option('once'),
'--delay' => $this->option('delay'),
'--force' => $this->option('force'),
'--memory' => $this->option('memory'),
'--sleep' => $this->option('sleep'),
'--timeout' => $this->option('timeout'),
'--tries' => $this->option('tries'),
]);
}
}
5、使用自定义命令监听队列,其他参数与 queue:work 一样使用https://learnku.com/articles/19460

php /var/www/ebank/artisan queue:work-listen --queue=email --sleep=3
// or 多个
php /var/www/ebank/artisan queue:work-listen --queue=email,test,test2,test3 --sleep=3

redis配置不同库就可以

修改器

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
<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;

class Baby extends Model
{
protected $table = 'baby';
protected $appends = ['age'];

public function getAgeAttribute()
{
$date = new Carbon($this->birthday);
return Carbon::now()->diffInYears($date);
}
}
这个代码比较简单,就是通过已有属性 birthday,计算 Baby 几岁了,得到 age 属性。

前端就可以直接拿到结果:https://learnku.com/articles/21982#topnav

return $baby->age;

class User extends Model {
/**
* 更改为日期类型的属性
*
* @var array
*/
protected $dates = [
'created_at',
'updated_at',
'expires_at',
'gets_mad_at'
];
/**
* 我们检索时始终将名字大写
*/
public function getFirstNameAttribute($value) {
return ucfirst($value);
}

/**
* 我们检索时始终将姓氏大写
*/
public function getLastNameAttribute($value) {
return ucfirst($value);
}
/**
* 将名称保存到数据库时,始终将名字大写
*/
public function setFirstNameAttribute($value) {
$this->attributes['first_name'] = ucfirst($value);
}

/**
* 将名称保存到数据库时,始终将姓氏大写
*/
public function setLastNameAttribute($value) {
$this->attributes['last_name'] = ucfirst($value);
}
/**
* 我们将密码保存至数据库时,总是哈希后保存。
*/
public function setPasswordAttribute($value) {
$this->attributes['password'] = Hash::make($value);
}
/**
* 总是 json_decode settings字段,所以它们是可用的
*/
public function getSettingsAttribute($value) {
return json_decode($value);

// 你可以确保同时返回一个数组
// return json_decode($value, true);
}

/**
* 保存到数据库时,总是对 settings 字段进行 json_encode
* Always json_encode the settings when saving to the database
*/
public function setSettingsAttribute($value) {
$this->attributes['settings'] = json_encode($value);
}

}
// 获取 awesomedudette 用户 https://learnku.com/laravel/t/27551
$user = App\User::where('username', 'awesomedudette')->first();

// 查找此用户的过期时间前一小时
$explodeWarning = $user->explodes_at->subHour();
$user = App\User::create([
'first_name' => 'chris',
'last_name' => 'sevilleja',
'email' => 'chris&64;scotch.io',
'password' => 'password',
'expires_at' => Carbon::now()->addMonth(),
'explodes_at' => Carbon::now()->addDay(),
'gets_mad_at' => Carbon::yesterday()
]);

响应宏原理

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
Response::macro('success', function ($data = [], $message = 'success') {
return new JsonResponse([
'code' => 0,
'data' => $data,
'message' => $message
], 200);
});

接下来, 你可以再任何地方使用它response()。https://godruoyi.com/posts/laravel-response-macro-principle

//UserController.php

public function users(UserRepository $userRepository)
{
return response()->success($userRepository->all(), 'success');
}

注意,你只能通过response()这个全局方法或是app('Illuminate\Routing\ResponseFactory')来使用它

response()->success();//OK
app('Illuminate\Routing\ResponseFactory')->success();//OK

//Response Facade
Response::success();//ok

(new \Illuminate\Http\Response)->success();//Error
每一次请求结束,PHP 的变量都会 unset,Laravel 的 singleton 只是在某一次请求过程中的singleton;你在 Laravel 中的静态变量也不能在多个请求之间共享,因为每一次请求结束都会 unset。理解这些概念,是写高质量代码的第一步,也是最关键的一步。因此记住,PHP是一种脚本语言,所有的变量只会在这一次请求中生效,下次请求之时已被重置,而不像Java静态变量拥有全局作用。

队列优先级的一个坑

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
php artisan queue:work --queue=high,low
这样,当我们的任务需要优先发送时,就可以通过指定队列名high来优先发送。

dispatch((new Job)->onQueue('high'));
但是当你后续任务没有指定队列名(high、low)时,你的队列任务永远也不会执行。(比如我们在发送消息通知时)

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;

class YourNotification extends Notification implements ShouldQueue
{
use Queueable;
}
你发现即使你按照文档说的,implements ShouldQueue并且use Queueable,该通知还是无法加入队列。

那是因为config\queuq.php配置中,指定了默认的队列名为default,所以所有的队列任务,如果没指定队列名时,默认是default

但是我们在启动队列进程时,只指定了high和low。当然不会生效。https://godruoyi.com/posts/a-pit-in-the-laravel-queue-priority

解决办法: 1、修改config\queuq.php默认队列名为low或high 2、启动队列进程时添加default(--queue=high,default,low)

合成图片

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
require 'vendor/autoload.php';

use Intervention\Image\ImageManager;

$manager = new ImageManager(array('driver' => 'imagick'));
如果是在 Laravel 中,可直接通过 app('image') 或使用 Intervention\Image\Facades\Image 门面类。


use Intervention\Image\Facades\Image;

//或
$manager = app('image');
合成

// $image = $manager->make($bg);
// $image = Image::make($bg);

$image = app('image')->make($bg);

// 二维码图片https://godruoyi.com/posts/intervention-image-was-used-to-synthesize-images
$qrcodeImage = app('image')->make($qrcodeurl)->resize(200, 200);
// 重置 头像 大小
$avatarImage = app('image')->make($httpClient->request('GET', $avatarurl)->getBody())->resize(200, 200);

$image->text('Nickname', 376, 320, function ($font) {
$font->file(public_path('fonts/SimHei.ttf'));
$font->size(40);
$font->color('#000000');
$font->align('center');
$font->valign('top');
});

$image->insert($qrcodeImage, 'bottom', 0, 360);
$image->insert($avatarImage, 'top', 0, 105);

http code 204

1
2
3
4
5
204 为成功状态响应码,表示目前请求成功,但客户端不需要更新其现有页面。204 响应默认是可以被缓存的。在响应中需要包含头信息 ETag。

使用惯例是,在 PUT 请求中进行资源更新,但是不需要改变当前展示给用户的页面,那么返回 204 No Content。如果新创建了资源,那么返回 201 Created 。如果页面需要更新以反映更新后的资源,那么需要返回 200

所以大家在做 RestFul API 接口是,store、update 什么的,别全 200 200 的返回了,不正规。https://learnku.com/articles/28056

json_encode 中文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public/index.php

$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

// 取到内容https://learnku.com/articles/28054#topnav
$content = $response->original;

// 检查原始内容的类型是否需要转 json
if ($content instanceof Arrayable ||
$content instanceof Jsonable ||
$content instanceof ArrayObject ||
$content instanceof JsonSerializable ||
is_array($content)) {
// 重新设置响应内容
$response->setContent(json_encode($content, JSON_UNESCAPED_UNICODE));
}

$response->send();

升级至Laravel 5.7报错setTrustedProxies() must be of the type integer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
打开 App\Http\Middleware\TrustProxies

将代码:http://www.ithov.net/php/1644.html

protected $headers = [
Request::HEADER_FORWARDED => 'FORWARDED',
Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR',
Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
];

修改为:

protected $headers = Request::HEADER_X_FORWARDED_ALL;

openssl_encrypt() expects parameter 1 to be string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
5.5 升级到 5.5.42 后遇到的 Cookie 序列化问题
Laravel 新版为了防止 PHP 对象的序列化/反序列化漏洞被利用,不再对 Cookie 值进行自动的序列化和反序列化处理。
\Cookie::queue('user', ['id' => 1, 'name' => 'admin'], 720, '/')

Laravel 更新到 v5.5.42 后,因为 Laravel 不再自动对 Cookie 值 ['id' => 1, 'name' => 'admin'] 进行序列化处理,而 openssl_encrypt ( string $data ... ) 只能加密字符串数据,这个时候程序就会抛出错误:openssl_encrypt() expects parameter 1 to be string, array given。

解决方法:https://segmentfault.com/a/1190000018799611

新版里面在中间件 AppHttpMiddlewareEncryptCookies 新增静态属性 $serialize,当设置为 true 时可开启 Cookie 值的自动序列化和反序列化处理。
/**
* Indicates if cookies should be serialized.
*
* @var bool
*/
protected static $serialize = true;
【推荐】将 Cookie 值使用 JSON 函数编码成字符串后再进行存储(获取 Cookie 值后需调用 JSON 函数进行解码)。
\Cookie::queue('user', json_encode(['id' => 1, 'name' => 'admin']), 720, '/');

多库下的DB::transaction()事务失效

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
DB::transaction(function () use ($uid, $roleId) {
RoomUserRole::insert([
'uid' => $uid,
'role_id' => $roleId,
'created_at' => LARAVEL_START,
'updated_at' => LARAVEL_START
]);

RoomUserRoleLog::insert([
'uid' => $uid,
'handle_type' => 1,
'admin_uid' => Auth::user()->id,
'created_at' => LARAVEL_START,
'updated_at' => LARAVEL_START
]);

});
mysql 第二句会报错抛出一个异常, 查看数据库时第一句依然出入成功
原文:https://blog.csdn.net/mathphp/article/details/82593779
DB::transaction()使用的是默认库的事务操作。所以要指定哪个数据库的事务

DB::connection('mysql2')->transaction(function () use ($uid, $roleId) {
RoomUserRole::insert([
'uid' => $uid,
'role_id' => $roleId,
'created_at' => LARAVEL_START,
'updated_at' => LARAVEL_START
]);

RoomUserRoleLog::insert([
'uid' => $uid,
'handle_type' => 1,
'admin_uid' => Auth::user()->id,
'created_at' => LARAVEL_START,
'updated_at' => LARAVEL_START
]);

}); // 这样你会发现事务才正常回滚
DB::connection('mysql_chat_room')->beginTransaction();
DB::connection('mysql_chat_room')->commit();
DB::connection('mysql_chat_room')->rollBack(); // 指定库,不然都会跑默认配置库的事务

执行时间和内存消耗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getElapsedTime(int $decimals  = 2)
{
return number_format(microtime(true) - request()->server('REQUEST_TIME_FLOAT'), $decimals) . ' s';
}
内存使用:


function getMemoryUsage(precision = 2)
{
$size = memory_get_usage(true);

$unit = ['b', 'kb', 'mb', 'gb', 'tb', 'pb'];

return round($size / pow(1024, ($i = floor(log($size, 1024)))), $precision) . ' ' . $unit[$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
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// 针对命令显示帮助信息
php artisan --help OR -h
// 抑制输出信息
php artisan --quiet OR -q
// 打印 Laravel 的版本信息
php artisan --version OR -V
// 不询问任何交互性的问题
php artisan --no-interaction OR -n
// 强制输出 ANSI 格式
php artisan --ansi
// 禁止输出 ANSI 格式
php artisan --no-ansi
// 显示当前命令行运行的环境
php artisan --env
// -v|vv|vvv 通过增加 v 的个数来控制命令行输出内容的详尽情况: 1 个代表正常输出, 2 个代表输出更多消息, 3 个代表调试
php artisan --verbose
// 移除编译优化过的文件 (storage/frameworks/compiled.php)
php artisan clear-compiled
// 显示当前框架运行的环境
php artisan env
// 显示某个命令的帮助信息
php artisan help
// 显示所有可用的命令
php artisan list
// 进入应用交互模式
php artisan tinker
// 配合 dump() 函数调试数据
php artisan dump-server
// 进入维护模式
php artisan down
// 退出维护模式
php artisan up
// 优化框架性能
// --force 强制编译已写入文件 (storage/frameworks/compiled.php)
// --psr 不对 Composer 的 dump-autoload 进行优化
php artisan optimize [--force] [--psr]
// 更改前端预设
// type_name (可以是 none, bootstrap, vue, react)
php artisan preset [options] [--] type_name
// 启动内置服务器
php artisan serve
// 更改默认端口
php artisan serve --port 8080
// 使其在本地服务器外也可正常工作
php artisan serve --host 0.0.0.0
// 更改应用命名空间
php artisan app:name namespace
// 清除过期的密码重置令牌
php artisan auth:clear-resets

// 清空应用缓存
php artisan cache:clear
// 移除 key_name 对应的缓存
php artisan cache:forget key_name [<store>]
// 创建缓存数据库表 migration
php artisan cache:table

// 合并所有的配置信息为一个,提高加载速度
php artisan config:cache
// 移除配置缓存文件
php artisan config:clear

// 程序内部调用 Artisan 命令
$exitCode = Artisan::call('config:cache');
// 运行所有的 seed 假数据生成类
// --class 可以指定运行的类,默认是: "DatabaseSeeder"
// --database 可以指定数据库
// --force 当处于生产环境时强制执行操作
php artisan db:seed [--class[="..."]] [--database[="..."]] [--force]

// 基于注册的信息,生成遗漏的 events 和 handlers
php artisan event:generate
// 罗列所有事件和监听器
php artisan event:list
// 缓存事件和监听器
php artisan event:cache
// 清除事件和监听器缓存
php artisan event:clear

// 生成新的处理器类
// --command 需要处理器处理的命令类名字
php artisan handler:command [--command="..."] name
// 创建一个新的时间处理器类
// --event 需要处理器处理的事件类名字
// --queued 需要处理器使用队列话处理的事件类名字
php artisan handler:event [--event="..."] [--queued] name

// 生成应用的 key(会覆盖)
php artisan key:generate

// 发布本地化翻译文件到 resources 文件下
// locales: 逗号分隔,如 zh_CN,tk,th [默认是: "all"]
php artisan lang:publish [options] [--] [<locales>]

// 创建用户认证脚手架
php artisan make:auth
// 创建 Channel 类
php artisan make:channel name
// 在默认情况下, 这将创建未加入队列的自处理命令
// 通过 --handler 标识来生成一个处理器, 用 --queued 来使其入队列.
php artisan make:command [--handler] [--queued] name
// 创建一个新的 Artisan 命令
// --command 命令被调用的名称。 (默认为: "command:name")
php artisan make:console [--command[="..."]] name
// 创建一个新的资源控制器
// --plain 生成一个空白的控制器类
php artisan make:controller [--plain] name
php artisan make:controller App\\Admin\\Http\\Controllers\\DashboardController
// 创建一个新的事件类
php artisan make:event name
// 创建异常类
php artisan make:exception name
// 创建模型工厂类
php artisan make:factory name
// 创建一个队列任务文件
php artisan make:job
// 创建一个监听者类
php artisan make:listener name
// 创建一个新的邮件类
php artisan make:mail name
// 创建一个新的中间件类
php artisan make:middleware name
// 创建一个新的迁移文件
// --create 将被创建的数据表.
// --table 将被迁移的数据表.
php artisan make:migration [--create[="..."]] [--table[="..."]] name
// 创建一个新的 Eloquent 模型类
php artisan make:model User
php artisan make:model Models/User
// 新建一个消息通知类
php artisan make:notification TopicRepliedNotification
// 新建一个模型观察者类
php artisan make:observer UserObserver
// 创建授权策略
php artisan make:policy PostPolicy
// 创建一个新的服务提供者类
php artisan make:provider name
// 创建一个新的表单请求类
php artisan make:request name
// 创建一个 API 资源类
php artisan make:resource name
// 新建验证规则类
php artisan make:rule name
// 创建模型脚手架
// <name> 模型名称,如 Post
// -s, --schema=SCHEMA 表结构如:--schema="title:string"
// -a, --validator[=VALIDATOR] 表单验证,如:--validator="title:required"
// -l, --localization[=LOCALIZATION] 设置本地化信息,如:--localization="key:value"
// -b, --lang[=LANG] 设置本地化语言 --lang="en"
// -f, --form[=FORM] 使用 Illumintate/Html Form 来生成表单选项,默认为 false
// -p, --prefix[=PREFIX] 表结构前缀,默认 false
php artisan make:scaffold [options] [--] <name>
// 生成数据填充类
php artisan make:seeder
// 生成测试类
php artisan make:test

// 数据库迁移
// --database 指定数据库连接(下同)
// --force 当处于生产环境时强制执行,不询问(下同)
// --path 指定单独迁移文件地址
// --pretend 把将要运行的 SQL 语句打印出来(下同)
// --seed Seed 任务是否需要被重新运行(下同)
php artisan migrate [--database[="..."]] [--force] [--path[="..."]] [--pretend] [--seed]
// 创建迁移数据库表
php artisan migrate:install [--database[="..."]]
// Drop 所有数据表并重新运行 Migration
php artisan migrate:fresh
// 重置并重新运行所有的 migrations
// --seeder 指定主 Seeder 的类名
php artisan migrate:refresh [--database[="..."]] [--force] [--seed] [--seeder[="..."]]
// 回滚所有的数据库迁移
php artisan migrate:reset [--database[="..."]] [--force] [--pretend]
// 回滚最最近一次运行的迁移任务
php artisan migrate:rollback [--database[="..."]] [--force] [--pretend]
// migrations 数据库表信息
php artisan migrate:status

// 为数据库消息通知创建一个表迁移类
php artisan notifications:table
// 清除缓存的 bootstrap 文件
php artisan optimize:clear
// 扩展包自动发现
php artisan package:discover

// 为队列数据库表创建一个新的迁移
php artisan queue:table
// 监听指定的队列
// --queue 被监听的队列
// --delay 给执行失败的任务设置延时时间 (默认为零: 0)
// --memory 内存限制大小,单位为 MB (默认为: 128)
// --timeout 指定任务运行超时秒数 (默认为: 60)
// --sleep 等待检查队列任务的秒数 (默认为: 3)
// --tries 任务记录失败重试次数 (默认为: 0)
php artisan queue:listen [--queue[="..."]] [--delay[="..."]] [--memory[="..."]] [--timeout[="..."]] [--sleep[="..."]] [--tries[="..."]] [connection]
// 查看所有执行失败的队列任务
php artisan queue:failed
// 为执行失败的数据表任务创建一个迁移
php artisan queue:failed-table
// 清除所有执行失败的队列任务
php artisan queue:flush
// 删除一个执行失败的队列任务
php artisan queue:forget
// 在当前的队列任务执行完毕后, 重启队列的守护进程
php artisan queue:restart
// 对指定 id 的执行失败的队列任务进行重试(id: 失败队列任务的 ID)
php artisan queue:retry id
// 指定订阅 Iron.io 队列的链接
// queue: Iron.io 的队列名称.
// url: 将被订阅的 URL.
// --type 指定队列的推送类型.
php artisan queue:subscribe [--type[="..."]] queue url
// 处理下一个队列任务
// --queue 被监听的队列
// --daemon 在后台模式运行
// --delay 给执行失败的任务设置延时时间 (默认为零: 0)
// --force 强制在「维护模式下」运行
// --memory 内存限制大小,单位为 MB (默认为: 128)
// --sleep 当没有任务处于有效状态时, 设置其进入休眠的秒数 (默认为: 3)
// --tries 任务记录失败重试次数 (默认为: 0)
php artisan queue:work [--queue[="..."]] [--daemon] [--delay[="..."]] [--force] [--memory[="..."]] [--sleep[="..."]] [--tries[="..."]] [connection]

// 生成路由缓存文件来提升路由效率
php artisan route:cache
// 移除路由缓存文件
php artisan route:clear
// 显示已注册过的路由
php artisan route:list

// 运行计划命令
php artisan schedule:run

// 为 session 数据表生成迁移文件
php artisan session:table
// 创建 "public/storage" 到 "storage/app/public" 的软链接
php artisan storage:link

// 从 vendor 的扩展包中发布任何可发布的资源
// --force 重写所有已存在的文件
// --provider 指定你想要发布资源文件的服务提供者
// --tag 指定你想要发布标记资源.
php artisan vendor:publish [--force] [--provider[="..."]] [--tag[="..."]]
php artisan tail [--path[="..."]] [--lines[="..."]] [connection]

// 缓存视图文件以提高效率
php artisan view:cache
// 清除视图文件缓存
php artisan view:clear

https://laravelcode.cn/docs/5.8

Laravel Excel

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
composer require maatwebsite/excel
php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider"

php artisan make:export UsersExport --model=User

<?php

namespace App\Exports;

use App\User;
use Maatwebsite\Excel\Concerns\FromCollection;

class UsersExport implements FromCollection
{
public function collection()
{
return User::all();
}
}
控制器中调用

use App\Exports\UsersExport;
use Maatwebsite\Excel\Facades\Excel;
use App\Http\Controllers\Controller;

class UsersController extends Controller
{
public function export()
{
return Excel::download(new UsersExport, 'users.xlsx');
}
}
https://learnku.com/articles/29089

模型过滤

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
public function index()
{
return Book::query()
->where('title', 'like', '%我们%')
->where('price', '>=', 30)
->where('publisher', '大地出版社')
->orderBy('id', 'DESC')
->get();
}

abstract class QueryFilter
{

protected $request;
protected $builder;

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

public function apply(Builder $builder)
{
$this->builder = $builder;

foreach ($this->filters() as $name => $value) {
if (method_exists($this, $name)) {
call_user_func_array([$this, $name], array_filter([$value]));
}
}

return $this->builder;
}

public function filters()
{
return $this->request->all();
}
}

class BookFilter extends QueryFilter
{
public function title($title)
{
return $this->builder->where('title', 'like', "%{$title}%");
}

public function price($price)
{
return $this->builder->where('price', '>=', "%{$price}%");
}

public function publisher($publisher)
{
return $this->builder->where('publisher', $publisher);
}
}
通过 URL 的参数来进行动态查询。

books?title=我们 // 查找标题含有我们的图书

books?title=我们&price=25 // 查找标题含有我们并且价格大于25元的图书
class Book extends Model
{
public function scopeFilter($query, QueryFilter $filters)
{
return $filters->apple($query);
}
}
最后,我们原来控制器的方法将改为下面的样子:

public function index(BookFilter $filters)
{
return Book::filter($filters)->get();
}
https://learnku.com/articles/29100#reply91762

Laravel 配置双模板

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
composer require whichbrowser/parser
php artisan make:middleware Template
class Template
{
protected $except = [];

public function handle($request, Closure $next)
{
$result = new WhichBrowser\Parser(getallheaders());
// 如果是桌面类型, 返回true
$isDesktop = $result->isType('desktop');
if ($isDesktop) {
// 加载pc端的模板文件
$path = resource_path('views/pc/');
} else {
// 加载mobile端的模板文件
$path = resource_path('views/mobile/');
}
// 获取视图查找器实例
$view = app('view')->getFinder();
// 重新定义视图目录
$view->prependLocation($path);
// 返回请求
return $next($request);
}
}
protected $middleware = [
\App\Http\Middleware\Template::class,
];
return view('registration.index', $data);
如从 PC 设备打开网页:加载 /resources/views/pc/registration/index.blade.php 模板

如从移动设备打开网页:加载 /resources/views/mobile/registration/index.blade.php 模板
https://learnku.com/articles/24789

助手函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
php artisan make:provider HelperServiceProvider
public function register()
{
foreach(glob(app_path('Helpers') . '/*.php') as $file) {
require_once $file;
}
}

App\Providers\HelperServiceProvider::class,
function carbon($time = null, $tz = null)
{
return new \Carbon\Carbon($time, $tz);
}
https://blog.foof.dev/posts/laravel-elegant-add-helper-function-0

创建无限极分类树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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']; // 根节点只有中华人民共和国,所以直接返回中国的所有子节点
}
https://learnku.com/articles/30088

任务重复执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
当前任务还没有执行完毕的时候另一个相同的任务也会执行,从而导致任务重复。例如想象一下我们执行每分钟生成一次报告的任务,在经过一段时间后,数据量变得很大导致执行时间多于 1 分钟,这样就会导致在上一个任务还没结束的时候另一个相同的任务开始执行

当我们在开会进行激烈的讨论时,我会从我桌子里拿出来一个尖叫鸡。只有手里拿着尖叫鸡的人才能说话,如果你没有拿着尖叫鸡你是不能说话的。你只能向会议主持人请示,只有在你拿到尖叫鸡的时候你才能说话否则只能等待。当你讲话完毕的时候,将尖叫鸡还给会议主持人,主持人会将尖叫鸡给到下一个人来让其说话。这样会确保人们不会互相交谈,同时他们也会有自己的时间来进行讲话。
public function create(Event $event)
{
return $this->cache->add($event->mutexName(), true, 1440);
}

public function exists(Event $event)
{
return $this->cache->has($event->mutexName());
}

public function forget(Event $event)
{
$this->cache->forget($event->mutexName());
}
register_shutdown_function(function () {
$this->removeMutex();
});
https://learnku.com/articles/30550

创建一个命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ php artisan make:command HashCommand
$signature 属性,设置命令名、命令参数、命令选项;
$description 属性,设置命令的描述信息;
handle() 方法,包含实际的命令逻辑代码。
修改 $signature 属性,设置 hash 命令的命令名、命令参数、命令选项:

protected $signature = 'hash {text} {--U|uppercase}';
public function handle()
{
$text = $this->argument('text');
$uppercase = $this->option('uppercase');

$md5text = $uppercase ? strtoupper(md5($text)) : md5($text);

$this->info("md5('{$text}') = $md5text");
}

$ php artisan hash "hello world"
md5('hello world') = 5eb63bbbe01eeed093cb22bb8f5acdc3
带上 -U 选项:

$ php artisan hash "hello world" -U
md5('hello world') = 5EB63BBBE01EEED093CB22BB8F5ACDC3

自定义 Laravel 的 404 页面

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
app/Exceptions/Handler.php 文件中修改 render 方法
public function render($request, Exception $exception)
{
if ($this->isHttpException($exception)) {
if ($exception->getStatusCode() == 404) {
return response()->view('errors.' . '404', [], 404);
}
}

return parent::render($request, $exception);
}创建一个新的视图 errors/404.blade.php 。

public function render($request, Exception $exception)
{
if ($this->isHttpException($exception)) {
if (view()->exists('errors.' . $exception->getStatusCode())) {
return response()->view('errors.' . $exception->getStatusCode(), [], $exception->getStatusCode());
}
}

return parent::render($request, $exception);
}
创建一个 自定义异常 。运行以下代码来创建一个名为 TestingHttpException 的异常。

php artisan make:exception TestingHttpException
在 app/Exceptions/Handler.php 文件中,修改 render 方法。

public function render($request, Exception $exception)
{
if ($exception instanceof TestingHttpException) {
return response()->view('errors.testing');
}
return parent::render($request, $exception);
}
如果异常是 TestingHttpException 的实例,它将返回 errors.testing 视图。https://learnku.com/laravel/t/27873

图片处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
composer require intervention/image  https://learnku.com/laravel/t/30978
// 创建一张空的画布,像素3628x1757,背景白色
$img = Image::canvas(3628, 1757, '#fff');

// 获取本地图片,可以获取input上传文件
$leftImage = Image::make(base_path().'/public/p.jpg')->resize(2255, 1305);
$rightImage = Image::make(base_path().'/public/f.jpg')->resize(1000, 1000);

// 插入到画布,left-top是距离左侧和顶部,值对应的是后面 100 100 处
$img->insert($leftImage, 'left-top', 100, 100);
$img->insert($rightImage, 'right-top', 100, 250);

// 插入文本,通过回调设置参数,file上传的是字体库,需要自己下载放入
$img->text('王*军 320830**********542', 1757, 1550, function($font) {
$font->file(base_path().'/public/fzjw.ttf');
$font->size(160);
$font->color('#000');
$font->align('center');
$font->valign('top');
$font->angle(0);
});
//可以直接返回图像,也可通过$img->save()进行保存图片
return $img->response('jpg');

取消事件触发

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
//获取model里面的事件
$dispatcher = YourModel::getEventDispatcher();

//禁用model里面的事件
YourModel::unsetEventDispatcher();

// 调用model的save方法
$yourInstance->save();

//启用事件
YourModel::setEventDispatcher($dispatcher);
//Visitor model有saved事件,某些地方某些情况监听到后做一些其他操作
protected $dispatchesEvents = [
'saved' => VisitorSavedEvent::class,
];
//有时候不需要触发事件,是可以取消的,可以调用model的update方法避开save()或者取消事件(推荐后者)。
// 获取来访信息
$visitor = Visitor::where('id', $request->visitor_id)->first();
//取消事件触发https://learnku.com/articles/31490
$dispatcher = Visitor::getEventDispatcher();
Visitor::unsetEventDispatcher();
$ret = $visitor->save();
Visitor::setEventDispatcher($dispatcher);
//5.7
YourModel::withoutEvents(function(){
// do something...
});

记录memcache mongodb运行时间

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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
cache   \vendor\laravel\framework\src\Illuminate\Cache\Repository.php

public function __construct(Store $store)
{
$this->start = microtime(true);
$this->store = $store;
}
protected function fireCacheEvent($event, $payload)
{
/**
* 添加cache响应时间
*/
$total = round((microtime(true) - $this->start) * 1000, 2);
$payload[] = $total;

if (isset($this->events))
{
$this->events->fire('cache.'.$event, $payload);
}
}
public function put($key, $value, $minutes)
{
$this->start = microtime(true);

$minutes = $this->getMinutes($minutes);

if ( ! is_null($minutes))
{
$this->store->put($key, $value, $minutes);

$this->fireCacheEvent('write', [$key, $value, $minutes]);
}
}
vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php
public function __construct(array $attributes = array())
{
if($this->connection == "mongodb") {
$this->_time_cost_start = microtime(true);
}

$this->bootIfNotBooted();

$this->syncOriginal();

$this->fill($attributes);
}
public function save(array $options = array())
{
if($this->connection == "mongodb") {
$this->_time_cost_start = microtime(true);
}
*/
protected function performInsert(Builder $query, array $options = [])
{
if ($this->fireModelEvent('creating') === false) return false;


if ($this->timestamps && array_get($options, 'timestamps', true))
{
$this->updateTimestamps();
}

$attributes = $this->attributes;

$conn = $query->getModel()->connection;
if($conn == "mongodb") {
unset($attributes['_time_cost_start']);
unset($attributes['_time_cost_total']);
}
protected function fireModelEvent($event, $halt = true)
{
if ( ! isset(static::$dispatcher)) return true;

/**
* 将监听事件类名去掉,去掉class
* 添加mongodb响应时间
*/
//$event = "eloquent.{$event}: ".get_class($this);
$event = "eloquent.{$event}";
$method = $halt ? 'until' : 'fire';

if($this->connection == "mongodb") {
$this->_time_cost_total = round((microtime(true) - $this->_time_cost_start) * 1000, 2);
}
return static::$dispatcher->$method($event, $this);
}

//Mysql event handle
$app['events']->listen('illuminate.query',function($sql, $binds, $time,$name) use ($app){
self::HandleMysqlSysLog($sql, $binds, $time, $name, $app);
});

//MongoDB event handle
$app['events']->listen('eloquent.saved',function($model) use ($app){
self::MongodbSysLog('saved', $app, $model);
});
$app['events']->listen('eloquent.deleted',function($model) use ($app){
self::MongodbSysLog('deleted', $app, $model);
});

//Memcache event handle
$app['events']->listen('cache.write',function($key, $value, $time, $total) use ($app){
self::MemcacheSysLog('put',$key,$total);
});
$app['events']->listen('cache.delete',function($key, $total) use ($app){
self::MemcacheSysLog('delete',$key,$total);
});
$app['events']->listen('cache.hit',function($key, $value, $total) use ($app){
self::MemcacheSysLog('get',$key,$total);
});
$app['events']->listen('cache.missed',function($key, $total) use ($app){
self::MemcacheSysLog('missed',$key,$total);
});

public static function MysqlSysLog($sql, $binds, $time, $name, $app){
foreach ($binds as $bind) {
$sql = preg_replace('/\?/', '\'' . $bind . '\'', $sql, 1);
}
//调用select时先判断使用读库还是写库,而insert/update/delete统一使用写库 Illuminate/Database/Connectors/ConnectionFactory.php Illuminate/Database/Connection.php
$connection = $app['db']->getconnections();
if ($name) {
if (preg_match('#^select[\s\S]*$#', $sql, $match)) {
$pdo = $connection[$name]->getreadPdo();
$host = explode(' ', $pdo->getAttribute(\PDO::ATTR_CONNECTION_STATUS))[0];
$server_info = $pdo->getAttribute(\PDO::ATTR_SERVER_INFO);
} else {
$pdo = $connection[$name]->getpdo();
$host = explode(' ', $pdo->getAttribute(\PDO::ATTR_CONNECTION_STATUS))[0];
$server_info = $pdo->getAttribute(\PDO::ATTR_SERVER_INFO);
}

$data = [
'rs' => 'mysql://' . $host.':'.$connection[$name]->getconfig('port') . '/' . $connection[$name]->getconfig('name'),
'cmd' => $sql,
'output_content' => [
'database' => $connection[$name]->getconfig('database'),
'host' => $host,
'name' => $connection[$name]->getconfig('name'),
'port' => $connection[$name]->getconfig('port'),
'driver' => $connection[$name]->getconfig('driver'),
'server_info' => $server_info
],
't_total' => $time
];
self::SaveSysLog($data);
}
}
public static function MemcacheSysLog($cmd, $key, $total){
if(app('cache')->getStore() instanceof \Illuminate\Cache\MemcachedStore){
$servers = implode('/', array_keys(app('cache')->getMemcached()->getStats()));
$data = [
'rs' => 'mc://' . $servers,
'cmd' => $cmd . ' ' . $key,
't_total' => $total,
'code' => 0
];
}
self::SaveSysLog($data);
}
public static function MongodbSysLog($cmd, $app, $model){
$connection = $app['db']->getconnections();
if(isset($connection['mongodb'])) {
$data = [
'rs' => 'Mongo://' . $connection['mongodb']->getconfig('host') . ':' . $connection['mongodb']->getconfig('port') ,
'cmd' => $cmd,
'output_content' => [
'database' => $connection['mongodb']->getconfig('database'),
'host' => $connection['mongodb']->getconfig('host'),
'port' => $connection['mongodb']->getconfig('port'),
'driver' => $connection['mongodb']->getconfig('driver')
],
't_total' => $model->_time_cost_total,
'rs_content' => $model->toArray()['_id']
];
}

self::SaveSysLog($data);
}
public static function SaveSysLog($data){
if(php_sapi_name()=='cli'){
return ;
}

openlog('app',LOG_PID|LOG_ODELAY,LOG_LOCAL6);
try{
syslog(LOG_INFO,json_encode($data));
} catch (\Exception $e) {
\Log::info('syslog_error', ['data' => $data]);
}
closelog();
}

vendor/laravel/framework/src/Illuminate/Cache/MemcachedStore.php:164
public function get($key)
{
$this->start = microtime(true);
$value = $this->memcached->get($this->prefix.$key);
//$total = round((microtime(true) - $this->start) * 1000, 2);
$this->end = microtime(true);

$this->handleMcSysLog('get', $key);
if ($this->memcached->getResultCode() == 0)
{
return $value;
}
}

private function handleMcSysLog($cmd, $key)
{
$total = round(($this->end - $this->start) * 1000, 2);
$rs = implode('/', array_keys($this->memcached->getStats()));
$data = [
'rs' => 'mc://' . $rs,
'rs_type' => 6,
'cmd' => $cmd . ' ' . $key,
't_total' => $total,
'code' => $this->memcached->getLastErrorCode(),
'ext' => $this->memcached->getLastErrorMessage()
];


}

记录Redis运行时间

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
predis

use \Predis\Command\CommandInterface;
use \Predis\Connection\StreamConnection;

class RedisLog extends StreamConnection
{
private $tstart = 0;
private $debugBuffer = array();

public function connect()
{
$this->tstart = microtime(true);

parent::connect();
}

private function storeDebug(CommandInterface $command, $direction)
{
$firtsArg = $command->getArguments();
$timestamp = round((microtime(true) - $this->tstart) * 1000,2);
$log = [];
$log['cmd'] = $command->getId();
$log['key'] = isset($firtsArg) ? $firtsArg : ' ';
$log['server'] = "$direction $this";
$log['time'] = $timestamp;
dump(['rs' => 'redis://' . trim($log['server']), 'cmd' => $command->getId(), 't_total' => $timestamp]);

// $this->debugBuffer[] = &$log;
unset($log);
}

public function writeRequest(CommandInterface $command)
{
$this->tstart = microtime(true);//执行命令行的时候重新算 这样就不会叠加
parent::writeRequest($command);

// $this->storeDebug($command, '->');
}

public function readResponse(CommandInterface $command)
{
$response = parent::readResponse($command);
$this->storeDebug($command, '');

return $response;
}

public function getDebugBuffer()
{
return $this->debugBuffer;
}

public static function debug()
{
$options = array(
'connections' =>[
'tcp' => '\App\Services\RedisLog',
],
);

$client = new \Predis\Client(config('database.redis'), $options);
$client->get('redis:test');

var_export($client->getConnection());
/* OUTPUT:
array (
0 => 'SELECT 15 -> 127.0.0.1:6379 [0.0008s]',
1 => 'SELECT 15 <- 127.0.0.1:6379 [0.001s]',
2 => 'SET foo -> 127.0.0.1:6379 [0.001s]',
3 => 'SET foo <- 127.0.0.1:6379 [0.0011s]',
4 => 'GET foo -> 127.0.0.1:6379 [0.0013s]',
5 => 'GET foo <- 127.0.0.1:6379 [0.0015s]',
6 => 'INFO -> 127.0.0.1:6379 [0.0019s]',
7 => 'INFO <- 127.0.0.1:6379 [0.0022s]',
)
*/
}
}

记录guggle运行时间

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

use Illuminate\Http\Request;
use GuzzleHttp\Event\EmitterInterface;
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Event\EndEvent;
use GuzzleHttp\Event\Emitter;

class GuzzleEmitter extends Emitter
{
public function __construct($rs_info = 'api_time')
{
$start = 0;
$this->on('before', function (BeforeEvent $event, $name) use(&$start, $rs_info){
$start = microtime(true);
});
$this->on('end', function (EndEvent $event) use(&$start, $rs_info){
$url = $event->gettransferInfo()['url'];
$parse = parse_url($url);
if ($event->getException()) {
dump(['rs' => $url, 'ext1' => sprintf('%s://%s%s',$parse['scheme'],$parse['host'],$parse['path']), 'cmd' => $event->getRequest()->getmethod(), 'code' => $event->gettransferInfo()['http_code'], 't_total' => intval((microtime(true)-$start)*1000), 'p_ip' => $event->gettransferInfo()['primary_ip'], 'req_id'=>defined('REQUEST_ID')?(REQUEST_ID):'',]);
} else {
$method=$event->getRequest()->getmethod();
$post_data=($method=='POST')?((string)$event->getRequest()->getBody()):'';
dump([
'rs' => $url,
'cmd' => $method,
'code' => $event->gettransferInfo()['http_code'],
't_total' => intval((microtime(true)-$start)*1000),
'p_ip' => $event->gettransferInfo()['primary_ip'],
'rs_content'=>$post_data,
'ext1' => sprintf('%s://%s%s',$parse['scheme'],$parse['host'],$parse['path']),
'req_id'=>defined('REQUEST_ID')?(REQUEST_ID):'',
]);
}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
默认情况下,Laravel 生成的所有 cookie 都是经过加密和签名的,防止被非法篡改。但 Laravel 也支持不对 cookie 加密,具体做法如下:

修改 app/Http/Middleware 目录中 App\Http\Middleware\EncryptCookies 中间件的 $except 属性,该属性是个数组,把不需加密的 cookie 名加入到该数组属性中就可以了:

/**
* 不需要被加密的cookies名称
*
* @var array
*/
protected $except = [
'cookie_name',
];
1
2
3
4
5
6
7
8
9
10
11
12
13
//获取cookie的三种尝试
Cookie::get('cookie_name');
request()->cookie('cookie_name');
$request->cookie('cookie_name');
//返回值均为null laravel 设置 cookie 是加密之后的,而我获取的 cookie 是外来 cookie,由于解密失败,所以怎么获取都是空
https://learnku.com/laravel/t/31939
//添加到cookie名称到 App\Http\Middleware\EncryptCookies 的 排除名单 中:
class EncryptCookies extends Middleware
{
protected $except = [
'cookie_name',
];
}

Laravel Excel 导出 csv

1
2
3
4
5
6
7
8
9
10
11
12
13
Laravel Excel 2.0 的版本和 3.0 的版本有很大的区别,使用 composer 加载的时候要注意版本,3.02.0 的语法基本不同了
composer require maatwebsite/excel ~2.1
php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider"
config/excel.php 中找到 csv,把里面的 use_bom=>false 改为 use_bom=>true,这样导出的 csv 文件就会有 bom 头,不会出现乱码。

Excel::create(iconv('UTF-8', 'GBK', '文章点赞数据'),function ($excel) use($params){
$excel->sheet('score',function ($sheet) use($params){
$sheet->rows($params);
});
})->export('csv');
https://learnku.com/articles/32002
按照 semver https://semver.org/lang/zh-CN/ 的规则,主版本号本来就是做不兼容 API 修改的,升级了主版本号,语法当然不一样了。这不算是坑,只是你不了解而已。
https://github.com/rap2hpoutre/fast-excel maatwebsite/excel 感觉太耗内存了

项目开发规范

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
常用的 HTTP 动词有下面五个。

GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。
所有 URL 必须 是全小写,所有的路由都要用 name

Route::get('user/create','UserController@create')->name('admin.user.create');
一个是打开 create 页面的请求,一个是 create 数据的请求,其中的路由规范如下:

路由采用 reseful API 的设计规范

Route::get('member/create', 'MemberController@create')->name('admin.member.create');
Route::post('member/store', 'MemberController@store')->name('admin.member.store');
ORM 的设计规范

框架的 ORM 只单纯的做模型关联使用,另外新建 Repsitory 层做数据库的逻辑处理,

Repsitory 的命名采用驼峰法命令规范,比如

<?php
namespace App\Repository\Admin;
use App\Models\User;

class UserRepository {
Controller 的设计规范

数据验证在 request 层进行处理,保证进行控制器的数据是干净的。
对于数据的处理则调用对应的 Repsitory 层处理,控制器只做响应和返回对应的数据。https://learnku.com/articles/32141

Laravel 中 Facade

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

Route::get('/', function () {
return view('welcome');
});

这里 Route 就是用 Facade 实现类方法 get 的静态调用。

Laravel 中的 Facade 解决类什么问题?

在 php 中,很多情况都需要使用一个容器获取到所有的对象,然后再调用改对象的方法,这样在编写代码的时候就会看到很长的一个调用链。例如:
在 Yii2 中,几乎所有的系统类都是在 app 容器当中,对这些系统类进行操作都需要执行 Yii::$app->route 获取到类实例,然后在执行方法 Yii::$app->route->get()。但是如果用 Facade 实现之后的调用就是 Route::get()。这样的写法是的代码更加简洁。

Laravel 中 Facade 是怎么实现的?

思路是通过__callStatic 魔术方法将方法调用代理到实际的对象方法中去。


abstract class Facade
{
...
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
...
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}

if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}

return static::$resolvedInstance[$name] = static::$app[$name];
}
...
public static function getFacadeAccessor(){
//子类返回类名
}
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();

if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}

return $instance->$method(...$args);
}
}

class Route extends Facade{
public static function getFacadeAccessor(){
return 'router';
}
}
根据每个 Facade 中提供的 getFacadeAccessor 返回实际的对象类名,获取类对象。每个类对象一旦创建,就放在一个静态数组中,因此在一次请求中最多只会被创建一次。

有没有其他的实现方式?

从上面的代码可以看到,其实核心就是一个静态代理的功能。那么有没有其他的实现方式了呢?

class Facade{
public static $instance;
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}

if (isset(static::$instance)) {
return static::$instance;
}

return static::$instance = static::$app[$name];
}
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();

if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}

return $instance->$method(...$args);
}
}

class Route extends Facade{

}
可以看出,上面的方式也能够实现静态代理,类似于 Facade 的功能。都是通过魔术方法实现。作用上,都简化了代码编写。

两种不同实现方式的区别

第二种实现方式有一个很大的缺点,那就是必须继承 Facade 类。PHP 本身只能继承一个类,所以第二种实现方式对于一些需要继承其他类的对象是不适合的。https://learnku.com/articles/31964

Laravel 默认邮箱登录改成用户名登录

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
默认的,Laravel 使用的是 Illuminate\Foundation\Auth\AuthenticatesUsers 这个 trait 完成登录功能的。通过观察 AuthenticatesUsers 的代码,发现下面一段很有意思的代码:

protected function validateLogin(Request $request)
{
$this->validate($request, [
$this->username() => 'required|string',
'password' => 'required|string',
]);
}
public function username()
{
return 'email';
}
可以看到,是应为 trait 里定义了用户名就是 email,所以才会使得验证的时候通过用户邮箱验证。所以我们只需要定义一个 trait,覆盖 AuthenticatesUsers 中的 username() 方法即可实现后端代码通过用户名验证登录。

新增的 trait 代码

namespace App\Utils;

use Illuminate\Foundation\Auth\AuthenticatesUsers as LaravelAuthenticatesUsers;
trait AuthenticatesUsers {
use LaravelAuthenticatesUsers;
public function username()
{
return 'name';
}
}
其实还有另一个简单的修改方式,直接在 LoginController 中新增 username() 方法。由于当前定义方法会覆盖 trait 的方法,因此也能达到修改的目的。但是会破坏登录代码的整体一致性,所以最好还是通过新增 trait 的方式实现。

同时要记得修改前端 blade 文件中对输入参数的验证,然后就可以使用用户名登录了 https://learnku.com/articles/32431

设置错误等级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 /vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php:12
public function bootstrap(Application $app)
{
$this->app = $app;

if ($app->environment('production')) {
error_reporting(E_ERROR);
} else {
error_reporting(-1);
}

set_error_handler([$this, 'handleError']);

set_exception_handler([$this, 'handleException']);

register_shutdown_function([$this, 'handleShutdown']);

if ( $app->environment('production'))
{
ini_set('display_errors', 'Off');
}
}

图片添加水印

1
2
3
4
5
6
7
8
9
composer require intervention/image
在我的测试图片文件夹 images 里有一张主图 main.png 和一张水印图 watermark.png。

public function addWatermark(){
$img = Image::make(public_path('images/main.png'));
$img->insert(public_path('watermark.png'),'bottom-right',10, 10);
$img->save();
}
https://learnku.com/laravel/t/32414

Laravel 配置二级目录访问

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
域名 http://test.local 正常访问,对应项目地址 E:/Project/Test
域名 http://test.local/module2 正常访问,对应项目地址 E:/Project/Module2
废话不说上配置https://learnku.com/articles/32499

Nginx 配置文件 test_local.conf

server {
listen 80;
server_name test.local;

root E:/Project/Test;
index index.php index.html;

# 转发到端口
location /module2/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $proxy_host;
proxy_set_header X-Forwarded-Port $proxy_port;
proxy_pass http://127.0.0.1:8081/;
}

location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}

server {
listen 8081;
server_name 127.0.0.1;
root E:/Project/Module2;
index index.php index.html;

location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
module2 的 Laravel 配置 Url

.env

APP_URL=http://test.local/module2
app/Providers/AppServiceProvider

public function boot()
{
// 用于修正URL生成的链接
app('url')->forceRootUrl(config('app.url'));
}
public function register()
{
// 用于修正分页的链接
\Illuminate\Pagination\Paginator::currentPathResolver(function () {
return $this->app['url']->current();
});
}

Lumen 日志自定义

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
1、在 Providers 目录建立 LogServiceProvider.php 文件 代码如下

<?php
namespace App\Providers;

use Carbon\Laravel\ServiceProvider;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Processor\IntrospectionProcessor;

class LogServiceProvider extends ServiceProvider
{
public function boot()
{
$handlers[] = (new RotatingFileHandler(storage_path('logs/lumen.log'), 0))
->pushProcessor(new IntrospectionProcessor())
->setFormatter(new LineFormatter(null, null, true, true));

$this->app['log']->setHandlers($handlers);
}
}
2、在 bootstrap/app.php 注册该服服

$app->register(App\Providers\LogServiceProvider::class);
3、使用测试

app('log')->info('我是测试日志');
4、生成的日志信息

[2018-04-11 10:25:29.313456] local.INFO: 我是来测试日志的 {"content":1111}
https://learnku.com/articles/32543

Lumen 实时记录 SQL

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
1、在 Listeners 目录新建 QueryListener.php 文件 代码如下:

<?php
namespace App\Listeners;

use Illuminate\Database\Events\QueryExecuted;

class QueryListener
{
public function handle(QueryExecuted $event)
{
if (env('APP_ENV', 'production') == 'local') {
$sql = str_replace('?', "'%s'", $event->sql);
$log = vsprintf($sql, $event->bindings);
$this->put_log('sql', $log);
}
}

private function put_log($file = 'app', $content = '')
{
$data = date('Y-m-d');
$cut_line = str_repeat("-", 100);
is_dir(storage_path('logs/sql')) or mkdir (storage_path('logs/sql'), 0777, true); // 文件夹不存在则创建
$content = '[' . date('Y-m-d H:i:s') . "]" . $content;
@file_put_contents(storage_path('logs/sql/' . $file . '-' . $data . '.log'), $content . "\n" . $cut_line . "\n\n", FILE_APPEND);
}
}
2、在 Providers/EventServiceProvider.php 添加如下代码

<?php

namespace App\Providers;

use Laravel\Lumen\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [

'Illuminate\Database\Events\QueryExecuted' => [
'App\Listeners\QueryListener'
],
];
}
3、在 bootstrap/app.php 文件中开启 EventServiceProvider 注册即可

$app->register(App\Providers\EventServiceProvider::class);
4、接下来写一个 sql 语句就能在 storage/logs/sql 看到生成的 sql 日志了

app('db')->where('id', '>', '5')->get();
https://learnku.com/articles/32540

替换 Word 里面变量并导出 PDF

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
    composer require phpoffice/phpword
use PhpOffice\PhpWord\TemplateProcessor;
$path = storage_path('aa.docx');
// 生成world 存放目录
$filePath = storage_path('contract.docx');
// 声明模板象并读取模板内容
$templateProcessor = new TemplateProcessor($path);
// 替换模板内容
$templateProcessor->setValue('contract', '北京乙方'); // 乙方

// 生成新的 world
$templateProcessor->saveAs($filePath);
apt-get install unoconv
#如果报错请求服务器语言设置为 LANG=”en_US.UTF-8″

#使用命令把 word 转为 pdf
unoconv -f pdf aa.docx
#这个时候在当前目录下就会有一个 aa.pdf 的文件出来
#但是会发现如果是中文的情况下转出来的 pdf 是乱码该如何解决
# 把电脑本机的宋体上传到服务器字体目录下 /usr/share/fonts 新建一个目录 win 或者其它,把中文字体上传到该目录下
apt-get install mkfontscale #安装这个工具
# 进入到/usr/share/fonts/win/ 执行命令
mkfontscale && sudo mkfontdir && sudo fc-cache -fv
# 然后重启服务器让字体生效
reboot
# 最后在执行
unoconv -f pdf aa.docx
# 看是不是中文乱码的问题解决了
使用 php 的执行 shell 的函数来调用该函数自动生成即可

shell_exec('/usr/binunoconv -f pdf aa.docx')

主键进行批量更新

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
/**
* 根据Id批量更新数据,可以放在model里面,使得每个model都是调用这个方法
* @author yezi
* @param array $multipleData
* @return bool
*/

public static function updateBatch($multipleData = array())
{
$tableName = \DB::getTablePrefix() . app(Contract::class)->getTable();
if(!is_array($multipleData)){
throw new CustomValidationException('must be an array',null,'6001');
}
foreach ($multipleData as &$row){

if(!array_key_exists('id',$row)){

throw new CustomValidationException('参数错误,缺少主键',null,'6001');

}

$row[Model::FIELD_UPDATED_AT] = Carbon::now();

}

if ($tableName && !empty($multipleData)) {

$updateColumn = array_keys($multipleData[0]);

$referenceColumn = $updateColumn[0];

unset($updateColumn[0]);

$whereIn = "";

$q = "UPDATE " . $tableName . " SET ";

foreach ($updateColumn as $uColumn) {

$q .= $uColumn . " = CASE ";

foreach ($multipleData as $data) {

$q .= "WHEN " . $referenceColumn . " = " . $data[$referenceColumn] . " THEN '" . $data[$uColumn] . "' ";

}

$q .= "ELSE " . $uColumn . " END, ";

}

foreach ($multipleData as $data) {

$whereIn .= "'" . $data[$referenceColumn] . "', ";

}

$q = rtrim($q, ", ") . " WHERE " . $referenceColumn . " IN (" . rtrim($whereIn, ', ') . ")";

return \DB::update(\DB::raw($q));

} else {

return false;

}

}https://geixue.com/blogs/channels/laravel/laravel-shares-a-way-to-do-bulk-updates-based-on-the-primary-key-qjhqc5

Laradock 中使用 beanstalkd

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

docker-compose up -d beanstalkd
composer require pda/pheanstalk
更改 .env 文件

设置 QUEUE_CONNECTION 为 beanstalkd, 设置 beanstalkd 为默认队列驱动。
新增配置项 QUEUE_HOST=beanstalkd,代表使用 laradock 中 beanstalkd 的 host 以及端口,端口默认值为 11300
QUEUE_HOST=beanstalkd
QUEUE_CONNECTION=beanstalkd
更改 config/queue.php 文件

修改 connections 下面的 beanstalkd 数组中 host 的值为 env('QUEUE_HOST', 'localhost'),意思就是默认使用 .env 文件中定义的 QUEUE_HOST 值

'connections' => [
.
.
.

'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => env('QUEUE_HOST', 'localhost'),
'queue' => 'default',
'retry_after' => 90,
'block_for' => 0,
],
.
.
.
],
使用 beanstalkd_console 从 Web 界面管理您的队列

运行 Beanstalkd 控制台容器
docker-compose up -d beanstalkd-console
访问 http://localhost:2080/ https://learnku.com/articles/32366

注册了多少条路由

1
2
$ php artisan route:list | wc -l | awk '{print $1 - 4}'
获取到上面的统计到的行数再减去 4(因为 list 中包含了 3 行制表符 和 1 行表头),最终得到了你的路由数量

Laravel ORM SQL 语句查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在 app/Providers/AppServiceProvider 中的 boot 方法中新增以下代码:

\Illuminate\Database\Query\Builder::macro('sql', function () {
$bindings = $this->getBindings();
$sql = str_replace('?','%s',$this->toSql());
return vsprintf($sql, $bindings);
});
\Illuminate\Database\Eloquent\Builder::macro('sql', function(){
return ($this->getQuery()->sql());
});
二、使用方式

查询带参数的 SQL 语句可在 ORM 后调用方法

// 正确用法
User::query()->where('id', 1)->sql();

// 错误用法
User::query()->where('id', 1)->get()->sql();
User::query()->where('id', 1)->first()->sql();
https://learnku.com/articles/33484

cache

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
use Illuminate\Support\Facades\Cache;

Cache::get('xxx');
app('Illuminate\Contracts\Cache\Factory')->get('xxx');
app('Illuminate\Cache\CacheManager')->get('xxx');
app('cache')->get('xxx');
app()['cache']->get('xxx');
cache('xxx');
function cache()
{
$arguments = func_get_args();

if (empty($arguments)) {
return app('cache');
}

if (is_string($arguments[0])) {
return app('cache')->get(...$arguments);
}

if (! is_array($arguments[0])) {
throw new Exception(
'When setting a value in the cache, you must pass an array of key / value pairs.'
);
}

if (! isset($arguments[1])) {
throw new Exception(
'You must specify an expiration time when setting a value in the cache.'
);
}

return app('cache')->put(key($arguments[0]), reset($arguments[0]), $arguments[1]);
https://learnku.com/articles/33628

Laravel 的请求是怎么到达控制器

1
2


分类列表无限分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static function getTree($data,$pId,$level = 0){
static $list = [];
foreach ($data as $key => $value){
//第一次遍历,找到父节点为根节点的节点 也就是pid=0的节点
if ($value['parent_id'] == $pId){
//父节点为根节点的节点,级别为0,也就是第一级
$value['level'] = $level;
$value['display_name'] =$value['displayname'];
$value['displayname'] = str_repeat('━━', $value['level']).$value['displayname'];

//把数组放到list中
$list[] = $value;
//把这个节点从数组中移除,减少后续递归消耗
unset($data[$key]);
//开始递归,查找父ID为该节点ID的节点,级别则为原级别+1
self::getTree($data, $value['id'], $level+1);

}
}
return $list;
}
优雅的树形数据结构管理包,基于Closure Table模式设计. https://github.com/jiaxincui/closure-table

获取路由

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
$routeCollection = \Route::getRoutes();

foreach ($routeCollection as $value) {
$routes[] = [$value->getPath(),$value->methods()[0], $value->getActionName()];
}
Route::get('routes', function() {
$routeCollection = Route::getRoutes();

echo "<table style='width:100%'>";
echo "<tr>";
echo "<td width='10%'><h4>HTTP Method</h4></td>";
echo "<td width='10%'><h4>Route</h4></td>";
echo "<td width='10%'><h4>Name</h4></td>";
echo "<td width='70%'><h4>Corresponding Action</h4></td>";
echo "</tr>";
foreach ($routeCollection as $value) {
echo "<tr>";//sina/vendor/laravel/framework/src/Illuminate/Routing/Route.php:919
echo "<td>" . $value->getMethods()[0] . "</td>";
echo "<td>" . $value->getPath() . "</td>";
echo "<td>" . $value->getName() . "</td>";
echo "<td>" . $value->getActionName() . "</td>";
echo "</tr>";
}
echo "</table>";
});

https://stackoverflow.com/questions/18394891/how-to-get-a-list-of-registered-route-paths-in-laravel
https://stackoverflow.com/questions/15955295/displaying-registered-routes-in-laravel

php artisan route:list sina/vendor/laravel/framework/src/Illuminate/Foundation/Console/RouteListCommand.php:14

自动将空字符串转为 null

1
2
3
4
5
6
laravel 有一个 ConvertEmptyStringsToNull 中间件,自动将空字符串转为 null
在 App\Http\Kernel.php 中,注释了这个中间件就好了。
QueryException In Connection.php line 664 :
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'judge_group' cannot be null

打印下 Request 返回的数据,Laravel 有个中间件会把 ' ' 转为 null。https://learnku.com/laravel/t/35052

Laravel 5.5 升级到 6.0

1
2
3
4
5
6
使用 phpredis 替代 predis

Laravel 开始推荐使用 phpredis 来代替 predis。

所以要记得先安装 phpredis, 然后在 config/app.php 中去掉 Redis 别名
https://learnku.com/articles/35056

API 和 后台接口分离

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
发现路由数量变多后影响到了性能,这个时候需要区别不同服务器去加载不同的路由。

如何去别不同的服务器区别环境,但是又要区别是生产环境。
可以使用 app()->environment(); 方法实现,生产环境和测试环境的区别。

查看代码后发现可以使用更多的方法。

/**
* 获取或检查当前应用程序环境。
*
* @return string|bool
*/
public function environment()
{
// 返回传递给函数的参数数量
if (func_num_args() > 0) {
// 如果第一个参数是数组就去第一个,不是的话取全部的。
$patterns = is_array(func_get_arg(0)) ? func_get_arg(0) : func_get_args();

return Str::is($patterns, $this['env']);
}

return $this['env'];
}
Str::is 函数判断给定的字符串是否匹配给定的模式。星号 * 可以用来表示通配符:

# 判断在 API 环境
app()->environment("production.api");
# 判断在 ADMIN 环境
app()->environment("production.admin");
# 判断在所有环境
app()->environment("production.*");
修改 RouteServiceProvider 文件

/**
* Define the routes for the application.
*/
public function map()
{
// 公共路由

if (app()->environment('production.api')) {
# production api 路由
$this->mapApiRoutes();
} elseif (app()->environment('production.admin')) {
# production admin 路由
$this->mapAdminRoutes();
} else {
# local testing stanging 环境下加载所有路由
$this->mapApiRoutes();

$this->mapAdminRoutes();
}
}
https://learnku.com/articles/35099

laravel路由分割

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
Route::group(['middleware' => ['web']], function () {
Route::group(['prefix' => 'user', 'namespace' => 'User'], function () {
require app_path('Http/Routes/user.php');
});

Route::group(['prefix' => 'admin', 'namespace' => 'Admin'], function () {
require app_path('Http/Routes/admin.php');
});
Route::group(['prefix' => 'api', 'namespace' => 'Home'], function () {
require app_path('Http/Routes/homeapi.php');
});
});
app/Providers/RouteServiceProvider.php 的 map 方法中可以如下定义:
public function map(Router $router)
{
$router->group(['namespace' => $this->namespace], function ($router) {
//require app_path('Http/routes.php');
foreach (glob(app_path('Http//Routes') . '/*.php') as $file) {
$this->app->make('App\\Http\\Routes\\' . basename($file, '.php'))->map($router);
}
});
}
https://learnku.com/laravel/t/2484/the-best-way-to-split-routesphp-laravel-routing-file
php artisan route:cache

public function map()
{
$this->mapWebRoutes();
$this->mapApiRoutes();
//
}

protected function mapWebRoutes()
{
Route::group([
'middleware' => 'web',
'namespace' => $this->namespace,
], function ($router) {
require base_path('routes/web.php');
});
}

protected function mapApiRoutes()
{
Route::group([
'middleware' => ['api', 'auth:api'],
'namespace' => $this->namespace,
'prefix' => 'api',
], function ($router) {
require base_path('routes/api.php');
});
}
http://www.xiaot123.com/post/laravel_route

按照URL
if (Str::is('api.*', request()->server('HTTP_HOST') ?? '')) {
$this->mapApiRoutes();
} elseif (Str::is('admin.*', request()->server('HTTP_HOST') ?? '')) {
$this->mapAdminRoutes();
} else {
$this->mapApiRoutes();

$this->mapAdminRoutes();
}

Laravel 项目的简单 CMS

Laravel 之道

Laravel 代码生成器 https://learnku.com/laravel/t/34772 https://github.com/GooGee/Entity-Builder

使用 Docker LNMP 部署 PHP 开发环境

PHP-fpm MongoDB 连接数

Laravel 源码写的一个简易框架

轻松部署 Laravel 应用

接近免费的 SSL 证书 https://www.digital-sign.com.cn/register

一键生成 https 根域名证书更方便,推荐 certbot-auto 脚本

Laravel 的开源api文档管理系统 Wizard

CentOS7 轻松部署 Laravel 应用

超大文件上传

使用 AetherUpload 视频上传过程

axeadmin 让你的 Laravel 快速拥有一个后台

Laravel 的请求是怎么到达控制器

控制台实时查看 sql

HTTP 协议免费升级到 HTTPS

Laravel-echo-server 开发聊天室和客服功能

laravel之道

Laravel 代码生成工具

Laravel 源码阅读 - Eloquent

laravel最佳实践

Laravel+vue 个人博客

Laravel 从学徒到工匠精校版

服务容器

Laravel5.8 版本内核源码注解

Laravel-admin 源码分析系列

Laravel 源码阅读

Laravel 数据库脱敏工具

Laravel 深入浅出指南

Laravel5.5LTS 版本源码内核阅读注解

laravel 源码详解https://github.com/LeoYang90/laravel-source-analysis

Laravel + Vue + Oss 搭建的图片墙

基于 Laravel + Ant-design-pro 界面美观漂亮的后台

Laravel-admin 安装分析

电子商务后台系统

基于laravel的博客

laravel5.8版本源码分析

基于 adminLTE3、Laravel、swoole 的微服务管理平台

Laravel-snappy 生成 PDF 踩坑记录

docker/laradock 安装

Laravel 融合 Markdown

Laravel框架关键技术解析

Laravel Markdown Blog

laravel markdown api文档

markdown 来撰写文档

Laravel 融合 Elasticsearch/Algolia

Laravel Artisan 命令大全

Laravel 底层分析系列文章:生命周期

大话后端开发高高并发的奇技淫巧

PHP 跨域的 CORS 中间件

Laravel 谷歌翻译

Laravel 搭建 Composer 包

Laravel 和 layui 包含基础 RBAC 权限的管理后台

生命周期和容器 Container

Supercharged Excel exports and imports in Laravel

MySQL 避坑宝典

文章收藏的扩展包

多语言与多时区的解决方案

laravel框架源码分析注解

Laravel 团队任务管理系统

基于 Laravel + Ant-design 界面美观漂亮的后台

Laravel 本地语言包自动翻译插件

Laravel+Layim+GatewayWorker workerman 实现实时聊天功能

「Laravel 文档助手」 - 油猴脚本

Laravel 短信扩展包

Laravel 中的事件监听

基于Laravel构建请求信息日志以及接口智能分析

Laravel 5.8 速查表

Laravel 关联模型扩展

Laravel 的第三方平台登录

Laravel- 访问设备识别组件-BrowserDetect

Laravel 道场

Laravel5.5 支付宝支付

Laravel 广播

Laravel 5.5 升级到 5.7 小记

生成chm文档

深入研究 Laravel 源码第二天

依赖注入扩展包

PHP 权限管理框架 Casbin

laravel 代码生成器 2.0

轻松部署 Laravel 应用

深入了解 Laravel 的工作机制

Laravel 中 IoC 容器

laravel之道系列

Laravel核心代码学习

laravel服务容器

Laravel 的生命周期比喻的更形象一点

paypal PHP-sdk 支付 / 回调 / 退款全流程

Laravel 5.5 和 React 的个人博客系统

Laravel 从零单排系列教程

基于 Tire 树的敏感词检测

QQ 第三方登录

PHP 有限状态机

laravel vue博客

Voyager 的使用及二次开发

Composer 自动加载源码

GitLab CI 踩坑

小白折腾服务器(一):docker 搭建 lnmp+ 使用 deployer 部署

不使用框架的情况下也能写出现代化 PHP 代码

基于 Laravel 的 CMS

Laravel LDAP 包

PhpSpreadsheet 简单 Excel 导入导出

valet 切换 PHP 版本

Laravel Money 金钱单位格式化

Laravel 中自定义用户登录的数据表users

浅析依赖倒转、控制反转、IoC 容器、依赖注入

用语言 (非代码) 说清楚 IoC 到底是什么

PHP中的依赖注入(DI)容器

十五个常用的 Laravel 集合

Laravel 服务容器,IoC,DI

Laravel 服务容器、服务提供器、契约实例讲解

Laravel 服务容器,IoC,DI

《提问的智慧》小测验

Laravel 使用 Elasticsearch 全局搜索

PHP实现的一些算法例子

计算机网络读书笔记

正则preg_match匹配长度

优酷 YouKu OAuth2 第三方登录库

开源学校管理系统

超全的设计模式简介

如何应对服务器压力

数据库中用一个值来保存多种情况:二进制和按位异或

在阿里云上搭建自己的git服务器

RabbitMQ 的应用场景以及基本原理介绍

编译安装nginx1.9.7+php7.0.0

Laravel 开发 API 更得心应手

别再使用 JWT 作为 Session 系统

同一公司下多产品的用户权限管理系统设计

数据结构和算法

PHP中关于时间(戳)、时区

大文件上传扩展

Laravel 微信小程序后端搭建

Algolia DocSearch 轻松实现文档全站搜索

SOLID 设计原则

MySQL分组查询TOP N的实践和踩坑

不要在循环体中使用 array_merge ()

查询物流信息的扩展包

PHPStrom 转 VSCode 折腾记录

【看完就懂】Laravel 服务容器,IoC,DI

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

Laravel Excel 的五个隐藏功能

woann-chat 基于 laravelS 和 layim 的聊天系统

Laravel-Excel 3.0 文档

Laravel 进阶教程

Laravel Redis 广播 实例

基于 The 996 Prohibited License 的项目Laravel-admin-select2

处理 API 数据格式

Laravel 项目全自动接口管理

Auth 2.0 的一种简单解释

Laravel 从零单排系列教程

Laravel 5.7 和 JSON Web 令牌(tymon/jwt-auth) - 用户认证

生产环境的 Composer

输出 SQL 语句

Laravel Passport 为你的 REST API 增加用户认证功能

不要在循环体中使用 array_merge ()

Laravel API throttle 限流原理分析

Laravel 登录原理剖析

Laravel 安装和开发环境

Cookie 禁用了,Session 还能用吗

设计模式

PHP 详细面试总结

php设计模式 https://baijunyao.com/

《PHP 设计模式全集 2018》

Laravel Markdown Blog

设计模式超级简单的解释

Laravel Markdown Blog learnku.net

PHP 开发环境安装配置Laradock

头像自动生成

Laravel-admin 集成 Markdown 编辑器

swoole 邮件系统

Laravel 测试之 —— PHPUnit 入门教程

composer 解析

Laravel 5.7 撸了一个论坛

Laravel 文档助手」 - 油猴脚本

全新搭建 PHP 开发环境

编写具有描述性的 RESTful API

Laravel-China 课程文章同步发布

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

Laravel5.5 使用 Elasticsearch 做引擎

Laravel Eloquent 小技巧

Laravel Cron 定时任务 “跳坑” 点

Laravel + Laravel-echo + EasyWeChat 实现微信扫码登录

你可能不太需要 Laravel-Excel

Oss Flysystem 扩展

Laravel5.6 整合 RabbitMQ 消息队列

Laravel 集合(Collection)的基础用法

『 OAuth2.0』 猴子都能懂的图解

Laravel 的消息通知系统

Laravel 项目中使用 Elasticsearch 做引擎

简聊 Session 与 Token 身份验证

六个值得参考的 Laravel 开源项目

Laravel 启动流程分析

Lumen/Laravel 集成 GatewayWorker (Workerman),实现简单的聊天系统.

Laravel踩坑日记之基本配置及Demo

腾讯云智 AI 图像服务的 Laravel SDK

基于Laravel支持markdown的博客Vien Blog

Laravel 团队任务管理系统

「Laravel 文档助手」 - 油猴脚本

队列原理分析

Laravel + H-ui 搭建后台管理系统

laravel手册

基于 larecipe 做的 Laravel 速查表

Laravel 中连接 Webscoket

自动生成 CURD

laravel5.5和vue.js结合的前后端分离项目模板websocket聊天室