安装扩展

当前执行环境为 7.2

安装 redis

1
$ pecl install redis

在 安装 redis 的时候需要 igbinary.h

1
2
3
4
checking for json includes... /usr/local/Cellar/php@7.2/7.2.29/include/php
checking for redis json support... enabled
checking for igbinary includes... configure: error: Cannot find igbinary.h
ERROR: `/private/tmp/pear/temp/redis/configure --with-php-config=/usr/local/opt/php@7.2/bin/php-config --enable-redis-igbinary=y --enable-redis-lzf=y --enable-redis-zstd=y' failed
1
$ pecl install igbinary

可能又会出现

1
2
3
4
...
checking for libzstd files in default path... not found
configure: error: Please reinstall the libzstd distribution
ERROR: `/private/tmp/pear/temp/redis/configure --with-php-config=/usr/local/opt/php@7.2/bin/php-config --enable-redis-igbinary=y --enable-redis-lzf=y --enable-redis-zstd=y' failed

安装 swoole

1
$ pecl install swoole

在安装 swoole 的时候会遇到

错误信息如下:

1
2
3
4
5
6
7
8
....
In file included from /private/tmp/pear/temp/swoole/php_swoole.h:53:
/private/tmp/pear/temp/swoole/include/swoole.h:620:10: fatal error: 'openssl/ssl.h' file not found
#include <openssl/ssl.h>
^~~~~~~~~~~~~~~
1 error generated.
make: *** [php_swoole_cxx.lo] Error 1
ERROR: `make' failed

没有加 openssl 库的路径或者指定 openssl 库的路径不对,缺少头文件。
没有 openssl 的话使用 brew 安装一个 openssl,在 pecl 安装的时候加上对应路径即可

1
enable openssl support? [no] : yes --with-openssl-dir=/usr/local/opt/openssl@1.1

[译] php 中更简洁的三元运算符 ?:

原文地址 Even shorter ternary operators in PHP using ?:

今天我发现了 PHP 三元运算符的一个小小的用法. 这给我干涸的大脑一点乐趣!

PHP 三元运算符是对参数赋值时候的一个简洁的主要用法. 一个主要的用法: PHP 三元运算符能够让你在一行代码中描述判定代码, 从而替换掉类似以下的代码:

1
2
3
4
5
if (isset($value)) {
$output = $value;
} else {
$output = 'No value set.';
}

使用以下代码替代:

1
$output = isset($value) ? $value : 'No value set.';

第二个代码例子是非常简洁的用法, 在多种情况下(并非所有), 这是一个非常实用的用法. 有许多关于是否应该使用三元运算符的争辩;让我说, 这就是一个工具, 向其他工具一样, 只是用的正确与否.

常用的语法是 (expression) ? value if truthy : value if falsy.这个表达式可以是一个变量, 测试这个变量是真还是假:

1
$output = $value ? $value : 'No value set.';

问题是: 以上的例子很常用同时也重复的有些烦人: 写两次 $value 就像是感到错误一样.

好在是, 我今天发现在 PHP 5.3 中介绍了一个更简洁的使用三元运算符的语法. 你可以从手册中学到, 但是这里我们怎么样让上边的例子更简洁呢:

1
$output = $value ?: 'No value set.';

这个看起来很熟悉, 这个是因为很像其他的简写运算符:

1
$value = $value . $other_value;

转换成:

1
$value .= $other_value;

为了更简洁, 这个意味着我们可以这样简写并不意味着我们就应该这么写. 但是, 当我们写简洁代码的时候, 这种方式会看起来更清楚, 我们应该这么写, (并且这个特性允许我们在多种情况下使用这个运算符[this feature allows us to DRY up the ternary operator in many cases])

肉眼看到的相同两个字串的不同

祭出两个相同(其实不同)的字符串

1
2
$strA = '8888‬';
$strB = '8888';

我们肉眼看到的这两个字符串是绝对相同的, 对的, 他是一个’8888’, 让我们用 php (世界上最好的语言) 输出下两个字串的长度

1
2
3
4
5
6
7
# code
var_dump($strA);
var_dump($strB);

# result
string(7) "8888‬"
string(4) "8888"

我滴妈呀, 怎么会不同呢, 纳闷..

让我们撕下她的伪装

1
2
3
for ($i = 0, $iMax = strlen($strA); $i < $iMax; $i++) {
var_dump($strA[$i]);
}
1
2
3
4
5
6
7
string(1) "8"
string(1) "8"
string(1) "8"
string(1) "8"
string(1) "�"
string(1) "�"
string(1) "�"

咦, 这是个什么鬼. 不像是正常字符啊. 哦, 对了, 我们是用的Utf-8 字符集, 这三个应该是一个字符, 我们把它组合起来

获取到 ASCII 码值

1
2
3
for ($i = 0, $iMax = strlen($strA); $i < $iMax; $i++) {
var_dump(ord($strA[$i]));
}
1
2
3
4
5
6
7
int(56)
int(56)
int(56)
int(56)
int(226)
int(128)
int(172)

这个编码值是 [226 128 172], 让我们找到它.

看, 在这里

31a1858d413ad156f113694da1f6f6aa.jpeg

来自于这个网站, 好像是输出格式化标识符. 以上.

[原] 怎样创建 PSR-4 的 Php 包

本文是帮助初学者搭建基础的 php composer 包, 本项目源码地址

目录结构和初建准备

首先创建一个目录来存放所有文件, 这里我 命名为 util-demo , 目录中需要包含两个目录 src/, tests/, 所有的代码需要放置到 src/ 目录, 所有的测试文件需要放置到 tests/ 目录

初始化文件夹, 并使用 git 初始化项目, 便于进行版本管理

1
2
3
$ mkdir util-demo
$ cd util-demo
$ git init

初始化 composer

这里假定你已经安装了 composer, 如果没有安装, 请到 https://getcomposer.org/download/ 进行安装

初始化并输入包名, 这里默认使用 duoli/util-demo 包名

1
2
3
4
5
6
7
$ composer init

Welcome to the Composer config generator

This command will guide you through creating your composer.json config.

Package name (<vendor>/<name>) [duoli/util-demo]:

输入描述

1
Description []: first util package demo

输入作者名称, n 跳过

1
Author [duoli <zhaody901@126.com>, n to skip]:

设定包的稳定版本, 默认是 stable, 详细查看 minimum-stability (root-only)

1
Minimum Stability []:

安装类型 : Package Type, 默认是 library

1
Package Type (e.g. library, project, metapackage, composer-plugin) []:

License, 协议, 关于协议部分, 你可以选择自己需要的协议, 阮老师这里有一个阅读指南, 可以进行参考 如何选择开源许可证?, 对于协议部分的写法可以参考 许可协议 license 这里

1
License []:

require

设定三方依赖, 表明当前项目/包是否有依赖于其他包进行开发, 这里选择 no,

1
2
3
4
Define your dependencies.

# 生产依赖
Would you like to define your dependencies (require) interactively [yes]?

require-dev

开发依赖, 这里我们加入 phpunit 作为单元测试依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Would you like to define your dev dependencies (require-dev) interactively [yes]?

Search for a package: phpunit

Found 15 packages matching phpunit

[0] phpunit/phpunit
...
[14] brianium/paratest

Enter package # to add, or the complete package name if it is not listed: 0
Enter the version constraint to require (or leave blank to use the latest version):
Using version ^8.5 for phpunit/phpunit
Search for a package:

当不需要额外增加包的时候, 直接回车, 确认安装包依赖, 初始化完成

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
{
"name": "duoli/util-demo",
"description" : "first util package demo",
"require-dev": {
"phpunit/phpunit": "^8.5"
},
"license": "MIT",
"authors": [
{
"name": "duoli",
"email": "zhaody901@126.com"
}
],
"require": {}
}

Do you confirm generation [yes]?

Would you like to install dependencies now [yes]? yes
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 29 installs, 0 updates, 0 removals
- Installing sebastian/version (2.0.1): Downloading (100%)
......
- Installing phpunit/phpunit (8.5.8): Downloading (100%)
sebastian/global-state suggests installing ext-uopz (*)
phpunit/phpunit suggests installing phpunit/php-invoker (^2.0.0)
Package phpunit/php-token-stream is abandoned, you should avoid using it. No replacement was suggested.
Writing lock file
Generating autoload files
5 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

上边初始化完成之后, 我们文件目录应该如下所示

1
2
3
4
.
├── composer.json
├── composer.lock
└── vendor

作为包管理来讲, 我们需要忽略 composer.lockvendor 文件夹, 因为在项目中使用此包的时候会进行全局的安装, 不提交是为了不产生冗余代码.

我们加入 .gitignore 文件, 加入这两个文件夹的忽略, 另外由于要生成 phpunit 测试文件, 我们还需要加入 .phpunit.* , 我这里还额外加入了 IDE 的忽略

.gitignore

1
2
3
4
5
6
7
8
9
# ide
.idea

# project
composer.lock
vendor/

# phpunit
.phpunit.*

文件自动加载 autoload

autoload

这里是上边初始化没有提及到的, autoload 指定了包的加载方式, Composer 可以自动加载包内文件, 但是你需要指定你的文件的加载方式, 这里我们使用的是 psr-4 标准, 详细的规范可以阅读 PSR-4 自动加载规范

这里需要在 composer.json 中追加

1
2
3
4
5
6
7
8
  ...
"autoload": {
"psr-4": {
"Duoli\\UtilDemo\\": "src",
"Duoli\\UtilDemoTests\\": "tests"
}
}
...

因为这个加载是后续添加的, 想要识别需要我们手动运行下 composer dumpautoload, 这样才可以识别我们添加的命名空间映射

另这里我额外约定了一个测试的命名空间, 方便对测试文件进行命名空间的目录识别

travis 测试集成

如果打算在GitHub上托管包,一个不错优点是集成Travis CI,这是一个持续集成应用程序,它会自动运行单元测试文件. 当接受 pr 时,会非常有用,因为你可以快速查看是否所有的测试都通过验证。

要与 Travis 集成,添加一个名为 .travis.yml 的文件,并复制以下内容

1
2
3
4
5
6
7
8
9
10
11
language: php

php:
– 7.0
– hhvm

before_script:
– composer self-update
– composer install –prefer-source –no-interaction –dev

script: phpunit

在这里,不会深入讨论使用 Travis 的问题,但这应该是一个很好的开始,你可以自己来进行研究如何使用它.

编写代码

现在可以编写代码了, 打开 src 目录创建 File.php 文件, 复制如下代码

1
2
3
4
5
6
7
8
9
class File
{

public function extension($filename)
{
return pathinfo($filename, PATHINFO_EXTENSION);
}

}

如果你曾经查看过旧的 PHP 包源码,可能会发现一个 Duoli/UtilDemo 目录。PSR-4 不再使用嵌套的目录结构,而是允许直接在 src 目录下编写类

编写单元测试

现在我们已经编写了包源码,可以开始编写一些测试了。PHPUnit 需要一个名为 phpunit.xml 的文件来定义一些设置。在根目录中创建一个新的phpunit.xml文件,并复制以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<phpunit
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Duoli Util Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>

如果不熟悉这个文件中意思,不用担心,你无需关注设置了哪些内容(当前来讲), 这个文件只是定义一些设置和文件应该如何自动加载的约定.

接下来,在 tests 目录下,创建一个名为 StrTest.php的文件,并复制以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php namespace Duoli\UtilDemoTests;

use Duoli\UtilDemo\File;
use PHPUnit\Framework\TestCase;

class FileTest extends TestCase
{

public function testExtension()
{
$extension = File::extension('readme.md');
$this->assertEquals('md', $extension);
}

}

打开命令行并运行:

1
$ ./vendor/bin/phpunit

PHPUnit 会自动运行测试,并给出一个绿色的 OK (1 test, 1 assertion) 输出。

在编写测试文件时,需要确保测试类继承了 PHPUnit\Framework\TestCase

文档

最后要做的是创建一个 Readme.md 的文档。如果打算在 GitHub 上托管代码,这个文件会在代码页面显示出来,目的是描述包以及包如何工作.

还应该在源代码中包含一个许可证。默认情况下,你发布的任何东西都是你的版权所有,如果你想让其他人使用你的代码,你需要给它一个许可。一个好的选择是 MIT 许可

发布到 github 和 Packagist

现在已经完成了你的包,你可以将它推到 GitHub 和 Packagist。

GitHub 是一个托管的 git 存储库服务,它使得在软件协作变得非常容易。

Packagist 是查找和使用 PHP 包的服务商

要将代码推送到 GitHub,创建一个新的存储库, 然后设定 git 的远端, 将代码推上去即可, 命令是

1
2
$ git remote add origin git@github.com:username/demo.git
$ git push -u origin master

在 Github 上集成 Packagist 和 Travis, 需要找到设置, 并且在  Webhooks & Services 部分找到这两个服务, 集成这两个服务需要授权 github 账户并且设置一些简单的步骤.

概括

如果以前从未开发过已发布的 php 包,那么需要考虑很多问题。然而,一旦你操作过不止一次就不至于这么复杂了.

PHP 的包结构非常简单,而 PSR-4 使其更加简单。一旦了解了该结构的每一部分是用于做什么,其他人阅读 PHP 包就会容易很多.

参考文章

[原] 本地搭建 packagist 的注意事项

搭建本地 packagist 平台

搭建平台按照 3.4. 使用 Satis 处理私有仓库 这个文档来进行, 因为 2.0 版本支持的是 docker 方式, 这里暂时不做赘述

编写代码

编写代码, 完成单元测试

创建 代码仓库

推送代码

更新 satis 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
...
"repositories": [
{
"type": "vcs",
"url": "git@codeup.aliyun.com:dadi/poppy/ext-alipay.git"
},
{
"type": "vcs",
"url": "git@codeup.aliyun.com:dadi/poppy/ext-im.git"
}
],
"require": {
"poppy/ext-alipay": "*",
"poppy/ext-im": "*"
},
...
}

让服务器支持代码拉取并自动执行包命令

因为 codeup 拉取代码白名限制, 所以需要对可以拉取的代码仓库启用 key 配置

合适的时候用 && 和 || 缩写 条件语句

是不是经常看到这样的写法 ?

1
2
3
!$var && $var = '你好坏!';

$error or die('Error!');

明白他的意思吗?
&& 为 同时真 运算符.
A && B, 只有当 A 和 B 都为真的时候,这句才是真
PHP 会先判断A是否为真, 若A为真的话 就会继续判断B.
所以,当 A 为真, B 是一个语句的时候, B 就会运行.
同理,当 A 为假的时候, 这句一定是假, 就没有必要往后判断了, 此时, B 就不会运行.
||, or 则不同
A or B, 只要 A 或者 B 中有一个是真, 这句就是真
PHP先判断 A 是否为真, 若 A 为真的话, 此句一定是真, 没有必要再去判断B
所以当 A 为假的时候, PHP会继续判断 B 是不是真, 才能得到这句的结果
此是, B如果是一个语句就会运行.
还记得入门时候那数据连接那句吗? 现在应该很好懂为什么加个OR了吧?

1
mysql_connect($host,$user,$pwd) or die('Mysql Error!');

如果连接失败,前面就是假, 后面的DIE就会运行咯!
留个作业:

1
2
3
$var = '';
!$var && echo "空的!";
!$var || print "空的!";

哪个可以成功执行呢? 想想看为什么?

关于 Php 的周计算

对于周计算(周榜)会在年底的时候出现了问题, 所以这里重新看了下关于周的定义并且重新改写了周函数, 如果不知道周定义, 函数的写法依旧会出现问题.

Php 的周定义是 ISO Week

我们看下定义

ISO周日历系统是ISO 8601日期和时间标准的一部分,是一种闰周历系统。这个系统主要用在政府和商务的会计年度,用以维持时序。这个系统依据格里历的年度中特定的一个周日,决定该年是否要增加一个星期。
格里历的置闰循环是400年97个闰日,包含20,871个完整的星期。在每个循环中有71年会有额外的第53周,一年的平均长度是52.1775周;平均每个月有4.348125个星期。
一个ISO周数年(也可以简称为ISO年)有52或53个完整的星期,也就是以364天或371天取代了常用的365或366天。这额外增加出来的一个星期称为闰周,然而在ISO 8601并没有这个名词。每个星期从星期一开始。每年的第一个星期包含当年的第一个星期四(并且总是包含1月4日)。ISO周的年编号因此会稍微偏离1月1日几天。

这里有几个定义, 我们拿一个来进行说明, 详细查看 wiki

下面是实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 根据日期返回每年的周数
* 例如 2020-01-01 会返回 [2019,52] 周, 用于统计部分
* @param string $date 日期
* @param int $start 默认以那一天作为第一天的开始
* @return array
*/
public static function week(string $date, $start = Carbon::MONDAY): array
{
/** @var Carbon $carbon */
$carbon = Carbon::createFromFormat('Y-m-d', $date)->startOfWeek($start);

$startWeek = (clone $carbon)->subDays($carbon->dayOfWeek - 1);
$endWeek = (clone $carbon)->addDays((7 - $carbon->dayOfWeek) % 7);

if ($endWeek->format('W') === '01') {
return [$endWeek->year, $endWeek->format('W')];
}
else {
return [$startWeek->year, $startWeek->format('W')];
}
}

Php (Code Review) - 01

1. 计算 1 次

1
2
3
4
5
6
7
8
9
10
11
<?php
# before
if (strlen($nickname) < 6 || strlen($nickname) > 18) {
return $this->setError('昵称长度必须在两到六个个汉字,六到十八个英文之间');
}

# after
$length = strlen($nickname);
if ($length < 6 || $length > 18) {
return $this->setError('昵称长度必须在两到六个个汉字,六到十八个英文之间');
}

2. divide by zero 的问题

优化点:

  • totalCount 是 数值
  • 需要考虑除数为 0 的情况
1
2
3
4
5
6
7
8
9
10
11
12
# bad
$totalCount = (clone $SDb)->count();
$overNewRate = (new Number($overCount))->divide((new Number($totalCount)));
$overNewRate = round($overNewRate->multiply(100)->getValue(), 2);

# good
try {
$overNewRate = (new Number($overCount))->divide($totalCount);
$overNewRate = round($overNewRate->multiply(100)->getValue(), 2);
} catch (\Exception $e) {
$overNewRate = 0;
}

3. 使用 (string) 替代 strval

Analyzes if PHP4 functions (intval, floatval, doubleval, strval) are used for type casting and generates hints to use PHP5’s type casting construction (i.e. ‘(type) parameter’).

1
2
3
4
5
// bad
$input = strval($_POST['name']);

// good
$input = (string) $_POST['name'];

4. 合并 isset 的多重判定

The inspection is advising when multiple ‘isset(…)’ statements can be merged into one

1
2
3
4
5
6
7
8
9
// bad
if (isset($dir['origin']) && isset($dir['doc'])) {
// ...
}

// good
if (isset($dir['origin'], $dir['doc'])) {
// ...
}

5. If 多条件语法的合并

1
2
3
4
5
6
7
8
9
10
11
// bad
if ($profile->chid_status === UserProfile::STATUS_FAIL) {
if ($profile->chid_failed_at) {
// ...
}
}

// good
if ($profile->chid_status === UserProfile::STATUS_FAIL && $profile->chid_failed_at) {
// ...
}

6. 引号的使用

对于单纯字符串使用 ', 对于变量和文本进行混排的, 使用 "

1
2
3
4
5
6
7
8
9
// bad
$domain     = "domain.com";
$random     = str_random(7);
$randomMail = 'prefix_'.$random.'@'.$domain;

// good
$domain     = 'domain.com';
$random     = str_random(7);
$randomMail = "prefix_{$random}@{$domain}";
1
0 => '',

7. empty 使用的时机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// bad
// 来源于订单取消过期
$orders = OrderHunter::
// ...
->where('created_at', '<', Carbon::now()->subMinutes($minutes))
->get();
if (!empty($orders)) {
$Play = new Play();
$orders->each(function (OrderHunter $item) use ($Play) {
// ...
});
}

// good
// 因为这里返回的是一个对象. empty 对象会返回的是 true, 当然这种对于实现了 countable 的来说又是另外一种概念
// todo 这里也需要测试
if ($orders->count()) {
$Play = new Play();
$orders->each(function (OrderHunter $item) use ($Play) {
// ...
});
}

8. 数组箭头后不允许换行

1
2
3
'hunter' => [
0 => 'lol',
],

9. If 后的 ; 需要去掉

1
2
3
if (!$info = $Order->info('003200330087', 62, 1)) {
\Log::error($Order->getError());
}

10. 参数等号对齐

1
2
$hunter_type    = OrderHunter::ORDER_NORMAL;
$subtotal_price = (new Number($price_id ?? 0))->multiply($num)->getValue();

11. 命名空间和 php 开始 <?php 存在于一行内

1
<?php namespace Order\Tests\Configuration;

12. 删除文件尾部的 ?>

php 文件的典型标记是以 <?php 开头, ?> 结尾。但是在 Zend Framework 中却不推荐在 php 文件末尾加 ?>
因为在<?php ?>之外的任何字符都会被输出到网页上,而之中的却不会。所以在末尾不加 ?> 可以预防 php 文件被恶意加入字符输出到网页。

13. 数组的键名

在 PHP 中, 使用不经单引号包含的字符串作为数组键名是合法的, 但是我们不希望如此 – 键名应该总是由单引号包含而避免引起混淆. 注意这是使用一个字符串, 而不是使用变量做键名的情况

1
2
3
4
5
6
7
8
// 错误
$foo = $assoc_array[blah];
// 正确
$foo = $assoc_array['blah'];
// 错误
$foo = $assoc_array["$var"];
// 正确
$foo = $assoc_array[$var];

避免在大数组上使用 in_array

避免在大的数组上使用 in_array(), 同时避免在循环中对包含 200 个以上元素的数组使用这个函数. in_array() 会非常消耗资源. 对于小的数组这种影响可能很小, 但是在一个循环中检查大数组可能会需要好几秒钟的时间. 如果您确实需要这个功能, 请使用 isset()来查找数组元素. 实际上是使用键名来查询键值. 调用 isset($array[$var]) 会比 in_array($var, array_keys($array)) 要快得多.

SQL 脚本格式

SQL 代码常常会变得很长, 如果不作一定的格式规范, 将很难读懂. SQL 代码一般按照以下的格式书写, 以关键字换行:

1
2
3
4
5
6
$sql = 'SELECT *
<-one tab->FROM ' . SOME_TABLE . '
<-one tab->WHERE a = 1
<-two tabs->AND (b = 2
<-three tabs->OR b = 3)
<-one tab->ORDER BY b';

这里是应用了制表符后的例子:

1
2
3
4
5
6
$sql = 'SELECT *
FROM ' . SOME_TABLE . '
WHERE a = 1
AND (b = 2
OR b = 3)
ORDER BY b';

禁止使用单字母开头的变量(无意义的变量)

1
$tKey, $tVal

14. 使用 (int) code 替代 intval(code)

1
2
3
4
// deprecated
$is_apply = intval(input('is_apply'));
// succcess
$is_apply = (int) input('is_apply');

15. 去除多余的 else

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// deprecated
$id = (int) input('id');
if ($id) {
$item = PluginHelp::find($id, ['id', 'help_title as title', 'updated_at', 'content']);
return Resp::web(Resp::SUCCESS, '获取文章内容成功', $item);
}
else {
return Resp::web(Resp::SUCCESS, '列表为空');
}

// suggest
$id = (int) input('id');
if ($id) {
$item = PluginHelp::find($id, ['id', 'help_title as title', 'updated_at', 'content']);
return Resp::web(Resp::SUCCESS, '获取文章内容成功', $item);
}
return Resp::web(Resp::SUCCESS, '列表为空');

16. 对象和数组的不同

1
2
3
// Json 中对象返回 {}
// Json 中数组返回 []
// PHP 中空数组和对象均为 []

17. 使用临时变量以避免复合条件语句

1
2
3
4
5
6
7
8
9
10
# good
$itemValid = $itemMoney > 800 && $level > 3 && $valid > 0;
if($itemValid && isReady()) {
display();
}

# bad
if($itemMoney > 800 && $level > 3 && $valid > 0 && isReady()) {
display();
}

18. Switches 语句应该套用以下格式,并且每个分支必须注释清楚

1
2
3
4
5
6
7
8
switch (condition) {
case 0 :
// show something
break;
default :
// this is some code
break;
}

19. 数字变量转换

1
2
3
4
5
// bad
$ids = !is_array($id) ? [$id] : $id;

// good
$ids = (array) $id;

20. 不要使用硬编码

1
2
3
4
5
6
7
8
9
10
// 写常量是属于硬编码, 这里不要使用硬编码
// Bad
if ($owner->pub_is_good != 1) {
return $this->setError('您并非优质商人, 无法发布优质订单');
}

// Good
if ($owner->pub_is_good != Front::PUB_GOOD) {
return $this->setError('您并非优质商人, 无法发布优质订单');
}
1
2
3
4
5
// bad
$db->query("Update xd_company ...");

// good
$db->query("Update {$db_prefix}company ...");

21. 文件命名导致的冲突

1
2
3
4
5
6
7
8
9
// 文件名称
// TransToaccountTransfer.php
// 这里会出现文件类不存在的情况
// Found by Lipengtao

// 类名称
class TransToAccountTransfer {
...
}

22. 不正确的 Switch 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// bad : 目标值和匹配值不同
switch ($win_dot) { // 0
case ($win_dot == 0): // true
$result = $one_number->multiply($coefficient)->getValue();
break;
case ($win_dot >= 1 && $win_dot < 100): // false
$dis_price = $one_number->multiply($this->discount($win_dot))->multiply($coefficient)->getValue();
$result = $one_number->subtract($dis_price)->multiply($coefficient)->getValue();
break;
case ($win_dot > 99):
$result = $up_number->multiply($coefficient)->getValue();
break;
}

// good : 应该使用 if - else
if ($win_dot == 0):
$result = $one_number->multiply($coefficient)->getValue();
elseif ($win_dot >= 1 && $win_dot < 100):
$dis_price = $one_number->multiply($this->discount($win_dot))->multiply($coefficient)->getValue();
$result = $one_number->subtract($dis_price)->multiply($coefficient)->getValue();
else ($win_dot > 99):
$result = $up_number->multiply($coefficient)->getValue();
endif

23. 负数的写法

负数使用 ‘-‘ 来进行拼凑感觉会比较容易出问题

1
2
3
4
5
# bad : 这里是减去的总金额
$price = '-' . $order->total_price;

# good : 这里是我认为标准的写法
$price = (new Number($order->total_price))->negate()->getValue();

24. 注意 数组 + 和 array_merge 的不同

两个数组相加 如果前面的数组和后面数组 key 相同,前面的 key 值会覆盖后面的 key 值,array_merge() 后面的数组相同的 key 会覆盖前面的

25. 检测存在数据, 先进行检测, 然后再检测数据的值

1
2
3
4
5
6
7
8
9
10
11
// 修复前 : 发放会员折扣券
$setting = sys_setting(DiscountCoupon::settingKey());
if ($setting['week'] && $setting['send_at'] && isset($setting['week'], $setting['send_at'])) {
......
}

// 修复后, 先检测是否设定, 然后再检测值
$setting = sys_setting(DiscountCoupon::settingKey());
if (isset($setting['week'], $setting['send_at']) && $setting['week'] && $setting['send_at']) {
......
}

26. 数据库的 clone

1
2
3
4
5
6
7
// clone
$Db = OrderUserCoupon::where('account_id', $account_id)
->where('status', OrderUserCoupon::STATUS_UNUSED)
->where('start_at', '<=', Carbon::now()->toDateString());
if (!(clone $Db)->exists()) {
return $this->setError('暂无可用优惠券');
}

27. 同一个函数中存在多个不同类型的变量采用匈牙利命名法

1
2
3
// 匈牙利命名法
$arrCoupon = $coupon->toArray();
$objCoupon = $coupon;

28. 空数组约束

1
2
3
// 空数组
$array = null;
$array = (array) $array;

29. 注释的写法

需要支持代码提示

1
2
3
4
5
6
/**
* 系统推荐优惠券
* @param array|OrderUserCoupon[] $coupons 可用优惠券列表
* @param float $price 订单价格
* @return array|mixed
*/

30. 三元运算符的简写[?:/??]

Using null coalescing operator in PHP 7 simplifies code structure.

1
2
3
4
5
// bad
Form::model(isset($data['params']) ? $data['params'] : null)

// good
Form::model($data['params'] ?? null)

下面这种情况使用与 value 已经定义的情况

1
2
3
4
5
// bad
$output = $value ? $value : 'No value set.';

// good
$output = $value ?: 'No value set.';

Composer 安装和配置

安装

方法 1 : 官方方式

参考官方文档 : https://getcomposer.org/download/

方法 2 : 下载并给予权限

1
2
3
$ wget https://mirrors.aliyun.com/composer/composer.phar
$ chmod +x composer.phar
$ mv composer.phar /usr/local/bin/composer

全局配置文件

修改 composer 的全局配置文件

Aliyun

阿里云开源镜像提供的 packagist 镜像服务

https://developer.aliyun.com/composer

1
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer

安畅网络镜像

1
composer config -g repos.packagist composer https://php.cnpkg.org

交通大学镜像

1
composer config -g repos.packagist composer https://packagist.mirrors.sjtug.sjtu.edu.cn

华为云

1
composer config -g repo.packagist composer https://mirrors.huaweicloud.com/repository/php/

腾讯云

1
composer config -g repos.packagist composer https://mirrors.cloud.tencent.com/composer/

phpcomposer 镜像

1
composer config -g repo.packagist composer https://packagist.phpcomposer.com

关闭全局配置

1
composer config -g --unset repos.packagist

更换镜像

1
2
3
composer config -g repo.packagist composer 镜像地址
composer clearcache
composer update || install

参考

修改记录

2021-10-15 : 加入安装方法, 移除已关闭镜像