PHP 进化史:Personal Home Page Tools 梦开始的地方

PHP , 原理1637 字

Personal Home Page Tools

1.0.8 是我们在官网上能够下载到的最古老的版本,虽然这个时候也叫 PHP,但是和现在的 PHP 意思是完全不同的,下面这张图是在源代码中的截图。

2023-03-13T08:49:59.png

PHP Tools - Personal Home Page Tools 个人网站工具集,严格意义上来说,这个时候的 PHP 只是一个 CGI 的工具集,它包含了一个非常简单的语法分析引擎,只能理解一些指定的宏和一些Home Page后台的常见功能,如留言本,计数器和一些其他的素材。

2023-03-22T13:01:01.png

Rasmus Lerdorf,就是这个家伙,在 1995 年仅用了一个下午就完成了 PHP 的第一个版本,对我们的生活其实产生了深远的影响。在 2004 年 PHP 更是成为了年度语言,并且在其后很长的一段时间内保持着前五,虽然近些年来 PHP 一直在被唱衰,甚至在国内大厂不断地用 Go 代替 PHP 的环境下,在 2023 年仍然没有跌出前十。

PHP 源代码下载,历史的归档源代码我们可以到 https://museum.php.net/ 这里进行下载。

2023-03-13T11:15:18.png

我们用 cloc 分析一下源代码发现,这个时候的 'PHP' 其实只有两千多行代码,其中真正用 C 语言实现的代码共计是 2455 行,等到了php2.0.1 的时候, C 语言的代码量就已经打到了进阶 58957 行。

2023-03-13T11:13:10.png

和现在的 PHP 代码行数完全不是一个量级的,这是因为在当年大多数都是静态页面,而开发 PHP 的目的就是为了统计网站的访问人数。

这个时候的 PHP 是如何执行的呢,又或者 CGI 程序是怎么回事?

用 Shell 也能写网站

这里不会跟朋友们扯太多空洞洞的理论,因为理论知识很容易就可以获取到,随便看几本书就可以了,或者去百度百科,都会有非常详细的介绍。

在上世纪 90 年代互联网刚刚起步,那个时候大家都是在用 html 搭建一个个人简历或者是公司简介,这个时代就是 web1.0 时代,很快 CGI 出现了,拜 CGI 之赐,网站不再只有固定不变的图形和文字,借由程序动态产生的网页可以让网站好象『活』了起来。小从简单的网页计数器,留言版,大至处理众多资料的搜寻引擎,可做线上实时交易的电子商务、网络下单等。CGI 简单、开放、跨平台、与程序语言独立的特性,使得编写网站应用程序变得很容易。

  1. 用户访问apache
  2. apache 调用 cgi程序,在这个过程中会将请求数据发送给 cgi 程序。
  3. cgi 输出执行业务逻辑并且输出页面。

按照这个原理,其实任何语言都可以编写 cgi 程序,下面我们用 shell 来编写一个打印 get 参数的 echo 服务。

#!/bin/bash

echo 'Status: 200 OK'
echo 'Content-Type: text/html; charset=utf-8'
echo 'Cache-Control: no-cache'
echo 'Pragma: no-cache'
echo
echo '<html>'
echo '<head>'
echo '<h1>echo query:</h1>'
echo '<p>'
echo  $QUERY_STRING
echo '</p>'
echo '</body>'
echo '</html>'
echo

2023-03-13T11:37:26.png

PHP 的使用案例

这个时候 PHP 提供了四个 cgi 程序:phpl.cgi, phpf.cgi, phplmon.cgi, phplview.cgi

其实这个时候的 PHP 就已经在简化 HTML 开发了,其中提供了一些内置函数可以帮我们进行模板替换。

<ul>
  <li>$today = <!--$today--></li>
  <li>$version = <!--$version--></li>
  <li>$_GET['name'] = <!--$name--></li>
  <li>system load:<pre><!--! w|head -n1 --></pre></li>
</ul>

<br>This page has been accessed a total of <a href="phplmon.cgi"><b>3</b></a> times now! <b>3</b> times today.<br>Page was last updated on Dec 22, 2021 at 7:43.</center></font>

2023-03-22T13:44:38.png

我们可以看到 <!--$today--> <!--$version--> 都已经被 phpl.cgi 程序给替换掉了。

PHP 源代码分析

PHP 的目录结构

这就是这个版本的 PHP 主要执行流程。我们来看一下代码目录

.
├── License
├── README
├── build
│   ├── phpf.cgi
│   ├── phpl.cgi
│   ├── phplmon.cgi
│   └── phplview.cgi
├── common.c
├── common.h
├── config.h
├── error.c
├── html_common.h
├── phpf.c
├── phpl.c
├── phplmon.c
├── phplview.c
├── post.c
├── post.h
├── subvar.c
├── version.h
├── wm.c
└── wm.h
  • common.c & common.h 一些公共函数的声明,我们看到有很多函数其实是没有.h 声明的,其实就放在了这里。
  • config.h : 相当于php.ini 用来存储 PHP 相关的配置信息
  • html_common.h && error.c :公共函数,这个名字起的让人很迷惑,里面有处理html_error 的处理函数,也有公用头部函数。
  • phpl.c
  • phplmon.c
  • phpview.c
  • phpf.c
  • subvar.c :变量替换函数
  • version.h :存放了 PHP 的版本信息,作者名字。
  • vm.c & vmc.h :字符串匹配函数,有趣的是这段代码并不是 Rasmus Lerdorf 写的,而是 COPY 过来的,并且他在代码中感谢了 Justin Slootsky。这说明无论是什么人都会有自己的短板。

在这里的 build 文件其实是不存在的,这是为了方便编译我自己就修改了 Makefile 文件。

PHPL.C 的执行流程

  1. 初始化变量
  2. 获取 Post 数据,如果有则初始化 Post 链表,如果没有则不执行
  3. 校验参数,也就是 CGI 程序发过来的 argv,校验不通过则渲染错误 html
  4. 渲染(其实就是标准输出)HTML,其中包括模板解析,替换等操作
  5. 释放Post 链表,结束程序

PHP 的配置文件

我们先来看配置文件 config.h :

define ROOTDIR "/usr/somewhere/user_id/public_html"
define HTML_DIR "html" 

#define LOGDIR "logs/"

#define ACCDIR "logs/"
#define NOACCESS "NoAccess.html"

这个时候的配置文件可以说是相当简洁了,只有四个参数,所以根本不需要独立的配置文件,我们只需要在编译之前进行修改就可以了。

  • ROOTDIR:项目根目录,在例子中,我们使用 phpl.cgi?var.html, var 就存储在根目录下,如果没有设置的话,回去找 apached 的默认配置,并且增加上 /public_html。
  • HTMLDIR:如果你不想选择使用 rootdir 也可以利用这个进行指定目录
  • LOGDIR:日志
  • ACCDIR:日志
  • NOACCESS:无法访问调转到指定页面。

PHP 的模板替换

在 phpl.c 中定义了两个宏。

#ifndef STARTSEP
#define STARTSEP "<!--"
#endif
#ifndef ENDSEP
#define ENDSEP   "-->"

这两宏的作用其实就是代表着模板替换的开始和结尾。

在这个时候,其实 PHP 有点类似于 Smarty 模板引擎,在 PHP 中预设了很多变量,当加载 HTML 的时候,如果发现 <!-- --> 模板标记,就会将其中的内容替换成已经赋值的变量

2023-03-22T13:47:28.png

我们可以看到截图中代码替换,其实就是检测到 $total 就替换成已经实现准备好的变量。

  • 在 632 和 637 行 其实就是去截取 <!-- --> 中的内容
  • 查找到对应的标记进行替换。

在 651 行中有 $today,我们以此举例。

static int todays_cnt;

在 phpl.c的最上方定义了静态变量 todays_cnt,其实该记录存储的就是今日的访问记录。

if(fgets(cntbuf,127,fp)) {
    s=strchr(cntbuf,' ');
    if(s) *s='\0';
        cnt=atoi(cntbuf);
        if(s) todays_cnt=atoi(s+1);
    } else {
    cnt=0;
    todays_cnt=0;
}

PHP 中的 Post 数据

用链表存储 Post 数据

2023-03-22T15:43:09.png

在 phpl.c 中第 88 行定义了一个全局静态变量, pdtop 其结构定义在 post.h 中。

typedef struct _postdata {
    char    var[32];
    char    val[128];
    struct _postdata *next;
} postdata;

这是一个链表,第一个 char 存储的事 post 的 key,第二个 char 存储的是值,在这里我们可以看出,在这个时候 PHP 最大只能接受 128 个字符。

获取 post 数据

在 cgi 协议中 http 请求中的数据是通过环境变量交给 cgi 程序的。

2023-03-22T15:37:12.png

Post 数据的释放

在我们申请内存后,必须对其进行释放,否则就会造成内存泄露,当 php 执行完成后会对已经申请过的内存地址进行释放。

2023-03-22T15:46:01.png

其实操作非常简单,就是判断第一个节点是否为空,如果不为空则证明有 post 的数据,然后开始遍历链表,释放掉所有申请的内存地址。

参考

maksim
Maksim(一笑,吡罗),PHPer,Goper
OωO
开启隐私评论,您的评论仅作者和评论双方可见