标签 php 下的文章

PHP 命名空间(namespace)详解

PHP 的命名空间(namespace)概念是从 5.3.0 开始引入的。在其它程序语言中,命名空间相当于文件夹,是用来对类文件进行分组的。许多 PHP 框架已经全面使用命名空间特性。

命名空间的引入,主要是为了解决类、函数、常量与 PHP 内部或者第三方库之间的名称冲突。

基础

语法主要是声明与使用。

声明:

// file: foo.php
namespace foo;
class Bar {}

使用:

// file: test.php
include 'foo.php';
$bar = new \foo\Bar();
// 使用别名
use foo as first;
$bar2 = new first\Bar();

声明必须放在其它代码之前,declare 关键字除外。

注意,在使用别名时,前面不能加反斜杠(因为反斜杠表示全局命名空间,而别名不是实际存在的命名空间)。但是引入进来的命名空间 foo,在使用时,必须加上正确的限定,确保命名空间的正确解析。在设置了别名后,原名称依然可用。

如果当前代码没有声明命名空间,则为全局命名空间(或者称为根空间)。

如果当前代码声明了命名空间,那么使用不带命名空间的类,会默认以当前命名空间作为限定。如果当前命名空间不存在该类,则会报错。

要使用引用文件中的类,且被引用的文件使用了命名空间,必须带上命名空间前缀。否则,使用的是当前命名空间下的类。

命名空间的名称,可以是多层级的(可称之为子命名空间),如 namespace foo\bar,使用时可以把它当作一个层级一样处理。

关键字 namespace 也可以出现在命名空间的“路径”中,放在“路径”的开头,指当前的命名空间,不管当前文件有没有声明命名空间。也就是说,在不是反斜杠开头的名称前加 namespace\ 总是正确的。

由于命名空间可能层次很多,书写麻烦,可以使用 use 语句设置别名,不加 as 则表示使用命名空间最后一节作为别名。

use My\Full\Classname;
use My\Full\Classname as C;

上一条语句与 use MyFullClassname as Classname 效果一样。下一条语句,是为了进一步简化别名。

注意,use 语句中的命名空间不需要以反斜杠开头,但在开头加上反斜杠也没有问题。如果 as 的别名与当前命名空间下类名相同(当然该同名类一定不会在当前文件中),将优先使用 as 别名对应的类。

导入函数(使用 function 关键字):

// PHP 5.6+
use function My\Full\functionName as func;

导入常量(使用 const 关键字):

// PHP 5.6+
use const My\Full\CONSTANT;

以上导入函数和常量的名称,如果与当前命名空间的名称冲突,可以用 as 取一个别名。

为了简化操作,可以将多个 use 语句写到一行:

use My\Full\Classname as Another, My\Full\NSname;

与其它语言不一样的地方是,同一个命名空间的代码可以定义在不同的文件中。

使用常量 __NAMESPACE__ 可以获得当前代码所在的命名空间。

进阶

一个文件多个命名空间

PHP 也允许在一个文件声明多个命名空间。

<?php
namespace MyProject;

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }

namespace AnotherProject;

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }

它表示,文件中的代码按块划分到 2 个命名空间。一般像这样使用,主要用于将多个文件合并到一个文件:

<?php
namespace MyProject {

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }
}

namespace AnotherProject {

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }
}

namespace { // global code
session_start();
$a = MyProject\connect();
echo MyProject\Connection::start();
}

上面代码中,第三个 namespace 后没有命名空间名称,这表示是全局代码。混合在一个文件中的代码只能这样组织,命名空间的大括号外不能有任何 PHP 代码(declare 语句除外)。

命名空间的层次关系

名词定义:

  • 非限定名称(Unqualified name):名称中不包含命名空间分隔符(反斜杠)的标识符,例如 Bar
  • 限定名称(Qualified name):名称中含有命名空间分隔符(反斜杠)的标识符,例如 fooBar
  • 完全限定名称(Fully qualified name):名称中包含命名空间分隔符,并以命名空间分隔符开始的标识符,例如 fooBar。 namespaceBar 也是一个完全限定名称。

前面两种名称,没有以反斜杠开头,是相当于当前命名空间的名称。可以将 PHP 命名空间与文件系统作一个简单的类比。在文件系统中访问一个文件有三种方式:

  1. 相对文件名形式如foo.txt。它会被解析为 currentdirectory/foo.txt,其中 currentdirectory 表示当前目录。因此如果当前目录是 /home/foo,则该文件名被解析为/home/foo/foo.txt。
  2. 相对路径名形式如subdirectory/foo.txt。它会被解析为 currentdirectory/subdirectory/foo.txt。
  3. 绝对路径名形式如/main/foo.txt。它会被解析为/main/foo.txt。

PHP 命名空间使用同样的原理。例如,类名可以通过三种方式引用:

  1. 非限定名称,即不包含前缀的类名称,例如 $a=new foo(); 或 foo::staticmethod();。如果当前命名空间是 currentnamespace,foo 将被解析为 currentnamespacefoo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,则 foo 会被解析为foo。 注意:如果命名空间中的函数或常量未定义,则该非限定的函数名称或常量名称会被解析为全局函数名称或常量名称。(注意只是函数和常量,不包括类。类的访问行为与此不同,见下文。)
  2. 限定名称,或包含前缀的名称,例如 $a = new subnamespacefoo(); 或 subnamespacefoo::staticmethod();。如果当前的命名空间是 currentnamespace,则 foo 会被解析为 currentnamespacesubnamespacefoo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,foo 会被解析为subnamespacefoo。
  3. 完全限定名称,或包含了全局前缀操作符的名称,例如, $a = new currentnamespacefoo(); 或 currentnamespacefoo::staticmethod();。在这种情况下,foo 总是被解析为代码中的字面名(literal name)currentnamespacefoo。

全局命名空间

反斜杠()表示全局命名空间,使用 PHP 内部函数,始终可以使用反斜杠作为前缀。

访问任意全局类、函数或常量,都可以使用完全限定名称,例如 strlen() 或 Exception 或 INI_ALL。尤其是当命名空间内,存在与全局类、函数、常量相同的名称时,若想使用全局类、函数、常量,则必须加上反斜杠,否则使用的是当前命名空间下的类、函数、常量。

namespace A\B\C;

/* 这个函数是 A\B\C\fopen */
function fopen() { 
     /* ... */
     $f = \fopen(...); // 调用全局的fopen函数
     return $f;
}

在声明命名空间的代码中访问全局类(必须使用反斜杠)

namespace A\B\C;
class Exception extends \Exception {}

$a = new Exception('hi'); // $a 是类 A\B\C\Exception 的一个对象
$b = new \Exception('hi'); // $b 是全局类 Exception 的一个对象

$c = new ArrayObject; // 致命错误, 找不到 A\B\C\ArrayObject 类

但是,对于函数和常量来说,如果当前命名空间中不存在该函数或常量,PHP 会使用全局空间中的函数或常量。

注:本文部分内容转载自官方文档。

参考文档:
PHP 命名空间

PHP 编码规范

PHP 编码规范

当多人协作编码时,一份共同遵守的编码规范必不可少。它有利于阅读和编写代码,既可以提高新成员熟悉代码的效率,也可以提高老成员编码的效率。

如果以下规范与框架规范冲突,则以框架规范为准。

本文档中使用了大量的“能愿动词”,说明如下:

  • 必须:绝对,严格遵循,请照做,无条件遵守;
  • 一定不能:禁令,严令禁止;
  • 应该:强烈建议这样做,但不强求;
  • 不该:强烈不建议这样做,但不强求;
  • 可以:更多的选择。

1. 命名

变量名、类的属性名 必须 使用小驼峰形式(如 $varName)。

常量名中所有字母 必须 大写,单词间 应该 以下划线分隔。

文件夹名所有字母 应该 使用小写字母,单词间 应该 以下划线分隔。

类名 必须 使用大驼峰形式(如 ClassName)。

定义类的文件名 应该 与类名相同,或者包含类名,同样 应该 使用大驼峰形式。

方法名 必须 使用小驼峰形式。

PHP 的关键字 必须 全部小写。

系统常量(true, false, null必须 小写。

2. 基础规范

代码 必须 使用 4 个空格符而不是 Tab 键进行缩进。

每行代码的字符数 应该 控制在 120 个以内。

类和方法的起始花括号({必须 在声明后自成一行,结束花括号(}必须 在代码主体后自成一行。

类的属性和方法 必须 添加访问修饰符(private, protected, public),abstractfinal 必须 写在访问修饰符之前,而 static 必须 写在访问修饰符之后。

<?php

abstract class ClassName
{
    protected static $fooVar;

    abstract protected function baz();

    final public static function barShow($a, $b = null)
    {
        // 方法主体
    }
}

赋值符号(=)的左右 必须 有且只有一个空格。

取反符号(!)后 必须 有一个空格。

纯 PHP 代码文件 应该 省略末尾的结束标签(?>)。

非空行后 不该 有多余的空格。

每行 一定不能 存在多条语句。

文件 应该 以一个空白行作为结束。

字符串 应该 使用单引号。

3. 编码风格

3.1 类、属性和方法

关键词 extendsimplements 必须 写在类名称的同一行。

<?php

class ClassName extends ParentClass implements ImplementName
{
    // 这里面是常量、属性、类方法
}

implements 的继承列表也 可以 分成多行,但每个继承接口名称都 必须 分开独立成行,包括第一个。

<?php

class ClassName extends ParentClass implements
    ImplementName1,
    ImplementName2,
    ImplementName3
{
    // 这里面是常量、属性、类方法
}

每个属性都 必须 添加访问修饰符。
一定不能 使用关键字 var 声明一个属性。
每条语句 一定不能 定义超过一个属性。
不该 使用下划线作为前缀,来区分属性是 protectedprivate

所有方法都 必须 添加访问修饰符。
不该 使用下划线作为前缀,来区分方法是 protectedprivate
方法名称后 一定不能 有空格符,参数左括号后和右括号前 一定不能 有空格。
参数列表中,每个逗号后面 必须 要有一个空格,而逗号前面 一定不能 有空格。
有默认值的参数,必须 放到参数列表的末尾。

<?php

class ClassName
{
    public function foo($arg1, &$arg2, $arg3 = [])
    {
        // 方法主体
    }
}

訪問修飾符 必須 按最小訪問原則。例如只在本類中使用的方法,必須使用 private;當該方法需要被外部訪問時,再改為 public

3.2 控制结构

控制结构的关键字(如 if)后 必须 有一个空格,而调用方法、函数的左括号前 一定不能 有空格。
控制结构的起始花括号 应该 写在声明的同一行,而最后一个结束花括号 必须 在主体后自成一行。
控制结构的左括号后,右括号前 一定不能 有空格。
结构体主体 必须 要有一次缩进。
注意,控制结构的花括号并不总是单独成行。

if 条件语句

<?php

if ($expr1) {
    // if 代码块
} elseif ($expr2) {
    // elseif 代码块
} else {
    // else 代码块
}

应该 使用关键词 elseif 代替所有 else if
主体部分,必须 使用花括号括起来,即使只有一句。

switch 语句

<?php

switch ($expr) {
    case 0:
        echo '第一种情况, 带有 break';
        break;
    case 1:
        echo '第二种情况';
        // no break
    case 2:
    case 3:
    case 4:
        echo '第三种情况, 直接 return';
        return;
    default:
        echo '默认情况';
        break;
}

case 语句 必须 相对 switch 进行一次缩进,而 break 语句以及 case 内的其它语句都 必须 相对 case 进行一次缩进。
如果存在非空的 case 直穿语句,主体中 必须 有类似 // no break 的注释。

while 和 do...while 语句

<?php

while ($expr) {
    // 循环主体
}

do {
    // 循环主体
} while ($expr);

for 语句

<?php

for ($i = 0; $i < 10; $i++) {
    // 循环主体
}

foreach 语句

<?php

foreach ($iterable as $key => $value) {
    // 循环主体
}

try...catch 语句

<?php

try {
    // 主体代码
} catch (FirstExceptionType $e) {
    // 异常处理代码
} catch (OtherExceptionType $e) {
    // 异常处理代码
}

3.3 闭包

关键词 function 后以及关键词 use 的前后都 必须 要有一个空格。
开始花括号 必须 写在声明的同一行,结束花括号 必须 紧跟主体结束的下一行。

<?php

$closureWithArgs = function ($arg1, $arg2) {
    // 主体
};

$closureWithArgsAndVars = function ($arg1, $arg2) use ($var1, $var2) {
    // 主体
};

参数列表以及变量列表 可以 分成多行,这样,包括第一个在内的每个参数或变量都 必须 单独成行,而列表的右括号与闭包的开始花括号 必须 放在同一行。

<?php

$longArgs_longVars = function (
    $longArgument,
    $longerArgument,
    $muchLongerArgument
) use (
    $longVar1,
    $longerVar2,
    $muchLongerVar3
) {
   // 代码主体
};

3.4 命名空间

namespace 声明后 必须 插入一个空白行。
所有 use 必须namespace 后声明。
每条 use 声明语句 必须 只有一个 use 关键词。
use 声明语句块后 必须 要有一个空白行。

<?php
namespace Vendor\Package;

use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;

// 更多的代码...

3.5 注释与空行

单行注释 应该 放在行末,与代码之间 应该 保留 2 个空格,而较长的注释,应该 放在相应代码的上一行;注释符号(//)与注释之间 应该 保留 1 个空格。

较复杂的代码,应该 详细说明。

<?php

/**
 * 算法标题
 *
 * 首先,调用...取得...数据
 * 其次,对...数据进行循环处理,得到...
 * 最后,按...格式输出数据
 */

清楚明了的代码 不该 增加注释:

<?php

$criteria = new CDbCriteria;
// 仅获取id,parentid字段
$criteria->select = 'id,parentid';
// 排除已删除节点
$criteria->addCondition('is_delete=0');

$list = $modelClassName::model()->findAll($criteria);

代码块之间 应该 用一个空行分隔,如类的方法之间使用一个空行分隔。

附0 开发工具推荐

PhpStorm
PHPDesigner
PhpED

附1 Git 使用

提交时简明扼要地描述所变更代码的主要内容
至少形成一个基本逻辑再提交
不相关的修改应该分开提交
不必要的修改不要提交(如调试时多加了个空行,提交时查看变更内容可避免)
开发周期较长(超过一周)的需求应该新建一个分支

提交信息格式:
修改Bug:BF(###): xxxxxxx
完成需求:PR(###): xxxxxxx

参考文档

  1. PSR - PHP标准规范

PHP 运行模式

PHP 主要有四种运行模式

  1. CGI 模式
  2. FastCGI 模式
  3. CLI 模式
  4. Module 模式

CGI 模式

CGI 的全称为 Common Gateway Interface,即通用网关接口。它是一种描述 Web 服务器与应用处理程序之间传输数据格式的协议。

作为 PHP 的一种运行模式,它是比较陈旧的,现在几乎不再使用。每有一个用户请求,都会创建一个子进程,然后处理请求,处理完后结束这个子进程。(也称为 Fork-And-Execute 模式)

根据原理可以看出,这种模式无法处理高并发的场景。同一时间,用户请求增多,会产生大量子进程,导致服务器资源紧张,直到崩溃。

FastCGI 模式

是 CGI 模式的升级版,它不会每个请求都 Fork 一个子进程,而是创建一个或多个常驻的子进程。这些子进程在处理完一个请求后,不会结束掉自己,而是等待或者处理后续的请求。因此也叫常驻型 CGI。这可以显著降低进程创建与进程销毁所耗的资源,以及 CGI 子进程启动后初始化的时间。

PHP-FPM(PHP-FastCGI Process Manager)是 PHP 语言中实现了 FastCGI 协议的进程管理器,它随 PHP 源码一起编译、安装。

通常 Nginx 使用这种模式运行 PHP。

CLI 模式

CLI 的全称为 Command Line Interface,即命令行接口。PHP 默认会安装,通过 php -v 命令可以看到 cli 字样。

php -r <code> 运行 PHP 代码,不需要加开始与结束标记(<? 和 ?>)
php -f <file> 运行 PHP 文件。其中参数 -f 可以省略,即 php 命令后直接输入文件名。

Module 模式

是将 mod_php5 模块集成到 Web Server 软件中,来运行 PHP。

通常 Apache 使用这种模式运行 PHP。需要在 httpd.conf 中配置:

LoadModule php5_module modules/mod_php5.so

当请求 .php 文件时,Apache 就会调用 php5_module 来解析 PHP 脚本。

附加知识

PHP 的运行模式,也可以称为 SAPI,全称为 Server Application Programming Interface, 即服务端应用编程接口。SAPI 为 PHP 提供了一个和外部通信的接口。PHP 通过 SAPI 与其它程序进行数据交换。针对不同的应用场景,PHP 提供了多种不同的 SAPI,如 apache、apache2filter、apache2handler、cli、cgi、embed 、fast-cgi、isapi 等等。

可通过 php_sapi_name() 函数获取 Web 服务器与 PHP 之间的接口类型,也可通过常量 PHP_SAPI 获取,它们返回相同的值。

还可以在 phpinfo() 中查看到使用了哪种 Server API。

Nginx + PHP 设置文件上传大小

你可能会碰到服务器 413 错误,当文件上传到服务器时,返回如下结果:

HTTP/1.1 413 Request Entity Too Large

原因

服务器限制了文件上传大小。默认大小,Nginx 为1M,PHP 为2M。因此超过1M便返回413.

如何修改大小限制

需要同时修改 PHP 和 Nginx 的配置。

PHP

编辑 php.ini 文件,找到对应的位置:

; Maximum size of POST data that PHP will accept.
; http://php.net/post-max-size
post_max_size = 10M

; Maximum allowed size for uploaded files.
; http://php.net/upload-max-filesize
upload_max_filesize = 8M

post_max_size 指表单 POST 数据的最大值;
upload_max_filesize 指上传文件的最大值;
注意:这个参数 file_uploads = On,确保它是打开的。

Nginx

编辑 nginx.conf 文件或者子配置文件,在 server 段的 location 中配置:

location / {
    root   /home/eric/workspace/GameFeedback/www;
    index  index.html index.htm index.php;
    if (!-e $request_filename) {
        rewrite ^/(.*)$ /index.php?/$1 last;
    }
    client_max_body_size 8m;
}

location ~ \.php$ {
    root           /home/eric/workspace/GameFeedback/www;
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include        fastcgi_params;
    client_max_body_size 8m;
}

注意:两个 location 都要配置,否则无效。

最后,需要重启 Nginx 服务;如果是 php-fpm,也要重启以使配置生效。