《PHP and MySQL Web Development》 第5版中部分知识总结。

1.介绍

1.什么是PHP

PHP是专门为Web设计的服务端脚本语言。

PHP目前解释为PHP Hypertext Preprocessor.

PHP7包含了Zend引擎的重构。

2.使用PHP

PHP是不可见的,因为PHP解释器会提前运行脚本,并将PHP用脚本的输出代替。PHP在服务端被解释和运行,而JavaScript等其它客户端技术在用户机器上的浏览器中解释执行。所以使用PHP,可以产生纯净的HTML,被任何浏览器可见。

从安全的角度来说,从用户处获取数据并直接在浏览器上显示,是一个非常有风险的行为。需要经过函数htmlspecialchars()再传递给浏览器。

变量显示的两种方式:

//1.使用单引号,用.连接字符串echo htmlspecialchars($tireqty).' tires<br />';
//2.将变量放在双引号中$tireqty = htmlspecialchars($tireqty);  echo "$tireqty tires<br />";

PHP的一个特性是,不需在使用之前声明变量。

PHP有三种特殊的类型:NULL,resource,callable

PHP是一种弱类型语言,它可以在任何时间,根据存储的内容,改变变量的类型。

PHP中变量的变量:

$varname = 'tireqty';
$$varname = 5;
//等同于
$tireqty = 5;

声明和使用常量:

define('TIREPRICE', 100);
echo TIREPRICE;	//特点是没有$符号

PHP中关于变量范围的重要几点:

1.脚本中的全局变量在整个脚本中,除了函数内部,都可见。

2.函数中被声明为global的变量,是指同名的全局变量。

3.在函数内部创建并声明为static的变量从函数外部不可见,但是在函数的每次运行中会保存其数值。

超全局变量包括:$GLOBALS,$_SERVER,$_GET,$_POST,$_COOKIE,$_FILES,$_ENV,$_REQUEST(GET,POST,COOKIE),$_SESSION

赋值的返回值:

$b = 6 + ($a = 5)中,$b的值是11。

引用符:

$a = 5;
1. $b = $a;
//a的值会被复制,存储到另一块内存中。a的值改变,b的值不变。
2. $b = &$a;
//a和b指向同一块内存。a和b的值同时改变。
//但是a被销毁后,b的值仍然存在。(相当于别名,而非指针)

完全相同运算符===,只有值相等,并且类型相同时才会返回true。如果是两个数组,不仅需要有相同的键值对,还得顺序和类型相同。

<>也可以表示不相同。

+在数组中是并集运算。

运行运算符:

//1.Unix
$out = `ls -la`;
//2.Windows
$out = `dir c:`;
echo '<pre>'.$out.'</pre>';

PHP中的变量处理函数:

1.测试设置变量类型
string gettype(mixed var);
bool settype(mixed var, string type);
is_real()	//是否是float类型
is_scalar()	//是否是整数、布尔、string或浮点数类型
is_numeric()	//是否是数字或数字串
is_callable()	//是否是一个有效函数的名字  
2.测试变量状态
bool isset(mixed var[,mixed var[,...]])
void unset(mixed var[,mixed var[,...]])
bool empty(mixed var)	//变量不存在或者为0或者为空
//如果是false,显示为空;如果是true,显示为1。
3.重新解释变量
int intval(mixed var[, int base=10])
float floatval(mixed var)
string strval(mixed var)

变量的变量与for循环结合:

//对name1,name2,name3这样的变量进行迭代
for($i=1;$i<=$numnames;$i++) {$temp = "name$i";echo htmlspecialchars($$temp).'<br />';
}

停止当前脚本运行语句:exit。在错误检查时常用。

3.PHP存储和提取数据

$_SERVER['DOCUMENT_ROOT']打开服务器的Web文档根目录。

文件模式中,为获得最大的可移植性,一般建议加上b。Windows系统区分二进制文件和文本文件,Unix系统不区分。

操作文件:

1.打开文件
$fp = fopen("$document_root/../orders/orders.txt",'ab');
//如果php.ini中,allow_url_open是打开的,那么可以通过FTP和HTTP打开文件
2.写文件
fwrite($fp, $outputstring, strlen($outputstring)); //fputs等同
3.关闭文件
fclose($fp);
4.一次读一行文件
while(!feof($fp)) {}	//使用while循环读文件,直到文件末尾
$order = fgets($fp);	//fgets读到\n或EOF停止
string fgetss(resource fp[, int length[, string allowable_tags]])	//默认剔除PHP和HTML标签
array fgetcsv(resource fp, int length[, string delimiter[, string enclosure[, string escape]]])	//可以设定分解符,length设置为0可以不限制长度
5.一次读整个文件
//自动打开文件,输出内容,关闭文件
int readfile(string filename,[bool use_include_path[, resource context]]);
//将文件读入数组中
$filearray = file(string filename);
//返回文件全部内容
file_get_contents(string filename);
//需要fopen,将指针位置处的内容输出,成功返回true
fpassthru($fp);
6.
fgetc($fp);	//一次读一个字符,返回EOF
string fread(resource fp, int length); //读任意大小
file_exits(string filename);
filesize(string filename);
unlink(string filename);
rewind($fp);	ftell($fp);	  
int fseek(resource fp, int offest[, int whence]);
//whence默认为SEEK_SET开始位置,还可以为SEEK_CUR,SEEK_END。

处理并发:对文件上锁

bool flock(resource fp, int operation[, int &wouldblock]);
//如果当前进程阻塞,第三个参数将返回true
LOCK_SH	//读锁
LOCK_EX //写锁
LOCK_UN //释放锁   

flock()不支持NFS等网络文件系统,也不支持一些不允许锁操作的文件系统如FAT。在一些操作系统上,它在进程级实施,在使用多线程API时无法正常工作。

@符号抑制该函数的任何错误提示,可以用对用户更友好的方式显示错误信息。

@$fp = fopen("$document_root/../orders/orders.txt",'ab');
if(!$fp) {echo "<p><strong> Your order could not be processed at this time.  .Please try again later.</strong></p></body></html>";  exit;	//如果错误退出脚本
}

使用fwrite时,在Unix系统中,需要给apache用户组可写的权限:

1.查看配置文件apache2.conf,搜索User,可以看到
# These need to be set in /etc/apache2/envvars
User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}
2.然后查看/etc/apache2/envvars,看到:
export APACHE_RUN_USER=www-data
export APACHE_RUN_GROUP=www-data
所以apache用户组的名字为www-data
3.然后创建目录,并授予权限:
sudo mkdir path/to/orders
sudo chgrp apache path/to/orders
sudo chmod 775 path/to/orders
4.PHP数组

PHP中数组可以动态调整大小。

1.创建数组:

$products = array( 'Tires', 'Oil', 'Spark Plugs' );
$products = ['Tires', 'Oil', 'Spark Plugs'];
$odds = range(1, 10, 2);
$letters = range('a', 'z');

2.使用自定义的索引:

$prices = array('Tires'=>100, 'Oil'=>10, 'Spark Plugs'=>4); //隐式创建
$prices['Tires'] = 100;

3.遍历数组:

//默认索引
foreach($products as $current) {echo $current.' ';
}  
//自定义索引
foreach($prices as $key => $value) {echo $key.'-'.$value.'<br />';
}
while($element = each($prices)) { //或者list($product, $price) = each($prices)echo $element['key'].'-'.$element['value'];echo '<br />';
}
//each返回4个键值对,'key'和0等价,'value'和1等价。
//注意,数组会记录当前元素的位置,如果使用each()方法两次遍历数组,需要在第二次调用前reset($prices)。
//二维数组的遍历,自定义索引可以采用while循环
for ($row = 0; $row < 3; $row++) {  while (list( $key, $value ) = each( $products[$row])){ echo '|'.$value;  }  echo '|<br />';
}

4.数组排序:

sort();	//对默认索引的数组排序
asort(); //对自定义索引的值排序
ksort(); //对自定义索引的键排序
rsort(); arsort(); ksort(); //倒序排序
bool array_multisort(array &a[, mixed order = SORT_ASC[, mixed sorttype = SORT_REGULAR[,...]]]) //对多维数组第一维排序,SORT_DESC用于降序

用户自定义排序:

function compare($x, $y) {  if ($x[1] == $y[1]) {    return 0;  } else if ($x[1] < $y[1]) {    return -1;  } else {    return 1;  }
}
usort($products, 'compare');

uasort()uksort()分别对非数值型索引索引的键、值进行排序。

随机排序:shuffle()

逆序:

//直接逆向
array_reverse($numbers);
//一个一个数字放进去
$numbers = array();
for($i=10; $i>0; $i--) {  array_push($numbers, $i);
}
//array_pop()

从文件中读数组可能需要的处理:

array explode(string separator, string string[, int limit]);

每个数组内部都有一个指针,指向数组的当前元素:

1.next()和each()都会将指针指向下一个元素
each()先返回当前元素,再移动指针
next()先移动指针,再返回当前元素
2.
reset()返回指向数组第一个元素的指针
end()返回指向数组最后一个元素的指针
3.使用end()和prev()逆序数组
prev()先逆序移动指针,再返回当前元素

对数组中的每个元素都应用某个函数:

bool array_walk(array arr, callable func[, mixed userdata]);
function my_print($value){  echo "$value<br />";
}
array_walk($array, 'my_print');
//如果需要加上第三个参数,则不能忽略key
function my_multiply(&$value, $key, $factor){  //需要改变数组值,传递引用$value *= $factor;
}
array_walk($array, 'my_multiply', 3);

计算数组中元素的个数:

count()等同于sizeof()
array_count_values($array); 返回数字中数值的频率表

将数组转换成纯量标量:

extract(array var_array[, int extract_type] [, string prefix]);
extract_type:
EXTR_OVERWRITE //默认
EXTR_SKIP
EXTR_IF_EXISTS
5.字符串操作和正则表达式

1.修剪函数:

string trim(string);	//除去字符串前后的空格以及\n\r\t\x0B\0
ltrim();rtrim();
chop(); //类似于trim();

2.标准化函数:

如果在浏览器中显示,则需要htmlspecialchars将HTML中具有特殊含义的字符转换成HTML实体。

string htmlspecialchars(string string[, int flags = ENT_COMPAT | ENT_HTML401 [, string encoding = 'UTF-8' [, bool double_encode = true ]]])

如果是发送邮件,重要的是不能够包含\r\n,需要使用str_replace("\r\n","",$变量名)

将换行转换为<br />的函数:nl2br()。使用顺序nl2br(htmlspecialchars($string))

使用print进行标准化输出:

string sprintf(string format [, mixed args...]);
int printf(string format [, mixed args...]);
// %[+]['padding_character][-][width][.precision]type
printf ("Total amount of order is %2\$.2f (with shipping %1\$.2f) ",$total_shipping, $total);
//2\$指明使用第2个参数,这种方法也可用于重复参数。

调整大小写:

strtoupper();strtolower();
ucfirst();ucwords();

3.组合分割字符串

array explode(string separator, string input [, int limit]);
string implode(string separator, array input);
//一次返回一个字符串
string strtok(string input, string separator);
$token = strtok($feedback," ");
while($token!="") {echo $token."<br />";$token = strtok(" ");
}
//返回子串
string substr(string string, int start[, int length]);

4.字符串比较

//等于返回0,大于返回整数,小于返回负数
int strcmp(string str1, string str2);
//大小写不敏感
strcasecmp();
//用更自然的方式比较,认为'12'比'2'大
strnatcasecmp();
//计算字符串长度
strlen();

5.使用字符串函数匹配和替换子串

1.在字符串中找字符串
//返回第一次needle出现的haystack或者false,strchr()等同
string strstr(string haystack, string needle[, bool before_needle=false]);
//大小写不敏感
stristr();
2.找到子串的位置
//返回needle第一次在haystack中出现的位置或者false
int strpos(string haystack, string needle[, int offest=0]);
//返回最后一次出现的位置或者false
strrpos();
3.替换函数
//参数都可以是array
mixed str_replace(mixed needle, mixed new_needle, mixed haystack[, int &count]);
//替换某个特殊的子串
string substr_replace(mixed string, mixed replacement, mixed start[, mixed length]);
//如果length是0,则替换串会直接插入到字符串中,不会覆盖原有字符;如果大于0,则表示希望被替代的字符数量;如果小于0,则表示希望停止替换的位置,位置从后往前计算。

注意,false在像PHP这样的弱类型语言中,很多时候等于0。由于字符串的第一个位置也是0,这就会在判断时带来一些问题。可以使用$result === false进行判断。

6.正则表达式

在PHP中使用正则表达式模式尽量用单引号。如果使用双引号,要匹配$字符,需要使用\\\$。因为字符串是由双引号包裹,所以先由PHP解释器将其解释为\$,再由正则表达式解释器将其解释为$

/shop/		匹配shop
/http:\/\//	匹配http://
#http://#	匹配http://,使用#作为分界符
/shop/i 	匹配shop,不区分大小写
/.at/		.可以匹配除\n外的任意单字符
/[a-zA-Z]/ 	匹配单个的所有字母符号
/[^a-z]/	匹配除a-z的所有单个符号
/[[:alpha:]1-5]/	匹配单个字母符号或者1-5之间的单个数字
/[[:alnum:]]+/		匹配至少一个数字字母字符
*零次或多次,+一次或多次,?零次或一次
/(very ){1,3}/		匹配"very ","very very ","very very very "
/^[a-z]$/	匹配只有a-z单个字符的字符串
/com|edu|net/
\d			匹配一个十进制数字
\D			匹配除十进制数字以外的一个字符
\w			匹配任何字母数字或下划线
/^([a-z]+) \1 black sheep/	匹配baa baa black sheep
/^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-\.]+$/
匹配邮箱

7.用正则表达式处理子串

1.找到
int preg_match(string pattern, string subject[, array matches[, int flags=0[, int offest=0]]]);
//如果找到一个匹配返回1,没有找到返回0,出现错误返回false
//所以需要用===来检验返回值到底是false还是0
2.替换
mixed preg_replace(string pattern, string replacement, string subject[, int limit=-1[, int &count]]);
3.分割
array preg_split(string pattern, string subject[, int limit=-1[, int flags=0]]);
6.重用代码和编写函数

1.使用include()require()

PHP不关注所需文件的扩展名。

如果文件以.inc或其它非标准扩展名结尾存储在Web文档树中,并且用户直接从浏览器中加载它们,用户可以直接以文本的形式看到代码。所以应该使用标准扩展名或者不放在Web文档树中。

2.编写函数

在PHP中,函数名不区分大小写。函数定义前加上function

可以在函数中退出php:

<?php  function my_function() {?>My function was called<?php  }
?>

PHP不支持函数覆盖,如果不考虑在类内的情况。

创建有可变数量参数的函数:

function var_args() {  echo 'Number of parameters:';  echo func_num_args();  //返回参数数量echo '<br />';  $args = func_get_args();  //返回参数数组foreach ($args as $arg) {    echo $arg.'<br />';   }
}
//func_get_arg()一次获取一个参数

3.传递值和传递引用

function increment($value, $amount=1) {$value = $value + $amount;
}

这个函数是没有意义的。$value的值不会发生改变。方法之一是在函数内部,将$value声明为global,但这就要求全局中被增加的变量名只能为value

方法之二是使用引用:&$value

4.使用return ;在函数结束前停止函数运行,或者返回值。

PHP允许使用递归函数。递归函数需要设置递归终止条件。

5.使用匿名函数

1.
function my_print($value) {echo "$value <br />";
}
array_walk($array, 'my_print');
2.//使用匿名函数
array_walk($array, function($value){echo "$value <br />";});
3.//使用变量存储匿名函数
$printer = function($value){echo "$value <br />";};
$printer('Hello');	//直接调用
array_walk($printer);

匿名函数中使用全局变量:

$markup = 0.20;
$apply = function(&$val) use ($markup) {$val = $val * (1+$markup);
};
7.PHP面向对象

一个对象可以是实体的,也可以是概念上的。

面向对象的核心优点是封装,也成为数据隐藏。只有通过对象的接口,才能访问对象内部的数据。

不需改变接口,就可以修改对象实现方式的细节;改变接口,会在项目中引起连锁反应,但是封装可以使得修改不会级联到项目其它部分。

对象可以被划分为

面向对象的语言必须支持多态,即不同的类对于同一方法有不同的行为。多态可以看作是行为的特性。在PHP中,只有类的成员函数可以是多态的。

继承便于复用代码,节省代码量。

访问修饰符:public(默认)、private、protected。protected方法能够被继承,但是不能被显式调用。

使用访问函数:__get()和__set()避免将属性设置为public。使得只有一段代码可以访问该属性。

PHP支持类中函数重载。函数重载,即多个函数名称相同,参数列表不同。

继承中的函数重写:子类与基类相同的属性,默认值被修改;子类与基类相同的函数,功能有所不同。

PHP中不支持多重继承,使用接口特征来实现多重继承的功能。一个类可以实现多个接口,一个类也可以组合多个特征。

特征与接口的区别:特征包含实现。现有类的方法会重写trait中的方法,trait中的方法会重写继承的方法。

使用构造器:

class classname {  function __construct($param)  {    echo "Constructor called with parameter ".$param."<br />";  }
}
$a = new classname("First");
//析构器是__destruct()

使用访问函数:

class classname {  private $attribute;function __get($name)  {    return $this->$name;  }  function __set ($name, $value)  {    $this->$name = $value;  }
}
$a = new classname();
$a->attribute = 5;	//此时__set()方法会被隐式调用

继承语法:

class B extends A {}

函数重写中,调用基类方法:parent::operation();,但方法中使用的是子类的属性。使用final关键字修饰方法,则方法无法被重写;修饰类,则方法无法被继承。

实现接口的语法:

class B implements A {}

声明和使用特征:

trait logger 
{public function logmessage($message, $level='DEBUG'){//内容省略}
}
class fileStorage 
{use logger;	//使用traitfunction store($data) {$this->logmessage($data);}
}

如果使用的两个特征有同名函数:

use fileLogger, sysLogger  
{    fileLogger::logmessage insteadof sysLogger;    	sysLogger::logmessage as private logsysmessage;  
}

通过面向对象方式编写的Page类:

<?phpclass Page{  //将所有需要修改的内容设置成属性// class Page's attributes  public $content;  public $title = "TLA Consulting Pty Ltd";  public $keywords = "TLA Consulting, Three Letter Abbreviation, some of my best friends are search engines"; public $buttons = array("Home"   => "home.php", "Contact"  => "contact.php", "Services" => "services.php", "Site Map" => "map.php");  // class Page's operations  //php弱类型的好处就在于所有的属性都可以用一个set方法解决public function __set($name, $value)  {    $this->$name = $value;  }//Display函数一般不用重写,里面的小函数容易被重写public function Display()  {    echo "<html>\n<head>\n";    $this -> DisplayTitle();    $this -> DisplayKeywords();    $this -> DisplayStyles();    echo "</head>\n<body>\n";    $this -> DisplayHeader();    $this -> DisplayMenu($this->buttons);    echo $this->content;    $this -> DisplayFooter();    echo "</body>\n</html>\n";  }  public function DisplayTitle()  {    echo "<title>".$this->title."</title>";  }  public function DisplayKeywords()  {    echo "<meta name='keywords' content='".$this->keywords."'/>";  }//插入样式,PHP可以结束再重新开始public function DisplayStyles()  {     ?>       <link href="styles.css" type="text/css" rel="stylesheet">    <?php  }  public function DisplayHeader()  {     ?>       <!-- page header -->    <header>    <img src="logo.gif" alt="TLA logo" height="70" width="70" /> <h1>TLA Consulting</h1>    </header>    <?php  }  public function DisplayMenu($buttons)  {    echo "<!-- menu -->    <nav>";//遍历数组while (list($name, $url) = each($buttons)) {$this->DisplayButton($name, $url, !$this->IsURLCurrentPage($url));    }    echo "</nav>\n";  }  public function IsURLCurrentPage($url)  {    //查看是否是当前页面地址,注意是三个等号判断是否为falseif(strpos($_SERVER['PHP_SELF'],$url)===false)    {return false;    }    else    {return true;    }  }  public function DisplayButton($name,$url,$active=true)  {    //如果不是当前页面,则可激活,否则不可激活if ($active) { //href??><div class="menuitem"><a href="<?=$url?>"><img src="s-logo.gif" alt="" height="20" width="20" /><span class="menutext"><?=$name?></span></a></div><?php    } else { ?><div class="menuitem"><img src="side-logo.gif"><span class="menutext"><?=$name?></span>				</div><?php    }    }  public function DisplayFooter()  {    ?>    <!-- page footer -->    <footer><p>&copy; TLA Consulting Pty Ltd.<br />Please see our <a href="legal.php">legal information page</a>.</p>    </footer>    <?php  }
}
?>

PHP中更先进的面向对象特征:

类中的const声明的常量,可以不经过实例化直接使用:Math::pi

类中static方法声明的函数static function squared($input) {}。也可以不经过实例化直接调用。

通过$b instanceof B检查类的类型。

使用function check_init(B $someclass) {}进行类型提示。如果B是A的子类,那么传递类型为A的变量会引发错误;如果B是A的基类,那么可以传递类型为A的变量。

Late Static Bindings:

<?phpclass A {    public static function whichclass() {echo __CLASS__;    }public static function test() {self::whichclass();    }}class B extends A {    public static function whichclass() {echo __CLASS__;    }}A::test();B::test();
?>

调用的结果两次都是A。为使得test方法能够调用B对于whichclass的实现,需要使用后期静态绑定。将self::whichclass()替换成static::whichclass()

通过__call()函数实现函数重载

//获取两个参数,第一个是方法名,第二个是参数数组
public function __call($method, $p)
{  if ($method == "display") {    if (is_object($p[0])) {$this->displayObject($p[0]);    } else if (is_array($p[0])) {$this->displayArray($p[0]);    } else {$this->displayScalar($p[0]);    }  }
}

迭代器。

生成器:生成器看起来像函数,但是实际运行的效果像迭代器。

使用生成器和其它函数的区别是,使用yield而不是return将值传递给调用代码。需要在foreach循环中调用生成器函数。这样会创建一个生成器对象,有效存储生成器内部函数的状态。外部foreach循环每迭代一次,内部生成器对象就会运行直到达到yield的位置,也就是完成一次内部的迭代。

需要重点理解的是,生成器可以保持状态。当外部foreach循环开始新一轮迭代的时候,内部的生成器就会从上次暂停的位置开始接着运行,直到到达下一个yield声明。通过这种方式,就可以实现在代码主线和生成器函数之间来回传递执行

另外,在生成器中,任何时候,只有一个值被创建并存储在内存当中。

<?phpfunction fizzbuzz($start, $end){  $current = $start;  while ($current <= $end) {    if ($current%3 == 0 && $current%5 == 0) {yield "fizzbuzz";    } else if ($current%3 == 0) {yield "fizz";    } else if ($current%5 == 0) {yield "buzz";    } else {yield $current;    }    $current++;  }}foreach(fizzbuzz(1,20) as $number) {  echo $number.'<br />';}
?>

将类转换为字符串:使用__toString()方法

class Printable
{  public $testone;  public $testtwo;  public function __toString()  {    return(var_export($this, TRUE));  }
}
//var_export()函数可以打印出类中的所有属性

使用reflection API查看现有类和对象的结构和内容信息。

<?phprequire_once("page.php");$class = new ReflectionClass("Page");echo "<pre>".$class."</pre>";
?>
8.PHP错误处理

在PHP中,异常必须手动抛出。

try
{if (!($fp = @fopen("$document_root/../orders/orders.txt", 'ab'))) {throw new fileOpenException();}if (!flock($fp, LOCK_EX)) {throw new fileLockException();}if (!fwrite($fp, $outputstring, strlen($outputstring))) {throw new fileWriteException();}flock($fp, LOCK_UN);fclose($fp);echo "<p>Order written.</p>";
}
catch (fileOpenException $foe)
{echo "<p><strong>Orders file could not be opened.<br/>Please contact our webmaster for help.</strong></p>";
}
catch (Exception $e)
{echo "<p><strong>Your order could not be processed at this time.<br/>Please try again later.</strong></p>";
}

2.MySQL

1.设计数据库

需要避免三种异常:修改、插入、删除异常

使用原子列:多对多关系中;避免设计出现大量空白的属性;

数据库设计主要有两种表组成:

1.描述现实实体的简单表:

它们之间可能通过外键,产生一对一或一对多关系。

2.描述实体间多对多关系的联系表。

Web数据库的结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cyBGvOXJ-1658559421336)(D:\个人\整理\8.PNG)]

1.用户的浏览器发出HTTP请求,获取特定的Web页面results.php

2.Web服务器收到对results.php的请求,找到文件,将其发送至PHP引擎以求进一步处理。

3.PHP引擎解析代码。代码中有需要连接数据库执行查询的命令,所以PHP打开了一个与MySQL数据库的连接,并发送合适的查询。

4.MySQL数据库收到数据库查询,执行并返回结果给PHP引擎。

5.PHP引擎执行完脚本,并将查询结果在HTML中格式化显示,然后将HTML返回给Web服务器。

6.Web服务器将HTML返回给浏览器。

随着应用程序复杂性的增加,通常需要将PHP应用程序分为:与MySQL对接的数据库层,包含应用程序核心的业务逻辑层,管理HTML输出的表示层

2.创建Web数据库
1.创建数据库和用户
2.设置用户权限

1.设置用户权限应该遵守最小特权原则。

2.MySQL中用户权限可以分为6级:

Global、Database、Table、Column、Stored Routine、Proxy User

3.MySQL中有三种基本权限类型:

适用于授予普通用户的权限、适用于授予管理员的权限、一些特殊权限。

不应当使除了管理员以外的用户访问mysql数据库。

4.创建用户并授予权限示例:

mysql->grant select,insert,update,delete,index,alter,create,drop->on books.*->to 'bookorama' identified by 'bookorama123';

5.MySQL中4种基本的列类型:

数字、日期和时间、字符串、空间

3.创建数据表
4.MySQL中的标识符:

数据库、表、列、索引、别名、视图、存储过程

MySQL中的数据库映射到底层文件目录,表映射到一个或多个文件。这种文件系统映射会影响名称的大小写:

如果操作系统中目录和文件名是大小写敏感的(如大部分Unix),则数据库与表名大小写敏感;如果不是(如Windows),则数据库与表名大小写不敏感。

列名和偏置名不是大小写敏感的。

为了最大的可移植性,应该把所有的标识符都设置成小写。

3.使用MySQL数据库

SQL代表 Structured Query Language。

SQL涵盖了DDL和DML。DDL是用于定于数据库的数据定义语言,DML是用于查询数据库的数据操作语言。

MySQL中常用的查询类型:等连接和左连接。

1.等连接:

SELECT Orders.OrderID, Orders.Amount, Orders.Date
FROM Customers, Orders
WHERE Customers.Name = 'Julie Smith' 
and Customers.CustomerID = Orders.CustomerID;

Customers.CustomerID = Orders.CustomerID语句提供了连接条件。

2.左连接:

左连接用于查找不匹配的行。

比如说查找没有下过订单的用户:

SELECT Customers.CustomerID, Customers.Name
FROM Customers 
LEFT JOIN Orders
USING (CustomerID)
WHERE Orders.OrderID IS NULL;

Using子句指明了连接条件,只有两个表列名相同时才可以使用该子句。否则应该使用on Customers.CustomerID = Orders.CustomerID

使用别名,在对同一个表进行操作时非常有用:

SELECT C1.Name, C2.Name, C1.City
FROM Customers AS C1, Customers AS C2
WHERE C1.City = C2.City
AND C1.Name != C2.Name;
4.用PHP连接数据库

连接数据库的步骤:

1.检查并过滤来自用户的数据

2.建立至相应数据库的连接

3.查询数据库

4.获取查询结果

5.将查询结果展示给用户

//除去两端的空格,也可以将全是空格的结果处理为空
$searchterm = trim($_POST($searchterm));
//判断是否输入为空
if(!$searchtype || !$searchterm) {exit;
}
//使用白名单:检验输入type是否为有效值
switch($searchtype) {case 'Title':case 'Author':break;default:exit;
}
//建立连接
$db = new mysqli('localhost','username','password','database');
//也可以用面向过程的方法$db = mysqli_conncet(...);但是这种方法返回的是资源而不是对象,需要将$db作为资源传递给其它的mysqli函数,类似于fopen的使用
//检验数据库是否连接成功
if(mysqli_connect_errno()) {exit;
}
//更改默认数据库
@$db->select_db(dbname);
//使用预处理查询,加速查询并避免SQL注入
//预处理查询的基本概念是,先发送需要运行的查询模板,再分开发送数据
$query = "select ISBN, author, title, price from books where $searchtype=?";
$stmt = $db->prepare($query);
$stmt->bind_param('s', $searchterm);
$stmt->execute();
//获取查询结果
//为获取查询结果的总行数,需要告诉PHP检索并暂存所有的行
$stmt->store_result();
echo "$stmt->num_rows";
//为获取查询结果,需要将列的结果绑定到一系列变量上,每次调用fetch函数,结果集中下一行列的结果将填充到这些变量中
$stmt->bind_result($isbn,$author,$title,$price);
$stmt->execute();
while($stmt->fetch()) {echo "$isbn,$author,$title,$price";
}
//另一种方法
$result = $stmt->get_result();
mysqli_fetch_array(); //以数组形式返回下一行的数据,第二个参数MYSQLI_ASSOC以列名为主键,MYSQLI_NUM以数字为主键,MYSQLI_BOTH返回两个数据集
mysqli_fetch_all();//返回数组的数组
mysqli_fetch_object();
//断开数据库连接(非必要)
$result->free();
$db->close();

判断插入语句是否执行成功:$stmt->affected_rows>0

使用称为PDO的数据访问抽象扩展,可以以同一个接口访问不同的数据库。

5.高级MySQL管理
1.特权系统

存储特权信息的表,位于mysql数据库中,一共有7个:

user,host,db,tables_priv,columns_priv,proxies_priv,procs_priv

mysql使用授权表的方法:

1.连接验证

MySQL使用user表来检查是否你能够连接至数据库。

如果想让系统更安全,应该避免使用:为空的用户名,为通配符的主机名,没有密码的用户。

2.请求验证

在建立连接,并输入请求后,MySQL会按照db、tables_priv、columns_priv的顺序查找相关信息,直到权限满足为止。

如果是存储过程,MySQL使用procs_priv表。

如果是代理操作,使用proixes_priv表。

刷新权限的方法:在mysql控制台中输入flush privileges;或者在操作系统中输入mysqladmin flush-privilegesmysqladmin reload

2.安全使用MySQL数据库

1.操作系统角度:

不要在类Unix系统上以root用户运行MySQL服务器。这样,MySQL用户就有了在操作系统上任意位置读写文件的特权。

专门设置一个MySQL用户运行mysqld。

设置只能够被MySQL用户访问的文件夹。

将MySQL服务器置于内网中或防火墙之后。关闭3306端口。

2.密码

确保所有用户都有密码。

PHP连接数据库需要密码,可以将用户名和密码放在一个名为dbconnect.php的文件内完成,在需要的时候引用该文件。该文件应该放在Web文档树之外,只由适合的用户访问。

如果放在Web文档树.inc或其它后缀名的文件中,确保Web服务器知道将其解析成PHP,所以这些详细信息不会被浏览器以文本的形式显示。

不要在数据库中以明文的形式存储密码。

3.用户权限

最小权限原则。

不要将mysql数据库的访问权限授予非管理员用户。

不要将PROCESS, FILE, SUPER, SHUTDOWN, RELOAD权限授予非管理员用户,除非必要,否则也不要授予管理员用户。

GRANT和ALTER也应该小心授予。

避免Web服务器上的非管理员用户访问mysqladmin进程。

4.Web相关

对于特定的Web应用,应该设置一个特殊的用户用于连接。

总是检查来自用户的所有数据及数据大小。即使是选择题,用户也可以通过篡改URL来攻击脚本。

记住除非使用SSL,否则用户输入的数据将在浏览器和服务器之间以明文传输。

6.Web应用安全风险
1.对敏感数据的访问

为减小信息暴露的风险,需要限制信息被访问的方法可访问信息的人员

需要安全的设计、配置、编码、测试以减小成功攻击的风险,减小信息意外曝光的可能性;

移除服务器上不必要的服务,以减少潜在脆弱点;

使用户验证他们的真实身份:密码和数字签名;

使用SSL实现浏览器和服务器之间的安全传输。

2.数据的修改

对文件的修改包含数据修改和可执行文件的修改。修改后的可执行文件可能包含黑客的后门。

避免修改:使用操作系统提供的文件权限工具,避免系统遭到未经授权的访问。

侦察修改:如果系统安全被破坏了,如何知道重要文件是否被修改了?就算知道某些程序和文件被篡改了 ,想要恢复,又如何确定哪一版本的数据是干净的?

3.数据的丢失和破坏

意外灾害可能会导致数据的丢失和破坏。

进行备份以降低风险。

4.拒绝服务

拒绝服务攻击难以防卫,是因为攻击方式不计其数。

拒绝服务攻击者可以通过在目标机器上安装一个大量占用处理器时间的软件,反向垃圾邮件,使用自动化工具。

为抵御拒绝服务攻击,1.可以关闭DDoS工具的常用端口。

2.可以在负载平衡器上阻塞已知有问题的流量。

3.可以开发一种机制,使得部分或大多数站点保持静态。

4.在应用程序中实现功能标记,使得打开和关闭功能成为可能。在高负载时,可以关闭不太关键或更昂贵的功能应对额外的流量。

5.恶意代码注入
6.受攻击的主机

攻击者获得了系统的管理员权限。

处理的办法是使用纵深防御策略,它意味着考虑系统不同方面可能出问题的所有可能性,并在每个方面添加保护层。

7.抵赖

身份验证:由可信第三方颁发的数字身份验证

消息防篡改

所需面对的人:攻击者、使用受感染机器的无知用户、心怀怨恨的公司员工、硬件偷窃者、我们自己所写的代码

7.建立安全的Web应用
1.处理安全问题的策略

1.合理认识安全

安全应该是应用核心设计的一部分,在任何时期都不能被忽视。通过在一开始就考虑到我们系统被滥用或攻击的方式,并合理的设计代码。

2.平衡安全性和可用性

3.监督安全

2.增加代码的安全性

1.过滤用户的输入

二次检查用户输入以得到预期值:服务端使用switch语句处理用户发过来的选择结果;

对最基础的值进行过滤:通过类型转换,确保是期望的类型;

防止SQL注入攻击:尽可能使用参数化查询,使数据与SQL语句分离。mysqli_querymysqli::query方法一次只能运行一条查询。

2.转义输出

使用htmlspecialchars()htmlentities()函数转义输出。

3.代码组织

配置服务器,使其只接受对.php和.html类型文件的请求;

将重要文件放到公共文档树之外。

4.文件系统的考虑

如果通过用户输入的文件名,在文档目录下查找文件,应对用户输入进行过滤,避免用户通过..\..\..\php\php.ini的方式,进入其它目录,查看到一些重要文件。

5.代码稳定性和Bug

可能使用原型,来完成我们产品的全面设计。

收集产品的测试资源。

使用自动化测试。

在部署应用之后监控应用的运行情况。

3.让你的Web服务器和PHP更加安全

确保Web服务器和PHP被合理的配置。

总是使用最新的软件;

在下载新软件时,制作一个自动化的脚本;

绝不能第一次直接在生产服务器上进行安装:应先进行测试,在确保新版本的软件适用于Web应用程序之后,再将其部署到生产服务器。应确保该过程是自动化的,所以可以有一系列确切的步骤将其复制到生产环境;

浏览php.ini。将php.ini保存在版本控制系统中。

Web服务器配置:对于Apache服务器来说,所有的配置选项都在httpd.conf文件中。需要确保httpd在运行时,不具备超级用户的管理权限;确保Apache安装目录的文件权限是合理的;通过在http.conf中包含适当的指令来隐藏不希望被看到的文件(最好的方法还是将其移出文档树)。

在共享的PHP/MySQL服务上运行Web应用程序:查看支持的服务列表,了解私人空间的配置情况;寻找提供整个目录树而不只是一个根文档的托管服务;查看php.ini的配置信息;查看他们所使用的软件版本;

4.数据库服务器安全

用户和权限系统:了解所使用数据库的验证和权限系统;保证所有账户都有密码;默认用户的权限不要太多;确保只有超级用户账号可以访问权限表和管理数据库。

将数据发送至服务器:过滤数据;验证输入数据在所在域内。

控制到服务器的连接:限制用户允许连接的位置;使用加密连接。

运行服务器:不要以超级用户权限运行MySQL;创建具有绝对最小权限集的用户;

5.保护应用程序所在网络

防火墙:过滤掉不需要的流量,阻止访问不希望浏览的部分网络。大量端口完全用于内部流量,很少用于与外界交互;如果禁止在这些端口上进入或离开内部网络,可以降低我们的设备受到危害的风险。

使用DMZ区:将Web服务器与内部网络和外部网络隔离开;保护服务器不受来自内部和外部的攻击,使用更多层的防火墙也可以进一步保护内部网络。

准备对DoS和DDoS攻击的应对。

6.电脑和操作系统安全

使用最新版本的操作系统;

只运行必要的软件;

在物理上保证操作系统的安全。

7.灾难风险规划

灾难风险规划常常被忽视。它是一系列文档和程序的集合,用于解决下列情况发生时所出现的问题:

灾害中,整个或部分数据中心被摧毁;

开发团队死亡或者严重受伤;

公司总部烧毁;

一个网络攻击者或者不满的员工,摧毁了服务器上的所有数据。

通过为这些事件做准备,做出清晰的行动计划,预演其中一些更关键的部分,预先进行少量的金融投资,可以在真正的问题出现时,将企业从潜在的灾难性损失中恢复过来。

一些可能要做的事情:

1.确认所有的数据每天都得到备份,并在其它地区进行存储;

2.有异地的文档,对如何重建服务器环境和Web应用进行了描述。至少将重建过程排演一遍;

3.在多地备份Web应用的所有代码。可能将其存储在Github等外部代码库中。

4.对于较大的团队,禁止团队的所有成员乘坐同一辆交通工具。

5.运行自动化工具,确保服务器操作正常。拥有匿名呼叫名单,以便找人解决营业时间之外的问题。

6.与硬件供应商提前协商,以便在数据中心被销毁的情况下立即使用新硬件,或者保留备件。

8.用PHP进行验证
1.识别访问者

1.Web浏览器可以告诉服务器自己的类型,版本和所处操作系统

2.使用JavaScript可以获取访问者屏幕的分辨率和颜色,以及浏览器的大小

3.根据IP地址识别访问者的地理位置

如果假设只有一个人从特定计算机上的特定账户访问您的网站,并且一个人只使用一台计算机,你就可以在用户计算机上存储Cookie来识别用户。

但是现实中,不是总能满足这种情况。所以需要用户证明他自己的身份,也就是进行验证。验证通常可以用来进行访问控制,或者提供个性化的设置。

2.实施访问控制

1.存储密码

在数据库中存储密码对应的散列值,在验证时,比较用户输入密码的散列值是否与数据库中的散列值相等。这样就可以避免将实际的密码存入数据库中。

2.增加密码的安全性

一般使用的hash函数有crypt(),md5(),sha1()和sha256()。

PHP提供了一个函数password_hash,它使用足够强度的单向散列算法创建密码的散列。优点是可以通过只在一处改变配置而不是多处改变代码,来修改加密算法;另外是参数对应的PASSWORD_DEFAULT,会被PHP进行修改,以保证默认算法的安全性。

3.保护多个页面

可能多个页面都需要用户的信息,每访问一个新的页面,用户信息就需要在客户端和服务器之间重新传输,不安全也不方便。

使用HTTP基本验证机制和会话来解决这个问题。

HTTP基本身份验证:在浏览器窗口打开期间,浏览器自动存储用户的详细信息,并在每次访问时发送给服务器。

如果将基本身份验证与SSL和数字证书结合使用,Web事务的所有部分都将具备强大的安全性。

可以使用PHP进行基本验证;也可以使用Apache的mod_auth_basic模块进行基本验证,通过.htaccess文件对文件进行保护。

9.PHP与文件系统和服务器交互

1.上传文件

判断是否有上传错误:$_FILES['the_file']['error']大于0,需要输出上传错误;

判断是否为特定的文件类型;

判断是否为通过POST上传的文件:is_uploaded_file($_FILES['the_file']['tmp_name'])返回True,否则可能是上传文件攻击;

判断文件是否移动成功。

会话上传过程可以实时反馈文件的上传进度,可以使用脚本不断读取会话的数据。

提高上传文件安全性的方法:

禁止未验证用户上传文件;将用户上传文件名修改为更安全的文件名,避免用户操纵;使用basename()函数避免用户目录冲浪;注意用户上传的文件名,避免写覆盖。

10.使用网络和协议函数

1.收发邮件:

2.从网页中获取信息:

$contents = file_get_contents($url),url参数最好使用urlencode函数进行加密。需要注意数据源的结构是否发生了改变。

3.使用网络查找函数:

gethostbyname($host),通过主机名获取IP地址;gethostbyaddr($IP),通过IP地址获取主机名。

连续使用不一定能得到同一个结果,因为可能网站使用了虚拟主机服务,一个IP地址对应多个域名。

getmxrr($emailhost,$mxhostsarr)以数组形式返回邮件交换记录集合。

11.在PHP中使用会话控制

HTTP是一个无状态的协议。即,HTTP没有内置的维持两个事务之间状态的方式。

会话控制使得在一个网站上通过一个会话跟踪一个用户成为可能。

PHP中的session由唯一session ID标识。该ID由PHP生成,并在会话生命期中存储在客户端。它可能以cookie的形式存储在用户电脑上,也可能通过URL传输。session ID可以用于注册会话变量,变量的内容存储在服务器的文件上,客户端只能看到session ID。通过session ID,便可以访问服务器上文件的内容。

cookie是在多个事务间保持状态的另一种解决方式,在该方式中,有着干净的URL。cookie是脚本存储在客户端的一小部分信息。当浏览器连接至一个URL时,它首先会在本地搜索cookie,如果能够找到相关的cookie,则将cookie中存储的信息发送给服务器。

在PHP会话控制中,不需要手动设置cookie。会话函数将自动创建所有需要的cookie。

1.实施一个简单的Session

1.开始一个session

session_start()首先判断现在是否有一个session,如果没有会创建一个。如果有,就会加载已注册的session变量。在所有使用session的脚本前调用session_start()是很有必要的。

另外一种方法是设置PHP,在有人连接至网站时,自动开启一个session。在php.ini中使用session.auto_start选项,但是这样没法将对象作为session变量,因为在session中使用对象之前必须加载对象的定义。

2.注册一个session变量

$_SESSION['myvar']=5,就可以注册一个变量,直到session结束前都可以使用。

session的过期时间在session.gc_maxlifetime中设置。

3.使用session变量

如果使用对象作为session变量,需要在调用session_start()函数加载变量之前,包含类的定义。

使用前需要注意session变量是否被赋值。

4.销毁变量和session

注销变量:unset($_SESSION['myvar'])

注销会话中的所有元素:$_SESSION = array();

在完成session后,应该先注销所有变量,再调用session_destroy()

12.集成JavaScript和PHP

jQuery是当今使用的极受欢迎的JavaScript框架。该框架为JavaScript编程提供了统一的API,而不用考虑浏览器的差异。

jQuery开发的两个核心概念:选择器和事件。

选择器:

//#作为id选择器
var last_name = $('#last_name');
var nameElements = $('#first_name #last_name');
//.作为name选择器
var nameElements = $('.name');
//基于HTML属性的选择器
var nameElements = $('input[type='text']');
var documentBody = $('body');
//使用伪选择器
var firstInput = $('#myForm input:first');//某个表单中第一个input输入
//在内存中创建新的HTML元素
var newParagraph = $('<p>This Is some <strong>Strong Text</strong></p>');

使用选择器。jQuery选择器都是复数形式,意味着只有一个元素的选择器会被看作是一个只有一个元素的集合,而不是单个的元素。

//单个元素使用val获取或者设置值
var myInput = $('#first_name');
myInput.val();
//使用addClass增加新的类
var nameFields = $('.name');
nameFields.addClass('form-control');
//即使集合中没有元素,也仍然是集合,所以需要用长度判断集合是否为空
if(nameFields.length>0)

事件:

jQuery中的事件,一部分是JavaScript中固有的,一部分是jQuery的构造。

在HTML文档的层次结构中,事件从源HTML元素中诞生,穿过父元素,最终穿过整个文档,触发任何正在监听它们的操作。

在jQuery中,首先使用选择器标识相关的HTML元素,然后使用on方法监听并在触发该事件时执行逻辑。其中最简单的版本是,监听ready事件,在完全加载HTML文档及其资源时触发:

$(document).on('ready', function(event) {// 
});

在一组元素中,找到触发事件的元素:借助event的特性

$('button').on('click',function(event) {var button = $(event.target); 
});

使用event.preventDefault()阻止浏览器对事件默认行为的发生。

使用$.ajax()方法:

方法原型:$.ajax(string url, object settings)

//GET方法
$.ajax('/example.php', {'method' : 'GET','success' : function(data, textStatus, jqXHR) {console.log(data);}
});
//POST方法
$.ajax('/example.php', {'method' : 'POST','data' : {'myBoolean' : true,'myString' : 'This is some sample data.'},'success' : function(data, textStatus, jqXHR) {console.log(data);},'error' : function(jqXHR, textStatus, errorThrown) {console.log("An error occurred: " + errorThrown);}
});
//简化的GET方法
$.get('/example.php', {'queryParam' : 'paramValue'
}, function(data, textStatus, jqXHR) {console.log(data);
});
//简化的POST方法
$.post('/example.php', {'postParam' : 'paramValue'
}, function(data, textStatus, jqXHR) {console.log(data);
});
//从服务器上加载JavaScript文档并运行
$.getScript('/path/to/my.js', function(){//可以使用my.js里的任何函数
});
//对特定URI实施GET方法,并将返回值解析为JSON文档
$.getJSON('/example.php', {'jsonParam' : 'paramValue'
}, function(data, textStatus, jqXHR) {console.log(data);
})