Welcome
目前网络上充斥着大量的陈旧信息,让PHP新手误入歧途,传播着错误的实践和糟糕的代码,这必须得到纠正。PHP之道网站的 目标就是搜集PHP最佳实践、编码规范和网络上的权威学习指南,给PHP学习者提供一个易于阅读,快速查找的入口。
翻译
PHP之道已经翻译成多种语言:
Polish (Coming Soon)
Portuguese (Coming Soon)
Russian (Coming Soon)
Ukrainian (Coming Soon)
免责声明
PHP没有规范化的使用方式,本网站只是展示业界的最佳实践、可用的选项和有用的信息,目的是帮助PHP新手,并对以往的经验进行反思。
本文档会随着相关技术的发展,持续更新其中的信息和示例。
如何参与网站建设
推广网站
PHP之道 有多个banner宣传图片可以放在你的站点上显示,让更多开发者知道这个网站,找到权威的学习资料!
Getting Started
使用当前稳定版本 (5.4)
内置的Web服务器
有了它,你可以不用安装和配置功能齐全的Web服务器,就可以开始学习PHP(要求PHP 5.4版本)。要启动内置Web服务器,需要从你的命令行终端进入项目的Web根目录,执行下面的命令:
>php-Slocalhost:8000
Mac 安装
OSX系统会预装PHP,只是版本比最新稳定版低一点。 目前Lion下是PHP 5.3.6,Mountain Lion下是5.3.10.
Windows 安装
代码风格指南
PHP社区百花齐放,拥有大量的函数库、框架和组件。PHP开发者通常会在自己的项目中使用若干个外部库,因而PHP代码遵循或尽量接近 同一个代码风格就非常重要,可以让开发者方便地把多个代码库集成在自己的项目中。
通常情况下,你的PHP代码应该遵循其中一项或多项标准,从而其他开发者可以方便地阅读和使用你的代码。这些标准都是在前一个标准 上附加新的规则,所以使用PSR-1就同时要求遵循PSR-0,但可以不遵循PSR-2。
Language Highlights
编程范式
PHP是一个灵活的动态语言,支持多种编程范式。这些年来一直在不断的进化,重要的里程碑包括PHP 5.0 (2004)增加完善的 面向对象模型、PHP 5.3 (2009)增加匿名函数和命名空间和PHP 5.4 (2012)增加traits.
面向对象编程
PHP具有完整的面向对象编程特性,如类、抽象类、接口、继承、构造函数、克隆和异常等。
函数式编程
PHP支持第一类函数(first-class function),即函数可以赋值给变量,包括用户自定义的函数和内置函数,然后动态调用它。 函数可以作为参数传递给其他函数(即高阶函数),也可以作为函数返回值返回。
PHP支持函数递归调用,即函数自己调用自己,不过在实际的PHP代码中,我们更喜欢用迭代来代替递归。
2009年发布的PHP 5.3开始引入支持闭包的匿名函数。
PHP 5.4支持把闭包绑定到对象作用域,并改善其可调用性,从而可以在大部分场景中使用匿名函数替代普通函数。
元编程
PHP通过反射API和魔术方法机制,支持多种方式的元编程。开发者通过魔术方法,如__get(),__set(),__clone(),__toString(),__invoke()等,可以改变类的行为。Ruby开发者经常说PHP没有method_missing方法,实际上通过__call()和__callStatic()就可以 完成同样的功能。
命名空间
如前所述,PHP社区的众多开发者已经开发了大量的代码。这意味着一个函数库中的PHP代码可能使用了另外一个库中相同的类名,如果它们共享一个命名空间,则会产生冲突导致异常。
命名空间解决了这个问题。如PHP手册里描述的那样,命名空间类似于操作系统中的目录,两个同名文件可以共存于不同的目录。同理,同名的PHP类可以在不同的PHP命名空间下共存,就这么简单。
因而把代码放在自己的命名空间下就显得非常必要,这样其他人就可以放心的使用这些代码,而无需担心与其他函数库的命名冲突。
标准PHP库
标准PHP库(SPL)和PHP一起发布,提供了一组类和接口,包括了常用的数据结构如栈,队列和堆等,以及遍历这些数据结构的迭代器, 或者你还可以自己实现SPL接口。
命令行接口
PHP的主要目的是开发Web应用,不过它的命令行脚本接口(CLI)也非常有用。PHP命令行编程可以帮你完成自动化的任务,如测试,部署和 应用管理。
CLI PHP编程非常强大,可以直接调用你自己的app代码而无需创建Web图像界面,需要注意的是不要把CLI PHP脚本放在公开的web目录下!
在命令行下运行PHP:
> php -i
接下来写一个简单的”Hello, $name” CLI程序,先创建名为hello.php的脚本:
运行上面的脚本,在命令行输入:
> php hello.php
Usage: php hello.php [name] > php hello.php world
Hello, world
依赖管理
如今有大量的PHP函数库、框架和组件可供选择,一个项目中可能会使用其中的若干——这就是项目的依赖。到目前为止,PHP还没有有效的 项目依赖管理方案。即使你手工的管理它们,你还不得不处理它们的自动加载问题。
目前主要有两个PHP包管理系统:Composer和PEAR,哪个适合你呢?答案是两个都需要。
管理单个项目的依赖时使用Composer
管理整个系统的PHP依赖时使用PEAR
通常情况下,Composer包只在你项目中明确指定时才可用,而PEAR包在所有的PHP项目中可用。尽管PEAR听起来似乎更简单,但是根据每个 项目制定方案可能更合适。
Composer and Packagist
Composer是一个出色的PHP依赖管理器,把项目的依赖列在composer.json文件中,然后通过一些简单的命令,Composer就会 自动的帮你下载这些依赖,并配置好自动加载路径。
如何安装Composer
Composer可以安装在本地(在当前工作目录,不推荐这种方式),也可以安装在系统中(如/usr/local/bin)。假设你要在本地安装,在 项目的根目录执行:
它会下载composer.phar(PHP二进制文档),然后你就可以用php运行它来完成项目依赖的管理。请注意:如果 你通过管道直接把下载的代码传给PHP解释器,请先在线阅读代码以确保该代码是安全的。
如何手动安装Composer
手动安装composer有点麻烦,不过很多开发者可能更喜欢这种安装方式。使用交互式安装程序,它会检查你安装的PHP:
PHP版本满足要求
.phar文件可以正确执行 - 相关目录的权限设置正确 - 没有加载某些不兼容的扩展
相应的php.ini设置正确
而手动安装则需要你自己做这些事情,你必须自己权衡利弊,以决定是否手动安装。下面是手动获取Composer的方法:
chmod +x$HOME/local/bin/composer
目录$HOME/local/bin(或你自己选择其它目录)应该在你的$PATH环境变量中,从而可以直接运行composer命令。
这样文档中描述的运行Composer的命令php composer.phar install,就可以用如下命令替代:
composerinstall
如何定义和安装依赖
{
"require": {
"twig/twig":"1.8.*"}
}
第二步:在项目根目录运行:
phpcomposer.pharinstall
这会在vendors/下载和安装项目依赖。最后在应用的PHP入口文件添加下面代码,告诉PHP使用Composer自动加载器加载项目的依赖库:
现在你就可以使用项目依赖的库了,它们会在需要的时候自动加载。
PEAR
Coding Practices
基础知识
PHP是一个伟大的语言,可以让各个层次的程序员都能够快速高效地完成编码任务。虽然如此,我们还是经常会因为临时救急或者 坏习惯而忽视了PHP的基础。为了解决这个问题,这部分专门给开发者回顾一下PHP的基础编码实践。
日期和时间
PHP使用DateTime类完成读取、设置、比较和计算日期与时间。虽然PHP中有很多日期和时间处理相关的函数,但是DateTime类提供了 完善的面向对象接口完成各项常见操作,而且还能处理时区,这里不作深入介绍。
要使用DateTime,可以用工厂方法createFromFormat()把原始的日期时间字符串转换为DateTime对象,或直接用new \DateTime获得当前日期和时间的DateTime对象。用format()方法可以把DateTime对象转换成字符串输出。
format('m/d/Y') ."\n";
DateTime计算时间时通常需要DateInterval类,如add()和sub()方法,都是将DateInterval作为参数。尽量避免直接用 时间戳表示时间,夏令时和时区会让时间戳产生歧义,使用间隔日期更为妥当。计算两个日期的差值使用diff()方法,返回 DateInterval对象,输出显示也很方便。
add(new \DateInterval('P1M6D')); $diff = $end->diff($start); echo "Difference: " . $diff->format('%m month, %d days (total: %a days)') . "\n"; // Difference: 1 month, 6 days (total: 37 days)
DateTime对象之间可以直接比较:
最后一个例子是DatePeriod类的用法,它用于循环事项(recurring events)的迭代。它的构造函数参数为:start和end,均为 DateTime对象,以及返回事项的间隔周期。
format('m/d/Y') . " "; }
设计模式
在代码和项目中使用常见模式是有好处的,可以让代码更易于管理,同时也便于其他开发者理解你的项目。
如果你的项目使用了框架,那么在代码和项目结构上,都会遵循框架的约束,自然也就继承了框架中的各种模式, 这时你所需要考虑的是让上层代码也能够遵循最合适的模式。反之,如果没有使用框架,那么就需要你自己选择 适用于当前项目类型和规模的最佳模式了。
异常
异常是大部分流行语言的标准特性,但是PHP开发者却不太重视。其他语言如 Ruby极度倚赖异常,在任何错误发生的时候,如HTTP请求失败 、DB查询错误,甚至图片资源未找到,都会抛出一个异常,以及时提示那里发生了一个错误。
PHP则对此很宽松,如调用file_get_contents()失败,只是返回FALSE并提示一个warning信息而已。很多老的PHP框架,如 CodeIgniter会返回false,然后在自己的日志里记录一个消息,开发者需要使用如$this->upload->get_error()的方式来查看发生了什么 错误。这么做需要你自己检查是否有错误,并需要根据不同类调用不同的方法来获取错误消息,而不能让错误明显的显示出来。
这种做法的另外一个弊端是当类自动在屏幕打印一个错误,然后退出进程,阻止了其他开发者动态处理该错误的机会。而异常则是让开发者知道 发生了错误,并让他们选择如何处理:
SPL异常
默认的异常类Exception没有含义,常见的做法是给它设定一个有意义的名字:
}
如使用__call()魔术方法,对不存在的方法调用抛出一个throw new BadFunctionCallException;,既避免了抛出含义模糊的 Exception异常,也省去了自定义异常类的麻烦。
数据库
如果应用只是使用一个数据库的话,原生驱动就工作的非常好,否则使用MySQL的同时,还需要使用MSSQL或Oracle数据库的话,那么 就没有办法只使用一个原生驱动了,只能分别学习各个数据库驱动的API,这非常令人生厌。
PDO
PDO是数据库连接抽象库,从PHP 5.1.0开始提供,提供多种数据库的统一的操作接口。PDO不会转化你的SQL查询或者模拟缺失特性; 它只是提供统一的API去连接不同的数据库而已。
更重要的是,PDO允许你绑定SQL查询语句中的变量,而无需担心SQL注入问题,这主要通过PDO statements和变量绑定来实现。
假设PHP脚本接收一个数字ID作为查询参数,从数据库取回一条记录。下面是一种错误的做法:
query("SELECT name FROM users WHERE id = ". $_GET['id']);// <-- NO!
prepare('SELECT name FROM users WHERE id = :id'); $stmt->bindParam(':id', filter_input(INPUT_GET,'id', FILTER_SANITIZE_NUMBER_INT), PDO::PARAM_INT); $stmt->execute();
这才是正确的代码,在PDO statement中绑定一个参数,使得查询被发给数据库之前,对输入参数进行转义,防止SQL注入攻击。
另外一个要注意的问题是,如果数据库连接没有隐式地关闭,那么数据库连接数可能会超过数据库服务器的限制而连接失败,这种 错误在其他编程语言中比较常见。PDO对象在销毁的时候会隐式的关闭数据库连接,只要你把指向它的引用全部删除即可,如设置 为NULL。如果没有,PHP也会在脚本结束时关闭所有非持久化的数据库连接。
抽象层
很多框架都提供了自己的数据库抽象层,有的是基于PDO,有的不是。它们通过PHP方法来包装实际的查询,能够模拟出只存在于 某些数据库系统的特性,给你一个真正的数据库抽象层。这么做会带来一些性能的损失,但是在一个需要支持MySQL、PostgreSQL 和SQLite的应用中,这个损失相对于由此带来的代码一致性而言是可以接受的。
有些抽象层遵循PSR-0命名空间标准,可以集成在任意的应用中:
安全
Web应用安全
使用Bcrypt做密码哈希
一个存在用户登录的PHP应用,用户名和密码(哈希后的)会保存在数据库中,用于将来登录时进行身份验证。因而对存储在数据库中的 密码进行正确的_哈希_非常重要。如果密码没有哈希,那么数据库被黑或者非法访问时,所有的用户帐号都将泄漏。
使用Bcrypt做密码哈希,Bcrypt非常简单,并可以确保数据库被攻击后,无法反向工程恢复密码的明文。
下面是几个PHP的Bcrypt库:
数据过滤
永远不要在PHP代码中信任外部输入,在使用之前一定要先过滤和验证,filter_var和filter_input函数可以帮助过滤文本和 验证文本格式(如邮箱地址)。
外部输入可以是:$_GET和$_POST表单输入数据、$_SERVER超级变量中的某些值和通过fopen('php://input', 'r')获取的 HTTP请求体。要记住外部输入不仅仅是用户提交的表单数据,还包括上传和下载的文件、session变量、cookie数据和第三方Web服务提供的 数据等。
当外部数据被存储合并之后,下次读取时,它们仍然算是外部输入,每次在代码中处理的时候,需要问自己是否已经正确过滤,是否可以 信任它们。
数据需要根据不同用处,进行不同的_过滤_,如果把未经过滤的数据输出到HTML页面,它可以在你的网站里执行HTML和JavaScript!即通常 说的跨站脚本攻击(XSS)。避免XSS的一个策略就是过滤外部输入的所有HTML标签,删除或者转义其中的HTML实体。
另外一个例子是传给命令行命令的选项,这可能非常危险(通常不是一个好主意),不过你可以用内置的escapeshellarg函数过滤命令行的 参数。
数据清洗
数据验证
数据验证外部输入就是你预期的,如你在处理注册表单时,需要验证email地址、电话号码和年龄等数据
配置文件
在创建应用的配置文件时,请遵循下面的业界最佳实践:
配置文件保存在Web不能直接访问和上传的目录中。
如果配置文件只能放在文档根目录时,请使用.php作为文件名后缀,这样即使直接访问该配置文件,也不会输出配置信息。 - 配置文件内容应该加密,或者对文件设置访问权限。
注册全局变量
提示:从PHP 5.4.0开始,register_globals配置已经删除,不再生效。保留这个配置,只是提示依赖该配置的应用进行升级。
启用register_globals配置后,$_POST,$_GET和$_REQUEST中的变量自动注册为全局变量,使得应用很难辨别变量的确切来源,从而产生安全漏洞。
例如:$_GET['foo']将注册变量$foo,这会覆盖程序中未声明的同名变量。如果你使用PHP 5.4.0之前的版本,请确保已经把register_globals设置为off。
错误提示
错误日志可以帮助追查应用的Bug,但是也会暴露应用的结构信息而产生安全问题,为此,需要在开发环境和线上环境设置不同的配置,防止 敏感信息的泄漏。
开发环境
要在开发环境显示错误提示,需要在php.ini中配置以下配置项:
display_errors: On
error_reporting: E_ALL
log_errors: On
线上环境
要在线上环境隐藏错误提示,需要在php.ini中配置以下配置项:
display_errors: Off
error_reporting: E_ALL
log_errors: On
这样设置后,线上错误会记录到Web服务器的错误日志中,而不是直接显示给用户。如果想了解更多错误提示相关的设置,请参考手册:
测试
为PHP代码编写自动化测试被认为是一个最佳实践,可以帮助你构建出高质量的应用。自动化测试可以帮助你确认没有因为重构或添加 新功能而破坏原有功能,所以应该重视自动化测试。
PHP有多种类型的测试工具和框架可以使用,具体方法各有区别——但是它们的目标都是避免手工测试,满足大型QA组织的需求,保证最新的 更改没有破坏已有功能。
测试驱动开发
测试驱动开发(TDD)是以非常短的开发周期,不断进行迭代的软件开发流程:首先开发者针对改进或新功能编写失败的自动化测试用例,然后编写代码使测试用例通过, 最后重构代码,让代码满足可接受的标准。Kent Beck,该技术的创建者或者说重新发现者,在2003年声明TDD鼓励简单的设计和提振信心。
目前对应用有多种类型的测试:
单元测试
单元测试是从编写开始,贯穿于整个开发周期的一种用于保证函数、类和方法的行为与预期一致的编程方法。通过检查各个函数和方法的输入和输出值,你可以保证它们 内部逻辑已经正确执行;通过依赖注入、编写mock类和stubs,你可以验证依赖是否已经正确处理,提高测试覆盖率。
在编写一个类或函数的时候,应该为它的每一个行为创建一个单元测试,至少你要保证它收到错误参数时能够触发错误,而参数正确时能正常工作。这可以帮你在后面 修改类或函数的时候,确认已有功能仍然正常工作。PHP中var_dump()的功能与此类似,但是它是无法用于创建应用的。
单元测试的另外一个用武之地是在给开源项目贡献代码时,如果你编写一个测试,证明代码存在bug,然后修复代码,让测试通过,这样该补丁被接受的概率要高很多。 如果你的项目接受人家的补丁,你应该把单元测试作为项目的一项要求。
集成测试
集成测试(也称集成与测试,缩写为I&T)是把各个独立模块集成在一起,作为一个整体进行测试的软件测试阶段,它处于单元测试和验收测试之间。集成测试把已经 做过单元测试的模块集成在一块,然后运行集成测试用例,最终输出一个可以进行系统测试的系统。
很多单元测试工具同时也可以用于集成测试,并且原理也是相通的。
功能测试
有时也称为验收测试,使用工具创建自动化的测试用例,然后在真实的系统上运行,这一点与单元测试验证单个模块的正确性和集成测试验证模块间交互的正确性是有 区别的,这些工具通常使用真实的数据集来模拟真实用户的使用行为来验证系统的正确性。
功能测试工具
行为驱动开发
行为驱动开发(BDD)有两种方式:SpecBDD和StoryBDD。SpecBDD关注技术行为或代码,而StoryBDD关注业务、特性和交互,这两种方式都有对应的PHP框架。
BDD链接
测试辅助工具
除了测试驱动和行为驱动开发框架,还有大量的通用框架和函数库,可以在各种开发方法下使用。
工具链接
服务器和部署
部署PHP应用到线上Web服务器的方式有很多种。
平台即服务(PaaS)
PaaS提供运行PHP Web应用所需的系统和网络环境,对PHP应用和框架只需要做少量的配置即可。
虚拟或独立主机
如果你愿意或想学习系统管理,那么虚拟或独立主机可以让你完全控制自己的运行环境。
nginx和PHP-FPM
Apache和PHP
共享主机
PHP非常流行,很少有服务器没有安装PHP的,因而有很多共享主机,不过需要注意服务器上的PHP是否是最新稳定 版本。共享主机允许多个开发者把自己的网站部署在上面,这样的好处是费用非常便宜,坏处是你不知道将和哪些 网站共享主机,因此需要仔细考虑机器负载和安全问题。如果项目预算允许的话,避免使用共享主机是上策。
缓存
PHP自身效率很高,但是执行创建远程连接、加载文件等操作时容易出现瓶颈,幸运的是,我们有很多工具来加速这部分操作,或减少 这些耗时操作的执行次数。
字节码缓存
W在一个PHP文件被执行时,它先被编译为字节码(也称opcode),然后这些字节码被执行。如果文件没有修改,那么字节码也会保持不变, 这意味着编译这一步白白浪费了CPU资源。
这就是引入字节码缓存的原因,通过把字节码保存在内存中来消除冗余的编译,重用它们完成后续的调用。配置字节码缓存非常简单, 而且可以极大地提高应用的执行效率,没有理由不使用字节码缓存。
流行的字节码缓存方案有:
对象缓存
很多时候,在代码中缓存对象可以带来很大的收益,例如获取代价很大的数据和查询结果很少变化的数据库调用。我们可以使用对象 缓存系统缓存这些数据,大大加快后续的同类访问请求。如果你在取得这些数据之后,把它们缓存在系统中,在后续对这些数据的请求 中,就可以直接使用缓存中的对象,这么做可以很大的提示系统性能,减少服务器的负载。
很多流行的字节码缓存方案也允许你缓存自定义数据,因此我们更应该充分利用对象缓存功能。APC、XCache和WinCache都提供API, 让你把数据缓存在他们的内存cache中。
使用最多的内存对象缓存系统是APC和memcached,APC是很好的一个对象缓存方案,它提供了简单的API来让你把对象存储在内存中,而且 配置和使用都非常容易,它的一个缺点是只能在本机使用。Memcached则是另外一种方式,它是一个单独的服务,可以通过网络访问,这 意味着可以在一个地方写入数据,然后在不同的系统中访问这份数据。
在单机性能上,APC通常比Memcached更高,如果你不需要多台服务器或者其他Memcached的高级功能,APC可能是你的最佳选择。
APC的示例:
学习更多对象缓存系统:
资源
From the Source
People to Follow
Mentoring
PHP PaaS供应商
框架
大量的PHP开发者使用框架,而不是重复发明轮子来创建自己的Web应用。框架抽象出底层通用的业务逻辑,给使用者了提供简单易用的接口。
不是所有的项目都需要框架,有时候原生的PHP就能满足需求,但是需要框架的时候,有三种类型的框架可供选择:
* 微框架 * 全能(Full-Stack)框架 * 组件框架
微框架仅是一个包装器(Wrapper),尽量快地把HTTP请求路由到回调函数、控制器或方法上,有些框架也会提供一些函数库,如基本的数据库 操作。微框架主要用于构建远程HTTP服务。
全能框架则是在微框架的功能之上提供了更多的功能特性,如ORM,验证组件等。
组件框架则是一组独立功能库的集合,多个基于组件的框架集合在一起,甚至可以用作微框架或者全能框架。
组件
如前所述,组件是另外一种创建、实现和发布开源代码的方式,当前社区存在很多组件库,最主要的两个:
社区
PHP User Groups
PHP Conferences
The PHP community also hosts larger regional and national conferences in many countries around the world. Well-known members of the PHP community usually speak at these larger events, so it’s a great opportunity to learn directly from industry leaders.