选择高起点(下),PHP框架之争
图片来源网络
2.3 项目分类
在前面做了那么准备工作后,接下来再来简单认识下我们通常需要开发哪些类型的网站项目。因为不同的项目,其做法和要求也不尽相同,它的关注点和所遇到的问题也会有所区别。根据实际的开发经验,在商业中,一般会接触到以下这几类项目:前台网站系统、后台管理系统、计划任务系统、接口服务系统。下面分别简单介绍对比。
2.3.1 前台网站系统
面向最终消费者用户人群,对用户体验和人机交互要求高。为了响应市场的快速变化,需要频繁进行版本迭代和更新。与此同时,伴随着网站的不断成长,注册人数的攀升,对系统的稳定性、吞吐量、高并发、安全性等非功能性要求会越来越苛刻。因此所面临的挑战和压力都是非常巨大的。如果你有机会参与此类项目系统的开发,将能得到很好的历练。
2.3.2 后台管理系统
主要面向特定内部人群,小众群体。例如提供给公司内部的运营人员使用的运营平台,开放给第三方供应商使用的仓库管理平台,企业使用的ERP管理系统等,提供给技术人员使用的监控平台系统。这类系统,主要更关注的是数据的准确性,以及数据流和对应的工作流程,需要进行不同角色下的权限控制。能够容忍一定的延迟,并且对UI设计要求不高。重点在于能很好帮助人们进行高效地工作,有效地进行对系统的管理和对数据的维护。
2.3.3 接口服务系统
当业务逐渐扩张时,系统也会从原来的单个网站,慢慢发展成具备由多个子系统、多种异构系统共同构建而成的企业级系统生态圈。这时,接口服务系统会应运而生。面向的服务对象是前面的前台网站系统、后台管理系统,以及其他接口服务系统,甚至是将要讲到的计划任务系统。主要是为其客户端提供可用的接口服务,以完成特定的功能或服务,实现对数据的处理、查询、存储和加工、校验等。本质上为了让数据和信息能在各系统之间更有效地流转,而这些系统的边界可能是小到只是公司内部的通讯,也可能会大到跨国跨企业跨行业之间的通讯。接口服务系统充当着不可或缺的角色,为多终端设备提供了统一的接口服务,但是一旦出现问题,影响面将会是巨大的。因此需要做好降级、容灾、监控等一系列事宜。
2.3.4 计划任务系统
最后要介绍的是计划任务系统。这是一类神秘的系统,前台系统和管理系统都能通过网页的界面进行访问和操作,接口系统也起码可以有返回和输出。但是计划任务系统是最神秘的系统,因为基本上它是看不见,摸不着的。它会在背后默默帮你处理完成全部任务、事务和通知等工作。似乎看起来它可以帮我们做很多事情,但由于其看不见的特性,往往难以调试,一旦出问题也难以重现和排查。而一旦发生故障,其影响面又是深远的,因为它会对数据进行错误的处理而产生脏数据,使整个系统陷入数据紊乱状态。更让人担忧的是,系统会基于这些脏数据产生二次脏数据,如此循环,不断恶化。所以,对这类系统的开发一定不能掉以轻心。
最后,简单对比一下这四类项目,便于在日后进行项目开发时有点针对性,并做出相应的决策和管理措施。
表2-1 四种项目的对比
项目类型 |
面向的人群 |
图形化界面 |
特点 |
---|---|---|---|
前台网站系统 |
最终用户,终端消费者 |
简洁或者设计标准极高的网站页面 |
关注用户体验,对功能性需求和非功能性需求要求都很高 |
后台管理系统 |
内部人群,小众人群 |
可以接受水平一般的UI设计和延时 |
重点在于能很好帮助人们进行高效地工作,有效地进行对系统的管理和对数据的维护 |
接口服务系统 |
客户端是其他系统,而不是人 |
不需要界面 |
通过接口服务对外提供数据或服务,为多终端设备提供了统一的接口服务,稳定性要求高 |
计划任务系统 |
通常没有服务的对象,自已完成后台异步计划任务 |
无界面 |
在背后完成特定任务,但往往难以调试,一旦出现问题,将会产生脏数据,影响深远 |
如果再结合前面刚学到的技术,就可以发现前台网站系统、后台管理系统和接口服务系统都是通过php-fpm的CGI模式来访问的,而最后的计划任务系统则通过CLI命令行模式来访问。如果你知道不同运行模式下的差异,就能很容易在今后的项目开发中避开或者解决同类的问题。
2.3.5 最好不要这么做
如果在项目开发的前期,我们可以预见并意识到需要同时开发这几类项目系统时,尽早将不同类型的系统分开是很有好处的。最好不要把前台网站系统、后台管理系统、接口服务系统和计划任务系统都放在同一个站点,同一个项目内。比如,我们需要开发一个网上商城,不要把商城的官网,商品管理后台、提供给第三方供应商系统调用的接口系统、用于进行数据分析和运营推广的计划任务系统都堆砌在一起。更为常见的现象是,前台网站系统会和后台管理系统放在一起,这在前期是可以的。但一旦项目发展到某个程度时,尽早拆分可以有效进行风险管控。
2.4 框架之争
前来应聘的同学,经常会问这样一个问题:贵公司使用的是会哪个开发框架?
关于这个问题,作为面试官,我的回答是:公司目前采用的框架有多种情况。有的项目完全没有使用框架,有的项目使用的是内部框架,有的项目则使用开源框架,甚至有些项目使用的是基于已有框架改良提升过后的衍生框架。
那么在最初开始时,我们又应该怎么选取开发框架呢?是完全不用框架,还是自主研发框架,还是使用开源框架?
完全不用框架是不可取的,因为会导致开发速度超慢。除了开发业务功能之外,还要重复搭建通用基础设施。随着业务需求的不断增长与变化,可能原来简单封装的类库会频频出现意想不到的问题和限制业务的发展。更重要的是,很多前人已发现容易触发的问题,都会逐一在我们身上重现。但是,自主研发框架更是成本高昂。它需要你掌握更多更全面的知识,并要求具备一定的项目经验,才能设计并研发出经得起实际考验的开发框架。
更好的建议,对于初学者或者中级和初级开发工程师来说,选择开源框架是个不错的选择。开源框架通常都是代码质量优异,普及度范围广,有活跃交流社区和充分文档说明的。
如果选择了开源框架,那么新问题又来了。那么多开源框架,应该选择哪一个呢?PHP开源框架,随便都能罗列出很多选择。例如:Yii,Laravel,Symfony,CodeIgniter,Phalcon,ThinkPHP,Zend Framework,Slim,PhalApi等等,而这只是长长清单里面的一小部分。
框架没有好坏之分,还是要以合适为主。要根据将要开发的项目类型,以及需要实现的业务功能,选取与之匹配的框架,同时还要考虑当前团队的熟悉程度和兴趣偏好,最后进行综合权衡、考虑、选择。
选定某个开发框架之后,根据其官方文档的入门教程,下载、安装、部署后即可马上开启我们的项目开发。
2.5 开始编写第一行代码
等了那么久,终于可能开始编写我们的第一行PHP代码了。
2.5.1 PHP专业素养
在编写代码过程中,可能会遇到很多新的问题,关于PHP语法,关于当前使用的开发框架,或者关于其他方面。尽早全面学习一下PHP的语法是很有必要的,你可以在网上找到完整的入门教程,也可以找到PHP编程技术进阶的书籍来学习。如果对任何PHP的函数,或者PHP的知识有疑问,都可以随时查看PHP官网文档。
专家技巧:有疑问,随时翻阅PHP官网文档:http://www.php.net/。
虽然PHP学习门槛很低,但是大家千万不能对PHP掉以轻心,因为里面还有很多微妙的知识点需要综合全面地学习,并牢牢掌握,才能轻松应对开发过程中各种疑难杂症。下面我们就通过一道真实的PHP面试题来窥探其中的奥妙。
一道微妙的PHP面试题
先来看下一道简单却非常微妙的PHP面试题。哪怕是有丰富经验的开发人员也会回答错误,因为其中牵涉的理论、知识很多。
题目如下:请问,你觉得以下代码会输出什么,并解释为什么。
$str = 'php';
$str['name'] = array('dogstar');
var_dump($str);
如果你不通过执行上面代码,就能准确无误说出正确的答案,至少说明:
你是一个细心的人
对PHP语言的理解非常到位
但实际情况是,在面试过程中,很多人都回答不出正确的答案,更无从解释其中的原由。
回答最多的答案(但是错误的)是:输出一个数组,即:array('dogstar')。他们认为,把一个字符串当作数组使用时,会把这个变量转换为一个数组类型,从而得到这个结果。这时候,我是表示很怀疑的,怀疑他是不是把以前所学的编程语言知识都丢了,或者根本没上过相应的专业课程逃课去约会了。把指针下的某个下标进行赋值,会改变当前指针所指的变量类型?!
也有人说直接挂掉,但这种说法并不多。那真正的答案是什么呢?希望读者在继续往下看之前,先自己停下来,思考一下如果是你来回答这道题,你会怎么回答,又如何解释?
解题过程细说
要想得知最终正确的答案,至少需要对PHP的字符串和数组有较为深刻的理解,以及PHP基本类型和变量的底层知识有一定的认识。我们可以把这道题,当作一道高中时代的方程式,一步步进行分解、拆解、转换,便可慢慢水落石出,最终得到正确的答案,而不应不加思索就臆断结果是什么。
编程语言,以及计算机都是理性的,更多是靠逻辑和推理,是客观的事实,而不是靠主观臆断。
首先,第一行代码完全没有异议,是把字符串“php”赋给了变量$str。但这正是“恶梦”的开始。
$str = 'php';
真正复杂、饱含细节的在于第二行代码,即:
$str['name'] = array('dogstar');
但在开始做题之前,需要回顾一些PHP的基本知识。虽然说是基本,但我却非常惊讶很多开发同学居然对此都了解不多,或者说看过但没记住或者理解,甚至完全没去接触过。
先来看下PHP的基本类型。PHP的基本类型有哪些?或者说,通常情况下,编程语言的基本类型有哪些?摘自PHP官方文档(链接:http://php.net/manual/zh/language.types.php),基本类型有:Boolean 布尔类型、Integer 整型、Float 浮点型、String 字符串、Array 数组、Object 对象、Resource 资源类型、NULL、Callback / Callable 类型。
其次是对PHP数组下标的理解。PHP数组,实际上是HASH的实现方式。理解这一数据结构后,再来理解它的所提供的操作就顺理成章了。PHP数组里面的知识很多,但这里只说一个点:PHP数组的下标。
先来简单看下这份额外的代码:
$arr = array();
$arr[0] = 'A';
$arr['x'] = 'B';
$arr[1] = 'C';
$arr['y'] = 'D';
$arr[] = 'E'; // 没有写下标
问:最后E字母对应的数组下标是多少?为什么?不少同学,在回答这个问题时也卡住了。因为不知道PHP数组的下标的规则。
上面的问题回答不出来,对我们的面试题影响不大。但接下来这个问题就非常关键了:PHP数组的下标有哪些类型?如果这一点不清楚,那么会对很多PHP的函数和操作,都得不到透彻深刻的理解。
其实,这些重要的信息在PHP官方文档上都有记载。但平时在学习PHP过程中,很多人都是觉得看这些“又长又臭”的文档没意思,而且很花时间。此时更宁愿去网上搜索所谓的快速入门教程,但这些教程往往也是PHP开发新手编写的,他们虽然解决了问题,但很可能他们也是理解未全面,或者说明不够全面。简单来说,一切资料,都应从官网文档上寻求正统的参考。
一如这里,PHP数组的下标类型,在PHP官方文档(链接:http://php.net/manual/zh/language.types.array.php)已有记载。
可以用 array() 语言结构来新建一个数组。它接受任意数量用逗号分隔的
键(key) => 值(value)对。
array( key => value
, ...
)// 键(key)可是是一个整数 integer 或字符串 string// 值(value)可以是任意类型的值
关键信息,高亮如下:“// 键(key)可是是一个整数 integer 或字符串 string”。
也就是说,PHP数组的下标有两种类型,一种是整数,一种是字符串。需要记住这一点,后面会用到这里的知识点和规则。
接下来,看下如何进行PHP数组与字符串之间的比较。那么下一个问题来了。PHP的数组与PHP字符串有什么不同,又有什么相同之处呢?很多同学都知道数组与字符串的区别,但实际上它们也是有相同之处的。这样说,比较抽象,我们可以通过一些示例来加快理解。比如下面代码:
$lib = 'PHPUnit';
echo $lib[0], "\n";
echo $lib[1], "\n";
echo $lib[10], "\n";
这里有一个字符串变量,它的值是(我喜欢的单元测试):PHPUnit。然后,分别输出它第0个位置、第1个位置、第10个位置的值。那么会输出什么呢?
很多语言,包括C/C++,Java等,以及PHP,字符串其实也是一个有序的序列。不同的是,不同语言底层实现方式会有差异。例如C语言中,要在字符串最后加一个结束符“\0”,不然就会导致内存问题;Java语言的字符串则是缓冲区的不变值;而PHP的字符串也可以当作是一个数组的形式。例如这里的可用图表示成:
图2-4 PHPUnit字符串的数组形式
不难得知,$lib[0]会输出字符P,$lib[1]会输出字符H,最后$lib[10]的下标不存在,会出现Notice并输出空(严格来说是NULL)。铺垫了那么多,其实是为了说明,某种情况下,PHP的字符串也可以当作数组来使用,当然与数组还是有很大区别的。
回到面试题:先看左边
回到前面的面试题。尝试解释一下第二行代码背后发生的事情。
$str['name'] = array('dogstar');
先来做个拆解。按照PHP语言的解析机制,执行的顺序是先执行等号右边的代码,再到左边的表达式。所以,先来看等号左边部分发生了什么事情:
$str['name']
任何问题的处理,都离不开它的上下文。乍一看这样的代码是没问题的,但关键是:$str它不是一个数组,而是一个字符串。前面我们已经知道:
PHP字符串也可以通过下标来操作,但实际有效下标只能是0、1、2这样的数字位置。
PHP数组下标有两种类型,可以是整数或者字符串。
那么,对于一个字符串变量,给定一个字符串的下标?这意味着什么呢?PHP又是如何处理的呢?
显然,最起码,这个字符串不会因此而变成一个数组,它还是一个字符串。就像质量守恒定律,一块金属,哪怕是飞上太空,到了月球,它的质量也是不变的。所以,给定一个字符串的下标,但只允许是整数下标,那么PHP语言,就会像其他语言一样,进行隐式类型转换。很多人对显式类型转换、向上、向下类型转换也不太清楚。即:把字符串下标转成整数下标。怎么转?又是一个新问题。
关于PHP字符串转整数的规则,这里再简单说明一下。PHP字符串怎么转成整数?快速问一个PHP开发人员,他都可以告诉你,可以这样做:使用intval()函数,或者前面加个(int)。那好,你继续问他,以下这些代码,结果是什么?
echo intval('123'), "\n";
echo intval('abc'), "\n";
echo intval('123abc'), "\n";
echo intval('abc123'), "\n";
echo intval('1a2b3c'), "\n";
这时,就不一定能完全回答出来了。知道怎么使用PHP的函数是一回事,知道为什么结果是这样又是一回事。理解背后的规则更为重要,意义更大。
那么,PHP字符串转成整数的规则是什么呢?其实PHP官方文档(链接:http://php.net/manual/zh/language.types.string.php#language.types.string.conversion)也早已记载,即:
该字符串的开始部分决定了它的值。如果该字符串以合法的数值开始,则使用该数值。否则其值为
0(零)。合法数值由可选的正负号,后面跟着一个或多个数字(可能有小数点),再跟着可选的指数部分。指数部分由
'e' 或 'E' 后面跟着一个或多个数字构成。
图2-5 PHP官方文档,字符串转换为数值
简单来说,就是从前面开始,一直连续有效的部分,都会转换成整数。所以,对于下标name,若转换成整数,即$str['name']会相当于:
$str[intval('name')];
结果是什么呢?根据刚学的内容,不难知道结果会是0,即字符串“name”会转化成整数0。
小结一下,到现在的情况是:
图2-6 字符串下标转换为数值后
到这里,我们已经逼近真相了。我们已经知道,第二行代码,实际上是把字符串的第一个位置(下标为0)进行赋值,所以最后$str不会变成数组类型,只是改变下标为0的值而已。至于0下标变成了什么,可以喝口水,休息一下,然后继续讨论。
面试题的右半部分
利用数学的代入法,第二行代码就变成了:
// $str['name'] = array('dogstar');
$str[0] = array('dogstar');
问题又来了,要把一个数组塞进字符串的第一个位置,会发生什么呢?
图2-7 待写入的数组
是直接只覆盖一个位置,还是后面的位置也会连带覆盖?这里不难理解,只会覆盖一个位置,即下标为0这个下标。那么对于只有一个坑位,怎么存放得了一个数组这样的庞然大物呢?
先来看下,数组又是怎么转换成字符串的。这里就不再展开细讲,因为大家已经熟悉。数组转成字符串,会变成“Array”这样的字样。即:
echo strval(array('dogstar')); // 结果是 Array
所以,我们的题目又简化成了:
// $str['name'] = array('dogstar');// $str[0] = array('dogstar');
$str[0] = 'Array';
因为只有一个坑位,可再简化成:
// $str['name'] = array('dogstar');// $str[0] = array('dogstar');// $str[0] = 'Array';
$str[0] = 'A';
最后,结合上下文,应该就不能知道最终正确答案是什么了。
<?php
$str = 'php';// $str['name'] = array('dogstar');// 左边等效于,对$str下标为0的元素进行赋值// $str[0] = array('dogstar');// 右边等效于,把数组array('dogstar')转换成字符串Array// $str[0] = 'Array';
$str[0] = 'A';// 最终输出的结果是:Ahp
var_dump($str);
上面是面试题目的简化版,把全部注释去掉后,就能更清楚看出里面蕴藏的知识点了。
更完整的回答
在我看来,回答正确的答案,只能得到60分,达到合格线。如果能解释其中执行过程的细节,则可以达到80分。若能把PHP牵及的知识,进行系统的讲解,则可以达到95分。最后,如果能指明执行过程中发生的因隐式类型转换而发生的警告或者其他错误信息,以及在不同版本下PHP执行的差异,就更为完整了。
总而言之,这是一道基本的PHP题目,但关联的知识点非常多,考察面广。除了可用于评估开发同学对于PHP语言的把握程度,还能考察是否具备高级开发所应具备的软能力——细致、严谨、求真。
此外,在开发前,系统性地学习,阅读开源框架的开发手册或者使用说明是很也有必要的。哪怕是粗读,大概知道有哪些框架特性、注意事项和功能模块,也会对后面的项目开发大有帮助。而不是等到有问题了,才去辛苦排查,甚至不知切入点在哪。
2.5.2 事务型脚本
即便使用成熟的开源框架,也很难保证最终就一定能编写规范的代码,交付高质量的项目。有很多初学者,都喜欢把全部的代码堆砌在一起,喜欢放在同一个文件,甚至在同一个函数内完成全部的功能。一不留神,就会把对参数的接收和验证,对数据库的操作,领域业务的逻辑处理,数据的返回与输出等混在一起,变成一个“大泥球”。这就是行业内所说的事务型脚本。
事务型脚本好不好?好,但只局限于简单性的功能,以及停留在开发前期。每个事务型的脚本,在成熟的项目里最后都会成为一块巨大的技术债务,只有当初编写它的人才能勉强敢做出改动和修改,因为事务型脚本藏着不可预估的缺陷。
但是,在项目开发的前期我们不是为了求快吗?若每方面都要考虑到以后,那岂不是很累?而且,过早优化是否会得不偿失?说到这里,我们有必要再来回顾一下经典的MVC模式了。
2.5.3 再谈MVC模式
MVC模式是使用最为广泛的分层模式,也是误解较多的模式。不用事务型脚本开发的话,我们可以遵循MVC模式,但是注意不要落入俗套。不要以为听说过MVC模式,或者简单地看过它的说明就觉得已经熟练掌握。
首先,MVC模式不是万能的。在软件行业内,没有银弹。没有哪个方案可以解决全部的问题。MVC模式只能在理解的基础上加以使用,如果不加判断就生搬硬套,会很容易造成误解和误用。MVC模式只是众多分层模式中的一种,另外在前端开发中还有MVP模式、MVVM模式。可以说,MVC对于前面所说的前台网站系统和后台管理系统是适用的,但对于本来就不需要的接口服务系统和计划任务系统来说,强加View层就显得格格不入了。这就是为什么在PhalApi开源框架里采用的是ADM模式(Api-Domain-Model),不仅没有View层,而且还把Controller层改成Api接口层,并增加Domain领域业务层。你也可以根据项目的需要,设计规定不同的分层模式。
其次,不管采用哪种分层模式,都有通用的依赖原则。即:高层不应该依赖底层,并且底层不应该调用高层,如果需要调用,则应通过回调函数或者事件侦听方式来实现。
最后,要严格按照分层模式各层的职责来划分代码的层级,分离关注点。不应该把对数据库的操作放到Controller控制层,也不应该把对视图的处理放到Model层,更不应该把决策调度,以及规则处理放到View层。
2.5.4 我心中的企业级系统代码
每做一个项目,开发一个系统,我都会当作是自己的作品,并追求更高的质量,自我要求当前这个作品要比上一个作品好。
我希望,我编写的代码,能适应当下复杂多变的需求,能应对实际运行环境中各种残酷的情况,能在未来不确定性面前可以不断演进和迭代。
即便多年过去,这些代码依然存活,依然被使用,依然发挥着强大的作用并持续创建价值,仿佛这些代码就是昨天刚刚编写的一样。看到或维护此部分代码的后继开发人员,甚至会惊叹这些代码如同有魔法一般能自我进化并保持与时代的同步,只需要微调一下便能满足新需求。如果是由于业务或市场等外部原因,系统下线了,代码虽然没有再执行,但仍然有参与和学习的价值,因为它里面蕴含着丰富的思想、原则和模式。
但我觉得,最为重要的是,当你在阅读这份代码时,最大的感触就是:这份代码仿佛是你写的一样,感觉到自然、亲切。它是那么容易理解,那么达意,纵使是让你来写,你也会这样写,或者它本就应该这样写。这是因为在你和我,乃至其他成千上万的开发人员,在心中,在脑中,在潜意识里,我们都有一套共同语言和模式,对于当前的代码和系统有着共同的认知和解释。
这就是我心中对企业级系统代码的定位。
本章小结
对于初学者,选择一个高起点,能让你更好的赢在起跑线上,对今后的做事方式都会有间接而深远的影响。
服务器首选Linux,这是我从业多年的建议。那些熟悉Linux操作的技术开发人员,往往更能迎难而上,当别人驻足在问题表面而不知所措时,他们能在极短的时间内找到问题所在,然后迅速修复。套用我的导师曾经给我的忠告,我想对读者说:早晚你都会喜欢上Linux操作系统的(以前我的导师是对我说:你早晚会喜欢上用vim来编程的,当时我惊讶不已,觉得这是完全不可能的事。而现在我每天都在用vim,并且乐在其中)。
使用集成式、一键安装的开发环境很有吸引力,但请不要这么做。自己亲自动手搭建LNMP环境,会让你收益良多。现在遇到问题不可怕,不会解决也不可怕,正因为不懂,我们才需要学习、成长和进步。投入一点时间和精力,慢慢地去理解问题背后的原因和原理,然后解决它,这会让你更加印象深刻。日后再遇到类似的问题,你就能更容易以专家的角度来快速排查和解决。
开发网站项目可以分为:前台网站系统、后台管理系统、接口服务系统以及计划任务系统。不同的项目,有不同的特点,需要注意的事项也不同。对于框架的选择,则可以是无框架、自主研发框架、使用开源框架或者使用二次开发后的框架。对于刚入门的开发人员,建议是使用合适待开发项目的开源框架,快速进行开发。
当开始编写代码时,要避免把全部的代码都塞在一个PHP文件里,变成“大泥球”似的事务型脚本。也要注意不要过于轻视MVC模式,或者认为MVC模式是万能的,就不加考虑就生搬硬套。严格要求自己是有好处的,特别对于PHP编程基础这一方面,要在平时的学习、工作和业余中有意识地不断强化自己的PHP专业素养,始终保持细致、严谨、求真的态度。我们也通过一道微妙的PHP面试题为例子详细地探讨了这一工匠精神。
作为本章的总结,可以浓缩为一句话:选择高起点,并始终以高标准严格要求自己。
正所谓,磨刀不误砍柴功。本章的前期准备,将会为我们打下坚实的基础。当项目开发完成后,就需要进行下一步了——向世界发布我们的代码!