file_exist()和is_file()哪个性能更优?

今天浏览项目代码,在项目自动加载注册的路由方法发现前辈判断拼接的控制器文件是否存在时,用的是file_exists($file),而我个人风格更偏向于is_file(),为了弄清楚两个函数哪个性能更优,查阅了官方手册和一些博客文章。

PHP手册描述如下:

file_exists检查文件或目录是否存在

is_file判断给定文件名是否为一个正常的文件

由此我们知道,在功能上file_exists=is_file+is_dir,相当于后两者的集合,既可以判断文件是否存在,又可判断目录是否存在,那么这两个都可以判断文件是否存在的函数其性能哪个占优呢?

写程序验证一下(程序见附件):

分别执行1000次,记录所需时间。

文件存在(当前目录)
is_file:0.4570ms
file_exists:2.0640ms

文件存在(绝对路径3层/www/hx/a/)
is_file:0.4909ms
file_exists:3.3500ms

文件存在(绝对路径5层/www/hx/a/b/c/)
is_file:0.4961ms
file_exists:4.2100ms

文件不存在(当前目录)
is_file:2.0170ms
file_exists:1.9848ms

文件不存在(绝对路径5层/www/hx/a/b/c/)
is_file:4.1909ms
file_exists:4.1502ms

目录存在
file_exists:2.9271ms
is_dir:0.4601ms
目录不存在
file_exists:2.9719ms
is_dir:2.9359ms

is_file($file)
file_exists($file)
当$file是目录时,is_file返回false,file_exists返回true

文件存在的情况下,is_file比file_exists要快得多;
要检测文件所在的目录越深,速度差越多,但至少快4倍。

文件不存在的情况下,is_file比file_exists要慢一点点,但可以忽略不计。

目录存在的情况下,is_dir比file_exists要快得多;
目录不存在的情况下,is_dir比file_exists要慢一点点,但可以忽略不计。

结论:
如果要判断文件是否存在,用函数 is_file(),
如果要判断目录是否存在,用函数 is_dir(),
好像没地方需要用file_exists了,不确定传入的参数是文件还是目录的时候用。

—————————————-测试代码————————————————-

function runtime($t1){
return number_format((microtime(true) – $t1)*1000, 4).’ms’;
}
$times = 1000;

$t1 = microtime(true);
for($i=0;$i<$times;$i++){
is_file(‘/www/hx/www.9enjoy.com/config.php’);
}

echo ‘<br>is_file:’.runtime($t1);

$t2 = microtime(true);
for($i=0;$i<$times;$i++){
file_exists(‘/www/hx/www.9enjoy.com/config.php’);
}
echo ‘<br>file_exists:’.runtime($t2);

/*
$t3 = microtime(true);
for($i=0;$i<$times;$i++){
is_dir(‘/www/hx/www.9enjoy.com/’);
}
echo ‘<br>is_dir:’.runtime($t3);
*/

in_array()踩坑记录

前几天写api,前端发过来的参数约定是数字1-3,按照开发文档,我写了如下代码,

if (!in_array($param, range(0, 3))) {
    $vo['error_code'] = 101;
    return $vo;
}

后来自己单元测试,发现发送模拟参数’1abc’和’abc’,居然没返回错误码(报错),经查阅PHP手册,得知该函数还有第三个参数,是否进行参数类型比较,默认为FALSE,这就导致字符串’1abc’在和整型int比较时,由于PHP是弱类型语言,会发生隐式类型转换,所以这里’1abc’转换为’1’,同理‘abc’转换为字符串类型’0’,从而返回true;

要避免这个坑,有以下两个方法

1.自行进行强制类型验证

if (!is_int($param) || !in_array($param, range(0, 3))) {
    $vo['error_code'] = 101;
    return $vo;
}

2.填充in_array()第三个参数为True,函数内部则会进一步检查类型是否相同

if (!in_array($param, range(1, 3),True)) {
    $vo['error_code'] = 101;
    return $vo;
}

 

PHP call_user_func_array 用法解释

主要用在函数名不确定,参数不确定的情况下,它体现了一种通过传递不同参数来调用不同方法的思想

1.直接调用方法

function say($word){
echo $word;
}
call_user_func_array(‘say’,array(‘Hello PHP’));
/*第一个参数为要调用的方法名,字符串形式,第二个参数为要传给方法的参数,数组形式*/
/*
*这如果say()方法不存在,通过call_user_func_array调用后只会返回false,
*而如果采用直接调用的形式:say(),则会直接报错
*/
2.调用对象中的方法

class S{
public function __construct(){

}
public static function say1($word){
echo $word;
}
public function say($word){
echo $word;
}
}
/*使用方式一:无需实例化调用类的静态方法*/
call_user_func_array(array(‘S’,’say1′),array(‘Hello PHP’));

/*使用方式二:不实例化调用类的非静态方法*/
call_user_func_array(array(‘S’,’say2′),array(‘Hello PHP’));
/*这里不会对自动实例化类,如果调用的方法中有$this语句,会报错,一般不这么用*/

/*使用方式三:实例化后调用类的非静态方法*/
$s = new S();

call_user_func_array(array($s,’say2′),array(‘Hello PHP’));
call_user_func_array(array(&$s,’say2′),array(‘Hello PHP’));/*&这个符号有或没有貌似没差别*/
call_user_func_array(array(new S(),’say2′),array(‘Hello PHP’));

[转载]缓存系列文章(三)缓存常用更新策略对比(一致性)。

转载出处:http://carlosfu.iteye.com/blog/2269678

 一、缓存的几种更新策略

 

从下面的表格看,缓存的更新策略大致分为三种,本文将从一致性和维护成本两个方面对于三种缓存更新策略进行简要说明,因为这些东西比较理论和抽象,如哪里说得不对,欢迎拍砖。

 

注:

(1) 一致性:缓存和真实数据源(例如mysql, hbase, elasticsearch等等)是否存在一段时间数据的不一致。

(2) 维护成本: 开发人员的开发和维护成本。

策略 一致性 维护成本
LRU/LIRS/FIFO算法剔除 最差
超时剔除 较差 较低
主动更新

 

二、LRU/LFU/FIFO算法剔除

 

1. 使用场景:

通常用于缓存使用量超过了预设的最大值时候(缓存空间不够),如何对现有的数据进行清理。例如FIFO会把最新进入缓存的数据清理出去, LRU会把最近最少使用的数据清理掉。

例如:Memcache使用的是LRU,具体Memcache如何实现的,这里就不在赘述了,网上资料多的是。

例如:Redis使用maxmemory-policy这个配置作为内存最大值后对于数据的更新策略。

配置名 含义 默认值
maxmemory 最大可用内存 不使用该配置,也就对内存使用无限制
maxmemory-policy 内存不够时,淘汰策略 volatile-lru
  • volatile-lru -> 用lru算法删除过期的键值
  • allkeys-lru -> 用lru算法删除所有键值
  • volatile-random -> 随机删除过期的键值
  • allkeys-random -> 随机删除任何键值
  • volatile-ttl -> 删除最近要到期的键值
  • noeviction -> 不删除键,只返回一个错误

 

2. 常用算法:

这里不再赘述,常用的算法有如下几种:

FIFO[first in first out]

LFU[Less Frequently Used]

LRU[Least Recently used]

 

 

3. 一致性

可以想象,要清理哪些数据,不是由开发者决定(只能决定大致方向:策略算法),数据的一致性是最差的。

 

4. 维护成本

这些算法不需要开发者自己来实现,通常只需要配置最大maxmemory和对应的策略即可。

开发者只需要有这个东西,知道是什么意思,选择自己需要的算法,算法的实现是由缓存服务器实现的。

 

 

三、超时剔除

 

1. 使用场景:

   就是我们通常做的缓存数据过期时间设置,例如redis和memcache都提供了expire这样的API,来设置K-V的过期时间。

一般来说业务可以容忍一段时间内(例如一个小时),缓存数据和真实数据(例如:mysql, hbase等等)数据不一致(一般来说,缓存可以提高访问速度降低后端负载),那么我们可以对一个数据设置一定时间的过期时间,在数据过期后,再从真实数据源获取数据,重新放到缓存中,继续设置过期时间。

例如: 一个视频的描述信息,我们可以容忍一个小时内数据不一致,但是涉及到钱的方面,如果不一致可想而知。

   

2. 一致性:

    一段时间内(取决于过期时间)存在数据一致性问题,即缓存数据和真实数据源数据不一致。

 

3. 维护成本

      用户的维护成本不是很高,只需要设置expire过期时间即可(前提是你的业务允许这段时间可能发生的数据不一致)。

 

四、主动更新

 

1. 使用背景:

   业务对于数据的一致性要求很高,需要在真实数据更新后,立即更新缓存数据。

具体做法:例如可以利用消息系统或者其他方式(比如数据库触发器,或者其他数据源的listener机制来完成)通知缓存更新。

 

2.  一致性:

   可以想象一致性最高(几乎接近强一致),但是有个问题:如果主动更新发生了问题,那么这条数据很可能很长时间不会更新了(所以可以结合超时剔除一起使用,下面最佳实践会说到)

 

3. 维护成本:

相当高,用户需要自己来完成更新(需要一定量的代码,从某种程度上加大了系统的复杂性),需要自己检查数据是否真的更新了之类的工作。

 

五、最佳实践

    其实最佳实践就是组合使用:

1. 一般来说我们都需要配置超过最大缓存后的更新策略(例如:LRU)以及最大内存,这样可以保证系统可以继续运行(例如redis可能存在OOM问题)(极端情况下除外,数据一致性要求极高)。

2. 一般来说我们需要把超时剔除和主动更新组合使用,那样即使主动更新出了问题,也能保证过期时间后,缓存就被清除了(不至于永远都是脏数据)。

[转载]缓存使用与设计系列文章(二)是否真的需要缓存?

转载出处:http://carlosfu.iteye.com/blog/2269678

一、缓存的成本和收益是什么:

 

既然要讨论是否真的需要缓存这个问题,就要知道缓存带来的成本与收益(好处、坏处)是什么?

收益 成本
缓存 + 后端存储(资源) 1. 加速读写

2. 降低后端负载

1. 数据不一致性

2. 代码维护成本

3. 架构复杂度

 

上面的表格应该清楚的表达了使用缓存后的收益和成本分别是什么。下面将进行详细的解析

 

二、缓存成本与收益详解:

 

1. 收益是很明显的,通常来说一个设计还不错的缓存系统,能够帮助你的业务实现加速读写,同时帮助降低了后端负载。

(1) 加速读写:通常来说加速是明显的,因为缓存通常都是全内存的系统,而后端(可能是mysql、甚至是别人的HTTP, RPC接口)都有速度慢和抗压能力差的特性,通过缓存的使用可以有效的提高用户的访问速度同时优化了用户的体验。

(2) 降低后端负载:通过缓存的添加,如果程序没有什么问题,在命中率还可以的情况下,可以帮助后端减少访问量和复杂计算(join、或者无法在优化的sql等),在很大程度降低了后端的负载。

2. 成本:

(1) 数据不一致性:无论你的设计做的多么好,缓存数据与权威数据源(可以理解成真实或者后端数据源)一定存在着一定时间窗口的数据不一致性,这个时间窗口的大小可大可小,具体多大还要看一下你的业务允许多大时间窗口的不一致性。

(2) 代码维护成本:加入缓存后,代码就会在原数据源基础上加入缓存的相关代码,例如原来只是一些sql, 现在要加入k-v缓存,必然增加了代码的维护成本。

(3) 架构复杂度:加入缓存后,例如加入了redis-cluster,一般来说缓存不会像Mysql有专门的DBA,很有可能没有专职的管理人员,所以也增加了架构的复杂度和维护成本。

 

三、如何选择?

 

如果当前系统的访问速度和访问量能够满足现有的要求,就不必增加缓存,其实像mysql并没有那么差,一台运行良好的Mysql,扛个QPS=1000没什么问题。

如果要加入选择了缓存,一定要能给出足够的理由,不是为了简单的show技术和想当然,最好的方法就是用数据说话:加速比有多少、后端负载降低了多少。

 

四、什么样的场景需要缓存?

 

在公司里,据我观察,无论怎么更新架构,使用各种新技术,但是80%的项目还是离不开SQL的,下面我们以SQL作为后端数据源、以Redis作为缓存层,说一下哪些场景是需要缓存的。

1、复杂开销大的计算、降低后端负载

以Mysql为例子,一些复杂的操作或者计算(例如大量联表操作、一些分组计算),如果不加

缓存,大量流量将在这些复杂计算的执行。

 

2. 加速请求响应

即使单条后端数据足够快(例如select * from table where id=?),那么依然可以利用redis/memcache将这些操作进行merge做优化(例如:cache(select * from table where id in(id1,id10….idK))),从而优化整个IO链的相应时间。

 

 

 

附图一张:

[转载]缓存使用与设计系列文章(一)缓存的一些基本常识。

 一、基本概念

 

1. Cache(缓存): 从cpu的一级和二级缓存、Internet的DNS、到浏览器缓存都可以看做是一种缓存。

维基百科: 写道
a store of things that will be required in the future, and can be retrieved rapidly.
(存贮数据(使用频繁的数据)的临时地方,因为取原始数据的代价太大了,所以我可以取得快一些)


 

2. Cache hit(缓存命中)(下图左)

When a data element is requested from cache and the elements exists for the given key.

3. Cahe miss(缓存未命中): 与Cache hit相反(下图右)

4. 缓存算法:缓存容量超过预设,如何踢掉“无用”的数据。

例如:LRU(Least Recently Used) FIFO(First Input First Output)Least Frequently Used(LFU) 等等

5. System-of-Record(真实数据源): 例如关系型数据库、其他持久性系统等等。

也有英文书叫做authority data(权威数据)

 

6. serialization-and-deserialization(序列化与反序列化):可以参考:序列化与反序列化(美团工程师写的,非常棒的文章)

后面也有单独文章去分析。


 

6. Scale Up (垂直扩容) 和 Scale out (水平扩容)

 

驴拉车,通常不是把一头驴养壮(有极限),而通常是一群驴去拉(当然每个个体也不能太差)。

 

 

服务器也是一样的道理,至少互联网是这样:

7. Write-through 和 write-behind

 

8.阿姆而达定律:用于计算缓存加速比

 

 

二、缓存的种类或者类型

 

1. LocalCache(独立式): 例如Ehcache、BigMemory Go

(1) 缓存和应用在一个JVM中。

(2) 缓存间是不通信的,独立的。

(3) 弱一致性。

 

2. Standalone(单机):

(1) 缓存和应用是独立部署的。

(2) 缓存可以是单台。(例如memcache/redis单机等等)

(3) 强一致性

(4) 无高可用、无分布式。

(5) 跨进程、跨网络

 

3. Distributed(分布式):例如Redis-Cluster, memcache集群等等

(1) 缓存和应用是独立部署的。

(2) 多个实例。(例如memcache/redis等等)

(3) 强一致性或者最终一致性

(4) 支持Scale Out、高可用。

(5) 跨进程、跨网络

 

4. Replicated(复制式): 缓存数据时同时存放在多个应用节点的,数据复制和失效的事件以同步或者异步的形式在各个集群节点间传播。(也是弱一致性)

这种用的不太多。

 

 

三、数据层访问速度:(作为开发人员要记住这些量级)

 

PHP合并数组操作“”+”与array_merge的区别

这个也算是自己在编码中碰到的一个坑

1、键名为数字(数字型字符串)时,array_merge()不会覆盖掉原来的值,但“+”合并数组则会把最先出现的值作为最终结果返回,而把后面的数组拥有相同键名的那些值“抛弃”掉

$arr1 = array('a','b');
$arr2 = array('c', 'd');
var_dump($arr1+$arr2);
//输出
array(2) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
}
var_dump(array_merge($arr1, $arr2));
//输出
array(4) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(1) "c"
[3]=>
string(1) "d"
}

2、键名为字符(非数字型字符)时,+仍然把最先出现的值作为最终结果返回,而把后面的数组拥有相同键名的那些值“抛弃”掉,但array_merge()此时会覆盖掉前面相同键名的值
$arr1 = array(0 => 'a', 'ab' => 'b');
$arr2 = array('0' => 'c', 'ab' => 'd');
var_dump($arr1+$arr2);
//输出
array(2) {
[0]=>
string(1) "a"
["ab"]=>
string(1) "b"
}
var_dump(array_merge($arr1, $arr2));
array(3) {
[0]=>
string(1) "a"
["ab"]=>
string(1) "d"
[1]=>
string(1) "c"
}


 

 

[转载]PHP反射(1)

PHP反射

注:文章转载自http://www.laravel-vue.xyz

反射介绍

PHP 反射机制,对类、接口、函数、方法和扩展进行反向工程的能力。
分析类,接口,函数和方法的内部结构,方法和函数的参数,以及类的属性和方法。

反射中常用的几个类:

  • ReflectionClass 解析类
  • ReflectionProperty 类的属性的相关信息
  • ReflectionMethod 类方法的有关信息
  • ReflectionParameter 取回了函数或方法参数的相关信息
  • ReflectionFunction 一个函数的相关信息

分析类:

class Student
{
    public $id;
    
    public $name;

    const MAX_AGE = 200;

    public static $likes = [];

    public function __construct($id, $name = 'li')
    {
        $this->id = $id;
        $this->name = $name;
    }

    public function study()
    {
        echo 'learning...';
    }

    private function _foo()
    {
        echo 'foo';
    }

    protected function bar($to, $from = 'zh')
    {
        echo 'bar';
    }
}

 

ReflectionClass

$ref = new ReflectionClass('Student');

// 判断类是否可实例化
if ($ref->isInstantiable()) {
    echo '可实例化';
}

// 获取类构造函数
// 有返回 ReflectionMethod 对象,没有返回 NULL
$constructor = $ref->getConstructor();
print_r($constructor);



// 获取某个属性
if ($ref->hasProperty('id')) {
    $attr = $ref->getProperty('id');
    print_r($attr);
}

// 获取属性列表
$attributes = $ref->getProperties();
foreach ($attributes as $row) {
    // 这里的 $row 为 ReflectionProperty 的实例
    echo $row->getName() , "\n";
}

// 获取静态属性,返回数组
$static = $ref->getStaticProperties();
print_r($static);

// 获取某个常量
if ($ref->hasConstant('MAX_AGE')) {
    $const = $ref->getConstant('MAX_AGE');
    echo $const;
}

// 获取常量,返回数组
$constants = $ref->getConstants();
print_r($constants);

// 获取某个方法
if ($ref->hasMethod('bar')) {
    $method = $ref->getMethod('bar');
    print_r($method);
}

// 获取方法列表
$methods = $ref->getMethods();
foreach ($methods as $key => $value) {
    // 这里的 $row 为 ReflectionMethod 的实例
    echo $value->getName() . "\n";
}

ReflectionProperty

if ($ref->hasProperty('name')) {
    $attr = $ref->getProperty('name');

    // 属性名称
    echo $attr->getName();

    // 类定义时属性为真,运行时添加的属性为假
    var_dump($attr->isDefault());

    // 判断属性访问权限
    var_dump($attr->isPrivate());
    var_dump($attr->isProtected());
    var_dump($attr->isPublic());
    // 判断属性是否为静态
    var_dump($attr->isStatic());
}

RefleactionMethod & ReflectionParameter

if ($ref->hasMethod('bar')) {
    $method = $ref->getMethod('bar');
    echo $method->getName();

    // isAbstract  判断是否是抽象方法
    //isConstructor  判断是否是构造方法
    //isDestructor  判断是否是析构方法
    //isFinal  判断是否是 final 描述的方法
    //isPrivate  判断是否是 private 描述的方法
    //isProtected 判断是否是 protected 描述的方法
    //isPublic 判断是否是 public 描述的方法
    //isStatic 判断是否是 static 描述的方法
    
    // 获取参数列表
    $parameters = $method->getParameters();
    foreach ($parameters as $row) {
        // 这里的 $row 为 ReflectionParameter 实例
        echo $row->getName();
        echo $row->getClass();

        // 检查变量是否有默认值
        if ($row->isDefaultValueAvailable()) {
            echo $row->getDefaultValue();
        }

        // 获取变量类型
        if ($row->hasType()) {
            echo $row->getType();
        }
        
    }
}

ReflectionFunction & ReflectionParameter

$fun = new ReflectionFunction('demo');
echo $fun->getName();
$parameters = $fun->getParameters();
foreach ($parameters as $row) {
    // 这里的 $row 为 ReflectionParameter 实例
    echo $row->getName();
    echo $row->getClass();

    // 检查变量是否有默认值
    if ($row->isDefaultValueAvailable()) {
        echo $row->getDefaultValue();
    }

    // 获取变量类型
    if ($row->hasType()) {
        echo $row->getType();
    }
}

综合实例

下面用一个简单的示例:如果用反射实例化类。
file: Student.php

class Student
{

    public $id;
    
    public $name;

    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }

    public function study()
    {
        echo 'learning.....';
    }

}

一般情况下,实例化类的时候,直接使用 new,但是我们现在不用这种方法,我们使用反射来实现。
file: index.php

require 'student.php';

function make($class, $vars = [])
{
    $ref = new ReflectionClass($class);

    // 检查类 Student 是否可实例化
    if ($ref->isInstantiable()) {
        // 获取构造函数
        $constructor = $ref->getConstructor();
        // 没有构造函数的话,直接实例化
        if (is_null($constructor)) {
            return new $class;
        }
        // 获取构造函数参数
        $params = $constructor->getParameters();
        $resolveParams = [];
        foreach ($params as $key => $value) {
            $name = $value->getName();
            if (isset($vars[$name])) {
                // 判断如果是传递的参数,直接使用传递参数
                $resolveParams[] = $vars[$name];
            } else {
                // 没有传递参数的话,检查是否有默认值,没有默认值的话,按照类名进行递归解析
                $default = $value->isDefaultValueAvailable() ? $value->getDefaultValue() : null;
                if (is_null($default)) {
                    if ($value->getClass()) {
                        $resolveParams[] = make($value->getClass()->getName(), $vars);
                    } else {
                        throw new Exception("{$name} 没有传值且没有默认值");
                    }
                } else {
                    $resolveParams[] = $default;
                }
            }
        }

        // 根据参数实例化
        return $ref->newInstanceArgs($resolveParams);
    } else {
        throw new Exception("类 {$class} 不存在!");
    }
}

## 情况一
try {
    $stu = make('Student', ['id' => 1]);
    print_r($stu);
    $stu->study();
} catch (Exception $e) {
    echo $e->getMessage();
}

## 情况二
try {
    $stu = make('Student', ['id' => 1, 'name' => 'li']);
    print_r($stu);
    $stu->study();
} catch (Exception $e) {
    echo $e->getMessage();
}

上面两种情况很明显第一种,缺少参数 name,无法实例化成功,第二种情况就可以实例化成功。

那么我们如果将类 Student 的构造函数修改为:

public function __construct($id, $name = 'zhang')
{
    $this->id = $id;
    $this->name = $name;
}

这样设置 name 有默认值的情况下,那么第一种情况也可以实例化成功。

情况三

第三种情况:如果在类的构造函数中有其他类为参数的情况下,那么也可以解析:

public function __construct($id, $name, Study $study)
{
    $this->id = $id;
    $this->name = $name;
    $this->study = $study;
}

那么这种情况下,在分析类的构造函数参数的时候,如果没有传递参数的话,就会递归调用 make 方法处理 Study 类,如果类存在的话,实例化。

file: study.php

// 我们这里不写构造函数,测试下没有构造函数的情况
class Study
{
    public function show()
    {
        echo 'show';
    }
}

将 Student 类的方法 study 修改为:

1
2
3
4
public function study()
{
    $this->name . ' ' . $this->study->show();
}

下面测试:

try {
    $stu = make('Student', ['id' => 1]);
    print_r($stu);
    $stu->study();
} catch (Exception $e) {
    echo $e->getMessage();
}

PHP 的反射是一个很用的功能,我这里只能很简单的讲解了一点皮毛,详细介绍和用法可参看 官方手册

[转载]php 整洁之道

clean-code-php

Table of Contents

  1. 介绍
  2. 变量
  3. 函数

介绍

本文由 yangweijie 翻译自clen php code,团建用,欢迎大家指正。

摘录自 Robert C. Martin的Clean Code 书中的软件工程师的原则 ,适用于PHP。 这不是风格指南。 这是一个关于开发可读、可复用并且可重构的PHP软件指南。

并不是这里所有的原则都得遵循,甚至很少的能被普遍接受。 这些虽然只是指导,但是都是Clean Code作者多年总结出来的。

Inspired from clean-code-javascript

变量

使用有意义且可拼写的变量名

Bad:

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

Good:

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

⬆ 返回顶部

同种类型的变量使用相同词汇

Bad:

getUserInfo();
getClientData();
getCustomerRecord();

Good:

getUser();

⬆ 返回顶部

使用易检索的名称

我们会读比写要多的代码。通过是命名易搜索,让我们写出可读性和易搜索代码很重要。

Bad:

// What the heck is 86400 for?
addExpireAt(86400);

Good:

// Declare them as capitalized `const` globals.
interface DateGlobal {
  const SECONDS_IN_A_DAY = 86400;
}

addExpireAt(DateGlobal::SECONDS_IN_A_DAY);

⬆ 返回顶部

使用解释型变量

Bad:

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

Good:

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

⬆ 返回顶部

避免心理映射

明确比隐性好。

Bad:

$l = ['Austin', 'New York', 'San Francisco'];
foreach($i=0; $i<count($l); $i++) {
  oStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  // 等等`$l` 又代表什么?
  dispatch($l);
}

Good:

$locations = ['Austin', 'New York', 'San Francisco'];
foreach($i=0; $i<count($locations); $i++) {
  $location = $locations[$i];
  
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  dispatch($location);
});

⬆ 返回顶部

不要添加不必要上下文

如果你的class/object 名能告诉你什么,不要把它重复在你变量名里。

Bad:

$car = [
  'carMake'  => 'Honda',
  'carModel' => 'Accord',
  'carColor' => 'Blue',
];

function paintCar(&$car) {
  $car['carColor'] = 'Red';
}

Good:

$car = [
  'make'  => 'Honda',
  'model' => 'Accord',
  'color' => 'Blue',
];

function paintCar(&$car) {
  $car['color'] = 'Red';
}

⬆ 返回顶部

###使用参数默认值代替短路或条件语句。 Bad:

function createMicrobrewery($name = null) {
  $breweryName = $name ?: 'Hipster Brew Co.';
  // ...
}

Good:

function createMicrobrewery($breweryName = 'Hipster Brew Co.') {
  // ...
}

⬆ 返回顶部

函数

函数参数最好少于2个

限制函数参数个数极其重要因为它是你函数测试容易点。有超过3个可选参数参数导致一个爆炸式组合增长,你会有成吨独立参数情形要测试。

无参数是理想情况。1个或2个都可以,最好避免3个。再多旧需要加固了。通常如果你的函数有超过两个参数,说明他多做了一些事。 在参数少的情况里,大多数时候一个高级别对象(数组)作为参数就足够应付。

Bad:

function createMenu($title, $body, $buttonText, $cancellable) {
  // ...
}

Good:

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) {
  // ...
}

⬆ 返回顶部

函数应该只做一件事

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

Bad:

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

Good:

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

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

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

⬆ 返回顶部

函数名应当描述他们所做的事

Bad:

function addToDate($date, $month) {
  // ...
}

$date = new \DateTime();

// It's hard to to tell from the function name what is added
addToDate($date, 1);

Good:

function addMonthToDate($month, $date) {
  // ...
}

$date = new \DateTime();
addMonthToDate(1, $date);

⬆ 返回顶部

函数应当只为一层抽象,当你超过一层抽象时,函数正在做多件事。拆分功能易达到可重用性和易用性。.

Bad:

function parseBetterJSAlternative($code) {
  $regexes = [
    // ...
  ];

  $statements = split(' ', $code);
  $tokens = [];
  foreach($regexes as $regex) {
    foreach($statements as $statement) {
      // ...
    }
  }
  
  $ast = [];
  foreach($tokens as $token) {
     // lex...
  }

  foreach($ast as $node) {
   // parse...
  }
}

Good:

function tokenize($code) {
  $regexes = [
    // ...
  ];

  $statements = split(' ', $code);
  $tokens = [];
  foreach($regexes as $regex) {
    foreach($statements as $statement) {
      $tokens[] = /* ... */;
    });
  });

  return tokens;
}

function lexer($tokens) {
  $ast = [];
  foreach($tokens as $token) {
    $ast[] = /* ... */;
  });

  return ast;
}

function parseBetterJSAlternative($code) {
  $tokens = tokenize($code);
  $ast = lexer($tokens);
  foreach($ast as $node) {
    // parse...
  });
}

⬆ 返回顶部

删除重复的代码

尽你最大的努力来避免重复的代码。重复代码不好,因为它意味着如果你修改一些逻辑,那就有不止一处地方要同步修改了。

想象一下如果你经营着一家餐厅并跟踪它的库存: 你全部的西红柿、洋葱、大蒜、香料等。如果你保留有多个列表,当你服务一个有着西红柿的菜,那么所有记录都得更新。如果你只有一个列表,那么只需要修改一个地方!

经常你容忍重复代码,因为你有两个或更多有共同部分但是少许差异的东西强制你用两个或更多独立的函数来做相同的事。移除重复代码意味着创造一个处理这组不同事物的一个抽象,只需要一个函数/模块/类。

抽象正确非常重要,这也是为什么你应当遵循SOLID原则(奠定Class基础的原则)。坏的抽象可能比重复代码还要糟,因为要小心。在这个前提下,如果你可以抽象好,那就开始做把!不要重复你自己,否则任何你想改变一件事的时候你都发现在即在更新维护多处。

Bad:

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

    render($data);
  }
}

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

    render($data);
  }
}

Good:

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

    render($data);
  }
}

⬆ 返回顶部

通过对象赋值设置默认值

Bad:

$menuConfig = [
  'title'       => null,
  'body'        => 'Bar',
  'buttonText'  => null,
  'cancellable' => true,
];

function createMenu(&$config) {
  $config['title']       = $config['title'] ?: 'Foo';
  $config['body']        = $config['body'] ?: 'Bar';
  $config['buttonText']  = $config['buttonText'] ?: 'Baz';
  $config['cancellable'] = $config['cancellable'] ?: true;
}

createMenu($menuConfig);

Good:

$menuConfig = [
  'title'       => 'Order',
  // User did not include 'body' key
  'buttonText'  => 'Send',
  'cancellable' => true,
];

function createMenu(&$config) {
  $config = array_merge([
    'title'       => 'Foo',
    'body'        => 'Bar',
    'buttonText'  => 'Baz',
    'cancellable' => true,
  ], $config);

  // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu($menuConfig);

⬆ 返回顶部

不要用标志作为函数的参数,标志告诉你的用户函数做很多事了。函数应当只做一件事。 根据布尔值区别的路径来拆分你的复杂函数。

Bad:

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

Good:

function createFile($name) {
  touch(name);
}

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

⬆ 返回顶部

避免副作用

一个函数做了比获取一个值然后返回另外一个值或值们会产生副作用如果。副作用可能是写入一个文件,修改某些全局变量或者偶然的把你全部的钱给了陌生人。

现在,你的确需要在一个程序或者场合里要有副作用,像之前的例子,你也许需要写一个文件。你想要做的是把你做这些的地方集中起来。不要用几个函数和类来写入一个特定的文件。用一个服务来做它,一个只有一个。

重点是避免常见陷阱比如对象间共享无结构的数据,使用可以写入任何的可变数据类型,不集中处理副作用发生的地方。如果你做了这些你就会比大多数程序员快乐。

Bad:

// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
$name = 'Ryan McDermott';

function splitIntoFirstAndLastName() {
  $name = preg_split('/ /', $name);
}

splitIntoFirstAndLastName();

var_dump($name); // ['Ryan', 'McDermott'];

Good:

$name = 'Ryan McDermott';

function splitIntoFirstAndLastName($name) {
  return preg_split('/ /', $name);
}

$name = 'Ryan McDermott';
$newName = splitIntoFirstAndLastName(name);

var_export($name); // 'Ryan McDermott';
var_export($newName); // ['Ryan', 'McDermott'];

⬆ 返回顶部

不要写全局函数

在大多数语言中污染全局变量是一个坏的实践,因为你可能和其他类库冲突并且你api的用户不明白为什么直到他们获得产品的一个异常。让我们看一个例子:如果你想配置一个数组,你可能会写一个全局函数像config(),但是可能和试着做同样事的其他类库冲突。这就是为什么单例设计模式和简单配置会更好的原因。

Bad:

function config() {
  return  [
    'foo': 'bar',
  ]
};

Good:

class Configuration {
  private static $instance;
  private function __construct($configuration) {/* */}
  public static function getInstance() {
     if(self::$instance === null) {
         self::$instance = new Configuration();
     }
     return self::$instance;
 }
 public function get($key) {/* */}
 public function getAll() {/* */}
}

$singleton = Configuration::getInstance();

⬆ 返回顶部

封装条件语句

Bad:

if ($fsm->state === 'fetching' && is_empty($listNode)) {
  // ...
}

Good:

function shouldShowSpinner($fsm, $listNode) {
  return $fsm->state === 'fetching' && is_empty(listNode);
}

if (shouldShowSpinner($fsmInstance, $listNodeInstance)) {
  // ...
}

⬆ 返回顶部

避免消极条件

Bad:

function isDOMNodeNotPresent($node) {
  // ...
}

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

Good:

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

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

⬆ 返回顶部

避免条件声明

这看起来像一个不可能任务。当人们第一次听到这句话是都会这么说。 “没有一个if声明” 答案是你可以使用多态来达到许多case语句里的任务。第二个问题很常见, “那么为什么我要那么做?” 答案是前面我们学过的一个整洁代码原则:一个函数应当只做一件事。当你有类和函数有很多if声明,你自己知道你的函数做了不止一件事。记住,只做一件事。

Bad:

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

Good:

class Airplane {
  // ...
}

class Boeing777 extends Airplane {
  // ...
  public function getCruisingAltitude() {
    return $this->getMaxAltitude() - $this->getPassengerCount();
  }
}

class AirForceOne extends Airplane {
  // ...
  public function getCruisingAltitude() {
    return $this->getMaxAltitude();
  }
}

class Cessna extends Airplane {
  // ...
  public function getCruisingAltitude() {
    return $this->getMaxAltitude() - $this->getFuelExpenditure();
  }
}

⬆ 返回顶部

Avoid 避免类型检查 (part 1)

PHP是弱类型的,这意味着你的函数可以接收任何类型的参数。 有时候你为这自由所痛苦并且在你的函数渐渐尝试类型检查。有很多方法去避免这么做。第一种是考虑API的一致性。

Bad:

function travelToTexas($vehicle) {
  if ($vehicle instanceof Bicycle) {
    $vehicle->peddle($this->currentLocation, new Location('texas'));
  } else if ($vehicle instanceof Car) {
    $vehicle->drive($this->currentLocation, new Location('texas'));
  }
}

Good:

function travelToTexas($vehicle) {
  $vehicle->move($this->currentLocation, new Location('texas'));
}

⬆ 返回顶部

避免类型检查 (part 2)

如果你正使用基本原始值比如字符串、整形和数组,你不能用多态,你仍然感觉需要类型检测,你应当考虑类型声明或者严格模式。 这给你了基于标准PHP语法的静态类型。 手动检查类型的问题是做好了需要好多的废话,好像为了安全就可以不顾损失可读性。保持你的PHP 整洁,写好测试,做好代码回顾。做不到就用PHP严格类型声明和严格模式来确保安全。

Bad:

function combine($val1, $val2) {
  if (is_numeric($val1) && is_numeric(val2)) {
    return val1 + val2;
  }

  throw new \Exception('Must be of type Number');
}

Good:

function combine(int $val1, int $val2) {
  return $val1 + $val2;
}

⬆ 返回顶部

移除僵尸代码

僵尸代码和重复代码一样坏。没有理由保留在你的代码库中。如果从来被调用过,见鬼去!在你的版本库里是如果你仍然需要他的话,因此这么做很安全。

Bad:

function oldRequestModule($url) {
  // ...
}

function newRequestModule($url) {
  // ...
}

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

Good:

function newRequestModule($url) {
  // ...
}

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

⬆ 返回顶部

##有问题反馈 在使用中有任何问题,欢迎反馈给我,可以用以下联系方式跟我交流