从毕业论文到开源项目:我的PHP接口框架开发与优化历程

有一次,我的毕业论文题目是:旅游照片浏览与分享系统中Web Services子系统的设计与实现。当时我正在和团队一起开发一个旅游照片浏览和分享系统,其中我负责界面子系统的开发。因此,迫切需要一个合适的接口开发框架来支持项目的业务发展。但通过发现和比较,当时国内成熟的、流行的、专注的专注于PHP接口的开源框架并不多。因此,我决定独立开发一个接口框架,一来丰富毕业论文的设计,二来填补PHP接口框架的空缺。

从毕业论文到开源项目:我的PHP接口框架开发与优化历程

这是我第一次正式接触接口服务系统的开发,从而与接口系统结下了深厚的渊源。毕业后进入职场,继续将自己的原始开发框架应用到实际的商业项目开发中,并不断完善和优化。经过一段时间,当框架稳定成熟后,我正式将其作为 PHP 开源框架发布。网上,名为:PhalApi,一个PHP轻量级开源接口框架。其口号是:帮您创造价值!

经过几年的成长,这个开源框架逐渐被开发者认可,并应用于电商、医疗、旅游、游戏等行业,为更多商业项目或非营利项目提供快速开发技术。基础知识和解决方案。除了补充比较完整的开发文档之外,我还编写了电子书《初识PhalApi:探索接口服务开发技术》并在图灵社区发布。

本章我们再次继续探索接口服务系统。可以说,这部分是《初识PhalApi:探索接口服务开发技巧》这本书的简化版,然后是简化版,只是提到的内容和重点会有所不同。一方面,本章更多地关注如何从业务角度开发高可用的接口服务系统。无论您是否使用框架,您都可以按照本章介绍的技术来设计和开发框架。另一方面,也是最重要的,本章将结合我多年的界面开发经验、一线开发现状以及大型企业级系统开发的制约因素来全面分享和讲解如何构建更加完善的接口体系,使其能够匹配复杂领域的发展需求,满足苛刻的特性,并能与其他业务系统产生共鸣,产生更大的凝聚力。

8.1 浅谈接口系统的分类

开发接口框架和开发接口服务系统之间存在着细微的区别。前者更关心如何直接帮助界面开发人员更好地完成具体的项目开发,为工程师提供友好的界面和设计,降低学习和入门成本,间接满足业务发展的需求。后者专注于领域业务的开发,面临业务逻辑处理和业务需求的快速迭代,同时还要兼顾客户端调用、场景使用以及系统之间的对接。

8.1.1 按技术分类

根据随着时间的推移使用的通信协议,可以获得对应协议背后具有不同实现的接口系统。早期使用SOAP(简单对象访问协议),与XML可扩展标记语言一起用来构建Web Services。当时我的毕业论文设计使用的技术主要是Web Service,但目前在实际的商业项目中很少使用。只有极少数国内企业或国外科技公司可以继续使用Web Service。随后,业界出现了RPC(Remote procedure Call)远程过程调用协议,对应的开发类库是PHPRPC。与Web Service相比,PHPRPC会更加轻量级。

现在大部分的接口系统都采用了大家熟知的HTTP协议,再加上比XML格式更加简洁的JSON格式,组合成:HTTP+JSON+API。这种实现方式也是我毕业以来在专业发展过程中看到的应用最广泛的实现方式,包括但不限于开源框架、个人项目、初创公司、中小型接口系统、大型企业。 Level中间层接口。因此,在本章的高可用接口服务系统中,我们也重点讨论这种实现方法。

另一方面,如果按照接口系统的规范和设计理念来分类,可以分为RESTful和微服务。 RESTful是一种软件架构设计风格。其核心思想是:一切都是资源,并制定了相应的规范。如需了解更多信息,建议阅读《RESTful Web APIs》一书。微服务是Martin Fowler大师近年来提出的概念,并被广泛流传和引用。如果您之前没有接触过微服务,您可以关注微服务的这些重要特性:

微服务可以说是针对当前接口系统发展混乱的一剂良药。他们为我们开发界面系统提供了很多启发性的帮助。因此,我们的章节也以微服务为根本指导思想,致力于为客户提供高可用的接口服务。

8.1.2 按客户分类

之前我们按照接口系统内部的实现技术或者设计方法来对技术进行分类。另一方面,也可以根据调用客户端进行分类。这种分类的方式让我们能够更加关注业务端、客户端使用场景和业务需求进行考虑和设计。如果说,对于企业来说“客户就是上帝”,那么对于我们的服务器开发工程师来说,我们也应该秉持“客户就是上帝”的服务态度。接口服务应该服务于客户端,而不是限制客户端。不能是“我们的服务器有什么,你的客户端就用它”,而应该提倡“我们的客户端需要什么,我们的服务器就提供”。

为什么这么说是因为界面系统开发团队和客户端开发团队属于不同的管理部门。在日常的跨部门协作沟通过程中,经常会出现优先级不一致或者沟通不畅的情况。更糟糕的是,有时会让双方的开发团队都不高兴。因此,作为服务器端开发者,我们应该始终以友好的态度为客户服务,尽力提供客户需要的接口服务,无论内部实现有多么困难。

言归正传,主要的客户端有:浏览器(包括PC和M)、App移动应用、服务器系统。

如果客户端是浏览器,那么我们就需要和前端开发工程师对接。他们使用的主要编程语言是Javascript,需要仔细注意浏览器兼容性问题,同时兼顾PC版和M版。或者是出于前后端分离的考虑,又或者是传统异步加载实践的驱动,我们需要向浏览器提供JSON格式的数据接口,以便浏览器客户端可以进行Ajax调用。这时,根据客户端的特点,需要考虑是否存在跨域问题,是否需要支持JSONP并防止XSS攻击,是否需要将整个站点切换为HTTPS协议,以及HTTP协议下的兼容性和过渡考虑,以及接口结果是否适合使用CDN缓存。还有它的紧急刷新机制……更重要的一点是安全设计。一方面,要防止恶意爬虫抓取重要业务数据。另一方面,获取和操作用户敏感个人信息时需要登录。身份验证。

如果客户端是App移动应用,安全性会比较高。由于客户端和服务器之间可以进行签名验证,虽然存在被逆向工程破解的可能性,但与Javascript的源代码相比,使用Java开发的Android应用程序和使用Object-C/Swift开发的iOS应用程序编译后更难识别,更难破解。同时,移动应用程序可以说更加灵活,因为它们可以很大程度上摆脱浏览器的约束和限制。例如App可以通过WebKit等方式嵌入H5页面。但对于移动应用来说,我认为最值得关注的问题是不同版本的App应用与服务器接口之间的兼容性问题。一定要记住,在开发新版本App、升级接口系统的接口服务时,一定要考虑与旧App版本的兼容性。如有需要,还应提供不同编程语言的SDK包,方便客户快速开发和接入。

最后,我们有一个特殊的客户端,它不是最终用户直接使用的应用程序、页面或软件,而是也运行在服务器上的系统。也就是说,此时的客户端本身就是一个服务器系统,很可能也是一个接口服务系统。如果客户端系统和服务器接口系统都是同一个公司的系统,那么就需要考虑内网部署,甚至部署在同一个机房​​,也就是我们常说的内网接口服务系统。如果提供给外部公司或者第三方系统访问,就要考虑是否搭建开放接口平台。比如国外的Facebook、Twitter、亚马逊开放平台,还有国内的微信开放平台、支付宝开放平台、新浪微博开放平台等。此时,访问控制、权限控制、配套计费系统和控制台应考虑管理接口。

上述两种分类方法是按技术分类和按客户分类。前者帮助我们梳理了接口体系内部实现的要素和要点,而后者则为我们提供了构建更加完善、配套的接口服务生态的机会。更多的想法。

8.2 面向世界的界面输入

本节主要讨论接口的输入,包括接口参数验证、电子签名、身份验证、权限控制等安全主题。同时,我们也扩展和延伸了配置优于实现这一思想的讨论。接口的输入是客户端和服务器之间的桥梁,也是两者之间的安全检查环节。我们不仅要保证两端通信的友好性,还要保证服务器不被非法请求攻击或损害。

8.2.1 接口参数验证我对接口参数验证的思考

第一次正式实习的时候,我开始参与接口系统的开发,也就在那个时候我正式接触到了接口参数的概念。当时我发现了一个很有趣的现象,那就是接口项目代码中充斥着接口参数验证、解析、处理、原创的重复代码。参与维护这些代码的同事不知道是否没有意识到这些代码味道,或者没有发现接口参数验证背后的共性。他们没有对这部分的实现做任何改进、优化或者总结。

不过,秉承着KISS原则和DRY原则,出于对接口参数验证这个小领域的兴趣,我开始尝试思考、分析和设计这部分。我希望能够合理、恰当地封装接口参数的所有操作,包括判断参数是否传递、设置默认值、类型转换、数据合法性检测,甚至接口参数描述和文档的结合。经过一段时间的努力,接口参数验证的封装已经初具规模。我在日常项目开发中应用它,大大消除了重复代码,并且更加优雅地完成了接口参数的操作。

当然,我也希望我对接口参数验证的细化也能够提供或者开放给更多的技术开发者。他们只需编写一行代码,甚至无需编写代码,只需进行配置即可实现他们期望的操作。在幕后,接口参数验证将自动且稍微智能地完成剩下的工作。如果可能的话,我希望这些能够成为一个规范,一个被广泛接受的协议。

带着这样的初心,从实习到毕业设计,从全职从事各种商业项目和系统的开发到正式加入开源社区,最初的接口参数验证,经过不断的演变和逐步完善,如今已成为PhalApi是开源接口开发框架中不可缺少的一部分。 PhalApi中提供的参数规则提供了接口参数规则的友好定义、配置和描述。通过参数的规则配置而不是为参数编写原始代码,显着提高了开发效率和质量,受到很多开发者的青睐。另外,这些参数规则还可以用于自动生成接口在线文档,一举多得。正因为如此,这些参数规则已经成为PhalApi开源框架中的规范和标准,甚至有开发者将这个模块提取出来应用到其他项目和框架中。

下面,我们以一个简单的登录界面为例,大致了解一下PhalApi框架的参数规则。如果您使用的是PhalApi 2.x版本,在接口实现类中,可以使用下面的配置示例来控制登录界面的两个参数,账号和密码。

 array(
                'username' => array('name' => 'username', 'require' => true, 'min' => 1, 'max' => 50, 'desc' => '用户名'),
                'password' => array('name' => 'password', 'require' => true, 'min' => 6, 'max' => 20, 'desc' => '密码'),
            ),
        );
    }
}

这里,对于/?s=User.Login接口服务,配置了两个参数,一是账户用户名,二是密码password。根据上面的配置不难理解,账号和密码都是必填参数,并且账号的最小长度为1,最大长度为50;密码长度在6到20之间。

class User extends Api {
    …… 
    public function login() {
        $username = $this->username;   // 账号参数
        $password = $this->password;   // 密码参数
        // 更多其他操作……
    }
}

配置好参数规则后,获取参数就非常简单了。因为只需要使用类属性即可获取对应的参数。如上所示,$this->username 和 $this->password 分别代表账户参数和密码参数。这些类成员属性名称对应于参数规则中的数组下标值。

如果客户端传递了有效的账号和密码,相应的参数就会自动填充到类成员属性中。如果客户端不提供任何参数,接口服务会返回类似如下的错误信息:

{
    "ret": 400,
    "data": [
    ],
    "msg": "客户端非法请求:缺少必要参数username"
}

除了参数解析和验证之外,通过参数规则,还可以指定数据源,比如限制为POST参数、GET参数或者COOKIE等。如果现有的参数规则类型不能满足项目的开发需求,您也可以自行扩展添加所需的参数类型。和其他开源框架一样,PhalApi的参数规则也可以支持回调函数的配置和调用。综上所述,内置参数类型共有9种,分别是:

更酷的是,框架会根据配置的参数规则自动生成在线文档。刚才配置的账号和密码参数与自动生成的文档对应。部分截图类似如下:

图8-1 帐号和密码参数

以上都是参数规则的外在表现,但我更多想跟大家分享的是其背后的实现、设计和思路。可以说,整个参数规则系统是围绕可重用​​性、易用性和友好性构建的。从大到小、从上到下,简单概括一下关键环节是:

首先,我们需要在框架核心执行流程的适当位置添加参数规则的入口调度。这个机会在接口类基类 PhalApi\Api::createMemberValue() 方法中。它的任务是根据配置的参数规则将客户端传递的参数转换为接口类的成员属性。

图8-2 PhalApi 2.x的核心时序图

打开PhalApi 2.x的源码,可以看到这里的实现只是一个foreach循环,从概念角度来说是高度抽象,而其背后则是规范角度和实现角度的细节。我们先看一下入口源码怎么写:

getApiRules() as $key => $rule) {
            $this->$key = DI()->request->getByRule($rule);
        }
    }
}

注意,这依赖于另外两种实现,一种是多组参数规则的合并,另一种是根据规则获取参数。将多组参数规则的组合实现放入接口类中是经过精心设计的。因为参数规则的配置本身直接来自于接口类的getRules()钩子方法,即来自于接口实现子类本身,更进一步,这里的参数规则还可以进一步细分为通用接口参数规则和指定接口参数规则。其次,还有一个原因是,在接口类内部处理配置文件./config/app.php中的apiCommonRules公共参数比较合适。最后,对于配置了白名单的接口服务,可以在接口类层进行临时的动态调整。仔细一看,这是一个巧妙的设计。

接下来让我们把放大镜移到 PhalApi\Request::getByRule() 上,继续探索根据规则获取参数的奥秘。此时,参数获取操作将在请求类Request中完成。将其放在Request类中是有原因的。首先,请求类包含来自各种来源的参数,例如:$_POST、$_GET、$_COOKIE、$_SERVER、$_REQUEST,从中可以直接提取所需的数据源;第二,如果缺少必要的参数,可以在这个环节直接抛出异常,并向客户端给出相应的错误提示;最后,我们还可以验证服务器上配置的参数的合法性,并完成对底层的调用。下面的源码很好的解释了这三个含义。

 '', 'type' => '', 'defalt' => ...) 参数规则
     * @return mixed
     * @throws BadRequestException
     * @throws InternalServerErrorException
     */
    public function getByRule($rule) {
        $rs = NULL;
        if (!isset($rule['name'])) {
            throw new InternalServerErrorException(T('miss name for rule'));
        }
        // 获取接口参数级别的数据集
        $data = !empty($rule['source']) && substr(php_sapi_name(), 0, 3) != 'cli' 
            ? $this->getDataBySource($rule['source']) 
            : $this->data;
        $rs = Parser::format($rule['name'], $rule, $data);
        if ($rs === NULL && (isset($rule['require']) && $rule['require'])) {
            throw new BadRequestException(T('{name} require, but miss', array('name' => $rule['name'])));
        }
        return $rs;
    }
}

继续放大,靠近第四个链接中的统一格式化操作。考虑到大多数情况下,string字符串是使用频率最高、最常见的类型,因此如果不指定参数类型,则默认类型为string。如果没有指定参数默认值,则默认值为NULL。完成了这些必要的初始化和准备工作之后,就到了体现PHP动态脚本语言优势的时候了。根据开发者配置的参数规则中的类型,可以组装成给定的格式化具体实现类名,结合依赖注入,实现单例模式下格式化类实例的资源管理,最终调用下接口PhalApi统一接口规范\Request\Formatter::parse()。为了本章的完整性,请允许我发布相关的源代码片段。

get($diKey, $diDefautl);
        if (!($formatter instanceof Formatter)) {
            throw new InternalServerErrorException(
                \PhalApi\T('invalid type: {type} for rule: {name}', array('type' => $type, 'name' => $rule['name']))
            );
        }
        return $formatter->parse($value, $rule);
    }
}

最后一步,我们终于到达了剧情的结局。前面提到,PhalApi 2.x 内置了 9 种参数类型,默认类型为 string。因此,以字符串格式化实现类StringFormatter为例,举一反三,了解其他8个格式化实现类的思路以及自定义扩展格式化类的开发规范。

filterByStrLen(strval($value), $rule));
        $this->filterByRegex($rs, $rule);
        return $rs;
    }
}

这一层属于实现角度,更多的是实现细节,包括但不限于参数的类型转换、范围检测和反序列化解析。

整个设计最终映射到代码模型。涉及到的类、依赖和包嵌套层次,可以参考使用PHPDoc生成的图形。

图8-3 参数规则格式化类图

至此,我们已经很快了解了PhalApi 2.x中参数规则的实现思路和微架构设计。在《PhalApi初识》一书中,我详细介绍了如何使用参数规则。这里我重点分享一下如何从框架的角度为技术开发者设计一套友好易用的参数规则。如果您也打算这样做,可以参考这里的方法。

Yii的语句验证规则和Symfony的验证

当然,与其重新发明轮子,更推荐的做法是直接使用开源框架提供的验证器。例如,Yii的语句验证规则包括CFormModel表单验证和CActiveRecord模型验证;另一个例子是 Symfony 的 Validation 验证。从某种程度上来说,验证是一个值得深入研究的细分领域,很多优秀的资料都在这方面进行了介绍和总结。

接下来我们简单回顾一下或者了解一下 Yii 和 Symfony 是如何使用参数验证的。

摘自 Yii 1.1 Definitive Guide,有以下示例:

class LoginForm extends CFormModel
{
    public $username;
    public $password;
    public function rules()
    {
        return array(
            array('username, password', 'required'),
            array('password', 'authenticate'),
        );
    }
    public function authenticate($attribute,$params)
    {
        ……
    }
}

在登录表单中,为帐号和密码配置了相应的规则。账号和密码都需要,密码会通过LoginForm::authenticate()进一步验证。

PhalApi 的参数规则很大程度上受到 Yii 的启发。因为Yii中有很多功能是可以通过配置来实现的,除了表单验证之外,还有模型、视图组件等,这些配置虽然强大,但是强大到有点复杂,难以记忆和使用。每次使用Yii开发时,我都要搜索官网寻找类似的示例代码或者查看类手册来了解如何配置。因此,在设计PhalApi参数规则时,我基于Yii的启发做了一些创新,力求尽可能的易用和简单。顺便说一下,很多著名画家都是从临摹别人的作品开始的。只有掌握了之后,再加入自己的想法,才能设计出更加精美的艺术品。同样的,在设计框架的时候,也应该这样做。只有集百家之长,博采众长,才能开发出更有思想、更感人的项目。

另一方面,摘录Symfony官方文档,我们也可以看到表单验证的相关配置。

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('myField', TextType::class, array(
            'required' => true,
            'constraints' => array(new Length(array('min' => 3)))
        ))
    ;
}

即使你从未使用过 Symfony 框架,也不难理解上面配置的含义:对于字段 myField,指定了文本类型,并且是必填字段,最小长度为 3。多么容易去理解。反向恢复很容易,正向使用也很容易。

8.2.2 约定优于配置、配置优于实现

配置的魅力实在是太大了,不得不单独用一节来总结和分享。简而言之,本节基于这样一个前提:我们都认同编写易于人类理解的代码的价值。通过这个值配置的是绿色通道。

首先,约定胜过配置。

Ruby 是一种优秀的编程语言,它处处体现了约定优于配置的理念。您可以通过一个简单的代码示例来理解这一约定。照常操作:

def say_hello(name)
    # 先打声招呼
    puts "hello #{name}"
end

对于返回布尔类型的操作,请在方法名称后添加问号。

def more_money?()
    # 假设有点钱
    return true
end

对于一些可能引发异常的危险操作,方法名称后会附加一个感叹号。

def borrow_me!(money)
    #  假设钱不够
    raise "Not enough momey!"
end

串联起来,我们假设有一个这样的用户故事:你好某某,你有钱吗?借我一些吧!那么对应的代码片段可以表示为:

say_hello '某某人'
borrow_me! 100 if more_money?

对应的运行效果类似如下:

hello 某某人
./test.rb:13:in `borrow_me!': Not enough momey! (RuntimeError)

通过这样一个简单的案例,你就可以体验到Ruby元编程语言下的约定和实践。这些大大小小的约定不仅不会增加开发者的认知负荷,而且构成了Ruby开发者共同的开发语言。无需太多解释,Ruby 程序员就能很快破译这些符号和约定背后的含义。

其次,配置优于实施。

可以说,有了正规的开发团队,合作就会更高效,沟通也会更顺畅。因为大家都能很快理解简洁的代码所体现的意图和目的,不存在混乱和困惑。如果开发语言缺乏功能支持,或者开发团队缺乏一致的编程氛围,你可以退而求其次,采用配置优先于实现的方法。

关于配置方法,可以看JAVA中使用注解的例子。如果您需要运行一组单元测试,可以这样做:

    @RunWith(Suite.class)  
    @Suite.SuiteClasses({    
        Test1.class,   
        Test2.class,  
        Test3.class,  
        TestSuite2.class  
    })  
    public class TestSuite1 {  
    }

相当于JUnit 3中的以下实现代码:

public class TestSuite1 {  
    public static Test suite() {  
        TestSuite suite = new TestSuite("Test for package1");  
        suite.addTest(new JUnit4TestAdapter(Test1.class));  
        suite.addTest(new JUnit4TestAdapter(Test2.class));        
        suite.addTest(new JUnit4TestAdapter(Test3.class));  
        suite.addTest(new JUnit4TestAdapter(TestSuite2.class));  
        return suite;  
    }  
}

你可能想看:
« 上一篇
下一篇 »

相关推荐

php实时推送,php实地培训

2026年10月05日

53阅读

php乱码出现问号的原因,php乱码出现问号的原因有哪些

2026年10月05日

44阅读

php一句话木马,一句话木马ctf

2026年10月05日

48阅读

php获取数组长度,php中如何获取数组的长度

2026年10月05日

50阅读

php面试题库及答案,php程序员面试真题库

2026年10月05日

47阅读

phpcontent-type修改,php源码二次修改需要多少钱

2026年10月05日

42阅读