# 第 2 章 选择高起点 现在起,从微观角度,我们将会着手开始搭建一个小网站;从宏观角度,我们将踏入软件开发这个领域。在这条路上,我们会遇到很多之前未接触过的技术、概念、工具和框架等。在迎来自己参与搭建的千万级高并发大型网站系统前,我们需要做好承受各种问题和压力的心理准备,酝酿持续学习的心态,并有意识地不断投入时间和精力。 好的开始,是成功的一半。如果你是一个初学者,或者是刚入门的新手,我建议是在一开始就选择一个高起点。也许有人会问,选择低起点,选择入门成本低、学习曲线平缓的路径不好吗?这样可以快速上手。没错,确实是这样,但这只是短期的收益。我们还要从长期的发展来考虑。 如果仅是作为对技术的简单尝鲜,以及日后的目标也只是停留在普通的网站维护和开发时,那么低起点是可以的。但是,如果你也和我一样,致力成为专业的软件开发工程师,致力于尽我们最大的努力,开发并交付价值最大化的软件的话,那么你应该选择一个高起点。对于类似Windows图形化界面和集成开发环境XAMPP,正因为它的简单性、易用性,使得我们容易产生依赖、错觉和习惯。 如果解决方案都是别人现成提供的,只会安装使用,那么最多只能算是一名使用者。一旦出了问题就会措手不及、不知所措,因为从没亲自深入解决过遇到的问题。而专家都是能亲自解决各种难题的人,并且专家也会经常帮助他人解决问题。错觉则是指这些大众化的软件容易让人忽略背后的原理、底层实现、数据结构和算法等冰山以下的内容,觉得软件开发是那么简单,殊不知背后还蕴藏着如此庞大的知识体系。有时候,不知道自己不知道是危险的。参加马拉松长跑比赛和百米冲刺赛跑的运动员,都会有意识在比赛前不断地锻炼,他们会选择与之类似的运动难度而不只是在公园内散散步就企图希望能获得好成绩。同样,在软件开发中,我们也要时刻注意要锻炼我们的思维能力,有意识地训练我们的左右脑。当然不能只是通过简单使用一键安装式的软件来刺激我们的大脑,而是要深入细节,深入底层,深入过程,有意识地去学习、掌握和接触大量的技术、知识和信息。 由俭入奢易,由奢入俭难。类似地,大脑从高度集中、活跃状态到放松状态容易,而从放松状态进入活跃状态很难。特别当一个已经长期都习惯放松平衡的状态时,更难。还记得吗?软件开发是一项需要高智力的脑力开发活动。你的思维方式决定了你所编写的代码,也决定了今后做事的方式——不管高效与否。 今天的选择,会在潜移默化下无意识地影响我们今后几年内工作的方式,也会间接制约我们后面所能取得的成就和成果。通俗来说,往往今天的丰富收获,并不在于我们今天做了什么,或者是最近几天做了什么,而在于多年前做了什么,以及这么多年以来我们一直做了什么。 假设多年后的你穿越回到当前,如果你也已经意识到了一个高起点对于专业软件开发工程师的重要性,那么就让我们来开始新的选择吧——选择一个高起点!下面,将会介绍怎样选择操作系统、开发环境和框架。即便你是有经验的开发人员,相信也可以从中获益。 ## 2.1 服务器系统首选Linux 一定要抵制住对Windows服务器系统的诱惑,要抵制住对GUI图形化界面的诱惑。 这里,并不是说Windows系统不好,也不是说CLI就一定要比GUI好。像高考填志愿一样,没有最好,只有更合适。后面我们将学习的网站开发所取用的技术大部分都是开源的,PHP开发语言是开源的,MySQL数据库是开源的,Nginx服务器是开源的,鉴于此,我觉得LNMP“黄金组合”再搭配开源的Linux操作系统是非常合理的,而不是Windows商业系统。除此之外,我们在后续搭建企业级网站系统过程中还会用到很多开源的软件、开源的框架和开源的类库,这是一个庞大的开源生态圈。如果硬要在这个开源生态圈内注入一个商业系统,会显得格格不入,也会对文化、技术、架构等产生一定副作用。当然,并不是说Windows系统不好,Windows系统很强大,但在这里适合选择Linux。 另一方面,作为一名专业人士,很有必要熟练掌握命令行终端的操作,也就是对各种shell命令的使用。通过使用命令,我们可以将任务脚本化,即在一个脚本内使用多条命令就可以完成一个任务的操作。这样可以节省每次手动重复操作的时间。更重要的是,脚本化代表的是一种自动化处理的能力,即可以完全脱离人工干预而进行的操作。试想一下,这是对宝贵的人力资源多大的解脱!举个例子,我们需要为每个小区的每位业主发送月度物业费邮件电子账单,假设业主有1000人。如果是人工来操作的话,需要登录邮箱然后分别编写邮件,找到对应的收件人,然后核实发送,这个过程显然时间漫长并且容易出错。如果换种方式,将这些操作全部都使用命令脚本来实现的话,只需要每个月,花一分钟执行这个命令即可。人力成本大为降低的背后就是效率极大的提升。纵使是这样,如果负责发送邮件通知的那个人,因为忘记或者刚好那天是国家节假日而没发邮件的话,就会产生差错。这时,我们可以将此脚本任务通过Linux的crontab定时任务固化下来。这样,完全不需要任何人干预,电脑就会自动并且非常负责任地帮我们完成任务啦! 千万不要因为Linux的高深莫测就打退堂鼓。Linux本身并不复杂,很多人,特别是初学者觉得它很难。这只是因为Linux设计时面向的人群就不是普通的电脑使用者,而是面向特定领域的专业人士,尤其是面向技术类专业人士。这一过程的转换之所以难,是因为对我们多年来使用操作系统习惯和思维的转换,才会觉得很难。Linux没有提供GUI图形界面,在上面我们不能使用鼠标左右移动,没有良好的人机交互体验。要想在我们的大脑和Linux操作系统之间进行通讯,我们只能使用键盘,敲下指令,回车,等待终端的输出反馈。 这里有一个悖论,看似这样的人机操作,限制了我们的操作速度,限制了我们的可视范围,但恰恰是这样的CLI终端,为我们打开了更广阔的知识大门,打开了思维上的新天地。Linux秉持着简单之美的原则,在屏幕上输出的信息全部都是非常有用的。如果能在一屏内显示完整,决不会给你显示两屏。对于那些非关键性、可显示也可不显示的信息,则通常都不会显示。如果说我们是在和高效的命令行打交道,还不如说是我们正在和开发提供这些命令行的专家在对话,与这些来自世界各地大师级的专家在“沟通”,我们收获的不仅是更高效完成当前任务,从长远角度上看,收获的是向专家级权威人士靠拢的思维方式和见解。当哪天,你能看懂顶级专家编写的代码和论著时,你离专家也就不远了。 但是学习Linux这个过程,特别是刚开始的时候,可以说是步履维艰的。这点我深有体会。我也是刚毕业不久才正式学习和使用Linux操作系统的,当时连一个tar解压打包的命令都要保存下来,每次执行时都要找出来贴上去,然后改改参数再执行,因为对shell命令很陌生,完全记不住。而现在,我已经可以熟练地掌握常用命令的操作,并且对于未曾接触过的命令,也可以快速通过man命令来查看帮助。 选择Linux操作系统,还有一个很大的现实原因就是:当毕业后,你会发现,你所就职的公司,基本上绝大部分的网站系统都是运行在Linux操作系统之上。谁更懂Linux,谁就能更容易解决将要发生的问题,并且更能胜任随时到来的挑战。 服务器系统首选Linux,这是我对全部致力于成为专业开发人士的建议之一。 ## 2.2 自已动手搭建LNMP环境 确定了操作系统后,接下来,自然就到了LNMP环境。LNMP是中小型公司,乃至大型公司都会选择的方案。因为这个“黄金组合”成熟稳定,并且熟悉的人多。前面也有简单介绍,这里再稍微罗列下: + L表示Linux系列的操作系统,在本书中我们使用的是CentOS。 + N表示Nginx,它可以实现均衡负载、反向代理、读写分离、URL重写等一系列功能。 + P表示PHP,即网站开发语言,一种解释性脚本编程语言。 + M表示MySQL,即开源的关系型数据库,可用于持久化存储数据。 与其他的教程不同,我不建议使用一键式集成开发环境,哪怕你现在是一个小白,我也会建议你一开始就自己动手搭建LNMP环境。 刚开始时,有可能你需要花费几天的时间,才能把整个开发环境搭建起来。但是没关系,不要急,也不要浮躁。要沉下心来,如果现在遇到这点难题就放弃的话,那么我们将会与千万级访问量的大型系统失之交臂。设想一下,如果你日后能在短时间内(例如几分钟内)找出使网站系统崩溃的原因并恢复正常,你需要具备哪些能力?你又需要为之做些什么呢?没错!就是当下,要把这个LNMP环境拿下。 就像医生诊断病人一样,如果他连人体的构造,各器官的位置和作用都不了解的话,他是不可能找得出病理的,更别说开药治病。病人也不会放心把自己的健康甚至生命托付给一无所知的医生。同样的道理,如果我们连LNMP都不了解,公司又怎能放心把网站系统托付给我们来维护和管理呢?这里说的了解,不是指表面上知其然,会使用,还要知其所以然,清楚涉及的安装、配置、优化、错误处理、实现原理等。能在今后和其他人进行技术沟通时,做到一问一答,举一反三。而不是一问三不知。 关于LNMP开发环境的搭建,网上已经有很多教程,大家可以根据自己的情况,结合当前使用的环境进行搭建,这里不再赘述。但我接下来会讲一些非常重要的关键点,讲一些不一样的内容。这些环节通常会容易被人遗忘或忽略,但却又是不可轻视的。至少从很多前来应聘的初级、中级开发工程师的回答上可以看出,他们缺少对这一块的认识。 ### 2.2.1 Nginx的站点配置在哪? Nginx的配置,在不同的系统上,因为安装方式不同,所存放的位置也不相同。我们先来简单看下Nginx的配置有哪些,再来看下可以通过什么途径找到这些配置的位置。最后看下配置Nginx的原则。 #### CentOS上的配置 默认情况下,Nginx的默认配置目录是在:/etc/nginx/,并且在这目录下会有一个nginx.conf的默认配置文件,也就是Nginx的配置文件。这里面有比较多重要、关键性的配置。其中,需要注意的是,如果在这份配置里搜索“include”,你会找到类似这样的配置: ``` http { # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. include /etc/nginx/conf.d/*.conf; server { # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; } } ``` 这里有两处include配置。在http内但在server外的inclue的这行配置指明了,Nginx会从加载/etc/nginx/conf.d/目录下全部以conf为文件扩展名的配置文件。换言之,在此目录下正确配置的站点,都能通过Nginx来访问。根据这条线索和技巧,你就能快速找到Nginx的站点配置在哪。 #### Ubuntu上的配置 因为有时候,站点的配置不一定“藏”在这里。例如在Ubuntu上,就倾向放在/etc/nginx/sites-enabled/这个目录下。在Ubuntu系统上,Nginx.conf的配置会这样: ``` include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; ``` 它除了加载 /etc/nginx/conf.d/目录下,全部以.conf为后缀的配置文件外,还会加载/etc/nginx/sites-enabled/下全部的配置文件,注意,这时不管文件是什么后缀都会加载。似乎技术开发人员故意要为难技术开发人员,另外还有一个/etc/nginx/sites-available/目录。这个目录又是什么呢?原来这里才是放置Nginx真身配置的地方,在sites-enabled那个目录放置的是配置的“替身”,即指向/etc/nginx/sites-available/这个目录的软链接。举一个例子就很容易明白了。假设有一个配置文件: ``` /etc/nginx/sites-available/www.test.com ``` 需要再添加软链接: ``` ln -s /etc/nginx/sites-available/www.test.com /etc/nginx/sites-enabled/www.test.com ``` 然后重启Nginx才能访问www.test.com这个站点。 #### 编译安装方式的配置 有时Nginx又比较调皮。它默认的配置文件nginx.conf可能是在/usr/local/nginx/conf/nginx.conf。如果采用的是下载Nginx源代码再手动编译安装的话,Nginx目录位置根据技术人员在安装时指定的位置不同而不同。可以在Nginx执行configure时指定,如: ``` ./configure --prefix=/usr/local/nginx ``` 在此nginx.conf配置文件里面,可能会看到: ``` include ./vhosts/*.conf; ``` 则表示,此时Nginx的站点配置文件在/usr/local/nginx/conf/vhosts/这个目录下,并且需要以.conf为后缀。 Nginx的配置位置,可能还会有很多情况。但如果大家掌握了技巧,不管它在哪,我们都能快速找出来。这背后,只需要对include配置和文件路径的一点了解就可以了。要注意是绝对路径,还是相对路径;同时要注意是全部文件后缀,还是指定了.conf文件后缀。下面通过示例再稍微小结一下。 ``` # 绝对路径,仅限.conf后缀 include /etc/nginx/default.d/*.conf; # 绝对路径,不限后缀 include /etc/nginx/sites-enabled/*; # 相对路径,仅限.conf后缀 include ./vhosts/*.conf; # 相对路径,不限后缀 include ./vhosts/*; ``` > 专家技巧:根据nginx.conf文件里面的include配置项,可以快速找到Nginx站点的配置目录。 #### Nginx配置的原则 在开始配置你的站点前,注意不要把配置追加在/etc/nginx/nginx.conf默认文件里,也不要把多个站点都配置在/etc/nginx/conf.d/下的同一份配置文件里。而是应该一个网站,一份Nginx配置,并且以站点的名称作为文件名前缀。例如参考以下这样的配置: ``` /etc/nginx/conf.d/www.examples.com.conf /etc/nginx/conf.d/docs.examples.com.conf /etc/nginx/conf.d/api.examples.com.conf ``` 上面配置了三个站点,分别是:www.examples.com、docs.examples.com和api.examples.com。每个站点一份Nginx配置,以后维护更方便,也更简单。 > 专家原则:一个网站,一份Nginx配置,并以站点域名命名。 Nginx还有很多强大的配置,我们会在后面学习过程中,不断学习更多高级的技能。这时,我们先再来关注下,Nginx是如何和PHP-FPM通信的,也就是Nginx是怎样执行PHP文件的? ### 2.2.2 Nginx如何与PHP-FPM通信? 知道Nginx站点配置在哪后,下一步需要了解每份Nginx配置里面都有哪些内容?Nginx的配置有很多,刚开始时,我们只需要关注以下几项目配置。我把这称为最小化可用配置,即使网站运行起来的最基本的配置。 + **listen** 侦听的端口,默认是80,可省略。 + **server_name** 网站域名,多个域名使用空格分开。 + **index** 默认访问的首页索引访问文件,一般对于静态网站是index.html,对于PHP则是index.php,对于C#则是index.asp,依此类推。 + **root** 网站访问的根路径,通常我建议在项目内建立一个单独的public目录给外部访问,这样更安全,也更容易管理。 + **location** 对PHP请求的转发和处理。Nginx本身不执行PHP文件,而是将对应的PHP请求通过FastCgi交由php-fpm进程来处理。 下面来看下对应的示例配置。以刚才的/etc/nginx/conf.d/www.examples.com.conf配置为例,在里面配置以下内容。 ``` server { listen 80; server_name www.examples.com; index index.html index.php; root /path/to/www.examples.com/public; location ~ .*\.(php|php5)?$ { fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } ``` 可以看到,这里侦听的是80端口,网站域名为www.examples.com。默认首页为index.html和index.php,网站根目录为指向www.examples.com目录下的public子目录。最后,则是关键的部分,Nginx进程与PHP-FPM进程之间如何通讯的配置。这里传递的方式是通过socket来通讯的: ``` fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; ``` 但在Linux操作系统上,两个不同的进程之间的通讯,还可以通过端口,这就是为什么有时候会这样配置: ``` fastcgi_pass 127.0.0.1:9000; ``` 当你看到的是不同的配置时,不要困惑,一旦你了解了进程之间的通讯手段,就很容易明白了。 清楚这一点有什么用处呢?当你清楚整个请求背后的大概处理流程后,当发生问题或者故障时,就可以快速定位排查啦。 #### 一个比喻,了解Nginx的执行流程 我们可以来找个比喻,你就能很容易明白上面Nginx的配置以及其执行流程了。 假设,我们开了一张新餐馆。把新店的位置以及餐馆正门告诉市场后,就会陆续迎来我们尊贵的客人。而对于网站,则是通过域名和端口来告诉互联网,告诉广大冲浪者。例如我们的www.examples.com域名和80端口。 ``` listen 80; server_name www.examples.com; ``` 客人来了之前,他会拿起菜牌,挑选喜欢的菜式,然后向服务员下单。对于网站,网站访客会浏览页面上的内容和信息,然后点击页面链接进入他们感兴趣的页面。也就是说有人发起了请求,访问了我们网站根目录下的某些文件或页面。 ``` index index.html index.php; root /path/to/www.examples.com/public; ``` 但是服务员本人不会下厨,服务员只会把客人要点的菜交由厨师来处理,然后等待厨师完成后,再把美食呈现给客人。Nginx和这个处理方式是类似的,Nginx本身不执行PHP文件,而是把PHP的请求通过socket或者IP和端口转发给php-fpm进程来处理。最后,等待php-fpm处理完成返回结果后,再中转返回给客户端。这就是为什么我们会看到: ``` location ~ .*\.(php|php5)?$ { fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } ``` 一旦明白了解这个过程,后面遇到任何问题,都不必惊慌。利用已经掌握的知识,稍微分析下,你就能很快找到原因了。下面我们会来看下可能会遇到哪些问题。 ### 2.2.3 HOST绑定与网站访问 现在,我们可以开始简单测试网站的访问了。 首先,我们先创建一个简单的首页。继续前面的例子,先在www.examples.com/public目录下,添加index.php文件,并放置以下代码。 ```php // $ vim ./index.php <?php echo "<h1>Hello Wolrd!</h1>"; ``` 在本地开发时,我们通常需要绑定HOST后,才能访问自己的网站。因为这时网站还没发布,还只是在局域网内,并且公网的域名也还没进行公网DNS解析。绑定HOST的配置很简单,假设网站服务器的IP是192.168.1.2,那么对应的host配置是: ``` # www.examples.com的本地开发环境 192.168.1.2 www.examples.com ``` 但问题是,对于Windows、Linux和Mac操作系统,配置这个host的地方又有点不同。对于Winonws,通常位于C:\Windows\System32\drivers\etc\hosts;对于Linux和Mac,通常是保存在/etc/hosts文件。 添加保存后,这里使用浏览器就能这样访问啦! ![图像说明文字](http://epub.ituring.com.cn/api/storage/getbykey/screenshow?key=180357e1cb8bb2b8b102) 图2-1 搭建Nginx和PHP后的示例首页访问效果 是不是很简单?也许,但现实并不总是那么顺利的。在搭建过程中,你有可能会遇到各种问题。 ### 2.2.4 关注错误日志 以下是你有可能遇到的问题,这里的目的并不是为了罗列出全部的问题和解决方案,而是为了分享在面对问题时,如何快速定位问题。从而哪怕是新手,也能做到有章可循,有条不紊地解决问题。 首先,我们先来了解一件法宝。这件法宝就是错误日志。例如Nginx的错误日志和php-fpm的错误日志。如前面的示例中,可以这样追加Nginx的错误日志配置: ``` server { server_name www.examples.com; # 略…… error_log /var/log/nginx/www.examples.com.error_log; } ``` 对于php-fpm,可以在php.ini配置文件中修改error_log选项,指定错误日志的文件路径。如下: ``` error_log = /var/log/php5-fpm.log ``` 记得,在配置完上面内容后,都需要重启nginx或者php-fpm。 > 专家技巧:遇到问题时,要首先关注查看错误日志。 好了,准备妥当后,让我们看下将可能会遇到哪些问题,又将是如何进行分析、排查和解决。 #### 502 Bad Gateway 错误 不管出现什么错误,首先看下Nginx的错误日志,可以看到当出现“502 Bad Gateway”错误时,有类似如下的错误日志: ``` 2018/03/10 15:18:46 [error] 21426#0: *16 connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: www.examples.com, request: "GET / HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "www.examples.com" ``` ![图像说明文字](http://epub.ituring.com.cn/api/storage/getbykey/screenshow?key=1803d16423d464e0e710) 图2-2 502 Bad Gateway错误 Nginx的日志都是有一定格式的,从中不难发现提示了这样的错误: ``` connect() failed (111: Connection refused) while connecting to upstream ``` 这里是指连接失败,即Nginx与php-fpm通讯失败。如果nginx配置没错的话,可检查php-fpm是否已启动。这类简单的问题,往往是困扰新手的大问题。就好像很多维修人员上门去解决电脑无法开机时,原来是因为未插电源。 启动php-fpm后,应该就能正常访问了。 #### 404 File not found 错误 注意,前面是说应该能正常访问,而不是一定能。因为还可能会有别的问题。例如常见的404 File not found。导致404的原因有很多种,那种明显文件不存在,或者配置错误的原因就不说了。我这里分享另外一个会导致404,却很隐晦的原因。即便是有经验的人,也可能会忽略。 同样,出错404错误时,先来看下错误日志信息。会看到类似这样的提示: ``` 2018/03/10 15:23:18 [crit] 21426#0: *19 stat() "/path/to/www.examples.com/public/" failed (13: Permission denied), client: 127.0.0.1, server: www.examples.com, request: "GET / HTTP/1.1", host: "www.examples.com" ``` 这里看到是权限不足。 对比查看下我们的PHP源代码的文件权限,检测后发现也是有执行权限的。那为什么还是没执行权限呢? ``` ll ./www.examples.com/public/index.php -rwxrwxr-x 1 dogstar dogstar 36 Mar 10 14:56 ./www.examples.com/public/index.php* ``` 这是因为不仅需要PHP文件本身要有执行权限,还要求它的当前目录,全部父级目录也需要有执行权限。假设index.php文件的存储路径为/home/dogstar/projects/www.examples.com/public/index.php,那就要逐一检测各个目录的执行权限。 通过排查,可以发现dogstar这个用户的目录没有执行权限。而通常在Linux创建新账号时,其账号对应的目录都是没有执行权限的,这点需要注意。 ``` $ ll /home drwxr--r-- 36 dogstar dogstar 4096 Mar 10 14:56 dogstar/ ``` 解决的方法很简单,只需要追加执行权限即可,例如: ``` $ chmod +x /home/dogstar ``` 再重新页面重新访问,就能正常访问首页了。如果还不行,再逐级排查。 #### 500 Internal Server Error 错误 可以说,500错误是我们日后经常会遇到的错误。如果php-fpm已经启动,并且也具备全部执行权限时,若出现500错误时,通常都是PHP本身的问题,这样可以方便我们进一步收拢排查的范围。先来看下Nginx的错误日志。 ``` 2018/03/10 15:41:04 [error] 21429#0: *35 FastCGI sent in stderr: "PHP message: PHP Parse error: syntax error, unexpected $end, expecting ',' or ';' in /path/to/www.examples.com/public/index.php on line 4" …… ``` 这表明是PHP语法有错误。查看下对应的PHP源代码,原来是刚才我们新加的代码有问题。 ```php <?php echo "<h1>Hello World!</h1>"; echo "by dogstar" ``` 最后一行代码少了分号,补上即可正常访问。 ### 2.2.5 PHP的两份配置 知道编写PHP代码的程序员不少,但知道PHP有几种运行模式的人并不多。 如果在网上搜索一下,可以找到例如这5种运行模式: + CGI(Common Gateway Interface),通用网关接口 + FastCGI(Long-Live CGI),常驻型CGI + CLI(Command Line Interface),命令行运行模式 + Web模块模式,Apache等Web服务器运行的模式 + ISAPI(Internet Server Application Program Interface) 不同的运行模式,是有所差异的。而对我们技术开发人员来说,主要在以下这两方面。 #### 配置上的差异 以常用的CGI和CLI这种运行模式为例,php-fpm使用的配置文件很有可能和命令行终端使用的配置文件是不同的。这也为什么,我们安装了一个PHP扩展,明明在浏览器访问是可以,但通过命令行来访问却是失败的。又或者反过来,命令行终端正常,在浏览端访问时却失败了。 #### 如何找到php-fpm和命令终端用的是哪份配置? 要想快速找到命令终端用的是哪份配置,非常简单,使用以下命令即可: ``` $ php --ini Configuration File (php.ini) Path: /etc/php5/cli Loaded Configuration File: /etc/php5/cli/php.ini Scan for additional .ini files in: /etc/php5/cli/conf.d Additional .ini files parsed: /etc/php5/cli/conf.d/curl.ini, /etc/php5/cli/conf.d/gd.ini, /etc/php5/cli/conf.d/mcrypt.ini, …… ``` 通过这个php --ini这个命令,它会告诉你当前使用的是哪份配置。例如这里的是/etc/php5/cli/php.ini。 对于php-fpm用的是哪份配置,有两种方式,你可以继续使用命令行,如: ``` $ ps -ef | grep php-fpm root 22190 1 0 15:46 ? 00:00:00 php-fpm: master process (/etc/php5/fpm/php-fpm.conf) ``` 通过查看进程的信息,可以看到用的是哪份php-fpm.conf,然后在同目录下找到对应的php.ini。 另外一种方式,是更常见也是更可靠的做法。就是通过phpinfo()函数来查看配置。例如添加phpinfo.php文件,并写入: ``` // www.examples.com$ vim ./public/phpinfo.php <?php phpinfo(); ``` 然后保存,访问,就可以看到类似这样: ![图像说明文字](http://epub.ituring.com.cn/api/storage/getbykey/screenshow?key=1803d406cd7afe56bf97) 图2-3 phpinfo页面信息 从中可以找到php.ini的位置是在/etc/php5/fpm/php.ini。 如果php-fpm和命令行模式使用的不是同一份配置,会容易产生不一致的问题。在这里,你可以创建软链,使一方指向另一方。当然,你也可以继续手动维护两份配置。 > 专家技巧:为降低维护成本,将php-fpm和命令行模式的php.ini配置文件指向同一份文件。 ### 2.2.6 数据库环境配置的方式 最后我们简单来说一下,如何集成MySQL数据库,也就是LNMP黄金组合的最后一部分。 首先,千万不要把数据库账号和密码写死在代码里。这是初级项目,个人项目,学校项目经常会做的事。但我们应当养成开发商业级项目的习惯,始终坚持高标准,高要求,尽量与日后线上商业系统的做法保持一致。 更好的方式,是采用配置方式来配置数据库的账号、密码、用户名等重要信息。一来避免敏感信息的泄露,二来方便不同环境下开发、测试、部署的便利。 考虑以下最初的MySQL数据库配置: ``` // www.examples.com$ vim ./config/db.php <?php return array( 'name' => 'examples', // 数据库名字 'host' => 'localhost', // 数据库域名 'user' => 'root', // 数据库用户名 'password' => '123456', // 数据库密码 'port' => 3306, // 数据库端口 ); ``` 这里采用的是硬编码的方式。这是常用、简单但也是有一定弊端的做法。 更好的方式,是通过环境变更来进行配置。这样会更灵活,也更容易进行管控。例如改为: ``` // www.examples.com$ vim ./config/db.php <?php return array( 'name' => $_ENV['DB_NAME'], // 数据库名字 'host' => $_ENV['DB_HOST'], // 数据库域名 'user' => $_ENV['DB_USER'], // 数据库用户名 'password' => $_ENV['DB_PASS'], // 数据库密码 'port' => $_ENV['DB_PORT'], // 数据库端口 ); ``` 不过,好的做法都需要一点成本。这里的成本就是需要我们额外添加对应的数据库环境变量配置。打开/etc/php5/fpm/pool.d/www.conf配置文件,然后同步追加以下配置: ``` env[DB_NAME]='examples' env[DB_HOST]='localhost' env[DB_USER]='root' env[DB_PASS]='123456' env[DB_PORT]=3306 ``` 然后,保存并重启php-fpm。你会发现,以上的配置不一定能正常读取。这是因为php-fpm默认是没有开启ENV环境变量的。每一次,我们都可以从新的问题获得新的知识。这时,可以修改/etc/php5/fpm/php.ini文件,并修改variables_orders配置项在前面追加表示ENV的字母E,从原来的GPCS变成EGPCS。如: ``` variables_order = "EGPCS" ``` 然后,再次重启php-fpm。这时通过浏览器就能正常获取到上面配置的数据库信息了。 但是(生活里总有很多但是),CLI命令行模式呢?是否也能正常获取ENV环境变量?还记得我们前面说过吗?CLI不一定共用php-fpm的配置,因此这时你很可能还要同步修改/etc/php5/cli/php.ini文件里面的variables_orders配置项。 好了,但是(又一个但是,希望读者可以忍住骂我的声音),CLI模式下读取到ENV配置为空又是什么原因呢?这是因为,命令行方式下读取的环境变量来自/etc/environment文件。这时,需要切换到root用户权限,并追加以下配置,注意格式有所差异。 ``` # vim /etc/environment export DB_NAME='examples' export DB_HOST='localhost' export DB_USER='root' export DB_PASS='123456' export DB_PORT=3306 ``` 因为是Linux的环境变量,所以需要export来导入。 通过环境变量而不是hard code的方式,我们就可以在不同的环境进行部署,而不需要来回修改代码中的配置。只需要修改不同环境上的数据库配置,就可以实现不同环境下的数据库连接了。 > 专家技巧:通过环境变量而不是硬编码的方式,进行数据库连接信息的配置。 这时,总算可以告一段落了。休息一下,我们稍候继续! ## 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 Framewor,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面试题。哪怕是有丰富经验的开发人员也会回答错误,因为其中牵涉的理论、知识很多。 题目如下:请问,你觉得以下代码会输出什么,并解释为什么。 ``` <?php $str = 'php'; $str['name'] = array('dogstar'); var_dump($str); ``` 如果你不通过执行上面代码,就能准确无误说出正确的答案,至少说明: + 你是一个细心的人 + 对PHP语言的理解非常到位 但实际情况是,在面试过程中,很多人都回答不出正确的答案,更无从解释其中的原由。 回答最多的答案(但是错误的)是:输出一个数组,即:array('dogstar')。他们认为,把一个字符串当作数组使用时,会把这个变量转换为一个数组类型,从而得到这个结果。这时候,我是表示很怀疑的,怀疑他是不是把以前所学的编程语言知识都丢了,或者根本没上过相应的专业课程逃课去约会了。把指针下的某个下标进行赋值,会改变当前指针所指的变量类型?! 也有人说直接挂掉,但这种说法并不多。那真正的答案是什么呢?希望读者在继续往下看之前,先自己停下来,思考一下如果是你来回答这道题,你会怎么回答,又如何解释? #### 解题过程细说 要想得知最终正确的答案,至少需要对PHP的字符串和数组有较为深刻的理解,以及PHP基本类型和变量的底层知识有一定的认识。我们可以把这道题,当作一道高中时代的方程式,一步步进行分解、拆解、转换,便可慢慢水落石出,最终得到正确的答案,而不应不加思索就臆断结果是什么。 编程语言,以及计算机都是理性的,更多是靠逻辑和推理,是客观的事实,而不是靠主观臆断。 首先,第一行代码完全没有异议,是把字符串“php”赋给了变量$str。但这正是“恶梦”的开始。 ``` <?php $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数组的下标。 先来简单看下这份额外的代码: ``` <?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字符串有什么不同,又有什么相同之处呢?很多同学都知道数组与字符串的区别,但实际上它们也是有相同之处的。这样说,比较抽象,我们可以通过一些示例来加快理解。比如下面代码: ``` <?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的字符串也可以当作是一个数组的形式。例如这里的可用图表示成: ![图像说明文字](http://epub.ituring.com.cn/api/storage/getbykey/screenshow?key=18034c5392a1ec44dc8d) 图2-4 PHPUnit字符串的数组形式 不难得知,$lib[0]会输出字符P,$lib[1]会输出字符H,最后$lib[10]的下标不存在,会出现Notice并输出空(严格来说是NULL)。铺垫了那么多,其实是为了说明,某种情况下,PHP的字符串也可以当作数组来使用,当然与数组还是有很大区别的。 #### 回到面试题:先看左边 回到前面的面试题。尝试解释一下第二行代码背后发生的事情。 ``` <?php $str['name'] = array('dogstar'); ``` 先来做个拆解。按照PHP语言的解析机制,执行的顺序是先执行等号左边的代码,再到右边的表达式。所以,先来看等号左边部分发生了什么事情: ``` <?php $str['name'] ``` 任何问题的处理,都离不开它的上下文。乍一看这样的代码是没问题的,但关键是:$str它不是一个数组,而是一个字符串。前面我们已经知道: + PHP字符串也可以通过下标来操作,但实际有效下标只能是0、1、2这样的数字位置。 + PHP数组下标有两种类型,可以是整数或者字符串。 那么,对于一个字符串变量,给定一个字符串的下标?这意味着什么呢?PHP又是如何处理的呢? 显然,最起码,这个字符串不会因此而变成一个数组,它还是一个字符串。就像质量守恒定律,一块金属,哪怕是飞上太空,到了月球,它的质量也是不变的。所以,给定一个字符串的下标,但只允许是整数下标,那么PHP语言,就会像其他语言一样,进行隐式类型转换。很多人对显式类型转换、向上、向下类型转换也不太清楚。即:把字符串下标转成整数下标。怎么转?又是一个新问题。 关于PHP字符串转整数的规则,这里再简单说明一下。PHP字符串怎么转成整数?快速问一个PHP开发人员,他都可以告诉你,可以这样做:使用intval()函数,或者前面加个(int)。那好,你继续问他,以下这些代码,结果是什么? ``` <?php 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' 后面跟着一个或多个数字构成。 ![图像说明文字](http://epub.ituring.com.cn/api/storage/getbykey/screenshow?key=1803948973361d31b4e3) 图2-5 PHP官方文档,字符串转换为数值 简单来说,就是从前面开始,一直连续有效的部分,都会转换成整数。所以,对于下标name,若转换成整数,即$str['name']会相当于: ``` $str[intval('name')]; ``` 结果是什么呢?根据刚学的内容,不难知道结果会是0,即字符串“name”会转化成整数0。 小结一下,到现在的情况是: ![图像说明文字](http://epub.ituring.com.cn/api/storage/getbykey/screenshow?key=18033cd497719170ed75) 图2-6 字符串下标转换为数值后 到这里,我们已经逼近真相了。我们已经知道,第二行代码,实际上是把字符串的第一个位置(下标为0)进行赋值,所以最后$str不会变成数组类型,只是改变下标为0的值而已。至于0下标变成了什么,可以喝口水,休息一下,然后继续讨论。 #### 面试题的右半部分 利用数学的代入法,第二行代码就变成了: ``` // $str['name'] = array('dogstar'); $str[0] = array('dogstar'); ``` 问题又来了,要把一个数组塞进字符串的第一个位置,会发生什么呢? ![图像说明文字](http://epub.ituring.com.cn/api/storage/getbykey/screenshow?key=1803542d95bcbf8d1b3d) 图2-7 待写入的数组 是直接只覆盖一个位置,还是后面的位置也会连带覆盖?这里不难理解,只会覆盖一个位置,即下标为0这个下标。那么对于只有一个坑位,怎么存放得了一个数组这样的庞然大物呢? 先来看下,数组又是怎么转换成字符串的。这里就不再展开细讲,因为大家已经熟悉。数组转成字符串,会变成“Array”这样的字样。即: ``` <?php 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面试题为例子详细地探讨了这一工匠精神。 作为本章的总结,可以浓缩为一句话:选择高起点,并始终以高标准严格要求自己。 正所谓,磨刀不误砍柴功。本章的前期准备,将会为我们打下坚实的基础。当项目开发完成后,就需要进行下一步了——向世界发布我们的代码!