PHP组件和框架的数量多的让人难以置信。有像Symfony和Laravel这样的巨型框架,也有像Silex和Slim这样的微型框架。甚至还有些在现代PHP组件出现之前就存在很久的传统框架,例如CodeIgniter。现代PHP生态系统是一个名副其实的代码大熔炉,这有助于开发者们构造令人惊奇的应用。
不幸的是,那些老的PHP框架都是在相对封闭的环境下开发出来的,它们没法与其他的PHP框架共享代码。如果你的项目使用某个老的PHP框架,你会牢牢被困在框架之中,陷入框架自身的生态系统而不能自拔。如果你对框架本身提供的工具还算满意,那么这种相对集中的环境倒也没有什么问题。但是,如果使用了CodeIgniter框架的同时还想从Symfony框架中挑选几个优秀的类库来使用,那你可能就没有那么幸运了,除非你自己专门为你的项目写个一次性的适配器。
what we've got here is a failure to communicate(在这里我们完全无法沟通)
--电影铁窗喋血的经典台词
看到问题所在了吗?封闭的环境中开发出来的框架它们的设计初衷就不是为了和其它框架进行互通。无论是对开发者来说(被所选择的框架限制了创造力)还是对框架自身(他们重复开发着某些已经存在的代码)来说,这种设计方式是非常低效的。尽管如此,我还是有好消息要告诉大家。PHP社区对框架的认识已经从集中式框架模型过渡到了由高效、公用、专一的组件构成的分布式生态系统。
PHP-FIG的营救计划
一些PHP框架的开发者们意识到这个问题,于是他们在2009年的php|tek(著名的PHP会议)上进行了讨论。讨论的核心是如何提升框架内部的互通性和开发效率。譬如,如果某个PHP框架可以分享出一个monolog这样的非耦合的日志类,那么我们是不是就不用每次都去开发一个新的紧耦合的日志记录类了?如果某个PHP框架可以使用Symfony框架的symfon/httpfoundation组件中的优秀的HTTP request和response类,那么我们是不是就不需要开发自己的HTTP request和response类了?为了实现这个目标,PHP框架需要使用某种通用的语言来实现交互以及分享它们的框架。而它们需要的就是标准。
这些在php|tek会议上偶遇的PHP框架的开发者们最终创建了PHP Framwork Interop Group(PHP-FIG)。PHP-FIG就是由一群PHP框架项目的代表组成的,按照PHP-FIG的官方网站的说法,他们的职责就是“讨论各自项目的共性从而找到可以协同工作的方式”。PHP-FIG制定了一些推荐(recommendations),PHP的框架可以自愿选择是否实现这些recommendations以提高交互性并共享各自的框架。
PHP-FIG是一群PHP框架项目的代表自创的组织。它的会员并不是通过选举产生的,除了满怀着发展PHP社区的意愿外这些会员并没有什么特别之处。任何人都可以申请会员资格。任何人都可以对处于提议阶段的PHP-FIG recommendations提交自己的反馈。最终的PHP-FIG recommendations通常被采纳并被许多最大最流行的PHP框架实现。我非常鼓励你参与到PHP-FIG之中,你只要做到发送你的反馈、帮助你最喜爱的PHP框架一起打造未来。
我们常说PHP-FIG负责提供各种recommendations,理解这一点非常重要,因为推荐不等同于规则,推荐不是必须要执行的。这些recommendations都是经过审慎考虑、精雕细琢的建议,它们可以为PHP开发者们(甚至PHP框架的作者们)带来更多的便利。
框架协作
PHP-FIG的任务是实现框架协作。而框架协作意味着通过接口、自动加载和编码风格来实现协同工作。
接口
PHP框架之间通过共享的接口协同工作。框架通过PHP接口可以确认第三方依赖库提供的了哪些方法,而无需考虑这些依赖库是如何实现这些接口的。
参考第二章,可以了解PHP接口的详细说明。
举个例子,某个PHP框架很开心的分享了一个第三方的logger对象,假设这个对象实现了emergency()、alert()、critical()、error()、warning()、notice()、info()和debug()这几个方法。这些方法具体是怎么实现的无关紧要。每个框架只需要关心第三方依赖库是否实现了这些方法就行。
接口使得PHP开发者们可以构造、分享和使用功能专一的组件取代庞大的框架。
自动加载
PHP框架之间通过自动加载协同工作。通过自动加载功能,PHP解释器在运行时可以自动定位并根据需要来加载一个PHP类。
在PHP标准制定之前,PHP组件和框架使用魔术函数\_autoload()或者更高级点的spl_autoload_register()方法来实现它们自己独立的自动加载器。这迫使我们需要去熟悉每个组件和框架各自特有的自动加载器才能使用它们。现如今,绝大多数现代PHP组件和框架都与公用的自动加载器标准兼容。这意味着我们可以使用一个统一的自动加载器将多个PHP组件组合并匹配在一起使用。
编码风格
PHP框架之间通过编码风格来协同工作。你的编码风格决定了空格、大小写以及括号的位置(尤其重要)。如果所有的PHP框架都能统一一套标准的编码风格,那么PHP开发者们就不需要每次在使用新的PHP框架时还得去适应新的编码风格,PHP框架的代码就不会显得那么陌生。一套标准的编码风格还能降低项目中新人的入门门槛,这样他们就可以集中精力去解决bug,而不用花大量的事件去学习陌生的编码风格。
标准的编码风格还能改进我们自己的项目。每个开发者都有在编码上的独特癖好,当开发者们在同一个代码库上一起工作时麻烦就会出现。一套标准的编码风格能够帮助所有的团队成员立刻明白同一个代码库中彼此的代码而不用去管代码的作者是谁。
PSR是什么?
PSR是PHP standards recommendation(PHP推荐标准)的缩写。如果你最近阅读过一些PHP方面的博客,你或许已经看到了一些诸如PSR-1、PSR-2、PSR-3这样的术语。这些都是PHP-FIG的推荐。他们的名字以PSR-开头,结尾跟着一个数字编号。每个PHP-FIG的推荐都是用来解决在大多数PHP框架中经常遇到的特定问题的。为了不让各种PHP框架不断的去解决同样的问题,它们可以采纳这些PHP-FIG的推荐,在共享的解决方案基础上构建各自框架。
截止到本书发行为止,PHP-FIG已经发布了5个推荐:
- PSR-1:基本编码风格
- PSR-2:严格编码风格
- PSR-3:Logger接口
- PSR-4:自动加载
namespace My\Component;use Symfony\Components\HttpFoundation\Request;use Symfony\Components\HttpFoundation\Response;class App{ // 类的定义体}
类
<?php namespace My\App;class Administrator extends User{ // 类的定义体}
<?phpnamespace Animals;class StrawNeckedIbis{ public function flapWings($numberOfTimes = 3, $speed = 'fast') { // 方法定义体 }}
作用域
<?phpnamespace Animals;class StrawNeckedIbis{ // 设置了作用域的静态属性 public static $numberOfBirds = 0; // 设置了作用域的方法 public function __construct() { static::$numberOfBirds++; }}
控制结构
<?php$gorilla = new \Animals\Gorilla;$ibis = new \Animals\StrawNeckedIbis;if ($gorilla->isAwake() === true) { do { $gorilla->beatChest(); } while ($ibis->isAsleep() === true); $ibis->flyAway();}
你可以自动兼容PSR-1和PSR-2编码风格。许多代码编辑器依照PSR-1和PSR-2来自动格式化你的代码。另外,还有些工具可以帮助你依照PHP标准来检查并且格式化你的代码。譬如PHP Code Sniffer,我们常称之为phpcs,通过这个工具(可以直接在命令行中使用或者在IDE里使用)可以报告出你的代码与给定的PHP代码标准之间的差异。你可以通过几乎所有的包管理系统(譬如PEAR、Homebrew、Aptitude或者Yum)来安装phpcs。
<?phpnamespace Psr\Log;interface LoggerInterface{ public function emergency($message, array $context = array()); public function alert($message, array $context = array()); public function critical($message, array $context = array()); public function error($message, array $context = array()); public function warning($message, array $context = array()); public function notice($message, array $context = array()); public function info($message, array $context = array()); public function debug($message, array $context = array()); public function log($level, $message, array $context = array())}
每个接口方法都对应了RFC 5424协议的一个级别并接收两个参数。第一个参数$message必须是一个字符串或者是一个包含了__toString()方法的对象。第二个参数$context是可选的,它提供了一个数组用来保存占位符对应的值,以便替换第一个参数中的标记(占位符)。
<?phpuse Monolog\Logger;use Monolog\Handler\StreamHandler;date_default_timezone_set('America/New_York');// 准备 logger$log = new Logger('myApp');$log->pushHandler(new StreamHandler('logs/development.log', Logger::DEBUG));$log->pushHandler(new StreamHandler('logs/production.log', Logger::WARNING));// 使用 logger$log->debug('This is a debug message');$log->warning('This is a warning message');
PSR-4:autoloader
<?phpinclude 'path/to/file1.php';include 'path/to/file2.php';include 'path/to/file3.php';
经常看到,对吗?你可能对require()、require_once()、include()和inluce_once()函数已经非常熟悉了。这些函数用来将一个外部的PHP文件加载到当前脚本中,如果你的PHP脚本不多的话,这样的代码工作起来完全没有问题。但是,如果你需要包含上百个PHP脚本呢?如果你需要包含上千个脚本呢?这就暴露出了require()和include()函数的扩展问题,这也是PHPautoloader之所以这么重要的原因。一个autoloader是一个用来在运行时按需求查找PHP类、接口或者trait并将它们加载到PHP解释器中的策略,它不需要像前面的例子中那样明确的指出需要被加载的文件路径。
<?phprequire_once '/path/to/lib/Twig/Autoloader.php';Twig_Autoloader::register();
试想一下,你的应用程序里的每个PHP组件,你都要去研究一下文档才能知道如何去注册它们特有的autoloader。PHP-FIG意识到了这个问题并且提出了PSR-4 autoloader推荐来促进组件的协同工作。多亏有了PSR-4,我们可以只使用一个autoloader来自动加载我们应用程序中的所有PHP组件。太不可思议了。绝大多数现代PHP组件和框架都兼容PSR-4。如果你开发并发布了你自己的组件,请确保它们也是兼容PSR-4的!让你的组件也可以和像Symfony、Doctrine、Monolog、Twig、Guzzle、SwiftMailer、PHPUnit、Carbon以及许多其它的组件一样协同工作。
<?php/** * An example of a project-specific implementation. * * After registering this autoload function with SPL, the following line * would cause the function to attempt to load the \Foo\Bar\Baz\Qux class * from /path/to/project/src/Bar/Baz/Qux.php: * * new \Foo\Bar\Baz\Qux; * * @param string $class The fully-qualified class name. * @return void */spl_autoload_register(function ($class) { // project-specific namespace prefix $prefix = 'Foo\\'; // base directory for the namespace prefix $base_dir = __DIR__ . '/src/'; // does the class use the namespace prefix? $len = strlen($prefix); if (strncmp($prefix, $class, $len) !== 0) { // no, move to the next registered autoloader return; } // get the relative class name $relative_class = substr($class, $len); // replace the namespace prefix with the base directory, replace namespace // separators with directory separators in the relative class name, append // with .php $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php'; // if the file exists, require it if (file_exists($file)) { require $file; }});
把这段代码复制粘贴到你的应用程序中,修改$prefix和$base_dir变量,那么你就拥有了一个自己的可以工作的PSR-4 autoloader。然而,如果你想自己去写自己的PSR-4 autoloader,赶紧收手并问问自己这样做是否真的值得。为什么?因为我们可以使用Composer依赖管理工具来自动生成PSR-4 autoloader。真的很方便,这就是我将在下面的第四章介绍的。