有一天产品经理给大家提了个需求,指着淘宝说,做个一样的,这个功能很简单,怎么实现我不管……
说时迟那时快,小明拿起键盘就是干。小明快速的封装了两个类,分别如下:
TestA.php
<?php class TestA{ public function getDemo(){ echo '淘宝首页'; } }
TestB.php
<?php class TestB{ public function getDemo(){ echo '淘宝商品页'; } } /**** 小明拿到需求后干劲十足,于是在代码中添加了一亿行注释 什么渣渣需求,大爷不干了…… 什么渣渣需求,大爷不干了…… 什么渣渣需求,大爷不干了…… *****/
包含注释信息的TestB.php文件有1G,请自行粘贴复制将TestB.php文件弄成1G左右,下面会有惊喜。
然后创建一个index.php文件来调用这两个类。
<?php
include("TestA.php");
include("TestB.php");
if(isset($_GET['type'])){
if($_GET['type'] == 1){
$a = new TestA();
}else{
$a = new TestB();
}
$a->getDemo();
}else{
echo "页面不存在,请指访问 http://127.0.0.1:3000/index.php?type=1";
}
在当前目录下打开命令行,然后执行 php -S 127.0.0.1:3000
(启动PHP服务),再访问 http://127.0.0.1:3000/index.php?type=1
等你的页面加载完,你会打心里说一句“这么卡,PHP这语言真**”。
于是我们优化一下index.php的代码,让首页不那么卡,优化后的代码如下:
<?php
if(isset($_GET['type'])){
if($_GET['type'] == 1){
include("TestA.php");
$a = new TestA();
}else{
include("TestB.php");
$a = new TestB();
}
$a->getDemo();
}else{
echo "页面不存在,请指访问 http://127.0.0.1:3000/index.php?type=1";
}
这次再访问 http://127.0.0.1:3000/index.php?type=1
发现速度快了很多。
从上面这个简单的例子可以得出下面一些结论:
- PHP是解释型语言,每次执行时都需要加载文件、编译文件中的代码、最后执行。虽然注释不是代码,但也会被加载导致速度变慢。
- 硬盘的读取速度很慢,45M/秒左右。
- 按需加载能优化程序,因为不同请求要加载的类和文件是不一样的。
一. 解释型语言的优缺点
编程语言分为解释型语言和编译型语言,像c,golang就是编译型语言,需要build成可执行文件才能运行。像PHP,Python,JavaScript就是解释型语言,可以理解为需要实时编译(解释)再执行。
作为解释型语言,PHP最大的优点有如下几个:
- 不关注变量类型,解释器会自己推算出变量类型。定义变量很轻松,很大程度的提升了编程速度。比如定义了
$a = 1000;
之后执行$a = 12.11;
和$a = "abc";
程序都能正常执行,开发人员不用关心变量是int、float、char。 - 热更新,代码修改了之后是实时生效的,不用重启服务器。
- 自带很多实用的扩展,常用的时间处理,字符串处理,文件处理,数据库读写等等,真正的开箱即用。
- 容易上手,也导致很多人只注重功能实现不注重代码质量。
作为解释型语言,PHP最大的缺点如下:
- 有很多历史包袱,年纪太大的锅。
- 函数命名不规范,怎么开心怎么来的节奏,今天小驼峰明天匈牙利。
- 缺少好用的包管理器和依赖管理方案,安装扩展很麻烦。
- 只能用于Web开发,其他领域不实用。也算是优点吧(专一)
二. 硬盘读取速度的局限
任何编程语言,在项目代码多了之后都会遇到硬盘读取速度的瓶颈,很多编程语言设计之初就引入了包管理的概念,比如C++ 的 using namespace std;
比如 golang的 import "fmt"
Python的 import re
。通过合理的对包做管理,就能解决由于项目文件太大导致用户请求变慢的问题,很少有哪个请求或者方法需要将所有包代码都加载到内存中。
如果一个资源文件的大小是100K,400个用户同时发起请求就是40M。这个时候硬盘的瓶颈就出来了,通常我们会将文件放入到内存中,内存的读取速度在10G/秒左右,100K的文件支持10万用户同时获取。理论上是这样,但网卡不一定承受的住,现在大部分网卡还是千M网卡,扛不住10G/秒的请求。有兴趣的朋友可以看看PHP教程系列3-写PHP程序前必须知道的5点信息。
三. 按需加载要解决哪些问题
还是之前的例子,小明封装的文件越来越多,新加一个文件就写一条 include 语句,淘宝网有几万个页面,难道要写几万个include?我们需要再优化一下代码,让程序变的智能一点,别写太多include。优化后的代码如下:
<?php
function autoload($className)
{
$fileName = $className. '.php';
include $fileName;
}
spl_autoload_register('autoload');
if(isset($_GET['type'])){
if($_GET['type'] == 1){
$a = new TestA();
}else{
$a = new TestB();
}
$a->getDemo();
}else{
echo "页面不存在,请指访问 http://127.0.0.1:3000/index.php?type=1";
}
我们去掉了之前的include语句,通过 spl_autoload_register()
函数来实现按需加载,这个函数的含义是如果当前找不到类名,就去指定的目录查找。通过 spl_autoload_register()
方法我们把 include 去掉了,这时候再新建TestC.php ,TestD.php 都不需要再通过 include 引入。大大的节省了编程时间,简单的实现了按需加载。
不过上面的方法有个问题,如果我需要引入小芳的代码库,小芳代码库的文件名并不等于类名,例如文件名是TestC.class.php类名是TestC。我们就没法通过类名加载到指定的文件了。如果小芳也有一个TestA.php文件,和小明的冲突了怎么办?
为了解决上面这两个问题,PHP5.3引入namespace
命名空间的概念,PHP5.3之前的版本是不支持命名空间的。命名空间的引入,主要就是为了优化包管理,按需加载等问题。官方指定了PSR-0和PSR-4两个自动加载规范,其中PSR-0已经被弃用。现在PSR-4应用最广泛,通过Composer一句命令就实现。PHP官方制定了很多编码规范,主要是防止一些PHP程序员瞎写代码。具体规范地址看这里 https://www.php-fig.org/psr/
PSR-0 早期版本的按需加载规范
<?php function autoload($className) { $className = ltrim($className, '\\'); $fileName = ''; $namespace = ''; if ($lastNsPos = strrpos($className, '\\')) { $namespace = substr($className, 0, $lastNsPos); $className = substr($className, $lastNsPos + 1); $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; } $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; require $fileName; } spl_autoload_register('autoload');
PSR-4 最新版本的按需加载规范
PSR-4可通过composer实现(理论上所有PSR标准都能通过composer命令来自动实现)。composer.json信息如下:
{ "name": "lesliexiong/php-server", "description": "server", "authors": [ { "name": "layne", "email": "layne.xfl@gmail.com" } ], "require": { "php": ">=5.4.0" }, "autoload": { "psr-4":{ "Layne\\Taobao\\": "src/" } } }
然后将 TestA.php 和 TestB.php 放到src目录下,最后执行
composer install
,所有按需加载的代码会自动生成。之后任何人都可以访问封装好的TestA.php 和 TestB.php。像Yii2,Laravel5这种上万个源代码文件的项目就是使用Composer做管理并共享组件。下一章我将详细说说composer的使用。
四. 写在最后
为了解决按需加载、命名冲突、包管理这些问题,我们引出了命名空间和PSR标准,这些标准都来之不易,每一位PHPer都应该掌握。代码千万行,注释第一行,编码无标准,家人两行泪。