在讨论 PHP 时,没有必要回避房间里的大象;PHP 曾经是一个非常平庸的工具,这可能是一种轻描淡写的说法。事实上,我最近看到一篇文章谈到了为什么人们不喜欢PHP的问题。然而,根据我个人的经验,开发人员经常把他们认为的 PHP 和它的实际情况混为一谈。
事实是,PHP的名声是从某处得来的。但是 PHP v4 和更早的那些古老而黑暗的日子已经离我们远去。如果你没有惊恐地尖叫着跑开,你就会看到 PHP 的发展,而且它确实在发展。
在这篇文章中,我将介绍现代的PHP开发环境是什么样的,回顾一下在语言本身和作为补充的工具。随着现在web开发的复杂化,你不能孤立地判断语言。你需要了解所有帮助你创建高质量软件的卫星技术。
在文章的最后,希望你会对你所确定的PHP有多烂有了新的想法。然而,如果你是 PHP 的粉丝,你会有更多的理由来为你的选择辩护。让我们开始吧!
什么是现代 PHP?
在我们开始讨论 PHP 的优点之前,让我们先确定现代 PHP 的定义。在写这篇文章的时候,PHP v8.1.0刚刚看到了曙光,PHP基金会即将成为现实,而PHP v5.6已经到了寿命的尽头。所以,当我提到现代PHP时,我指的是v7及以后的版本。
自从在v5.0中被重写以来,语言及其工具的发展令人印象深刻。PHP v5.0标志着PHP历史上的一个拐点,使其进入了真正的面向对象语言的领域。
另一个不连续的飞跃是引入了Composer,一个 PHP 依赖关系管理器,这无疑划清了业余和专业开发之间的界限。但是,我有点超前了,我们以后会深入讨论这个问题。让我们回顾一下在过去几个版本中对PHP所作的一些主要改进。
从≥v7.x开始的PHP语言改进
自2015年12月3日发布的PHP v7.0以来,引入了几个令人兴奋的新特性,如类型声明、内置密码学、对复杂数据结构的支持、命名参数和属性。
语法也经历了一些强大的改进,如箭头函数、宇宙飞船操作符和空值凝聚。每一个新版本都比前一个版本有重大的性能改进。
这些新特性中的每一个都可能让三、四个版本以前离开PHP的人感到非常震惊。为了获得这些伟大的功能,你可能必须是一个重度的PHP用户,然而,对于我们这些比较随意地使用PHP的人来说,PHP已经引入了更多的针对日常使用情况的新功能。
现在我们已经掌握了最新的PHP版本所引入的功能,让我们来建立我们的工具箱。在下面的章节中,我将讨论一些我认为是在PHP中进行专业软件开发时不可缺少的工具。它们是按递增的顺序介绍的,这意味着我相信这将是最容易采用的途径。
调试器
在引入像XDebug和ZendDebugger这样的调试器之前,开发者不得不花过多的时间去了解一个应用程序的错误行为的根本原因。
在实践中,调试涉及到在程序执行过程中查看变量的内容。
一般来说,PHP是以批处理模式使用的,这意味着只有在脚本运行完成后才可以看到输出,这使得开发人员很难猜测错误发生时的背景是什么。
此外,可用于这项任务的工具如 [var_dump](https://www.php.net/manual/en/function.var-dump.php), echo,以及 print_r 构成了留下痕迹的高风险,可能会暴露敏感信息并降低恶意攻击者的门槛。
XDebug和ZendDebugger都能很好地与PhpStorm和VS Code等现代IDE合作,解决上述问题。如果你喜欢直接通过命令行。 [phpdbg](https://www.php.net/manual/en/book.phpdbg.php)自5.6版起就与PHP捆绑在一起。
依赖性管理
在PHP中,将外部库作为依赖关系导入曾经是一件非常痛苦的事情。然而,PHP的成熟度最突出的变化之一是Composer的发布。在Composer之前,PHP使用PEAR,它以一种更原始的方式解决了同样的问题。
例如,使用PEAR的单个项目的依赖性是很复杂的。使用PEAR的依赖管理是一种全有或全无的情况,所以在同一台服务器上运行几个项目是很困难的,特别是如果每个项目都依赖不同的或冲突的依赖集。
另一方面,Composer的依赖性管理要简单得多。每个项目都有自己的composer.json 和vendor 文件夹,使一切都自成一体。
Composer的另一个巨大优势是它的版本系统,它有内置的智能来确定最适合的依赖树;想想那些有自己的依赖性的依赖。另一方面,PEAR在这方面做得很差。
现在,PHP的最佳实践需要熟悉Composer。我们要介绍的大多数工具都需要在你的工作环境中提供它。
MVC框架
如果你正在构建任何非微不足道的应用程序,在你真正解决客户的问题之前,你有可能要创建大量的模板代码。想想诸如认证、路由和数据库管理等问题。
在以前的PHP时代,这些是一个真正的挑战。如今,有许多MVC框架可用,最明显的是Symfony和Laravel,你可以把它们作为任务的基础。Symfony和Laravel都拥有大量的社区支持和广泛的使用。
自动测试
自动测试工具已经成为整个软件开发行业的一个标准。每种语言都有自己的工具,而PHP最大的玩家肯定是phpUnit。
phpUnit最初是作为一个单元测试框架设计的,但其他工具已经帮助它扩展到提供其他类型的测试,如端到端和集成测试。
使用phpUnit是非常简单的。假设你有一个像下面这样的类。
<?php
namespace LeewayAcademy;
class Calculator
{
public function add(int $a, int $b): int
{
return $a + $b;
}
}
阅读代码,你可能认为它会工作。但使用phpUnit,你可以定义一组可重复的测试,帮助你建立和证明你的信心水平。例如,一个测试用例会像下面这样。
<?php
use PHPUnit\Framework\TestCase;
use LeewayAcademy\Calculator;
class CalculatorTest extends TestCase
{
public function testAdd()
{
$sut = new Calculator();
$this->assertEquals(3, $sut->add(1, 2));
$this->assertEquals(10, $sut->add(5, 5));
$this->assertEquals(10, $sut->add(0, $sut->add(4, 6)));
}
}
上面的代码用不同的输入集运行add 方法,然后验证输出是否符合预期。你可以用phpUnit运行测试,使用以下命令。
php vendor/phpunit/phpunit/phpunit --no-configuration --filter CalculatorTest --test
上面的代码将产生像下面这样的输出。
Testing started at 10:07 ...
PHPUnit 9.5.11 by Sebastian Bergmann and contributors.
Time: 00:00.006, Memory: 4.00 MB
OK (1 test, 3 assertions)
你可以随心所欲地运行这种测试。如果所有的测试都通过了,你就会有一些实际的证据,证明你的应用程序正在做它应该做的事情。当然,这些工具只是和你写的测试一样好,但这完全是另一种讨论。
其他值得一提的工具包括Codeception和behat。两者都使用phpUnit,但有不同的方法来编写测试。
静态分析工具
缺乏静态分析曾经是PHP和其他非编译语言的一个大缺点。一些bug被很好地隐藏在不明显的执行路径中,在正常的测试情况下很难发现它们。我们现在有phpstan、Psalm和Exakat,仅举几例。例如,考虑下面这个错误。
<?php
function f(int $p): int
{
return $p * 2;
}
$a = 'M';
echo f($a);
通过静态分析工具,像上面这样的type 不匹配的bug可以在不运行代码的情况下被检测出来,只需发出下面这样的命令即可。
vendor/bin/phpstan analyse test.php --level 5
上面的代码将产生以下输出。
1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
------ ----------------------------------------------------------
Line test.php
------ ----------------------------------------------------------
10 Parameter #1 $p of function f expects int, string given.
------ ----------------------------------------------------------
[ERROR] Found 1 error
现在,你有非常精确的错误信息,否则可能会被忽略。通过将这些工具纳入持续集成管道或作为Git Hooks的一部分运行,你可以更容易地提高代码库的质量。
部署工具
开发人员的工作并不是在他们写完最后一行代码后就结束了。要想接触到受众,你的应用程序必须首先到达生产服务器。
在旧版本的PHP中,部署你的应用程序需要将新文件推送到远程位置。然而,现在,这就有点复杂了。你可能要处理数据库更新、目录权限和其他大量的小任务,以使一切正常运行。很多时候,错过了其中的一个动作或以不同的顺序运行它们会使整个部署失败。
就像自动化测试工具一样,PHP生态系统提供了非常好的工具,可以把你的应用程序带到生产中,并根据需要保持更新,防止出现巨大的头痛。其中一些包括Deployer、Rocketeer、Pomander和easydeploy。举个例子,这是我在一个客户的项目中使用的Deployer的配置。
<?php
namespace Deployer;
require 'recipe/codeigniter.php';
// Project name
set('application', 'APP');
// Project repository
set('repository', 'git@bitbucket.org:maurochojrin/REPO.git');
set('branch', 'master');
set('default_stage', 'prod');
// [Optional] Allocate tty for git clone. Default value is false.
set('git_tty', true);
// Shared files/dirs between deploys
add('shared_files', [
'application/config/database.php',
'app_env.php',
]);
add('shared_dirs', [
'application/sessions',
'application/logs',
'assets/uploads/excel',
'logs',
]);
// Writable dirs by web server
add('writable_dirs', [
'application/sessions',
'assets/uploads',
'application/logs',
]);
// Hosts
host('THE_HOST')
->stage('prod')
->identityFile('~/.ssh/MauroChojrin.pem')
->set('user', 'ubuntu')
->set('deploy_path', '~/{{application}}');
// Tasks
task('build', function () {
run('cd {{release_path}} && build');
});
task('pwd', function () {
$result = run('pwd');
writeln("Current dir: $result");
});
// [Optional] if deploy fails automatically unlock.
after('deploy:failed', 'deploy:unlock');
有了这个配置,每当我把一个新版本推送到生产中时,我只需运行下面的命令。
dep deploy
该脚本将远程运行使应用程序对用户可用所需的所有任务。如果你还在通过FTP推送文件,你可能要看看这些工具了。
异步执行
说到PHP,另一个常见的抱怨是它缺乏异步执行支持。在这个方向上有几个项目,如Swoole和ReactPHP。看看下面的代码,它是从Swoole的例子库中提取的。
#!/usr/bin/env php
<?php
declare(strict_types=1);
/**
* How to run this script:
* docker exec -t $(docker ps -qf "name=client") bash -c "time ./io/blocking-io.php"
*
* This script takes about 3 seconds to finish, and prints out "12".
*
* Here the PHP function sleep() is used to simulate blocking I/O. The non-blocking version takes about 2 seconds to
* finish, as you can see in script "non-blocking-io.php".
*/
(function () {
sleep(2);
echo '1';
})();
(function () {
sleep(1);
echo '2';
})();
将其与非阻塞的对应代码进行比较。
#!/usr/bin/env php
<?php
declare(strict_types=1);
/**
* How to run this script:
* docker exec -t $(docker ps -qf "name=client") bash -c "time ./io/non-blocking-io.php"
*
* This script takes about 2 seconds to finish, and prints out "21".
*
* Here the Swoole function co:sleep() is used to simulate non-blocking I/O. If we update the code to make it work in
* blocking mode, it takes about 3 seconds to finish, as you can see in script "blocking-io.php".
*
* To see how the code is executed in order, please check script "non-blocking-io-debug.php".
*/
go(function () {
co::sleep(2);
echo '1';
});
go(function () {
co::sleep(1);
echo '2';
});
从语法上看,它们非常相似,但在下面,第二个版本是利用Swoole的力量进行并行处理,减少了实现最终结果所需的时间。
在PHP v8.1中,Fibers作为一个开箱即用的特性被引入,所以如果async是你的目标,没有什么可以阻止你在不离开PHP的情况下实现它。
总结
PHP已经走过了漫长的道路。遗憾的是,并不是每个PHP开发者都遵循了这些最佳实践,所以你仍然可以在外面找到很多面条状的代码。然而,这更多地反映了个人的责任而不是工具的缺陷。
从好的方面看,如果你愿意,有很多优秀的资源可以提高PHP的水平。我希望你喜欢这篇文章。如果你还没有,我希望你现在是 PHP 的粉丝,或者至少愿意给它一个机会。让我们来扭转PHP的声誉。
The postModern tools for PHP developersappeared first onLogRocket Blog.