2.1. PHP基础

bzfshop 采用 PHP 5.3 之后的语法做开发,程序完全是 OOP 的,和一些老的开源程序(比如 ecshop, discuz …)差别很大,如果你之前只是对 ecshop 那种动不动就几千行代码一个PHP文件熟悉,那么你对 bzfshop 会感到陌生。PHP 5.2 已经是很老的东西,放弃那些过时的语法,拥抱 PHP 5.3 是最好的选择。这章是为 你从 ecshop 之类转过来做基础知识的准备,了解PHP的新语法,熟悉它并且拥抱它。

2.1.1. 命名空间

PHP 5.3 引入了 namespace ,这对我们组织代码、防止命名冲突提供了极大的便利。bzfshop 所有的代码都是按照命名空间进行组织,了解命名空间你就能更好的看懂代码。

// 传统的 PHP 函数声明 为了避免 命名冲突 都是用了一连串的前缀,这种结果就是名字很长很难看,改名字很麻烦

function bzf_service_user_do_login(...){
        ...
}



// 采用命名空间的代码,函数名简短了很多

namespace Bzf\Service ;

class User {
        public function doLogin(...){
                ...
        }
}



// 使用命名空间的代码,用 use语句 引入对应的命名空间然后就可以使用了

use Bzf\Service\User;

$userService = new User();
$userService->doLogin();

命名空间让代码更加简短清晰。我们可以把代码根据命名空间进行模块化分类,代码放到应该属于的模块组件中,根据名字就能看出代码的功能。bzfshop 的功能模块请参见 protected 目录,从目录名你应该就能猜出下面的代码是做什么的。

[提示]

代码按照模块划分,每个 PHP 文件都包含尽量少的功能,不同的功能应该放到不同的模块里面。bzfshop 每个 PHP 文件尽量不超过 500 行,绝对不出现一个 PHP 文件有几千行代码的情况。

2.1.2. AUTOLOAD

PHP 的 autoload 是配合 Class 来一起使用的。当用户 $instance = new MyClass(); ,如果 MyClass 没有被 require(…/path/MyClass.php), PHP 系统会自动调用一个 __autoload 方法去找到这个 PHP 文件,并且自动做 require(…)。所以,正确的 命名空间+目录结构 可以让你几乎不用做任何的 require(…) 操作了。

传统的PHP代码例子:

// 传统 PHP 代码需要写一堆的 require 方法来引入自己使用的模块

require(ROOT_PATH . 'includes/inc_constant.php');
require(ROOT_PATH . 'includes/cls_ecshop.php');
require(ROOT_PATH . 'includes/cls_error.php');
require(ROOT_PATH . 'includes/lib_time.php');
require(ROOT_PATH . 'includes/lib_base.php');
require(ROOT_PATH . 'includes/lib_common.php');
require(ROOT_PATH . 'includes/lib_main.php');
require(ROOT_PATH . 'includes/lib_insert.php');
require(ROOT_PATH . 'includes/lib_goods.php');
require(ROOT_PATH . 'includes/lib_article.php');

使用 AUTOLOAD 机制之后的代码(不需要写死文件名):

use Core\Service\Goods\Goods as GoodsBasicService;

// 查询商品信息
$goodsBasicService = new GoodsBasicService();
$goodsInfo         = $goodsBasicService->loadGoodsById($goods_id);

autoload 和 namespace 的结合。autoload 需要某种方式找到代码文件,比如上面的 Core\Service\Goods\Goods 我们怎么找到这个文件呢? 这就需要一种 namespace → 文件路径 的对应规则。bzfshop 对应规则很简单 "namespace按照目录结构存放" ,所以这个文件会存在于 protected/Core/Service/Goods/Goods.php。只要你的文件放在正确的目录路径下 autoload 就能够自动找到并且加载它,你再也不需要自己调用 require(…) 来引入文件了。

[提示]

bzfshop 中 namespace 和 目录路径一一对应,autoload 可以自动加载代码。

2.1.3. GOTO语句

PHP 5.3 正式加入了对 goto 语句的支持,bzfshop 大量使用 goto 语句来组织我们的代码,让整个控制逻辑更清晰。

层层嵌套的 if—else 是 bug 之源

if—else 是用得非常普遍的逻辑跳转语句,但是如果逻辑一旦复杂之后 if—else 就会写得非常难看,如下面代码:

// 一层一层嵌套的 if--else,几十行甚至几百行的代码,你自己能分清哪个 if 和 哪个 else 对应吗?

if($cond1){
        if($cond2){
                if($cond3){
                }else{
                }
        }else{
                if($cond4){
                }else{
                        ...
                }
        }
}else{
        ...
}

if—else 语句用得多了,自己首先就混乱了,连哪个 if 和哪个 else 对应都看不清楚了,你自己能理清楚这其间的逻辑吗?所以,if—else 不可避免需要用,但是一定要尽量少用,尤其是一层一层嵌套的使用那绝对是 bug 之源。

复杂 if—else 出口太多

在写程序的时候我们经常遇到 "如果条件不满足,就退出" 的情况,一旦 if—else 复杂之后,我们经常会发现 "退出的时候忘记放回某些值了",比如下面代码:

if(!$cond1){
        return $value1; // 出口1
}

if(!$cond2){
        return $value2; // 出口2
}

// 出口3,这里忘记 return 返回值了(常见 bug 来源)

GOTO 之美,单一入口,单一出口

GOTO 能够有效的组织程序逻辑,保证我们的程序是 "单一入口,单一出口" 的,避免了 多出口 引起的 "忘记返回值" bug。看下面的代码:

// <- 程序 唯一 入口

$value = 'default value';

if(!$cond1){
        goto out_fail; // 错误,退出
}

if($cond2){
        $value = 'value 2';
        goto out; // 正常,退出
}

if(!$cond3){
        goto out_fail; // 错误,退出
}

$value = 'other value'; // 设置返回值,然后正常退出

out:
        return $value; // 程序正常退出 -> 唯一出口

out_fail:
        return null; // 程序异常退出 -> 唯一出口

程序运行逻辑无非一个正确,一个错误。正确的时候返回值,错误的时候设置错误信息,返回一个缺省值(比如 null)。采用 goto 来组织代码,确保 正确的时候从 out 出去,错误的时候从 out_fail 出去,所有的返回值操作都在这两个出口做,程序内部逻辑只跳转到这 2 个出口,这样代码逻辑就清晰很多,同时也不容易因为 "忘记设置值" 而带来的 bug。

复杂 GOTO,正确分离逻辑

程序正确执行只有一种情况,但是程序错误执行可能就会有多种情况,有些时候这些错误处理会非常的复杂。用 GOTO 来组织这些错误逻辑,能够保证我们不会遗漏任何的错误情况,比如下面代码:

// <- 程序唯一入口

$error = null;
$errorMessage = '';
$value = 'default value';

if(!$cond1){
        // 错误 1 只是简单的返回 null 就可以了
        goto out_fail;
}


if(!$cond2){
        // 错误 2 ,需要显示一个出错消息提示
        $errorMessage = 'cond2 error';
        goto out_set_error_message;
}

if(!$cond3){
        // 错误 3, 需要报告一个严重错误
        $error = 'code -1';
        $errorMessage = 'cond3 error';
        goto out_report_error;
}

$value = 'final value';

out:
        return $value;  // 程序正确 -> 唯一出口

// 从这里开始统一进行出错处理

out_report_error:
        $this->reportError($error);

out_set_error_message:
        $this->addFlashMessage($errorMessage);

out_fail:
        return null; // 程序错误 -> 唯一出口
[注意]

if—else 是 bug 之源需要慎用,请用好 GOTO 去组织你的代码

2.1.4. 其它语法

其它更多的 PHP 语法(比如 static override 之类)请你自己查询 PHP 的手册,自己学习了。