​​​​ 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
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

HTTP 响应头部中,有一个字段,叫做 X-Frame-Options,该字段可以用来指示是否允许自己的网站被嵌入到其他网站的 <iframe> 或者 <object> 标签中。该头部有三个值

DENY - 始终不允许嵌入,即使是同一个域名
SAMEORIGIN - 只能在相同域名中嵌入
ALLOW-FROM uri - 设置允许的域
通常,可以在 HTTP 代理中进行配置,比如 nginx

add_header X-Frame-Options SAMEORIGIN;
Laravel 自带了用来「只允许同域名嵌入」的中间件,我们只需要在 /app/Http/Kernel.php 中添加即可

// /app/Http/Kernel.php
protected $middleware = [
\Illuminate\Http\Middleware\FrameGuard::class,
];
该中间件的实现如下

<?php

namespace Illuminate\Http\Middleware;

use Closure;

class FrameGuard
{
/**
* Handle the given request and get the response.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle($request, Closure $next)
{
$response = $next($request);

$response->headers->set('X-Frame-Options', 'SAMEORIGIN', false);

return $response;
}
}
https://learnku.com/articles/35201
https://securityheaders.com/

可以直接扫描特定 URL 是否包含一些安全头信息。类似的头还有 X-Content-Type-Options、 Referrer-Policy、Feature-Policy 等。

Uncaught ReflectionException: Class request does not exist

1
2
3
4
5
PHP Fatal error:  Uncaught ReflectionException: Class request does not exist in /home/vagrant/learnku/vendor/laravel/framework/src/Illuminate/Container/Container.php:790
Stack trace:
在 App\Exceptions\Handler::report() 方法里,使用:

dd($exception);

Laravel 下 TNTSearch+jieba-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
TNTSearch+jieba-php 这套组合可以在不依赖第三方的情况下实现中文全文搜索;
composer require vanry/laravel-scout-tntsearch
'providers' => [
...
/**
* TNTSearch 全文搜索
*/
Laravel\Scout\ScoutServiceProvider::class,
Vanry\Scout\TNTSearchScoutServiceProvider::class,
],
composer require fukuball/jieba-php

php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

配置项 config/scout.php 中增加 tntsearch

'tntsearch' => [
'storage' => storage_path('indexes'), //必须有可写权限
'fuzziness' => env('TNTSEARCH_FUZZINESS', false),
'searchBoolean' => env('TNTSEARCH_BOOLEAN', false),
'asYouType' => false,

'fuzzy' => [
'prefix_length' => 2,
'max_expansions' => 50,
'distance' => 2,
],

'tokenizer' => [
'driver' => env('TNTSEARCH_TOKENIZER', 'default'),

'jieba' => [
'dict' => 'small',
//'user_dict' => resource_path('dicts/mydict.txt'), //自定义词典路径
],

'analysis' => [
'result_type' => 2,
'unit_word' => true,
'differ_max' => true,
],

'scws' => [
'charset' => 'utf-8',
'dict' => '/usr/local/scws/etc/dict.utf8.xdb',
'rule' => '/usr/local/scws/etc/rules.utf8.ini',
'multi' => 1,
'ignore' => true,
'duality' => false,
],
],

'stopwords' => [
'的',
'了',
'而是',
],
],
env 增加配置项

SCOUT_DRIVER=tntsearch
TNTSEARCH_TOKENIZER=jieba
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Article extends Model
{
use Searchable;

/**
* 索引的字段
*
* @return array
*/
public function toSearchableArray()
{
return $this->only('id', 'title', 'content');

// return $this->toArray();
}
}
生成索引:

php artisan scout:import "App\Model\Article"
https://learnku.com/articles/27617#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
原理实现:有序集合从中间分为前后2部分,当要查当数值等于中间值,直接返回;当要查询数值大于中间值,则说明要查询的数值在后半部分,那么继续二分后半部分;当要查询数值小于中间数值时,说明要查询的数值在前半部分,那么继续二分前半部分。(过滤掉一半数据查询)

<?php
/**
* 递归实现
* array $arr 有序数组
* int $search 要查询的数字
* int firstIndex 数组起始位置
* int lastIndex 数组结束位置
* @return
*/
function binarySearchRecursion(array $arr, $search, $lastIndex, $firstIndex = 0)
{
$len = count($arr);
if ($len <= 0) {
return -1;
}
$middle = intval(($firstIndex + $lastIndex) / 2);
if ($search == $arr[$middle]) {//找到直接返回
return $arr[$middle];
} elseif ($search > $arr[$middle]) {//去后面查,数组起始位置变为$middle + 1。
return binarySearchRecursion($arr, $search, $middle + 1, $lastIndex);
} else {//去前面查,数组结束位置为$middle - 1。
return binarySearchRecursion($arr, $search, $firstIndex, $middle - 1);
}
return -1;
}

/**
* 递归实现
* array $arr 有序数组
* int $search 要查询的数字
* @return
*/
function binarySearch($arr, $search)
{
$len = count($arr);
if ($len <= 0 ) {
return -1;
}
$firstIndex = 0;
$lastIndex = $len - 1;
while($firstIndex <= $lastIndex) {
$middle = intval(($firstIndex + $lastIndex) / 2);
if ($search == $arr[$middle]) {//出口
return $arr[$middle];
} elseif ($search > $arr[$middle]) {//去后面查,数组起始位置变为$middle + 1。
$firstIndex = $middle + 1;
} else {//去前面查,数组结束位置为$middle - 1。
$lastIndex = $middle - 1;
}
}
return -1;
}
https://learnku.com/articles/35240

子孙树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$arr = [
['id'=>1,'parent_id'=>0,'type'=>5],
['id'=>2,'parent_id'=>1,'type'=>3],
['id'=>3,'parent_id'=>0,'type'=>1],
['id'=>4,'parent_id'=>3,'type'=>3],
];
var_dump(getSubTree($arr));

function getSubTree($data, $parent = 'parent_id', $son = 'id', $pid = 0)
{
$tmp = [];
foreach ($data as $key => $value) {
if ($value[$parent] == $pid) {
$value['child'] = getSubTree($data, $parent, $son, $value[$son]);
$tmp[] = $value;
}
}
return $tmp;
}
http://www.putyy.com/article/42

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
class UserController extends Controller
{
public function index()
{
$users = User::all();

return view('users.index', compact('users'));
}
}
改为 <?php

namespace App\Repositories;

use App\User;

class DbUserRepository
{
public function all(): array
{
return User::all()->toArray();
}
}

class UserController extends Controller
{
private $users;

public function __construct( )
{
$this->users = new DbUserRepository;
// $this->users = new RedisRepository;
}

public function index()
{
$users = $this->users->all();
return $users;
}
}
依赖正转的不合理之处在哪里呢?位于高层的控制器依赖于具体的底层数据获取服务,当底层发生变动时,就需要对应的修改高层的内部结构。

我们对依赖关系进一步分析,可知控制器关注的并不是具体如何获取数据,控制器关注的是「数据的可获取性」这一抽象。因此,我们应当将依赖关系进行反转,将对依赖的具体声明职责转移到外部,让控制器仅依赖于抽象层(数据的可获取性)。这种解决方式称之为 控制反转 或 依赖倒置。通过控制反转,高层不再依赖于具体的底层,仅仅是依赖于抽象层,高层和底层实现了解耦。
用接口来表示「数据的可获取性」这一抽象

<?php

namespace App\Repositories;

interface UserRepositoryInterface
{
public function all(): array;
}
UserController 依赖的是「数据的可获取性」,不依赖于具体的实现

class UserController extends Controller
{
private $users;

public function __construct(UserRepositoryInterface $users)
{
$this->users = $users;
}
}
具体的实现交给对应的仓库类即可

class DbUserRepository implements UserRepositoryInterface {}
class RedisRepository implements UserRepositoryInterface { }
根据自己的需要注入对应的服务,这样就实现了依赖注入。

$userRepository = new DbUserRepository;
$userController = new UserController($userRepository)
总的来说,依赖注入由四部分构成

被使用的服务 - DbUserRepository 或者 RedisRepository 等
依赖某种服务的客户端 - UserController
声明客户端如何依赖服务的接口 - UserRepositoryInterface
依赖注入器,用于决定注入哪项服务给客户端

一个简单的服务容器的实现

namespace App\Services;

use Exception;

class Container
{
protected static $container = [];

/**
* 绑定服务
*
* @param 服务名称 $name
* @param Callable $resolver
* @return void
*/
public static function bind($name, Callable $resolver)
{
static::$container[$name] = $resolver;
}

/**
* 解析服务
*
* @param 服务名称 $name
* @return mix
*/
public static function make($name)
{
if(isset(static::$container[$name])){
$resolver = static::$container[$name];
return $resolver();
}

throw new Exception("不存在该绑定");
}

}
绑定服务

App\Services\Container::bind('UserRepository', function(){
return new App\Repositories\DbUserRepository;
});
解析服务

$userRepository = App\Services\Container::make('UserRepository');
$userController = new UserController($userRepository)
Laravel 的服务容器的功能则更加的强大,比如,可以将接口与具体的实现进行绑定,通常在 服务提供者 中使用服务容器来进行绑定

public function register()
{
$this->app->singleton(UserRepositoryInterface::class, function ($app) {
return new UserRepository;
});
}

高并发业务场景下的秒杀解决方案

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

在秒杀前将商品的库存信息加入到 Redis 缓存中。如下格式:
$redis->lpush('商品id',1);
/**
*
* 1.接受用户请求
* 2.验证用户是否已经参与秒杀,商品是否存在
* 3.根据商品id减少商品队列中的库存数量
* 4.将用户的秒杀数据写入server层中,并返回秒杀数据对应的唯一key值
* 5.用户点击下单,根据serve层中的缓存数据,生成订单数据并减少数据库商品的库存数据
*/
$getParams = $_POST;
$userId = $getParams['userId'];
$goodsId = $getParams['goodsId'];

$key = 'goods:miaosha:';
$userResult = $redis->get($key.$userId);
if($userResult){
$userResult = json_decode($userResult,true);
echo json_encode(['result'=>$userResult['result'],'key'=>$key.$userId]);// 已经参与过秒杀了
die();
}else{
$goodqueue = 'goods:queue:'.$goodsId;
$result = $redis->lpop($goodqueue);// 删除商品redis队列缓存
if($result){
$data = json_encode(['result'=>'OK','userId'=>$userId,'goodsId'=>$goodsId]);
$redis->set($key.$userId,$data);// 将秒杀信息写入缓存中
echo json_encode(['result'=>'OK','userId'=>$userId,'goodsId'=>$goodsId,'key'=>$key.$userId]);
die();
}else{
echo json_encode(['result'=>'FAIL','message'=>'商品不存在','goodsId'=>$goodsId]);// 商品库存不存在
die();
}
}/**
* 用户下单界面
*/
require_once __DIR__.'/redis_connect.php';
$key = $_GET['key'];
$data = $redis->get($key);
/**
* 生成订单,订单入库
*
*/https://learnku.com/articles/35141
redis list 可以批量插入数据,不一定每次都只插入一个值.

$numberArr = range(1,100);
//var_dump($numberArr);
$redis->lPush('goods:queue:5',...$numberArr); // 可变参数

PHP 开启 Opcache 后如何优雅地部署 PHP 代码

1
2
3
4
5
提交了代码并且部署了以后,线上代码依然是旧的。所以我执行了下
/etc/init.d/php-fpm reload 就生效了。
opcache_reset (); 但是注意了 这个函数是强制清楚所有 cache 所以 如果并发较高的系统 不能直接这么搞 还有就是 它有两个清除模式,cli 下只能清除 cli 的 cache fpm 的话 必须通过 fpm 请求方式清除才行
https://github.com/gordalina/cachetool
https://learnku.com/laravel/t/35142

模型::query () 无数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function getPost()
{
return Post::query()
}

public function index()
{
$post = $this->getPost();
return view('index', [
'post' => $post
]);
}
@foreach($post->where('category_id',2)->get() as xxx){...}
@foreach($post->where('category_id',3)->get() as xxx){...}
改(clone $post)->where(xxxxxxxxx)
https://learnku.com/laravel/t/35341

JSON_encode 小数位丢失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$a = '{"orderAmt":500.00}';
$a_json_decode = json_decode($a,true);
[
"orderAmt" => 500.0,
]
$a_json_encode = json_encode($a_json_decode);
"{"orderAmt":500}"
使用字符串类型的 500.00,或使用字符串拼接 JSON
json_encode($a_json_decode, JSON_PRESERVE_ZERO_FRACTION)
=> "{"orderAmt":500.0}"

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

从 PHP 5.6.6+ 开始,json_encode 支持使用 JSON_PRESERVE_ZERO_FRACTION 选项以告知引擎确保浮点数始终编码为浮点数,但对于形如 500.00 (值为 500,精确到小数点后两位的浮点数)仅能保证最终输出 500.0

即使临时配置 serialize_precision 为 10,在初始化数组时使用 number_format 函数格式化小数位数,最后在 json_encode 时填入选项 JSON_PRESERVE_ZERO_FRACTION | JSON_NUMERIC_CHECK 也仅能输出 500.0

支付宝公钥证书 PHP 版本 SDK

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
function getRootCertSN($str)
{
// return '687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6';
$arr = preg_split('/(?=-----BEGIN)/', $str, -1, PREG_SPLIT_NO_EMPTY);
$str = null;
foreach ($arr as $e) {
$sn = getCertSN($e, true);
if (!$sn) {
continue;
}
if ($str === null) {
$str = $sn;
} else {
$str .= "_" . $sn;
}
}
return $str;
}

function getCertSN($str, $matchAlgo = false)
{
/*
根据java SDK源码:AntCertificationUtil::getRootCertSN
对证书链中RSA的项目进行过滤(猜测是gm国密算法java抛错搞不定,故意略去)
java源码为:

if(c.getSigAlgOID().startsWith("1.2.840.113549.1.1"))

根据 https://www.alvestrand.no/objectid/1.2.840.113549.1.1.html
该OID为RSA算法系。
*/
if ($matchAlgo) {
openssl_x509_export($str, $out, false);
if (!preg_match('/Signature Algorithm:.*?RSA/im', $out, $m)) {
return;
}

}
$a = openssl_x509_parse($str);
$issuer = null;
// 注意:根据java代码输出,需要倒着排列 CN,OU,O
foreach ($a["issuer"] as $k => $v) {
if ($issuer === null) {
$issuer = "$k=$v";
} else {
$issuer = "$k=$v," . $issuer;
}
}
# echo($issuer . $a["serialNumber"] . "\n");
$serialNumberHex = decimalNotation($a['serialNumberHex']);
$sn = md5($issuer . $serialNumberHex);
return $sn;
}

function decimalNotation($hex)
{
$dec = 0;
$len = strlen($hex);
for ($i = 1; $i <= $len; $i++) {
$dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
}
return $dec;
}
https://learnku.com/articles/35315

字符串表达式计算

1
2
3
4
5
6
7
8
9
$a = 10;
var_dump(eval('return $a > 5;'));

// 输出:
// bool(true)
system('php -r "echo 1 + 2;"');

echo exec('php -r "echo 1 + 2;"');
https://shockerli.net/post/php-expression-string/

用户登录密码改为 md5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
新建一个文件 Libraries,在 Libraries 目录下新建一个 MD5.php 文件

namespace App\Libraries;

use Illuminate\Contracts\Hashing\Hasher;

class MD5 implements Hasher
{
/**
* Hash the given value.
*
* @param string $value
*
* @return array $options
* @return string
*/
public function make($value, array $options = [])
{
return md5($value);
}

/**
* Check the given plain value against a hash.
*
* @param string $value
* @param string $hashedValue
* @param array $options
*
* @return bool
*/
public function check($value, $hashedValue, array $options = [])
{
if(empty($hashedValue)){
return true;
}
return $this->make($value) === $hashedValue;
}

/**
* Check if the given hash has been hashed using the given options.
*
* @param string $hashedValue
* @param array $options
*
* @return bool
*/
public function needsRehash($hashedValue, array $options = [])
{
return false;
}
}
Providers 文件下面新建一个文件 MD5ServiceProvider.php
namespace App\Providers;

use Illuminate\Auth\EloquentUserProvider;
class MD5ServiceProvider extends EloquentUserProvider
{

//继承EloquentUserProvider类,调用父类的构造函数
public function __construct($hasher, $model)
{
parent::__construct($hasher, $model);
}

/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
//
}

/**
* Register the application services.
*
* @return void
*/
public function register()
{
//
}
}
AuthServiceProvider.php 文件里 boot 方法里添加如下代码

Auth::provider('MD5', function ($app) {
$model = config('auth.providers.users.model');
return new MD5ServiceProvider(new MD5, $model);
});
修改 config/auth.php 里的 providers
'providers' => [
'users' => [
'driver' => 'MD5',//'driver' => 'eloquent',//eloquent默认加密码方式
'model' => App\User::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
],
修改 app/Http/Controllers/Auth/RegisterController.php 里的 create,修改代码如下

protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => md5($data['password']),
]);
}
https://learnku.com/articles/35407

Laravel跨库跨连接的事务操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Laravel 下的跨库事务操作是基于连接的 当执行 DB::beginTransaction(); 的时候 其实是和默认的数据库配置建立了连接 后面的操作 commit 或者 rollback 都是操作的这个默认数据库 如果在这中间操作了其他的数据库 对他是不生效的
同时 commit 和 rollback 都 指定连接
try {
//开启默认数据库的事务
DB::beginTransaction();
//开启test数据库的事务
DB::connection('test')->beginTransaction();
//中间各种数据库操作
Table1::xxxxxx();
Table2::xxxxxx();
if (true) {
//一起提交
DB::commit();
DB::connection('test')->commit();
} else {
//一起回滚
DB::rollback();
DB::connection('test')->rollback();
}
} catch (\Exception $exception) {
echo "catch some errors:".$exception->getMessage();
}
https://caihongtengxu.github.io/2018/20181009/index.html

array_splice无法自定义键值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function array_insert (&$array, $position, $insert_array) {
$first_array = array_splice ($array, 0, $position);
$array = array_merge ($first_array, $insert_array, $array);
}


$arr = array(
'tt' => 1333,
'cc' => 333,
'aaz' => 2333,
'ee' => 78,
);
$temp["bb"] = 33;
array_insert($arr,1,$temp);
>>> $arr
=> [
"tt" => 1333,
"bb" => 33,
"cc" => 333,
"aaz" => 2333,
"ee" => 78,
]

数字转度量

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
/**
* 数字转度量
*
* @param int $num 数字
* @return string|int
*/
function num2metric($num, $precision = 0) {
$unitList = [
'P' => 15,
'T' => 12,
'G' => 9,
'M' => 6,
'W' => 4,
'K' => 3,
];

$num = (int) $num;

foreach($unitList as $name => $pow) {
$size = pow(10, $pow);

if($num >= $size) {
return round($num / $size * 100, $precision) / 100 . $name;
}
}

return $num;
}
https://www.hongfs.cn/2018/10/php/php-metric-prefix/
num2metric(1000) // 1K
num2metric('10000') // 1W
num2metric('A') // 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
/**
* 获取两个坐标之间距离
*
* @param int|float $lat1 第一个坐标纬度
* @param int|float $lon1 第一个坐标经度
* @param int|float $lat2 第一个坐标纬度
* @param int|float $lon2 第二个坐标经度
* @param string $unit 距离单位 M 法定英里 K 公里 N 海里
* @return int|float
*/
function getDistanceBetweenPoints($lat1, $lon1, $lat2, $lon2, $unit = 'K') {
if (($lat1 == $lat2) && ($lon1 == $lon2)) {
return 0;
}

$theta = $lon1 - $lon2;
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
$dist = acos($dist);
$dist = rad2deg($dist);
$miles = $dist * 60 * 1.1515;
$unit = strtoupper($unit);

if ($unit === 'K') {
return $miles * 1.609344;
} else if ($unit === 'N') {
return $miles * 0.8684;
} else {
return $miles;
}
}https://www.hongfs.cn/2019/06/php/php-get-distance-between-points/
getDistanceBetweenPoints(113.276885, 23.090654, 113.320331, 23.096197);
// PHP: 4.8368890520256 公里
// 高德地图: 4491 米
//restapi.amap.com/v3/distance?key-您的key&origins=113.276885,23.090654&destination=113.320331,23.096197&type=e

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
/**
* 后缀名验证
*
* @param string $extension 后缀名
* @return bool
*/
protected function has_extension(string $extension)
{
return in_array($extension, ['jpg', 'jpeg', 'png', 'gif', 'bmp']);
}

/**
* 图片上传
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
// 获取上传键值
$name = $request->input('name', 'file');

if($request->hasFile($name)) {
// 文件格式上传

$file = $request->file($name);
$extension = $file->extension();

if(!$this->has_extension($extension)) {
return '格式错误';
}

$filename = $file->store();
} else if(preg_match('/^(data:\s*image\/(\w+);base64,)/', $request->input($name), $matches)) {
// base64 上传

$extension = $matches[2];

if(!$this->has_extension($extension)) {
return '格式错误';
}

$file = $request->input($name);
$file = preg_replace('/^(data:\s*image\/(\w+);base64,)/', '', $file);
$file = str_replace(' ', '+', $file);

// 生成保存文件名
$filename = str_random(40) . '.' . $extension;
Storage::put($filename, base64_decode($file));
} else {
return '格式错误';
}

return '上传成功[' . $filename . ']';
}https://www.hongfs.cn/2019/06/php/laravel/laravel-upload-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
$ composer require "overtrue/pinyin:~4.0"
/**
* 地区列表 - 拼音排序
*
* @return \Illuminate\Http\Response
*/
public function list()
{
$list_tmp = DB::table('area')
->select('code', 'name')
->orderBy(DB::raw('convert(name using gbk)'))
->get();

$pinyin = new Pinyin();

$list = [];

$list_tmp->map(function($item) use(&$list, $pinyin) {
// 去除城市名最后面的市字
// if(substr($item->name, -3) === '市') {
// $item->name = substr($item->name, 0, -3);
// }

// 获取城市名第一个字
$name_first = substr($item->name, 0, 3);

// 获取拼音
$name_pinyin = $pinyin->convert($name_first)[0];

// 获取第一个字母并且转换为大写
$name_pinyin = strtoupper(substr($name_pinyin, 0, 1));

if(!isset($list[$name_pinyin])) {
$list[$name_pinyin] = [];
}

$list[$name_pinyin][] = (array) $item;
});
{
"code": 1,
"data": {
"G": [
{
"code": 5108,
"name": "广元市"
},
{
"code": 4401,
"name": "广州市"
}
],
"S": [
{
"code": 4403,
"name": "深圳市"
}
]
}
}
https://www.hongfs.cn/2019/09/php/laravel/laravel-area-list-pinyin-sort/

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
  class MyDB extends SQLite3
{
function __construct()
{
//根据sqlite提供的open接口,输入密钥key,
//$this->open('EnMicroMsg.db',SQLITE3_OPEN_READWRITE,'71ca1d4');
$this->open('test.db');//test.db存在就链接,不存在就创建
}
}
$db = new MyDB();
if(!$db){
echo $db->lastErrorMsg();
} else {
echo "Opened database successfully\n";
}

$sql =<<<EOF
SELECT * from COMPANY;
EOF;

$ret = $db->query($sql);
while($row = $ret->fetchArray(SQLITE3_ASSOC) ){
echo "ID = ". $row['ID'] . "\n";
echo "NAME = ". $row['NAME'] ."\n";
echo "ADDRESS = ". $row['ADDRESS'] ."\n";
echo "SALARY = ".$row['SALARY'] ."\n\n";
}
echo "Operation done successfully\n";
$db->close();
https://learnku.com/laravel/t/35478

PHP7.4 可能会对 Laravel 生态带来的改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
php 7.4 alpha 版本已经发布了,新特性很多,不过我只关注了预加载(preload)特性。
我们都知道,laravel include 的文件比较多,会带来较大的磁盘 io, 预加载则直接解决了这个问题。https://learnku.com/laravel/t/30557
fpm 基本不会成为你的并发瓶颈,数据库往往率先成为并发的瓶颈。
只有在网络通讯应用中,fpm 才有可能成为瓶颈,但是,fpm 也不能作为通讯组件来使用啊

开启 preload 后,对于需要加载很多文件的项目来说,性能直接有 13%~16% 的提升,那些加载文件过多的框架和项目将会得到和加载一个文件的项目接近的性能
这个特性,是直击 laravel 的痛点的。
我们都知道,laravel 这个框架,加载了很多文件导致性能比一般框架要慢,这个预加载功能,可以助推 laravel 的普及

php 的运行模式本来就有 fpm 和 cli 两种,其中 fpm 属于 php 的一种特色,目的是简化 web 开发,从而让开发者专注于创作,而不是处理技术细节。preload 的灵感来自于 java 的 HotSpot(jvm 的黑科技,可以做到平均 gc 时长仅为 1 毫秒),也就是在不增加开发复杂度的情况下,对性能进行 16%~16% 的透明提升,这对于有性能提升需求,但又不想改变 fpm 模式的项目来说意义是很大的。

如果你的项目规模超大(我对超大的定义是 uv / 日大于 2 亿),如果还继续使用 php 的话,那么就可以用 php 的 cli 模式自定义 http 服务,通过 epool 或者 sellect 实现 io 异步轮询,php 文件直接常驻内存,对于性能的提升(对比于 fpm)通常会有 2080 倍的性能提升。跟此相关的解决方案有 workerman,swoole 等。

刚才没事跑到 thinkphp 官网上,看到了他们把 workman 作为了框架的一部分,用 workman 启动一个 httpServer,就可以切换到常驻内存模式,在编码上会有一点改变,比如 fpm 模式下的 session,文件上传,和 cli 的就不是一回事了,但是如果项目本身使用了 thinkphp 框架封装好的 seesion 或者文件上传方法,那么就不会有问题,这意味着,如果前期你为了赶进度使用了 fpm 模式,那么到了后期,你仍然有无缝切换到 cli 模式的选择权。

无限极分类

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
$arrs = [
[
'id'=>1,
'parent_id'=>0
],
[
'id'=>2,
'parent_id'=>1
],
[
'id'=>3,
'parent_id'=>2
],
[
'id'=>4,
'parent_id'=>0
],
[
'id'=>5,
'parent_id'=>0
],
];function children($id,$arrs){
$result =[];
foreach ($arrs as $v){
if($id==$v['parent_id']){
$result[]=$v;
}
}
return $result;
}
>>> children(0,$arrs)
=> [
[
"id" => 1,
"parent_id" => 0,
],
[
"id" => 4,
"parent_id" => 0,
],
[
"id" => 5,
"parent_id" => 0,
],
]

function allChildren($id,$arrs){

$result = [];
$children = children($id,$arrs);//获取儿子数组
foreach($children as $k=>$child){
$result[$k]=$child;
$childResult = allChildren($child['id'],$arrs);//获取儿子的儿子的儿子无穷尽也
foreach ($childResult as $subChild) {
$child['children'][]=$subChild;
$result[$k] = $child;
}
}
return $result;
}

//实现类似于children($id,$arrs)方法
public function children(){
return $this->hasMany(get_class($this),'parent_id');
}
//实现了上面的allChildren($id,$arrs)方法
public function getAllChildren()
{
$result = [];
$children = $this->children;

foreach ($children as $child) {
$result[] = $child;

$childResult = $child->getAllChildren();
foreach ($childResult as $subChild) {
$result[] = $subChild;
}
}

return $result;
}
测试
https://learnku.com/articles/12466/unlimited-classification#reply52473
Model::find(1)->getAllChildren();
public function children() {
return $this->hasMany(get_class($this), 'parent_id' );
}

public function allChildren() {
return $this->children()->with( 'allChildren' );
}
https://github.com/betterde/tree
$categorys = children::with('allChildren')->first();
$arr = [];
array_walk_recursive($categories,function ($v, $k) use(&$arr) {
if($k == 'id')
$arr[] = $v;
});
https://segmentfault.com/a/1190000010359094
Laravel 的无限级分类插件 https://github.com/lazychaser/laravel-nestedset

限制请求频率中间件

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
use Closure;
use Illuminate\Support\Facades\Cache;
use App\Libs\Xres;

class ThrottleRequests {

/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$unique = $request->getClientIp() . $request->getRequestUri();
if (Cache::has('ip_list')) {
$ip_list = Cache::get('ip_list');
} else {
$ip_list = array();
}
$index = array_search($unique, array_column($ip_list, 'ip'));
if (false === $index) {
array_push($ip_list, ['ip' => $unique, 'time' => $this->time()]);
Cache::put('ip_list', $ip_list, 1440);
} else {
$gap = $this->time() - $ip_list[$index]['time'];
$ip_list[$index]['time'] = $this->time();
Cache::put('ip_list', $ip_list, 1);
if ($gap < 1000) {
return Xres::error("请求频率过快", 200);
}
}
return $next($request);
}

private function time(){
return (int)(microtime(true)*1000);
}
}
https://learnku.com/articles/35823

Laravel MongoDB 数据库查询

1
2
3
4
5
composer require jenssegers/mongodb
$capsule->getDatabaseManager()->extend('mongodb', function($config)
{
return new Jenssegers\Mongodb\Connection($config);
});

树状数据结构存储方式

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
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 CHECK (lft> 0),
`rgt` int(11) NOT NULL 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
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

冬令时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function getSeason($timezone){
$localzone = date("e");
date_default_timezone_set($timezone);
$season = date("I");
date_default_timezone_set($localzone);
return $season;
}
function is_dst($timestamp,$timezone)
{
$timezone = date('e'); //获取当前使用的时区
date_default_timezone_set($timezone); //强制设置时区
$dst = date('I',$timestamp); //判断是否夏令时
date_default_timezone_set($timezone); //还原时区
return $dst; //返回结果
}
print_r(is_dst(1570143661,'Europe/London'));
print_r(is_dst(time(),'America/New_York'));
print_r(getSeason('America/Chicago'));
https://3v4l.org/iWVMb

展开表达式

1
2
3
4
5
6
7
8
9
<?php
function add($a, $b, $c) {
return $a + $b + $c;
}

$operators = [2, 3];
echo add(1, ...$operators);
?>
https://www.php.net/manual/zh/migration56.new-features.php

helper函数

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
public function register()
{
foreach (glob(app_path('Helpers') . '/*.php') as $file) {
require_once $file;
}
$this->directoryToArray(app_path('Helpers'));
}
private function directoryToArray($directory, $recursive = false) {
$array_items = array();
if(!is_dir($directory)) return "$directory folder does not exist";
if ($handle = opendir($directory)) {
while (false !== ($file = readdir($handle))) {
if ($file != "." && $file != "..") {
if (is_dir($directory. "/" . $file)) {
if($recursive) {
self::directoryToArray($directory. "/" . $file, $recursive);
}
if($this->validateExt($file)){
$file = $directory . "/" . $file;
require_once $file;
}
} else {
if($this->validateExt($file)){
$file = $directory . "/" . $file;
require_once $file;
}
}
}
}
closedir($handle);
}
}
private function validateExt($file){
$file_path = pathinfo($file);
if($file_path ['extension'] == 'php'){
return true;
}
return false;
}
config/app.php
App\Providers\HelperServiceProvider::class,
App\Providers\AppServiceProvider::class,

function carbon($time = null, $tz = null)
{
return new \Carbon\Carbon($time, $tz);
}

魔术方法__call

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
class Family{
protected $childName = [];
public function fatherName(){
return 'Dale';
}
public function motherName(){
return 'Kelly';
}
public function __call($name,$param){
if($name == 'setChildName'){
$childNmae = [$param];
return $this
}
}

}
$family = new Family();
$setChildName = $family->setChildName('Kobe');
在使用服务容器时,我们的目标是以下面形式调用我的方法。

$myContainer = app('myContainer');
$myContainer->myClass()->myFunction();
当然我可以用构造函数的方法来解决,但是当依赖了很多类的时候,构造函数是毁灭性的。调用一个方法会实例化N个类,十分不可取。所以我改造myClass如下。

class myClass{
public function __call($name, $arguments)
{
$className = 'App\Container\\'.$name;
if (class_exists($className)){
$class = new \ReflectionClass($className);
return $class->newInstanceArgs($arguments);
}
}
}
这样会在调用的时候,只会实例化一个类,而且代码简洁明了https://dalebao.github.io/2019/04/27/Laravel-and-magic/

获取每年所有周

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
function get_week($year)
{
$year_start = $year . "-01-01";
$year_end = $year . "-12-31";
$startday = strtotime($year_start);
if (intval(date('N', $startday)) != '1') {
$startday = strtotime("next monday", strtotime($year_start)); //获取年第一周的日期
}
$year_mondy = date("Y-m-d", $startday); //获取年第一周的日期

$endday = strtotime($year_end);
if (intval(date('W', $endday)) == '7') {
$endday = strtotime("last sunday", strtotime($year_end));
}

//如果是当前年则统计到当前周
$now_year = date('Y');
if ($now_year == $year) {
$num = date('W', strtotime(date('Y-m-d')));
} else {
$num = intval(date('W', $endday));
if ($num == '1'){
$num = intval(date('W',$endday -(7*24*3600)))+1;
}
}

for ($i = 1; $i <= $num; $i++) {
$j = $i -1;
$start_date = date("Y-m-d", strtotime("$year_mondy $j week "));
$end_day = date("Y-m-d", strtotime("$start_date +6 day"));
if (!(date("Y",strtotime("$year_mondy $j week ")) > $year)){
$week_array[$i] = array(
substr(str_replace("-", ".", $start_date), 5),
substr(str_replace("-", ".", $end_day), 5));
}
}
return $week_array;
}https://dalebao.github.io/2019/04/27/%E8%8E%B7%E5%8F%96%E6%AF%8F%E5%B9%B4%E6%89%80%E6%9C%89%E5%91%A8/

php yield

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
 public function parseStr(...$param){
$str = '';
foreach ($param as $key => $value) {
//处理数组
if (is_array($value)) {
$value = md5(json_encode($value));
}
$str .= $value;
}

return $str;
}

public function handle()
{
// 单独输出到updateChapter.log文件
\Log::useFiles(storage_path().'/logs/data.log');
$iTime = time();
\Log::info('start', ['timestamp' => $iTime]);
$i = 0;
$ret = $this->traverse();
foreach ($ret as $v) {
foreach ($v as $value) {
$key = $this->parseStr('wx:', $value->wx_id);
$count = (int) Redis::pfcount($key);
// redis里的uv更大时更新,并记录更新前后数据
if ($count > $value->uv) {
\Log::info('update id:'.$value->id, [$value->uv => $count]);
$i++;
(new StatisticModel(['hash_id' => $value->wx_id]))->where('id', $value->id)->update(['uv' => $count, 'update_time' => $iTime]);
}
}
}
// 记录更新时间与数量
\Log::info('end', ['timestamp'=>time(), 'count' => $i,'interval' => time() - $iTime]);
return true;
}

private function traverse()
{
$page = 3000;
for ($i = 1; $i < 6; $i++) {
$count = DB::table('wx_statistics_'.$i)->count();
if ($count < 1) {
// 记录进度
\Log::info('update schedule', ['table success' => $i]);
continue;
}
$num = ceil($count/$page);
for ($j = 0; $j < $num; $j++) {
$ret = DB::table('wx_statistics_'.$i)
->select(['id', 'wx_id', 'uv'])
->orderBy('id', 'asc')
->skip($j*$page)
->take($page)
->get()
->toArray();

if (empty($ret)) {
continue;
}
yield $ret;
}

\Log::info('update schedule', ['table success' => $i]);
}
return true;
}
http://blog.13sai.com/essay/199

PHP 规范 - Symfony 代码规范

1
2
3
4
5
使用 php-cs-fixer 工具来自动检查编码规范

$ cd your-project/
$ php php-cs-fixer.phar fix -v
https://learnku.com/articles/36079

Laravel 限制条数后再分页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$per_page = 15; // 每页条数
$limit_page = 30; // 限制总页数
$data = Article::orderBy('id', 'desc')->limit($per_page * $limit_page)->paginate($per_page);
此种写法并不能生效,因为 paginate 方法中内置的 limit 会替换掉我们的 limit
取出要展示的条数的最小 id

在总记录中设置 id 大于上一条件得到的最小 id

即,用 where 替换 limit 限制总条数

$last_id = Article::orderBy('id', 'desc')
->limit($per_page * $limit_page)
->pluck('id')
->sort()
->first();
$data = Article::orderBy('id', 'desc')
->where('id', '>', $last_id)
->paginate($per_page);
https://learnku.com/articles/36260

php.ini 配置

1
2
3
4
5
6

文件上传
file_uploads = On
max_file_uploads = 20
upload_max_filesize = 2M
max_execution_time = 30 值 为 0 代表没有限制

PHP Traits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
“ Trait 是单继承语言(如 PHP )中的一种代码复用机制。 Trai 的目的是减少单继承语言的一些限制,能让开发者自由的重用在不同的类层次结构下几个独立类中的方法。 Trait 的语义组合与类的定义在某种程度上减少了代码的复杂度,避免了与多继承和 Mixins 相关的一些典型问题。

Trait 与类非常相似,但它的目的仅仅是用更好、一致的方式汇聚一些方法。 Trait 本身不能被实例化。除了传统继承之外,它能水平组合行为,换言之,类的应用程序不需要继承。”
namespace App\Http\Traits;

use App\Brand;
trait BrandsTrait {
public function brandsAll() {
// 从品牌表中获取所有品牌.
$brands = Brand::all();

return $brands;
}
}
use App\Http\Traits\BrandsTrait;

class YourController extends Controller {
use BrandsTrait;

public function addProduct() {
$brands = $this->brandsAll();
}
}
https://learnku.com/laravel/t/36211

正则表达式

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
/*
给定一组手机号,必须由[0123456789]组成的,才选出来从哪找?从字符串的开始找,找到字符串的结束 ^ $
找谁[01235689]
找几个?11个
*/
$arr = array('13800138000''13487656887''434456''45454353434543');
//$patt = '/^[^47]{11}$/'; //补集方法
$patt = '/^[01235689]{11}$/';//集合方式
foreach($arr as $v){
preg_match_all($patt,$v,$res);
print_r($res);
}
$str = 'tommorw is another day,o2o ,you dont bird me i dont bird you';

>>> preg_split('#\W+#',$str)
=> [
"tommorw",
"is",
"another",
"day",
"o2o",
"you",
"dont",
"bird",
"me",
"i",
"dont",
"bird",
"you",
]
//查询纯数字或者纯字母的词
$str = 'hello o2o 2b9 250';
$patt = '/\b[a-zA-Z]+\b|\b[0-9]+\b/';//最少一个
preg_match_all($patt,$str,$res);
print_r($res);

//查询苹果系统的产品
$str = 'ipad,iphone,imac,ipod,iamsorry';
$patt = '/\bi(pad|phone|mac|pod)\b/';
preg_match_all($patt,$str,$res);
print_r($res);
把手机号中间的 4 位替换为 *

$str = '13800138000 , 13426060134 ';
//前3位和后4位放子表达式中,中间4位随便,保留子表达式.替换中间的4位
$patt = '/(\d{3})\d{4}(\d{4})/';
//preg_match_all($patt,$str, $res);
//print_r($res);
echo preg_replace($patt, '\1****\2', $str);//138****8000 , 134****0134
$str = 'bob李';
$patt = '/^[\x{4e00}-\x{9fa5}]+$/u';
echo preg_match($patt,$str)?'国货':'杂货';https://learnku.com/articles/36227

curl 下载文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function curl_download_file($url,$path)
{
$ch=curl_init();
curl_setopt($ch,CURLOPT_POST,0);
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
$content=curl_exec($ch);
// file_put_contents($path, $content);
// $file=fopen($path,'w');
// fwrite($file,$content);
// fclose($file);
header('Expires: 0'); // no cache
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
header('Cache-Control: private', false);
header('Content-Type: application/force-download');
header('Content-Disposition: attachment; filename="' . basename($url) . '"');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . strlen($content)); // provide file size
header('Connection: close');
echo $content;
}
curl_download_file('https://www1.hkexnews.hk/listedco/listconews/sehk/2019/1108/2019110800352_c.xlsx','test.xlsx');

更新数据后返回原来数据

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
 $article = Finance::where('state', 0)
->orderBy('id', 'asc')->select('id', 'state', 'author')
->first();

$old_article = $article->toArray();

if($article){
$article->state = 1;
$article->author = 'elesos'';
$article->save();
}

return $old_article;
当把一个对象已经创建的实例赋给一个新变量时,新变量会访问同一个实例,就和用该对象赋值一样。此行为和给函数传递入实例时一样。可以用克隆给一个已创建的对象建立一个新实例。
$old_article = $article = Finance::where('state', 0)
->orderBy('id', 'asc')
->first();
old_article 和 article 都是指向同一块内存(或者说同一个对象实例。从 C 语言的角度理解,可以类比为他们都是保存指向同一个对象的 内存地址,即指针),那么对于 article 的修改自然也会反应到 old_article 上
$article = Finance::where('state', 0)
->orderBy('id', 'asc')
->first();
// 关键:复制 article 实例到 old_article。
// 若支持,此时 old_article 指向的实例与 article 不一致
$old_article = clone $article;

$article->state = 1;
$article->save();
// 如果证实可以,此处 old_article state 应为 0
echo $old_article->state;
https://learnku.com/laravel/t/36338

生成器

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
最普通的方式就是一次性读取文件内容,然后再进行遍历。

<?php

function getLinesFromFile($fileName) {

// 打开文件
if (!$fileHandle = fopen($fileName, 'r')) {
return;
}

// 一次读取每一行并保存
$lines = [];
while (false !== $line = fgets($fileHandle)) {
$lines[] = $line;
}

fclose($fileHandle);

return $lines;
}

$lines = getLinesFromFile('test.txt');
foreach ($lines as $line) {

}
当使用该函数读取大文件时,就会因为内存不足而报错。

PHP Fatal error: Allowed memory size of 134217728 bytes exhausted
function getLinesFromFile($fileName) {
if (!$fileHandle = fopen($fileName, 'r')) {
return;
}

while (false !== $line = fgets($fileHandle)) {
yield $line;
}

fclose($fileHandle);
}

$lines = getLinesFromFile('test.txt');
foreach ($lines as $line) {

}

yield;
yield $value;
yield $key => $value;
生成器看上去是函数,实际上是 Generator 类的实例。

function simpleGenerator()
{
yield;
}

echo get_class(simpleGenerator()) // Generator
既然是对象,就可以将其赋值给变量。

$gen = simpleGenerator();
Generator 对象已经实现了 Iterator 接口

$gen instanceof Iterator // true
function echoLogger() {
while (true) {
// 接受外部的传值
$log = yield;
echo 'Log: ' . $log . "\n";
}
}

$logger = echoLogger();
$logger->send('Foo'); // Log: foo
$logger->send('Bar'); // Log: bar
https://learnku.com/articles/36331

多维数组变一维

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 public static function flatten($array, $depth = INF)
{
$result = [];
foreach ($array as $item) {
$item = $item instanceof Collection ? $item->all() : $item;
if (! is_array($item)) {
$result[] = $item;
} elseif ($depth === 1) {
$result = array_merge($result, array_values($item));
} else {
$result = array_merge($result, static::flatten($item, $depth - 1));
}
}
return $result;
}
https://learnku.com/articles/36348

phpexcel遇到内存溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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
class ExportService
{

public static $outPutFile = '';

/**
* 导出文件
* @param string $fileName
* @param $data
* @param array $formFields
* @return mixed
*/
public static function exportData($fileName = '', $data, $formFields = [])
{
$fileArr = [];
$tmpPath = \Yii::$app->params['excelSavePath'];

foreach (array_chunk($data, 10000) as $key => $value) {
self::$outPutFile = '';
$subject = !empty($fileName) ? $fileName : 'data_';
$subject .= date('YmdHis');
if (empty($value) || empty($formFields)) {
continue;
}

self::$outPutFile = $tmpPath . $subject . $key . '.csv';
if (!file_exists(self::$outPutFile)) {
touch(self::$outPutFile);
}
$index = array_keys($formFields);
$header = array_values($formFields);
self::outPut($header);

foreach ($value as $k => $v) {
$tmpData = [];
foreach ($index as $item) {
$tmpData[] = isset($v[$item]) ? $v[$item] : '';
}
self::outPut($tmpData);
}
$fileArr[] = self::$outPutFile;
}

$zipFile = $tmpPath . $fileName . date('YmdHi') . '.zip';
$zipRes = self::zipFile($fileArr, $zipFile);
return $zipRes;
}

/**
* 向文件写入数据
* @param array $data
*/
public static function outPut($data = [])
{
if (is_array($data) && !empty($data)) {
$data = implode(',', $data);
file_put_contents(self::$outPutFile, iconv("UTF-8", "GB2312//IGNORE", $data) . PHP_EOL, FILE_APPEND);
}
}

/**
* 压缩文件
* @param $sourceFile
* @param $distFile
* @return mixed
*/
public static function zipFile($sourceFile, $distFile)
{
$zip = new \ZipArchive();
if ($zip->open($distFile, \ZipArchive::CREATE) !== true) {
return $sourceFile;
}

$zip->open($distFile, \ZipArchive::CREATE);
foreach ($sourceFile as $file) {
$fileContent = file_get_contents($file);
$file = iconv('utf-8', 'GBK', basename($file));
$zip->addFromString($file, $fileContent);
}
$zip->close();
return $distFile;
}

/**
* 下载文件
* @param $filePath
* @param $fileName
*/
public static function download($filePath, $fileName)
{
if (!file_exists($filePath . $fileName)) {
header('HTTP/1.1 404 NOT FOUND');
} else {
//以只读和二进制模式打开文件
$file = fopen($filePath . $fileName, "rb");

//告诉浏览器这是一个文件流格式的文件
Header("Content-type: application/octet-stream");
//请求范围的度量单位
Header("Accept-Ranges: bytes");
//Content-Length是指定包含于请求或响应中数据的字节长度
Header("Accept-Length: " . filesize($filePath . $fileName));
//用来告诉浏览器,文件是可以当做附件被下载,下载后的文件名称为$file_name该变量的值
Header("Content-Disposition: attachment; filename=" . $fileName);

//读取文件内容并直接输出到浏览器
echo fread($file, filesize($filePath . $fileName));
fclose($file);
exit();
}
}
} $fileName = "库存导入模板";
$stockRes = []; // 导出的数据
$formFields = [
'store_id' => '门店ID',
'storeName' => '门店名称',
'sku' => 'SKU编码',
'name' => 'SKU名称',
'stock' => '库存',
'reason' => '原因'
];
$fileRes = ExportService::exportData($fileName, $stockRes, $formFields);
$tmpPath = \Yii::$app->params['excelSavePath']; // 文件路径
$fileName = str_replace($tmpPath, '', $fileRes);

// 下载文件
ExportService::download($tmpPath, $fileName);
https://tsmliyun.github.io/php/%E5%85%B3%E4%BA%8EPHP%E5%86%85%E5%AD%98%E6%BA%A2%E5%87%BA%E7%9A%84%E6%80%9D%E8%80%83/

基于雪花算法的 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
第一个 bit 为未使用的符号位。
第二部分由 41 位的时间戳(毫秒)构成,他的取值是当前时间相对于某一时间的偏移量。
第三部分和第四部分的 5 个 bit 位表示数据中心和机器 ID,其能表示的最大值为 2^5 -1 = 31
最后部分由 12 个 bit 组成,其表示每个工作节点每毫秒生成的序列号 ID,同一毫秒内最多可生成 2^12 -14095 个 ID。
composer require godruoyi/php-snowflake
$snowflake = new \Godruoyi\Snowflake\Snowflake;

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

$snowflake->id();
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()
));
});
}
}
64 位的二进制最大能生成 19 位的 ID,所以在设计数据库长度的时候最好预留下长度
public function getCurrentMicrotime()
{
return floor(microtime(true) * 1000) | 0;//浮点数转数字
}
bindec('0111111111111111111111111111111111111111111111111111111111111111')
=> 9223372036854775807
>>> strlen(9223372036854775807)
=> 19
https://learnku.com/articles/32575

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

当请求进入到 Nginx 中,Nginx 提供了一个 FastCgi 模块 来把 Http 请求映射为对应的 Fastcgi 请求。该模块提供了 fastcgi_param 指定来完成映射关系。它的主要作用就是把 Nginx 中的变量翻译成 PHP 中能够理解的变量

fastcgi_pass, 用来指定 FPM 进程监听的地址,Nginx 会把所有的 PHP 请求映射成 fastcgi 请求,然后发送到这个地址上。

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

客户端发送请求到 Nginx
加载 nginx.conf 文件,把所有 .php 结尾的请求特殊处理
加载 FastCGI 模块,完成请求参数的解析映射,生成 FastCGI 请求
然后通过 fastcgi_pass 参数把 FastCGI 请求发送给 PHP-FPM 处理
PHP-FPM 收到请求,分配给空闲 worker 子进程
worker 子进程加载 PHP 解析器等 完成 PHP 执行获取结果
PHP-FPM 是一种 master/worker 进程架构。首先会启动一个 master 主进程,主要功能用来完成 PHP 环境的初始化,事件监听,子进程状态管理等等。然后会启动若干 worker 子进程来处理 PHP 请求。

master 进程的工作流程

1. 初始化 CGI,注册进程信号初始化全局变量。
2. 完成 PHP 环境初始化。加载 php.ini 解析配置文件,加载 PHP 模块记录函数符号表,加载 zend 扩展,设置禁用函数和类库设置,注册回收内存方法。
3. 完成 PHP-FPM 初始化。加载并解析 php-fpm.conf 文件,获取进程相关参数,初始化进程池以及事件模型等。
4. 处理子进程相关操作。fork 子进程,进行事件监听等。

worker 进程工作流程

1. 接收请求。这里是不需要初始化 PHP 运行环境的。
2. 处理请求。获取请求内容注册全局变量 ($_GET,$_POST,$_SERVER 等),然后根据请求信息访问对应的 PHP 文件,然后将 PHP 脚本文件交给 Zend 引擎处理。
3. 处理结果。在 Zend 引擎处理完毕后将会进行回调,发送响应内容,释放内存等

测试 php-fpm 配置内容是否正确 使用 -t 参数, 还可以通过加 -c 指定 php.ini 文件,通过 -y 指定 php-fpm.conf 文件

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

使用 root 权限启动子进程 通过增加 -R 参数

/usr/sbin/php-fpm7 -c xxx/xxx/xxx/php.ini -y /xxx/xxx/xxx/php-fpm.conf -R

重新创建容器 并添加 --cap-add=SYS_PTRACE 给容器追加 Ptrace 功能

docker run --name website_name -p 11280:80 --cap-add=SYS_PTRACE -v /data/website/website_name:/app -d showtime/php-javabridge:v1 https://learnku.com/articles/28683

Elasticsearch/Algolia 全文搜索

1
2
3
4
5
https://www.elastic.co/cn/downloads/elasticsearch
新版的 ES 是内置的 java 环境
windows 直接启动 elasticsearch.bat

https://learnku.com/articles/30812#reply117421

PHP 系统调用time()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$year = date('Y', time());
$month = date('m', time());
系统调用就是操作系统提供给用户程序访问计算机资源的接口,更要命的是,系统调用极其耗时
class TimeWrapper
{
private static $now_time = 0;

/**
* @param bool $force_refresh 是否强制刷新
* @return int
*/
public static function getTime($force_refresh = false)
{
if ($force_refresh) {
self::$now_time = time();
} else {
if (!self::$now_time) {
self::$now_time = time();
}
}
return self::$now_time;
}
}
https://learnku.com/articles/36538

curl 下载进度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
class Downloader
{

static $lastDownloaded = 0;
static $lastTime = null;

public static function download($url, $fileName, $date)
{
ini_set('memory_limit', Config::$memory_limit); //调整最大占用内存
$code = ['"', '*', ':', '<', '>', '?', '/', '\\', '|'];
$fileName = preg_replace('# #','',$fileName);
$fileName = str_replace($code, '', $fileName);
if (!is_dir(Config::$path)) {
mkdir(Config::$path);
}

$filePath = Config::$path.'/'.date('Ymd',strtotime($date)).'_'.$fileName.'.mp4';
if (file_exists($filePath)){
echo "\033[0;32m"."文件已存在"."\033[0m\n";
return;
}

$header = array();
$header[] = "User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36Name'";
$header[] = "Referer:http://91porn.com";

$ch = curl_init();
// 从配置文件中获取根路径
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT,300);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
if (property_exists('Config', 'proxy')) {
curl_setopt($ch, CURLOPT_PROXY, Config::$proxy);
}
// 开启进度条
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
// 进度条的触发函数
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, array(new self, 'progress'));
// ps: 如果目标网页跳转,也跟着跳转
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

$data = curl_exec($ch);
curl_close($ch);

if ($data) {
$file = fopen($filePath,"w+");
fputs($file,$data);//写入文件
fclose($file);
}

// 使用rclone上传onedrive,其中“91porn:/91porn”对应网盘名称和路径
// $command = 'rclone move -P '.$filePath.' 91porn:/91porn';
// system($command);

unset($data);
}

/**
* 进度条下载.https://github.com/zzjzz9266a/91porn_php
*
* @param $ch
* @param $downloadSize 总下载量
* @param $downloaded 当前下载量
* @param $uploadSize
* @param $uploaded
*/
function progress($resource, $downloadSize = 0, $downloaded = 0, $uploadSize = 0, $uploaded = 0){
if ($downloadSize === 0) {
return;
}

if ($downloaded == $downloadSize) {
printf("下载完成: %.1f%%, %.2f MB/%.2f MB\n", $downloaded/$downloadSize*100, $downloaded/1000000, $downloadSize/1000000);
Downloader::$lastDownloaded = 0;
Downloader::$lastTime = 0;
return;
}

if (microtime(true)-Downloader::$lastTime <= 1) {
return;
}

$speed = ($downloaded-Downloader::$lastDownloaded)/(microtime(true)-Downloader::$lastTime)/1000;

Downloader::$lastDownloaded = $downloaded;
Downloader::$lastTime = microtime(true);

$downloaded = $downloaded/1000000;
$downloadSize = $downloadSize/1000000;

if ($speed < 1000) {
$speedStr = ", 下载速度:%.2f kb/s ";
}else{
$speedStr = ", 下载速度:%.2f mb/s ";
$speed = $speed/1000;
}
$progress = $downloaded/$downloadSize*100;
printf("下载进度: %.1f%%, %.2f MB/%.2f MB".$speedStr."\r", $progress, $downloaded, $downloadSize, $speed);
}
}

function random_ip()
{
$a = rand(0, 255);
$b = rand(0, 255);
$c = rand(0, 255);
$d = rand(0, 255);
return $a.'.'.$b.'.'.$c.'.'.$d;
}

队列执行频率限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
每分钟限制执行 10 次 JOB(注意是 JOB,而不是整个队列)

use Illuminate\Support\Facades\Redis;
Redis::throttle('key')->allow(10)->every(60)->then(function () {
// 任务逻辑...
}, function () {
// 无法获得锁...

return $this->release(10);
});
并发,限制同一时间只执行一个 JOB

Redis::funnel('key')->limit(1)->then(function () {
// 任务逻辑...
}, function () {
// 无法获得锁...

return $this->release(10);
});方法里的参数 key 是自定义的 redis key,如果需要多个 job 共用一个限制,则可以使用同一个 key

https://learnku.com/articles/30054

上传文件和下载

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
 /*
文件上传函数
@param string $name 文件上传文件域的name值
@param string $dir 文件保存路径
@param array $allow 文件允许上传的类型
return string $filename 文件名 如果失败 返回false
*/

function upload($name,$dir='./upload/',$allow=array('jpg','gif','jpeg','png')){
//echo $name;exit;
//var_dump($_FILES);exit;
//1.判断文件上传错误
if($_FILES[$name]['error']>0){
//echo '上传错误';
switch($_FILES[$name]['error']){
case 1:
echo '上传的文件超过了 php.ini 中upload_max_filesize 选项限制的值.';
break;
case 2:
echo '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值';
break;
case 3:
echo '文件只有部分被上传.';
break;
case 4:
echo '没有文件被上传.';
break;
case 6:
echo '找不到临时文件夹.';
break;
case 7:
echo '文件写入失败.';
break;
}
return false;
}

//2.判断你文件上传的类型是否是你想要的类型
//2.1允许上传的类型

//2.2 获取后缀名
$suffix = pathinfo($_FILES[$name]['name'],PATHINFO_EXTENSION);
//echo $suffix;exit;
//2.3 判断是否是我们允许上传的类型
//var_dump(in_array($suffix,$allow));exit;
if(!in_array($suffix,$allow)){
//不允许上传的类型
echo '大哥你的上传类型不符合';
return false;
}
//3.起名字
$filename = date('Ymd').uniqid().mt_rand(0,9999).'.'.$suffix;
//echo $filename;exit;

//4.判断保存路径是否存在
//4.1 得到保存路径

//4.2 处理保存路径和后面的斜杠
$save_path = rtrim($dir,'/');
$save_path .='/';

//4.3 保存路径中的时间文件夹处理
$save_path .=date('Y/m/d/');

//4.4 判断保存的路径是否存在
if(!file_exists($save_path)){
mkdir($save_path,777,true);
}

//4.5 拼接一个完整的保存路径
$path = $save_path.$filename;
//echo $path;exit;

//5.判断是否是httppost方式上传
if(!is_uploaded_file($_FILES[$name]['tmp_name'])){
echo '滚蛋!';
return false;
}

//6.移动图片
if(!move_uploaded_file($_FILES[$name]['tmp_name'],$path)){
echo '移动失败';
return false;
}

//7.返回移动成功的图片名
return $filename;

}
下载文件
<!-- 浏览器认识这样的类型,就会被解析 -->
<a href="./action.php?name=1.html">1.html</a>
<a href="./action.php?name=1.php">1.php</a>
<a href="./action.php?name=1.txt">1.txt</a>
<a href="./action.php?name=1.jpg">1.jpg</a>
//接收一下name值.
$name = $_GET['name'];

//实现下载功能
//强制浏览器弹出另存为对话框
header('content-Disposition:attachment;filename="'.$name.'"');

//此时只是下载了一个空文件,需要利用readfile读一遍所有的内容.便可下载.
$path = './downlist/'.$name;
readfile($path);
https://learnku.com/articles/36744

app 接口对接

1
2
3
4
5
6
7
8
9
10
11
if (empty($arr)) {
$arr = (object) null;//$arr = new stdClass;
}
echo json_encode($arr);
$arr = ["id"=>"1","name"=>null];
$data = [
"id"=>(int) $arr["id"],
"name"=>(string) $arr["name"],
];
echo json_encode($data);
json_encode ($result ,JSON_FORCE_OBJECT),返回格式为 {},

异常类

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
function doSomething(){
if(something error){
throw new SomethingException('something error');
}
...
do something
}
//调用
try{
doSomething();
}catch(SomethingException $e){
echo "something goes error";
}
// 调用方无法从外部得知时哪里出了问题,因为只返回了 false
function checkoutOrder($orderNumber){
$orderModel = OrderModel::query()->where('order_number',$orderNumber)->first();
if(!$orderModel){
return false;
}
$payResut = PayService::pay($orderModel);
if(!$payResut){
return false;
}
}
function checkoutOrder($orderNumber){
$orderModel = OrderModel::query()->where('order_number',$orderNumber)->first();
if(!$orderModel){
throw new OrderNotFoundException($orderNumber);
}
$payResut = PayService::pay($orderModel);
if(!$payResut){
throw new PaymentException(); // 这个异常应该上面的 PayService 中抛出,为了更清晰就写在这
}
}

// 调用
try{
checkoutOrder('ORDER00001');
}catch(OrderNotFoundException $e){
return response('订单不存在:'.$e->getMessage(),404);
}catch(PaymentException $e){
return response('支付失败:'.$e->getMessage(),500);
}
app/Exceptions/Handler.php 中根据异常名、紧急程度调用第三方通知工具(钉钉、邮件等)通知项目错误。

public function report(Exception $exception){
if ($this->shouldntReport($exception)) {
return;
}
// 如果异常类中存在 report 方法,就使用自身的
if (method_exists($exception, 'report')) {
return $exception->report();
}

$msg = "系统异常:" . $exception->getMessage();
$msg .= "\n文件:" . $exception->getFile();
$msg .= "\n行号:" . $exception->getLine();
$msg .= "\n参数:" . json_encode(['form_params' => request()->all()]);

DingService::sendWarning($msg);
parent::report($exception);
}
在 render 方法根据异常名返回不同的客户端响应:

public function render($request, Exception $exception)
{
if ($exception instanceof OrderException) {
return $this->handleOrderException($exception, $request);
}
if ($exception instanceof PaymentException) {
return $this->handlePaymentException($exception, $request);
}
return parent::render($request, $exception);
}
https://learnku.com/articles/36657

where or 查询

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
 SELECT * FROM user
WHERE
group_id = 'group id'
AND (
name = 'name'
OR mobile_number = 'mobile number'
OR email = 'email'
OR `score` > 1000
)
DB::table('users')
->where('group_id', 'group id')
->where(function ($query) {
$query->where('name', 'name')
->orWhere('mobile_number', '=', 'mobile number')
->orWhere('email', '=', 'email')
->orWhere('score', '>', '1000');
})
->get();
DB::table('users')
->where('group_id', 'group id')
->where(function ($query) {
if ($params['name']) {
$query->orWhere('name', $params['name'])
}

if ($params['mobile_number']) {
$query->orWhere('mobile_number', $params['mobile_number'])
}

if ($params['email']) {
$query->orWhere('email', $params['email'])
}

if ($params['score']) {
$query->orWhere('score', '>', $params['score'])
}

})
->get();
$orWhere = [];
if ($params['name']) {
$orWhere[] = ['name', '=', $params['name'], 'OR'];
}
if ($params['mobile_number']) {
$orWhere[] = ['mobile_number', '=', $params['mobile_number'], 'OR'];
}
if ($params['email']) {
$orWhere[] = ['email', '=', $params['email'], 'OR'];
}
if ($params['score']) {
$orWhere[] = ['score', '>', $params['score'], 'OR'];
}

DB::table('users')
->where('group_id', 'group id')
->where($orWhere)
->get();
$orWhere = [];
if ($params['name']) {
$orWhere['name'] = $params['name'];
}
if ($params['mobile_number']) {
$orWhere['mobile_number'] = $params['mobile_number'];
}
if ($params['email']) {
$orWhere['email'] = $params['email'];
}
if ($params['score']) {
$orWhere[] = ['score', '>', 1000, 'OR'];
}
DB::table('users')
->where('group_id', 'group id')
->where(function ($query) use ($orWhere) {
$query->orWhere($orWhere);
})
->get();
https://learnku.com/articles/36743

查询封装

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
select * from posts where title='xxx' and content='xxxx';

use Illuminate\Http\Request;
use Illuminate\Database\Query\Builder;

abstract class QueryFilter
{

protected $request;
protected $builder;

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

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

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

}

return $this->builder;
}

protected function init()
{
//子类可以做一些初始化的查询
}
public function filters()
{
return $this->request->all();
}
}
namespace App\Services\Search\Db\Filter;

class PostFilter extends QueryFilter
{

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

public function content($content)
{
return $this->builder->where('content','like', "%{$content}%");
}
}
use DB;

DB::table('posts')->where(function($query){
app(\App\Services\Search\Db\Filter\PostFilter::class)->apply($query);

})->get();
https://learnku.com/articles/36716

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

use Redis;
use Carbon\Carbon;

trait ViewCountsHelper {
// 缓存相关
protected $hash_prefix = 'topic_view_counts_';
protected $field_prefix = 'topic_';

public function viewCountIncrement()
{
// 获取今日 Redis 哈希表名称,如:topic_view_counts_2017-10-21
$hash = $this->getHashFromDateString(Carbon::now()->toDateString());

// 字段名称,如:topic_1
$field = $this->getHashField();

// 当前阅读数,如果存在就自增,否则就为 1
$count = Redis::hGet($hash, $field);
if ($count) {
$count++;
} else {
$count = 1;
}

// 数据写入 Redis ,字段已存在会被更新
Redis::hSet($hash, $field, $count);
}

public function syncTopicViewCounts()
{
// 获取昨日的哈希表名称,如:topic_view_counts_2017-10-21
$hash = $this->getHashFromDateString(Carbon::now()->toDateString());

// 从 Redis 中获取所有哈希表里的数据
$counts = Redis::hGetAll($hash);

// 如果没有任何数据直接 return
if (count($counts) === 0) {
return;
}

// 遍历,并同步到数据库中
foreach ($counts as $topic_id => $view_count) {
// 会将 `topic_1` 转换为 1
$topic_id = str_replace($this->field_prefix, '', $topic_id);

// 只有当话题存在时才更新到数据库中
if ($topic = $this->find($topic_id)) {
$topic->view_count = $this->attribute['view_count'] + $view_count;
$topic->save();
}
}

// 以数据库为中心的存储,既已同步,即可删除
Redis::del($hash);
}

public function getViewCountAttribute($value)
{
// 获取今日对应的哈希表名称
$hash = $this->getHashFromDateString(Carbon::now()->toDateString());

// 字段名称,如:topic_1
$field = $this->getHashField();

// 三元运算符,优先选择 Redis 的数据,否则使用数据库中
$count = Redis::hGet($hash, $field) ? : $value;

// 如果存在的话,返回 数据库中的阅读数 加上 Redis 中的阅读数
if ($count) {
return $this->attribute['view_count'] + $count;
} else {
// 否则返回 0
return 0;
}
}

public function getHashFromDateString($date)
{
// Redis 哈希表的命名,如:topic_view_counts_2017-10-21
return $this->hash_prefix . $date;
}

public function getHashField()
{
// 字段名称,如:topic_1
return $this->field_prefix . $this->id;
}
}
在需要此功能的模型中 use Traits\ViewCountsHelper 即可


namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\Topic;

class SyncTopicViewCounts extends Command
{
protected $signature = 'topic:sync-topic-view-counts';
protected $description = '将话题 view_count 从 Redis 同步到数据库中';

/**
* Execute the console command.
*
* @return mixed
*/
public function handle(Topic $topic)
{
$topic->syncTopicViewCounts();
$this->info("同步成功!");
}
}

namespace App\Http\Controllers;

use App\Models\Topic;
use Illuminate\Http\Request;

class TopicsController extends Controller
{
public function show(Topic $topic)
{
$topic->viewCountIncrement(); // 自增浏览数

dd($topic->view_count); // 获取浏览数
}
}
https://learnku.com/articles/32615

where in

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$where['status'] = 1;
$ids = [1,2];
$where[] = [function($query) use ($ids){
$query->whereIn('id', $ids);
}];
$list = User::where($where)
->get();
生成 sql 如下

select * from `users` where (`status` = 1 and (`id` in (1, 2)))

$status = 1;
$ids = [1,2];
User::when($status, function ($query, $status) {
return $query->where('status', $status);
})
->when($ids, function ($query, $ids) {
return $query->whereIn('id', $ids);
})
->get();
https://learnku.com/articles/36964

付费文章试读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
对 HTML 进行截断:

closetags(str_limit($article->body, 1000))
闭合标签:

function closetags($html) {
libxml_use_internal_errors(true);

$dom = new \DOMDocument;
$dom->loadHTML('<meta http-equiv="content-type" content="text/html; charset=utf-8">' . $html);

// Strip wrapping <html> and <body> tags
$mock = new \DOMDocument;
$body = $dom->getElementsByTagName('body')->item(0);
foreach ($body->childNodes as $child) {
$mock->appendChild($mock->importNode($child, true));
}

return trim($mock->saveHTML());
}https://learnku.com/laravel/t/36859

全局记录管理员的所有操作

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
namespace App\Providers;
use App\Models\AdminLog;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
class LogServiceProvider extends ServiceProvider
{
/**
* Register services.
*https://gist.github.com/Hanson/b8916bf13c38336f8eb33e537fab3723
* @return void
*/
public function boot()
{
Event::listen('eloquent.*', function ($eventName, $data) {
if (preg_match('/eloquent\.(.+):\s(.+)/', $eventName, $match) === 0) {
return;
}
/** $match @val array
array (
0 => 'eloquent.booting: App\\Models\\Groupon',
1 => 'booting',
2 => 'App\\Models\\Groupon',
)
*/
// only record when 'created', 'updated', 'deleted'
if (!in_array($match[1], ['created', 'updated', 'deleted'])) {
return;
}
// only record the admin operation.
if (!Auth::guard('admin')->check()) {
return;
}
$model = $data[0];
$class = get_class($model);
$diff = array_diff_assoc($model->getOriginal(), $model->getAttributes());
$keys = array_keys($diff);
$data = [];
foreach ($keys as $key) {
if ($key === 'updated_at') {
continue;
}
$data[$key] = [
'old' => $model->getOriginal($key),
'new' => $model->getAttributes()[$key]
];
}
$admin = Auth::guard('admin')->user();
// You can create the table with your situation
AdminLog::query()->create([
'admin_id' => $admin->id,
'url' => request()->fullUrl(),
'action' => $match[1], // updated created deleted
'ip' => request()->getClientIp(),
'model_id' => $model->id,
'model_type' => $class,
'data' => $data,
'created_at' => now(),
]);
});
}

Column not found: 1054 Unknown column

1
2
3
4
5
6
7
8
9
10
11
$res = Model::where('date',date('Ymd'))
->whereraw('(a=1 or b=2)')

$date=date('Ymd');
$res = Model::where('date',$date)
->whereraw('(a=1 or b=2)')
提示Column not found: 1054 Unknown column
$res=Model::where('date',$date)
->where(function ($query) use($date){
$query->where('a', '>', $date)->orwhere('b', '2');
})

groupBy 分组查询的分页

1
2
3
4
5
6
7
8
9
10
11
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use App\Models\UserHasCard;
...
$page = 1;//当前页
$pageSize = 10;
$model = UserHasCard::groupBy('user_id');
$count = DB::table($model)->count();
$data = $model->limit($pageSize)->offset(($page-1)*$pageSize)->get();
$list = new LengthAwarePaginator($data,$count,$pageSize,$page);
https://learnku.com/articles/37239

php artisan tinker编码

1
2
3
4
5
6
7
8
9

>>> \App\Ip::where('zt', '未付')-> get();
Illuminate\Database\QueryException with message 'SQLSTATE[HY000]: General error: 1267 Illegal mix of collations (gbk_chinese_ci,IMPLICIT) and (utf8_unicode_ci,COERCIBLE) for operation '=' (S
QL: select * from `ip` where `zt` = 未付)'

\App\Ip::query('select convert(zt USING gbk) from ip where zt = "未付"')->limit(2)->get()
>>> \App\Ip::query('select convert(zt USING gbk) where zt = 未付')->limit(2)->get()
select convert(zt USING gbk) from `ip` where zt = "未付"
https://learnku.com/laravel/t/37299

api文档l5-swagger

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 darkaonline/l5-swagger
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
http://122.51.155.172:8000/api/documentation

vi app/Http/Controllers/MyController.php
<?php

/**
* @OA\Info(title="Test", version="0.0.1")
*/
// 别直接复制这里的注释,缩进破坏了,
// 请上 github 上复制它的用例。这里只作为演示作用

class MyController
{

/**
* @OA\Get(
* path="/api/syahi",
* @OA\Response(response="200", description="An example resource")
* )
*/
public function say()
{
return ['msg' => 'Hello World'];
}
// Other Code...
}
php artisan l5-swagger:generate

它会生成 API 文档,默认在项目根目录: storage\api-docs\api-docs.json
https://learnku.com/articles/37313#reply119615

Laravel 里面的 chunk 分块效率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
 public function chunkTest()
{
// 每次处理
$speed = 1000;
// 进度条
$bar = $this->output->createProgressBar(TestModel::query()->count());
// 记录开始时间
$timeStart = $this->microtime_float();
// chunk 分块处理数据
TestModel::query()->chunk($speed, function ($item) use ($bar, $speed) {
// 业务处理逻辑...
// ....
// 进度条步进{$speed}步\ $bar->advance($speed);
});
$bar->finish();

// 处理完成,记录结束时间
$timeEnd = $this->microtime_float();
$time = $timeEnd - $timeStart;
// 输出信息
$this->info('chunk用时:'. $time);
}
public function idTest()
{
// 进度条
$bar = $this->output->createProgressBar(TestModel::query()->count());
$timeStart = $this->microtime_float();
// 记录最大的id
$maxId = 0;
// 每次处理多少条数据
$speed = 1000;

while (true) {
$models = TestModel::query()
// 每次循环加上id条件
->where('id', '>', $maxId)
->limit($speed)
->orderBy('id')
->get();

// 处理具体业务逻辑...

// 如果没有数据就代表处理完成,break;
if ($models->isEmpty()) {
break;
}

// 记录下本次的最大id,下次循环时当作条件
$maxId = $models->max(['id']);

$bar->advance($speed);
}

$timeEnd = $this->microtime_float();
$time = $timeEnd - $timeStart;
$bar->finish();
$this->info('id条件用时: '. $time);
}
https://learnku.com/articles/37541

Facade demo

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
php artisan make:provider MyServiceProvider
namespace App\Providers;
use B;
use Illuminate\Support\ServiceProvider;

class TestProvider extends ServiceProvider
{
/**
* Register services.
*
* [[@return](https://learnku.com/users/31554)](https://learnku.com/users/31554) void
*/
public function register()
{
$this->app->singleton('my-facade',function(){
return new B();
});
}

/**
* Bootstrap services.
*
* [[@return](https://learnku.com/users/31554)](https://learnku.com/users/31554) void
*/
public function boot()
{
//
}
}
B.php
class B{
public function b(){
echo 'this class B function a ,you are right~!';
}
}
在 app.php 中 分别加入
'providers'=>[
......
App\Providers\MyServiceProvider::class,
]
'aliases' => [
......
'MyFacade'=>\App\Providers\Facades\MyProvider::class,
]
在 app/Providers 文件夹下创建 Facades 文件夹 并创建 MyFacade.php 文件
namespace App\Providers\Facades;
use Illuminate\Support\Facades\Facade;

class MyFacade extends Facade
{
public static function getFacadeAccessor()
{
return 'my-facade';
}
}
\MyFacade::b();
https://learnku.com/articles/36610

验证码(Captcha)包乱码

1
2
3
4
5
6
7
8
9
10
https://github.com/Gregwar/Captcha
use Gregwar\Captcha\CaptchaBuilder;
$code = new CaptchaBuilder;
$code->build(102, 35);
header('Content-type: image/jpeg');
$code->output();

我是在laravel中使用,会出现乱码。但是调用CaptchaBuilder::save()正常生成图片。
群里的朋友说,可以在最有加一个exit();我试了一下,能行。 应该是在laravel的控制器中执行完毕会有内容输出,影响到了验证码的正常输出。但是加exit()总不是办法,会影响到session
然后换成ob_end_clean()放在最前面,也能行。 我有一篇博客说过,ob_end_clean()和ob_clean()然后换成ob_clean()放在最前面试了一下,不行。 所以猜测,laravel在控制器结束后,会往缓冲区写点东西。而ob_clean()只清空缓冲区,并没有关闭。 所以无法正常输出!http://www.shiguopeng.cn/archives/24

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
用户表 角色表 用户角色表 权限表 权限角色表
class User extends Authenticatable
{
use Notifiable;

/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];

// 用户和角色的模型关联关系
public function roles()
{
return $this->belongsToMany(Role::class);
}

/****************************************
* 封装一个方法方便使用
* 1. 需要的权限
* 2. 遍历当期那用户拥有的所有角色
* 3. 再通过角色判断是否有当前需要的权限
****************************************/
public function hasPermission($permissionName)
{
foreach ($this->roles as $role) {
if ($role->permisssions()->where('name', $permissionName)->exists()) {
return true;;
}
}

return false;
}
}
class Role extends Model
{
// 用户和角色的模型关联关系
public function users()
{
return $this->belongsToMany(User::class);
}

// 角色和权限的模型关联关系
public function permissions()
{
return $this->belongsToMany(Permission::class);
}
}
class Permission extends Model
{
// 角色和权限的模型关联关系
public function roles()
{
return $this->belongsToMany(Role::class);
}
}
http://www.shiguopeng.cn/archives/260
https://github.com/spatie/laravel-permission

路由多版本拆分

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
  /**
* 路由加载 多版本
*/
protected function mapAppRoutes(){
// 获取路由文件夹下的所有目录和文件
foreach (scandir(base_path('routes')) as $dir){

//匹配所有以v开头的目录作为版本号
if(starts_with($dir,'v')){

//匹配目录下的php文件
foreach (glob(base_path('routes/'.$dir) . '/*.php') as $file) {

//前缀 目录版本号/文件名
Route::prefix($dir.'/'.basename($file,'.php'))
->middleware('api')
//设置命名空间
->namespace($this->namespace.'\\'.ucfirst(basename($file,'.php')))
->group($file);
}
}
}
}
路由目录文件如下https://learnku.com/articles/37684

├── routes
│ └── v1
│ ├── a.php
│ ├── b.php
│ └── v2
│ ├── c.php

lua 脚本 运行 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
class IncrementExistingKeysBy extends ScriptCommand
{
public function getKeysCount()
{ // Tell Predis to use all the arguments but the last one as arguments
// for KEYS. The last one will be used to populate ARGV.\ return -1;
}

public function getScript()
{
return <<<LUA
local cmd, insert = redis.call, table.insert
local increment, results = ARGV[1], { }

for idx, key in ipairs(KEYS)
if cmd('exists', key) == 1 then
insert(results, idx, cmd('incrby', key, increment))
else
insert(results, idx, false) end
end

return results
LUA;
}
}
调用方式

$client = new Predis\Client($single_server, array(
'profile' => function ($options) {
$profile = $options->getDefault('profile');
$profile->defineCommand('increxby', 'IncrementExistingKeysBy');

return $profile;
},
));

$client->mset('foo', 10, 'foobar', 100);

var_export($client->increxby('foo', 'foofoo', 'foobar', 50));
https://learnku.com/articles/37661

Laravel 使用 Markdown 做文章编辑后台

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
composer require chenhua/laravel5-markdown-editor
php artisan vendor:publish --tag=markdown
修改 config/markdowneditor.php 配置文件
<?php
return [
"default" => 'local', //默认返回存储位置url.也可以设置七牛上传路径,阿里云存储文件等等
"dirver" => ['local'], //存储平台 ['local', 'qiniu', 'aliyun']
"connections" => [
"local" => [
'prefix' => 'uploads/markdown', //本地存储位置,默认uploads
],
"qiniu" => [
'access_key' => '',
'secret_key' => '',
'bucket' => '',
'prefix' => '', //文件前缀 file/of/path
'domain' => '' //七牛自定义域名
],
"aliyun" => [
'ak_id' => '',
'ak_secret' => '',
'end_point' => '',
'bucket' => '',
'prefix' => '',
],
],
];
//主页显示
public function index(Request $request)
{
return view('index');
}
//提交
public function add(Request $request)
{
$content = $request->input('test-editormd-html-code');
$article=Article::create($content);
}
https://learnku.com/articles/30858

Laravel 的测试与 PHP Unit

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
// /tests/Feature/ExampleTest.php
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testBasicTest()
{
// /vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php
$response = $this->get('/');

// /vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php
$response->assertStatus(200);
}
}
vendor/bin/phpunit tests/ExampleTest.php
https://learnku.com/articles/37699

ORM 禁止 update_at 的自动更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Call to a member function toDateTimeString() on string
$user = \App\Models\User::find(1);
$user->sex = 1;
$user->timestamps = false;
$user->save();
$updatedUser =$user->fresh();
return [
'id' => $updatedUser->id,
'name' => $updatedUser->name,
'sex' => $updatedUser->sex,
'created_at' => $updatedUser->created_at->toDateTimeString(),
'updated_at' => $updatedUser->updated_at->toDateTimeString(),
];
https://learnku.com/articles/37717

无限级分类

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
Schema::create('categories', function (Blueprint $table) {
$table->increments('id');
$table->integer('parent_id');
$table->string('code');
$table->string('name');
$table->string('path');
$table->timestamps();
});
public function childCategory() {
return $this->hasMany('App\Models\Category', 'parent_id', 'id');
}

public function allChildrenCategorys()
{
return $this->childCategory()->with('allChildrenCategorys');
}
//无限极分类测试
public function index(){
$category = Category::with('allChildrenCategorys')->find(5);
$re = $category->allChildrenCategorys;

dd($re);
}
//find一个parent_id = 0就可以
https://learnku.com/articles/14068/simple-practice-of-laravel-infinite-class-classification

Laravel 在 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
29
30
31
32
33
$topics = Topic::limit(2)->with(['user'=>function($query){
$query->select('id','username');
}])->get();
class BaseModel extends \Eloquent{
public function scopeWithOnly($query, $relation, Array $columns)
{
return $query->with([$relation => function ($query) use ($columns){
$query->select(array_merge(['id'], $columns));
}]);
}
}
在我们普通的 Model 类都继承基类:

class Topic extends BaseModel{
public function user()
{
return $this->belongsTo('User');
}
}
然后使用就很方便了:https://learnku.com/laravel/t/1220/laravel-queries-only-individual-fields-in-with-q

$topics = Topic::limit(2)->withOnly('user', ['username'])->get();

public function scopeWithOnly(object $query, string $relation, array $columns)
{
return $query->with([$relation => function ($query) use ($columns) {

if (count($columns) == 1) {
$columns = array_merge(['id'], $columns);
}
$query->select($columns);
}]);
}

同步config

1
2
3
4
5
6
7
8
9
file_put_contents(base_path() . '/config/cnpscy.php', '<?php return ' . var_export($data_list, true) . ';');
$data_list 数组的 key => value 格式即可。

我是存放 config 下面的 cnpscy

读取:

config('cnpscy.key')
https://learnku.com/laravel/t/38053

删除MySQL数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$start =  time();
while(true) {
try {
if(time() - $start > 100) {
// break;
\DB::disconnect('mysql::write');//断开重新连接
$sql="SET SESSION wait_timeout=65535";
\DB::connection('mysql::write')->select($sql);
$start = time();
}
\DB::connection('mysql::write')->where('id','>', 0)->delete();
}catch (\Exception $e) {
sleep(1);
//exit();
\DB::reconnect('mysql::write');
}

composer 支持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
https://github.com/shanyul/array2xml
cat composer.json
"extra": {
"laravel": {
"providers": [
"Shanyuliang\\Array2xml\\ServiceProvider"
]
}
}

ServiceProvider.php
namespace Shanyuliang\Array2xml;


class ServiceProvider extends \Illuminate\Support\ServiceProvider
{
protected $defer = true;

public function register()
{
$this->app->singleton(Array2xml::class, function(){
return new Array2xml();
});

$this->app->alias(Array2xml::class, 'array2xml');
}

public function provides()
{
return [Array2xml::class, 'array2xml'];
}
}
public function getXml()
{
$arr = [
'aaa' => [
'bbb' => 'ccc',
'eee' => 'fff'
]
];
$response = app('array2xml')->generate($arr);
}

Eloquent Collection 中获 id 数组

1
2
3
4
5
6
7
8
// 返回ID数组
$permissionIDs = $role->permissions->pluck('id');
//与上述结果相同。甚至相同的字符数:)
$permissionIDs = $role->permissions->modelKeys();
$allPermissions = Permission::pluck('id');
$permissions = $role->permissions->pluck('id');
$allPermissions = Permission::all()->modelKeys();
https://learnku.com/laravel/t/39007

hasMany 来处理『无限极分类』

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
Schema::create('categories', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->unsignedBigInteger('category_id')->nullable();
$table->foreign('category_id')->references('id')->on('categories');
$table->timestamps();
});
class Category extends Model
{

public function categories()
{
return $this->hasMany(Category::class);
}

}
public function childrenCategories()
{
return $this->hasMany(Category::class)->with('categories');
}
调用 Category::with(‘categories’),将得到下级 “子分类”,但是通过 Category::with(‘childrenCategories’) 将能帮你实现无限极。
public function index()
{
$categories = Category::whereNull('category_id')
->with('childrenCategories')
->get();
return view('categories', compact('categories'));
}
resources/views/categories.blade.php
<ul>
@foreach ($categories as $category)
<li>{{ $category->name }}</li>
<ul>
@foreach ($category->childrenCategories as $childCategory)
@include('child_category', ['child_category' => $childCategory])
@endforeach
</ul>
@endforeach
</ul>
resources/views/admin/child_category.blade.php
<li>{{ $child_category->name }}</li>
@if ($child_category->categories)
<ul>
@foreach ($child_category->categories as $childCategory)
@include('child_category', ['child_category' => $childCategory])
@endforeach
</ul>
@endif
https://learnku.com/laravel/t/38977

路由不生效

1
2
3
4
5
6
7
8
9
10
11
12
13
Effect 为正常控制器补录  Effect_back 为旧的

使用 find . -name "*.php" |xargs grep -i "Effect_back" 发现了问题所在


./vendor/composer/autoload_classmap.php: 'App\\Http\\Controllers\\Effect\\ActivityController' => $baseDir . '/app/Http/Controllers/Effect_back/ActivityController.php',
此问题应该是在你有新旧文件时 执行过 composer dump-autoload 所导致旧的文件自动加载进来 并进行了缓存


composer clear-cache

composer dump-autoload
https://learnku.com/articles/39287

Laravel Eloquent

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
$user = User::where('name', 'wuqinqiang')->first();
$user->age = 18;
$user->wechat->nickName = "Remember";
$user->save()
那么此时,会保存用户信息,但是并不会保存对应 Wechat 的关联信息,想让它一起保存咋么办?也很简单。把 save 换成 push 即可。

$user->push();
public static function boot()
{
parent::boot();
static::creating(function ($model) {
//业务逻辑
});
}
那要是不想在操作时触发任何事件则么办?你可以执行一个回调函数而无需触发任何事件:

$user = User::withoutEvents(function () {
return //保存或者更新代码
});

$ user = User :: where('name','wuqinqiang')->first();
$ user-> isDirty(); // 还没被动过 返回 false
$ user-> name ='curry';
$ user-> isDirty(); //模型被动过了 返回true
$ user-> isDirty('email'); // 可以传入参数 验证某个参数是否被动过,这个我还没碰过 当然 false
$ user-> isDirty('name'); // true
如果你还想具体了解到哪些是被动过了,可以使用:

$user->getDirty(); 你将得到一个数组 ["name" => "curry"]
$ user = User :: where('name','wuqinqiang')->first();
$user->name = 'curry';
$user->getOriginal(); // 返回数组 "name" => "wuqinqiang"
$user->getOriginal('name'); //指定要看哪个属性的原值 "wuqinqiang"
https://learnku.com/articles/39263

自定义 Artisan 命令

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
// routes/console.php
Artisan::command('password:reset {userId} {--sendEmail}', function(){
// 第一个参数是命令的名称
// 执行一些操作 ,比如重置用户密码 ...
})
protected $signature = 'password:reset {userId} {--sendEmail}';
// 定义 :password:reset {userId}
// 执行 :php artisan password:reset 5

$this->argument(); // 返回一个包含所有参数的数组( 第一个元素是命令名称 )
// [
// "command" => "password:reset",
// "userId" => "5"
// ]

$this->argument('userId');
// "5"
// 定义 :password:reset {--userId=}
// 执行 :php artisan password:reset --userId=5

$this->option(); // 返回一个包含所有选项的数组
// [
// "userId" => "5",
// "help" => false,
// "quiet" => false,
// "verbose" => false,
// "version" => false,
// "ansi" => false,
// "no-interaction" => false,
// "env" => null
// ]
$this->option('userId');
// "5"
// 提示用户输入
$email = $this->ask("你的邮箱是什么?");

// 提示用户输入 ,但用 "*" 隐藏输入内容
$password = $this->secret("你的数据库密码是什么?");

// 提示用户输入是 / 否 ,返回布尔值 ,除了按 y 和 Y ,其他的输入都会返回 false
if ($this->confirm("你确定要重置用户的密码?"));

// 提示用户选择选项 ,如果用户没有选择 ,默认值就是最后一个选项
$tips = '你希望在每天什么时间备份数据库?'
$options = array("08:00", "20:00", "12:00");
$default = 0;
$index = $this->choice($tips, $options, $default);
// 这里注意返回的是用户选择的 key ,而不是 value
print_r($optios[$index]);

// 还可以使用关联数组
$tips = '你现在有两个选择';
$options = array("a" => "自己脱", "b" => "我帮你脱");
$default = "b";
$index = $this->choice($tips, $options, $default);
print_r($optios[$index]);
$headers = array('name', 'email');
$data = array(
array('马云', 'jack_ma@aliyun.com'),
array('马化腾', 'pony@qq.com')
);
// $data = App\User::all(['name', 'email'])->toArray();
$this->table($headers, $data);
// 将进度条分为 10 份
$total = 10;
// 创建进度条
$this->output->progressStart($total);
// 循环
for ($i = 0; $i < $total; $i++) {
sleep(1);
// 进度条步进
$this->output->progressAdvance();
}
// 结束进度条
$this->output->progressFinish();
# 2
2/10 [=====>----------------------] 20%
# 10
10/10 [============================] 100%
// Artisan::call()
Route::get('test-artisan', function () {
$exitCode = Artisan::call("password:reset", [
'userId' => 15, '--sendEmail' => true
])
})
// $this->call() 或者 $this->callSilent()
Route::get('test-artisan', function () {
// 这个 callSilent() 有什么区别我也不太懂
$exitCode = $this->callSilent("password:reset", [
'userId' => 15, '--sendEmail' => true
])
})https://learnku.com/articles/39394

递归子级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
在 model 里面增加子级关联

public function child(){
return $this->hasMany(self::class,'pid');
}
在 model 里面增加子级递归关联

public function childRecursive()
{
return $this->child()->with('childRecursive');
}
调用的时候建议用 with 方法预加载https://learnku.com/articles/39687

$menu = Menu::with('childRecursive')->find($id);

laravel5.5 博客第八篇

多商户laravel vue开源商城

基于 Laravel + swoole + 小程序的开源电商系统

Laravel API 错误处理

laravel + swoole + adminlte 高效开发模板

Laravel 项目 伪静态分页处理

更快的找到自己所需的模型关联类型

Laravel-dompdf优雅导出PDF

Laravel + Vue + Oss 搭建的图片墙小站

Laravel editor.md 支持截图 / 粘贴上传图片

ConsoleTVs/Charts 简单快速绘制统计图

91 个常见的 Laravel 面试题和答案

Laravel 5.2 项目升级至 5.8 的过程

Laravel 中实现「福勒的货币设计模式」

Laravel6.0 框架和 semantic-ui 开发的一个个人博客社区网站

laravel 源码详解

仿 Laravel-china 的搜索包

Laravel 5.5 + H+UI 框架的权限管理后台

Laravel 服务容器

博客支持 Markdown,支持 RBAC 权限管理

三分钟从零部署 Laravel 应用到阿里云 ECS

Laravel+easywechat 实现公众号微信支付

Laravel-Gii 可视化代码生成工具

Laravel + Vue + Swoole 实现的简单 Web 聊天

laravel容器

Laravel MongoDB 数据库查询

Laravel 框架 5.1 升级到 5.5

php excel

Laravel 性能优化入门

安装 laradock 以及运行 Laravel 项目

过滤敏感词汇扩展

Windows 环境下安装 Laravel

Laravel-layui-admin : 支持 Laravel6.0 的 RBAC 后台系统

Laravel-Binlog 扩展

Laravel 自带消息模块搭建小程序实时推送消息

基于支付宝 OpenAPI 开源的 PHP 语言版本 SDK

gitbook 的 Laravel 源码详解

Application 类 make 方法调用栈流程图

快速laravel后台管理系统

PHP 实现 Websocket 协议

Laravel-S 项目之初体验

基于laravel5.5 + H-ui开发的一款基础后台管理系统

快速laravel后台管理系统

Laravel-admin 码的电子商务前后端系统

Laravel+vue 个人博客https://github.com/sweida/laravel-blog-api/

详解 laravel 源码 https://leoyang90.gitbooks.io/laravel-source-analysis/content/

web开发者知识体系电子书

EasyWechat 和 Laravel notification 发送微信小程序模板消息

QQ 登录、微信登录、微博登录、GitHub 登录

快速定位无用路由

Laravel 上将图片上传到阿里云 OSS

快速记忆php系统数组函数

附近的店铺

高性能的定时调度服务Forsun的Laravel组件

Laravel 查询附近的数据

elasticsearch中文发行版

多级联动

Laravel 下 Elasticsearch/Algolia 全文搜索

Phpstorm 开启 Laravel 代码提示

PHP 最优秀资源的整理汇集

腾讯地图搜索

Laravel 框架 5.1 升级到 5.5

Laravel Authorization:支持 ACL、RBAC、ABAC 等模型的授权库

PSR-12 编码规范扩充

Laravel 下 Elasticsearch/Algolia 全文搜索 使用案例

轻量级全文检索引擎 TNTSearch 和中文分词

laravel下TNTSearch+jieba-php实现中文全文搜索

postman使用记录

免费开源的在线文档管理插件

诗词博客

Elasticsearch 国内镜像下载站

PHP抖音机器人