​​​​ php 代码整理 | 苏生不惑的博客

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
$items = array(
1 => array ('id' => 1, 'pid' => 0, 'name' => ' 江西省 '),
2 => array ('id' => 2, 'pid' => 0, 'name' => ' 黑龙江省 '),
3 => array ('id' => 3, 'pid' => 1, 'name' => ' 南昌市 '),
4 => array ('id' => 4, 'pid' => 2, 'name' => ' 哈尔滨市 '),
5 => array ('id' => 5, 'pid' => 2, 'name' => ' 鸡西市 '),
6 => array ('id' => 6, 'pid' => 4, 'name' => ' 香坊区 '),
7 => array ('id' => 7, 'pid' => 4, 'name' => ' 南岗区 '),
8 => array ('id' => 8, 'pid' => 6, 'name' => ' 和兴路 '),
9 => array ('id' => 9, 'pid' => 7, 'name' => ' 西大直街 '),
10 => array ('id' => 10, 'pid' => 8, 'name' => ' 东北林业大学 '),
11 => array ('id' => 11, 'pid' => 9, 'name' => ' 哈尔滨工业大学 '),
12 => array ('id' => 12, 'pid' => 8, 'name' => ' 哈尔滨师范大学 '),
13 => array ('id' => 13, 'pid' => 1, 'name' => ' 赣州市 '),
14 => array ('id' => 14, 'pid' => 13, 'name' => ' 赣县 '),
15 => array ('id' => 15, 'pid' => 13, 'name' => ' 于都县 '),
16 => array ('id' => 16, 'pid' => 14, 'name' => ' 茅店镇 '),
17 => array ('id' => 17, 'pid' => 14, 'name' => ' 大田乡 '),
18 => array ('id' => 18, 'pid' => 16, 'name' => ' 义源村 '),
19 => array ('id' => 19, 'pid' => 16, 'name' => ' 上坝村 '),
);
function genTree($items) {
$tree = array (); // 格式化好的树
foreach ($items as $item)
if (isset($items[$item['pid']]))
$items[$item['pid']]['son'][] = &$items[$item['id']];
else
$tree[] = &$items[$item['id']];
return $tree;
}

插入排序算法

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
class Person
{
public $id;
public $data;
}
//https://learnku.com/articles/32586
function insertSort(&$data,$n)
{
for ($i=1;$i<$n;$i++){
if ($data[$i]->id<$data[$i-1]->id){
$j = $i-1;
$x = $data[$i]->id;
$obj = $data[$i];
while($j>-1&&$x<@$data[$j]->id){
$data[$j+1] = $data[$j];
$j--;

}
$data[$j+1] = $obj;
}
}
}
(function(){

$person = new Person();
$index = ['23'=>'勺颠颠','65'=>'老油条','21'=>'烧包谷','9'=>'烧耳块','4'=>'肥嘟嘟','7'=>'霉戳戳','32'=>'一坨肉','6'=>'老扎哇'];
$data = [];
foreach ($index as $k=>$v){
$obj = clone $person;
$obj->id = $k;
$obj->data = $v;
$data[] = $obj;
}
insertSort($data,8);

print_r($data);

})();

分表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace App\Models;

use DB;
use Schema;
use Illuminate\Database\Eloquent\Model;

class Logs extends Model
{
protected $guarded = ['id'];
protected $table;

public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->table = 'log_'.date('Y');
if (!Schema::hasTable($this->table))
{
DB::update('create table '.$this->table.' like logs');
}
}
}
Logs::where('uid','')->first();

sentry 升级

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
server {
listen 80;
server_name track.example.com;

set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

location / {
// 添加这两行
default_type text/html; // 设置 content-type 表示这是一个网页
return 202; # 返回 202 表示已经接收,但是并不处理
client_max_body_size 100M;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host-Real-IP $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-Pcol http;
proxy_pass http://localhost:10000;
}
}
使用这两行,就可以保证客户端正常请求了数据,但是我却把它给抛弃了。保证客户端的正常浏览。
cd /data/
cp -r onpremise onpremise2
cd onpremise
docker-compose down
git pull
git diff docker-compose.yml
git checkout docker-compose.yml
git pull
export SENTRY_IMAGE='sentry:9.1.2'
docker-compose build --pull
docker-compose run --rm web upgrade

docker-compose up -d
https://learnku.com/articles/32569

基于雪花算法的 PHP ID 生成器

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
https://github.com/godruoyi/php-snowflake
$ composer require godruoyi/php-snowflake -vvv
$snowflake = new \Godruoyi\Snowflake\Snowflake;

$snowflake->id();
// 1537200202186752
$snowflake = new \Godruoyi\Snowflake\Snowflake($datacenterId, $workerId);

$snowflake->id();
$snowflake = new \Godruoyi\Snowflake\Snowflake;
$snowflake->setStartTimeStamp(strtotime('2019-09-09')*1000);

$snowflake->id();
集合laravel
// App\Providers\AppServiceProvider

use Godruoyi\Snowflake\Snowflake;
use Godruoyi\Snowflake\LaravelSequenceResolver;

class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->singleton('snowflake', function () {
return (new Snowflake())
->setStartTimeStamp(strtotime('2019-08-08')*1000)
->setSequenceResolver(new LaravelSequenceResolver(
$this->app->get('cache')->store()
));
});
}
}
$snowflake = new \Godruoyi\Snowflake\Snowflake;
$snowflake->setSequenceResolver(function ($currentTime) {
static $lastTime;
static $sequence;

if ($lastTime == $currentTime) {
++$sequence;
}

$lastTime = $currentTime;

return $sequence;
})->id();
https://learnku.com/articles/32575

内置web服务器

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

php -S localhost:8000 -t public/
cd /home/baoguoxiao/www/php/demo
php -S localhost:8000 router.php
router.php 文件的代码

/**
* 对URL进行解析,并获取请求的文件名
*/
$uri = urldecode(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH));

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

/**
* 加载入口文件
*/
require_once "./index.php";
vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php

/**
* 执行命令.
*
* @return int
*
* @throws \Exception
*/
public function handle()
{
// 切换路径到 public 目录
chdir(public_path());

// 在命令台进行输出相关内容
$this->line("<info>Laravel development server started:</info> <http://{$this->host()}:{$this->port()}>");

// 执行外部程序,并且 $status 为系统的返回状态
passthru($this->serverCommand(), $status);

// $status 为0 表示执行正常, 为其他大于0的数字表示出现了错误,有可能是端口被抢占了,这个时候就会接着判断是否进行再次尝试
if ($status && $this->canTryAnotherPort()) {
// 对绑定的端口号加1 默认是8000, 如果失败则重试端口号为8001,再次失败重试端口号为8002,以此类推。
$this->portOffset += 1;
// 再次调用此程序
return $this->handle();
}
// 返回状态值
return $status;
}

/**
* 获取完整的 server 命令.
*
* @return string
*/
protected function serverCommand()
{
return sprintf('%s -S %s:%s %s',

// 获取PHP可执行命令的路径
ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false)),

// 获取需要绑定的host
$this->host(),

// 获取需要绑定的端口
$this->port(),

// 对需要执行的参数进行转义处理。这里的 server 就是我们之前说的路由文件,它在项目的根路径下
ProcessUtils::escapeArgument(base_path('server.php'))
);
}
对上面的命令进行翻译一下,实际上就是执行的

cd ./public
php -S 0.0.0.0:8000 ../server.php
note:

这里我们可以看到一个区别就是之前我自己写的代码,host 都是 localhost, 但是这里写的是 0.0.0.0。这两个有什么区别呢?

其实区别很简单,比如我之前写的 localhost 绑定的ip 是 127.0.0.1, 这个相当于一个回环地址,那么我们就只允许本机的IP进行访问。而 0.0.0.0,则表示我们对ip不进行限制,所有的IP都可以进行访问。
那我们接着再来看看项目根目录下面的server.php:

/**
* Laravel - A PHP Framework For Web Artisans
*
* @package Laravel
* @author Taylor Otwell <taylor@laravel.com>
*/

$uri = urldecode(
parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
);

// 这个文件允许我们从内置 PHP web 服务器中模拟 Apache 的 "mod_rewrite" 功能.
// 这提供了一种测试 Laravel 应用程序的便捷方法,
// 而无需在此安装"真正的" web 服务器软件。
if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) {
return false;
}

require_once __DIR__.'/public/index.php';
https://github.com/mowangjuanzi/blog/blob/master/posts/2019.05.18-php_built_in_web_server.md

响应宏

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
在任意一个ServiceProvider的boot方法里(ResponseMacroServiceProvider),使用Response Facade注册


Response::macro('success', function ($data = [], $message = 'success') {
return new JsonResponse([
'code' => 0,
'data' => $data,
'message' => $message
], 200);
});

接下来, 你可以再任何地方使用它response()。

//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
https://godruoyi.com/posts/laravel-response-macro-principle

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
use Intervention\Image\ImageManager;

$manager = new ImageManager(array('driver' => 'imagick'));
use Intervention\Image\Facades\Image;

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

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

// 二维码图片
$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);
中文字体文件https://github.com/StellarCN/scp_zh/tree/master/fonts

nginx 配置多域名

1
2
3
4
5
6
7
8
9
10
11
12
13
这个是精确匹配问题:

nginx server_name 匹配不到时,默认取第一个 server { } (端口和 ip 一致)

你项目中配置:

1. server_name www.test.com
2. server_name www.mytest.com
访问 test.com/mytest.com 都找不到对应的 server_name (www.test.com != test.com , www.mytest.com != mytest.com),所以就取第一个 server { } 匹配,所以均输出 test

server_name mytest.com *.mytest.com

https://learnku.com/laravel/t/32300

插入折半排序

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
class Person
{
public $id;
public $data;
}

function insertSort(&$data,$n)
{
for ($i=1;$i<$n;$i++){
$low = 0;
$high = $i-1;
$temp = $data[$i];
while ($low<=$high){
$mid = floor(($low+$high)/2);
if ($data[$mid]->id>$temp->id){
$high = $mid-1;
}else{
$low = $mid+1;
}
}
for ($j=$i;$j>$low;$j--){
$data[$j] = $data[$j-1];
}
$data[$low] = $temp;
}
}
(function(){

$person = new Person();
$index = ['23'=>'勺颠颠','65'=>'老油条','21'=>'烧包谷','9'=>'烧耳块','4'=>'肥嘟嘟','7'=>'霉戳戳','32'=>'一坨肉','6'=>'老扎哇'];
$data = [];
foreach ($index as $k=>$v){
$obj = clone $person;
$obj->id = $k;
$obj->data = $v;
$data[] = $obj;
}

insertSort($data,8);
print_r($data);

})();
https://learnku.com/articles/32691

sql拼接

1
2
3
4
$statusInStr = implode(',', '1,2,3');
字符串
$statusInStr = "'".implode("','", [1,2,3])."'";//'1','2','3'
"select * from table where status in ({$statusInStr})"//select * from table where status in ('1','2','3')

x-www-form-urlencoded 与 multipart/form-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
x-www-form-urlencoded,表单默认的 Content-type 类型,支持 ASCII-text 文本内容
multipart/form-data,允许提交表单包含: files,non-ASCII-text,Binary 类型数据
当表单使用 application/x-www-form-urlencoded 时,需要对参数进行 urlencode 编码和序列化

如,表单提交参数(key-value)为:

param1:website
param2:https://www.google.com
经过 urlencode 编码后:

param1:website
param2:https%3A%2F%2Fwww.google.com
再经过序列化,得到结果

param1=website&param2=https%3A%2F%2Fwww.google.com
multipart/form-data

一个 multipart/form-data 消息体,包含多个块组成,每个块代表一个有效的表单控件,并使用 boundary 的字符串分割:

第一部分,Content-Disposition: form-data 参数名称,如,name="my_control
第二部分,Content-Type: text/plain,Content-Type: image/png
第三部分,消息内容
例如表单:

<FORM action="http://server.com/cgi/handle"
enctype="multipart/form-data"
method="post">
<P>
What is your name? <INPUT type="text" name="submit-name"><BR>
What files are you sending? <INPUT type="file" name="files"><BR>
<INPUT type="submit" value="Send">
<INPUT type="reset">
</FORM>
假设,submit-name 输入 Larry 文本,files 选择文件 file1.txt

Content-Type: multipart/form-data; boundary=AaB03x

--AaB03x
Content-Disposition: form-data; name="submit-name"

Larry
--AaB03x
Content-Disposition: form-data; name="files"; filename="file1.txt"
Content-Type: text/plain

... contents of file1.txt ...
--AaB03x--
https://learnku.com/articles/32812

Model::setAttribute()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
App\GameWork::create([
"task_id" => "23571",
"type" => 2,
"take_time" => "1563085291",
"send_time" => 1563093004,
"chufa" => 10,
"worker" => 0,
"work_time" => 0,
"log" => "超时",
"uid" => 271,
"game_id" => 3532054,
"buyer_id" => 64
]);
结果提示我

TypeError: Too few arguments to function Illuminate/Database/Eloquent/Model::setAttribute(), 1 passed in /Library/WebServer/Documents/api/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php on line 617 and exactly 2 expected
把自增关闭就好了,laravel 默认是所有表都有个自增 ID,我的表不需要

public $incrementing = false;
https://learnku.com/laravel/t/31164

处理unicode解码

1
2
3
4
5
6
7
8
9
10
11
// change unicode to unt-8
function replace_unicode_escape_sequence($match) {
return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE');
}
function unicode_decode($str) {
return preg_replace_callback('/u([0-9a-f]{4})/i', 'replace_unicode_escape_sequence', $str);
}
$str = unicode_decode('{"u5173u952eu8bcd":[{"","key":"u767eu5ea6"}]}');
//{"关键词":[{"","key":"百度"}]}
如果编码是 \u5173\u952e\u8bcd 正则改为/\u([0-9a-f]{4})/i 即可
http://cuihuan.net/2015/11/28/php%20%E5%A4%84%E7%90%86unicode%E8%A7%A3%E7%A0%81/

恶意提交攻击

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
 // check 环境ip
if (!$this->isWhiteList($ip)) {
$echo_str = "提交过于频繁,请稍后再试!";
// 构建ip的时间栈数据
if (!is_array($_SESSION[$ip])) {
$_SESSION[$ip] = array();
}
if (isset($_SESSION[$ip][0])) {
$_SESSION[$ip][] = time();
// session 保存时间为6小时。清理session
$post_interval_first = time() - $_SESSION[$ip][0];
if ($post_interval_first > 21600) {
$_SESSION[$ip] = array();
}
// 两次提交小于1s,禁止提交
$post_interval_pre = time() - $_SESSION[$ip][count($_SESSION[$ip]) - 3];
if ($post_interval_pre < 1) {
echo $echo_str;
exit;
};
// 您在10s内已经提交了3请求,禁止提交
$post_interval_third = time() - $_SESSION[$ip][count($_SESSION[$ip]) - 3];
if (isset($_SESSION[$ip][3]) && ($post_interval_third < 10)) {
echo $echo_str;
exit;
}
// 您在1分钟期间已经提交了5请求,禁止提交
$post_interval_fifth = time() - $_SESSION[$ip][count($_SESSION[$ip]) - 3];
if (isset($_SESSION[$ip][5]) && ($post_interval_fifth < 60)) {
echo $echo_str;
exit;
}
// 6小时内提交10次,禁止提交
if (isset($_SESSION[$ip][10])) {
echo $echo_str;
exit;
}
} else {
$_SESSION[$ip][] = time();
}
}
function isWhiteList($ip){
/**
* 内网ip默认全部存在于白名单中
*/
if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)){
return true;
}
// 是否在写死的whitelist 里面
return in_array($ip,$this->_WHTTE_LIST);
}
http://cuihuan.net/2016/01/07/%E6%9C%BA%E5%99%A8%E5%A4%9A%E6%AC%A1%E6%81%B6%E6%84%8F%E6%8F%90%E4%BA%A4%E6%94%BB%E5%87%BB/

切换镜像

1
2
3
4
5
6
7
8
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
composer update -vvv

执行成功以后,打开 composer.lock, 发现文件中的链接依旧是 laravel-china 的地址

删掉 lock,再 install,会安装最新的包,在生产环境这样,可能会搞出事来,,

composer update nothing 直接这样,就可以只更新 lock 文件里的源

composer Failed to execute unzip

1
2
3
4
5
6
7
8
 composer 多线程下载的扩展包(hirak/prestissimo),因为是全局安装的,其原理就是下到缓存,然后以后全部都走缓存安装。所以我第一件事 先全局卸载掉它

composer global remove hirak/prestissimo
然后清空当前 composer 的缓存

composer clear-cache
然后使用 laravel new blog, 你会发现没有报错了。
https://learnku.com/articles/32898

sentry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ git clone https://github.com/getsentry/onpremise.git
$ cd onpremise
$ ./install.sh
$ docker-compose up -d

在目录下的 docker-compose.yml 添加配置:

SENTRY_EMAIL_HOST: smtp.exmail.qq.com
SENTRY_EMAIL_USER: 你的邮箱地址
SENTRY_EMAIL_EMAIL: 你的邮箱地址
SENTRY_EMAIL_PASSWORD: 授权码
SENTRY_EMAIL_USE_TLS: 'true'
SENTRY_EMAIL_PORT: 587
重启 sentry 服务

docker-compose down && docker-compose up -d

抽离 vendor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
在 bootstrap 注册 autoload 就可以了。
./bootstrap/app.php

<?php
require_once __DIR__ . '/../vendor/autoload.php';

$autoload = new Composer\Autoload\ClassLoader();
$autoload->addPsr4('App\\', "../app/");
$autoload->addClassMap([
"database/",
"tests/"
]);
$autoload->register();
https://learnku.com/laravel/t/32840

dd 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
function dd(...$args)
{
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: *');
header('Access-Control-Allow-Headers: *');
http_response_code(500);

foreach ($args as $x) {
(new Illuminate\Support\Debug\Dumper)->dump($x);
}

die(1);
}

listen sql

1
2
3
4
\DB::listen(function (QueryExecuted $sql) {
\Log::info($sql->sql);
\Log::info((new \Exception())->getTraceAsString());
});

模型事件 updated

1
2
3
4
5
6
7
8
9
10
只有 $sku->{attribute} != $sku->getOriginal({attribute}) 不一致的时候才会触发
  getDirty() 不为空的时候才触发, 而且不会比较数据类型(判断是否 dirty 使用的是 == 而不是 ===)

直接 app(Model::class)->where()->update() 不会触发
  $sku = app(Sku::class), $sku->has_stock = 1; $sku->save() 这样才会触发

这是因为,使用 app(Model::class)->where()->update() 的时候,调用的是 Query/Builderupdate 方法,这里面是没法触发 update 事件的。

使用 $sku = app(Sku::class), $sku->has_stock = 1; $sku->save() 的时候,使用的 update 方法是 Eloquent/Model 里面的 update 方法。
https://blog.baiguiren.com/2019/04/22/laravel/Laravel%20%E6%A8%A1%E5%9E%8B%E4%BA%8B%E4%BB%B6%20updated%20%E8%A7%A6%E5%8F%91%E6%9D%A1%E4%BB%B6/

获取所有 collection

1
2
3
foreach (\DB::connection('xxx')->getMongoDB()->listCollections() as $collection) {
echo $collection->getName() . PHP_EOL;
}

获取带参数的 sql 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DB::table('users')->where('id', 1)->toSql();
select * from `users` where `id` = ?
$builder = DB::table('users')->where('id', 1);
$bindings = $builder->getBindings();
$sql = str_replace('?', '%s', $builder->toSql());
$sql = sprintf($sql, ...$bindings);
select * from `tb_user` where `id` = 1

\Illuminate\Database\Query\Builder::macro('sql', function () {
$bindings = $this->getBindings();
$sql = str_replace('?', '%s', $this->toSql());

return sprintf($sql, ...$bindings);
});
dd(DB::table('users')->where('id', 1)->sql());

https://blog.baiguiren.com/2018/06/27/laravel/Laravel%20%E4%B8%AD%E7%9A%84%20toSql%20%E8%8E%B7%E5%8F%96%E5%B8%A6%E5%8F%82%E6%95%B0%E7%9A%84%20sql%20%E8%AF%AD%E5%8F%A5/

命令行输出进度条

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
namespace App\Commands;

use Illuminate\Console\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutput;

class Test extends Command
{
protected $signature = 'test';

/**
* Execute the command.
*
* @return void
*/
public function handle()
{
//
$output = new ConsoleOutput();
$progressBar = new ProgressBar($output, 1000);
$progressBar->setFormat(" %elapsed:6s%/%estimated:-6s% 内存消耗: %memory:6s%\n%current%/%max% [%bar%] %percent:3s%%");

foreach (range(1, 1000) as $_) {
usleep(5000);
$progressBar->advance();
}

$progressBar->finish();
echo "\n";
}
}
$progressBar->start();
print "\n";
$progressBar->start();


$progressBar->setFormat("已处理数量: %count%"); // 这里是一个占位符,可以和进度条写在一起
$progressBar->setMessage($count, 'count'); // 使用 `$count` 替换输出内容中的 "%$count%"

with 通过闭包筛选

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
class User extends Model
{
public function profile()
{
return $this->hasOne('App\UserProfile');
}
}
App\User::with([
'profile' => function($query) {
$query->select(['id']);
}
])
->find(4)
->toArray();
即使数据库有记录,sql 也记录了对应的查询语句,但是 profile 关联却是空的
App\User::with([
'profile' => function($query) {
$query->select(['id', 'user_id']); // 多了 user_id
}
])
->find(4)
->toArray();
可以查找到正确的 profile 了
hasMany 方法默认第二第三个参数其实就是这两个集合建立关联的关键,第三个参数 user_id、第四个参数 id;这样一来我们就可以通过比较 Profile 的 user_id 和 User 里面的 id,如果相等,则这个 Profile 是属于这个 User 的,我们就把该 Profile 放进 User 的 profile 关联中,最后就得到我们想要的结果了
Laravel 先查询主要的数据(不带 with),查询完了之后,取出其中的 id 列数组(不一定都是id啊,只是举个例子),将这个数组作为条件去查找关联,有多少个关联就会再去查找多少次,查找完关联之后通过得到的结果的主键和关联数据的外键比对,相等则建立关联。

总结:在关联筛选 field 的时候,也必须要把关联的外键写进去,否则,即使产生了正确的 sql 语句,但是它们建立不了关联,通过 $user->profile 得到的还是一个空集合。
https://blog.baiguiren.com/2018/01/06/laravel/Laravel%205.1%20Eloquent%20with%20%E9%80%9A%E8%BF%87%E9%97%AD%E5%8C%85%E7%AD%9B%E9%80%89%E7%89%B9%E5%AE%9A%20field%20%E5%BE%97%E4%B8%8D%E5%88%B0%E7%BB%93%E6%9E%9C%E7%9A%84%E9%97%AE%E9%A2%98/

Laravel cookie加密解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$data = 'laravel';
$iv = random_bytes(16);
$key = 'this is key';

$encrypt = openssl_encrypt($data, 'AES-256-CBC', $key, 0, $iv);

var_dump($encrypt);
var_dump(openssl_decrypt($encrypt, 'AES-256-CBC', $key, 0, $iv));
Laravel 中的话,key 就是 .env 配置文件里面的 APP_KEY,除了 key 还有两个变化的参数就是 加密、解密的数据以及 iv。

也就是说,如果我们需要加密 cookie 的话,我们至少得保存下 加密后的数据以及 iv
虽然每次请求 cookie 都会发生变化,但是实际数据是没有变的,发生变化只是因为用来加密的 iv 变了(使用 random_bytes 方法生成)。

由于 iv 每次都变化,所以需要把 iv 也一同返回给浏览器,加上验证数据合法性的 mac,最后返回的就是下面的数组的 json 编码后在 base64 编码的数据:
[
'iv' => random_bytes(16), // 16位随机字节串
'value' => 'xxxx...', // 加密后的数据
'mac' => 'xxx...' // 后续请求验证数据合法性的字符串
]https://blog.baiguiren.com/2018/01/11/laravel/Laravel%20cookie%20%E5%8A%A0%E5%AF%86%E8%A7%A3%E5%AF%86%E5%8E%9F%E7%90%86/

Eloquent Builder 添加自定义方法

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
\Illuminate\Database\Query\Builder\Builder::macro('active', function () {
return $this->where('status', 1);
});
DB::table(xxx)->active()->get();

\App\Model\User::active()->first();
在 app/Providers 下面新建一个 Provider,把 macro 调用放到 Provider 的 register 方法中
namespace App\Providers;

use Illuminate\Database\Query\Builder;
use Illuminate\Support\ServiceProvider;

/** @mixin \Illuminate\Database\Eloquent\Builder */
class DatabaseServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
Builder::macro('active', function () {
return $this->where('status', 1);
});
}
}
加了 Providers 之后还要在 config/app.php 中配置这个 Provider。

修改模型对象的某个属性

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
$user = new User;
$user->age = 26;
dd($user->toArray(), $user->getAttributes()); // ['age' => 26], ['age' => 26]

我们想修改里面属性的时候比如 $user->info['job'] = 'PHP';,实际上等同于:


$user->__get('info')['job'] = 'PHP';
也就是说,等同于:


$temp = $user->__get('info');
$temp['job'] = 'PHP';
也就是说,我们在设置有层级的属性的时候,中间产生了临时变量。

假设大家知道了值传递、引用传递的区别。

这个时候问题就来了,如果这个 $user->info 是一个数组,我们最终修改的是一个临时数组,而 $user->info 还是不变。这个时候就报错了,因为这个修改是完全没有意义的。

而如果 $user->info 是一个对象的时候,我们临时变量也是一个对象,而对象是通过引用传递的,我们修改了这个对象的时候,实际上也修改了 $user->info,因为这个临时变量指向了原始对象。

$user = app(User::class);

$user->infoObj = new stdClass;
$user->infoObj->age = 26;
$user->infoObj->job = 'PHP';

// dd($user->toArray());

// Indirect modification of overloaded property User::$info has no effect
$user->info = [
'age' => 26,
];
$user->info['job'] = 'PHP';
https://blog.baiguiren.com/2018/07/05/laravel/Laravel%20Model%20-%20Indirect%20modification%20of%20overloaded%20property/

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
使用一次查询代替多次查询AdminUser::with('admin_user_info')->where('id', '>', 1)->get();
$products = \DB::table('product')->where('category_id', 1)->paginate();
$product_ids = $products->pluck('id')->toArray(); // 获取所有 id , 下面第二点有说到这个用法<br>
$some_other_infos = \DB::table('some_other_info') // 根据 id 数组一次查询所有关联数据
->whereIn('product_id', $product_ids)
->get();
$some_other_infos = array_column($some_other_infos, null, 'product_id'); // 使用 id 把结果集转换为关联数组,这样下面可以更高效地操作,否则我们只能两次 foreach 了
foreach ($products as $key => $product) {
$product['some_other_info'] = array_get($some_other_infos, $product['id']);
$products[$key] = $product;
}
namespace App\Model;

class AdminUser extends BaseModel
{

public function admin_user_info()
{
return $this->hasOne(AdminUserInfo::class);
}
}

$adminUsers = AdminUser::limit(10)->get();
$adminUsers->load('admin_user_info');
array_get、data_get 方法代替多个 isset 判断
// 获取一个用户下文章的一条评论
$user = User::with(['article.comment'])->first();
$content = isset($user->article) ? (isset($user->article->comment) ? $user->article->comment : '') : ''; // 传统写法
$content = data_get($user, 'article.comment.content'); // 评论内容
单数命名时候 Model 类不用定义 protected $table
重写 Model 类的 getTable 方法
public function getTable()
{
if (isset($this->table)) {
return $this->table;
}

return str_replace('\\', '', Str::snake(class_basename($this)));
}
$validator = \Validator::make(request()->all(), [
'buyer_id' => 'required',
'amount' => 'required|numeric|gt:0'
], [
'buyer_id.required' => ':attribute不能为空',
'amount.required' => '请填写:attribute',
'amount.numeric' => ':attribute必须为数字',
'amount.gt' => ':attribute必须大于0'
], [
'buyer_id' => '客户id',
'amount' => '金额'
]);
if ($validator->fails()) {
return $this->ajaxFail($validator->messages()->first());
} https://blog.baiguiren.com/2018/07/29/laravel/%E4%B8%AA%E4%BA%BA%E5%9C%A8%20Laravel%20%E5%BC%80%E5%8F%91%E4%B8%AD%E4%BD%BF%E7%94%A8%E5%88%B0%E7%9A%84%E4%B8%80%E4%BA%9B%E6%8A%80%E5%B7%A7(%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0)/

外部调用类的私有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class A
{
protected $protected = 'protected';

private $private = 'private';

protected function protectedMethod()
{
return 'Hello protected';
}

private function privateMethod()
{
return 'Hello private';
}
public function __get($name)
{
return $this->$name;
}

public function __call($name, $arguments)
{
return $this->$name(...$arguments);
}
}
$test = new A();

$test->protected;
$test->private;
$test->protectedMethod();
$test->privateMethod();
$test = function () {
echo $this->private;
echo $this->privateMethod();
};
$test = $test->bindTo(new A(), 'A');
$test();

30 分钟未付款自动取消订单

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
先保证 redis 的版本大于 2.8 ,现在绝大部分不成问题了,然后修改 redis 的配置文件,加入:notify-keyspace-events "Ex"
Cache::store('redis')->put('ORDER_CONFIRM:'.$order->id,$order->id,30); // 30分钟后过期--执行取消订单
然后我们来监听 ORDER_CONFIRM:ORDER_ID 的过期事件
先建个命令,我们一会儿的监听全靠他了。
php artisan make:command OrderExpireListen
然后把命令执行文件 app\Console\Commands\OrderExpireListen.php 写成这样:
namespace App\Console\Commands;
use App\Http\Models\Order;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis as Redis;
class OrderExpireListen extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'order:expire';
/**
* The console command description.
*
* @var string
*/
protected $description = '监听订单创建,在30分钟后如果没付款取消订单。';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
//
$cachedb = config('database.redis.cache.database',0);
$pattern = '__keyevent@'.$cachedb.'__:expired';
Redis::subscribe($pattern,function ($channel){ // 订阅键过期事件
$key_type = str_before($channel,':');
switch ($key_type) {
case 'ORDER_CONFIRM':
$order_id = str_after($channel,':'); // 取出订单 ID
$order = Order::find($order_id);
if ($order) {
$order->cancel(); // 执行取消操作
}
break;
case 'ORDER_OTHEREVENT':
break;
default:
break;
}
});
}
}
文件好了之后,使用
php artisan order:expire

解决自动断开连接超时现象
你改下 app\config\database.php 中 redis 节,增加一个 read_write_timeout :
'redis' => [
'client' => 'predis',
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
'read_write_timeout' => env('REDIS_RW_TIMEOUT', 5), // 读写超时设定
],
],
最后:使用supervisor监听order:expire http://118.25.60.91:9000/articles/30-fen-zhong-wei-fu-kuan-zi-dong-qu-xiao-ding-dan
https://crazyfzw.github.io/2019/03/26/redis-keyspace-notifications/
http://redisdoc.com/topic/notification.html

guzzle异步请求

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
$request = new \GuzzleHttp\Psr7\Request('GET', 'http://www.example.com');
$promise = $client->sendAsync($request)->then(function ($response) {
// 请求完成后的操作
$response->getBody();
});
$promise->wait();

$client = new Client(['base_uri' => 'http://httpbin.org/ip']);
$promise = $client->requestAsync('GET', 'http://httpbin.org/ip');
$promise->then(
function (ResponseInterface $res) {
echo $res->getStatusCode() . "\n";
},
function (RequestException $e) {
echo $e->getMessage() . "\n";
echo $e->getRequest()->getMethod();
}
);
dd($promise);
"guzzlehttp/guzzle": "5.3.1",
$client=new GuzzleHttp\Client();
$reqs = $client->createRequest('GET', 'http://httpbin.org/ip', ['future' => true]);
$client->send($reqs)->then(function ($response){
dump($response->json());//["origin" => "49.7.40.196, 49.7.40.196"] 这个输出在ok之后
});

dump('ok');

删除数组元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

删除数组元素有两种方式,第一种是使用 unset 方法。


unset($arr[1]);
此方法删除元素后,$arr[1] 为 null ,所有元素的索引值不变,也就是说元素不会重新排列,所以不是很实用。

要使元素删除后重新排列顺序,使用 array_splice 方法即可。


array_splice(array,start,length,array);
array_splice 的四个参数中,array 为要删除元素的数组;start 为要开始删除元素的位置(索引值);length 为要删除的元素数量;最后一个参数为可选参数,允许用一个新元素替代被删除的元素。

使用 array_splice 删除数组中索引为1的元素:


array_splice($arr,1,1);//重新索引了 不用array_values

如何查Session内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
首先配置Session存储,使用Memcached

session.save_handler = memcached
session.save_path = "localhost:11211"
查看session_id

<?php
session_start();
var_dump(session_id());
我们希望在Memcached里查询Session内容,怎么确定查询的Key呢?

尝试:

get vkfc4eefu7ihlu5iv7v3je8rm0
无效,应该这样:

get memc.sess.key.vkfc4eefu7ihlu5iv7v3je8rm0
原来在使用Memcached存储Session, key前缀是可以配置的。

<?php
var_dump(ini_get('memcached.sess_prefix')); // 'memc.sess.key.'

https://php1024.com/posts/50.htm

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
// 加上时间戳存入队列
$now_time = date("Y-m-d H:i:s");
$redis->rPush("call_log", $_GET['info'] . "%" . $now_time);
$redis->close();

// init redis
$redis_xx = new Redis();
$redis_xx->connect('ip', port);
$redis_xx->auth("password");
// 获取现有消息队列的长度
$count = 0;
$max = $redis_xx->lLen("call_log");
// 获取消息队列的内容,拼接sql
$insert_sql = "insert into fb_call_log (`interface_name`, `createtime`) values ";
// 回滚数组
$roll_back_arr = array();
while ($count < $max) {
$log_info = $redis_cq01->lPop("call_log");
$roll_back_arr = $log_info;
if ($log_info == 'nil' || !isset($log_info)) {
$insert_sql .= ";";
break;
}
// 切割出时间和info
$log_info_arr = explode("%",$log_info);
$insert_sql .= " ('".$log_info_arr[0]."','".$log_info_arr[1]."'),";
$count++;
}
// 判定存在数据,批量入库
if ($count != 0) {
$link_2004 = mysql_connect('ip:port', 'user', 'password');
if (!$link_2004) {
die("Could not connect:" . mysql_error());
}
$crowd_db = mysql_select_db('fb_log', $link_2004);
$insert_sql = rtrim($insert_sql,",").";";
$res = mysql_query($insert_sql);
// 输出入库log和入库结果;
echo date("Y-m-d H:i:s")."insert ".$count." log info result:";
echo json_encode($res);
echo "</br>\n";

// 数据库插入失败回滚
if(!$res){
foreach($roll_back_arr as $k){
$redis_xx->rPush("call_log", $k);
}
}

// 释放连接
mysql_free_result($res);
mysql_close($link_2004);
}
http://cuihuan.net/2017/01/20/%E3%80%90%E9%AB%98%E5%B9%B6%E5%8F%91%E7%AE%80%E5%8D%95%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E3%80%91redis%E9%98%9F%E5%88%97%E7%BC%93%E5%AD%98%20+%20mysql%20%E6%89%B9%E9%87%8F%E5%85%A5%E5%BA%93%20+%20php%E7%A6%BB%E7%BA%BF%E6%95%B4%E5%90%88/

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
class UserStatsCsvExporter implements UserStatsExporterContract
{
public function export(int $userId)
{
// Load user statistics...
// Export file...
}
}
在控制器中,我们会 new 一个类,然后调用里面的 export 方法。

class ExportController extends Controller
{
public function handle()
{
$userStatsExporter = new UserStatsCsvExporter();

return $userStatsExporter->export(12);
}

public function handle2(UserStatsCsvExporter $userStatsExporter)
{
return $userStatsExporter->export(12);
}
}

class UserStatsCsvExporter implements UserStatsExporterContract
{

/** @var Translator */
private $translator;

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

public function export(int $userId)
{
// Load user statistics...
// Export file...
}
}
class Translator
{
/** @var string */
private $language;

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

public function translate(string $word)
{
// Translate word...
}
}

class UserStatsExporterProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(UserStatsCsvExporter::class, function() {
return new UserStatsCsvExporter(new Translator(config('app.locale')));
});
}
}
绑定到接口
public function handle(UserStatsXmlExporter $userStatsExporter)
{
return $userStatsExporter->export(12);
}

public function register()
{
$this->app->bind(UserStatsXmlExporter::class, function() {
return new UserStatsXmlExporter(new Translator(config('app.locale'))
});
}

public function handle(UserStatsExporterContract $userStatsExporter)
{
return $userStatsExporter->export(12);
}
要让上面代码工作,我们还需要改动服务提供者的代码。

public function register()
{
$this->app->bind(UserStatsExporterContract::class, function() {
return new UserStatsXmlExporter(new Translator(config('app.locale')));
});
}

public function handle(UserStatsExporterContract $userStatsExporter)
{
dd(app(UserStatsExporterContract::class), app(UserStatsExporterContract::class));

return $userStatsExporter->export(12);
}
使用 singleton 来替换 bind 方法即可。

public function register()
{
$this->app->singleton(UserStatsExporterContract::class, function() {
return new UserStatsXmlExporter(new Translator(config('app.locale')));
});
}
https://learnku.com/articles/32333

反射动态代理

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
class mysql{
function connect($db){
echo "连接到数据库${db[0]}\r\n";
}
}

class sqlproxy{
private $target;
function __construct($tar)
{
$this->target[]=new $tar();
}
function __call($name, $args)
{
foreach ($this->target as $obj) {
$r=new ReflectionClass($obj);
if ($method= $r->getMethod($name)) {
if ($method->isPublic() && !$method->isAbstract()) {
echo "方法前拦截记录LOG\r\n";
$method->invoke($obj,$args);
echo "方法后拦截\r\n";
}
}
}
}
}
$obj=new sqlproxy('mysql');
$obj->connect('member');
方法前拦截记录LOG
连接到数据库member
方法后拦截
https://learnku.com/articles/32943

安装

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
https://pecl.php.net/package/imagick  
#wget http://pecl.php.net/get/imagick-3.4.4.tgz
#tar -xzvf imagick-3.4.4.tgz
#cd imagick-3.4.4
#phpize
#./configure --with-php-config=/php7/bin/php-config --with-imagick=/usr/local/imagemagick
checking Testing /usr/bin/Wand-config... Doesn't exist
checking Testing /usr/sbin/bin/MagickWand-config... Doesn't exist
checking Testing /usr/sbin/bin/Wand-config... Doesn't exist
checking Testing /opt/bin/MagickWand-config... Doesn't exist
checking Testing /opt/bin/Wand-config... Doesn't exist
checking Testing /opt/local/bin/MagickWand-config... Doesn't exist
checking Testing /opt/local/bin/Wand-config... Doesn't exist
configure: error: not found. Please provide a path to MagickWand-config or Wand-config program.

#yum install ImageMagick-devel -y
# ./configure --with-php-config=/php7/bin/php-config --with-imagick=/usr/local/imagemagick

checking whether stripping libraries is possible... yes
checking if libtool supports shared libraries... yes
checking whether to build shared libraries... yes
checking whether to build static libraries... no

creating libtool
appending configuration tag "CXX" to libtool
configure: creating ./config.status
config.status: creating config.h
#make && make install
Build complete.
Don't forget to run 'make test'.

Installing shared extensions: /php7/lib/php/extensions/no-debug-zts-20151012/
Installing header files: /php7/include/php/
php7 --ini
Configuration File (php.ini) Path: /opt/php7/etc/
Loaded Configuration File: /opt/php7/etc/php.ini
Scan for additional .ini files in: (none)
Additional .ini files parsed: (none)
#vi php.ini
extension=/opt/php7/lib/php/extensions/no-debug-zts-20151012/imagick.so

phpstorm gitbash

1
2
3
4
5
6
7
8
9
10
11
打开 PHPStorm 的设置,定位到 Tools -> Terminal
将 Shell Path 选项改为 {git 安装目录}\bin\sh.exe,重新打开终端,可以看到已切换成了 git bash
另外 git bash 可能会出现中文乱码的问题,需要在 Git 的安装目录下找到 ./etc/bash.bashrc 文件,在末尾添加:

$ vim ./etc/bash.bashrc

# support chinese
export LANG="zh_CN.UTF-8"
export LC_ALL="zh_CN.UTF-8"

https://learnku.com/articles/32981

composer 找不到文件

1
2
3
4
5
6
7
8
9
10
11
 findClasses 函数中,有这么一个去除代码中 heredoc 或 nowdoc 的操作:

// strip heredocs/nowdocs
$contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)(?:\s*)\\2(?=\s+|[;,.)])}s', 'null', $contents);
问题就出在这个正则。对于内容比较长的类源文件,这个正则替换可能会直接报错返回 null,这样就导致 composer 无法识别出该类,从而你的应用中就会报类不存在的错误。

通过调用 preg_last_error () 函数可以获取正则错误代码。我这里返回的错误代码为 2,也就是 PREG_BACKTRACK_LIMIT_ERROR,意思是回溯限制错误。这个错误受 php 配置影响,可以把 pcre.backtrack_limit 参数设置更大或者直接设置成 - 1 不受限制(可能会造成性能问题,谨慎操作)。这也是前面提到的为什么有的机器上正常,有的机器上又不正常,原因就在于这个配置不同。

OK,到此弄清楚是什么原因了。解决办法:1、可以将 pcre.backtrack_limit 参数设置大点再试试 2、看是否能减小类源文件中 heredocs/nowdocs 字符串的大小。此处我的解决办法是直接把这个超长 heredocs 字符串独立到 sql 文件中,代码中读取文件内容即可。

https://learnku.com/articles/32972

分组查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
select `u`.`id`,`u`.`name`,`u`.`num` from `users` as `u` left join (select `user_id`,count(*) as `num` from books group by `user_id`) as `b` on `u`.id = `b`.user_id  查询每个人的书数量
select distinct `u`.`id`,`u`.`name`,IFNULL( `b`.`num`, 0 ) AS num from `users` as `u` left join (select `user_id`,count(*) as `num` from books group by `user_id`) as `b` on `u`.id = `b`.user_id

//统计出所有内部员工的user_id

$user_ids = [1,2,3,4,5];

$bookQuery = Book::selectRaw('user_id,count(*) as number')->whereIn('user_id', $user_ids)->groupBy('user_id'); //制作一个query builder

$users = User::selectRaw('u.id,IFNULL( b.number, 0 ) AS number')
->from('users as u')
->distinct()
->whereIn('id', $user_ids)
->leftJoin(\DB::raw("({$bookQuery->toSql()}) as b"),function ($join) use($bookQuery){
//toSql()返回的是等待绑定参数的SQL语句
$join->mergeBindings($bookQuery->getQuery())->on('u.id','=','b.user_id');
//mergeBindings是将SQl的参数进行绑定
})
->get();
https://www.guaosi.com/2019/03/19/laravel-with-limit-and-group-show-zero/

取k个不重复的数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function getRandomN($max, $num) {
$count = 0;
$return = array();
while ($count < $num) {
$return[] = mt_rand(0, $max);
$return = array_flip(array_flip($return));
$count = count($return);
}
shuffle($return);
return $return;
}

return getRandomN(20, 10);

// output:[7,14,6,12,3,4,15,0,16,10]
function getRandomN($num, $n){
$startArray = range(0, $num);
$resultArray = [];
for($i = 0; $i < $n; $i++)
{
$random = mt_rand(0, $num - $i);
$resultArray[$i] = $startArray[$random];
$startArray[$random] = $startArray[$num - $i - 1];
}

return $resultArray;
}

return getRandomN(20, 10);
https://alpha2016.github.io/2019/04/09/PHP%E4%BB%8E0-n%E4%B9%8B%E9%97%B4%E9%9A%8F%E6%9C%BA%E5%8F%96k%E4%B8%AA%E4%B8%8D%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0/

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
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
composer require intervention/image
// 包含autoload文件
require 'vendor/autoload.php';

// 导入ImageManager类
use Intervention\Image\ImageManager;

// 创建ImageManager实例并指定要使用的驱动(默认GD库)
$manager = new ImageManager(array('driver' => 'imagick'));

// 创建Image实例然后操作图片
$image = $manager->make('public/foo.jpg')->resize(300, 200);

// 创建一个Image对象
$img = Image::make('public/foo.jpg');

// 获取图片文件大小
$size = $img->filesize();

// 获取图片尺寸(像素)
$width = $img->width();
$height = $img->height();

// 调整图片尺寸
$img->resize(300, 200);

// 调整图片的宽或高
$img->resize(300, null);
$img->resize(null, 200);

// 调整图片的宽同时保持图片比例
$img->resize(300, null, function ($constraint) {
$constraint->aspectRatio();
});

// 调整图片的高同时保持图片比例
$img->resize(null, 200, function ($constraint) {
$constraint->aspectRatio();
});

// 避免处理时造成文件大小增加
$img->resize(null, 400, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});

// 转码为其他格式并压缩图片
$img->encode('jpg', 75);

// 转码为 Data URL
$img->encode('data-url');

// 裁剪图片
$img->crop(100, 100, 25, 25);

// 旋转-45度
$img->rotate(-45);

// 垂直翻转及水平翻转
$img->flip('v');
$img->flip('h');

// 以最优方案裁剪为600*360
$img->fit(600, 360);

// 以最优方案裁剪为200*200
$img->fit(200);

// 以默认质量保存图片
$img->save('public/bar.jpg');

// 以给定质量保存图片
$img->save('public/bar.png', 60);
https://yiming.blog/article/201702121305.html

Redis PubSub实现简单消息推送

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
<!DOCTYPE html>
<html>
<head>
<title>swoole chat room</title>
<meta charset="UTF-8">
<script type="text/javascript">
if(window.WebSocket){
// 端口和ip地址对应不要写错
var webSocket = new WebSocket("ws://127.0.0.1:9502");
webSocket.onopen = function (event) {
//webSocket.send("Hello,WebSocket!");
};
webSocket.onmessage = function (event) {
var content = document.getElementById('content');
content.innerHTML = content.innerHTML.concat('<p style="margin-left:20px;height:20px;line-height:20px;">'+event.data+'</p>');
}

var sendMessage = function(){
var data = document.getElementById('message').value;
webSocket.send(data);
}
}else{
console.log("您的浏览器不支持WebSocket");
}
</script>
</head>
<body>
<div style="width:600px;margin:0 auto;border:1px solid #ccc;">
<div id="content" style="overflow-y:auto;height:300px;"></div>
<hr/>
<div style="height:40px">
<input type="text" id="message" style="margin-left:10px;height:25px;width:450px;">
<button onclick="sendMessage()" style="height:28px;width:75px;">发送</button>
</div>
</div>
</body>
</html>

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

$server->on('workerStart', function ($server, $workerId) {
$client = new swoole_redis;
$client->on('message', function (swoole_redis $client, $result) use ($server) {
if ($result[0] == 'message') {
var_dump($result);
foreach($server->connections as $fd) {
$server->push($fd, '频道为: ' $result[1] . ' 发送消息:' . $result[2]);
}
}
});
$client->connect('127.0.0.1', 6379, function (swoole_redis $client, $result) {
$client->subscribe('message'); // 队列名称可以自定义 });
});

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

// 屏蔽这段会报错 $server->on('message', function (swoole_websocket_server $server, $request) {
$server->push($request->fd, "hello");
});

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

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

FFmpeg 截取视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//获取文件路径
fwrite(STDOUT, "哪个视频文件:");
$video_path = trim(fgets(STDIN));

fwrite(STDOUT, "多少秒截取1张图:");
$s = trim(fgets(STDIN));

//设置输出路径
fwrite(STDOUT, "输出到哪里:");
$export_path = trim(fgets(STDIN));

$command = system('ffmpeg -y -i '.$video_path.' -vf "fps=1/'.$s.',scale=iw/4:-1,tile=2x2" -an '.$export_path.'_%d.png');

// fps = 1/2 每2秒截一张图,如果是每秒截一张 参数就是 fps=1
// scale 截图大小,上面的代码是设置宽为原始的1/4大小,高度自动,也可以设置成固定值如:120:80
// tile 网格化,自动将100张图合并成一张大图 https://learnku.com/laravel/t/33066

求两个集合的交集

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
$m = [1, 2, 3, 4];
$n = [1, 4, 7, 9];
$intersection = [];

foreach ($m as $m_v) {
foreach ($n as $n_v) {
if ($m_v === $n_v) {
$intersection = $m_v;//4
}
}
}
$m = [1, 2, 3, 4];
$n = [1, 4, 7, 9];
$intersection = [];

// 获取两个数组的长度
$length_m = count($m);
$length_n = count($n);

// 定义两个指针
$i = $j = 0;
$intersection = [];

while ($i < $length_m && $j < $length_n) {
if ($m[$i] === $n[$j]) {
$intersection[] = $m[$i];
} else if ($m[$i] < $n[$j]) {
$i++;
} else {
$j++;
}
}
https://www.hanjiaxin.com/2018/12/13/algorithm-1/

无限级分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
& 引用赋值
function doloop1(&$i = 1)
{
print_r($i);
$i++;
if ($i <= 10) {
doloop1($i);
}
}
doloop1();
static 静态变量
function doloop2()
{
static $i = 1;
print_r($i);
$i++;
if ($i <= 10) {
doloop2();
}
}
doloop2();
global 全局变量
$i = 1;
function doloop3()
{
global $i;
echo $i;
$i++;
if ($i <= 10) {
doloop3();
}
}
doloop3();
$data = [
['id' => 1, 'title' => 'Electronics', 'parent_id' => 0],
['id' => 2, 'title' => 'Laptops & PC', 'parent_id' => 1],
['id' => 3, 'title' => 'Laptops', 'parent_id' => 2],
['id' => 4, 'title' => 'PC', 'parent_id' => 2],
['id' => 5, 'title' => 'Cameras & photo', 'parent_id' => 1],
['id' => 6, 'title' => 'Camera', 'parent_id' => 5],
['id' => 7, 'title' => 'Phones & Accessories', 'parent_id' => 1],
['id' => 8, 'title' => 'Smartphones', 'parent_id' => 7],
['id' => 9, 'title' => 'Android', 'parent_id' => 8],
['id' => 10, 'title' => 'iOS', 'parent_id' => 8],
['id' => 11, 'title' => 'Other Smartphones', 'parent_id' => 8],
['id' => 12, 'title' => 'Batteries', 'parent_id' => 7],
['id' => 13, 'title' => 'Headsets', 'parent_id' => 7],
['id' => 14, 'title' => 'Screen Protectors', 'parent_id' => 7],
];

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

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

return $tree;
}

/**
* 递归获取无限极分类树
*
* @param array $data
* @param int $parent_id
* @param int $level
* @return array
*/
function make_tree2($data = [], $parent_id = 0, $level = 0)
{
$tree = [];
if ($data && is_array($data)) {
foreach ($data as $v) {
if ($v['parent_id'] == $parent_id) {
$tree[] = [
'id' => $v['id'],
'level' => $level,
'title' => $v['title'],
'parent_id' => $v['parent_id'],
'children' => make_tree2($data, $v['id'], $level + 1),
];
}
}
}
return $tree;
}
获取子节点以及节点的层级

/**
* 引用赋值方式
* @param array $data
* @param int $id
* @param int $level
* @return array
*/
function getSubTree($data = [], $id = 0, $level = 0)
{
static $tree = [];

foreach ($data as $key => $value) {
if ($value['parent_id'] == $id) {
$value['laravel'] = $level;
$tree[] = $value;
getSubTree($data, $value['id'], $level + 1);
}
}
return $tree;
}

/**
* 静态变量方式
* @param array $data
* @param int $id
* @param int $level
* @return array
*/
function getSubTree($data = [], $id = 0, $level = 0)
{
static $tree = [];

foreach ($data as $key => $value) {
if ($value['parent_id'] == $id) {
$value['laravel'] = $level;
$tree[] = $value;
getSubTree($data, $value['id'], $level + 1);
}
}
return $tree;
}

/**
* 全局变量方式
* @param array $data
* @param int $id
* @param int $level
* @return array
*/

$tree = []; //先申明变量
function getSubTree($data = [], $id = 0, $level = 0)
{
global $tree;

foreach ($data as $key => $value) {
if ($value['parent_id'] == $id) {
$value['laravel'] = $level;
$tree[] = $value;
getSubTree($data, $value['id'], $level + 1);
}
}
return $tree;
}
通过 pid 获取所有上级分类 常用于面包屑导航

/**
* getParentsByParentId2($categories, 9)
*
* @param array $data
* @param $parent_id
* @return array
*/
function getParentsByParentId($data = [], $parent_id)
{
static $categories = [];

if ($data && is_array($data)) {
foreach ($data as $item) {
if ($item['id'] == $parent_id) {
$categories[] = $item;
getParentsByParentId($data, $item['parent_id']);
}
}
}
return $categories;
}

function getParentsByParentId2($data = [], $parent_id)
{
$categories = [];

if ($data && is_array($data)) {
foreach ($data as $item) {
if ($item['id'] == $parent_id) {
$categories[] = $item;
$categories = array_merge($categories, getParentsByParentId2($data, $item['parent_id']));
}
}
}
return $categories;
}
https://learnku.com/articles/28252

MVC 的 “拓展 “模式

1
2
3
我们把 model 当成 Eloquent class,用一个处理数据库逻辑的 Repository 来辅助它,同样对于 controller 来言,它也有一个辅助它的功臣,那就是能处理商业逻辑 Service,这样就解决了臃肿的问题,view 呢?我们是不是忽略了它,并不是的,它也有属于它的处理显示逻辑的 Presenter
由于 SOLID 的单一职责原则与依赖反转原则:我们将数据库逻辑从 model 分离出来,由 repository 辅助 model,将 model 依赖注入进 repository。同样我们将商业逻辑从 controller 分离出来,由 service 辅助 controller,将 service 依赖注入进 controller。显示逻辑也从 view 分离出来,由 presenter 辅助 view,将 presenter 依赖注入进 view。这样写就避免了臃肿和后期维护的不方便。
https://learnku.com/articles/33413

window composer reuqire

1
2
3
4
5
6
composer require "overtrue/laravel-wechat:~4.0" 
实测 Linux 安装 PHP 扩展 pcntl 也可解决。

如使用 Windows,可composer.json 中增加以下两个字段 config(安装 ext-posix 和 ext-pcntl 的 php7.2 版本扩展)
"platform": { "ext-pcntl": "7.2", "ext-posix": "7.2"}
https://learnku.com/laravel/t/33416

位非运算

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
echo ~48&~75;
/**
* 最好了解下二进制的四则运算和位运算以及运算电路的相关涉及知识^_^
* 48的二进制值是:00110000 十六进制值是:ffffff30【我系统是64位的输出的结果就是这样】
* 其实呢计算机内存在存储数据的时候会把负数转换为补码存储【正数的原码,反码,补码完全一样】
* 而负数呢最高位为1用于表示负数,它的反码呢和正数是相反的,需要将0转换为1,1转换为0,补码操作就是加1操作
* 当然不同位数的内存长度不一样
*
* 75的二进制值是:01001011[我按8位算] 【如果是16位的就是:11111111 01001011,32位,64位的也是同样的道理】
* echo ~48;//结果为-49
* //1、转换为二进制时结果为:00110000【64位系统十六进制就是ffffff30】
* //2、此时反码为:11001111【64位系统十六进制就是ffffffcf】
* //3、减1操作为:11001110 【64位系统十六进制就是ffffffce】【11111111 11111111 11111111 11001110】
* //4、还原为:00110001【64位系统十六进制就是80000031】【二进制为:10000000 00000000 00000000 00110001】
* //5、二进制求和结果为:1+16+32=49【注意二进制的最高位1表示负数】输出的结果是:-49
*
* echo ~75;//结果为-76,一样的套路
*
* echo ~48&~75;//结果却是124
* 1、48的反码结果为:1100 1111
* 2、75的的码结果为:1011 0100
* 3、它们俩相与结果为:1000 0100 【十六进制为:ffffff84】【减1操作后为ffffff83】【自己想想十六进制如何数数^_^】
* 4、还原后为:0111 1100【十六进制为:8000007c】【二进制结果为:10000000 00000000 00000000 01111100】
* 5、求和结果为:-124
*/
它的结果是:-124
https://learnku.com/articles/33382

php html to pdf 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
30
31
32
33
34
35
git clone https://github.com/krakjoe/wkhtmltox
cd wkhtmltox
phpize
// 这里因为每个人安装环境不一样 /path/to/wkhtmltox/installation 请将这个地址替为wkhtmltox真实的安装路径
/// PATH 替换为真实的php配置路径
./configure --with-wkhtmltox=/path/to/wkhtmltox/installation --with-php-config = PATH
make
make install
将生成的 wkhtmltox.so 添加到 php.ini 的配置中,重启 php
use wkhtmltox\PDF\Converter as PDFConverter;
use wkhtmltox\PDF\Object as PDFObject;

$converter = new PDFConverter([
"out" => "test.pdf"
]);

$converter->add(new PDFObject(
file_get_contents("http://www.baidu.com")));

$converter->convert();

use wkhtmltox\Image\Converter as ImageConverter;
// 这里第一个参数可以传入 ` html ` 的字符串
$converter = new ImageConverter(null, [
"fmt" => "png",
"in" => "http://www.baidu.com",
"out" => "test.png"
]);

$converter = new ImageConverter($str, [
"fmt" => "jpg",
"out" => "test.jpg"
]);
$converter->convert();
https://learnku.com/articles/13472/teach-you-how-to-use-php-to-generate-pdf-and-image-gracefully

控制反转,依赖注入

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
// 定义写日志的接口规范
interface Log
{
public function write();
}

// 文件记录日志
class FileLog implements Log
{
public function write(){
echo 'file log write...';
}
}

// 数据库记录日志
class DatabaseLog implements Log
{
public function write(){
echo 'database log write...';
}
}

// 程序操作类
class User
{
protected $fileLog;

public function __construct()
{
$this->fileLog = new FileLog();
}

public function login()
{
// 登录成功,记录登录日志
echo 'login success...';
$this->fileLog->write();
}

}

$user = new User();
$user->login();
class User
{
protected $log;

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

public function login()
{
// 登录成功,记录登录日志
echo 'login success...';
$this->log->write();
}

}

$user = new User(new DatabaseLog());
$user->login();

// routes/web.php
Route::get('/post/store', 'PostController@store');

// App\Http\Controllers
class PostController extends Controller {

public function store(Illuminate\Http\Request $request)
{
$this->validate($request, [
'category_id' => 'required',
'title' => 'required|max:255|min:4',
'body' => 'required|min:6',
]);
}

}

// 获取User的reflectionClass对象
$reflector = new reflectionClass(User::class);

// 拿到User的构造函数
$constructor = $reflector->getConstructor();

// 拿到User的构造函数的所有依赖参数
$dependencies = $constructor->getParameters();

// 创建user对象
$user = $reflector->newInstance();

// 创建user对象,需要传递参数的
$user = $reflector->newInstanceArgs($dependencies = []);
这时候我们可以创建一个 make 方法,传入 User,利用反射机制拿到 User 的构造函数,进而得到构造函数的参数对象。用递归的方式创建参数依赖。最后调用 newInstanceArgs 方法生成 User 实例。 可能有些同学还不是很理解。下面我们用代码去简单模拟下

function make($concrete){
// 或者User的反射类
$reflector = new ReflectionClass($concrete);
// User构造函数
$constructor = $reflector->getConstructor();
// User构造函数参数
$dependencies = $constructor->getParameters();
// 最后生成User
return $reflector->newInstanceArgs($dependencies);
}

$user = make('User');
$user->login();

// 注意我们这里需要修改一下User的构造函数,如果不去修改。反射是不能动态创建接口的,那如果非要用接口该怎么处理呢?下一节我们讲Ioc容器的时候会去解决。

class User
{
protected $log;

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

public function login()
{
// 登录成功,记录登录日志
echo 'login success...';
$this->log->write();
}

}

function make($concrete){

$reflector = new ReflectionClass($concrete);
$constructor = $reflector->getConstructor();
// 为什么这样写的? 主要是递归。比如创建FileLog不需要传入参数。
if(is_null($constructor)) {
return $reflector->newInstance();
}else {
// 构造函数依赖的参数
$dependencies = $constructor->getParameters();
// 根据参数返回实例,如FileLog
$instances = getDependencies($dependencies);
return $reflector->newInstanceArgs($instances);
}

}

function getDependencies($paramters) {
$dependencies = [];
foreach ($paramters as $paramter) {
$dependencies[] = make($paramter->getClass()->name);
}
return $dependencies;
}

$user = make('User');
$user->login();
https://www.tinker777.com/show/12

英文字符占 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
// 计算单字节.
preg_match_all('/[a-zA-Z0-9_]/', $value, $single);
$single = count($single[0]) / 2;

// 多子节长度.
$double = mb_strlen(preg_replace('([a-zA-Z0-9_])', '', $value));

$length = $single + $double;
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));
}
function the_fucking_length_function($str)
{
return strlen(mb_convert_encoding($str, 'GB2312')) / 2;
//$length = (strlen($value) + mb_strlen($value)) / 2;
}
function mbstrlen($str) {
return ceil((strlen($str) + mb_strlen($str, "UTF8")) / 2);
}
mb_strwidth ('中国123','utf-8')//7
https://learnku.com/articles/4956/following-my-unique-needs-of-the-english-characters-accounted-for-05-chinese-characters-accounted-for-1
https://www.php.net/manual/zh/function.mb-strwidth.php

支付宝 App 第三方登录获取 用户信息

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
/**
* AlipayToken 获得用户 请求token, 通过它获得 用户信息
*
* 需要按照支付宝加签流程来。
*/
public function userInfo($app_auth_token)
{
$infoArr = [
'method' => 'alipay.system.oauth.token',
'app_id' => $this->app_id,
'charset' => 'utf-8',
'sign_type' => 'RSA2',
'timestamp' => date('Y-m-d H:i:s'),
'version' => '1.0',
'code' => $app_auth_token,
'grant_type' => 'authorization_code',
];

$signStr = $this->myHttpBuildQuery($infoArr);
$sign = urlencode($this->enRSA2($signStr));
$qureStr = $signStr.'&sign='.$sign;

$res = new Client();
$body = $res->get('https://openapi.alipay.com/gateway.do?'.$qureStr)->getBody()->getContents();
$body = json_decode($body);
if (!isset($body->alipay_system_oauth_token_response->access_token)) {
return '接口异常';
} else {
$autho_token = $body->alipay_system_oauth_token_response->access_token;
$userinfo = $this->aliPayUserInfo($autho_token);
return $userinfo; // 或则 返回 json_encode($userinfo) 根据实际需求来
}
}

/**
* AliPayUserInfo 通过 token 获取用户信息
*/
private function aliPayUserInfo($autho_token)
{
$infoArr = [
'method' => 'alipay.user.info.share',
'app_id' => $this->app_id,
'charset' => 'utf-8',
'sign_type' => 'RSA2',
'timestamp' => date('Y-m-d H:i:s'),
'version' => '1.0',
'auth_token' => $autho_token,
];

$signStr = $this->myHttpBuildQuery($infoArr);
$sign = urlencode($this->enRSA2($signStr));
$qureStr = $signStr.'&sign='.$sign;

$res = new Client();
$body = $res->get('https://openapi.alipay.com/gateway.do?'.$qureStr)->getBody()->getContents();
$body = json_decode($body);
if (!isset($body->alipay_user_info_share_response)) {
return '接口异常';
}
$body = $body->alipay_user_info_share_response;
return $body;
}
https://learnku.com/articles/30076#replies

树状数据结构存储方式

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
CREATE TABLE `categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` char(100) NOT NULL,
`pid` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
CREATE TABLE `categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` char(100) NOT NULL,
`lft` int(11) NOT NULL UNIQUE CHECK (lft> 0),
`rgt` int(11) NOT NULL UNIQUE CHECK (rgt> 1),
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
lft 和 rgt 是作为集合的边界,两者差值越大,则集合越大,里面的元素就越多。
根据子集,查找父级的分类

SELECT c2.*
FROM categories as c1, categories as c2
WHERE c1.lft BETWEEN c2.lft and c2.rgt
AND c1.title = '华为';

+----+-------------+-----+-----+
| id | title | lft | rgt |
+----+-------------+-----+-----+
| 1 | Smartphones | 1 | 14 |
| 5 | Harmony OS | 10 | 13 |
| 8 | 华为 | 11 | 12 |
+----+-------------+-----+-----+
根据父级,查找其底下所有的子集

SELECT c1.*
FROM categories AS c1, categories AS c2
WHERE c1.lft BETWEEN c2.lft AND c2.rgt
AND c2.title = 'Smartphones';

+----+-------------+-----+-----+
| id | title | lft | rgt |
+----+-------------+-----+-----+
| 1 | Smartphones | 1 | 14 |
| 3 | Android | 2 | 5 |
| 4 | iOS | 6 | 9 |
| 5 | Harmony OS | 10 | 13 |
| 6 | 小米 | 3 | 4 |
| 7 | iPhone | 7 | 8 |
| 8 | 华为 | 11 | 12 |
+----+-------------+-----+-----+
查看各个分类的级别

SELECT COUNT(c2.id) AS indentation, c1.title
FROM categories AS c1, categories AS c2下周三we'fv
WHERE c1.lft BETWEEN c2.lft AND c2.rgt
GROUP BY c1.title
ORDER BY c1.lft;

+-------------+-------------+
| indentation | title |
+-------------+-------------+
| 1 | Smartphones |
| 2 | Android |
| 3 | 小米 |
| 2 | iOS |
| 3 | iPhone |
| 2 | Harmony OS |
| 3 | 华为 |
+-------------+-------------+
https://learnku.com/articles/33630

敏感词过滤

PHP 中基于 Casbin 做 RBAC + RESTful 权限控制

方便导入和导出 Excel

windows10 swoole laravel

Windows 系统下的 Linux 子系统

【系统】打造自己最喜爱的 Windows10 —— U 盘启动盘制作篇

sentry 追踪 Laravel 生产环境 bug

尝试 Leetcode

LeetCode-Algorithms 算法题 PHP 实现

Leetcode database problem 数据库练习题

PHP 与 Swoole 浅析与学习

PHP开发环境laragon

php swoole视频

教你如何使用 PHP 优雅的生成 PDF 和 Image

使用 Laravel-snappy 生成 PDF 踩坑记录

初识wkhtmltopdf

个人学习记录

PHP 的在线正则表达式

优化Nginx及PHP-fpm的几种方式

php注释标记

PHP使用Dompdf

CentOS 下多版本 PHP 的安装与配置

终端显示你的 docker 状态

复杂度分享

Laravel-admin 源码分析系列

Laravel 如何实现数据的软删除

PHP开发规范单元测试

字符编码那些事儿

基于 gitbook 构建团队项目开发规范文档

PHP深入pack/unpack