系列一:HTML、CSS、PHP编程与系统开发(网安人专属)

⚠️ 本文仅用于学习与研究目的,切勿用于非法用途,违者后果自负。

前言:

想必大家在学习网络安全的过程中,一定会越发觉得代码是必备的知识,但是我们网安要接触到的编程语言非常之多(比如代码审计需要学会JAVA、PHP;安全工具开发要学会Go、Python;免杀代码还需要会C++等等)

我们不可能把每门语言按照开发的要求去学习,这样时间成本太大了,而且容易失去兴趣

因此,我打算整理一下我通过看网课和资料积累下来的笔记,把编程语言中与安全相关的、必备的知识拎出来,变成文章,一来巩固自身,二来分享给大家

希望大家会喜欢这篇文章,也欢迎大家来指出笔记中的问题~

注:本文为系列一,后续其他编程语言的笔记我会找个时间整理后上传,敬请期待~

WEB前端页面基本元素

一、HTML页面的构成要素

1、HTML:超文本标记语言,由一系列事先约定好的标记(标签)来描述一个页面的构成。HTML主要由三个部分组成:标记(或者叫标签)、属性、内容

 HTML页面里面的元素有(基本在word当中能实现的操作,比如设置颜色、大小……都能在html上得到体现):

 (1)文本:设置字体、颜色、大小

 (2)图片:设置边框、大小、位置

 (3)超链接:图片、文字等均可以添加超链接

 (4)表格:展示行列构成的结构化数据;可以为表格设置大小、背景等;表格里面可以放所有元素

 (5)表单:文本框、下拉框、单选框、复选框、按钮、文本域(也就是可以输入一大堆文本的地方)

 (6)多媒体:音频、视频、H5游戏等

3、CSS:层叠样式表,对页面元素进行布局和美化

4、JavaScript:在浏览器中运行的解释型编程语言,用于进行页面的交互(前后端的交互、用户之间的交互)

二、HTML的基本元素的定义和使用

1、文本

快捷键补充:

(1)注释:control + /

(2)自动生成一个html框架:! + 回车

<font color = "red">欢迎来到本网页!</font> <br/>
<font color = "blue" size = "5">欢迎来到本网页!</font>

但是,在HTML页面之中,文本内容不再建议使用font标签

通常来说,文本、图片等元素都会被放置在容器(表格、DIV当中。也就是说容器当中可以放下所有元素,包括容器自己。

<div>欢迎来到本网页</div>
<div style="color : orphans ; font-size : 30px">欢迎来到本网页</div>
<div style="font-family : 幼圆 ; font-size : 20px">欢迎来到本网页</div>
<!--div包裹的能自动换行,像之前展示的font标记就需要手动加上<br/>来进行换行-->
<!--补充<p/>表示换段,相当于两个<br/>-->

这种被style属性指定的或者包裹的,就是CSS样式

2、图片
 <img src = "image/lbxx.jpg" height="500" />
 <!--上面使用的是相对路径,也就是基于你目前编辑的文件的位置来确定的-->
3、超链接
  <a href = "http://www.baidu.com">点这里进入百度</a><br/>
  <a href = "http://www.baidu.com" target = "_blank">点这里进入百度</a><br/>
  <!-- 没有_blank就是直接在当前页面跳转到目的网站 -->
  <!-- 有_blank就是会打开一个新的页面去访问目的网站 -->
4、表格
<!-- 表格由行和列组成,主要使用三个标签 <table>定义表格整体属性 <tr>定义一行 <td>指定一列-->
      
<!-- 一行一列 -->
    <table>
        <tr>
            <td>
            </td>
        </tr>
    </table>
<!-- 一行两列 -->
    <table>
        <tr>
            <td>
            </td>
            <td>
            </td>
        </tr>
    </table>
<!-- 两行两列 -->
    <table>
        <tr>
            <td>
            </td>
            <td>
            </td>
        </tr>
        <tr>
            <td>
            </td>
            <td>
            </td>
        </tr>
    </table>

举例子:

    <table width = "500" height = "200" border="1">
        <tr>
            <td width="40%">这是第一行第一列</td>
            <td>这是第一行第二列</td>
            <td>这是第一行第三列</td>
        </tr>
        <tr>
            <td>这是第二行第一列</td>
            <td>这是第二行第二列</td>
            <td>这是第二行第三列</td>
        </tr>
    </table>

5、表单
  <!-- 文本框 -->
    <input type = "text" />
    <input type = "text" value = "123" />
    <input type = "text" placeholder="请输入你的用户名" />
    <!-- value文本框会有初始默认值 -->
    <!-- placeholder表示提示信息(会显示在文本框内),并且在文本框中输入信息后提示信息自动消失 -->

    <!-- 密码框(输入内容不回显) -->
    <input type = "password" />

    <!-- 按钮 -->
    <input type = "button" value = "这是一个按钮"/>
       <!-- 也可以这样 -->
       <button>点我</button>

    <!-- 下拉框 -->
    <select>
        <option selected>选项1</option>
        <option>选项2</option>
        <option>选项3</option>
    </select>
       <!-- selected属性表示默认选择哪个选项 -->

CSS

一、CSS的使用方式

要为HTML元素设定样式,有以下三种方式来使用CSS;

1、在元素中指定style属性
<td style = "border:solid 2px red; background-color:white; front-size:20px"></td>
2、在页面当中嵌入style样式块(推荐使用)
    <style>
        td{
            background-color: #7fffd4; /*颜色的RGB编码*/
            width: 25%;
            text-align: center;
            font-size: 30px;
        }
    </style>

通常建议将style标签放到head标签当中,可以针对当前页面所有元素生效

3、在页面中引入外部CSS文件
 <link rel = "stylesheet" type = "text/css" herf = "/page/css/bootstrap.css" />

外部CSS文件,可以针对全站的引入这个CSS的多个页面生效;放置于head标签中

二、CSS选择器(都放在head标签里面)

1、标签选择器
        td{
            background-color: #7fffd4;
            width: 25%;
            text-align: center;
            font-size: 30px;
        }
        /*为当前页面所有的td标签设置CSS*/
2、类选择器

通过设置元素的class属性来定位元素,在同一个页面当中,多个元素可以归属为同一个类

/*类选择器,以.开头,对元素中指定 class = "title"的元素设计样式*/
        .title{
            color:white; /* 文字的颜色 */
            font-size : 22px; /* 文字的大小 */
            float : right; /* 设置容器为向右浮动;我们知道被div包起来的内容会独占一行,若想要多个div并排就需要float */
            /*right就是元素往右浮动,依次排列到右侧*/
            /*left就是元素往左浮动,依次排列到左侧*/
            margin-right: 10px; /* 设置容器距离右边10px */
        }
3、ID选择器

HTML页面中的每个元素即任何一个标签,都具有ID这个属性,我们可以通过为元素设置一个唯一的ID识别符,进而利用CSS的ID选择器对其使用样式

        /* ID选择器以*开头,对元素中指定id = "title" 的元素设计样式 */
        #title{
            color: white;
            font-size : 22px;
            float : right;
            margin-right: 10px;
        }
4、属性选择器
        /* 为DIV元素下拥有type = "button"属性的元素设计样式 */
        div [type = "button"]{
            color: white;
            font-size : 22px;
        }
5、组合选择器

可以将标签选择器、ID选择器、类选择器和属性选择器等组合成不同的选择器类型

也可以通过指定父子关系来组成选择器(父子关系也就是谁嵌套谁的这么一个关系)

        /* 组合使用ID和标签选择器,并实现层次关系 */
        #button td {
            font-size : 32px;
            font-family: 微软雅黑;
            text-align: center;
        }
6、伪类选择器
        #button td:hovor{
            background-color: red;  /* 当鼠标划过的时候,变换背景颜色 */
        }

DIV盒模型

盒模型(也可称之为盒子模型)是CSS中一个重要的概念,理解了盒模型才能更好的排版。我们先来想象一下有一个盒子,他有:

外边距(margin):与其他盒子之间的距离

边框(border):就是盒子本身范围的具象化;当然,边框也是有厚度的,也可以有不同的色彩

内间距(padding):盒子边框与内容之间的填充距离(注意体会“填充”二字,是会使得整体高度或者宽度发生变化的)

内容(content):盒子里面装的东西,可多可少,可以是任意类型

上面列举的就是DIV的四个属性

每个属性都有上下左右之分(比如上外边距、下外边距、左外边距、右外边距……),若在声明的时候没有指明方向,那就是指四个方向都是这么配置的

在现代页面布局中,通常使用DIV+CSS进行,而table主要用于展示二维表格数据,不再用于布局

注意:

DIV容器是可以嵌套的,可以把容器放到另一个容器里面,从而完成更加复杂/整体的操作

字典与F12

(1)字典

如果遇到不熟悉的语法/用法,或者想要实现想法但是不知道语法,就可以去查询字典

搜索方法:

可以直接搜索集成性的字典(比如菜鸟教程)

也可以查询你要查询的函数或者问题

(2)关于F12

F12可以显示页面源代码,也可以用左上角的功能去定位你要看的部分

补充:Command + u直接显示源代码

使用DIV+CSS实现一个计算器

1、分析页面结构

基本设计之前都需要分析一下结构,根据DIV的特点(比如会独占一行,可以嵌套等)来进行初步的结构设计

2、布局实现

你心里要清楚你要得到的目标是什么,先初步实现,具体细节可以逐步调整

目前不含javascript版本

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>枫枫计算器</title>
<style>
    /* 上栏部分样式 */
    #top {
        width: 450px;
        height: 50px;
        margin: auto;
        background-color: gray;
        border-top-right-radius: 10px;
        border-top-left-radius: 10px;
    }
    #top .point{
        width: 20px;
        height: 20px;
        float: left;
        margin-left: 10px;
        border-radius: 10px; /*设置元素的外边框圆角*/
        margin-top: 15px;
    }
    #top .red{
        background-color: red;
    }
    #top .yellow{
        background-color: yellow;
    }
    #top .green{
        background-color: green;
    }
    #top #calc-title{
        font-size: 22px;
        color: white;
        float : right;
        margin-top : 8px;
        margin-right: 10px;
    }
    /*结果部分样式*/
    #result {
        width: 447px;
        height: 50px;
        margin: auto;
        border: solid 2px red;
    }
    /*按钮部分样式*/
    #button {
        width: 450px;
        height: 408px;
        background-color: gray;
        margin:auto;
    }
    #button div{
        width: 110.5px;
        height: 80px;
        float:left;
        background-color: #7fffd4;
        margin :1px;
        line-height:80px;/*垂直居中,原理就是让文本的高度与容器的高度一致,达到居中的目的*/
        text-align: center; /*水平居中*/
        font-size: 26px;
    }
    /* 设置悬停效果,利用伪类实现 */
    #button div:hover{
        background-color: orangered; /*变色*/
        font-size : 30px; /*变大*/
        /* 还可以设计很多类似的 */
    }
</style>
</head>
<body>
    <!-- 设置上栏 -->
    <div id = "top"> <!-- 用id这个属性的前提就是满足唯一性,也就是这个div不会和别的div具有共同配置的地方 -->
        <div class = "point red"></div>
        <!-- point red这样的写法表示这个元素属于point类也属于red类,目的是因为三个圆圈有很多的共性可以一并来设置 -->
        <div class = "point yellow"></div>
        <div class = "point green"></div>
        <div id = "calc-title">枫枫计算器</div>
    </div>
    <!-- 设置结果栏 -->
    <div id = "result">
    </div>
    <!-- 设置按钮区 -->
     <div id = "button">
        <div>AC</div>
        <div>+/-</div>
        <div>%</div>
        <div>÷</div>
        <div>7</div>
        <div>8</div>
        <div>9</div>
        <div>*</div>
        <div>4</div>
        <div>5</div>
        <div>6</div>
        <div>-</div>
        <div>1</div>
        <div>2</div>
        <div>3</div>
        <div>+</div>
        <div>0</div>
        <div>删除</div>
        <div>.</div>
        <div>=</div>
     </div>
</body>
</html>

JavaScript

一、DOM操作与BOM操作

JS直接操作页面的元素的方法集合称为DOM,是一套JS代码接口

BOM,用于通过JS直接操作浏览器,比如前进、后退、历史、导航、刷新等

二、JS定位元素

1、常用的元素定位的方法
        document.getElementById("calc-title").innerHTML; //通过ID来定位,由于id唯一,返回的就是元素本身
        document.getElementsByClassName("point"); //通过class来定位,注意s,对应了class并不唯一
                                                                                         //所以返回的就是包含这些元素的数组
        document.getElementsByName("name"); //通过name来定位,跟class类似,返回的也是包含这个元素的数组
        //等等
        //还可以使用Xpath来定位元素
              //取多个元素和取单个元素的区别就是要定位取数组的哪个位置
              var Calctitle = document.getElementById("calc-title").innerHTML; //单
              var Calctitle = document.getElementsByClassName("point")[0].innerHTML; //多

还可以通过XPath进行定位

XML(eXtensible Markup Language):可扩展标记语言

XML和HTML虽然都是标记语言,但是有区别:

HTML是用于描述页面元素,所有的标记都是事先约定(W3C万维网联盟约定的)好的,浏览器厂商统一支持

而XML用来描述一组数据,标记可以任意定义,他所描述的数据类似于数据库中的行和列

例如:

<bookstore>

<book id="1001">
  <title>Everyday Italian</title>
  <author>Everyday Italian</author>
  <year>2005</year>
  <price>30.00</price>
</book>

<book category="CHILDREN">
  <title>Harry Potter</title>
  <author>J K. Rowling</author>
  <year>2005</year>
  <price>29.99</price>
</book>
 
</bookstore>

就相当于一个表格:

title

author

year

price

1001

Everyday Italian

Everyday Italian

2005

30.00

1002

Harry Potter

J K. Rowling

2005

29.99

当然XML可以实现更加复杂的操作

那么了解完XML,那就简单说说如何利用XPath定位JS元素

例子:

​
//a[@href] 表示当前页面中所有的超链接中有href属性的元素
var result = document.evaluate("//a[@href]", document, null, XPathResult.ANY_TYPE, null);

//div[@id='xxx'] 表示当前页面中所有div元素中id=‘xxx’的元素
nodes=document.evaluate("//div[@id='xxx']", document).iterateNext();

​
2、JS代码执行顺序
……
<script type="text/javascript">
    var Calctitle = document.getElementById("calc-title").innerHTML;
    window.alert(Calctitle);
</script>
</head>
<body>
    <!-- 设置上栏 -->
    <div id = "top"> <!-- 用id这个属性的前提就是满足唯一性,也就是这个div不会和别的div具有共同配置的地方 -->
        <div class = "point red"></div>
        <!-- point red这样的写法表示这个元素属于point类也属于red类,目的是因为三个圆圈有很多的共性可以一并来设置 -->
        <div class = "point yellow"></div>
        <div class = "point green"></div>
        <div id = "calc-title">枫枫计算器</div>
……

上述代码无法正常弹框显示“枫枫计算器”这个内容,因为在渲染JS代码的时候,其中还没有出现‘calc-title’这个ID

解决方法:

(1)调整顺序

将JS代码放到出现了ID之后就可以了,但是这样的方法只适用于处理简单的情况

(2)将JS代码包裹在函数块中,在需要的时候调用

<script type="text/javascript">
    //被函数包裹的包裹的代码,如果没有触发条件或者调用函数的代码,则代码不会执行
    function alertCalc(){
           //在函数体中,变量之前加var表示该变量为函数内的局部变量,否则就是全局变量;因此建议在无论哪个位置代码都加上var
        var Calctitle = document.getElementById("calc-title").innerHTML;
        window.alert(Calctitle);
    }
</script>

触发函数的方法有很多:

onload元素事件(页面加载完成之后自动执行)

<body onload="alertCalc()">
......
</body>

点击调用函数

<div onclick="alertCalc()">AC</div> <!--单击-->
<div ondblclick="alertCalc()">AC</div> <!--双击-->

等等类似的方法

三、利用JS代码完成之前的计算器的交互任务

核心代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>枫枫计算器</title>
<!-- 下面的部分为CSS样式代码 -->
<style>
    /* 上栏部分样式 */
    #top {
        width: 450px;
        height: 50px;
        margin: auto;
        background-color: gray;
        border-top-right-radius: 10px;
        border-top-left-radius: 10px;
    }
    #top .point{
        width: 20px;
        height: 20px;
        float: left;
        margin-left: 10px;
        border-radius: 10px; /*设置元素的外边框圆角*/
        margin-top: 15px;
    }
    #top .red{
        background-color: red;
    }
    #top .yellow{
        background-color: yellow;
    }
    #top .green{
        background-color: green;
    }
    #top #calc-title{
        font-size: 22px;
        color: white;
        float : right;
        margin-top : 8px;
        margin-right: 10px;
    }
    /*结果部分样式*/
    #result {
        width: 437px;
        height: 50px;
        margin: auto;
        border: solid 2px red;
        text-align: right;
        padding-right: 10px;
        font-size:40px;
    }
    /*按钮部分样式*/
    #button {
        width: 450px;
        height: 408px;
        background-color: gray;
        margin:auto;
    }
    #button div{
        width: 110.5px;
        height: 80px;
        float:left;
        background-color: #7fffd4;
        margin :1px;
        line-height:80px;/*垂直居中,原理就是让文本的高度与容器的高度一致,达到居中的目的*/
        text-align: center; /*水平居中*/
        font-size: 26px;
    }
    /* 设置悬停效果,利用伪类实现 */
    #button div:hover{
        background-color: orangered; /*变色*/
        font-size : 30px; /*变大*/
        /* 还可以设计很多类似的 */
    }
</style>
<!-- 下面的部分为JS代码 -->
<script type="text/javascript">
    //输入数字
    function clicknumber(number){
        var result = document.getElementById("result");
        result.innerHTML = result.innerHTML + number; //+的作用取决于做+运算的两边的类型;比如是文本+数字,+就起到拼接的作用;如果是数字+数字,那么+就起到运算的作用;此处innerHTML是字符串,所以起到拼接作用
    }
    //输入运算符
    function clickoperator(operator){
        var result = document.getElementById("result");
        result.innerHTML = result.innerHTML + operator;
    }
    //得出运算结果
    function docalc(){
        var result = document.getElementById("result");
        var expression = result.innerHTML;
        result.innerHTML = eval(expression); //eval()不仅可以执行括号内的代码,还可以将括号内的字符串(如果是正确代码的话)当成代码执行
    }
    //清除所有字符
    function clearnumbers(){
        var result = document.getElementById("result");
        result.innerHTML = "";
    }
    //删除最右字符
    function Doback(){
        var result = document.getElementById("result");
        var len = result.innerHTML.length;
        result.innerHTML = result.innerHTML.substr(0,len-1); //从0开始,取len-1这么长的字符串
    }
</script>
</head>
<body onload="alertCalc()">
    <!-- 设置上栏 -->
    <div id = "top"> <!-- 用id这个属性的前提就是满足唯一性,也就是这个div不会和别的div具有共同配置的地方 -->
        <div class = "point red"></div>
        <!-- point red这样的写法表示这个元素属于point类也属于red类,目的是因为三个圆圈有很多的共性可以一并来设置 -->
        <div class = "point yellow"></div>
        <div class = "point green"></div>
        <div id = "calc-title">枫枫计算器</div>
    </div>
    <!-- 设置结果栏 -->
    <div id = "result">
    </div>
    <!-- 设置按钮区 -->
     <div id = "button">
        <div onclick="clearnumbers()">AC</div>
        <div>+/-</div>
        <div onclick="clickoperator('%')">%</div>
        <div onclick="clickoperator('/')">÷</div>
        <div onclick="clicknumber(7)">7</div>
        <div onclick="clicknumber(8)">8</div>
        <div onclick="clicknumber(9)">9</div>
        <div onclick="clickoperator('*')">*</div>
        <div onclick="clicknumber(4)">4</div>
        <div onclick="clicknumber(5)">5</div>
        <div onclick="clicknumber(6)">6</div>
        <div onclick="clickoperator('-')">-</div>
        <div onclick="clicknumber(1)">1</div>
        <div onclick="clicknumber(2)">2</div>
        <div onclick="clicknumber(3)">3</div>
        <div onclick="clickoperator('+')">+</div>
        <div onclick="clicknumber(0)">0</div>
        <div onclick="Doback()">删除</div>
        <div onclick="clickoperator('.')">.</div>
        <div onclick="docalc()">=</div>
     </div>
</body>
</html>

但是此时有BUG(比如运算符的连续输入、小数点的连续输入会导致运算不出结果等),还是需要完善的

解决运算符连续输入的问题:

两种解决方案:
1、设置一个开关(布尔类型的数据)
2、判断最后一个字符是否是运算符,如果是运算符,那么再输入运算符的时候进行一个替换

//第一个解决方案
<script type="text/javascript">
    var isOperatorClicked = false; //设置一个布尔类型的全局变量;命名规范(布尔类型的变量用is开头)
       //当然全局变量的话可以将var舍去,但是为了一致性就都加上var
       //是否是全局变量是看他所处于的位置的
//输入数字
    function clicknumber(number){
        var result = document.getElementById("result");
        result.innerHTML = result.innerHTML + number; //+的作用取决于做+运算的两边的类型;比如是文本+数字,+就起到拼接的作用;如果是数字+数字,那么+就起到运算的作用;此处innerHTML是字符串,所以起到拼接作用
        isOperatorClicked = false ; //未连续输入运算符,解除限制输入运算符的限制
    }
    //输入运算符
    function clickoperator(operator){
        var result = document.getElementById("result");
        //先要判断是否上一个字符是运算符,防止连续输入运算符
        if(isOperatorClicked == false){
            result.innerHTML = result.innerHTML + operator;
            isOperatorClicked = true; //运算符已经输入一次,限制再次连续输入运算符
        }
    }
    //得出运算结果
    function docalc(){
        var result = document.getElementById("result");
        var expression = result.innerHTML;
        result.innerHTML = eval(expression); //eval()不仅可以执行括号内的代码,还可以将括号内的字符串(如果是正确代码的话)当成代码执行
    }
    //清除所有字符
    function clearnumbers(){
        var result = document.getElementById("result");
        result.innerHTML = "";
        isOperatorClicked = false;
    }
    //删除最右字符
    function Doback(){
        var result = document.getElementById("result");
        var len = result.innerHTML.length;
        result.innerHTML = result.innerHTML.substr(0,len-1);
        isOperatorClicked = false; //用于更改结尾运算符(因为用户可能连续输入运算符是为了切换运算符,但是发现不能切换就利用删除去更换,但此时isOperatorClicked是true的,也就用户输入不了运算符)
      //但是仅仅这么写还是有bug的,因为用户万一删除到运算符为止然后又输入了一个运算符呢?要完善就需要额外的字符判断了,这里不再展示
    }
</script>

//第二个解决方案  
//输入运算符
    function clickoperator(operator){
        var result = document.getElementById("result");
        var len = result.innerHTML.length;
        var las = result.innerHTML.charAt(len-1);
        //判断字符串最后一个符号是否为运算符;是就替换最后一个运算符;不是就正常输入
        if(las == "+" || las == "-" || las == "%" || las == "/" || las == "*"){
            result.innerHTML = result.innerHTML.substr(0,len-1) + operator;
        }
        else{
            result.innerHTML = result.innerHTML + operator;
        }
    }

相信从上方的例子中可以发现,耦合度越高的程序他们相互之间影响也就越大,任何一个函数体出现问题可能会影响其他的函数

对于小数点的连续输入解决方法也是类似的,这里就不在描述了。

PHP

一、基本用法

1、代码块

PHP是运行于Web服务器中,主要用于网页处理。

必须要使用或者进行代码的包裹

<?php
 
?>

<?=

?>

可以在PHP的源文件(.php)中,直接写HTML代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <?php
        //可以在PHP的源文件(.php)中,直接写HTML代码;反之则不行
    ?>
</body>
</html>
2、注释
//可以注释一行

/* 可以用来注释一行
或者一个段落
只要被包裹起来即可*/
3、内容输出

有两种方式输出

<?php
    echo "hello\n";      // \n用于换行
    print "hello</br>";  //</br>用于换行,也可以写成<br/>或者<br>
//使用时都是可以加括号也可以不加括号
    echo ("hello");
    print ("hello");
//连续输出
       echo "111" , "222" ;   //注意print没有“,”的用法
       echo "111" . "222" ;   //在PHP中'.'表示字符串连接符
       print "111" . "222" ;
//综合利用
       echo "您好,您的余额为:" . 10000 . "元";
?>

注意:\n即换行符无法被浏览器解析(也就是浏览器中是没有换行的,但是查看页面源代码可以看到换行

只有这个标签才能被浏览器执行(与此同时由于使用了html标签那么查看页面源代码中就会看到这个标签存在)

字符串拼接的问题:

<?php
//连续输出
       echo "111" , "222" ;   //注意print没有“,”的用法
       echo "111" . "222" ;   //在PHP中'.'表示字符连接符
       print "111" . "222" ;
//综合利用
       echo "您好,您的余额为:" . 10000 . "元";
?>

引号的问题:

双引号:里面可以包裹字符串和变量

单引号:里面只能包裹字符串,不能引用变量

反引号:用于执行操作系统命令并返回结果

<?php
    $addr = "浙江宁波"; //变量的设定就是$加上命名
    echo "您所在的城市是:$addr <br>";
    echo '您所在的城市是: $addr <br>';
    echo `whoami`;
?>

乱码问题:

如果是windows操作系统,在利用echo和反斜杠执行操作系统命令(比如ipconfig)的时候在网页上会出现乱码

这是因为网页的编码是UTF-8,而操作系统的编码格式是GBK(中文编码)

解决方法:

 方法一:

 使用PHP内置函数header,往网页中写入GBK的响应头,让浏览器按照GBK的编码格式处理

 但是,这种方法会导致整个页面都是用GBK的编码格式,就让那些不是GBK编码的内容可能出现乱码情况

    header("content-type:text/html;charset='GBK'");

 方法二:

 使用PHP内置函数iconv,来对需要进行转码的文本进行编码格式的转换,不影响其他内容

<?php
    $result = `ipconfig`;
    $result = iconv("GBK","UTF-8",$result); //iconv(从哪个编码,专成哪个编码,要转的内容是什么)
    echo $result;
?>
4、通信过程

我们写的HTML代码是全文原封不动地(包括注释信息)发送给浏览器,然后让浏览器去执行代码,也就是说浏览器中查看源代码得到的就是我们原先写好的源代码。

但是我们的写的PHP代码,他并不是交给浏览器去执行,而是先交给脚本引擎,然后返回给浏览器的只是执行完成后的结果(也就是在浏览器中查看源代码是只能看到结果而看不到代码的)

当然,后续还可能牵扯到数据库,这个我们后面再说。

根据这个通信过程再结合php源代码中可以嵌套html、css、js等,我们就可以得到一个结论:

你在浏览器上看到的文本亦或是超链接不一定就是静态地写在网页上的,可能是php代码执行后返回的结果

比如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <?php
        echo "<div style='width:300px ; height:200px ; border: solid 2px red;'>".`whoami`."</div>";
    ?>
</body>
</html>

这里面返回的内容其实不是静态写在代码里面的,而是因为执行了whoami然后返回的结果

因为php的执行代码在网页上是看不到的,只能看到执行结果,所以在浏览器上查看页面源代码,看到的就形如静态文本

所以,在后续安全方面的学习中,能知道有这么一个现象,就可能会打开你的思路。

二、变量

1、数据类型

String(字符串):"welcome"     "宁波"

Integer(整型):1234     -1234

Float(浮点型):123.23    -123.23

Boolean(布尔型):true false

Array(数组):一组数据的集合(具体后续再讲)

Object(对象):具体后续再讲

NULL(空值)

2、命名规则

必要要求:
变量以$符号开始,后面跟着变量的名称
变量名必须以字母或者下划线字符开始
变量名只能包含字母、数字以及下划线(A-z0-9 _
变量名不能包含空格
变量名是区分大小写的($y $Y 是两个不同的变量)
非必要要求(但是是种规范):
变量名不要用中文全拼,最好使用英文单词
变量名不能用无意义的简写(XYOU),但是常规的简写是可以的(htmlmp3
函数名称必须使用动词或者动名词形式
变量名或者函数名等首字母小写;如果有多个单词,第二个单词的首字母建议大写

三、运算符

1、算数运算符

运算符

名称

描述

实例

结果

x + y

x 和 y 的和

2 + 2

4

x - y

x 和 y 的差

5 - 2

3

x * y

x 和 y 的积

5 * 2

10

x / y

x 和 y 的商

15 / 5

3

x % y

模(除法的余数)

x 除以 y 的余数

5 % 2
10 % 8
10 % 2

1 2 0

-x

设置负数

取 x 的相反符号

<?php $x = 2;echo -$x; ?>

-2

~x

取反

x 取反,按二进制位进行"取反"运算。运算规则: ~1=-2;    ~0=-1;

<?php $x = 2; echo ~$x; ?>

-3

a . b

并置

连接两个字符串

"Hi" . "Ha"

HiHa

2、赋值运算符

运算符

等同于

描述

x = y

x = y

左操作数被设置为右侧表达式的值

x += y

x = x + y

x -= y

x = x - y

x *= y

x = x * y

x /= y

x = x / y

x %= y

x = x % y

模(除法的余数)

a .= b

a = a . b

连接两个字符串

3、递增递减运算符

运算符

名称

描述

++ x

预递增

x 加 1,然后返回 x

x ++

后递增

返回 x,然后 x 加 1

-- x

预递减

x 减 1,然后返回 x

x --

后递减

返回 x,然后 x 减 1

4、比较运算符

运算符

名称

描述

实例

x == y

等于

如果 x 等于 y,则返回 true

5==8 返回 false

x === y

绝对等于

如果 x 等于 y,且它们类型相同,则返回 true

5==="5" 返回 false

x != y

不等于

如果 x 不等于 y,则返回 true

5!=8 返回 true

x <> y

不等于

如果 x 不等于 y,则返回 true

5<>8 返回 true

x !== y

不绝对等于

如果 x 不等于 y,或它们类型不相同,则返回 true

5!=="5" 返回 true

x > y

大于

如果 x 大于 y,则返回 true

5>8 返回 false

x < y

小于

如果 x 小于 y,则返回 true

5<8 返回 true

x >= y

大于等于

如果 x 大于或者等于 y,则返回 true

5>=8 返回 false

x <= y

小于等于

如果 x 小于或者等于 y,则返回 true

5<=8 返回 true

特别注意“=”,就比如

"100" == 100  //这个判断是true
"100" === 100 //这个判断是false

5、逻辑运算符

x and y

如果 x 和 y 都为 true,则返回 true

x=6 y=3 (x < 10 and y > 1) 返回 true

x or y

如果 x 和 y 至少有一个为 true,则返回 true

x=6 y=3 (x6 or y5) 返回 true

x xor y

异或

如果 x 和 y 有且仅有一个为 true,则返回 true

x=6 y=3 (x6 xor y3) 返回 false

x && y

如果 x 和 y 都为 true,则返回 true

x=6 y=3 (x < 10 && y > 1) 返回 true

x || y

如果 x 和 y 至少有一个为 true,则返回 true

x=6 y=3 (x5 || y5) 返回 false

! x

如果 x 不为 true,则返回 true

x=6 y=3 !(x==y) 返回 true

x + y

集合

x 和 y 的集合

x == y

相等

如果 x 和 y 具有相同的键/值对,则返回 true

x === y

恒等

如果 x 和 y 具有相同的键/值对,且顺序相同类型相同,则返回 true

x != y

不相等

如果 x 不等于 y,则返回 true

x <> y

不相等

如果 x 不等于 y,则返回 true

x !== y

不恒等

如果 x 不等于 y,则返回 true

四、分支语句

if-elseif-else语句

<?php
$t=date("H");
if ($t<"10")
{
    echo "Have a good morning!";
}
elseif ($t<"20")
{
    echo "Have a good day!";
}
else
{
    echo "Have a good night!";
}
?>

switch语句

<?php
$favcolor="red";
switch ($favcolor)
{
case "red":
    echo "你喜欢的颜色是红色!";
    break;
case "blue":
    echo "你喜欢的颜色是蓝色!";
    break;
case "green":
    echo "你喜欢的颜色是绿色!";
    break;
default:
    echo "你喜欢的颜色不是 红, 蓝, 或绿色!";
}
?>

五、循环语句

while : 只要指定的条件成立,则循环执行代码块

do...while : 首先执行一次代码块,然后在指定的条件成立时重复这个循环

for :循环执行代码块指定的次数

foreach : 根据数组中每个元素来循环代码块

break : 结束循环

countinu:结束本次循环,继续下一次循环

特殊情况:

在代码存在sleep的时候,输出缓冲区会被阻塞直到没有sleep了为止

可以使用ob_flush()和flush()两个函数来处理(作用是清空缓冲区),让缓冲区不再被sleep阻塞

六、函数

1、基本构成

 (1)函数名:函数名不能重复,并且名称可读性要强,最好是动词或动名词的形式

 (2)函数可以有参数:形参、实参

 (3)函数有处理过程,函数体

 (4)函数可以有返回值,也可以没有

 (5)函数若只有定义但是没有被调用,那么代码就不会运行

2、函数的三要素(函数规范、接口规范)

 (1)函数名

 (2)参数(参数的个数、顺序、类型)

 (3)返回值

 意义就是让函数在相互调用的时候能有一致的规范的定义,不用进行过多的修改。

3、例子
<?php
/**
*这个函数可以将两个数字相加,并且返回结果
*calc(数字1,数字2)
*/
function calc($a,$b){
    $result = $a + $b;
    return $result;
}
$result = calc(100,200);
echo $result;
?>

在函数前面使用/** */的方式可以让使用者在调用该函数的时候,看到你所写的注释

特殊的,php还可以在函数声明之前就调用该函数

<?php
$result = calc(100,200);
echo $result;
function calc($a,$b){
    $result = $a + $b;
    return $result;   //这行代码在这个函数中有点多此一举,但是这里只是为了将功能展示得更加全面
}
?>

但是不推荐这么写,因为不符合平时顺序上的逻辑

4、测试函数的函数(以判断电话号码是否符合规范来举例子)

我们在创建函数的时候会根据功能要求(调用后什么样的输入会有什么样的结果或者是调用后会有什么样的结果)去设计函数

那么测试函数的函数就是根据测试数据和对应的期望结果来判断我们设计的函数是否符合要求

这是一个很重要的思想

我们很多时候会对写好的函数进行优化、修改、完善等操作,但是改完之后我们就又要对函数的有效性进行验证了

这时候就可以利用测试函数来减少很多重复的工作,提高效率。

这个点和安全中的渗透测试机优点类似,利用渗透测试机对系统进行基本的检查,可以省去很多重复的步骤。

<?php
/**
 * 用于注册手机号码
 * 输入您的电话号码
 * 该函数会帮您自动检测该号码是否合规,并返回结果
 */
function checkNumber($phoneNumber){
    $len = strlen($phoneNumber);
    if($len != 11){ //要求位数为11位
        return false;
    }
    else if($phoneNumber[0] != 1){ //要求第一位为1
        return false;
    }
    else if($phoneNumber[1] <3 || $phoneNumber[1] >9){ //要求第二位是0-3
        return false;
    }
    else{
        $isphone = true ;
        for($i = 2 ; $i<=10 ; $i++){ //判断是否存在非数字
            if(!($phoneNumber[$i]>="0" && $phoneNumber[$i]<="9")){
                $isphone = false;
                break;
            }
        }
        if($isphone == false){
            return false;
        }
        else{
            return true;
        }
    }
}
/**
* 测试checkNumbe()函数的准确性
*/
function test_checkNumber($phone,$expect){
    if(checkNumber($phone) == $expect){
        echo "功能符合定义<br>";
    }
    else{
        echo "功能不符合定义<br>";
    }
}
test_checkNumber("18957828888",true);
test_checkNumber("18957828T88",false);
test_checkNumber("1895788",false);
test_checkNumber("10957828T88",false);
5、函数的目的

通过函数实现局部功能,通过局部去构造整体复杂的功能,不断地在做简化和优化;

通过函数在不断减少重复的工作;

每次优化和测试都可以在独立的函数中测试对应的部分,思路更加清晰;

这就是编程中很重要的思想。

七、正则表达式

1、作用

 (1)用于判断或者匹配某个字符串是否满足要求

 (2)用于从一个字符串中查找满足要求的内容

 (3)用于把一个字符串中满足要求的内容替换成其他内容

2、规则

描述

\

将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,'n' 匹配字符 "n"。'\n' 匹配一个换行符。序列 '\' 匹配 "" 而 "(" 则匹配 "("。

^

匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 '\n' 或 '\r' 之后的位置。

$

匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 '\n' 或 '\r' 之前的位置。

*

匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。

+

匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。

?

匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 。? 等价于 {0,1}。

{n}

n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。

{n,}

n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。

{n,m}

m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。

?

当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 "oooo",'o+?' 将匹配单个 "o",而 'o+' 将匹配所有 'o'。

.

匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用像"(.|\n)"的模式。

(pattern)

匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 '(' 或 ')'。

(?:pattern)

匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (|) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式。

(?=pattern)

正向肯定预查(look ahead positive assert),在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,"Windows(?=95|98|NT|2000)"能匹配"Windows2000"中的"Windows",但不能匹配"Windows3.1"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

(?!pattern)

正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如"Windows(?!95|98|NT|2000)"能匹配"Windows3.1"中的"Windows",但不能匹配"Windows2000"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

(?<=pattern)

反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。例如,"(?<=95|98|NT|2000)Windows"能匹配"2000Windows"中的"Windows",但不能匹配"3.1Windows"中的"Windows"。

(?<!pattern)

反向否定预查,与正向否定预查类似,只是方向相反。例如"(?<!95|98|NT|2000)Windows"能匹配"3.1Windows"中的"Windows",但不能匹配"2000Windows"中的"Windows"。

x|y

匹配 x 或 y。例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 则匹配 "zood" 或 "food"。

[xyz]

字符集合。匹配所包含的任意一个字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。

[^xyz]

负值字符集合。匹配未包含的任意字符。例如, 'abc' 可以匹配 "plain" 中的'p'、'l'、'i'、'n'。

[a-z]

字符范围。匹配指定范围内的任意字符。例如,'[a-z]' 可以匹配 'a' 到 'z' 范围内的任意小写字母字符。

[^a-z]

负值字符范围。匹配任何不在指定范围内的任意字符。例如,'a-z' 可以匹配任何不在 'a' 到 'z' 范围内的任意字符。

\b

匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。

\B

匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。

\cx

匹配由 x 指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符。

\d

匹配一个数字字符。等价于 [0-9]。

\D

匹配一个非数字字符。等价于 0-9。

\f

匹配一个换页符。等价于 \x0c 和 \cL。

\n

匹配一个换行符。等价于 \x0a 和 \cJ。

\r

匹配一个回车符。等价于 \x0d 和 \cM。

\s

匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。

\S

匹配任何非空白字符。等价于  \f\n\r\t\v。

\t

匹配一个制表符。等价于 \x09 和 \cI。

\v

匹配一个垂直制表符。等价于 \x0b 和 \cK。

\w

匹配字母、数字、下划线。等价于'[A-Za-z0-9_]'。

\W

匹配非字母、数字、下划线。等价于 'A-Za-z0-9_'。

\xn

匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,'\x41' 匹配 "A"。'\x041' 则等价于 '\x04' & "1"。正则表达式中可以使用 ASCII 编码。

\num

匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,'(.)\1' 匹配两个连续的相同字符。

\n

标识一个八进制转义值或一个向后引用。如果 \n 之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。

\nm

标识一个八进制转义值或一个向后引用。如果 \nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm。

\nml

如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。

\un

匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (?)。

修饰符

含义

描述

i

ignore - 不区分大小写

将匹配设置为不区分大小写,搜索时不区分大小写: A 和 a 没有区别。

g

global - 全局匹配

查找所有的匹配项。

m

multi line - 多行匹配

使边界字符 ^ 和 $ 匹配每一行的开头和结尾,记住是多行,而不是整个字符串的开头和结尾。

s

特殊字符圆点 . 中包含换行符 \n

默认情况下的圆点 . 是匹配除换行符 \n 之外的任何字符,加上 s 修饰符之后, . 中包含换行符 \n。

3、常用函数

(1)preg_match(模式,目标字符串)

 用于判断目标字符串是否匹配模式(正则);

 返回值为0/1或者true/false。

(2)preg_match_all(模式,目标字符串,输出结果)

 将目标字符串中符合模式(正则)的部分输出到输出结果地方

 输出结果的数据类型为数组类型

 对于数组的输出不能用echo,要用print_r或var_dump

 针对该函数,模式的写法有两种思路:

 思路一:匹配目标部分

 思路二:匹配包裹目标部分的左边部分和右边部分,而目标部分利用类似(.+?)的方式去匹配

(3)preg_replace(模式,要替换成什么,目标字符串)

 将目标字符串中符合模式的部分都替换成指定部分

 返回结果是替换后的字符串,所以一般要赋值才能实现效果(如:$result = preg_replace($pattern,"xxx","aaabbbccc");)

4、例子
匹配的一些例子:
<?php
function re($pattern,$source){
    $result = preg_match($pattern,$source);
    if($result){
        echo "匹配成功<br>";
    }
    else{
        echo "匹配失败<br>";
    }
}
re("/^go?/", "google"); //会匹配成功,因为没结尾符,所以只要前面符合就是符合的
re("/^go?$/", "google"); //会匹配失败,因为限制了结尾,该正则表达式的意思是必须匹配到的是g或者go
re("/^go{1,2}/", "gooooooogle"); //会匹配成功,因为没结尾符,所以只要前面符合就是符合的
re("/^\./", "."); //若要匹配符号(这个例子就是匹配.),则需要加上转译符,否则就是其符号代表的特殊用法
?>

优化一下之前写过的电话号码的匹配

<?php
function matchPhone($phone){
    $pattern = "#^1[3-9][0-9]{9}$#"; //用# #包裹和/ /包裹是等效的
    if(preg_match($pattern,$phone)){
        echo "符合";
    }
    else{
        echo "不符合";
    }
}
matchPhone("18911111111");
?>

如果是匹配IP地址呢

<?php
//由于IP地址(0.0.0.0)可以拆分成四个部分,只要一个部分能匹配上,剩下的只是连接问题
//因此为了方便,下面的例子只匹配一部分而不是整个IP
function matchIpAddress($ip){
    $pattern = "/(^[01]?\d?\d{1}$)|(^2[0-4]\d$)|(^25[0-5]$)/";
    if(preg_match($pattern,$ip)){
        echo "IP地址正确";
    }
    else{
        echo "IP地址错误";
    }
}
matchIpAddress("172");
?>
查找的例子1(模式是按照查找目标字符串来的):
<?php
function re_find(){
      $source = "18957222222是你的也是我的是大家的19999999292,但是你得想清楚00010101000,是什么13800000201,但是888888888又是什么你有没有思考过1827389100482102";
    $pattern = "#1[3-9][0-9]{9}#"; //匹配正确的电话号码
    preg_match_all($pattern,$source,$result);
    print_r($result);
}
re_find();
?>

执行后:

查找的例子2(模式是按照包裹目标的形式来的)
<?php
function re_find(){
    $source = '<li><a href="php-ref-curl.html">cURL 函数</a></li>
    <li><a href="php-ref-date.html">Date 函数</a></li>
    <li><a href="php-ref-directory.html">Directory 函数</a></li>
    <li><a href="php-ref-error.html">Error 函数</a></li>
    <li><a href="php-ref-filesystem.html">Filesystem 函数</a></li>
    <li><a href="php-ref-filter.html">Filter 函数</a></li>
    <li><a href="php-ref-ftp.html">FTP 函数</a></li>
    <li><a href="php-ref-http.html">HTTP 函数</a></li>
    <li><a href="php-ref-libxml.html">LibXML 函数</a></li>
    <li><a href="php-ref-mail.html">Mail 函数</a></li>
    <li><a href="php-ref-math.html">Math 函数</a></li>
    <li><a href="php-ref-misc.html">Misc 函数</a></li>
    <li><a href="php-ref-mysqli.html">MySQLi 函数</a></li>  
    <li><a href="php-ref-simplexml.html">SimpleXML 函数</a></li>
    <li><a href="php-ref-string.html">String 函数</a></li>
    <li><a href="php-ref-xml.html">XML Parser 函数</a></li>
    <li><a href="php-ref-zip.html">Zip 函数</a></li>';
    $pattern = '/<a href="(.+?)">/'; //匹配左右包裹的部分,中间部分模糊表示
    preg_match_all($pattern,$source,$result);
    print_r($result);
}
re_find();
?>

执行后:

替换的例子:
<?php
function re_rp(){
    $source = "18957222222是你的也是我的是大家的19999999292,但是你得想清楚00010101000,是什么13800000201,但是888888888又是什么你有没有思考过1827389100482102";
    $pattern = "#1[3-9][0-9]{9}#"; //匹配合法的是手机号码
    $result = preg_replace($pattern,"xxx",$source);
    print_r($result);
}
re_rp();
?>

执行后:

八、数组

1、基础使用

 (1)作用:数组就是将一组数据,用统一的结构来保存和使用

 (2)定义:

<?php
    $student = array("哆啦A梦","蜡笔小新","小葵","鸣人","佐助","鼬","卡卡西");
?>

 (3)基本操作

<?php
    $student = array("哆啦A梦","蜡笔小新","小葵","鸣人","佐助","鼬","卡卡西");
   
    //取数组的长度
    $count = count($student);
    echo $count."<br>";
   
    //通过下标取值,在php中,下标从0开始
    echo $student[0]."<br>";
   
    //修改数据的某个值
    $student[1] = "动感超人";
    echo $student[1]."<br>";
   
    //打印数组的内容
    print_r($student);
    echo "<br>";
   
    //删除最后的值
    array_pop($student);
    print_r($student);
?>

 上述代码的执行结果

7 //数组中的个数为7
哆啦A //数组的第一个是哆啦A
动感超人 //数组的第二个被修改成了动感超人
Array ( [0] => 哆啦A [1] => 动感超人 [2] => 小葵 [3] => 鸣人 [4] => 佐助 [5] => [6] => 卡卡西 ) //输出了一遍数组
Array ( [0] => 哆啦A [1] => 动感超人 [2] => 小葵 [3] => 鸣人 [4] => 佐助 [5] => ) //删除了数组的最后一个值

2、索引数组

这样定义的:$student = array("哆啦A梦","蜡笔小新","小葵","鸣人","佐助","鼬","卡卡西");

就是索引数组,他是以下标来取值的

有关索引数组有很多的操作,可以通过查找字典来学习

这里列举几个常用的:

随机下标:

<?php
    $student = array("哆啦A梦","蜡笔小新","小葵","鸣人","佐助","鼬","卡卡西");
    //array_rand()   从数组中随机选出一个或多个元素,返回键名(下标)
    //次函数返回的是数组类型的,array_rand(数组,取的个数);
    //其中取的个数默认是1
    $index = array_rand($student);
   echo $index; //由于默认取1个,所以可以用echo
   
    $index = array_rand($student,3);
    print_r($index); //由于取三个,不能再使用echo了

       //利用这个函数就可以实现随机点名
       $index = array_rand($student);
       echo $student[$index];

       //当然还可以使用其他函数来实现随机点名
    $index = rand(0,count($student)-1); //rand(数1,数2) 生成[数1,数2]范围内的随机整数
    echo $student[$index];
?>

数组去重复:

<?php
    $student = array("哆啦A梦","蜡笔小新","小葵","鸣人","佐助","鼬","卡卡西","蜡笔小新","蜡笔小新","蜡笔小新","鼬","卡卡西","鼬","卡卡西");
    $new_student = array_unique($student); //去除数组中重复的部分
    print_r($new_student);
?>

执行后输出结果为:
Array ( [0] => 哆啦A [1] => 蜡笔小新 [2] => 小葵 [3] => 鸣人 [4] => 佐助 [5] => [6] => 卡卡西 )
完成了去除重复的部分

遍历数组:

<?php
    //方法一:利用下标遍历
    $student = array("哆啦A梦","蜡笔小新","小葵","鸣人","佐助","鼬","卡卡西");
    for($i = 0 ; $i < count($student) ; $i++){
        echo $student[$i]."<br>";
    }
       //方法二:foreach遍历
       foreach($student as $s){
           echo $s."<br>";
    }
?>

排序

<?php
    $number = array("7","8","4","2","9");
    sort($number); //升序
    print_r($number);
    rsort($number); //降序
    print_r($number);
?>
3、关联数组

以key=>value组成键值对,取值的时候用key来取值,而不是下标

<?php
    $student01 = array('name'=>'哆啦A梦','age'=>'50','addr'=>'宁波','phone'=>'123476');
    $student02 = array('name'=>'蜡笔小新','age'=>'7','addr'=>'杭州','phone'=>'123456');
    $student03 = array('name'=>'小葵','age'=>'2','addr'=>'杭州','phone'=>'123496');
    $student04 = array('name'=>'鸣人','age'=>'23','addr'=>'木叶','phone'=>'122456');
    $student05 = array('name'=>'佐助','age'=>'23','addr'=>'木叶','phone'=>'125456');

    echo $student01['name']."<br>"; //输出数组中key为name的值
    $student01['addr'] = '浙江宁波'; //修改数组中key为addr的值

       //打印数组
       print_r($student01);
       //输出的内容:Array ( [name] => 哆啦A梦 [age] => 50 [addr] => 浙江宁波 [phone] => 123476 )
       echo  "<br>";

    //遍历数组中的value
    foreach($student01 as $stu){
        echo $stu."<br>";
    }
       /*
       输出的结果:
       哆啦A梦
       50
       浙江宁波
       123476
       */
   
       //遍历数组中的key和value
    foreach($student02 as $key=>$value){
        echo $key."=>".$value."<br>";
    }
       /*
       输出的内容:
    name=>蜡笔小新
    age=>7
    addr=>杭州
    phone=>123456
       */
   
       //直接取数组的最后一个值
       echo end($student05)."<br>";
   
    //判断数组中是否存在某值
    if(in_array('蜡笔小新',$student02)){
        echo "在<br>";
    }
   
    //把字符串根据指定规则打散成数组
    $teststr = "哆啦A梦#50#addr#宁波#phone#123476";
    $breakstr = explode("#",$teststr); //以#为分隔符打散成数组
    print_r($breakstr); //结果为:Array ( [0] => 哆啦A梦 [1] => 50 [2] => addr [3] => 宁波 [4] => phone [5] => 123476 )
    echo "<br>";

    //把数组合并成字符串
    $student = array("哆啦A梦","蜡笔小新","小葵","鸣人","佐助","鼬","卡卡西");
    $combarr = implode("~",$student); //将数组合并成字符串,并在每个值之间用~隔开
       //join()函数和implode用法一样
    echo $combarr."<br>"; //输出结果:哆啦A梦~蜡笔小新~小葵~鸣人~佐助~鼬~卡卡西
?>
4、二维数组以及多维数组
<?php
    $student01 = array('name'=>'哆啦A梦','age'=>'50','addr'=>'宁波','phone'=>'123476');
    $student02 = array('name'=>'蜡笔小新','age'=>'7','addr'=>'杭州','phone'=>'123456');
    $student03 = array('name'=>'小葵','age'=>'2','addr'=>'杭州','phone'=>'123496');
    $student04 = array('name'=>'鸣人','age'=>'23','addr'=>'木叶','phone'=>'122456');
    $student05 = array('name'=>'佐助','age'=>'23','addr'=>'木叶','phone'=>'125456');
   
    //二维数组
    $class = array($student01,$student02,$student03,$student04,$student05);
    //输出数据
    echo $class[1]['name']."<br>"; //输出蜡笔小新
?>

这个二维数组形如这样的一个表。看得出来二维数组跟数据表是一一对应的

学生

name

age

addr

phone

student01

哆啦A梦

50

宁波

123476

student02

蜡笔小新

7

杭州

123456

student03

小葵

2

杭州

123496

student04

鸣人

23

木叶

122456

student05

佐助

23

木叶

125456

二维数组里面装一维数组

三维数组里面装二维数组

以此类推

九、前后端交互处理

1、简介

前后端的交互过程就是HTTP协议的处理过程:请求与响应的处理过程

三种方式:

 (1)资源获取型:GET请求+URL地址

 (2)数据提交型:POST请求+URL地址+请求正文

 (3)AJAX提交:利用异步提交的方式,在不刷新当前页面的情况下跟后台交换数据然后更新部分网页内容

2、POST请求

(1)必须使用form标签将所有的表单元素包裹起来

(2)必须要在form标签中指定action属性(URL地址)

(3)必须要在form标签中指定method属性的提交方式为POST

(4)必须要确保在form标签内至少有一个提交按钮

(5)必须在文本框中设置属性name的值

提交页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>这是一个登陆界面</title>
    <style>
        div_x{
            width: 300px;
            height: 400px;
            border: solid 1px rgb(204,83,114);
            margin: auto;
        }
        .login{
            width: 350px;
            height: 50px;
            border: solid 0px red;
            margin: auto;
            text-align: center;
        }
        .top-100{
            margin-top: 100px;
        }
        .font-30{
            font: size 30px;
        }
        input{
            width: 300px;
            height: 35px;
            text-align: center;
            border-radius: 5px;
        }
        button{
            width: 300px;
            height: 35px;
            background-color: dodgerblue;
            color:whitesmoke;
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <form action="../php/login.php" method="post">   <!-- 默认是get模式 -->
        <div class = "login top-100 font-30">登 陆</div>
        <div class = "login">
            <input type = "text" name="username"/>
        </div>
        <div class = "login">
            <input type = "password" name = "password"/>
        </div>
        <div class = "login">
            <input type="text" name="vcode"/>
        </div>
        <div class="login">
            <button type="submit">登陆</button>
        </div>
    </form>
</body>
</html>

后台如何接受POST传过来的数据:

<?php
    $username = $_POST['username'];
    $password = $_POST['password'];
    $vcode = $_POST['vcode'];

    echo $username."-".$password."-".$vcode;
?>
3、GET请求

要求同POST请求

前端登陆页面同上(只是将form的method属性改为get或者不设置)

后台如何接受GET请求:

<?php
    //GET请求
    $username = $_GET['username'];
    $password = $_GET['password'];
    $vcode = $_GET['vcode'];

    echo $username."-".$password."-".$vcode;
?>
4、AJAX请求

服务器仅仅只是处理请求并返回请求结果(至于前端怎么处理这些数据,服务器并不关心)

浏览器对返回的请求结果用JS代码处理

基本要求:

(1)需要引入JQuery的JS库

 <script type="text/javascript" src="jquery-3.4.1.min.js"></script>

(2)不再需要form,只需要任意一个元素发起一个JS事件,让JS代码进行处理

前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>这是一个登陆界面</title>
    <style>
        div_x{
            width: 300px;
            height: 400px;
            border: solid 1px rgb(204,83,114);
            margin: auto;
        }
        .login{
            width: 350px;
            height: 50px;
            border: solid 0px red;
            margin: auto;
            text-align: center;
        }
        .top-100{
            margin-top: 100px;
        }
        .font-30{
            font: size 30px;
        }
        input{
            width: 300px;
            height: 35px;
            text-align: center;
            border-radius: 5px;
        }
        button{
            width: 300px;
            height: 35px;
            background-color: dodgerblue;
            color:whitesmoke;
            border-radius: 5px;
        }
    </style>
    <script type="text/javascript" src="jquery-3.4.1.min.js"></script>
    <script>
        function doPost(){
            //获取表单元素的值
            var username = $("#username").val();
            var password = $("#password").val();
            var vcode = $("#vcode").val();
            //通过一个字符串拼接成一个POST请求正文
            var param = "username=" + username + "&password=" + password + "&vcode=" + vcode;
            //利用AJAX发送POST请求,并获取响应
            //其中function(data)是一个匿名函数,即没有函数名;函数中的data就是本次请求的返回内容
            $.post('../php/login.php',param,function(data){
                window.alert(data);
            })
        }
    </script>
</head>
<body>
        <div class = "login top-100 font-30">登 陆</div>
        <div class = "login">
            <input type = "text" name="username" id="username"/> <!-- 设置ID属性的目的是为了方便通过ID选择器来选择文本框的值 -->
        </div>
        <div class = "login">
            <input type = "password" name = "password" id="password"/>
        </div>
        <div class = "login">
            <input type="text" name="vcode" id="vcode"/>
        </div>
        <div class="login">
            <button onclick="doPost()">登陆</button>
        </div>
</body>
</html>

login.php文件代码:

<?php
    $username = $_POST['username'];
    $password = $_POST['password'];
    $vcode = $_POST['vcode'];

    echo $username."-".$password."-".$vcode;
?>

可以看到后端用来响应POST请求的PHP文件代码内容并没有什么变化(该POST还是POST)

AJAX只是没有采用更新页面或者跳转页面的方式去显示后台的结果,而是根据后台返回的内容再在浏览器上根据JS代码所要求的方式来展现这些数据,让用户体验感更好。

打个形象的比方:

场景:你在电影院买票,凭票可以换免费爆米花

传统方式:

你在前台买完票,然后需要到专门放爆米花的地方去凭票取爆米花

AJAX方式:

你在前台买完票,服务员直接给你了免费的爆米花

付钱买票就相当于向后台发送了一个请求,票就是后台返回你的数据

其实此时的结果就是顾客可以吃到免费的爆米花了(传统和AJAX都一样),但是获取方式不一样(体验感不一样)

传统方式处理数据的方式就是让顾客换点(切换界面或者刷新)取爆米花;AJAX就是直接拿出(不用切换界面或者刷新)爆米花给顾客。

AJAX请求逻辑可以说明:

我们看到的页面信息和页面源代码不一定是一一对应的,有些后台返回的信息存在data当中,在前端中是看不到data里面的内容的。

十、访问数据库

1、步骤

(1)连接mysql数据库

(2)执行SQL语句(CRUD)

(3)处理SQL语句的结果

(4)关闭数据库连接

事实上,所有的I/O操作都需要实现打开和关闭两个基本操作:文件读写、网络访问、数据库访问

2、基本代码

前端页面采用传统的POST提交(代码在上一节)

数据库基本信息:

库:book

表:user

后端代码:

<?php
    //接受POST传值
    $username = $_POST['username'];
    $password = $_POST['password'];
    $vcode = $_POST['vcode'];

    $conn = mysqli_connect('127.0.0.1','root','root','book'); //连接数据库mysqli_connect(数据库的IP地址,用户名,密码,数据库名)
    mysqli_query($conn,'set names utf8'); //设置编码格式避免出现乱码
                                                                          //也可以使用内置函数mysqli_set_charset($conn,'utf8');
                                                                       //mysqli_query(连接好的数据库,sql语句),用于执行SQL语句;
    $sql = "select * from user where username = '$username' and password = '$password'";  //编辑好sql语句
    $result = mysqli_query($conn,$sql);  //将执行后的结果放入result中
    //mysqli_num_rows()是用来表示数据库查询结果有多少行数的,在这里如果没找到结果那么就是0行,因此可以用来判断用户输入的信息是否匹配
       if(mysqli_num_rows($result) == 1){
        echo "登录成功";
    }
    else{
        echo "登录失败";
    }
    mysqli_close($conn); //使用完成后记得关闭数据库
?>

这里有个问题:如果一开始数据库连接的部分出错,那么后面的部分就一定是错的

也就是会出现非常多的报错

为了解决这个问题我们可以将连接数据库的语句后面加上die函数:

$conn = mysqli_connect('127.0.0.1','root','root','book') or die("数据库连接出错");

这样就可以有效防止因为数据库信息输入错误导致的大量报错的现象

3、实现文章查询
<?php
    $conn = mysqli_connect('127.0.0.1','root','root','book');
    mysqli_query($conn,'set names utf8');
    $sql = "select articleid,headline,author,viewcount from article";
    $result = mysqli_query($conn,$sql);
    $rows = mysqli_fetch_all($result); //将数据库的查询的结果放到一个索引数组里面
    //注意数据库返回的结果通常是张二维表,那么存储他的数组也就是二维数组
    foreach($rows as $row){
        echo $row[0]."-".$row[1].'-'.$row[2].'-'.$row[3]."<br>";
    }
    mysqli_close($conn);
?>

结果:

1-How To Express yourself-zyf-3
2-Why you are confused?-fff-2
3-How To Make Friends With Others?-bei_nifaxianl-3

我们前面提到过PHP源文件可以嵌入HTML代码

因此我们可以写出更好的布局效果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?php echo date('Y-m-d H:i:s'); ?></title> <!--设置一个动态标题-->
    <style>
        table{
            width:800px;
            margin:auto;
            border:solid 1px green;
            border-spacing:0px;
        }
        td{
            border:solid 1px gray;
            height:30px;
        }
    </style>
</head>
<body>
    <table>
    <?php
        $conn = mysqli_connect('127.0.0.1','root','root','book');
        mysqli_query($conn,'set names utf8');
        $sql = "select articleid,headline,author,viewcount from article";
        $result = mysqli_query($conn,$sql);
        $rows = mysqli_fetch_all($result); //将数据库的查询的结果放到一个索引数组里面
        //注意数据库返回的结果通常是张二维表,那么存储他的数组也就是二维数组
        foreach($rows as $row){
            echo "<tr>";
            echo "<td>".$row[0]."</td>";
            echo "<td>".$row[1]."</td>";
            echo "<td>".$row[2]."</td>";
            echo "<td>".$row[3]."</td>";
            echo "</tr>";
        }
        mysqli_close($conn);
    ?>
    </table>
</body>
</html>

为了完善文章内容的读取,我们还可以在文章部分加入超链接,点击超链接之后就可以去查看文章的内容

若语句比较复杂可以将原先的HTML代码写出来,然后再去拼接想要的内容

//修改文章标题部分,改成超链接并传值

echo "<td><a href='content.php?id=".$row[0]."'>".$row[1]."</a>"."</td>";

content.php页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>
        <?php
        $id = $_GET['id'];
        $conn = mysqli_connect('127.0.0.1','root','root','book');
        mysqli_query($conn,'set names utf8');
        $sql = "select headline from article where articleid = '$id'";
        $result = mysqli_query($conn,$sql);
        $content = mysqli_fetch_all($result);
        foreach($content as $con){
            echo $con[0];
        }
        mysqli_close($conn);
        ?>
    </title>
    <style>
        table{
            width:800px;
            margin:auto;
            border:solid 1px green;
            border-spacing:0px;
        }
        td{
            border:solid 1px gray;
            height:50px;
        }
    </style>
</head>
<body>
    <table>
        <?php
            $id = $_GET['id'];
            $conn = mysqli_connect('127.0.0.1','root','root','book');
            mysqli_query($conn,'set names utf8');
            $sql = "select content from article where articleid = '$id'";
            $result = mysqli_query($conn,$sql);
            $content = mysqli_fetch_all($result);
            foreach($content as $con){
                echo "<tr>";
                echo "<td>".$con[0]."</td>";
                echo "</tr>";
            }
            mysqli_close($conn);
        ?>
    </table>
</body>
</html>

最终效果图:

这里有个优化的问题

我们上面将数据库的结果放到了一个索引数组里面

这就使得代码的可读性不是很强,因为0,1,2……时间久了就不知道什么意思了,亦或是数据库发生了变化,那么这些索引下标的意义就发生变化了,扩展性不强

所以我们可以使用:

$result = mysqli_query($conn,$sql);
$content = mysqli_fetch_all($result,MYSQLI_ASSOC);   //强制使用关联数组(当然还是二维数组)存储结果

或者可以:

$result = mysqli_query($conn,$sql);
$content = mysqli_fetch_assoc($result);    //用关联数组(当然还是二维数组)存储结果的第一行
4、利用AJAX实现用户注册

注册一个用户(用户名重复则注册失败)

前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="jquery-3.4.1.min.js"></script>
    <style>
        table{
            width:600px;
            margin:auto;
            border-spacing: 0;
            border:solid 1px green;
        }
        td{
            height:50px;
            border:solid 1px gray;
            text-align:center;
        }
        button{
            width: 200px;
            height:35px;
            background-color: dodgerblue;
            color:whitesmoke;
            border-radius: 5px;
        }
    </style>
    <script>
        function doPost(){
            //获取表单元素的值
            var username = $("#username").val();
            var password = $("#password").val();
            //通过一个字符串拼接成一个POST请求正文
            var param = "username=" + username + "&password=" + password;
            $.post('../php/regajax.php',param,function(data){
                if(data == "re-success"){ //根据返回的结果判断是否注册成功
                    window.alert("注册成功");
                }
                else{
                    window.alert("注册失败");
                }
            })
        }
    </script>

</head>
<body>
        <table>
            <tr>
                <td width="40%">用户名:</td>
                <td width="60%"><input type="text" id="username"/></td>
            </tr>
            <tr>
                <td>密码:</td>
                <td><input type="password" id="password"/></td>
            </tr>
            <tr>
                <td colspan="2"><button onclick="doPost()">注册</button></td>
            </tr>
        </table>
</body>
</html>

后端代码

<?php
    $conn = mysqli_connect('127.0.0.1','root','root','book') or die('数据库连接失败');
  
       $username = $_POST['username'];
    $password = $_POST['password'];
   
       $sql = "select * from user2 where username = '$username'";
    $result = mysqli_query($conn,$sql);
    if(mysqli_num_rows($result)==1){
        echo "re-fall";   //给前端中的匿名函数中的data判断的
    }
    echo "re-success";  //给前端中的匿名函数中的data判断的
    //至于后端要干什么操作用户是无感的,因为反馈的数据被前端中的匿名函数的data所捕获,怎么处置是前端的事情
       //将用户数据插入数据库
       $sql = "insert into user2(username,password) values('$username','$password')";
    mysqli_query($conn,$sql) or die('创建失败');
    mysqli_close($conn);
?>

5、用户注册+文件上传

注册一个用户(用户名重复则注册失败)

为了方便掩饰,没有使用AJAX的方式进行注册

前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Regist</title>
    <style>
        table{
            width:600px;
            margin:auto;
            border-spacing: 0;
            border:solid 1px green;
        }
        td{
            height:50px;
            border:solid 1px gray;
            text-align:center;
        }
        button{
            width: 200px;
            height:35px;
            background-color: dodgerblue;
            color:whitesmoke;
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <form action="../php/reg.php" method="post" enctype="multipart/form-data">
    <!-- 文件上传需要有enctype="multipart/form-data" -->
        <table>
            <tr>
                <td width="40%">用户名:</td>
                <td width="60%"><input type="text" name="username"/></td>
            </tr>
            <tr>
                <td>密码:</td>
                <td><input type="password" name="password"/></td>
            </tr>
            <tr>
                <td>头像:</td>
                <td><input type="file" name="photo"/></td>
            </tr>
            <tr>
                <td colspan="2"><button method="submit">注册</button></td>
            </tr>
        </table>
    </form>
</body>
</html>

后端代码:

<?php
    //修改时区为北京时间
    date_default_timezone_set("PRC");
   
       //连接数据库
    $conn = mysqli_connect('127.0.0.1','root','root','book') or die('数据库连接失败');
   
       //获得POST传过来的值
    $username = $_POST['username'];
    $password = $_POST['password'];
    $tmpPath = $_FILES['photo']['tmp_name']; //获取上传文件的临时路径
    $fileName = $_FILES['photo']['name']; //获取上传文件的原始名称
   
    //判断用户名是否重复
    $sql = "select * from user where username='$username'";
    $result = mysqli_query($conn,$sql);
    if(mysqli_num_rows($result) == 1){
        die('用户名重复');
    }
   
       //统一上传文件格式
    $newFilename =  date('Ymd_His.').end(explode(".",$fileName)); //修改文件名称,使得文件名称按照上传时间来命名
    //end(explode(".",$fileName))的作用是取出上传文件的后缀,因为explod将文件名称以点为分隔符打散成数组,end是取数组的最后一部分的值也就是文件后缀了
    move_uploaded_file($tmpPath,'./upload/'.$newFilename) or die('文件上传失败'); //修改原本的文件路径
    $date = date('Y-m-d H:i:s'); //当前时间
  
       //将新用户插入数据库
    $sql = "insert into user(username,password,filename,date) values('$username','$password','$newFilename','$date')";
    mysqli_query($conn,$sql) or die('创建用户失败');
    echo '创建成功';
    mysqli_close($conn);
?>

6、利用AJAX方式使得可以删除文章

在3中我们实现了文章目录和文章内容的读取

现在我们用AJAX的方式实现文章目录中可以删除该文章(并同步数据库)

修改article.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?php echo date('Y-m-d H:i:s'); ?></title> <!--设置一个动态标题-->
    <!-- 要使用AJAX方式,就需要引入js库 -->
    <script type="text/javascript" src="../html/jquery-3.4.1.min.js"></script>
    <style>
        table{
            width:800px;
            margin:auto;
            border:solid 1px green;
            border-spacing:0px;
        }
        td{
            border:solid 1px gray;
            height:30px;
        }
    </style>
    <!-- 获取要删除文章的articleid,然后提交给后台,后台返回结果给前端,前端再处理该如何展示结果,实现AJAX方式 -->
    <script>
        function doDelete(articleid){
            var content = "articleid=" + articleid;
            $.post('delete.php',content,function(data){
                if(data == "delete-success"){
                    window.alert("删除成功");
                    location.reload(); //刷新页面看到数据,也可以采用location.href = "article.php";跳转到本页面也同样达到了更新的效果
                }
                else{
                    window.alert("articleid:"+data+"删除失败");
                }
            });
        }
    </script>
</head>
<body>
    <table>
    <?php
        $conn = mysqli_connect('127.0.0.1','root','root','book');
        mysqli_query($conn,'set names utf8');
        $sql = "select articleid,headline,author,viewcount from article";
        $result = mysqli_query($conn,$sql);
        $rows = mysqli_fetch_all($result); //将数据库的查询的结果放到一个关联数组里面
        //注意数据库返回的结果通常是张二维表,那么存储他的数组也就是二维数组
        foreach($rows as $row){
            echo "<tr>";
            echo "<td id='articleid'>".$row[0]."</td>";
            echo "<td id='headline'><a href='content.php?id=".$row[0]."'>".$row[1]."</a>"."</td>";
            echo "<td id='author'>".$row[2]."</td>";
            echo "<td id='viewcount'>".$row[3]."</td>";
            echo "<td><button onclick='doDelete(".$row[0].")'>删除</button></td>";
            echo "</tr>";
        }
        mysqli_close($conn);
    ?>
    </table>
</body>
</html>

delete.php

<?php
    //连接数据库
    $conn = mysqli_connect('127.0.0.1','root','root','book') or die('数据库连接错误');
   
    $articleid = $_POST['articleid']; //获取前端传过来的要删除的文章id
    $sql = "delete from article where articleid = $articleid";
    mysqli_query($conn,$sql) or die('delete-fall');
    echo "delete-success"; //跟前端反应结果,是成功还是失败
    mysqli_close($conn);
?>

测试:

前端点击删除之后

经过弹窗提示删除成功之后,页面变成如下

数据库中的对应数据也被删除了

上面的删除过程存在着几个改进的地方:

首先,用户在页面点击删除的时候,万一是误触了呢?

所以,我们需要和用户交互一下

使用:

var result = window.confirm('你确定要删除该文章吗');
if(result == true){
       //实行删除代码
}
else{
       //返回页面
}

其次,在后台数据库这,我们实行的是直接删除(硬删除)

但是,一般在实际情况下,我们更多使用的是软删除

软删除有以下几种方式:

(1)回收站

回收站其实就是另外一个数据库/表,将删除的数据移到对应的库/表就好了

(2)设定列标识

我们在对应表中增加一个字段来判断该行是否有效

删除并非执行删除的sql语句,而是修改判断标识的值,修改为已删除

那么对应的,在展示文章列表的时候也就需要加一步判断文章是否有效这么一个步骤

所以用户看到的似乎是删除了,但是在数据库中还是存在该记录的

7、实现文章添加的页面

前端(add.html):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <table>
        <form action="../php/add.php" method="post">
            <tr>
                <td width="20%">文章标题</td>
                <td width="80%"><input type="text" name="headline" style="width:500px"/></td>
            </tr>
            <tr>
                <td>文章内容</td>
                <td>
                    <textarea style="width:500px;height:200px;" name="content"></textarea>
                  <!-- textarea的文本输入数量会更大 -->
                </td>
            </tr>
            <tr>
                <td colspan="2" align = "center">
                    <button>新增</button>
                </td>
            </tr>
        </form>
    </table>
</body>
</html>

后端(add.php):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <?php
        $conn = mysqli_connect("127.0.0.1","root","root","book") or die('数据库连接失败');
        mysqli_query($conn,"set names utf8");
        $headline = $_POST['headline'];
        $contnet =  $_POST['content'];
        $sql = "insert into article(headline,author,content,viewcount) values('$headline','zyf','$content',0) ";
        mysqli_query($conn,$sql);
        echo "新增成功";
        echo "<script>location.href = 'article.php'</script>";
        mysqli_close($conn);
    ?>
</body>
</html>
8、如果经常使用访问数据的操作

我们可以将连接数据库写成函数,然后在其他文件里引用这个函数

common.php

<?php
    function connect_Db(){
        $conn = mysqli_connect("127.0.0.1","root","root","book") or die('数据库连接失败');
        return $conn; //别人引用该函数的时候,返回一个数据库指针
    }
?>

别的php文件中如果需要引入的方法:

    <?php
        //引用common.php,也就是可以使用它里面设置的函数了
        require_once("common.php");
        include_once("common.php");
              //上述两个方法选一个即可
        $conn = connect_Db(); //获取函数返回指针
              //执行一些列操作,最后关闭数据库即可
        mysqli_close($conn);
    ?>

十一、Session&Cookie

1、前引

HTTP协议默认是无状态的协议

 这意味着每个HTTP请求都是独立的,与之前或之后的请求没有直接联系。协议对于事务处理没有记忆功能,每个请求都包含了处理该请求所需的完整数据。

为了解决上述问题,需要使用Session来将客户端的状态保存在服务器(的内存或者硬盘中)

首次访问的请求包中是没有Session ID的

然后服务器响应请求包之后会将Session ID发给客户端

客户端下次的请求就需要带着这个Session ID去访问

2、针对前面写的文章系统

此时的文章系统我们没有设置过Session

也就意味着任何人(即便没有经过登录)都可以随意的使用文章的添加、删除等操作

所以我们要利用Session去认证登录过的用户,还要去区分用户的身份(谁可以修改,谁只能读)

修改login.php

<?php
    $username = $_POST['username'];
    $password = $_POST['password'];
    $vcode = $_POST['vcode'];

    $conn = mysqli_connect('127.0.0.1','root','root','book') or die("数据库连接出错");
    mysqli_query($conn,'set names utf8');
    $sql = "select * from user where username = '$username' and password = '$password'";
    $result = mysqli_query($conn,$sql);
    if(mysqli_num_rows($result) == 1){
        //登录成功之后,给客户端一个Session ID
        session_start(); //启用PHP的Session模块,为客户端生成唯一标识
        //可以在SESSION中可以添加服务器想要保存客户端的信息
        //因为下一次客户端访问服务器的时候会带上这个SESSION,所以服务器就可以从SESSION中得到之前保存的关于客户端的信息
        $_SESSION['isLogin'] = 'true'; //存入登录状态
        $_SESSION['username'] = $username; //存入用户名
        echo "login-success";
    }
    else{
        echo "login-fall";
    }
    mysqli_close($conn);
?>

在tmp目录下有之前存储的Session ID,为了测试可以先清空

然后去登录页面

登录成功之后,服务器就会在响应包头里面给客户机一个Session ID

点开会发现有服务器存储的客户端的信息(登录状态:已经登录;用户名为zyf)

这里顺带说一下SESSION的格式

isLogin|s:4:"true";username|s:3:"zyf";
解释:
isLogin 为变量名
s:4 说明前面的变量是字符串,长度为4
“true=” 变量值为“true
username 为变量名
s:3 说明前面的变量是字符串,长度为3
“zyf” 变量值为“zyf

此时我们再去修改add.php,给他加一个用户需要登录之后才能添加文章的一个限制

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <?php
        session_start(); //启用session
              //判断用户是否是登录状态
        if($_SESSION['isLogin'] != 'true'){
            die('您无权操作,请先登录');
        }
        $conn = mysqli_connect("127.0.0.1","root","root","book") or die('数据库连接失败');
        mysqli_query($conn,"set names utf8");
        $headline = $_POST['headline'];
        $contnet =  $_POST['content'];
        $sql = "insert into article(headline,author,content,viewcount) values('$headline','zyf','$content',0) ";
        mysqli_query($conn,$sql);
        echo "新增成功";
        echo "<script>location.href = 'article.php'</script>";
        mysqli_close($conn);
    ?>
</body>
</html>

此时清空缓存,不登录,直接去访问add.html然后点添加文章

会发现

代码中我们启用了session(根据session_start();)也就是说服务器会在我们第一次访问的时候下发一个sessionid

但是此时我们是没有经过登录的,也就是说服务器给我们的sessionid里面添加登录状态信息(也就是没有isLogin = ‘true’)

那么,当我们登录之后

服务器端给我们的sessionid中包含了我们已经登录了的状态

那我们此时去访问add.html就是带着这个sessionid去访问的(也就是在请求包头里面都是带着我们的sessionid的),那么作为一个登录过的用户,当然就可以正常地添加文章了

根据以上原理,我们还可以设置更加精细的操作,比如设置只有管理员才能添加文章,普通用户只能读等等,这里就不再演示了。

3、安全漏洞引入

session的工作机制就决定了他存在安全漏洞,不可避免

黑客只需要拿到了服务器给你的sessionid,就可以伪造成你的身份去访问服务器,执行你能执行的权限

这个话题我们后续会继续深聊

十二、有关系统命令的执行和权限问题

1、前引

使用Apache/Nginx去运行php当中的用来执行系统命令的代码的时候,通常我们的权限是很低的(像在linux中运行Apache,我们默认去执行代码的用户是daemon;运行Nginx的话,用户是www,都是最低权限的用户)

这也就导致了我们写出来的命令很多时候不能得到有效的执行

2、解决上述问题

 方法一:

 将使用Apache/Nginx的用户更改为root

 也就是修改对应的配置文件

 Apache:

 

 Nginx:

 

 修改完配置文件之后记得重启服务,否则更改不会生效

 方法二:

 root授予用户执行某些指令的权限,然后用户通过在指令前添加sudo来正常执行指令

 以linux为例子:

 在root用户下输入visudo或者是vi /etc/sudoers来修改文件(在文件最后添加)

 例子:

jack ALL=(ALL) NOPASSWD:ALL
jack ALL=(root) NOPASSWD:/usr/bin/firewall-cmd,/usr/bin/systemctl
#解释:
#账户(给谁授予权限) 从哪台机器来的=(打算模拟的账号)
#NOPASSWD表示执行赋予指令的时候不需要输入密码
#NOPASSWD:可以执行的指令(要求必须写指令完整的路径)(ALL表示能执行所有的指令)
#ALL太不安全,通常采用白名单的方式,也就是类似于第二条例子
3、命令还是代码?

无论是php还是其他语言,我们能用代码层面解决的事情就少用命令层面的来解决

因为代码在不同的操作系统具有通用性,但是对于命令,你要是切换一下操作系统,比如从win到linux,那么他们的命令是截然不同的,你修改的内容就会增大。

十二、文件的读写操作

1、文件的读写步骤

 打开文件:fopen

 读写文件:fgets , fwrite

 关闭文件:fclose

2、基本操作

模式

描述

r

只读。在文件的开头开始。

r+

读/写。在文件的开头开始。

w

只写。打开并清空文件的内容;如果文件不存在,则创建新文件。

w+

读/写。打开并清空文件的内容;如果文件不存在,则创建新文件。

a

追加。打开并向文件末尾进行写操作,如果文件不存在,则创建新文件。

a+

读/追加。通过向文件末尾写内容,来保持文件内容。

x

只写。创建新文件。如果文件已存在,则返回 FALSE 和一个错误。

x+

读/写。创建新文件。如果文件已存在,则返回 FALSE 和一个错误。

附加函数:

(1)判断文件是否已经到达末尾:feof($fp)

(2)一次性将文件所有内容读出:file_get_contents($filename) 注意:里面的参数不是文件指针而是只要指定文件名即可

(3)利用file_get_contents() 还可以发送GET请求

(4)使用file_put_contents一次性写入文件

(5)获取当前文件指针所在位置:ftell

(6)直接将文件指针指向某个位置:fseek

3、读取文件的例子

(1)最基本的按行循环读取文件:

<?php
    $fp = fopen("/Users/username/Desktop/test/information.txt","r");  //可绝对路径也可相对路径
    //利用feof函数判断文件是否已经读到最后一行
    while(!feof($fp)){
        $line = fgets($fp); //默认读取一行,因此需要用循环来实现读取整个文件
        //之前说过文件中的换行\n不能被浏览器执行,所以需要将换行符替换成<br>才能体现出换行效果
        $line = str_replace("\n","<br>",$line);
        echo $line;
    }
    fclose($fp);
?>

结果:

(2)利用file_get_contents去直接读取文件内容(适用于文件内容较小的情况):

<?php
    //直接使用文件名,而不用去打开文件
    $content = file_get_contents("/Users/username/Desktop/test/information.txt");

    $content = str_replace("\n","<br>",$content);
    $content = iconv("GBK","UTF-8",$content); //防止出现中文乱码
    echo $content;
?>

(3)利用file_get_contents() 发送GET请求访问网页

<?php
    $content = file_get_contents("http://www.baidu.com/");
    echo $content;
?>

 思考:既然能发送get请求,是不是也可以实现下载一个文件、一张图片呢?

(4)实时地去读取文件

 <?php
        set_time_limit(0); //这种一直在运行的,若不设置时间,那就默认三十秒后停止
        $pos = 0; //设置读取位置的初值,0就是从头开始
        //由于需要实时地读取文件,所以我们需要一个死循环
        while(true){
            $fp = fopen("/Users/username/Desktop/test/information.txt","r");
            fseek($fp,$pos); //让文件指针定位到之前读的位置
            //开始读取现在的文件中内容直到读完
            while($line = fgets($fp)){
                echo $line."<br>";
            }
            $pos = ftell($fp); //记录文件读完时的指针位置
            fclose($fp);
            //睡几秒的固定搭配,否则影响输出,前面讲循环的时候讲过
          ob_flush();
            flush();
            sleep(2);
        }
    ?>
4、写入文件的例子
<?php
    $fp = fopen("/Users/username/Desktop/test/information.txt","a");
    fwrite($fp,"\nthisi is a test code");
    fclose($fp);
?>

使用file_put_contents一次性写入文件:

<?php
    $inPut = iconv("UTF-8","GBK","\n你好,这里是一段插入的文字"); //编码与要写入的文件的编码统一
    file_put_contents("/Users/username/Desktop/test/information.txt",$inPut,FILE_APPEND);
    //FILE_APPEND这个参数的意思就是采用追加的方式写入
?>

读取一个CSV文件(逗号分隔符),并且解析为二维数组:

CSV文件他用文本编辑器打开就是:

username,password,role
zyf,123456,admin
fff,456789,user

但是他本质上就是一个二维表,我们可以用excel表打开它来看一下结构:

username

password

role

zyf

123456

admin

fff

456789

user

但是我们在读取该文件内容的时候,还是我们文本编辑器里面的样子

我们为了体现他的结构可以将它存到二维数组里面

    

<?php
           //读取文件全部内容
        $content = file_get_contents("/Users/username/Desktop/test/test.csv");
              //将文件内容按行拆分,装入数组
        $rows = explode("\n",$content);
              //此时数组内容如下(还仅仅是一个一维数组)
        //Array ( [0] => username,password,role [1] => zyf,123456,admin [2] => fff,456789,user )
        $num = 1; //为了不读取第一行
        $line = array(); //先设置一个空数组,用来后面装入
        foreach($rows as $row){
          //这个if判断就是为了不读入第一行
            if($num == 1){
                $num++;
                continue;
            }
          //将读出来的一维数组的每一行都以逗号为分隔符拆散成数组
            $temp = explode(",",$row);
          //将数组存入空数组中,我们说过数组嵌套数组变成二维数组,数组嵌套二维数组变成三维数组……
            array_push($line,$temp);
        }
        print_r($line);
    ?>

结果就是我们想要的二维数组了

Array ( [0] => Array ( [0] => zyf [1] => 123456 [2] => admin ) [1] => Array ( [0] => fff [1] => 456789 [2] => user ) )

十三、JSON

1、前引

JSON即“JavaScript Object Notation”的简写,中文名称为“JS对象标记”是一种轻量级的数据交换格式,属于JS的一个子集。简洁和清晰的层次结构使得JSON成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成,并有效提升网络传输的效率 ,是目前在互联网上进行数据传输的重要手段。

2、数据的展现和存在形式

(1)数组

 包括:

 索引数组:适用于大多数的编程语言

 PHP的关联数组(Java中叫HashMap、Python中叫字典、JS中叫对象)

(2)CSV

 纯文本型的数据,带特定的格式(通过逗号分隔)(通过逗号划分成行和列)

(3)XML(eXtensible Markup Language 可扩展标记语言)

 与HTML格式完全一致,但是HTML是预先设定好的标签和属性用于网页的展现

 但是XML标签是自定义的,用于存储数据(类似于数据库中行和列这样的表现形式)

(4)JSON(JavaScript Object Notation)

(5)YAML:也是一种标记语言,通常用于服务器端或应用系统的配置文件

3、什么样的格式是JSON?

首先,需要要认识JS中的数组和对象

数组(用[ ]包裹):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSON</title>
    <script>
        function json(){
            var users = ["张三","李四","王五","赵六"]; //数组的形式[]
            //遍历数组
            for(var i=0;i<users.length;i++){
                document.write(users[i]+"<br/>");
            }
        }
    </script>
</head>
<body onload="json()">
   
</body>
</html>

对象(也就是php里面的关联数组)(用{ }包裹):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSON</title>
    <script>
        function json(){
            var user1 = {"name":"张三","sex":"男","age":"20","phone":"18069060000"}; //对象的形式{}
            //对象的输出
            document.write(user1.name + '<br/>');
            //或者
            document.write(user1['name']);
        }
    </script>
</head>
<body onload="json()">
   
</body>
</html>

那么对象和数组的组合使用就是JSON格式,比如:

(1)数组里面放对象

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSON</title>
    <script>
        function json(){
            var user1 = {"name":"张三","sex":"男","age":"20","phone":"18069060000"};
            var user2 = {"name":"李四","sex":"女","age":"21","phone":"18069990000"};
            var users = [user1,user2];
            //也可以写成下述格式,与上面一条等价,且在真实环境中更为常见
            var users = [{"name":"张三","sex":"男","age":"20","phone":"18069060000"},{"name":"李四","sex":"女","age":"21","phone":"18069990000"}];
                  //运行时选择一种方式即可,两种放一起会发生歧义
            //数据的输出
            document.write(users[0].name); //张三
                  //也可以写成
                  document.write(users[0]['name']); //张三
            document.write(users[1].name); //李四
        }
    </script>
</head>
<body onload="json()">
   
</body>
</html>

(2)对象里面放数组

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSON</title>
    <script>
        function json(){
            var users = {user1:["张三","男","20","18069060000"],user2:["李四","女","21","18069990000"]};
            //数据的输出
             document.write(users.user1[0]); //张三
             //等价写法
             document.write(users['user1'][0]); //张三
        }
    </script>
</head>
<body onload="json()">
   
</body>
</html>

(3)各种符合格式的嵌套都行:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSON</title>
    <script>
        function json(){
            var users = {user1:["张三","男","20",{'phone':'18069060000'}],user2:["李四","女","21","18069990000"]};
            //输出张三的电话号码
            document.write(users.user1[3].phone);
                  //等价写法
                  document.write(users['user1'][3]['phone']);
        }
    </script>
</head>
<body onload="json()">
   
</body>
</html>
4、JSON的序列化与反序列化

上一条笔记中我们已经知道了JSON格式了

那么,将一个数组/对象转换成字符串(JSON格式),这样的一个操作就叫做JSON序列化

反之,将字符串(JSON格式)转换成数组/对象,这样的操作就是JSON反序列化

注:

统一解释一下上面的数组/对象的意思:
    数组:索引数组(任何语言)
    对象:php中的关联数组、Python中的字典、Java中的HashMap……

相关代码:

 

 <?php
      require_once("common.php"); //引入含有打开数据库函数的文件
      $conn = connect_Db(); //获得返回指针
          
      $sql = "select articleid,author,viewcount from article";
      $result = mysqli_query($conn,$sql);
      $array = mysqli_fetch_all($result,MYSQLI_ASSOC); //将查询结果存入到一个关联数组当中
     
           $jresult = json_encode($array); //序列化,该函数其中的变量需要是一个数组
      echo $jresult; //输出序列化数据
   
           mysqli_close($conn);
  ?>

输出结果:

[{"articleid":"1","author":"zyf","viewcount":"4"},{"articleid":"4","author":"zyf","viewcount":"0"},{"articleid":"5","author":"zyf","viewcount":"0"},{"articleid":"6","author":"zyf","viewcount":"0"}]

很明显的一个JSON格式([ ]中包裹着{ }也就是数组中包裹着对象的形式)

可以对比一下没有序列化之前$array数组的信息:

Array ( [0] => Array ( [articleid] => 1 [author] => zyf [viewcount] => 4 ) [1] => Array ( [articleid] => 4 [author] => zyf [viewcount] => 0 ) [2] => Array ( [articleid] => 5 [author] => zyf [viewcount] => 0 ) [3] => Array ( [articleid] => 6 [author] => zyf [viewcount] => 0 ) )

这也就印证了我们开头说的JSON序列化是将数组/对象转换成字符串(JSON格式)的一个过程

那么对应的我们还可以执行反序列化代码:

  

  <?php
      $content = '[{"articleid":"1","author":"zyf","viewcount":"4"},                          {"articleid":"4","author":"zyf","viewcount":"0"},{"articleid":"5","author":"zyf","viewcount":"0"},{"articleid":"6","author":"zyf","viewcount":"0"}]';
      $array = json_decode($content);
      print_r($array);
  ?>

得到的输出内容就是:

Array ( [0] => stdClass Object ( [articleid] => 1 [author] => zyf [viewcount] => 4 ) [1] => stdClass Object ( [articleid] => 4 [author] => zyf [viewcount] => 0 ) [2] => stdClass Object ( [articleid] => 5 [author] => zyf [viewcount] => 0 ) [3] => stdClass Object ( [articleid] => 6 [author] => zyf [viewcount] => 0 ) )

5、将JSON数据交给前端的时候需要注意什么?

我们不妨写一下测试代码

后端(json-back.php)

<?php
    include_once("common.php");
    $conn = connect_Db();
    $sql = "select articleid,author,viewcount from article";
    $result = mysqli_query($conn,$sql);
    $array = mysqli_fetch_all($result,MYSQLI_ASSOC);

    $jresult = json_encode($array);
    echo $jresult; //将JSON序列化后的数据传回给前端
    mysqli_close($conn);
?>

前端(json-former.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test For JSON</title>
    <script type="text/javascript" src="jquery-3.4.1.min.js"></script> <!-- 使用AJAX传输 -->
    <script>
        //利用匿名函数,实现当访问时就执行
        window.onload = function(){
          //由于是测试我们不需要网后台传输数据,直接使用AJAX固定用法但是不传参数即可
            $.get('../php/json-back.php',function(data){
               window.alert(data);
            });
        }
    </script>
</head>
<body>
   
</body>
</html>

我们去访问前端页面

当前端页面请求后端数据的时候,后端返回给前端的数据是文本格式的(text/html)

这也就意味着如果我们要在前端的JS代码中正常使用后端传回来的数据,我们就需要去调整数据格式

因为文本在JS看来就是个文本信息,并不是JSON格式数据

但是,传回来的格式在不同的情况下是不一样的,我们在实现的时候可以先行检验一下,如果格式已经是application/json了,那就不需要额外的转换操作了

转换的方法:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test For JSON</title>
    <script type="text/javascript" src="jquery-3.4.1.min.js"></script>
    <script>
        window.onload = function(){
            $.get('../php/json-back.php',function(data){
                //使用eval函数,data如果是正确的代码格式,即使是文本/字符串表示的也能照常执行他表示的代码
              //在这里,虽然data是文本信息,但是文本信息描述的是正确的JSON格式
              //经过eval之后就当成正常代码执行了,所以得到的也就是JSON格式的数据
              var content = eval(data);
                document.write(content[0].author);
            });
        }
    </script>
</head>
<body>
   
</body>
</html>

十四、XML和XPath

1、基本定义

这个在上面的“JS定位”部分已经写过了,可以翻看上面的笔记

补充(下面会经常提到):

什么是节点?
如果你定位到了标签,那么此时的节点就是标签;如果你定位到了属性,那么此时的节点就是属性……

2、基本操作

先写一个xml文件(student.xml)

<?xml version="1.0" encoding="UTF-8"?>
<class id="ZYFEDU">
    <student sequence="1">
        <name>fff</name>
        <sex>男</sex>
        <age>22</age>
        <degree>本科</degree>
        <school>宁波大学</school>
    </student>
    <student sequence="2">
        <name>zzz</name>
        <sex>男</sex>
        <age>23</age>
        <degree>研究生</degree>
        <school>电子科技大学</school>
    </student>
    <student sequence="3">
        <name>yyy</name>
        <sex>男</sex>
        <age>21</age>
        <degree>博士</degree>
        <school>南京航空航天大学</school>
    </student>
</class>

利用php去获取里面的一些信息:

(1)获取标签名、获取标签里面的属性名/属性值
<?php
    $doc = new DOMDocument(); //DOMDocument()是一个类,$doc就是类的实例即对象
    $doc->load('../xml/student.xml'); //调用类里面的方法load,作用是引用一个xml文件

    //getElementsByTagName():根据标签名去返回数据
    //返回的类型是数组(因为同一个标签名可能在多个地方都使用到)
       //数组当中存着的都是DOMDocument这个类的对象(后续这样的类似的写法返回的都是这种对象数组,不再提醒)
    $nodes = $doc->getElementsByTagName('class');


    //item()就类似于索引数组的下标选择
       //在这item(0)就是选择第一个使用class标签的地方,item(1)就是选择第二个使用class标签的地方……
    //nodeName是获取节点名称,nodeValue是获取节点值
    //什么是节点?如果你定位到了标签,那么此时的节点就是标签;如果你定位到了属性,那么此时的节点就是属性……
    echo $nodes->item(0)->nodeName . "<br>"; //定位到了第一个class标签,输出节点名称,也就是class
    echo $nodes->item(0)->attributes->item(0)->nodeName."<br>"; //定位到了第一个class标签下的第一个属性,输出节点名称,也就是id
    echo $nodes->item(0)->attributes->item(0)->nodeValue."<br>"; //定位到了第一个class标签下的第一个属性,输出节点值,也就是ZYFEDU
?>
(2)读取第二个学生的所有信息
<?php
    $doc = new DOMDocument();
    $doc->load('../xml/student.xml');

    $nodes = $doc->getElementsByTagName('student'); //根据student标签获取数据,并存入数组
    $contents = $nodes->item(1)->childNodes; //选中第二个使用student标签中的所有子节点
    foreach($contents as $content){
        echo $content->nodeValue."<br>"; //输出所有节点的值
    }
?>
(3)读取所有学生的姓名
<?php
    $doc = new DOMDocument();
    $doc->load('../xml/student.xml');

    $nodes = $doc->getElementsByTagName('name');
    foreach($nodes as $node){
        echo $node->nodeValue."<br>";
    }
?>
(4)将XML文件读取为PHP的二维数组

首先熟悉一下一个用法:

<?php
    $doc = new DOMDocument();
    $doc->load('../xml/student.xml');

    $nodes = $doc->getElementsByTagName("student");  
   
    foreach($nodes as $key => $value){
        echo $key."<br>";
    }

?>

输出结果:

0
1
2
Array ( )

//上面$nodes就和下面的格式很相似(不等同):

$nodes = array($student1,$student2,$student3); //其中student又是一个关联数组

所以,foreach($nodes as $key => $value)这样的写法,$key的值明显就是为了确定是哪个student的,也就是0,1,2……

因此,这个$key可以用来作为我们的二维数组的下标使用

至于$value,就是我们定位到的每个student标签的对象

所以要将数组存入二维数组我们可以:

<?php
    $doc = new DOMDocument();
    $doc->load('../xml/student.xml');

    $nodes = $doc->getElementsByTagName("student");
    $students = array(); //设定一个空数组   
   
   
    foreach($nodes as $key => $value){
        $students[$key]['name'] = $value->getElementsByTagName('name')->item(0)->nodeValue;
        $students[$key]['sex'] = $value->getElementsByTagName('sex')->item(0)->nodeValue;
        $students[$key]['age'] = $value->getElementsByTagName('age')->item(0)->nodeValue;
        $students[$key]['degree'] = $value->getElementsByTagName('degree')->item(0)->nodeValue;
        $students[$key]['school'] = $value->getElementsByTagName('school')->item(0)->nodeValue;
    }
    print_r($students);

?>

结果:

Array ( [0] => Array ( [name] => fff [sex] => [age] => 22 [degree] => 本科 [school] => 宁波大学 ) [1] => Array ( [name] => zzz [sex] => [age] => 23 [degree] => 研究生 [school] => 电子科技大学 ) [2] => Array ( [name] => yyy [sex] => [age] => 21 [degree] => 博士 [school] => 南京航空航天大学 ) )

当然我们还有简便一点的写法:

<?php
    $doc = new DOMDocument();
    $doc->load('../xml/student.xml');

    $nodes = $doc->getElementsByTagName("student");
    $students = array(); //设定一个空数组   
    $tags = array('name','sex','age','degree','school'); //设置个标签数组

    foreach($nodes as $key => $value){
        foreach($tags as $tag){
            $students[$key][$tag] = $value->getElementsByTagName($tag)->item(0)->nodeValue;
        }
    }
    print_r($students);

?>
(5)childNodes这个属性存在的问题

比方说我们读取学生三所有的信息

<?php
    $doc = new DOMDocument();
    $doc->load("../xml/student.xml");

    $childs = $doc->getElementsByTagName('student')->item(2)->childNodes;
    foreach($childs as $child){
        echo $child->nodeValue."<br>";
    }

?>

结果输出:


yyy



21

博士

南京航空航天大学

我们可以看到这些空行并不是<br>造成的效果

这是因为childNodes他会把空格也当成一个子节点,这也就造成了我们输出的时候会有空行的结果

解决方法:

<?php
    $doc = new DOMDocument();
    $doc->preserveWhiteSpace = false; //不保留空白节点(默认是true)
       //注意填写位置,需要在引入文件之前
   
    $doc->load("../xml/student.xml");

    $childs = $doc->getElementsByTagName('student')->item(2)->childNodes;
    foreach($childs as $child){
        echo $child->nodeValue."<br>";
    }

?>

结果:

yyy

21
博士
南京航空航天大学

(6)修改节点的值
<?php
    $doc = new DOMDocument();
    $doc->preserveWhiteSpace = false; //不保留空白节点(默认是true)
    $doc->formatOutput = true; //格式化输出,在使用保存功能的时候才有效果
   
    $doc->load("../xml/student.xml");
   
    //修改第三个学生的sex和degree
    $doc->getElementsByTagName('student')->item(2)->childNodes->item(1)->nodeValue = "女";
    $doc->getElementsByTagName('student')->item(2)->childNodes->item(3)->nodeValue = "本科";
   
    //将修改结果保存到文件
    $doc->save('../xml/student.xml');
?>
(7)删除一个节点
<?php
    $doc = new DOMDocument();
    $doc->preserveWhiteSpace = false; //不保留空白节点(默认是true)
    $doc->formatOutput = true; //格式化输出,在使用保存功能的时候才有效果
   
    $doc->load("../xml/student.xml");

    //方法一:先找到父节点,然后再找到要删除的子节点,调用父节点的removeChild方法进行删除
    $father = $doc->getElementsByTagName('student')->item(2);
    $child = $doc->getElementsByTagName('degree')->item(2);
    $father->removeChild($child);

    //方法二:先找到要删除的子节点,然后找到它的父节点,然后调用父节点的removeChild方法进行删除
    $child = $doc->getElementsByTagName('degree')->item(2);
    $father = $child->parentNode;
    $father->removeChild($child);
   
    //将修改结果保存到文件
    $doc->save('../xml/student.xml');
?>
(8)将数组写入XML文件
<?php
    $doc = new DOMDocument('1.0','utf8'); //设定版本与格式避免乱码
    $doc->preserveWhiteSpace = false; //不保留空白节点(默认是true)
    $doc->formatOutput = true; //格式化输出,在使用保存功能的时候才有效果
   
   
    //设置学生数组,并将学生数组倒入到XML文件当中
    $student01 = array("id"=>"ZYFEDU01","name"=>"fff","sex"=>"boy","age"=>"22","degree"=>"本科","school"=>"宁波大学");
    $student02 = array("id"=>"ZYFEDU02","name"=>"zzz","sex"=>"boy","age"=>"23","degree"=>"研究生","school"=>"电子科技大学");
    $student03 = array("id"=>"ZYFEDU03","name"=>"yyy","sex"=>"gril","age"=>"21","degree"=>"博士","school"=>"北京大学");
    $students = array($student01,$student02,$student03);

    //创建根节点class,并设置id属性
    $class = $doc->createElement("class"); //创建class节点(创建)
    $class->setAttribute('id','ZYFEDU'); //为class节点设置属性名(id),并赋予属性值(ZYFEDU)
    $doc->appendChild($class); //把class节点放到哪个节点里面(定位)

    foreach($students as $index=>$student){
        $nodeStudent = $doc->createElement("student");  //(创建)
        $nodeStudent->setAttribute("sequence",$index+1);
        $class->appendChild($nodeStudent); //将studen节点放到class节点里面,注意写法(与上面的class做对比)(定位)
        foreach($student as $key=>$value){
            $newnode = $doc->createElement($key); //(创建)
            $text = $doc->createTextNode($value); //设定节点值(创建)
            $nodeStudent->appendChild($newnode);//(定位)
            $newnode->appendChild($text); //将节点值放到节点里面 (定位)
        }
    }


    $doc->save('../xml/student_new.xml');     //将修改结果保存到文件
?>

最终的效果(student_new.xml文件内):

<?xml version="1.0" encoding="utf8"?>
<class id="ZYFEDU">
  <student sequence="1">
    <id>ZYFEDU01</id>
    <name>fff</name>
    <sex>boy</sex>
    <age>22</age>
    <degree>本科</degree>
    <school>宁波大学</school>
  </student>
  <student sequence="2">
    <id>ZYFEDU02</id>
    <name>zzz</name>
    <sex>boy</sex>
    <age>23</age>
    <degree>研究生</degree>
    <school>电子科技大学</school>
  </student>
  <student sequence="3">
    <id>ZYFEDU03</id>
    <name>yyy</name>
    <sex>gril</sex>
    <age>21</age>
    <degree>博士</degree>
    <school>北京大学</school>
  </student>
</class>

可以看到上面的插入过程,简单而言就是“创建+定位”:

核心部分:
创建节点/创建节点值
定位创建好的节点/定位创建好的节点值

额外的:
还可以设定节点属性值

3、DOMDocument的属性和方法

属性:

Attributes 存储节点的属性列表(只读)
childNodes 存储节点的子节点列表(只读)
dataType 返回此节点的数据类型
Definition DTDXML模式给出的节点的定义(只读)
Doctype 指定文档类型节点(只读)
documentElement 返回文档的根元素(可读写)
firstChild 返回当前节点的第一个子节点(只读)
Implementation 返回XMLDoMImplementation对象
lastChild 返回当前节点最后一个子节点(只读)
nextSibling 返回当前节点的下一个兄弟节点(只读)
nodeName 返回节点的名字(只读)
nodeTye 返回节点的类型(只读)
nodeTypedvalue 存储节点值(可读写)
nodeValue 返回节点的文本(可读写)
ownerDocument 返回包含此节点的根文档(只读)
parentNode 返回父节点(只读)
Parsed 返回此节点及其子节点是否已经被解析(只读)
Prefix 返回名称空间前缀(只读)
preserveWhiteSpace 指定是否保留空白(可读写)
previoussibling 返回此节点的前一个兄弟节点(只读)
Text 返回此节点及其后代的文本内容(可读写)
url 返回最近载入的XML文档的URL(只读)
xml 返回节点及其后代的XML表示(只读)

方法:

appendchild 为当前节点添加一个新的子节点,放在最后的子节点后
cloneNode 返回当前节点的拷贝
createAttribute 创建新的属性
createCDATASection 创建包括给定数据的CDATA
createComment 创建一个注释节点
createDocumentFragment 创建DocumentFragment对象
createElement 创建一个元素节点
createEntityReference 创建EntityReference对象
createNode 创建给定类型,名字和命名空间的节点
createPorcessingInstruction 创建操作指令节点
createTextNode 创建包括给定数据的文本节点
getElementsByTagName 返回指定名字的元素集合
hasChildNodes 返回当前节点是否有子节点insertBefore在指定节点前插入子节点
Load 导入指定位置的XML文档
loadXML 导入指定字符串的XML文档
removeChild 从子结点列表中删除指定的子节点
replaceChild 从子节点列表中替换指定的子节点
Save XML文件存到指定节点
selectNodes 对节点进行指定的匹配,并返回匹配节点列表
selectSingleNode 对节点进行指定的匹配,并返回第一个匹配节点
setAttribute 对节点设需属性值
transformNode 使用指定的样式表对节点及其后代进行转换
transformNodeToobject 使用指定的样式表将节点及其后代转换为对象

4、XPath的元素定位

xml文件还是之前的student.xml

<?xml version="1.0" encoding="UTF-8"?>
<class id="ZYFEDU">
  <student sequence="1">
    <name>fff</name>
    <sex>男</sex>
    <age>22</age>
    <degree>本科</degree>
    <school>宁波大学</school>
  </student>
  <student sequence="2">
    <name>zzz</name>
    <sex>男</sex>
    <age>23</age>
    <degree>研究生</degree>
    <school>电子科技大学</school>
  </student>
  <student sequence="3">
    <name>yyy</name>
    <sex>女</sex>
    <age>21</age>
    <degree>博士</degree>
    <school>西安电子科技大学</school>
  </student>
</class>

精确匹配示例:

<?php
    $doc = new DOMDocument();
    $doc->preserveWhiteSpace = false;
    $doc->load("../xml/student.xml");
    $xpath = new DOMXPath($doc); //实例化XPath类

       //定位到class下属性sequence为1的student下的school
    $expression = "/class/student[@sequence='1']/school";
       //上面是使用绝对位置,下面是使用相对位置,效果等价
       $expression = "//student[@sequence='1']/school";
       //等价表述还有
       $expression = "student[1]/school"; //表示定位到第一个学生的学校(注意这里的1不是数组下标,是个数)

    $nodes = $xpath->query($expression);     //定位指定位置后返回数组
    echo $nodes->item(0)->nodeValue; //输出sequence为1的学生的学校名称
?>

结果输出:

宁波大学

模糊匹配示例:

<?php
    $doc = new DOMDocument();
    $doc->preserveWhiteSpace = false;
    $doc->load("../xml/student.xml");
    $xpath = new DOMXPath($doc); //实例化XPath类

       //使用模糊匹配,定位那些school中值含有“科技”的school节点
    $expression = "//student/school[contains(text(),'科技')]";

    $nodes = $xpath->query($expression);    //定位指定位置后返回数组
    echo $nodes->item(0)->nodeValue."<br>";  //输出第一个含有“科技”的学校名称
      echo $nodes->item(1)->nodeValue;  //输出第二个含有“科技”的学校名称
?>

输出结果:

电子科技大学
西安电子科技大学

十五、面向对象

1、什么是面向过程?

 面向过程的编程的基本构成便是“过程”,过程实现的方式就是“函数”,我们通过不同函数来实现不同的功能,并按照程序的执行顺序调用相应的函数,组成一个完整的可以运行的应用程序。我们可以在不同的函数当中实现不同的功能,也可以给函数传递不一样的参数来实现不同的功能,这是面向过程中模块化设计的原理

 但是面向过程只适合处理简单的任务,因为复杂任务中,势必有复杂的功能,那我们就需要设置一个一个的功能不同的函数,然后函数越来越多,代码量越来越大,最后极难维护。

 因此,我们引出了面向对象。

2、什么是面向对象?

面向对象OOP(Object-Oriented Programming):将世界万物的特性(属性)行为(方法)用编程语言进行抽象表达,具备比面向过程更加高级的特性。

(1)何为类?

class,类就是“一类事物的统称”,比如“防盗门”、“汽车门”、“卷帘门”是一类东西;“男人”、“女人”、“小孩”是一类东西。

能划分到一类的东西就一定会有相同的属性和方法。

例如,门这一类

门的属性:高、宽、厚度、材质、颜色……

门的方法:打开、关闭、上锁……

(2)何为实例?

instance,实例就是某一类的具体化,比如门是一个类,那么门的具体化就可以是我家里面的卧室门或者银行入口处的防盗门等

(3)何为对象?

往大了的说,世界万物皆对象,类可以是对象,实例可以是对象

往小了说,一个变量,甚至是一个数据类型也可以视为一个对象

(4)对象有什么特性?

封装:利用访问修饰符(public、private、protected等)去限制属性和方法的作用范围,这一过程就叫做封装

继承:子类能够继承父类的属性(非私有)和方法(非私有)

多态:不同的对象通过相同的行为(方法)可以表现出不同的形式和结果

3、具体例子
(1)补充细节

首先补充一个细节点:

在PHP当中

<?php
    $name = '张三';
    function getName(){
        echo $name; //会报错(错误信息:没有声明变量$name)
    }
       getName();
?>

可以看到,在没有声明全局变量也没有传递参数给函数的情况下,函数内部的信息和外部是“绝缘”的

如果想要getName函数使用到函数外面的信息:

<?php
    //两种方法
 
    //方法一:声明全局变量
    $name = '张三';
    function getName(){
        global $name;  //声明$name是全局变量
        echo $name;
    }

    //方法二:传递参数
    $name = '张三';
    function getName($name = $name){
        echo $name;
    }
      getName();
?>
(2)面向对象基础用法
(2.1)一个简单的例子
<?php

use Dom\Notation;
    //定义类People
    class People {
        var $name = ''; //在类当中不在称之为变量,要叫类的属性
        var $age = 0;   //定义属性的时候,可以给他一个初始值
        var $addr = '';
        var $nation = '';

        //定义类的方法,默认情况下,方法的定义与函数完全一致
        function talk(){
          //在类的定义中,$this指类的具体实例
            echo "$this->name 正在说话<br>";
        }
       
        function work(){
            echo "$this->name 人正在工作<br>";
        }
       
        //定义方法的时候,传参也可以定义初始值
        function eat($type = "米饭"){
          echo "$this->name 正在吃 $type";
        }
    }

    //实例化People,并进行属性和方法的调用
    $p1 = new People();
    $p1->name = "张三";
    $p1->age = 21;
    $p1->addr = "浙江宁波";
    $p1->nation = "汉族";
    $p1->talk();

       //一旦完成类的实例化,实例与实例之间并无关系,分布于不同的内存中
    $p2 = new People();
    $p2->name = "李四";
    $p2->age = 22;
    $p2->addr = "浙江杭州";
    $p2->nation = "汉族";
    $p2->work();
?>

输出结果:
张三 正在说话
李四 正在工作

(2.2)封装

基本概念:
    1)默认情况下(在没有明确设置访问修饰符时),所有属性和方法均为public
    2public公共,类和实例都可以使用
    3private私有,在定义它的类当中可以使用,但是在其实例和其子类无法直接使用
       3.1)针对私有属性,如何在实例中对其进行修改?
           利用公有方法对其私有属性进行修改/取得属性值
    4protected,表示受到类的保护,在定义它的类当中可以使用,其实例不能直接使用,但是在其子类当中可以使用

<?php
        class People {
            public $name = ''; //使用访问修饰符的时候,就不需要var了
            var $age = 0;
            private $addr = '浙江宁波';
            var $nation = '';
           
            //由于地址是私有属性,所以实例无法直接使用
            //如果要修改地址或者获取地址信息,可以利用公有方法来获取/修改私有属性
            public function reLocate($address){
                $this->addr = $address;
                echo "您的地址已经修改成 $this->addr <br>";
            }
        }

        $p1 = new People();
        $p1->reLocate("南京");
?>

输出:

您的地址已经修改成 南京

(2.3)继承
<?php
        class People {
            public $name = '张三';
            var $age = 0;
            private $addr = '浙江宁波';
            var $nation = '';

            public function talk(){
                echo $this->name."正在说话<br>";
            }
        }
       
        //定义一个类Man继承于People,也就是说Man是People的子类
        class Man extends People{
            //子类继承了父类所有非私有的属性和方法
        }

        $p = new Man();
        echo $p->name."<br>";
        $p->talk();
?>

输出:

张三
张三正在说话

关于重写:

!!!为了更好地理解,请理解下面表述,后若引用这部分则用《规则》来表示!!!:

继承可以这么理解,子类复制了一份父类(除了private修饰的部分外的其他所有部分),然后可以进行下面四个操作:

1、未继承到的部分:不可修改(重写)、不可读

2、若继承的部分用到了未继承到的部分则问父类要(只是让你看看但不能修改)

3、继承到的部分子类可以自己任意修改(也就是重写)或者直接使用

4、子类还可以在原有的基础上自己添加属性/方法。

5、继承到的部分和新增的部分是互相独立的,两部分没有关联。(这条是为了更好的理解第二条中的“未继承到的部分”)

先讲一个特殊用法

<?php
    class People{

    }
    $p = new People();
       //虽然在People类当中没有定义属性$name
       //但是可以通过下述方法来动态添加新的属性(默认是public)
    $p->name = "test";
?>

基本用法:

<?php
        class People {
            private $name = '张三';
            var $age = 0;
            private $addr = '浙江宁波';
            var $nation = '';

            public function talk(){
                echo $this->name."正在说话<br>";
            }
        }
       
        class Man extends People{
            //子类继承自父类的方法和属性,在子类当中可以进行覆盖,也称为“重写”
           
         
          /**
             * 两种情况:
             * (1)如果属性/方法在父类中是私有的,子类首先是不会继承父类的该类属性和方法的
                             所以这种情况子类是无法重写父类的属性的
                             子类只能通过定义新的同名属性来达成类似重写的效果,也就是我们说的“新产生的”
             * (2)如果属性/方法在父类中是公用/保护的,那么子类是可以继承这些属性/方法,
                                   那么子类对其重写就意味着,对继承的属性/方法有了新的定义(也就是并非新产生的)
             * 要分清楚什么是新产生,什么是继承,好好理解上面两种情况
             * 没有继承,就不存在重写;重写了,那么前提就是继承
             */
         
            var $addr = "南京";  //重写属性addr
            //重写方法talk
          function talk(){
                echo $this->name."is talking now<br>";
            }
           
        }

        $p = new Man();
        $p->name = "张小小"; //动态添加$name属性(因为$name在父类中为private,因此没有继承给子类)
        echo $p->name."<br>";
        echo $p->addr."<br>";
        $p->talk();
?>

输出:

张小小
南京
张小小is talking now

几个细节点:

<?php
        class People {
            private $name = '张三';
            var $age = 0;
            private $addr = '浙江宁波';
            var $nation = '';

            public function talk(){
                echo $this->name."正在说话<br>";
            }
        }
       
        class Man extends People{

        }
             
        $p = new Man();
              $p->name = "张小小"; //即使动态添加属性$name
        $p->talk();  //也会输出“张三正在说话”

              //这里输出为“张三正在说话”的原因见《规则》第二条、第五条
             
?>

如何调用被protected修饰的属性/变量?

子类中能使用父类中被protected修饰的属性/方法,但是不能在子类的实例中直接使用

<?php
        class People {
            public $name = '张三';
            var $age = 0;
            protected $addr = '浙江宁波';
            var $nation = '';

            protected function eat(){
                echo "吃东西<br>";
            }
        }
       
        class Man extends People{
            public function entertainment(){
              $this->eat(); //调用继承的eat方法
                //更正统的做法
                parent::eat();
                echo "看".$this->addr."电视台<br>";
            }
        }

        $p = new Man();
        $p->entertainment();
?>

输出:

吃东西
看浙江宁波电视

(2.4)多态

在弱类型的编程语言(比如像PHP)中,并不能很好的展现多态,所以在此类语言中多态并不是非常重要

但多态在强类型的编程语言(比如像C++、C#、Java)中是非常重要的

因此在PHP当中,我们就稍微简单了解即可

<?php
    class animal{
        function can(){

        }
    }
    class cat extends animal{
        function can(){
            echo "I can climb<br>";
        }
    }
    class dog extends animal{
        function can(){
            echo "I can swim<br>";
        }
    }

    function showAbility($obj){
        $obj->can();
    }
    showAbility(new cat());
    showAbility(new dog());
?>

结果:

I can climb
I can swim

可以看到弱类型的PHP并不能很好展现多态,我们就先暂且聊到这,大概有一个多态的概念即可。

(2.5)抽象类

只要类的方法中有一个方法使用abstruct关键字定义,则该类就是抽象类,该方法就是抽象方法

抽象类的特性:

    抽象类不能被实例化,只能被继承;抽象类中可以存在非抽象方法

    抽象类中的抽象方法只能有定义(方法名,参数),不能有实现代码
   
    抽象类当中的抽象方法必须在子类中有对应的实现
   
    抽象类中可以存在属性

<?php
    abstract class animal{
        abstract function can(); //抽象方法只能有定义
    }
    class cat extends animal{
        function can(){
            echo "I can climb<br>";
        }
    }
       $c = new cat();
       echo $c->can();
?>
(2.6)接口

是特殊的抽象类

接口要求:

抽象类当中不能有其他非抽象方法存在,必须都是抽象类

抽象类中可以有属性

接口通常用于定义一些规则(因为抽象类中定义的抽象方法必须在子类中使用,也就变相定义了规则)

    ……
       ……
       ……
    //接口
       abstract class animal{
        abstract function can();
        abstract function work();
    }
    ……
    ……
    ……
(2.7)final

被final修饰的类表示该类不能被继承

 

   final class animal{
                  ……
    }
4、用面向对象的特性改造一下数据库的操作

先简单封装一个DB类

common.php

<?php
    class DB{
        //为DB设置连接的必要属性
        var $hostname = "";
        var $username = "";
        var $password = "";
        var $database = "";

        //设计一个用于连接数据库的方法
        private function create_connect(){
            $conn = mysqli_connect($this->hostname,$this->username,$this->password,$this->database) or die("数据库连接失败");
            mysqli_query($conn,"set names utf8");
            return $conn;
        }

        //设计一个查询数据库的方法
        function query($sql){
            $result = mysqli_query($this->create_connect(),$sql);
            $rows = mysqli_fetch_all($result,MYSQLI_ASSOC);
            return $rows;
        }

        //设计一个更新数据库的方法
        function modify($sql){
            $result = mysqli_query($this->create_connect(),$sql);
            if($result == 0){
                die('数据库更新失败');
            }
        }
    }
?>

测试一下基本的方法是否正常

oop-use-database.php

<?php
    require_once "common.php";
    $db = new DB();
    $db->hostname = "127.0.0.1";
    $db->username = "root";
    $db->password = "root";
    $db->database = 'book';

    //查询
    $result = $db->query("select articleid,headline,author from article");
    print_r($result);

    //更新
    $db->modify("update article set headline='《WHAT A GOOD DAY》' where articleid=6");
?>

可以发现,我们平时需要一直调用各种函数去连接/访问/查询数据库的操作,现在变得非常的简洁。

在此基础上,我们还可以进行一些优化

存在的问题:
1)每次调用querymodify方法的时候都回去调用create_connect方法去连接一次数据库,浪费资源
2)数据库打开了,但是没有关闭
 (3)类的属性是公共的,有什么方法变成私有的

问题一的解决:
将数据库连接对象$conn定义成类的属性
然后使用类的构造方法__construct():当类在进行实例化的时候会触发执行
这是一个魔术方法(一般魔术方法以两个下划线开头)(PHP内置)
魔术方法介绍:https://segmentfault.com/a/1190000007250604


问题二的解决方法:
可以利用类的析构方法可以完成自动的关闭
类的析构方法__destruct():当类的实例使用完并从内存中释放的时候,就会触发该方法

问题三的解决方法:
使用构造方法__construct()可以设置参数这么一个功能

魔术方法的好处:
减少了我们手动调用方法的次数,因为魔术方法会在某个特定的时机(根据每个魔术方法的定义)自动地执行该魔术方法
我们则可以利用这一点,设定好魔术方法的内容,然后让他在特定的时间自己触发

修改后的代码:

<?php
    class DB {
        private $hostname;
        private $username;
        private $password;
        private $database;
        private $conn = null;
             
           //传参数并设置个默认值
           //实例被创建的时候会自动调用该魔术方法来实现数据库的连接,并给$conn赋值
        function __construct($hostname="127.0.0.1", $username="root", $password="root", $database="book") {
            echo "数据库开始连接<br>";
            $this->hostname = $hostname;
            $this->username = $username;
            $this->password = $password;
            $this->database = $database;
            $this->create_connect();
        }
        private function create_connect() {
            $this->conn = mysqli_connect($this->hostname, $this->username, $this->password, $this->database);
            if (!$this->conn) {
                die("数据库连接失败:" . mysqli_connect_error());
            }
            mysqli_set_charset($this->conn, "utf8mb4");
        }

        function query($sql) {
            if (!$this->conn) {
                die("数据库未连接");
            }
            $result = mysqli_query($this->conn, $sql);
            if (!$result) {
                die("查询失败:" . mysqli_error($this->conn));
            }
            return mysqli_fetch_all($result, MYSQLI_ASSOC);
        }

        function modify($sql) {
            if (!$this->conn) {
                die("数据库未连接");
            }
            $result = mysqli_query($this->conn, $sql);
            if (!$result) {
                die("数据库更新失败:" . mysqli_error($this->conn));
            }
        }
           //当实例结束并从内存中释放的时候,会自动调用该魔术方法,去释放数据库的连接
        function __destruct() {
            echo "数据库开始关闭连接<br>";
            if ($this->conn) {
                mysqli_close($this->conn);
            }
        }
    }
?>

<?php
    require_once "common.php";
    //连接数据库,并传入参数
       $db = new DB("127.0.0.1","root","root","book");

    //查询
    $result = $db->query("select articleid,headline,author from article");
    print_r($result);
    echo "<br>";
?>

输出结果:

数据库开始连接
Array ( [0] => Array ( [articleid] => 1 [headline] => HOW TO EXPRESS MYSLEF [author] => zyf ) [1] => Array ( [articleid] => 4 [headline] => session [author] => zyf ) [2] => Array ( [articleid] => 5 [headline] => 007 [author] => zyf ) [3] => Array ( [articleid] => 6 [headline] => WHAT A GOOD DAY [author] => zyf ) )
数据库开始关闭连接

从传输结果来看,实例结束并从内存中释放的时机是整个oop-use-database.php执行完毕的时候

十六、序列化与反序列化

1、序列化

将对象转换成字符串

目的:便于存储,便于传输

只要是符合序列化规则的字符串,无论是否是由serialize函数生成的,都可以被反序列化

(1)针对数组的序列化

<?php
       $student= array('name'=>'蜡笔小新','age'=>'7','addr'=>'杭州','phone'=>'123456');
       echo serialize($student); //serialize()序列化
?>

输出:

a:4:{s:4:"name";s:12:"蜡笔小新";s:3:"age";s:1:"7";s:4:"addr";s:6:"杭州";s:5:"phone";s:6:"123456";}

解释:
a:4:{}
a表示数组类型,4表示数组有四个元素,{}内为数组的具体内容
s:4:"name";
s表示为字符串类型,4表示长度为4字节,名为“name”,注意要有分号结尾

(2)针对类的实例的序列化

<?php
    require_once "common.php";
    $db = new DB();
    echo serialize($db)."<br>";
?>

输出(仅看序列化的部分):

O:2:"DB":5:{s:12:"DBhostname";s:9:"127.0.0.1";s:12:"DBusername";s:4:"root";s:12:"DBpassword";s:4:"root";s:12:"DBdatabase";s:4:"book";s:8:"DBconn";O:6:"mysqli":19:{s:13:"affected_rows";N;s:11:"client_info";N;s:14:"client_version";N;s:13:"connect_errno";N;s:13:"connect_error";N;s:5:"errno";N;s:5:"error";N;s:10:"error_list";N;s:11:"field_count";N;s:9:"host_info";N;s:4:"info";N;s:9:"insert_id";N;s:11:"server_info";N;s:14:"server_version";N;s:4:"stat";N;s:8:"sqlstate";N;s:16:"protocol_version";N;s:9:"thread_id";N;s:13:"warning_count";N;}}

解释:
O:2:"DB":5
O表示是对象,2表示长度为2字节,名为“DB”,5表示共有五个属性

可以看到hostnameusername等属性的前面多了个DB,这是因为他们都是私有属性,所以会加上类名;如果是protected类型的会在前面加上*;如果是public类型的是不会加上额外的东西的

请注意,在s:12:"DBhostname"中,DBhostname明明就10个字节,为什么前面会用12?
这是因为,序列化当中,如果属性是被private或者protected修饰的,则会添加上两个分隔符\0(表示空字符,所以我们看不到)的,所以长度会多2个字节
具体而言:
protected修饰的属性,在序列化后属性名会变成\0*\0PropertyName
private修饰的属性,在序列化后属性名会变成\0ClassName\0PropertyName
public修饰的属性,既没有额外前缀,也没有额外分隔符

如果要序列化一个类,则最好在__sleep魔术方法中指明要序列化的类属性

序列化的过程只包含类的属性不包含类的方法

2、反序列化

将一个字符串转换成对象

(1)针对数组的反序列化
<?php
    $array = unserialize('a:4:{s:4:"name";s:12:"蜡笔小新";s:3:"age";s:1:"7";s:4:"addr";s:6:"杭州";s:5:"phone";s:6:"123456";}'); //unserialize()反序列化
    print_r($array);
?>

输出:

Array ( [name] => 蜡笔小新 [age] => 7 [addr] => 杭州 [phone] => 123456 )

(2)针对类的实例的反序列化

反序列化类的实例的过程中,会调用析构方法(__distruct( )),但是不会调用构造方法(__construct( ))

这就会导致我们的数据库连接对象$conn为null,所以我们要采用魔术方法__wakeup( )

此时如果我们手动输入序列化字符串的话,可能会出现问题:

我们先在DB类当中添加__wakeup( )魔术方法

<?php
    class DB{
        private $hostname = "127.0.0.1";
        private $username = "root";
        private $password = "root";
        private $database = "book";
        private $conn = null;

        function __construct($hostname="127.0.0.1", $username="root", $password="root", $database="book") {
            echo "数据库开始连接<br>";
            $this->hostname = $hostname;
            $this->username = $username;
            $this->password = $password;
            $this->database = $database;
            $this->create_connect();
        }
        private function create_connect() {
            $this->conn = mysqli_connect($this->hostname, $this->username, $this->password, $this->database);
            if (!$this->conn) {
                die("数据库连接失败:" . mysqli_connect_error());
            }
            mysqli_set_charset($this->conn, "utf8mb4");
        }

        function query($sql) {
            if (!$this->conn) {
                die("数据库未连接");
            }
            $result = mysqli_query($this->conn, $sql);
            if (!$result) {
                die("查询失败:" . mysqli_error($this->conn));
            }
            return mysqli_fetch_all($result, MYSQLI_ASSOC);
        }

        function modify($sql) {
            if (!$this->conn) {
                die("数据库未连接");
            }
            $result = mysqli_query($this->conn, $sql);
            if (!$result) {
                die("数据库更新失败:" . mysqli_error($this->conn));
            }
        }

        function __destruct() {
            echo "数据库开始关闭连接<br>";
            if ($this->conn) {
                mysqli_close($this->conn);
            }
        }

        //当类的实例进行序列化的时候自动调用,并且返回一个数组,其内容包括要序列化的类属性
        function __sleep(){
            echo "正在序列化<br>";
            return array("hostname","username","password","database"); 
        }

        //当类的实例在进行反序列化的时候自动调用,并且可以在该方法中定义恢复类状态的代码
        function __wakeup(){
            echo "正在进行反序列化<br>";
            $this->create_connect();  //恢复数据库的连接
        }
    }
?>

此时,若我们“手敲”序列化字符串(也就是现在另外个文件里面序列化一下类的实例,然后将输出的序列化字符串赋值):

<?php
    require_once "common.php";
    $source = 'O:2:"DB":4:{s:12:"DBhostname";s:9:"127.0.0.1";s:12:"DBusername";s:4:"root";s:12:"DBpassword";s:4:"root";s:12:"DBdatabase";s:4:"book";}';
    $db = unserialize($source);
    $result = $db->query("SELECT articleid, headline, author FROM article");
    print_r($result);

?>

此时就会出现报错信息

Fatal error: Uncaught Error: Call to a member function query() on bool in /Applications/phpstudy/WWW/php/serialize.php:16 Stack trace: #0 {main} thrown in /Applications/phpstudy/WWW/php/serialize.php on line 16

原因就是在于:

privateprotected属性的序列化格式会在属性名前加上前缀(类名或者*)和属性分隔符(\0
具体而言:
protected修饰的属性,会变成\0*\0PropertyName
private修饰的属性,会变成\0ClassName\0PropertyName

unserialize()解析字符串时,属性名必须严格匹配
也就是说,如果属性是被private修饰的话,序列化字符串中的属性名就需要展现出对应的前缀和属性分隔符

php中分隔符\0表示空字符,因为在字符串中不能直接键入\0,所以要用chr(0)来代表他

解决方法:

一、不“手打”序列化字符串

直接

$source = serialize(new DB());

这样的话系统是会帮我们自动加上前缀和分隔符的
 
注意区分:

$source = serialize(new DB());
echo $souece;

若输出$source,那么当中的\0是无法展现的,所以我们复制这串输出结果的话是不含\0分隔符的

<?php
    require_once "common.php";

    $source = serialize(new DB());
    $db = unserialize($source);
    $result = $db->query("SELECT articleid, headline, author FROM article");
    print_r($result);
    echo "<br>";

?>

二、手动添加分隔符

<?php
    require_once "common.php";
    $source = 'O:2:"DB":4:{s:12:"'.chr(0).'DB'.chr(0).'hostname";s:9:"127.0.0.1";s:12:"'.chr(0).'DB'.chr(0).'username";s:4:"root";s:12:"'.chr(0).'DB'.chr(0).'password";s:4:"root";s:12:"'.chr(0).'DB'.chr(0).'database";s:4:"book";}';
    $db = unserialize($source);
    $result = $db->query("SELECT articleid, headline, author FROM article");
    print_r($result);
    echo "<br>";
?>

也能输出结果:

正在进行反序列化
Array ( [0] => Array ( [articleid] => 1 [headline] => HOW TO EXPRESS MYSLEF [author] => zyf ) [1] => Array ( [articleid] => 4 [headline] => session [author] => zyf ) [2] => Array ( [articleid] => 5 [headline] => 007 [author] => zyf ) [3] => Array ( [articleid] => 6 [headline] => WHAT A GOOD DAY [author] => zyf ) )
数据库开始关闭连接

可以看到,如果内容一长就很容易出错,所以这种方法不太推荐(但测试漏洞的时候可能有各种各样的限制,所以这种方法还是很有理解的必要的)

三、直接改成公有属性

将DB类中的属性都改成公有的属性

这样无论你是手打还是不手打,都没有问题

代码就不演示了

3、魔术方法

__sleep( ):在类进行序列化的时候调用,并且在里面需要明确定义序列化哪些类属性,以数组形式定义和返回

在DB类当中添加该方法

  //当类的实例进行序列化的时候自动调用,并且返回一个数组,其内容包括要序列化的类属性
  function __sleep(){
      echo "正在序列化";
      return array("hostname","username","password","database");   //只需要序列化这几个类属性
  }

再次输出结果:

数据库开始连接
正在序列化O:2:"DB":4:{s:12:"DBhostname";s:9:"127.0.0.1";s:12:"DBusername";s:4:"root";s:12:"DBpassword";s:4:"root";s:12:"DBdatabase";s:4:"book";}
数据库开始关闭连接

__wakeup( ):在类的实例在进行反序列化的时候调用,并且可以在该方法中定义恢复类状态的代码

在DB类当中添加该方法:

  //当类的实例在进行反序列化的时候自动调用,并且可以在该方法中定义恢复类状态的代码
  function __wakeup(){
      echo "正在进行反序列化<br>";
      $this->create_connect(); 
  }
4、反序列化漏洞(原理部分)

(1)字符串变成对象这个反序列化过程中,如果没有对字符串进行输入检查,很有可能注入恶意代码

(2)通常情况下,反序列化漏洞并非通过黑盒测试或者盲注的方式进行探测,而是通过代码评审进行漏洞验证(针对内部系统或者开源系统)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值