主要原因有2个:
结合上两条问题的起因,我们将整个商城也分为2步重构。
子系统:
具象系统:
库存系统表结构:
应用商城表结构:
主题商城表结构:
订单系统表结构:
优点:
缺点:
这首歌总让我想起一个故事,是别人的故事,还是自己的故事,却无法分辨。
出自五月天,但是我更喜欢梁静茹的版本。
我们常说的纽北赛道其实是指的纽博格林赛道的北赛道。纽博格林赛道(德语:Nürburgring,意指“纽堡的环道”)是一条位于德国莱茵兰-普法尔茨州的赛车跑道。最早修筑于1920年代,该赛道围绕着埃菲尔山区一称为纽堡的小村与中世纪古堡而建。由于跑道长度非常长(根据时代与设定的不同,该跑道最长距离曾长达28公里以上)、地形复杂充满挑战性,除了经常被认为是世界上最严苛、为了竞赛用途而建造的赛道之外[1],也经常被用作新款的量产车种上市之前进行耐久与性能实测的常用场地。苏格兰一级方程式传奇赛车手杰基·斯图尔特曾因此跑道的路线严苛,而称呼北环为“绿色地狱”(Green Hell),并在赛车界中广为流传[2]。
]]>走进汽车 - 浪漫意大利风情下的狂野猛兽 - 阿尔法·罗密欧
阿尔法·罗密欧,作为一个汽车品牌的名字,刚听到的时候就觉得B格十足。这车在世面出现的不多,认识阿斯顿马丁的人可能都要比它多,但它却也是十足的豪华品牌,拥有着悠久的历史文化。
出生于意大利🇮🇹,时尚和文艺之都,创厂历史最早可回溯至1907年,并从1986年开始加入菲亚特集团成为成员品牌之一。车厂原名ALFA(Anonima Lombarda Fabbrica Automobili,伦巴底公有汽车制造厂),但在1916年出身拿波里的实业家尼古拉·罗密欧(Nicola Romeo)入主该车厂,并将自己的家族名加入车厂名称中,而成为今日的阿尔法·罗密欧。
Alfa Romeo的商标中那条吞食人的蛇代表西元五世纪时米兰维斯孔蒂家族建立者所杀掉的蛇,十字是代表维斯孔蒂家族曾参加十字军东征,后来成为意大利米兰市的市徽。
以上内容来自于维基百科。不出所料,阿尔法·罗密欧是两个名字结合而来,其中的罗密欧和那个《罗密欧和朱丽叶》没有半毛钱关系,然而有趣的是它旗下有一个车型叫GIULIA(朱丽叶)。
需要注意一点是,在上面的历史中提到过,阿尔法·罗密欧现在是菲亚特集团的成员品牌。这个菲亚特集团是菲亚特克莱斯勒汽车集团的子部门,菲亚特克莱斯勒汽车集团是菲亚特和克莱斯勒的合并,这里出现了克莱斯勒,请记住它,在文章之后的部分会继续提到。
如今的阿尔法·罗密欧设计前卫,造型凶悍,追求速度,是时尚和性能的结合体。我就随便放几张图,应该也会有不少人对他的外在心生向往,那就我们就从他的外在说起。
上图中展示的是现在阿尔法·罗密欧主推的3款车型,分别对应的3种类别的车,跑车 4C Spider
,轿车 GIULIA
,SUV STELVIO
。可以看出来3款的都应用了家族式的造型,接下来主要介绍的是被大众普遍任何的轿车款 GIULIA
。
首先一看到 GIULIA
家族式的前脸,就足以抓住你的眼球。三叶草☘式的中网造型,醒目的车标点缀,锐利的氙气大灯,以及布满肌肉感的曲线线条,凶悍二个字形容它一点都不夸张。
一切都看似那么美好,直到它装上了车牌,整个世界都安静了。
当然我说的可能夸张了,但是逼死强迫症应该是没得跑了,而且车的前脸由于曲线的原因本身就是凹凸不平的,导致强行安上的车牌显得摇摇欲坠,随时都可能掉下来。
说完前脸我们接着来看侧面:
看一款车的侧面时候往往就像看一个人的背面,只能看出一个大体的身材。可以看到 GIULIA
侧面上半身比较普通,但它不是那种椭圆形,在靠近前脸的地方,它是一条折线,不是一个整体的曲线,也许是为了突出前脸部分的凶悍吧。GIULIA
下半身就有亮点了,我们可以在车门上能看到一条S型的曲线,比较骚气的腰钱,小蛮腰没毛病。然而前面说的都不重要,我觉得最亮眼的应该是其漂亮的轮毂,花瓣形的轮毂给整个侧面增色不少。
最后看一下尾部:
尾部微微上翘的屁股(后备箱处),接近倒三角造型的车灯,底部左右两侧和前脸一致的中网,以及2个圆形排气管。尾部整体是看起来显得平和很多,不过倒三角的车灯还是有点别致的,这造型会不会让你想到某位同门兄弟呢😏。
未完待续。
]]>试想一下,如果现在突然有一个面试官问你:什么是Cookie?什么是Session?你会怎么回答。
我想了下,首先大脑中的经验告诉我cookie是客户端缓存信息的一种方式,session则是放在服务端。但是这种说法总觉得有些牵强,于是下面我们从一个具体的请求出发,来看看它们在实际中是怎么应用的。
请注意一个前提,http是无状态的协议,无状态导致了服务器不知道客户端处理请求的结果。也就是说我们一个登录流程,用户在网页上输入账号密码并点击登录,浏览器发送登录请求,服务器收到请求并验证用户,记录用户登录状态,然后返回成功登录的信息,最后浏览器收到成功的消息,但是在下次浏览器发送请求时,如果这时候请求没有做特殊的处理,我是无法知道这个用户是否登录的状态。
在登录请求中,浏览器发送请求后,我们看下服务端做了什么处理:
sessionId也是服务端自动生成。
关键词:XSS
,html渲染
,前端安全
“跨站脚本攻击”(Cross Site Scripting)
第一种分类方式:
第二种分类方式:
反射型:经过后端,不经过数据库
存储型:经过后端,经过数据库
DOM:不经过后端,DOM—based XSS漏洞是基于文档对象模型Document Objeet Model,DOM)的一种漏洞,dom - xss是通过url传入参数去控制触发的
]]>“大多数职业(从医生到电工),多年的经验等同于多年的专业知识。但是在软件开发中,技术变化如此之快,你花费了大量时间学习技术和工具,一旦这些技术被取代,你的知识将变得毫无价值,因为它们大部分都是实施的细节。最终,所有这些年,你确实积累了一些一般性的经验,但与具体实施相关的知识,你都不再掌握了。
唯一留下的是那些基本的东西,你应该专注于软件开发的核心知识和数学知识,您的这些技能会不断增长,而不是随着技术潮流的变化而消失。”
一般来说,linux系统都会自带python,但是python的版本不会是最新的,例如centos6.5的是2.6.6的,接下来我们就将系统的python更新到2.7的最新版本。
首先从官网下载最新版的python
1 | wget https://www.python.org/ftp/python/2.7.13/Python-2.7.13.tar.xz |
这个过程可能比较慢,官网下就是很慢orz。下完后解压然后开始安装
1 | tar -xvf Python-2.7.13.tar.xz |
这个过程也会需要一段时间,如果中途报错,基本就是gcc的原因,先更新gcc(yum -y install gcc
)再重新安装。默认会将python安装/usr/local/bin/python2.7
上述步骤完成后我们来看现在系统中python的版本
1 | /usr/local/bin/python2.7 -V |
可以看到我们系统默认的python还是老版本的,说明我们/usr/bin/python
仍然指向2.6的python,所以接下来首先给旧版换个名称,然后将/usr/bin/python
指向我们新版python的目录
1 | mv /usr/bin/python /usr/bin/python26 |
ok,还剩最后一步,修改yum的配置,因为yum用的是老版本的python,我们更新系统的python后导致yum所使用的python版本不符
1 | vi /usr/bin/yum |
看到没,yum使用的是系统的python,我们将其第一行改成#!/usr/bin/python26
就ok了。
至此我们python的更新已经完成了,接下来再来安装个pip,pip是python的包管理器,可以说pip之于python类似于npm之于node,可想而知其重要性。关于pip的安装,以下给出一个最实用的安装方式。
1 | wget https://bootstrap.pypa.io/get-pip.py |
get-pip.py 会根据你的python版本自动安装pip及其依赖包(setuptools)。
]]>Linux部署篇 - 如何在linux上不是express的node服务
这篇文章主要讲的是linux系统下一个express的node项目的部署,express是一个web开发的node框架。
部署工具推荐使用pm2,还有个是forever,但是相对而言pm2用起来更方便的,所以首先我们安装pm2
1 | npm install pm2 -g |
然后使用pm2创建进程,例如我们这里启动一个express项目,关于pm2的其他使用看这里
1 | $ pm2 start bin/www |
express默认监听端口是3000,现在你应该已经可以通过http://yourdomain:3000
访问你的node项目了,如果还不行看这里。我们最后还可以使用nginx服务器做代理,修改nginx.conf
1 | http { |
现在你应该已经可以直接通过http://yourdomain
访问你的node项目了。
本文介绍怎么使用urllib2和thread让你的爬虫多线程运行。
1 | # !/usr/bin/env python |
]]>更多爬虫技巧请戳传送门
通过yum直接安装subversion
1 | yum install subversion |
创建版本库。版本库是一个svn用来进行版本管理的控制中心,我们将/home/svn作为我们管理所有版本库的目录,然后在其中创建一个project的版本库。
1 | mkdir /home/svn |
修改版本库配置。
1 | cd /home/svn/project && ls |
创建完毕的版本库中存在conf,hooks,locks等目录和文件。
1 | cd conf/ && ls |
进入conf目录我们能看到3个配置文件:authz,passwd,svnserve.conf。authz是svn用户的权限配置文件,passwd是svn用户密码的配置文件,svnserve.conf是版本库的主配置文件。
1 | vi passwd |
我们先修改passwd,在文件末尾添加一行表示添加一个用户。
1 | [users] |
然后修改authz,在文件末尾添加我们刚刚创建的svn用户对根目录的权限设置,这里我们设置为rw(读写),这里的根目录指的是版本库的根目录。
1 | [/] |
最后修改svnserve.conf,去除下面配置前的注释。
1 | anon-access = read |
这里说明一下,网上的资料有的说讲realm = My First Repository
这一行也去注释,实际测试没有必要,还有如果你在客户端尝试checkout时出现权限验证的错误可以将authz-db = authz
这一行注释回去就可以了。
启动和关闭svn服务。
1 | svnserve –d –r /home/svn/project/ |
-d指明以守护模式运行,-r指定根目录,还可以使用–listen-port=切换监听端口,默认端口为3690。关闭svn服务可以使用以下命令。
1 | ps -ef | grep svnserve |
本地与服务器数据同步。我们现在可以在客户端环境checkout到本地,windows环境下可以使用第三方工具TortoiseSVN,操作很方便,如果是linux客户端,我们可以使用checkout命令。
1 | $ svn checkout svn://myurl/project |
这里有一个误区,没有接触过版本控制的人可能尝试了在本地添加一个文件并上传,然后发现在服务器上找不到上传的文件。实际上版本控制记录了你的提交,你可以在本地的另一处checkout该项目发现你上传的文件已经存在了。在服务器上其实也是同一个道理,你在服务器上同样需要checkout一份代码,我们之前所作的其实就是创建了一个文件控制的中枢,你提交文件到svn服务器,然后在你的服务器上更新代码。也许你会觉得这个步骤繁琐了,别担心,我们可以使用svn的post-commit来自动同步代码的更新,来达到你本地的代码提交后服务器上的代码同步更新的功能。
1 | cd /home/svn/project/hooks |
找到你版本库的hooks目录,拷贝post-commit.tmpl为post-commit,然后编辑该文件,添加以下内容。
1 | REPOS="$1" |
注意第三条语句,/home/wwwroot/project/就是我放在服务器上的代码目录,这里的操作就是每次有svn提交后进入该目录然后更新代码。还要注意的是,请给予post-commit足够的权限,不然提交的时候会提示该文件出错。
1 | chmod 770 post-commit |
仍然需要注意的一个问题是如果你的svn目录中存在中文命名的文件,那么可能会出现以下类似的错误:
1 | svn: Error converting entry in directory '.' to UTF-8 |
这个时候你需要在之前编写的post-commit文件里的命令前加上一段设置编码。
1 | export LANG=en_US.UTF-8 && cd /home/wwwroot/project/ && /usr/bin/svn update --username sidfate --password sidfate |
网上下到带有密码的zip文件,但是又没有给出密码,着实头疼,不如自动动手用python写一个zip暴力破解程序。
阅读文本前推荐您先浏览之前的文章:Python 创建黑客字典
python提供了对zip文件的操作库zipfile。其中 ZipFile 类中的 extractall() 方法提供了 pwd 参数作为 zip 文件的密码。更多关于 zipfile
python 中的 optparse 模块可以用来处理命令行参数。其主要使用流程:首先,必须 import OptionParser 类,创建一个 OptionParser 对象,然后,使用 add_option 来定义命令行参数,每个命令行参数就是由参数名字符串和参数属性组成的。最后,一旦你已经定义好了所有的命令行参数,调用 parse_args() 来解析程序的命令行。更多关于 optparse
作为测试,我们将我们写一个名为 unzip.py 的脚本来破解密码,在它的同级目录下存在测试文件 test.zip 和字典文件 dict.txt(关于字典文件的创建),其中 zip 文件的密码为 123456 。
1 | import zipfile |
本文告诉你什么是黑客字典,如何用python轻松的生成黑客常用的密码字典。
黑客字典,其实就是一些常用的字符集合,往往包含了大量的刻意生成的字符串组合,用来暴力破解某些密码。我们可以用python,只书写几行代码,就可以迭代生成大量我们需要的字符串组合。
首先简单的介绍下用到的2个库:string 和 itertools。
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
1 | - string.lowercase: `abcdefghijklmnopqrstuvwxyz` |
这里我们的脚本文件为dict.py
,后面的参数分别为密码的最小长度、密码的最大长度以及保存的字典文件名。上面的例子运行成功后会在脚本文件同级目录下生成一个dict.txt
的字典文件,其中包含6位随机纯数字的所有组合。
用php自带的函数生产密码,验证密码;php时间和日期函数的使用。
password_hash()
函数生成密码的hash值。该函数的第一个参数是纯文本密码,第二个参数选择所用的hash算法,目前支持的算法有PASSWORD_DEFAULT
(采用bcrypt算法,PHP5.5.0默认)和PASSWORD_BCRYPT
(使用 CRYPT_BLOWFISH 算法),第三参数指定所有的选项,选项有salt(手动指定的盐,7.0已被废弃)和cost(工作因子)。工作因子的默认值是10。
password_verify()
函数可以验证密码和指定的哈希值,验证成功返回true,失败返回false。
password_needs_rehash()
函数验证给定的hash值是否实现了提供的算法和选项。该函数一个参数是给定的hash值,第二个参数选择hash算法,第三参数是选项。
DateTime类
DateTime类提供了一个面对对象的接口,用于管理日期和时间。一个DateTime实例表示了一个具体的日期和时间。通过DateTime类可以简单的创建DateTime实例。
1 |
|
DateTime类的构造函数可以不传任何参数,表示获取的是当前日期和时间的实例,也可以传递一个表示日期和时间的字符串指定其他的日期和时间,该字符串必须符合特定的格式。
当我们需要处理各种特殊格式的日期和时间时,可以使用DateTime::createFromFormat()
静态方法,针对自定义格式创建DateTime实例。该方法的第一个参数是表示日期和时间格式的字符串,第二个参数是使用指定格式的日期和时间的字符串。DateTime::createFromFormat()
静态方法使用的日期和时间字符串的格式和date()函数一样,具体的参照看这里。
1 |
|
DateInterval类
DateInterval实例表示长度固定的时间段(例如“两天”),或者相对而言的时间段(例如“昨天”)。DateInterval实例用于修改DateTime实例。例如DateTime类提供了add()
和sub()
两个方法,这两个方法的参数都是DateInterval实例,指定要添加到DateTime实例的时间量,或要从DateTime实例中减去的时间量。
DateInterval类构造方法的参数是一个表示间隔规格的字符串。间隔规格是一个以“P”开头的字符串,后面接一个整数,最后是一个周期标识符,限定前面的整数。有效的周期标识符有:
间隔规格中既可以有日期也可以有时间,如果两者都有,需要在两者中间加上“T”。例如“P2D”表示2天,P2DT1H2M
表示2天1小时2分钟。下面一个例子通过给DateTime的add()
方法传递了DateInterval实例来修改DateTime实例表示的日期。
1 |
|
DateTimeZone类
DateTime类构造方法还存在第二个参数,为一个DateTimeZone实例,表示的是时区。DateTimeZone实例可以有DateTimeZone类构造方法生成,需要传递给构造方法一个有效的时区标志。
1 |
|
DatePeriod类
当我们需要迭代处理相同间隔的一系列日期和时间时,我们可以使用DatePeriod类。DatePeriod类的构造方法必须提供3个参数:
DatePeriod的实例是一个迭代器每次迭代都会产生一个DateTime实例。
1 |
|
]]>ModernPHP 系列全集:传送门
现代化的php框架基本都继承了PDO,让我们来看看PDO的来源,以及它原生的使用方式。
PDO(PHP Data Objects,PHP数据对象)是一系列的PHP类,抽象了不同数据库的具体实现,提供了统一的接口来操作不同的数据库。
PDO类的构造方法的第一个参数为字符串,指定了DSN(Data Source Name,数据源名称),提供数据连接的详细信息。DSN的开头是数据库驱动器的名称(例如mysql或sqlite),然后接一个”:”号,后面是其他连接信息(主机名、端口号,数据库名和字符集等),更多内容可以查看pdo.drivers。PDO类构造方法的第二个参数和第三个参数分别是数据库的用户名和密码。举一个使用的列子:
1 |
|
当然,我们不建议使用硬编码的方式写入数据库的连接信息,这样一旦数据库连接出错,很可能就暴露给用户你的PHP代码。所以我们更建议将数据库连接信息写入一个配置文件,然后在主程序中引入配置文件。下面给出一个配置文件的样例:
1 |
|
在SQL语句中使用用户输入的数据一定要过滤,PDO通过预处理语句和参数绑定将过滤输入这项操作变得简单很多。
预处理语句是PDOStatement
的实例。一般可以使用PDO实例的prepare()
方法获取预处理语句对象。
1 |
|
在上面的SQL语句中,:email
是具名占位符,可以在$statement
实例中通过bindValue()
方法绑定为我们需要的值。预处理语句可以自动过滤$email
的值,防止SQL注入的发生。在一个SQL语句中可以存在多个多个具名占位符,bindParam()
方法提供了第三个参数,指定需要绑定值的数据类型(例如PDO::PARAM_INT
,更多常量),默认的数据类型是字符串。
有了预处理语句之后,就可以在数据库中执行SQL查询操作了。调用预处理对象的execute()
方法后会使用绑定的所有数据执行SQL语句。如果执行的操作是UPDATE,INSERT或DELETE,execute()
方法后工作就结束了(当然还可以通过该函数返回TRUE或FALSE判断操作成功与否)。但是如果是SELECT操作,我们还要去获取返回的数据,这时可以通过预处理对象的fetch()
,fetchAll()
,fetchColumn()
或fetchObject()
方法获取查询结果。
fetch()
用于获取结果数据集的一行,这个方法适用于迭代大型的数据集。fetchAll()
可以获取整个数据集,fetch()
和fetchAll()
的第一个参数为PDO类的返回方式常量,决定了如何返回查询结果,常用的常量有:
举一个使用的例子:
1 |
|
事务是指把一系列数据库语句当成单个逻辑单元来处理。也就是说,事务中的一系列sql语句要么都执行成功,要么根本不执行,也就是说事务具有原子性。事务的原子性也保证了数据的一致性,安全性和持久性。事务还能提升性能,因为它实际上是把多个查询排成队列,一次全部执行。
PDO支持事务,而且使用方便,你只要把构建和执行sql的操作放在PDO实例的beginTransaction()
和commit()
中。beginTransaction()
方法的作用是把后续生成的sql语句排入队列,commit()
方法执行原子事务队列中的sql语句。
下面演示一个未使用事务的例子,其操作是从A账户的钱转入B账户:
1 |
|
这里有一个问题是,当从A账户取钱的操作成功后,发生了一些不可抗拒的错误,导致了后面的把钱加到B账户的操作没有完成,那么这50元就凭空消失了,这显然是不合理的。使用事务我们可以解决这样的问题。下面是使用事务的方式:
1 |
|
]]>ModernPHP 系列全集:传送门
php自带有验证过滤方法,可以满足大部分的使用场景,不用再自己写正则了。
使用htmlentities()
函数过滤html输入,它会将字符转换成html实体。但是在默认情况下,htmlentities()
函数不会转义单引号,也检测不出输入字符串的字符集。所以正确的使用方式是额外传入2个参数,即第一个参数是输入的字符串,第二个参数设为ENT_QUOTES常量,转义单引号,第三个参数设为输入字符串的字符集。
1 |
|
一个低级而常见的错误即是将原始输入数据直接拼接成SQL查询语句,这将导致程序产生SQL注入漏洞。下面将描述一个错误的例子:
1 |
|
这时,假如用户构造恶意的输入数据,例如passwd=abc";--
,所有用户的密码都将会被设置为abc,很多数据库把--
当做注释符。使用PDO预处理语句可以防止这种情况的发生,关于PDO预处理在后面的文章中将讲到。
PHP提供了filter_var()
和filter_input()
函数来过滤不同类型的输入:电子邮件地址,URL编码字符串,整数,浮点数,HTML字符,URL和特定范围内的ASCII字符。
1 |
|
filter_var()
函数如果验证成功,会返回验证的值,如果验证失败,返回false。
]]>ModernPHP 系列全集:传送门
PSR是PHP Standards Recommendation(PHP推荐标准),规定了一系列PHP程序的标准方案。
PSR是PHP Standards Recommendation(PHP推荐标准)的简称。到目前为止,有四个被广泛认同的标准:
也许你还见过PSR-0(自动加载标准),PHP-FIG废弃了该推荐规范,用PSR-4替代了它。
php标签
必须把PHP代码放在<?php ?>或<? ?>标签中。
编码
必须使用UTF-8 without BOM的编码。
目的性
一个PHP文件可以定义符号(类,性状,函数和常量等),或者执行副作用的操作(例如,生成结果或者处理数据),但是不能同时做这两件事。
自动加载
PHP命名空间和类必须遵守PSR-4自动加载器标准。
类名
PHP类名必须使用首字母大写的驼峰命名方式,比如UserData。
常量名
PHP常量的名称必须全部使用大写字母,必要的话可以使用下划线连接单词,例如PAGE_SIZE。
方法名
PHP方法名必须使用首字母小写的驼峰命名方式,比如getUserInfo。
PSR-2相对于PSR-1的代码风格更加严格。
贯彻PSR-1
使用PSR-2代码风格之前必须先贯彻PSR-1。
缩进
这个话题就很热门了,代码的缩进一直分为两个阵营:tab键阵营,多空格阵营。《硅谷》中有一集男女两名程序员正一边编程一边约会,小伙子忍受不了姑娘使用空格键缩进,认为Tab键更节省文件体积,最后愤然离去,声称绝对不会和用空格键缩进的人滚床单,以免孩子面临艰难的人生抉择……可见这个问题在程序员中是比较突出和常见的,PSR-2推荐规范要求PHP代码使用4个空格的缩进。
文件代码行
PHP文件必须使用UNIX风格的换行符(LF),最后要有一个空行,而且不能使用PHP关闭标签?>。PHP每行代码不能超过80个字符,至少不能超过120个字符。每行末尾不能有空格。
关键字
PHP关键字应该使用小写字母。
命名空间
每个命名空间声明语句后必须跟着一个空行,类似的,使用use导入命名空间的语句后面也要加一个空行。下面是一个示例:
1 | <php |
PSR-2要求类定义体的起始括号应该在类名之后新起一行写,结束括号必须在定义体后新起一行,就像上面那个例子一样。如果类扩展其他类或者实现接口,extends和implements关键字必须和类名同一行。
方法
方法定义体的括号位置应该可类定义的括号一样。方法的参数需要注意:起始圆括号之后和结束圆括号之前都没有空格,方法的每个参数后面都有一个逗号和空格。
1 | <php |
可见性
类中的每个属性和方法都要声明可见性,即public、protected或者private,来决定类的内部和外部如何访问这些属性和方法。请不要再对属性使用var关键字,在私有方法前加下划线。如果雷属性或方法声明为abstract或final,这两个限定符必须放在可见性关键字之前。static限定符应该放在可见性关键字之后。
1 |
|
控制结构
所有控制结构关键字后面都要有一个空格。控制结构关键字包括:if、elseif、else、switch、case、while、do while、for、foreach、try和catch。如果控制结构关键字后面有一对圆括号,起始圆括号之后和结束圆括号之前都没有空格。与类和方法的定义体不同,空间结构关键字后面的起始括号应该和控制结构关键字在同一行。
1 |
|
这个推荐规范讲的是一个接口,规定PHP日志记录器组件可以实现的方法。
符合PSR-3推荐规范的PHP日志记录,必须包含一个实现Psr\Log\LoggerInterface
接口的PHP类。PSR-3接口复用了RFC-5424系统日志协议,规定要实现9个方法。
1 |
|
每个方法对应RFC 5424协议的一个日志级别,而且都接受两个参数。第一个参数$message
必须是一个字符串,或者是一个有__toString()
方法的对象。第二个参数$context
是可选的,这是一个数组,提供用于替换第一个参数中占位符的值。
$context
参数用于构造复杂的日志消息。消息文本中可以使用占位符,例如{placeholder_name}
。占位符由{,占位符名称和}组成,不能包含空格。$context
参数的值是一个关联数组,键是占位符的名称,对应的值用于替换消息文本中的占位符。
PSR-4的精髓是把命名空间的前缀和文件系统中的目录对应起来。下面是一个PSR-4规范的自动加载器的例子:
1 |
|
]]>ModernPHP 系列全集:传送门
PHP Framework Interop Group(框架协同工作组,简称PHP-FIG,)
对于PHP这门语言,众所周知的是其组件和框架的数量极其的多,毕竟世界上最好的语言。
如果你正在为项目选择PHP框架,你一定会为其多种多样的框架而头疼,例如有小巧精致的CI(CodeIgniter),最近几年都很火很全面的Laravel,国产框架ThinkPHP,或者企业使用广泛的Yii等等。
然后你可能会试着了解下每个框架的特性来个选出你想要的,你发现Symfony的辅助库很棒,大大减少了自己去封装时间,然而你考虑到你的项目的大小由此选择了CI,这时你会想到将Symfony的辅助库搬到CI框架中话就好了,但是你只能专门为你的项目编写一个一次性的适配器。
所以,看出问题了吗,单独开发的框架没有考虑到和其他框架的通信,而一旦你选择了这样的框架,你只能束缚在它的生态系统中了。 为了解决这个问题,一些PHP框架的开发者在php|tek碰头并组建了PHP Framework Interop Group(框架协同工作组,简称PHP-FIG,)。PHP-FIG制定了推荐规范,PHP框架可以自愿的遵守这些规范,改进与其他框架通信和共享的功能。PHP-FIG是框架代表自发组织的,它发布的推荐规范,不是强制规范,你也可以申请加入其中,共同推进PHP社区的发展。
PHP-FIG的使命是实现框架的互操作性,指的是通过接口,自动加载机制和标准的风格,让框架相互合作。
PHP框架之间通过共用的接口合作,框架只需要知道第三方依赖能提供什么方法,并不需要知道方法具体是如何实现的。PHP开发者使用接口可以开发、共享并使用专门的组件,而无需使用庞大的框架。
PHP框架之间通过自动加载机制合作。自动加载即时PHP解释器在运行时按需自动找到并加载PHP类的过程,在PHP标准出现之前,PHP组件和框架会使用魔术方法__autoload()
或spl_autoload_register()
方法实现各自的自动加载器。因此,你要去学习每个框架各自的加载器,但是现在,很多框架都遵守一个自动加载器标准,我们可以只使用一个自动加载器就能混合搭配多个PHP组件。
风格指的的是标准的代码风格,即如何使用空格、大小写、括号的位置等等。即使在一个团队合作的项目中,我们都知道共同使用一种代码风格的好处,从代码的可阅读性,可维护性上都有很大的帮助。将代码风格标准化还能降低项目新贡献者的门槛,让他们能花更多时间用在提升框架的价值上,而不是为不同的风格而头疼。
php-fig中文github:传送门
]]>ModernPHP 系列全集:传送门
本文介绍PHP中的闭包和匿名函数的概念,以及它们的具体使用。
闭包和匿名函数是PHP5.3.0中引入的特性。下面介绍下这两个概念的定义,可能看上去会比较难理解,但是后面会给出实例可以更好的掌握。
闭包指的是在创建时封装周围状态的函数。即便闭包所在的环境不存在了,闭包中封装的状态依然存在。
匿名函数其实是没有名字的函数,匿名函数可以赋值给变量,也能像对象那样被传递,常常被用作函数或者方法的回调。
理论上讲,闭包和匿名函数是不同的概念,但是PHP将其是做相同的概念。PHP闭包的语法和普通函数相同,但是其实它是伪装成函数的对象,事实上是Closure类的实例。
1 | $closure = function($name) { |
我们之所以能够调用$closure变量,是因为这个变量的值是一个闭包,而且闭包对象实现了__invoke()魔术方法。只要变量名后有(),PHP就会去查找并调用__invoke()方法。
很多的PHP函数会用到回调函数,比如array_map()和preg_replace_callback()函数。下面的例子给出了将闭包对象作为回调参数传给array_map()函数。1
2
3
4
5
6$numberPlusOne = array_map(function($nunmber) {
return $number + 1;
}, [1, 2, 3]);
print_r($numberPlusOne);
// 输出 [2, 3, 4]
如果你了解JavaScript的闭包,你应该知道JavaScript的闭包会自动封装引用的状态。但在PHP中并不会,你必须手动调用闭包对象的bindTo()方法或者使用use关键字,把状态附加到PHP闭包中。
使用use关键字附加状态常见得多,下面给出了例子,use关键字将变量附加到闭包时,附加的变量会记住附加时赋予它的值。1
2
3
4
5
6
7
8
9
10
11
12function enclosePerson($name) {
return function ($doCommand) use ($name) {
return sprintf('%s, %s', $name, $doCommand);
}
}
// 将字符串"Clay"封装在闭包中
$clay = enclosePerson("Clay");
// 传入参数,调用闭包
echo $clay('go away!');
// 输出 "Clay, go away!"
上述例子中,enclosePerson函数有一个参数$name,这个函数返回一个闭包对象,并且在闭包对象中封装了$name参数。即便返回的闭包对象跳出了enclosePerson()函数的作用域,$name参数的值还是能获取到,因为$name变量仍在闭包中。
上面提到过,PHP闭包是对象,它有一个bindTo方法,这个方法可以把Closure对象的内部状态绑定到其他对象上。bindTo的第二个参数很重要,其作用是指定绑定闭包的那个对象所属的PHP类。因此闭包可以访问闭包对象中受保护和私有的成员变量。
PHP框架经常使用bindTo方法把路由URL映射到匿名函数上。框架会把匿名函数绑定到应用对象上,这么做可以在这个匿名函数中使用$this关键字引用重要的应用对象,下面给出例子。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26class App
{
protected $routes = [];
protected $responseStatus = "200 OK";
protected $responseContentType = "text/html";
protected $responseBody = "Hello world";
public function addRoute($routePath, $routeCallback)
{
$this->routes[$routePath] = $routeCallback->bindTo($this, __CLASS__);
}
public function dispatch($currentPath)
{
foreach ($this->routes as $routePath => $callback) {
if($routePath === $currentPath) {
$callback();
}
}
header('HTTP/1.1 ' . $this->responseStatus);
header('Content-type: ' . $this->responseContentType);
header('Content-length: ' . mb_strlen($this->responseBody));
echo $this->responseBody;
}
}
App类中的addRoute()方法,它有2个参数,分别是路由路径(例如/users/josh)字符串和一个路由回调。dispatch()方法的参数是当前HTTP请求的路径,它会调用匹配的路由回调。第10行是重点,我们把回调函数绑定在了当前的App实例中,这么做就能在回调函数中处理App实例的状态。1
2
3
4
5
6$app = new App();
$app->addRoute('/users/josh', function() {
$this->responseContentType = 'application/json;charset=utf8';
$this->responseBody = json_encode(['name'=> 'josh']);
});
$app->dispatch('/users/josh');
]]>ModernPHP 系列全集:传送门
PHP生成器是PHP5.5.0引入的功能,生成器就是简单的迭代器,但与标准的PHP迭代器不同,PHP生成器不要求实现Iterator接口,从而减轻了类的负担。生成器会根据需求计算并产出要迭代的值,这对应用性能的影响重大。假如标准的PHP迭代器经常在内存中执行迭代操作,这要预先计算出数据集,性能低下;如果要使用特定的方式计算大量数据,对性能的影响更甚。此时我们使用生成器,即时计算出产出后续值,不占用宝贵的内存资源。
PHP生成器不能满足所有迭代操作的需求,因为如果不查询,生成器永远不知道下一个要迭代的值是什么,在生成器中无法快进或后退。生成器还是一次性的,无法多次迭代同一个生成器。不过,如果需要,可以重建或克隆生成器。
生成器的创建很简单,因为生成器就是PHP函数,只不过要在函数中一次或多次使用yield关键字。与普通的PHP函数不同的是,生成器从不返回值,只产出值。1
2
3
4
5function myGenerator() {
yield 'value1';
yield 'value2';
yield 'value3';
}
调用生成器函数时,PHP会返回一个属于Generator类的对象。这个对象可以使用foreach()函数迭代。每次迭代,PHP会要求Generator实例计算并提供下一个要迭代的值。生成器的优雅体现在,每次产出一个值后,生成器的内部状态会停顿;向生成器请求下一个值时,内部状态又会恢复。1
2
3
4
5
6
7
8foreach ($myGenerator as $yieldedValue) {
echo $yieldedValue, PHP_EOL;
}
// 输出:
// value1
// value2
// value3
下面我们来实现一个简单的函数,用于生成一个大范围内数值集合。
一个普通的栗子:1
2
3
4
5
6
7
8
9
10
11
12
13function makeRange($length) {
$dataset = [];
for($i=0; $i<$length; $i++) {
$dataset[] = $i;
}
return $dataset;
}
$customRange = makeRange(1000000);
foreach ($customRange as $i) {
echo $i, PHP_EOL;
}
上述的代码没有善用内存,因为makeRange()函数要为预先创建的一个有一百万个整数组成的数组分配内存。PHP生成器实现相同的操作。不过一个只会为一个整数分配内存。
使用生成器的栗子:1
2
3
4
5
6
7
8
9function makeRange($length) {
for($i=0; $i<$length; $i++) {
yield $i;
}
}
foreach (makeRange(1000000) as $i) {
echo $i, PHP_EOL;
}
这只是一个虚构的例子,在现实中,你可以想象一下使用生成器可以计算什么样的数据集。假设我们现在想迭代一个4GB的文本文件(例如黑客字典),而你的内存可能比4GB还小,这时候就不能把整个文件加载到内存中,下面使用生成器来实现读取一个cvs文件。
1 | function getRows($file) { |
上述例子一次只会为csv文件的一行分配内存。迭代大型数据集或者数列时最适合使用生成器,因为这样占用的系统内存量极少。
]]>ModernPHP 系列全集:传送门
本文讲述了何谓性状,以及PHP中性状出现的意义,怎么去使用。
这是PHP5.4.0引入的概念,既像类又像接口。性状是类的部分实现(即常量,属性和方法),可以混入一个或多个类中。性状有两个作用:表名类可以做什么(像是接口),提供模块化的实现(像是类)。
PHP语言使用的是典型的继承模型。在这个模型中,我们先创建基类,实现基本的功能,然后扩展这个基类,通过继承基类创建更多具体的类。这叫做继承层次结构,很多编程语言都是用这个模式。
但是,如果想让两个无关的PHP类具有类型的行为,应该如何去做?例如,Satellite(卫星类)和Car(汽车类)两个类的作用十分不同,在继承层次结构上没有共同的父类,但是这两个类应该都能使用地理编码技术转换成经纬度,然后在地图上显示。
这时候就可以使用性状来解决。性状可以把模块化的实现方式注入到多个无关的类中,还能促进代码的重用。
定义一个性状:1
2
3
4
trait MyTrait {
// 实现
}
我们接着使用上面提到的例子来演示如何使用性状。我们希望Satellite和Car类都能提供地理编码功能,而且意识到继承和接口都不是最佳方案。我们选择创建Geocodable(地理编码)性状,返回经纬度,然后在地图中绘制。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
trait Geocodable {
/* @var string */
protected $address;
/* @var \Geocoder\Geocoder */
protected $geocoder;
/* @var \Geocoder\Result\Geocoded */
protected $geocoderResult;
public function setGeocoder(\Geocoder\GeocoderInterface $geocoder)
{
$this->geocoder = $geocoder;
}
public function setAddress($address)
{
$this->address = $address;
}
public function getLatitude()
{
if(!isset($this->geocoderResult)) {
$this->geocodeAddress();
}
return $this->geocoderResult->getLatitude();
}
public function getLongitude()
{
if(!isset($this->geocoderResult)) {
$this->geocodeAddress();
}
return $this->geocoderResult->getLongitude();
}
protected function geocodeAddress()
{
$this->geocoderResult = $this->geocoder->geocode($this->address);
return true;
}
}
这个Geocodable性状定义了三个类属性:一个表示地址;一个是地理编码器对象(\Geocoder\Geocoder类的实例,这个类是来自第三方的组件);一个是地理编码器处理后的结果对象(\Geocoder\Result\Geocoded类的实例)。我们还定义了4个公有方法和一个受保护的方法。setGeocoder()方法用于注入Geocoder对象;setAddress()方法用于设定地址;getLatitude()和getLongitude()用于返回经纬度;geocodeAddress()方法把地址字符串传给Geocoder实例,获取地理编码器处理的结果。
1 |
|
这里需要注意,PHP解释器在编译的时候会把性状复制黏贴到类的定义体中,但是不会处理这个操作引入的不兼容问题。如果性状假定类中有特定的方法或属性(在性状中没有定义),要确保相应的类中有对应的属性和方法。
]]>ModernPHP 系列全集:传送门