• Welcome to Journal web site.

我是 PHP 程序员

- 开发无止境 -

Next
Prev

PHP 开发规范

Data: 2021-05-19 13:43:10Form: JournalClick: 16

基础规范

这是由 PHP-FIG(PHP Framework Interoperability Group)制定的 PHP 编码规范(PSR:Proposing a Standards Recommendation)

文档列表:PHP Standards Recommendations

重点阅读:PSR-1: Basic Coding Standard、PSR-12: Extended Coding Style


团队规范

代码注释

行注释:

// 空格后写注释内容

块注释:

/**
 * 标题后空一行
 *
 * @param $user
 * @return array
 */
 

类方法及属性顺序

  • 方法顺序:publicprotectedprivate
  • 属性顺序:const > public > proteced > private
class Demo
{
    const TIMEOUT = 60;
    public $table = 'user';
    protected $status = 1;
    private $field = 'age';

    public function func1()
    {
    }

    protected function func2()
    {
    }

    private function func3()
    {
    }
}
 

字符串中无变量时使用单引号

$str = ' ';
$str = 'Hello World';
$str = "Hello $name";

// SQL 语句例外(语法包含单引号)
$sql = "select * from table where company='四三九九网络股份有限公司'";
 

数组最后一个元素加逗号

增减数组元素后提交 Git,版本记录中不会产生干扰信息:

$array = [
    'a',
    'b',
    'c',
];

Laravel 项目规范

日志规范

Laravel 遵循 RFC 5424 定义了八个日志级别,为了避免错误使用,我们使用并重新定义其中四个等级:

级别 说明
Error 错误日志(出现错误,会导致系统不可用或功能中断)
Warning 警告日志(出现异常,但还不会影响功能的正常运行)
Info 信息日志(运行时的一些重要信息,默认日志输出级别)
Debug 调试日志(开发调试过程中一些详细的运行信息,默认不输出)
 

单数 or 复数?

为了不必要的混淆,控制器、路由、模型、数据表均使用单数命名

 

资源型控制器

假设需要处理 “用户” 相关的 HTTP 请求,使用 Artisan 命令创建控制器:

php artisan make:controller UserController --resource

接下来给控制器注册一个资源路由:

Route::resource('user', 'UserController');

此时资源的相关操作对应关系如下:

HTTP 方法 URI 动作(方法) 说明
GET /user/create create 创建用户页面
GET /user/{user}/edit edit 编辑用户页面
GET /user index 读取所有用户接口
POST /user store 保存用户接口
GET /user/{user} show 读取用户接口
PUT/PATCH /user/{user} update 更新用户接口
DELETE /user/{user} destroy 删除用户接口
 

自定义命令必须使用命名空间

所有的自定义命令,都必须使用项目命名空间,如:

php artisan namespace:send-email
php artisan namespace:clear-token

错误的例子:

php artisan send-email
php artisan clear-token
-------------------------------------------

变量

使用见字知意的变量名

坏:

$ymdstr = $moment->format('y-m-d');

好:

$currentDate = $moment->format('y-m-d');
 

同一个实体要用相同的变量名

坏:

getUserInfo();
getUserData();
getUserRecord();
getUserProfile();

好:

getUser();
 

使用可读性高的名称

代码是用来读的,命名时如果没有有意义、不好理解,那就是在伤害读者。

坏:

// 448 是什么意思?
$result = $serializer->serialize($data, 448);

好:

$JSON = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
 

使用自解释型变量

坏:

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);

saveCityZipCode($matches[1], $matches[2]);

不错(好一些,但强依赖于正则表达式的熟悉程度):

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);

[, $city, $zipCode] = $matches;
saveCityZipCode($city, $zipCode);

好(使用带名字的子规则,不用懂正则也能看的懂):

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+)\s*(?<zipCode>\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);

saveCityZipCode($matches['city'], $matches['zipCode']);
 

避免深层嵌套,尽早返回

坏:

function fibonacci(int $n)
{
    if ($n < 50) {
        if ($n !== 0) {
            if ($n !== 1) {
                return fibonacci($n - 1) + fibonacci($n - 2);
            } else {
                return 1;
            }
        } else {
            return 0;
        }
    } else {
        return 'Not supported';
    }
}

好:

function fibonacci(int $n): int
{
    if ($n === 0 || $n === 1) {
        return $n;
    }

    if ($n >= 50) {
        throw new \Exception('Not supported');
    }

    return fibonacci($n - 1) + fibonacci($n - 2);
}
 

少用无意义的变量名

别让读你的代码的人猜你写的变量是什么意思。写清楚好过模糊不清。

坏:

$l = ['Austin', 'New York', 'San Francisco'];

for($i = 0; $i < count($l); $i++) {
    $li = $l [$i];
    doStuff();
    doSomeOtherStuff();
    // ...
    // ...
    // ...
    // 等等, $li 代表什么?
    dispatch($li);
}

好:

$locations = ['Austin', 'New York', 'San Francisco'];

foreach($locations as $location) {
    doStuff();
    doSomeOtherStuff();
    // ...
    // ...
    // ...
    dispatch($location);
}
 

不要添加不必要上下文

如果从你的类名、对象名已经可以得知一些信息,就别再在变量名里重复。

坏:

class Car
{
    public $carMake;
    public $carModel;
    public $carColor;

    //...
}

好:

class Car
{
    public $make;
    public $model;
    public $color;

    //...
}

表达式

使用恒等式

不好(简易对比会将字符串转为整型):

$a = '42';
$b = 42;

if ($a != $b) {
   // 这里始终执行不到
}

对比 $a != $b 返回了 false 但应该返回 true ! 字符串 '42' 跟整数 42 不相等

好(使用恒等判断检查类型和数据):

$a = '42';
$b = 42;

if ($a !== $b) {
}

函数

函数参数(最好少于 2 个)

限制函数参数个数极其重要,超过 3 个可选参数会有很多参数组合要测试。

无参数是理想情况,通常如果你的函数有超过 2 个参数,说明它要处理的事太多了。 如果必须要传入很多数据,建议封装一个高级别对象作为参数。

坏:

function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void
{
    // ...
}

好:

class MenuConfig
{
    public $title;
    public $body;
    public $buttonText;
    public $cancellable = false;
}

$config = new MenuConfig();
$config->title = 'Foo';
$config->body = 'Bar';
$config->buttonText = 'Baz';
$config->cancellable = true;

function createMenu(MenuConfig $config): void
{
    // ...
}
 

函数应该只做一件事

这是迄今为止软件工程里最重要的一个规则。当一个函数做超过一件事的时候,他们就难于实现、测试和理解。当你把一个函数拆分到只剩一个功能时,他们就容易被重构,然后你的代码读起来就更清晰。如果你遵循这条规则,你就领先于大多数开发者了。

坏:

function emailClients(array $clients): void
{
    foreach($clients as $client) {
        $clientRecord = $db->find($client);
        if ($clientRecord->isActive()) {
            email($client);
        }
    }
}

好:

function emailClients(array $clients): void
{
    $activeClients = activeClients($clients);
    array_walk($activeClients, 'email');
}

function activeClients(array $clients): array
{
    return array_filter($clients, 'isClientActive');
}

function isClientActive(int $client): bool
{
    $clientRecord = $db->find($client);
    return $clientRecord->isActive();
}
 

函数名应体现它做了什么事

坏:

class Email
{
    public function handle(): void
    {
        mail($this->to, $this->subject, $this->body);
    }
}

$message = new Email(...);
// 啥?handle 处理一个消息干嘛了?是往一个文件里写吗?
$message->handle();

好:

class Email 
{
    //...

    public function send(): void
    {
        mail($this->to, $this->subject, $this->body);
    }
}

$message = new Email(...);
// 简单明了
$message->send();
 

不要用 flag 作为函数的参数

flag 就是在告诉大家,这个方法里处理很多事。前面刚说过,一个函数应当只做一件事。 把不同 flag 的代码拆分到多个函数里。

坏:

function createFile(string $name, bool $temp = false): void
{
    if ($temp) {
        touch('./temp/'.$name);
    } else {
        touch($name);
    }
}

好:

function createFile(string $name): void
{
    touch($name);
}

function createTempFile(string $name): void
{
    touch('./temp/'.$name);
}
 

封装条件语句

坏:

if ($article->state === 'published') {
    // ...
}

好:

if ($article->isPublished()) {
    // ...
}
 

避免用反义条件判断

坏:

function isDOMNodeNotPresent(\DOMNode $node): bool
{
    // ...
}

if (!isDOMNodeNotPresent($node))
{
    // ...
}

好:

function isDOMNodePresent(\DOMNode $node): bool
{
    // ...
}

if (isDOMNodePresent($node)) {
    // ...
}
 

避免条件判断

这看起来像一个不可能的任务。但是当有很多 if 语句的类和函数时,函数做了不止一件事。

坏:

class Airplane
{
    // ...

    public function getCruisingAltitude(): int
    {
        switch($this->type) {
            case '777':
                return $this->getMaxAltitude() - $this->getPassengerCount();
            case 'Air Force One':
                return $this->getMaxAltitude();
            case 'Cessna':
                return $this->getMaxAltitude() - $this->getFuelExpenditure();
        }
    }
}

好:

interface Airplane
{
    public function getCruisingAltitude(): int;
}

class Boeing777 implements Airplane
{
    public function getCruisingAltitude(): int
    {
        return $this->getMaxAltitude() - $this->getPassengerCount();
    }
}

class AirForceOne implements Airplane
{
    public function getCruisingAltitude(): int
    {
        return $this->getMaxAltitude();
    }
}

class Cessna implements Airplane
{
    public function getCruisingAltitude(): int
    {
        return $this->getMaxAltitude() - $this->getFuelExpenditure();
    }
}
 

移除僵尸代码

僵尸代码和重复代码一样坏。没有理由保留在你的代码库中。如果从来没被调用过,就删掉!因为还在代码版本库里,很安全。

坏:

function oldRequestModule(string $url): void
{
    // ...
}

function newRequestModule(string $url): void
{
    // ...
}

$request = newRequestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');

好:

function requestModule(string $url): void
{
    // ...
}

$request = requestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');

别写重复代码(DRY)

试着去遵循 DRY 原则。

尽你最大的努力去避免复制代码,它是一种非常糟糕的行为,复制代码通常意味着当需要变更一些逻辑时,修改不止一处。

坏:

function showDeveloperList(array $developers): void
{
    foreach($developers as $developer) {
        $expectedSalary = $developer->calculateExpectedSalary();
        $experience = $developer->getExperience();
        $githubLink = $developer->getGithubLink();
        $data = [
            $expectedSalary,
            $experience,
            $githubLink
        ];

        render($data);
    }
}

function showManagerList(array $managers): void
{
    foreach($managers as $manager) {
        $expectedSalary = $manager->calculateExpectedSalary();
        $experience = $manager->getExperience();
        $githubLink = $manager->getGithubLink();
        $data = [
            $expectedSalary,
            $experience,
            $githubLink
        ];

        render($data);
    }
}

好:

function showList(array $employees): void
{
    foreach($employees as $employee) {
        $expectedSalary = $employee->calculateExpectedSalary();
        $experience = $employee->getExperience();
        $githubLink = $employee->getGithubLink();
        $data = [
            $expectedSalary,
            $experience,
            $githubLink
        ];

        render($data);
    }
}

极好(最好让代码紧凑一点):

function showList(array $employees): void
{
    foreach($employees as $employee) {
        render([
            $employee->calculateExpectedSalary(),
            $employee->getExperience(),
            $employee->getGithubLink()
        ]);
    }
}
Name:
<提交>