PHP是一种解释性语言,可用于对网页进行预处理。PHP脚本在服务器端运行,其运行结果是一个可用来显示的网页。尽管可以完成许多类似工作,但是JavaScript和PHP的一大区别就是,JavaScript是在浏览器端运行的。事实上,浏览器会接收JavaScript代码并运行它,所以用户是可以查看JavaScript代码的。而PHP不会将原始代码交给浏览器,只会将其运行的结果交给浏览器,所以用PHP处理用户登陆、用户权限等问题是安全可靠的。
实际编写的时候,通常采用的方式是建立扩展名为php的文件(网页文件本质上是文本文件)。编写php代码和编写html代码并没有多少区别,而最方便的地方在于,在一个php文件中,两种代码是可以混编的。
规则:php代码需要包含在
提示:这是一个php和html混编的较为生动的例子。
这里的意思是,如果php中的变量$var的值为true,则放置一个标签,否则放置另一个标签。PHP的if语句可以像上面那样写,也可以写成C/C++风格的:
PHP采用的操作符和C/C++是类似的,例如用=表示赋值,==表示相等性比较,以及<和>(小于、大于)比较符、!取反、&&逻辑与、||逻辑或等。当然,也支持+-*/等数学表达式的运算。
这一点PHP和许多其他常见的编程语言很类似,也可以用if...else选择语句(之前已经见过了),PHP还包括while循环、foreach循环等,以后遇到了会详细介绍。
使用MySQL数据库是存储数据的一种方法,MySQL需要和PHP配合来完成对数据库的查询(这里术语“查询”包括写入、更新、读取等)操作。利用MySQL,你可以创建许多数据库(database),每个数据库可以包含多个表(table),而每个表包含若干字段。为了高效,一般会采取分类维护多个表的方式,而不是把所有数据都储存在同一个表中。
MySQL需要服务器支持。使用的第一步是建立一个数据库,可以用相应的图形化工具(例如phpMyAdmin)来建立数据库,也可以在终端直接使用下列SQL语句来创建一个名为database_name的数据库:
CREATEDATABASEdatabase_name;
创建好数据库之后,需要创建表。可以通过下列SQL语句来创建一个名为table_name的表:
USEdatabase_name;CREATETABLEtable_name(first_namevarchar(30),last_namevarchar(30),);
第一句说明在哪个数据库中添加表,第二个说明添加的表的详情。这里我们在表中添加了两个字段,分别叫做first_name和last_name,它们的类型是varchar(30)。其中varchar是一种可变长字符类型,而30代表了最大长度。
其他常见的数据类型如下:
你可能已经看出来了,MySQL的注释符为--。你可能觉得没太大用,但是它却是一种稍后要提到的攻击的关键之处。此外,一般字符串都应该使用变长的VARCHAR类型,而非定长的CHARACTER类型,因为后者会占据更多的空间,而这是不必要的。
现在你已经创建好了SQL数据表,并对PHP语言有了一个概览。下面我们直奔主题,学习如何对数据表进行查询。
为了使PHP和MySQL进行交互,需要为PHP提供你的数据库用户名、密码、数据库名和数据表名。当然,最重要的,查询操作的SQL语句。我们一一来观察是如何实现的。
下面来解释一下这一坨代码的工作原理。
如果把这些代码保存成一个网页,当用户打开网页的时候,如果各项参数正确,它就会完整地运行下去。
这里的SQL语句的含义是向叫做table_name的表中插入一行,其中把colume#字段的值相应地设置为value#。这里只设定了两个字段的数值(表中还可以有其他字段;没有显式说明的字段则留空或者使用数据表指定的默认值)。该语句的通用形式为:
INSERTINTOtable_name(column1,column2,...)VALUES('value1','value2',...)
如果你要做的仅仅是执行一个SQL语句,那么使用这种模式就可以完成。提醒一下,$dbc变量往往是重复使用的。
另一个常用的SQL语句就是修改某一行。它的形式为:
UPDATEtable_nameSETcolumn1='preferred_value1',column2='preferred_value2',...,WHEREid='$id'
当然,这个语句应该是写到一行的,不过为了清晰我分开来写。它的含义是,修改名为table_name的表中字段id的值是变量$id的值的所有行,把column1字段的值设为preferred_value1,把column2字段的值设为preferred_value2,依此类推。这里我们还看到,值既可以用常量表示,也可以用变量表示。
注意:会修改所有符合WHERE子句限定的条件的行(如果省略WHERE子句,就会修改所有行)。WHERE子句可以设定多个条件,也可以使用比较运算符。例如:
WHEREage>20ANDgender='male'WHEREis_admin='true'ORid='$id'
(如果你想问AND和OR为什么不是符号&&和||的话,我想提示你,不要把PHP语言和SQL语言搞混了。这是SQL语言,而我只说过PHP语言和C/C++有些类似)。
下面介绍其他SQL语句。
-删除table_name表中的所有行DELETEFROMtable_name--删除table_name表中email字段为david@example.com的所有行DELETEFROMtable_nameWHEREemail='david@example.com'--删除table_name表DROPTABLEtable_name--删除table_name表中的score字段ALTERTABLEtable_nameDROPCOLUMNscore--给table_name表添加一个叫age的字段,类型为INTEGERALTERTABLEtable_nameADDCOLUMNageINTEGER
可见,第一种方式的本质就是编写一条SQL语句,然后通过PHP来执行它。下面,我们来看第二种方式。
有时,我们不满足于让服务器去执行一条SQL语句。我们会需要从数据库中查询信息,然后把得到的信息储存起来(其实就是储存在变量中)。这样,我们需要一些额外的工作。先看一坨代码:
这里我们省略了define语句。
这一坨代码和上一坨的主要区别是,我们使用了mysqli_query()函数的返回值,把它保存到$result变量中。这个$result变量里边保存的即为执行SELECT语句的返回结果。
解释一下SELECT语句,它的作用是选取table_name表中符合WHERE子句条件的所有行。上面的语句会选定每一行的所有字段(通配符说明了这一点),并且把这些信息全部储存到变量$result中。
然后,用变量$row储存mysqli_fetch_array()函数的返回值。$row这个变量非常神奇,$row['column_name']这个事儿包含的内容正是刚才选定的行的column_name字段的值(事实上,$row正是一个数组)。这里,我们把它赋给了$problem_title变量。
到这里你应该问一个问题:如果满足WHERE子句条件的有不止一行的话怎么办?要解答这个问题,需要稍微细致的讲解一下$row这个事儿。如果满足条件的只有一行,那么使用$row=mysqli_fetch_array($result)自然会把这唯一的一行信息储存到$row中。如果有很多行,那么第一次使用$row=mysqli_fetch_array($result)会把第一行的信息储存到$row中,而第二次使用$row=mysqli_fetch_array($result)会把第二行的信息储存到$row中。如果这时没有下一行了,再次调用的话$row会储存逻辑假(false或0)。类似,如果符合WHERE子句条件的一行都没有,那么执行后$row直接存储逻辑假。
最后补充一点刚才没有提到的。如果不需要所有字段的数据,可以只选择需要的字段。方法是把原来SQL语句中的通配符换成字段名称。例如:
SELECTproblem_name,problem_typeFROMtable_nameWHEREproblem_id='$id'while循环在PHP中的应用举例如果我们要把一个数据库的许多行信息都展示在网页中,那么需要用到while循环和上面的第二种方式。代码如下:
".$user_id." ";echo"";}>
如果有一定编程基础的话上面的代码很容易看懂。上面新出现了三种用法,说明如下:
SELECTuser_id,user_rankFROMtable_nameORDERBYuser_rankDESC,user_idASC关于PHP中的echo语句,它可以用来生成文本,类似于C中的printf()函数。这里利用它直接生成HTML代码。它的用法参考例子就可以了。关于符号.的用法,它的作用是连接字符串(和变量),往往和echo配合使用,用法参考示例。从表单获取信息概述这一部分我们演示如何构建一个表单,使用户填写这个表单并把内容储存到数据库。这一技术是用户注册系统和用户互动的基础。
要实现这个功能,需要HTML和PHP配合完成。HTML负责表单,而PHP负责获取信息并使用SQL查询储存信息。首先来看HTML部分(就是普通的表单):
属于HTML部分的不再解释了,说一说新鲜的。这里的action属性后面的$_SERVER['PHP_SELF'](严格地说,$_SERVER),是PHP的一个超级全局变量,内容是当前页面的相对路径,例如signup.php。这个action属性的含义是指定用户填写的信息在哪里被处理,这里是在当前页面处理。一般的做法都是将负责处理这部分信息的PHP代码和HTML代码放在同一页面内。
下面来看一下相应的PHP处理部分的代码:
首先仍然是建立数据库连接。当用户点击sumbit按钮后,表单的内容会被储存在PHP中$_POST超级全局变量内,这个超级全局变量仍然是一个数组。isset()函数用来检查变量是否被设置,只有用户点击submit后isset($_POST['submit'])才返回真,所以不用担心,首次加载表单(那时用户还没有填写任何内容)是不会执行这部分PHP代码的,只有用户提交之后才会执行。用户填写的具体内容可以用$_POST['name']来获取。这里的name对应的是HTML中name属性的内容。这一段程序把用户填写的内容赋给变量,然后执行插入到数据库的操作。
这里新出现了一个内容,就是mysqli_close()函数,它的作用是关闭数据库连接。当我们不再需要这个连接的时候,及时关闭是一个好主意。
需要注意的是,这仅仅是最简单的代码,而且实际上是不完善的。如果要真正投入使用,我们需要使它更健壮一些。下面逐一讨论这些内容。
如果用户根本没有填写表单,就直接点击提交按钮,会发生什么?在上面的实例中,PHP依然会乖乖地把空内容插入,而这显然是垃圾信息,不是我们需要的。所以,需要在插入前检查被插入的变量是否为空。例如:
这里出现了empty()函数,用于检查内容是否为空。注意这里使用isset()是无效的,因为isset()检查的是是否“被设置”,而被设置为空也属于被设置。
用户输入有误时,上面的改进除了不执行SQL查询,并没有多少直观上的变化。用户不会收到任何信息表明他们的填写是不合适的。所以我们要在这时产生一些提示,引导用户正确填写表单。
我们执行的SQL语句中包含变量,执行的时候会直接把变量内容替换进去。而如果攻击者在输入框中输入一些危险的字符(通常包含SQL注释符--,以及其他预先精心设置的内容),就可能导致该次SQL查询完全被改写成攻击者需要的意思。为了防范这种攻击,我们需要对可能存在的危险字符进行过滤和转义,较为便捷的方法是使用两个函数。改进后的部分如下:
如果用户第一次填写失败,他们希望能保留已经填写好的内容,只做些修改就好了。这需要使用粘性表单技术。要实现,只需要稍稍改动HTML表单部分的代码:
显而易见,如果用户填写后因为某些原因没有提交而是回到了这个表单,并且之前填写了user字段的内容,那么此时$user变量已经被赋值了。那么就会在HTML表单显示这些内容,避免用户再次输入。
虽然上面说了很多,但是仅仅满足了我们最基本的输入要求。许多时候我们需要更为复杂的功能。举例来说,要写一个注册页面,必须检查用户名是否重复,还要对密码采取某种技术加密以保证安全。
基本原理就是,根据需要判重的字段(例如用户名)去数据库搜索。如果发现结果则用户名重复,如果没有找到则允许注册。需要一个新函数mysqli_num_rows(),返回SELECT语句得到的行数,根据其是否等于0进行判断。
把$user清空是为了配合粘性表单。
需要说明的是exit();函数,它会立刻终止PHP的运行。因为用户已经注册成功,没有必要执行后面的任何代码,所以使用这个函数。写自己的程序的时候可以亲自试验是否需要这一行、PHP和HTML在php中的顺序不同有何影响。我通常的做法是把PHP代码放在前面,HTML代码放在后面。
明文存储密码是对用户很不负责的,不仅数据库管理员可以看到密码,一旦数据库泄漏,密码就会被公开。所以,我们应该加密存储用户密码。在PHP中,可以使用sha1()函数进行加密(sha即securehashalgorithm的首字母缩写),它是一种不可逆的加密,加密后会生成定长的一段字符串,并且是无法由这段字符串还原原密码的。
加密的原理是,用户输入密码后,利用PHP把hash过的密码储存在数据库中。用户登陆的时候,把用户输入的密码进行hash运算,之后和数据库中的进行比对。
使用方法如下:
sha1($password)
可以用设置多个Cookie来存储许多内容,例如用户ID、用户组(管理员还是普通用户)等。
不要问我为什么设定在过去一个小时,设定几个小时都没问题。
设置Cookie有其潜在的危险。由于Cookie是保存在用户本地的,所以用户完全可以通过篡改Cookie来达到他们的目的。所以,把Cookie的值设置得“通俗易懂”不是一个好主意。例如,我们要用Cookie来保存登陆的用户名,如果单纯把这个用户名存入Cookie,那么攻击者会很容易通过修改成他人的用户名来伪造Cookie登陆。所以,我们需要其他的手段来防止这一点。
在网页间传递信息除了刚才介绍的POST方法外,还有GET方法。GET方法是通过URL来完成信息传递的。例如,构造下列网址:
这个例子中我们把2赋给了变量$id。当然,也可以构造这样的网址:
这个特性的用处之一就是可以根据网址的不同,配合数据库查询,返回不同的网页内容。例如我做的在线问答系统,就是根据problem_id来返回不同题目的。
注意,由于GET方法的数值是不可靠的(用户可以手动构造URL来传递他们想要的参数),所以应该仅仅用它来做一些无关痛痒的事情(例如显示不同的页面内容)。这里我并没有强调GET方法的数值是“透明”的:虽然POST方法的数值不会显示在URL中,但是它还是会通过HTTPHeader发送到服务器,用许多插件和小工具都可以查看HTTPHeader信息。
另外,如果你的表单是用来上传文件的,那么估计你会更喜欢POST方法:因为GET方法得到的URL可能会很长,甚至超过浏览器的限制!
最后一部分,来讲一下使用模板构造一个网站。
事实上,网站的每个页面中,有许多部分是完全相同的,例如数据库连接常量(就是那些define语句)以及每一页的header和footer部分等。这样,我们没必要在每一页内写相同的代码。除了麻烦和浪费空间以外,还有一点很重要的原因,就是修改的时候工作量很大。
PHP中require_once语句作用就是把其他文件的内容插入此处。例如,我们可以创建一个define.php,把define语句全部写到里面,并在每个页面顶部添加如下语句:
这样一来,会把define.php中的内容插入当前位置。同理,我们可以建立一个header.php和footer.php,写好页面的头部、底部之后在每个其他页面导入就可以了。
最后来讲一下PHP的错误处理机制。如果你写了有错误的PHP代码,那么运行的时候系统会自动生成一些错误提示信息并且打印到屏幕上,以提醒用户修复。通常,这些错误信息是分级的。首先,是notice。如果屏幕出现了notice:(...)的提示说明你有需要修复的小问题(你没有完全按照规则进行),不过问题不大,代码还是会继续执行完毕。而warning则更严重一些,如果出现warning,你可能需要思考一下你是否真的知道自己在做什么,并作出修改。但是,程序仍然会运行。如果出现了error,那么PHP是在跟你说:你是个白痴;这种代码无法执行,程序的运行会中止。
在写PHP程序的时候,我们需要这些错误提示来帮助我们改正错误,但是当产品发布的时候,开发人员往往倾向于隐藏错误提示:用户收到这些信息是很让人恼火的,而且,让他人知道你的代码有什么漏洞总归不是一个好主意,因为这可能被某些图谋不轨的攻击者加以利用。