刘金星个人博客

个人博客


  • 首页

  • 归档

  • 标签

  • 关于

Vue配置引入自己的包文件

发表于 2019-05-10 | 更新于 2019-08-22

#vue.config.js中配置编译node_modules中es6的包

因为公司后台项目使用前后端分离的方式,前端使用vue;
公司项目比较多,所以在开发过程中,把需要用到的公共 vue 组件和公共代码单独创建了 npm 包,因为我们包里面使用了es6的语法,在我们项目中引入公共包,编译报错,错误原因就是 js 语法报错;
找到解决方案,只要配置那个引入的包,也要使用es6的语法去编辑就好!vue.config.js 配置中添加如下如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const path = require('path')

function resolve(dir) {
return path.join(__dirname, dir)
}

module.exports = {
... // 其他配置
// 配置你的包也使用 es6 语法解析
chainWebpack: (config) => {
config.module
.rule('compile')
.test(/\.js$/)
.include
.add(resolve('node_modules/你包的名称'))
.end()
.use('babel').loader('babel-loader')
}
}

Vue实现SKU商品展示

发表于 2019-05-10 | 更新于 2019-08-22

由于公司开发了一个电商项目,涉及到前台商品属性的展示,所以百度上找了一下!找到了 周琪力写的一个算法例子,因为作者只有jQuery 实现demo, 自己仿照 demo 实现了一个 vue 的!

周琪力原文
原文的demo实现

vue实现demo展示

效果图:
这里写图片描述

附上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 实现商品属性的计算显示</title>
<style>
body {
font-size: 12px;
}
dt {
width: 100px;
text-align: right;
}
dl {
clear: both;
overflow:hidden;
}
dl.hl {
background:#ddd;
}
dt, dd {
float:left;
height: 40px;
line-height: 40px;
margin-left: 10px;
}
button {
font-size: 14px;
font-weight: bold;
width: 100px;
height: 30px;
margin: 0 10px;
}

.disabled {
color:#999;
border: 1px dashed #666;
}
.active {
color: red;
}

.top-but {
margin: 10px;
}

#skuId {
height: 24px;
font-size: 14px;
line-height: 24px;
}
</style>
</head>
<body>
<textarea id="values" style="width:600px;height:100px">
[
{ "颜色": "红", "尺码": "大", "型号": "A", "skuId": "3158055" },
{ "颜色": "白", "尺码": "大", "型号": "A", "skuId": "3158054" },
{ "颜色": "白", "尺码": "中", "型号": "B", "skuId": "3133859" },
{ "颜色": "蓝", "尺码": "小", "型号": "C", "skuId": "3516833" }
]
</textarea>
<div id="app">
<label>
默认选中 :
<input type="text" name="skuId" id="skuId" v-bind:value="skuId">
</label>
<button @click="getTextareaData" class="top-but"> 重新加载数据 </button> 当前属性ID:{{ skuId }}
<dl v-for="item, key in list.result" class="content" v-bind:class="{hl: highKeys[key]}">
<dt> {{key}} : </dt>
<dd>
<button
class="item"
v-for="value in item"
@click="handleActive(key, value)"
v-bind:class="{active: value.active, disabled: !value.active && value.disabled}"
> {{ value.name }} </button>
</dd>
</dl>
已经选择:{{ message }}
</div>
</body>
<script src="https://unpkg.com/vue "></script>
<script>
let vue = new Vue({
el: "#app",
data(){
return {
data: [],
skuId: "",
skuName: "skuId",
// 属性名称信息
keys: [],
// 数据集合{list.result list.items}
list: {},
// 分隔符
spliter: '\u2299',
result: {},
message: "",
highKeys: {},
};
},
methods: {
powerset(arr) {
let ps = [[]];
for (let i = 0; i < arr.length; i++) {
for (let j = 0, len = ps.length; j < len; j++) {
ps.push(ps[j].concat(arr[i]));
}
}

return ps;
},

/**
* 初始化数据
* @return
*/
initData() {
this.result = {};
this.keys = this.getAllKeys();
for (let i = 0; i < this.keys.length; i ++) {
this.highKeys[this.keys[i]] = false;
}

this.list = this.combineAttr(this.data, this.keys);
this.initSeleted(this.skuId);
this.buildResult(this.list.items)
this.updateStatus(this.getSelectedItem());
this.showResult();
},

/**
* 获取输入表单中的数据进行初始化
* @return
*/
getTextareaData() {
let data = document.getElementById('values').value;
let skuId = document.getElementById("skuId").value;

try {
this.data = JSON.parse(data);
let isHas = false;
for (let i = 0; i < this.data.length; i ++) {
if (skuId == this.data[i][this.skuName]) {
isHas = true;
break
}
}

this.skuId = isHas ? skuId : this.data[0][this.skuName];
this.initData();
} catch (e) {
this.data = [];
}
},

/**
* 正常属性点击
*/
handleNormalClick(key, value) {
for (let i in this.list.result[key]) {
if (i != value.name) {
this.list.result[key][i].active = false;
} else {
this.list.result[key][i].active = true;
}
}
},

/**
* 无效属性点击
*/
handleDisableClick(key, value) {
this.list.result[key][value.name]["disabled"] = false;
// 清空高亮行的已选属性状态(因为更新的时候默认会跳过已选状态)
for (let i in this.list.result) {
if (i != key) {
for (let x in this.list.result[i]) {
this.list.result[i][x].active = false;
}
}
}

this.updateStatus(this.getSelectedItem());
},

/**
* 高亮行
*/
highAttributes: function() {
for (let key in this.list.result) {
this.highKeys[key] = true;
for (let attr in this.list.result[key]) {
if (this.list.result[key][attr].active === true) {
this.highKeys[key] = false;
break;
}
}
}
},

/**
* 点击事件处理
* @param key 点击的行
* @param value 点击的按钮的数据
*/
handleActive: function(key, value) {
if (value.active == true) {
return false;
}

this.handleNormalClick(key, value);
if (value.disabled === true) {
this.handleDisableClick(key, value);
}

this.updateStatus(this.getSelectedItem());
this.highAttributes();
this.showResult();
},

/**
* 计算属性
* @param {[type]} data [description]
* @param {[type]} keys [description]
* @return {[type]} [description]
*/
combineAttr(data, keys) {
let allKeys = []
let result = {}

for (let i = 0; i < data.length; i++) {
let item = data[i]
let values = []

for (let j = 0; j < keys.length; j++) {
let key = keys[j]
if (!result[key]) {
result[key] = {};
}

if (!result[key][item[key]]) {
result[key][item[key]] = {"name": item[key], "active": false, "disabled": true};
}

values.push(item[key]);
}

allKeys.push({
path: values.join(this.spliter),
sku: item['skuId']
});
}

return {
result: result,
items: allKeys
}
},

/**
* 获取所有属性
* @return {[type]} [description]
*/
getAllKeys() {
let arrKeys = [];
for (let attribute in this.data[0]) {
if (!this.data[0].hasOwnProperty(attribute)) {
continue;
}

if (attribute !== this.skuName) {
arrKeys.push(attribute);
}
}

return arrKeys;
},

getAttruites(arr) {
let result = []
for (let i = 0; i < arr.length; i++) {
result.push(arr[i].path)
}

return result
},

/**
* 生成所有子集是否可选、库存状态 map
*/
buildResult(items) {
let allKeys = this.getAttruites(items)

for (let i = 0; i < allKeys.length; i++) {
let curr = allKeys[i];
let sku = items[i].sku;
let values = curr.split(this.spliter);
let allSets = this.powerset(values);

// 每个组合的子集
for (let j = 0; j < allSets.length; j++) {
let set = allSets[j]
let key = set.join(this.spliter)

if (this.result[key]) {
this.result[key].skus.push(sku)
} else {
this.result[key] = {
skus: [sku]
}
}
}
}
},

/**
* 获取选中的信息
* @return Array
*/
getSelectedItem() {
let result = [];
for (let attr in this.list.result) {
let attributeName = '';
for (let attribute in this.list.result[attr]) {
if (this.list.result[attr][attribute].active === true) {
attributeName = attribute;
}
}

result.push(attributeName);
}

return result
},

/**
* 更新所有属性状态
*/
updateStatus(selected) {
for (let i = 0; i < this.keys.length; i++) {
let key = this.keys[i],
data = this.list.result[key],
hasActive = !!selected[i],
copy = selected.slice();

for (let j in data) {
let item = data[j]["name"];
if (selected[i] == item) {
continue
}

copy[i] = item
let curr = this.trimSpliter(copy.join(this.spliter), this.spliter);
this.list.result[key][j]["disabled"] = this.result[curr] ? false : true;
}
}
},

trimSpliter(str, spliter) {
// ⊙abc⊙ => abc
// ⊙a⊙⊙b⊙c⊙ => a⊙b⊙c
let reLeft = new RegExp('^' + spliter + '+', 'g');
let reRight = new RegExp(spliter + '+$', 'g');
let reSpliter = new RegExp(spliter + '+', 'g');
return str.replace(reLeft, '')
.replace(reRight, '')
.replace(reSpliter, spliter)
},

/**
* 初始化选中
* @param mixed|Int|String skuId 需要选中的skuId
* @return {[type]} [description]
*/
initSeleted(skuId) {
for (let i in this.data) {
if (this.data[i][this.skuName] == skuId) {
for (let x in this.data[i]) {
if (x !== this.skuName) {
this.list.result[x][this.data[i][x]].active = true;
}
}
break;
}
}
},

/**
* 显示选中的信息
* @return
*/
showResult() {
let result = this.getSelectedItem()
let s = []
for (let i = 0; i < result.length; i++) {
let item = result[i];
if (!!item) {
s.push(item)
}
}

if (s.length == this.keys.length) {
let curr = this.result[s.join(this.spliter)]
if (curr) {
s = s.concat(curr.skus)
this.skuId = curr.skus[0];
}

this.message = s.join('\u3000-\u3000');
}
}
},

created() {
this.getTextareaData();
}
})
</script>
</html>

PHP安全Web常见攻击

发表于 2019-05-10 | 更新于 2019-08-22

SQL注入

  • 说明
    攻击者把SQL命令插入到Web表单的输入域或页面请求的字符串,欺骗服务器执行恶意的SQL命令。在某些表单中,用户输入的内容直接用来构造(或者影响)动态SQL命令,或作为存储过程的输入参数,这类表单特别容易受到SQL注入式攻击

  • 模拟

    1
    2
    3
    4
    // SQL
    $query = 'SELECT * FROM `user` WHERE `username` = ' . $username . ' AND `password` = '. $password;
    // 在没有过滤下 如果 $username 或者 $password 为 '1' OR 1 = 1 ,那么SQL 为
    // "SELECT * FROM `user` WHERE `username` = '1' OR 1 = 1 AND `password` = '1' OR 1 = 1";
  • 危害

    • 查询数据库中敏感数据
    • 绕过认证
    • 添加、删除、修改服务器数据
    • 拒绝服务
  • 防范

    • 检查变量的数据类型和格式
    • 过滤特殊符号
    • SQL组装的时候,对外部变量以及所有变量都进行过滤
    • 可以用sqlEscape、sqlImplode、sqlSingle、sqlMulti等函数过滤组装。过滤主要是一些’单引号这些可以破坏SQL组装的数据
    • 绑定变量,使用预处理语句

跨站脚本攻击

  • 说明

    XSS跨站脚本;恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意用户的特殊目的.反射型跨站。GET或POST内容未过滤,可以提交js以及HTML等恶意代码。

  • 模拟

    1
    2
    3
    4
    5
    6
    7
    <?php echo $_GET['message'] ?>
    // 正常url
    index.php?message=处理成功
    // 带js的url
    index.php?message=<script>alert(1)</script>
    // 恶意跳转url
    index.php?message=<script>window.location.href=user.php</script>
  • 危害

    • 获取到用户cookie信息
    • 跳转到钓鱼网站
    • 操作受害者的浏览器,查看受害者网页浏览信息等。
    • 蠕虫攻击。
  • 防范
    • 使用htmlspecialchars函数将特殊字符转换成HTML编码,过滤输出的变量

跨站请求伪造

  • 说明

    CSRF跨站请求伪造,也被称成为“one click attack”或者session riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。

  • 模拟

    某个购物网站购买商品时,采用http://www.shop.com/buy.php?item=watch&num=100,item参数确定要购买什么物品,num参数确定要购买数量,如果攻击者以隐藏的方式发送给目标用户链接
    ,那么如果目标用户不小心访问以后,购买的数量就成了100个

  • 危害

    • 强迫受害者的浏览器向一个易受攻击的Web应用程序发送请求,最后达到攻击者所需要的操作行为
  • 防范

    • 检查网页来源
    • 检查内置的隐藏变量
    • 使用POST,不要使用GET,处理变量也不要直接使用$_REQUEST
    • 采用类似随即码或者令牌的形式,让用户操作唯一性。 (每次用户登录网站随机生成一token,存放在cookie中,用户的所有操作中都需要经过token验证)

文件上传漏洞

  • 说明

    攻击者利用程序缺陷绕过系统对文件的验证与处理策略将恶意代码上传到服务器并获得执行服务器端命令的能力。

  • 常用的攻击手段有

    • 上传Flash跨域策略文件crossdomain.xml,修改访问权限(其他策略文件利用方式类似);
    • 上传病毒、木马文件,诱骗用户和管理员下载执行;
    • 上传包含脚本的图片,某些浏览器的低级版本会执行该脚本,用于钓鱼和欺诈。
    • 总的来说,利用的上传文件要么具备可执行能力(恶意代码),要么具备影响服务器行为的能力(配置文件)。
    • 上传Web脚本代码,Web容器解释执行上传的恶意脚本
  • 防范

    • 文件上传的目录设置为不可执行
    • 判断文件类型,设置白名单。对于图片的处理,可以使用压缩函数或者resize函数,在处理图片的同时破坏图片中可能包含的HTML代码
    • 使用随机数改写文件名和文件路径:一个是上传后无法访问;再来就是像shell、.php 、.rar和crossdomain.xml这种文件,都将因为重命名而无法攻击
    • 单独设置文件服务器的域名:由于浏览器同源策略的关系,一系列客户端攻击将失效,比如上传crossdomain.xml、上传包含Javascript的XSS利用等问题将得到解决

SESSION 劫持

  • 说明

    攻击者利用各种手段来获取目标用户的session id。一旦获取到session id,那么攻击者可以利用目标用户的身份来登录网站,获取目标用户的操作权限

  • 攻击者获取目标用户session id的方法
    • 暴力破解:尝试各种session id,直到破解为止;
    • 计算:如果session id使用非随机的方式产生,那么就有可能计算出来;
    • 窃取:使用网络截获,xss攻击等方法获得
  • 防范

    • 定期更改session id
    • 更改session的名称
    • 关闭透明化session id
    • 设置HttpOnly。通过设置Cookie的HttpOnly为true,可以防止客户端脚本访问这个Cookie,从而有效的防止XSS攻击

其他安全相关

  1. X-Frame-Options防止网页放在iframe中

    X-Frame-Options是一个HTTP标头(header),用来告诉浏览器这个网页是否可以放在iFrame内

    X-Frame-Options 有三个可选的值

    • DENY:浏览器拒绝当前页面加载任何Frame页面
    • SAMEORIGIN:frame页面的地址只能为同源域名下的页面
    • ALLOW-FROM:origin为允许frame加载的页面地址

      可以直接通过meta标签来设置,不需要放在http头部请求中

      1
      <meta http-equiv="X-Frame-Options" content="ALLOW-FROM http://optimizely.com">

参考文档

  1. 参考一
  2. 参考二

Yii2学习笔记

发表于 2019-05-10 | 更新于 2019-08-22

控制器

控制器周期

beforeAction

1
2
3
4
5
6
7
8
9
10
11
12
/**
* action 运行的前置操作
*
* 比较适合统一化的数据验证,请求数据的接受等
* @param \yii\base\Action $action action 类
* @return bool 返回ture 控制器周期才会走下去
*/
public function beforeAction($action)
{
var_dump($action->id, $action->controller->id);
return parent::beforeAction($action); // TODO: Change the autogenerated stub
}

afterAction

1
2
3
4
5
6
7
8
9
10
11
12
/**
* action 后置操作
*
* 比较适合返回最后的日志搜集
* @param \yii\base\Action $action action 类
* @param mixed $result
* @return mixed
*/
public function afterAction($action, $result)
{
return parent::afterAction($action, $result); // TODO: Change the autogenerated stub
}

模型

AR 模型 常用方法

查询数据

1
2
3
4
5
6
7
8
9
10
11
12
// 查询单条
$one = Post::find()->where(['id' => 1])->one();
// 或者
$one = Post::finOne(1);

// 查询多条
$posts = Post::find()->where(['status' => 1])->all();
// 或者
$posts = Post::findAll(['status' => 1]);

// 通过sql查询
$posts = Post::findBySql('SELECT * FROM `posts` WHERE `status` = :status', [':status' => 1])->all();

查询条件的设定

where 查询支持的参数

  • 字符串格式 例如: ‘status=1’

    1
    2
    3
    4
    // 对应的SQL: WHERE status = 1
    $query->where('status = 1');
    // 使用字符串方式,可以绑定参数
    $query->where('status = :status', [':status' => 1])
  • 哈希格式 例如: [‘status’ => 1, ‘type’ => 1]

哈希格式最适合用来指定多个 AND 串联起来的简单的”等于断言”子条件。

1
2
3
4
// 对应的SQL: WHERE (`status`=1) AND (`type`=1)
$query->where(['status' => 1, 'type' => 1])
// 如果值为一个数组会转为IN 对应SQL: WHERE (`status`=1) AND (`type` IN (1, 2, 3))
$query->where(['status' => 1, 'type' => [1, 2, 3]])

  • 操作符格式 例如: [‘=’, ‘status’, 1]

可以实现比较复杂的查询条件

  1. 格式

    1
    ['操作符', 操作数1, 操作数2, ...];
  2. 演示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 简单单个查询 对应的SQL: WHERE `status` >= 1
    $query->where(['>=', 'status', 1])

    // 多个条件 AND 连接 对应的SQL: WHERE (`status` >= 1) AND (`type` <= 3)
    $query->where(['and', ['>=', 'status', 1], ['<=', 'type', 3]])

    // 多个条件 OR 连接 对应SQL: WHERE (`status` IN (1, 3)) OR (`type` >= 2)
    $query->where('or', ['in', 'status', [1, 3], ['>=', 'type', 2]])

    // 比较复杂组合的SQL 对应SQL: WHERE (`create_type` = 6) AND (`status` = 0) AND (`is_fail` = 0) AND (((`last_view_time` = 0) AND (`create_time` <= 1503562511)) OR ((`last_view_time` > 0) AND (`last_view_time` <= 1503562511)))
    $query->where([
    'and',
    ['=', 'create_type', 6],
    ['=', 'status', 0],
    ['=', 'is_fail', 0],
    ['or',
    ['and', ['=', 'last_view_time', 0], ['<=', 'create_time', time()]],
    ['and', ['>', 'last_view_time', 0], ['<=', 'last_view_time', time()]]
    ]
    ]);
  3. 操作符说明

    • and 和 or 确定多个条件通过什么方式连接
    • between 和 not between

      1
      2
      // 数组需要四个元素 对应SQL: WHERE `id` BETWEEN 1 AND 10
      ['between', 'id', 1, 10]
    • in 和 not in

      1
      2
      // 对应SQL: WHERE `status` IN (1, 3)
      ['in', 'status', [1, 3]]
    • like

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // 默认 对应SQL: WHERE `name` LIKE '%username%'
      ['like', 'name', 'username']

      /**
      * 第二个范围值是一个数组,那么将会生成用 AND 串联起来的 多个 like 语句
      * 对应SQL: WHERE `name` LIKE '%user%' AND `name` LIKE '%name%'
      */
      ['like', 'name', ['user', 'name']]

      // 使用前缀查询 对应SQL: WHERE `name` LIKE 'user%'
      ['like', 'name', 'user%', false]
    • or like 与like 相同,只是第二个范围值是一个数组,那么将会生成用 OR 串联起来的 多个 like 语句

      1
      2
      3
      4
      5
      // 对应的SQL: WHERE `name` LIKE '%user%' OR `name` LIKE '%name%'
      ['or like', 'name', ['user', 'name']]

      // 自己定义前缀查询还是后缀查询 对应SQL: WHERE `name` LIKE 'user%' OR `name` LIKE '%name'
      ['or like', 'name', ['user%', '%name'], false]
    • not like 和 or not like 和 like 与 or like 用法相同

    • exists 和 not exists 需要一个操作数,该操作数必须是代表子查询 yii\db\Query 的一个实例
      1
      2
      3
      4
      // 生成的SQL: WHERE EXISTS (SELECT * FROM `user` WHERE `status` = 1)
      $queryOne = new Query();
      $queryOne->from('user')->where(['=', 'status', 1])->one();
      $query->where(['exists', $queryOne])
- \>, >=, =, <, <=
  • andWhere 和 orWhere 在原有条件的基础上 附加额外的条件。你可以多次调用这些方法来分别追加不同的条件

    1
    2
    3
    4
    5
    6
    7
    // 对应SQL: WHERE (`status` = 1) AND (`type` >= 1)
    $query->where(['=', 'status', 1]);
    $query->andWhere(['>=', 'type', 1]);

    // 对应的SQL: WHERE ((`status`=1) AND (`type`=1)) OR (`name`='username')
    $query->where(['status' => 1, 'type' => 1]);
    $query->orWhere(['name' => 'username']);
  • filterWhere 过滤查询(在用户输入查询的时候比较好用) 会忽略空值(null, ‘’, ‘ ‘, [])

    1
    2
    3
    4
    5
    6
    7
    // 不会添加查询条件
    $status = ''; // null, ' ', [] 都不会添加查询条件
    $query->filterWhere(['status' => $status]);

    // 添加查询条件: WHERE `status` = 0
    $status = 0;
    $query->filterWhere(['status' => $status]);

建议查询时候直接使用where() 方法中传递数组的方式定义where条件; andWhere() 和 orWhere() 会增加判断

查询对象转数组

asArray() 方法

在对查询数据没有其他操作的情况下,建议都转为数组(能够节省内存)

1
2
// 查询出来是一个数组
$posts = Post::find()->where(['status' => 1])->asArray()->all();

指定数组的键

indexBy() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$users = User::find()->where(['status' => 1])->asArray()->indexBy('id')->all();

// 查询出来的数组
[
'1' => [
'id' => 1,
'status' => 1,
'username' => 'username'
],
'3' => [
'id' => 3,
'status' => 1,
'username' => 'my name'
],
];

// 这样查询出来的数组,判断指定ID的用户是否存在比较方法(特别是循环中)
if (isset($user[1])) {
....
}

使用场景

现在后台程序需要显示文章信息,并且需要显示文章的作者信息

两个表的结构

文章表 article
字段名 | 字段说明
—|—
id | 文章ID
user_id | 文章作者ID
title | 文章标题

用户表 user

字段名 字段说明
id 用户ID
name 用户姓名
  • 方案一 使用表关联

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // Article 模型类中定义关联关系
    class Article extends ActiveRecord
    {
    public function getUser()
    {
    return $this->hasOne(User::className(), ['id' => 'user_id']);
    }
    }

    /**
    * 使用
    *
    * 说明这里也是只使用了两次查询
    * 第一次: SELECT * FROM `article` LIMIT 10
    * 第二次: SELECT * FROM `user` WHERE `id` IN (1, 2, ...)
    */
    $arrArticles = Article::find()->with('user')->limit(10)->asArray()->all();
    if ($arrArticles) {
    foreach ($arrArticles as $article) {
    if (isset($article['user'])) {
    // 使用作者名称
    $article['user']['name'];
    }
    }
    }
  • 方案二 使用indexBy()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 第一步查询出文章信息
    $arrArticles = Article::find()->limit(10)->asArray()->all();

    // 第二步获取到用户信息
    if ($arrArticles) {
    // 获取到用户ID
    $arrUserIds = array_column($arrArticles, 'user_id');
    // 查询到客户信息
    $arrUsers = User::find()->where(['id' => $arrUserIds])->indexBy('id')->asArray()->all();
    // 三 将用户名称追加到文章信息中
    foreach ($arrArticles as &$value) {
    $value['user_name'] = '';
    if (isset($arrUsers[$value['user_id']])) {
    $value['user_name'] = $arrUsers[$value['user_id']]['name'];
    }
    }
    }

批处理查询

当需要处理大数据的时候,像 yii\db\Query::all() 这样的方法就不太合适了, 因为它们会把所有数据都读取到内存上。为了保持较低的内存需求, Yii 提供了一个 所谓的批处理查询的支持。批处理查询会利用数据游标 将数据以批为单位取出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use yii\db\Query;

$query = (new Query())
->from('user')
->orderBy('id');

foreach ($query->batch() as $users) {
// $users 是一个包含100条或小于100条用户表数据的数组
}

// or if you want to iterate the row one by one
foreach ($query->each() as $user) {
// $user 指代的是用户表当中的其中一行数据
}

yii\db\Query::batch() 和 yii\db\Query::each() 方法将会返回一个实现了Iterator 接口 yii\db\BatchQueryResult 的对象,可以用在 foreach 结构当中使用。在第一次迭代取数据的时候, 数据库会执行一次 SQL 查询,然后在剩下的迭代中,将直接从结果集中批量获取数据。默认情况下, 一批的大小为 100,也就意味着一批获取的数据是 100 行。你可以通过给 batch() 或者 each() 方法的第一个参数传值来改变每批行数的大小。

查看执行语句

1
2
3
4
5
6
7
8
// 可以通过yii 自带的 debug 工具查看

// 通过model 类
// 查看执行SQL: SELECT * FROM `user` WHERE `status` IN (1, 2)
var_dump(User::find()->where(['status' => [1, 2, 3]])->createCommand()->getRawSql());

// 查看预处理的SQL: SELECT * FROM `user` WHERE `status` IN (:qp0, :qp1)
var_dump(User::find()->where(['status' => [1, 2, 3]])->createCommand()->getSql());

验证

验证规则定义

定义格式

1
2
3
4
5
6
7
8
// 第一个字段可以为字符串,也可以是数组
[[验证字段], 验证规则, 其他配置项...]

// 为字符串的时候必须为单个字段
['username', 'required', 'message' => '用户名不能为空']

// 多个字段验证
[['username', 'password'], 'required']

例子:

1
2
3
4
5
6
7
8
9
10
// 在模型的 rules 方法中定义
public function rules()
{
return [
// 不能为空
[['username', 'passwrod'], 'required'],
// 必须唯一
[['username'], 'unique'],
];
}

验证规则类型

说明
一般提示信息可以不用填写,会自动匹配 attributeLabels() 方法返回的字段说明信息

  • required: 必须值验证

    1
    2
    // requiredValue, message 可以不用填写
    [['字段名'], 'required', 'requiredValue' => '必填值', 'message' => '提示信息']
  • email 邮箱格式验证

    1
    ['email', 'email']
  • match 正则表达事验证

    1
    2
    3
    [['字段名'], 'match', 'pattern' => '正则表达式']
    // 正则取反
    [['字段名'], 'match', 'pattern' => '正则表达式', 'not' => true]
  • url 网址信息验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 验证https 或者 http 的网址
    ['字段名', 'url']

    /**
    * 输入字段可以不带 http 前缀,默认帮你添加
    * 输入的值可以是 www.baidu.com 也可以是 http://www.baidu.com
    * 输入 www.baidu.com 会帮你加上 http:// 最终为 http://www.baidu.com
    */
    [['字段名'], 'url', 'defaultScheme' => 'http']
  • captcha 验证码

    1
    ['验证码字段', 'captcha']
  • compare 比较

    1
    2
    3
    4
    5
    // compareValue 比较值 operator 比较类型
    ['字段', 'compare', 'compareValue' => 30, 'operator' => '>=']

    // 比较两个字段的值(确认密码需要与密码一致)
    [['confirm_password'], 'compare', 'compareAttribute' => 'password']
  • default 默认值 当字段为空那么赋默认值

    1
    2
    // value 定义默认值
    ['字段', 'default', 'value' => 30]
  • exist 验证存在

    1
    2
    3
    4
    5
    6
    7
    8
    // 验证数据username 在表中必须存在
    ['username', 'exist']

    // 定义查询的model, 用户名在admin 表中是否存在
    ['username', 'exist', 'targetClass' => Admin::className()]

    // 定义数据的字段
    ['username', 'exist', 'targetAttribute' => 'name']
  • unique 唯一性验证

    1
    2
    3
    4
    5
    /**
    * targetClass 可以配置查询那个表
    * targetAttribute 可以定义对应的字段信息
    */
    ['username', 'unique']
  • file 文件验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * extension 允许上传的文件类型
    * 其他配置项
    * checkExtensionByMimeType 是否使用mime类型检查文件类型(扩展名)
    * mimeTypes 允许的mimeTypes 类型
    * minSize 文件最小大小
    * maxSize 文件最大大小
    * 其他配置...
    */
    [['file'], 'file', 'extensions' => ['png', 'jpg', 'gif', 'jpeg']]
  • image 图片验证 继承于 File

    1
    2
    3
    4
    5
    6
    7
    /**
    * image 验证继承于 file 验证
    * file 定义的验证配置都可以使用
    * 其他配置
    * minWidth, minHeight, maxWidth, maxLength, ...
    */
    ['image', 'image', 'minWidth' => 100]
  • filter 滤镜[trim]

    1
    2
    3
    4
    5
    /**
    * filter 制定过滤的函数 可以使用匿名函数
    * skipOnArray 是否跳过数组过滤
    */
    ['username', 'filter', 'filter' => 'trim', 'skipOnArray' => true]
  • trim 处理

    1
    [['username', 'password'], 'trim']
  • in 范围值验证

    1
    2
    // range 定义范围值
    ['status', 'in', 'range' => [1, 2]]
  • integer 整数

    1
    ['age', 'integer']
  • number 数字

    1
    ['money', 'number']
  • double 浮点数

    1
    ['money', 'double']
  • string 字符串验证

    1
    2
    3
    4
    5
    /**
    * length 定义字符串最小长度和最大长度
    * min, max 单独定义指定最小,最大长度
    */
    ['title', 'string', 'length' => [1, 2]]
  • boolean 布尔值验证

    1
    2
    3
    4
    /**
    * strict 定义是否严格验证
    */
    ['字段名', 'boolean', 'trueValue' => true, 'falseValue' => false, 'strict' => true]
  • date 日期验证[data, time, datetime]

    1
    ['start_time', 'date']
  • ip 验证

    1
    [['user_ip'], 'ip']

自定义验证规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class User extends Model
{
public function rules()
{
return [
['username', 'validateUser', 'params' => [1, 2, 3]]
];
}

/**
* 自定义验证处理
*
* @desc 函数写的时候可以不接受参数
* @param string $attribute 字段名称
* @param mixed $params rules 中定义的params 参数信息
* @param \yii\validators\InlineValidator $validator 验证类
*/
public function validateUser($attribute, $params, $validator)
{
if ($this->user == 2) {
$this->addError($attribute, '不能等于二');
}
}
}

验证规则生效条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 可以定义指定验证的生效条件
[
['passowrd'],
'required',

// 指定服务器验证什么时候生效(当状态为1的时候密码不能为空)
'when' => function ($model) {
return $model->status == 1;
},

// 前端js 验证什么时候生效
'whenClient' => "function(attribute, value) {
return $('#status').val() == 1;
}",
],

验证场景

model 可能在不同的场景下使用,不同场景对应不同的业务逻辑,需要验证的字段和方式也就不一样,这个时候就需要用到验证场景

定义验证场景

model 中定义验证场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User extends Model
{
public function scenarios()
{
// 默认验证场景 default 的验证字段
$scenarios = parent::scenarios();

// 登录时候,验证用户名和密码
$scenarios['login'] = ['username', 'password'];

// 注册的时候要验证用户名、密码、确认密码
$scenarios['register'] = ['username', 'password', 'confirm_password'];
return $scenarios;
}

}

为规则指定验证场景

还是上面那个model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 定义验证规则
*/
public function rules
{
return [
// 一样定义通用验证
[['username', 'password'], 'trim'],

// on 定义使用的验证场景 可以是字符串,也可以是数组
[['username', 'password'], 'required', 'on' => ['login', 'register']],
['confirm_password', 'required', 'on' => 'register'],
['confirm_password', 'compare', 'compareAttribute' => 'password', 'on' => 'register'],
];
}

使用验证场景

一般在控制器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public function actionLogin()
{
// 使用验证场景
$user = new User();
$user->scenario = 'login';

// 或者在实例化指定验证场景
$user = new User(['scenario' => 'login']);

// 对model的属性进行赋值
$user->username = '123';
...

// 显示的调用验证
if ($user->validate()) {
// 通过验证
} else {
// 获取到错误信息
var_dump($user->getErrors());
}

/**
* 执行save()的时候会调用validate() 方法
* 除非save() 调用时指定不走验证
* $user->save(false)
*/
if ($user->save()) {
...
} else {
var_dump($user->getErrors());
}
}

验证规则使用注意

执行验证是通过rules 中的规则,从上往下验证的,一旦验证不通过,不会向下验证, 建议关于数据库的验证,尽量放到最后

数据的操作

块赋值

块赋值只用一行代码将用户所有输入填充到一个模型

1
2
3
4
5
6
7
8
$user = new User();
$user->attributes = Yii::$app->request->post('User', []);

// 等同于
$user = new User();
$data = Yii::$app->request->post('User', []);
$user->username = isset($data['username']) ? $data['username'] : null;
$user->password = isset($data['password']) ? $data['password'] : null;

  • 使用load() 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    $user = new User();

    /**
    * 默认使用Yii 表单
    * 提交的表单数组应该为
    * 表单元素 <input type="text" name="User[username]" />
    * [
    * 'User' => ['username' => 'username', 'password' => 'password']
    * ]
    */
    $user->load(Yii::$app->request->post());

    /**
    * 没有使用yii表单
    * <input type="text" name="username" >
    * 对应提交的值
    * ['username' => 'username', 'password' => '']
    */
    $user->load(Yii::$app->request->post(), '');
  • 使用属性 attributes

    1
    2
    3
    4
    5
    /**
    * attributes 需要接收一个数组
    * [属性字段 => 对应值]
    */
    $user->attributes = Yii::$app->request->post('User', []);
  • 使用 setAttributes() 方法(前面两种最终都是调用的 setAttributes() 方法)

    1
    2
    3
    4
    5
    6
    7
    $user = new User();

    /**
    * 需要接收一个数组
    * [属性字段 => 对应值]
    */
    $user->setAttributes(Yii::$app->request->post());

块赋值注意

  1. 需要定义为安全的字段才可以赋值成功
  2. 在没有定义场景的情况下,定义了规则的字段、便会认为是安全的,可以块赋值成功
  3. 定义了验证场景,只有场景定义的字段可以批量赋值

新增数据

1
2
3
4
5
6
7
$user = new User();
$user->username = 'username';
$user->password = 'password';
$user->insert();

// 或者使用
$user->save();

修改数据

  • 查询对象修改

    1
    2
    3
    4
    5
    6
    7
    $user = User::findOne(1);
    $user->username = 'username';
    $user->password = 'password';
    $user->update();

    // 或者使用
    $user->save();
  • 修改指定数据

    1
    2
    3
    4
    5
    // 将ID 等于 2的用户的状态改为 1, 第二个参数同 where() 方法中的参数
    $intNumber = User::updateAll(['status' => 1], ['id' => 2]]);

    // 使用字符的绑定参数查询修改
    User::updateAll(['status' => 2], 'id=:id', [':id' => 1]);
  • 属性值实现累加

    1
    2
    3
    4
    5
    6
    7
    /**
    * updateAllCounters
    * 第一个参数: 字段对应累加 正数值、累减的值 负数值
    * 第二个参数: 查询条件 同where() 方法,可以字符串,数组
    * 第三个参数: 查询绑定的参数
    */
    User::updateAllCounters(['status' => -2], 'id=:id', [':id' => 1]);

删除数据

  • 使用查询对象删除

    1
    2
    $user = User::findOne(1);
    $user->delete()
  • 删除指定数据

    1
    User::deleteAll(['status' => 0]);

AR的生命周期

AR的生命周期
理解AR的生命周期对于你操作数据库非常重要。生命周期通常都会有些典型的事件存在。对于开发AR的behaviors来说非常有用。

  • 当你实例化一个新的AR对象时,我们将获得如下的生命周期:

constructor
yii\db\ActiveRecord::init(): 会触发一个 yii\db\ActiveRecord::EVENT_INIT 事件

  • 当你通过 find() 方法查询数据时,每个AR实例都将有以下生命周期:

constructor

  1. yii\db\ActiveRecord::init(): 会触发一个 yii\db\ActiveRecord::EVENT_INIT 事件
  2. yii\db\ActiveRecord::afterFind(): 会触发一个 yii\db\ActiveRecord::EVENT_AFTER_FIND 事件
  • 当通过 yii\db\ActiveRecord::save() 方法写入或者更新数据时, 我们将获得如下生命周期:
  1. yii\db\ActiveRecord::beforeValidate(): 会触发一个 yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE 事件
  2. yii\db\ActiveRecord::afterValidate(): 会触发一个 yii\db\ActiveRecord::EVENT_AFTER_VALIDATE 事件
  3. yii\db\ActiveRecord::beforeSave(): 会触发一个 yii\db\ActiveRecord::EVENT_BEFORE_INSERT 或 yii\db\ActiveRecord::EVENT_BEFORE_UPDATE 事件
  4. yii\db\ActiveRecord::afterSave(): 会触发一个 yii\db\ActiveRecord::EVENT_AFTER_INSERT 或 yii\db\ActiveRecord::EVENT_AFTER_UPDATE 事件
  • 最后,当调用 delete() 删除数据时, 我们将获得如下生命周期:
  1. yii\db\ActiveRecord::beforeDelete(): 会触发一个 yii\db\ActiveRecord::EVENT_BEFORE_DELETE 事件
    执行实际的数据删除
  2. yii\db\ActiveRecord::afterDelete(): 会触发一个 yii\db\ActiveRecord::EVENT_AFTER_DELETE 事件

参考文档

  • Yii2中文社区 http://www.yiichina.com/
  • 深入理解Yii2.0 http://www.digpage.com/index.html
  • 看云Yii学习https://www.kancloud.cn/curder/yii/247740
  • 文档 http://www.awaimai.com/patterns

PHP操作MongoDB

发表于 2019-05-10 | 更新于 2019-08-22

PHP连接MongoDB

1
2
3
4
5
6
7
8
9
10
$connection = new Mongo();

// 负载均衡
$conn = new Mongo('localhost', array('replicaSet' => true));

// 持久连接
$conn = new Mongo('localhost', array('persist' => 't'));

// 带用户名和密码
$conn = new Mongo('mongodb://root:123@localhost');

选择数据库 和 集合

1
2
3
4
5
6
7
8
9
10
// 选择数据库
$db = $connection->mydata;

// 选择集合
$collection = $db->user;
// 选择集合的另一种方式
$collection = $db->selectCollection('user');

// 简洁写法
$collection = $db->mydata->user;

添加数据

  • 简单新增

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
    * $collection->insert() ) ^.^ --->
    * insert($array[, $arr = array(
    * 'safe' => false,
    * 'fsync' => false,
    * 'timeout' => 10000,
    * )])
    * @param array $array 添加数据信息
    * @param $arr 额外参数信息( 可选 )
    * $arr['safe'] boolean 是否安全写入(false)
    * $arr['fsync'] boolean 是否强制插入到同步磁盘(false)
    * $arr['timeout'] int 超时间( 毫秒 )
    */

    // 简单新增 , 返回布尔值 bool(true)
    $result = $collection->insert(array(
    'username' => 'myCeshi',
    'age' => 12,
    'time' => time(),
    'skills' => array('php', 'javascript', 'css', 'html'),
    ));
  • 向集合中安全插入数据,返回插入状态(数组)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    // 用于等待MongoDB完成操作,以便确定是否成功(当有大量记录插入时使用该参数会比较有用)
    $result = $collection->insert(array(
    'username' => 'liujx',
    'age' => 22,
    );, true);

    /**
    return array(
    'connectionId' => int 10
    'n' => int 0
    'syncMillis' => int 0
    'writtenTo' => null
    'err' => null
    'ok' => float 1
    ) ;
    */

更新数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* update(
array $criteria,
array $new[,
array $options = array()
]
)
* @param array $criteria 查询修改对象数组
* @param array $new 修改成的新对象数组
* @param array $options 更新的其他设置数组
* @return boolean 返回布尔值
*
* 更新数据说明
* $inc 自增
* $set 修改
* $unset 删除字段
*/
  1. 修改更新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 修改一条
    $result = $collection->update(
    array(
    'username' => 'myCeshi'
    ),
    array(
    '$set' => array(
    'username' => 'myTitle',
    'name' => 'xiaoTitle'
    )
    )
    );
  2. 替换更新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 替换一条
    $result = $collection->update(
    array(
    'username' => 'myCeshi'
    ),
    array(
    'myname' => '123',
    'title' => 'nonoe'
    )
    );
  3. 批量更新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    $update = array();
    $result = $collection->update(
    array(
    'username' => 'myCeshi'
    ),
    array(
    '$set' => array(
    'myname' => '123',
    'title' => 'nonoe'
    )
    ),
    array(
    'multiple' => true
    )
    );
  4. 自动累加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // age累加5( 修改一条 )
    $result = $collection->update(
    array(
    'username' => 'myTitle'
    ),
    array(
    '$inc' => array(
    'age' => 5
    )
    )
    );

删除数据 remove()

  • 删除指定数据

    1
    2
    3
    $result = $collection->remove(array(
    'username' => 'myTitle'
    ));
  • 清空集合

    1
    $collection->remove();
  • 删除指定的MongoId

    1
    2
    $id = new MongoId('557016db90428ba817000012');
    $result = $collection->remove(array('_id' => $id));

查询数据 find() 和 findOne()

  • 查询数据数目 count()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    $num = $collection->count();

    // 查询满足条件的数据
    $num = $collection->count(array(
    'username' => 'liujx'
    ));

    // 查询满足条件数据条数
    $num = $collection->count(array(
    'age' => array(
    '$gt' => 12,
    '$lt' => 30
    )
    ));
  1. $gt为大于
  2. $gte为大于等于
  3. $lt为小于
  4. $lte为小于等于
  5. $ne为不等于
  6. $exists不存在
  • findOne() 查询一条数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * findOne() 查询一条数据
    * @param array $criteria 查询条件数组
    * @return array 返回数组( 没有数据返回 null )
    */
    $data = $collection->findOne();
    $data = $collection->findOne(array(
    'username' => 'liujx'
    ));
  • find() 查询数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * find() 查询数据
    * @param array $criteria 查询条件数组
    * @return object 返回对象( 没有返回 null )
    * 注意:
    * 在我们做了find()操作,获得$cursor游标之后,这个游标还是动态的.
    * 换句话说,在我find()之后,到我的游标循环完成这段时间,如果再有符合条件的记录被插入到collection,那么这些记录也会被$cursor 获得.
    * 如果你想在获得$cursor之后的结果集不变化,需要这样做:
    * $cursor = $collection->find();
    * $cursor->snapshot();
    */
    $data = $collection->find(); // 查询所有数据,返回对象
    foreach ($data as $key => $value) {
    // var_dump($value);
    }
  1. 限制查询条数limit()

    1
    $data = $collection->find()->limit(5);
  2. 确定查询开始位置skip()

    1
    $data = $collection->find()->limit(10)->skip(5);
  3. 排序条件查询sort()

    1
    2
    $data = $collection->find()->limit(10)->sort(array('_id' => -1));
    // $data = $data->snapshot() ; # 排序和snapshot();
  4. 指定查询的列 fields() true 显示 false 不显示

    1
    2
    $data = $collection->find()->limit(10)->fields(array('_id' => false));    // _id 列不显示
    // $data = $collection->find()->limit(10)->sort(array('_id' => -1 ))->fields(array('username'=> true)) ; # 只显示username(_id默认显示)

索引操作 ensureIndex()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* ensureIndex(
array(),
array(
'name' =>'索引名称',
'background' => true ,
'unique' => true
)
)
* 详见:http://www.php.net/manual/en/mongocollection.ensureindex.php
*/

// 表示降序 -1表示升序
$collection->ensureIndex(array(
'age' => 1,
'type' => -1
));

// 索引的创建放在后台运行(默认是同步运行)
$collection->ensureIndex(
array(
'age' => 1,
'type' => -1
),
array(
'background' => true
)
);

// 该索引是唯一的
$collection->ensureIndex(
array(
'age' => 1,
'type' => -1
),
array(
'unique' => true
)
);

关闭连接 close()

1
$connection->close();

MongoDB学习

发表于 2019-05-10 | 更新于 2019-08-22

Window 环境安装MongoDB

  1. 下载安装 Mongodb
  2. 设置MongoDB 为wondows服务项( 配置环境变量 d:/MongoDB/bin) wind + R cmd.exe
  3. 启动MongoDB

    1
    mongod --logpath d:/server/mongo/logs/logs.txt --logappend --dbpath d:/server/mongo/data --directoryperdb --serviceName MongoDB -install

    参数说明

    • –logpath 日志文件地址
    • –logappend 添加日志的方式为追加
    • –dbpath 指定MongoDB数据存放的路径
    • –directoryperdb MongoDB按照数据库的不同,针对每一个数据库都建立一个目录,所谓的“目录每数据库
    • –auth 是否使用用户验证
    • –serviceName 安装Windows服务时使用的服务名
    • –install 安装MongoDB服务
    1. 启动MongoDB服务 net start MongoDB
    2. 关闭MongoDB服务 net stop MongoDB
    3. 使用MongoDB 需要先启动MongoDB 然后 mongo

添加用户和角色

  1. 添加用户使用db.createUser( user , writeConcern ) 函数使用说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* @param object user 这个文档创建关于用户的身份认证和访问信息
* @param mixed writeConcern 这个文档描述保证MongoDB提供写操作的成功报告。
*/
var user = {
"user": "root", // 用户名
"pwd": "loveme", // 密码
"customData": {employeeId: 12345}, // 用户说明
"roles": [
{
role: "clusterAdmin",
db: "admin"
},
{
role: "readAnyDatabase",
db: "admin"
},
"readWrite"
] // 用户角色
};
var write = {w: "majority", wtimeout: 5000};
db.createUser(user, write);
/*
Built-In Roles(内置角色):
1. 数据库用户角色:read、readWrite;
2. 数据库管理角色:dbAdmin、dbOwner、userAdmin;
3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;
4. 备份恢复角色:backup、restore;
5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
6. 超级用户角色:root
// 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase)
7. 内部角色:__system
PS:关于每个角色所拥有的操作权限可以点击上面的内置角色链接查看详情。
writeConcern文档( 官方说明 )
w选项:允许的值分别是 1、0、大于1的值、"majority"、<tag set>.
j选项:确保mongod实例写数据到磁盘上的journal(日志),这可以确保mongd以外关闭不会丢失数据。设置true启用.
wtimeout:指定一个时间限制,以毫秒为单位。wtimeout只适用于w值大于1.
*/

登录验证

1
2
mongo -u root -p loveme --authenticationDatabase test;
db.auth("root", "loveme") ; // 验证

删除用户

1
db.system.user.remove({"user": "root"});

MongoDB添加库

1
2
3
4
5
// 选择库
use mydata

// 删除库
db.dropDatabase();

MongoDB添加表

1
2
3
4
5
// 添加表
db.createCollection("collection_name");

// 删除表
db.collection_name.drop() ;

MongoDB 数据库操作

新增数据 insert() 和 save()

1
2
3
4
5
6
7
8
9
10
11
12
13
// 新增数据时'_id' 不赋值默认自带赋值
/**
* db.collection_name.insert() 添加数据
* @param object objNew 新增数据
*/
db.user.insert({"_id": 1, "name": "lovemeljx"});

/**
* db.collection_name.save( objNew )
* 新增数据(_id存在不新增)
* @param object objNew 更新的对象
*/
db.td_user.save({"name": "123", "username": "ljx"});

查询 find()

1
2
3
db.collection_name.find() ;	
// 查询所有 SQL: SELECT * FROM `user` ;
db.user.find() ;
  1. 指定返回指定的列 ( 键 )

    1
    2
    3
    4
    SQL:SELECT `name`,`age` FROM `user` ;

    db.user.find({}, {"name": 1, "age": 1}) ;
    /* 补充说明:第一个{} 放where条件 第二个{} 指定那些列显示和不显示( 0 不显示 1 显示) */
  2. WHERE 条件

    1
    2
    3
    SQL: SELECT `name`,`age` FROM `user` WHERE `name`='123' ;

    db.user.find({"name": "123"}, {"name": 1, "age": 1});
  3. 使用 AND

    1
    2
    3
    SQL: SELECT * FROM `user` WHERE `name`='123' AND `age`=21 ;

    db.user.find({"name": "123", "age": 21});
  4. 使用 OR( $or )

    1
    2
    3
    SQL: SELECT * FROM `user` WHERE `name`='123' OR `age`=21 ;

    db.user.find({"$or": [{"name": "123"}, {"age": 18}}]});
  5. 使用 < <= > >= !=( $lt , $lte , $gt , $gte , $ne )

    1
    2
    3
    SQL: SELECT * FROM `user` WHERE `id`>1 AND `id`<=3 ;

    db.user.find({"_id": {"$gt": 1, "$lte": 3}}) ;
  6. 使用 IN NOT IN ( $in , $nin )

    1
    2
    3
    SQL: SELECT * FROM `user` WHERE `id` IN (1,3,5) ;

    db.user.find({"_id": {"$in": [1, 2, 5]}});
  7. 匹配 null

    1
    2
    3
    SQL: SELECT * FROM `user` WHERE `name` IS NULL ;

    db.user.find({"name": null});
  8. LIKE模糊查询

    1
    2
    3
    SQL: SELECT * FROM `user` WHERE `name` LIKE '%123%';

    db.user.find({"name": /123/});
  9. 去掉重复的数据 DISTINCT

    1
    2
    3
    SQL: SELECT DISTINCT(`name`) FROM `user`;

    db.user.distinct("name");
  10. 统计查询 COUNT

    1
    2
    3
    4
    5
    6
    7
    8
    SQL: SELECT COUNT(*) FROM `user`;

    /**
    * 查询统计条件可以传参数
    * db.user.count({"name": "123"});
    * db.find({"name": "123"}).count();
    */
    db.user.count() ;
  11. 判断一个元素是否存在 $exists

    1
    db.td_user.find({"username": {"$exists": true}});
  12. 取反 $not

    1
    db.td_user.find({"username": {"$not": /123/}});
  13. sort()排序条件

    1
    2
    // 1 正序 -1 倒序
    db.user.find().sort({"_id": -1});
  14. 查询条数limit();

    1
    db.user.find().limit(10);
  15. 查询5-10间的数据

    1
    db.user.find().limit(5).skip(5);

16.数组查询(MongoDB特有)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* skills = ["java", "php", "javascript", "python"] ;
*/

// 找到skills
db.user.find({"skills": "php"});

// $all ** 必须同时包含
db.user.find({"skills": {"$all": ["java", "php"]}});

// $size ** 数组长度等于
db.user.find({"skills": {"$size": 2}});

// $slice **
db.user.find({"skills": {"$slice": [1, 1]}});

修改 update() AND save()

1
2
3
4
5
6
7
8
9
10
/**
* db.collection_name.update(criteria, objNew, upsert, multi)
* @param object criteria 查询条件对象
* @param object objNew 更新的操作
* @param boolean upsert 不存在是否添加
* @param boolean multi 是否全部跟新
*/
db.user.update(criteria, objNew, upsert, multi)
```
1. 修改一条数据

db.td_user.update({“username”: “123”}, {$set: {“username”: 456}});

1
2

2. 全部修改

db.td_user.update({“user”: “test”}, {$set: {“name”: “my-test”}}, false, true);

1
2

3. 不存在添加一条

db.td_user.update({“user”: “my-test”}, {$set: {“user”: “test”}}, true, false);

1
2

4. 添加所有

db.td_user.update({“user”: “my-test”}, {$set: {“user”: “test”}}, true, true);

1
2

5. 全更新了

db.td_user.update({“count”: {$gt: 15}}, {$inc: {“count”: 1}}, false, true);

1
2

6. 只更新了第一条

db.td_user.update({“count”: {$gt: 10}}, {$inc: {“count”: 1}}, false, false);

1
2
3
4

##### 跟新说明

* $inc 一个数字字段field增加value(数字)

db.td_user.update({“username”: “love”}, {$inc: {“age”: 2}});

1
2

* $set 修改值(所有值都支持)

db.td_user.update({“username”: “loveme”}, {$set: {“username”: “mylove”}});

1
2

* $unset 删除字段

db.td_user.update({“username”: “mylove”}, {$unset: {“age”: 1}});

1
* $push 将值追加到数组中

db.td_user.update({“username”: “mylove”}, {$push: {“age”: 23}});

1
2
3

#### 删除和修改数据 remove() AND findAndModify() AND runCommand()
1. remove

/**

  • remove( criteria )
  • @param object criteria 查询删除的对象
    */
    db.user.remove({“_id”: 0});
    1
    2

    2. findAndModify

/**

  • findAndModify( objNew ) objNew.remove 和 objNew.update 不能同时存在 runCommand()同
  • @param objNew.query 查询对象
  • @param objNew.sort 排序条件
  • @param objNew.update 修改的数据
  • @param objNew.remove 是否删除数据
    */
    db.user.findAndModify({
    query: {“_id”: 1},
    sort: {“_id”: -1},
    update: {$set: {“username”: “loveme_user”}},
    remove: false,
    });

for ( var i = 100 ; i< 200 ; i ++ ) {
db.user.save({
“id”: i + 1,
“username”: “loveme
“ + i,
“age” : 22
});
}
`

PHP面试-并发

发表于 2019-05-09 | 更新于 2019-08-22

目录

  • 高并发解决方案
  • 缓存
  • MySQL数据库
  • Nginx

高并发解决方案

  • 硬件方面:

    服务器性能: 网络-硬盘读写速度-内存大小-CPU处理数度

  • 软件方面:
    • 服务器: 使用nginx 代替 apache, nginx
    • 开启GZIP压缩优化网站、压缩网站内容可以节省网站流量
    • 构架部署:
      • HTML静态化
      • 前端资源服务器分离(文件单独部署服务器)
      • 数据库集群、库表散列(数据库读写分离、使用索引、分表)
      • 缓存(redis、memcache)
      • 负载均衡
      • CDN加速
  • PHP:

    • 升级PHP版本到PHP7
    • 加载近可能少的模块(关闭不需要的模块)
    • 使用Opcache
  • 代码层面的优化:

    • SQL查询的优化
    • 尽量多使用内置函数

缓存

Redis

Redis 简介:

Redis 是一个 key/value存储系统。和Memcache类似,它支持存储的value类型相当更多。包括 string(字符串)、list(链表)、set(集合)和zset(有序集合)。这些数据类型都支持 push/pop、add/remove 及取交集和差集及更丰富的操作,而且这些操作都是原子性的。这此基础上,Redis 支持各种不同方式的排序。与Memcached 一样,为了保证效率,数据都是缓存在内存中。区别是 Redis 会周期性的把跟新数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现master-slave(主从)同步

Redis支持的数据类型

String(字符串)

string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。
string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象.string类型是Redis最基本的数据类型,一个键最大能存储512MB。

1
2
3
set name 'string'
get name
string

Hash(哈希)

Redis hash 是一个键名对集合。
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象

1
2
3
4
5
6
7
8
9
10
# 设置key 为 user 的 name 字段为 liujx
hset user name liujx
hget user name
liujx

# 同时设置多个字段
hmset use name ljx age 18
hmget user name age
ljx
18

List(列表)

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

1
2
3
lpush users liujx
rpop users
liujx

Set(集合)

Redis的Set是string类型的无序集合。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

1
sadd key member

zset(sorted set:有序集合)

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。

1
zadd key member

Memcache

Memcache 简介:

Memcache 是一种高性能、分布式对象缓存系统,最初设计与缓解动态网站数据库加载的延迟性,你可以把它想象成一个大的内存HashTable,就是一个 key/value 键值对缓存

Redis 和 Memcache 区别

  1. Redis 支持更加丰富的数据存储类型,String、Hash、List、Set 和 Sorted Set。Memcached 仅支持简单的 key-value 结构。
  2. Memcached key-value存储比 Redis 采用 hash 结构来做 key-value 存储的内存利用率更高。
  3. Redis 提供了事务的功能,可以保证一系列命令的原子性
  4. Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中
  5. Redis 只使用单核,而 Memcached 可以使用多核,所以平均每一个核上 Redis 在存储小数据时比 Memcached 性能更高。

Redis 如何实现持久化

  1. RDB 持久化,将 Redis 在内存中的的状态保存到硬盘中,相当于备份数据库状态。
  2. AOF 持久化(Append-Only-File),AOF 持久化是通过保存 Redis 服务器锁执行的写状态来记录数据库的。相当于备份数据库接收到的命令,所有被写入 AOF 的命令都是以 Redis 的协议格式来保存的。

数据库

存储引擎

MyISAM 与 InnoDB的区别

  • InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务
  • InnoDB支持外键,而MyISAM不支持
  • InnoDB是聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高;而MyISAM是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的
  • Innodb不支持全文索引,而MyISAM支持全文索引,查询效率上MyISAM要高
  • MyISAM只支持表级锁,Innodb支持行级锁;但是InnoDB的行锁,只是在WHERE的主键是有效的,非主键的WHERE都会锁全表的

索引

作用

  • 能够大大提高查询效率
  • 可以通过建立唯一索引或者主键索引,保证数据库表中每一行数据的唯一性.
  • 建立索引可以大大提高检索的数据,以及减少表的检索行数
  • 在分组和排序字句进行数据检索,可以减少查询时间中 分组 和 排序时所消耗的时间(数据库的记录会重新排序)

索引也会有它的缺点:虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。建立索引会占用磁盘空间的索引文件

普通索引

这是最基本的索引,它没有任何限制,比如上文中为title字段创建的索引就是一个普通索引,MyIASM中默认的BTREE类型的索引,也是我们大多数情况下用到的索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
–- 直接创建索引
CREATE INDEX index_name ON table(column(length))

–- 修改表结构的方式添加索引
ALTER TABLE table_name ADD INDEX index_name ON (column(length))

–- 创建表的时候同时创建索引
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL ,
`time` int(10) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX index_name (title(length))
)

–- 删除索引
DROP INDEX index_name ON table

唯一索引

与普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值(注意和主键不同)。如果是组合索引,则列值的组合必须唯一,创建方法和普通索引类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
–- 创建唯一索引
CREATE UNIQUE INDEX indexName ON table(column(length))

–- 修改表结构
ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))

–- 创建表的时候直接指定
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL ,
`time` int(10) NULL DEFAULT NULL ,
PRIMARY KEY (`id`),
UNIQUE indexName (title(length))
);

全文索引

MySQL从3.23.23版开始支持全文索引和全文检索,FULLTEXT索引仅可用于 MyISAM 表;他们可以从CHAR、VARCHAR或TEXT列中作为CREATE TABLE语句的一部分被创建,或是随后使用ALTER TABLE 或CREATE INDEX被添加。////对于较大的数据集,将你的资料输入一个没有FULLTEXT索引的表中,然后创建索引,其速度比把资料输入现有FULLTEXT索引的速度更为快。不过切记对于大容量的数据表,生成全文索引是一个非常消耗时间非常消耗硬盘空间的做法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
–- 修改表结构添加全文索引
ALTER TABLE article ADD FULLTEXT index_content(content)

–- 直接创建索引
CREATE FULLTEXT INDEX index_content ON article(content)

–- 创建表的时候直接指定
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL ,
`time` int(10) NULL DEFAULT NULL ,
PRIMARY KEY (`id`),
FULLTEXT (content)
);

单列索引、多列索引

多个单列索引与单个多列索引的查询效果不同,因为执行查询时,MySQL只能使用一个索引,会从多个索引中选择一个限制最为严格的索引。

组合索引(最左前缀)

多个单列索引与单个多列索引的查询效果不同,因为执行查询时,MySQL只能使用一个索引,会从多个索引中选择一个限制最为严格的索引。

1
2
3
4
5
6
7
8
9
-- 建立的索引
ALTER TABLE `article` ADD INDEX `index_titme_time` (`title`(50), `time`(10));

-- 使用上面索引
SELECT * FROM `article` WHREE `title` = '测试' AND `time` = 1234567890;
SELECT * FROM `article` WHREE `title` = '测试';

-- 没有使用索引
SELECT * FROM `article` WHREE `time` = 1234567890;

创建原则

  • 选择唯一性索引
  • 为经常需要排序、分组和联合操作的字段建立索引
  • 为常作为查询条件的字段建立索引
  • 限制索引的数目
  • 尽量使用数据量少的索引
  • 尽量使用前缀来索引
  • 删除不再使用或者很少使用的索引
  • 最左前缀匹配原则,非常重要的原则
  • 尽量选择区分度高的列作为索引
  • 索引列不能参与计算,保持列“干净”
  • 尽量的扩展索引,不要新建索引

索引优化

MySQL只对一下操作符才使用索引:<,<=,=,>,>=,between,in,以及某些时候的like(不以通配符%或_开头的情形)

SQL优化

索引问题

  • 避免对索引字符进行计算
  • 避免在索引字段上面使用not,<>,!=
  • 避免在索引字段列上使用IS NULL 和 IS NOT NULL
  • 避免在索引字段列上出现数据类型转换
  • 避免在索引字段上使用函数
  • 避免建立的索引列中存在空值

前导模糊查询不能使用索引

1
2
3
4
5
-- 没有使用索引
SELECT * FROM `order` WHERE `desc` LIKE '%abc'

-- 使用索引
SELECT * FROM `order` WHERE `desc` LIKE 'abc%'

明确知道只有一条数据,limit 1 可以提高效率

不用使用SELECT * 而是指定查询字段

Nginx

nginx + php-fpm 出现502问题排查

nginx出现502有很多原因,但大部分原因可以归结为资源数量不够用,也就是说后端php-fpm处理有问题,nginx将正确的客户端请求发给了后端的php-fpm进程,但是因为php-fpm进程的问题导致不能正确解析php代码,最终返回给了客户端502错误。

php-fpm 进程数不够用

使用 netstat -napo |grep “php-fpm” | wc -l 查看一下当前fastcgi进程个数,如果个数接近conf里配置的上限,就需要调高进程数。
但也不能无休止调高,可以根据服务器内存情况,可以把php-fpm子进程数调到100或以上,在4G内存的服务器上200就可以

调高调高linux内核打开文件数量

可以使用这些命令(必须是root帐号)

1
2
3
echo 'ulimit -HSn 65536' >> /etc/profile
echo 'ulimit -HSn 65536' >> /etc/rc.local
source /etc/profile

脚本执行时间超时

如果脚本因为某种原因长时间等待不返回 ,导致新来的请求不能得到处理,可以适当调小如下配置。

  • nginx.conf里面主要是如下

    1
    2
    3
    fastcgi_connect_timeout 300;
    fastcgi_send_timeout 300;
    fastcgi_read_timeout 300;
  • php-fpm.conf里如要是如下

1
request_terminate_timeout = 10s

缓存设置比较小

修改或增加配置到nginx.conf

1
2
3
proxy_buffer_size 64k;
proxy_buffers 512k;
proxy_busy_buffers_size 128k;

这部分参见: http://www.nginx.cn/102.html

PHP面试-基础二

发表于 2019-05-09 | 更新于 2019-08-22

目录

  1. PHP基础数据类型
  2. PHP预定义变量
  3. PHP魔术函数
  4. PHP魔术常量
  5. COOKE和SESSION
  6. PHP设计模式
  7. PHP实现排序算法
  8. PHP编码规范PSR

基础数据类型

● 四种标量类型

  1. Boolean 布尔类型;判断方法 is_bool(); 类型强转(bool),(boolean)
  2. Integer 整型;判断方法is_integer();类型强转(int),(integer)
  3. Float 浮点型,也称作 Double;判断方法is_float();类型强转(float),(double),(real)
  4. String 字符型;判断方法is_string();类型强转(string)

    ● 两种复合类型

  5. Array 数组;判断方法is_array();类型强转(array)
  6. Object 对象 ;判断方法is_object();类型强转(object)

● 两种特殊类型

  1. Resource 资源;判断方法is_resource()
  2. NULL NULL;判断方法 is_null();类型强转(unset)

● 伪类型

伪类型(pseudo-types) 是 PHP 文档里用于指示参数可以使用的类型和值。 请注意,它们不是 PHP 语言里原生类型。 所以不能把伪类型用于自定义函数里的类型约束(typehint)。

  1. mixed
  2. number
  3. callback
  • 补充说明:
  1. 通过gettype()获取变量的类型字符串
  2. 字符串单引号和双引号的区别

    • 转义字符不同
    • 对变量的解析不同(双引号解析变量)
    • 解析速度不同(单引号快)

预定义变量

  • $GLOBALS 引用全局作用域中可用的全部变量
  • $_SERVER 这种超全局变量保存关于报头、路径和脚本位置的信息。
  • $_REQUEST HTTP Request 变量(默认情况下包含了 $_GET,$_POST 和 $_COOKIE 的数组。)
  • $_GET 通过 URL 参数传递给当前脚本的变量的数组。
  • $_POST 当 HTTP POST 请求的 Content-Type 是 application/x-www-form-urlencoded 或 multipart/form-data 时,会将变量以关联数组形式传入当前脚本
  • $_FILES 通过 HTTP POST 方式上传到当前脚本的项目的数组
  • $_ENV 通过环境方式传递给当前脚本的变量的数组。
  • $_COOKIE 通过 HTTP Cookies 方式传递给当前脚本的变量的数组。
  • $_SESSION 当前脚本可用 SESSION 变量的数组。
  • 补充说明$_GET和$_POST区别
  1. GET使用URL或Cookie传参。而POST将数据放在BODY中。
  2. GET的URL会有长度上的限制,则POST的数据则可以非常大。
  3. POST比GET安全,因为数据在地址栏上不可见。

魔术函数

  • __construct()

    PHP 5 允行开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。

  • __destruct()

    PHP 5 引入了析构函数的概念,这类似于其它面向对象的语言,如 C++。析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。

  • __call()

    在对象中调用一个不可访问方法时,__call() 会被调用。

  • __callStatic()

    在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。

  • __get()

    读取不可访问属性的值时,__get() 会被调用。

  • __set()

    在给不可访问属性赋值时,__set() 会被调用。

  • __isset()

    当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用

  • __unset()

    当对不可访问属性调用 unset() 时,__unset() 会被调用。

  • __sleep()

    serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。

  • __wakeup()

    与之相反,unserialize() 会检查是否存在一个 wakeup() 方法。如果存在,则会先调用 wakeup 方法,预先准备对象需要的资源。

  • __toString()

    __toString() 方法用于一个类被当成字符串时应怎样回应。

  • __invoke()

    当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。

  • __set_state()

    PHP 5.1.0 起当调用 var_export() 导出类时,此静态 方法会被调用。

  • __clone()

    当复制完成时,如果定义了 clone() 方法,则新创建的对象(复制生成的对象)中的 clone() 方法会被调用,可用于修改属性的值(如果有必要的话)。

  • __debugInfo()

    当转储对象以获取应显示的属性时,此方法由var_dump()调用。 如果方法未在对象上定义,那么将显示所有public,protected和private属性。

  • __autoload

    尝试加载未定义的类(该方法在PHP7.2中已经删除)

魔术常量

  • __LINE__ 当前文件中的行号
  • __FILE__ 返回文件的完整路径和文件名。如果用在包含文件中,则返回包含文件名。自 PHP 4.0.2 起,FILE 总是包含一个绝对路径,而在此之前的版本有时会包含一个相对路径。
  • __FUNCTION__ 返回文件的完整路径和文件名。如果用在包含文件中,则返回包含文件名。自 PHP 4.0.2 起,FILE 总是包含一个绝对路径,而在此之前的版本有时会包含一个相对路径。
  • __CLASS__ 返回类的名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该类被定义时的名字(区分大小写)。在 PHP 4 中该值总是小写字母的。
  • __METHOD__ 返回类的方法名(PHP 5.0.0 新加)。返回该方法被定义时的名字(区分大小写)

COOKE和SESSION

COOKIE

1
2
3
4
5
6
7
8
9
10
11
/**
* 设置cookie
* @param string $name cookie 名称
* @param string $value cookie 值
* @param int $expire cookie 保存时间
* @param string $path cookie 有效目录
* @param string $domain cookie 保存域名
* @param bool $secure cookie 是否通过HTTPS协议传输
* @param bool $httponly cookie 不允许客户端修改
*/
bool setcookie(string $name[, string $value = ''[, int $expire = 0[, string $path = '' [, string $domain = '' [, bool $secure = false[, bool $httponly = false ]]]]]])

SESSION

  • 设置相关
    1) session.save_handler = file
    用于读取/回写session数据的方式,默认是files
    2) session.save_path = “/var/lib/php/session”
    指定保存session文件的目录,可以指定到别的目录,但是指定目录必须要有httpd守护进程属主(比如apache或www等)写权限,否则无法回存session数据
    3) session.auto_start = 0
    如果启用该选项,用户的每次请求都会初始化session。我们推荐不启用该设置,最好通过session_start()显示地初始化session

  • 相关函数

session_start: 初始 session。

session_destroy: 结束 session。

session_unset: 释放session内存。

session_name: 存取目前 session 名称。

session_module_name: 存取目前 session 模块。

session_save_path: 存取目前 session 路径。

session_id: 存取目前 session 代号。

session_register: 注册新的变量。

session_unregister: 删除已注册变量。

session_is_registered: 检查变量是否注册。

session_decode: Session 资料解码。

session_encode: Session 资料编码。

  1. session_start()
    函数session_start会初始化session,也标识着session生命周期的开始。要使用session,必须初始化一个session环境。有点类似于OOP概念中调用构造函数构创建对象实例一样。
    session初始化操作,声明一个全局数组$_SESSION,映射寄存在内存的session数据。如果session文件已经存在,并且保存有session数据,session_start()则会读取session数据,填入$_SESSION中,开始一个新的session生命周期
  2. $_SESSION
    它是一个全局变量,类型是Array,映射了session生命周期的session数据,寄存在内存中。在session初始化的时候,从session文件中读取数据,填入该变量中。在session生命周期结束时,将$_SESSION数据写回session文件。
  3. session_register()
    在session生命周期内,使用全局变量名称将注全局变量注册到当前session中。所谓注册,就是将变量填入$_SESSION中,值为NULL。它不会对session文件进行任何IO操作,只是影响$_SESSION变量。注意,它的正确写法是session_register(‘varname’),而不是session_register($varname)
  4. session_unregister()
    与session_register操作正好相反,即在session生命周期,从当前session注销指定变量。同样只影响$_SESSION,并不进行任何IO操作
  5. session_unset()
    在session生命周期,从当前session中注销全部session数据,让$_SESSION成为一个空数组。它与unset($_SESSION)的区别在于:unset直接删除$_SESSION变量,释放内存资源;另一个区别在于,session_unset()仅在session生命周期能够操作$_SESSION数组,而unset()则在整个页面(page)生命周期都能操作$_SESSION数组。session_unset()同样不进行任何IO操作,只影响$_SESSION数组
  6. session_destroy()
    如果说session_start()初始化一个session的话,而它则注销一个session。意味着session生命周期结束了。在session生命周期结整后,session_register, session_unset, session_register都将不能操作$_SESSION数组,而$_SESSION数组依然可以被unset()等函数操作。这时,session意味着是未定义的,而$_SESSION依然是一个全局变量,他们脱离了关映射关系。 通过session_destroy()注销session,除了结束session生命周期外,它还会删除sesion文件,但不会影响当前$_SESSION变量。即它会产生一个IO操作
  7. session_regenerate_id()
    调用它,会给当前用户重新分配一个新的session id。并且在结束当前页面生命周期的时候,将当前session数据写入session文件。前提是,调用此函数之前,当前session生命周期没有被终止(参考第9点)。它会产生一个IO操作,创建一个新的session文件,创建新的session文件的是在session结束之前,而不是调用此函数就立即创建新的session文件。
  8. session_commit()
    session_commit()函数是session_write_close()函数的别名。它会结束当前session的生命周期,并且将session数据立即强制写入session文件。不推荐通过session_commit()来手工写入session数据,因为PHP会在页面生命周期结束的时候,自动结束当前没有终止的session生命周期。它会产生一个IO写操作
  9. session_end()
    结束session,默认是在页面生命周期结束的之前,PHP会自动结束当前没有终止的session。但是还可以通过session_commit()与session_destroy()二个函数提前结束session。不管是哪种方式,结束session都会产生IO操作,分别不一样。默认情况,产生一个IO写操作,将当前session数据写回session文件。session_commit()则是调用该函数那刻,产生一个IO写操作,将session数据写回session文件。而session_destroy()不一样在于,它不会将数据写回session文件,而是直接删除当前session文件。有趣的是,不管是session_commit(),还是session_destroy()都不会清空$_SESSION数组,更不会删除$SESSION数组,只是所有session*函数不能再操作session数据,因为当前的session生命周期终止了,即不能操作一个未定义对象。

设计模式

  • 一、单例设计模式

单例模式的要点有三个:

  1. 一是某个类只能有一个实例

  2. 二是它必须自行创建这个实例

  3. 三是它必须自行向整个系统提供这个实例

为什么要使用PHP单例模式

php的应用主要在于数据库应用, 一个应用中会存在大量的数据库操作, 在使用面向对象的方式开发时, 如果使用单例模式, 则可以避免大量的new 操作消耗的资源,还可以减少数据库连接这样就不容易出现 too many connections情况。

一个类来全局控制某些配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php

namespace lib\mode;

/**
* Class Man
* @package lib\mode
* @desc 设计模式单例模式
* 1 私有化构造函数和克隆函数
* 2 提供方法实例化对象
*/
class Man
{
// 保存实例化对象属性
private static $instance;

/**
* Man constructor.
* @desc 私有化构造函数
*/
private function __construct()
{

}

/**
* __clone() 私有化克隆函数
*/
private function __clone()
{
return self::$instance;
}

/**
* getInstance() 提供公共方法获取对象
* @return Man
*/
public static function getInstance()
{
if (!self::$instance) {
self::$instance = new self();
}

return self::$instance;
}

/**
* 其他方法
*/
public function test()
{
echo '单例设计模式';
}
}
  • 二、简单工厂模式
  1. 抽象基类:类中定义抽象一些方法,用以在子类中实现
  2. 继承自抽象基类的子类:实现基类中的抽象方法

    • 工厂类:用以实例化所有相对应的子类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<?php

namespace lib\mode;

/**
* Class Factory
* @package lib\mode
* @desc 设计模式 简单工厂模式
* 1、提供一个工厂类,生产出指定类
* 2、提供一个抽象类,设计出需要抽象的方法,让子类实现
* 3、子类实现抽象基类
*/
class Factory
{
/**
* createObject() 工厂提供创建对象方法
* @param string $name 需要创建的类名
* @param mixed|null $config 配置信息
* @return bool
*/
public static function createObject($name, $config = null)
{
$mixReturn = false;
if ($name) {
$name = '\\lib\\mode\\'.$name;
if(class_exists($name)) {
try {
$mixReturn = new $name($config);
} catch (\Exception $e) {
$mixReturn = false;
}
}
}

return $mixReturn;
}
}

/**
* Class Operation
* @package lib\mode
* @desc 抽象基类, 提供抽象方法
*/
abstract class Operation
{
/**
* getValue() 提供抽象方法,计算两个值的操作后的值
* @param $value1
* @param $value2
* @return mixed
*/
abstract public function getValue($value1, $value2);
}

/**
* Class Add
* @package lib\mode
* @desc 加法类
*/
class Add extends Operation
{
public function getValue($value1, $value2)
{
return $value1 + $value2;
}
}

/**
* Class Subtract
* @package lib\mode
* @desc 减法类
*/
class Subtract extends Operation
{
public function getValue($value1, $value2)
{
return $value1 - $value2;
}
}

// 使用 加法类
$obj = Factory::createObject('Add');
var_dump($obj->getValue(8, 13));

// 使用 减法类
$obj = Factory::createObject('Subtract');
var_dump($obj->getValue(8, 13));
  • 三、观察者模式
    定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新【GOF95】 又称为发布-订阅(Publish-Subscribe)模式、模型-视图(Model-View)模式、源-监听(Source-Listener)模式、或从属者(Dependents)模式
    
  1. 抽象主题(Subject)角色:主题角色将所有对观察者对象的引用保存在一个集合中,每个主题可以有任意多个观察者。 抽象主题提供了增加和删除观察者对象的接口。

  2. 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在观察的主题发生改变时更新自己。

  3. 具体主题(ConcreteSubject)角色:存储相关状态到具体观察者对象,当具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。

  4. 具体观察者(ConcretedObserver)角色:存储一个具体主题对象,存储相关状态,实现抽象观察者角色所要求的更新接口,以使得其自身状态和主题的状态保持一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<?php
namespace lib\mode;

/**
* Class Observe
* @package lib\mode
* @desc 设计模式 观察者模式
* 1、监听器
* 2、被观察者
*/

/**
* Class Subject
* @package lib\mode
* @desc 抽象主题角色
*/
class Subject implements \SplSubject
{
private $observers;
private $name;

public function __construct($name)
{
$this->observers = new \SplObjectStorage();
$this->name = $name;
}

/**
* attach() 添加一个新的观察者对象
* @param \SplObserver $observer
*/
public function attach(\SplObserver $observer)
{
$this->observers->attach($observer);
}

/**
* detach() 删除一个已经注册过的观察者
* @param \SplObserver $observer
*/
public function detach(\SplObserver $observer)
{
$this->observers->detach($observer);
}

public function notify()
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}

public function getName()
{
return $this->name;
}
}

/**
* Class Observe
* @package lib\mode
* @desc 观察者角色信息类
*/
class Observe implements \SplObserver
{
public function update(\SplSubject $subject)
{
var_dump(__CLASS__. '-' . $subject->getName());
}
}

/**
* Class Observe
* @package lib\mode
* @desc 观察者角色信息类1
*/
class Observe1 implements \SplObserver
{
public function update(\SplSubject $subject)
{
var_dump(__CLASS__. '-' . $subject->getName());
}
}

// 使用说明
$observer1 = new Observe();
$observer2 = new Observe1();
$subject = new Subject("test");

$subject->attach($observer1);
$subject->attach($observer2);

$subject->notify();

排序算法

  • 冒泡排序

    思路分析:在要排序的一组数中,对当前还未排好的序列,从前往后对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即,每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    /**
    * 冒泡排序
    * @param array $array
    * @return array
    */
    function bubbleSort($array)
    {
    $length = count($array);
    if ($length > 1) {
    for ($i = 0; $i < $length; $i++) {
    for ($x = 0; $x < ($length - $i - 1); $x++) {
    if ($array[$x] > $array[$x + 1]) {
    $tmp = $array[$x + 1];
    $array[$x + 1] = $array[$x];
    $array[$x] = $tmp;
    }
    }
    }
    }

    return $array;
    }
  • 快速排序

    找到当前数组中的任意一个元素(一般选择第一个元素),作为标准,新建两个空数组,遍历整个数组元素,
    如果遍历到的元素比当前的元素要小,那么就放到左边的数组,否则放到右面的数组,然后再对新数组进行同样的操作,
    不难发现,这里符合递归的原理,所以我们可以用递归来实现。

使用递归,则需要找到递归点和递归出口:
递归点:如果数组的元素大于1,就需要再进行分解,所以我们的递归点就是新构造的数组元素个数大于1

递归出口:我们什么时候不需要再对新数组不进行排序了呢?就是当数组元素个数变成1的时候,所以这就是我们的出口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 快速排序
* @param array $array
* @return array
*/
function quickSort(array $array)
{
if ($array && is_array($array)) {
$length = count($array);
if ($length > 1) {
$left = $right = [];
for ($i = 1; $i < $length; $i ++) {
// 判断当前元素的大小和第一个元素比较
if ($array[$i] < $array[0]) {
$left[] = $array[$i];
} else {
$right[] = $array[$i];
}
}

// 递归调用
if ($left) {
$left = quickSort($left);
}

if ($right) {
$right = quickSort($right);
}

return array_merge($left, [$array[0]], $right);
}
}

return $array;
}
  • 选择排序

    思路分析:在要排序的一组数中,选出最小的一个数与第一个位置的数交换。然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 选择排序
* 在要排序的一组数中,选出最小的一个数与第一个位置的数交换。然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
* @param array $array
* @return array
*/
function selectSort(array $array)
{
$length = count($array);
if ($length > 1) {

for ($i = 0; $i < $length - 1; $i ++) {
$index = $i;
for ($x = $i + 1; $x < $length; $x ++) {
// 默认$array[$index] 为最小值
if ($array[$index] > $array[$x]) {
// 记录最小值
$index = $x;
}
}

// 已经确定最小值的位置,保持到$index中;如果发现最小值的位置与当前假设的位置$i不同,则位置互换即可。
if ($index != $i) {
$tmp = $array[$index];
$array[$index] = $array[$i];
$array[$i] = $tmp;
}
}
}

return $array;
}
  • 插入排序

    在要排序的一组数中,假设前面的数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

    /**
    * 插入排序
    * 在要排序的一组数中,假设前面的数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
    * @param array $array
    * @return array
    */
    function insertSort(array $array)
    {
    $length = count($array);
    if ($length > 0) {
    for ($i = 1; $i < $length; $i ++) {
    $tmp = $array[$i];

    // 内层循环,比较插入
    for ($x = $i - 1; $x >= 0; $x --) {
    if ($tmp < $array[$x]) {
    // 发现插入的元素要小,交换位置,将后边的元素与前面的元素互换
    $array[$x + 1] = $array[$x];
    $array[$x] = $tmp;
    } else {
    break;
    }
    }
    }
    }

    return $array;
    }

PHP面试-基础一

发表于 2019-05-09 | 更新于 2019-08-22
  • PHP中数组 + 和 array_merge 区别
  1. 当键名为数字时,array_merge不会覆盖原来的值,+会抛弃后面的值

    1
    2
    3
    4
    5
    $array1 = [1, 2];
    $array2 = [3, 4];

    var_dump($array1 + $array2); // [1, 2]
    var_dump(array_merge($array1, $array2)); // [1, 2, 3, 4]
  2. 当键名为字符串时,+ 会舍弃掉后面键相同的元素,array_merge键相同的后面元素覆盖前面元素

    1
    2
    3
    4
    5
    $array1 = ['user' => 1, 'name' => 2];
    $array2 = ['user' => 3, 'name' => 4];

    var_dump($array1 + $array2); // ['user' => 1, 'name' => 2]
    var_dump(array_merge($array1, $array2)); // ['user' => 3, 'name' => 4]
  • GET和POST的区别?
  1. GET在浏览器回退时是无害的,而POST会再次提交请求。
  2. GET产生的URL地址可以被Bookmark,而POST不可以。
  3. GET请求会被浏览器主动cache,而POST不会,除非手动设置。
  4. GET请求只能进行url编码,而POST支持多种编码方式。
  5. GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
  6. GET请求在URL中传送的参数是有长度限制的,而POST没有。
  7. 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
  8. GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
  9. GET参数通过URL传递,POST放在Request body中。
  10. GET产生一个TCP数据包,POST产生两个TCP数据包。
  • COOKIE和SESSION的区别?
  1. COOKIE保存在客户端,而SESSION则保存在服务器端
  2. 从安全性来讲,SESSION的安全性更高
  3. 从保存内容的类型的角度来讲,COOKIE只保存字符串(及能够自动转换成字符串)
  4. 从保存内容的大小来看,COOKIE保存的内容是有限的,比较小,而SESSION基本上没有这个限制
  5. 从性能的角度来讲,用SESSION的话,对服务器的压力会更大一些
    SEEION依赖于COOKIE,但如果禁用COOKIE,也可以通过url传递
  • 单引号和双引号的区别?
  1. 单引号不解析特殊字符,双引号能够解析变量和特殊字符串
  2. 单引号的速度大于双引号
  • isset 和 empty 的区别?
  1. isset 判断变量是否已经定义,empty 判断一个变量是否为空
  2. 当变量为空、空字符、0, isset 和 empty 返回都为 true
  3. 当变量未定义、或者变量为null, isset 返回 false, empty 返回ture
  • echo、print、print_r、var_dump 的区别?
  1. echo: 可以一次输出多个值,多个值之间用逗号分隔。echo是语言结构(language construct),而并不是真正的函数,因此不能作为表达式的一部分使用。
  2. print: 函数print()打印一个值(它的参数),如果字符串成功显示则返回true,否则返回false。
  3. print_r: 可以把字符串和数字简单地打印出来,而数组则以括起来的键和值得列表形式显示,并以Array开头。但print_r()输出布尔值和NULL的结果没有意义,因为都是打印”\n”。因此用var_dump()函数更适合调试。
  4. var_dump: 判断一个变量的类型与长度,并输出变量的数值,如果变量有值输的是变量的值并回返数据类型。此函数显示关于一个或多个表达式的结构信息,包括表达式的类型与值。数组将递归展开值,通过缩进显示其结构。
  • 什么是MVC?

MVC是一种软件架构的思想,将软件按照模型、视图、控制器
来划分。模型负责封装业务处理逻辑,视图负责输入和输出(
表示逻辑),控制器负责协调模型和视图。

  • Model 模型:

需要先写接口,然后实现接口中声明的方法。
业务处理逻辑:业务本身的处理流程,另外,还包括
为保证业务处理正常可靠执行的基础服务(事务、安全、
日志等等)。

  • View 视图:

输入:提供相应的操作界面,方便用户使用。
输出:将模型返回的结果以合适的方式来展现。

  • Controller 控制器:

协调: 视图向控制器发请求,由控制器来选择相应的
模型来处理;模型返回的结果给控制器,由控制器来
选择合适的视图,生成相应的界面给用户。

  • 传值和传引用的区别?
  1. 按值传递:函数内对值的任何改变在函数外部都会被忽略。
  2. 引用传递:函数内对值的任何改变在函数外部也能反映出这些修改。
  3. 应用场景:按值传递时,php必须复制值,而按引用传递则不需要复制值,故引用传递一般用于大字符串或对象。

进阶篇

  • 简述 S.O.L.I.D 设计原则
- - -
SRP 单一职责原则 一个类有且只有一个更改的原因
OCP 开闭原则 能够不更改类而扩展类的行为
LSP 里氏替换原则 派生类可以替换基类使用
ISP 接口隔离原则 使用客户端特定的细粒度接口
DIP 依赖反转原则 依赖抽象而不是具体实现
  • PHP7 和 PHP5 的区别,具体多了哪些新特性?
  1. 性能提升了两倍
  2. 增加了结合比较运算符 (<=>)
  3. 增加了标量类型声明、返回类型声明
  4. try...catch 增加多条件判断,更多 Error 错误可以进行异常处理
  5. 增加了匿名类,现在支持通过new class 来实例化一个匿名类,这可以用来替代一些“用后即焚”的完整类定义
  • 为什么 PHP7 比 PHP5 性能提升了?
  1. 变量存储字节减小,减少内存占用,提升变量操作速度
  2. 改善数组结构,数组元素和 hash 映射表被分配在同一块内存里,降低了内存占用、提升了 cpu 缓存命中率
  3. 改进了函数的调用机制,通过优化参数传递的环节,减少了一些指令,提高执行效率
  • 简述一下 PHP 垃圾回收机制(GC)

PHP 5.3 版本之前都是采用引用计数的方式管理内存,PHP 所有的变量存在一个叫 zval 的变量容器中,当变量被引用的时候,引用计数会+1,变量引用计数变为0时,PHP 将在内存中销毁这个变量。

但是引用计数中的循环引用,引用计数不会消减为 0,就会导致内存泄露。

在 5.3 版本之后,做了这些优化:

  1. 并不是每次引用计数减少时都进入回收周期,只有根缓冲区满额后在开始垃圾回收;
  2. 可以解决循环引用问题;
  3. 可以总将内存泄露保持在一个阈值以下。

了解更多可以查看 PHP 手册,垃圾回收机制。

  • 如何解决 PHP 内存溢出问题
  1. 增大 PHP 脚本的内存分配
  2. 变量引用之后及时销毁
  3. 将数据分批处理
  • Redis、Memecached 这两者有什么区别?
  1. Redis 支持更加丰富的数据存储类型,String、Hash、List、Set 和 Sorted Set。Memcached 仅支持简单的 key-value 结构。
  2. Memcached key-value存储比 Redis 采用 hash 结构来做 key-value 存储的内存利用率更高。
  3. Redis 提供了事务的功能,可以保证一系列命令的原子性
  4. Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中
  5. Redis 只使用单核,而 Memcached 可以使用多核,所以平均每一个核上 Redis 在存储小数据时比 Memcached 性能更高。
  • Redis 如何实现持久化?
  1. RDB 持久化,将 Redis 在内存中的的状态保存到硬盘中,相当于备份数据库状态。
  2. AOF 持久化(Append-Only-File),AOF 持久化是通过保存 Redis 服务器锁执行的写状态来记录数据库的。相当于备份数据库接收到的命令,所有被写入 AOF 的命令都是以 Redis 的协议格式来保存的。

Web 安全防范

  • CSRF 是什么?如何防范?

CSRF(Cross-site request forgery)通常被叫做「跨站请求伪造」,可以这么理解:攻击者盗用用户身份,从而欺骗服务器,来完成攻击请求。

防范措施:

  1. 使用验证码
  2. 给每一个请求添加令牌 token 并验证
  • XSS 是什么?如何防范?

XSS(Cross Site Scripting),跨站脚本攻击,攻击者往 Web 页面里插入恶意 Script 代码,当用户浏览该页之时,嵌入其中 Web 里面的 Script 代码会被执行,从而达到恶意攻击用户的目的。

防止 XSS 攻击的方式有很多,其核心的本质是:永远不要相信用户的输入数据,始终保持对用户数据的过滤。

  • 什么是 SQL 注入?如何防范?

SQL 注入就是攻击者通过一些方式欺骗服务器,结果执行了一些不该被执行的 SQL。

SQL 注入的常见场景

  1. 数据库里被注入了大量的垃圾数据,导致服务器运行缓慢、崩溃。
  2. 利用 SQL 注入暴露了应用程序的隐私数据

防范措施:

  1. 保持对用户数据的过滤
  2. 不要使用动态拼装 SQL
  3. 增加输入验证,比如验证码
  4. 对隐私数据加密,禁止明文存储

说明

  • 本文载自:https://github.com/todayqq/PHPerInterviewGuide/blob/master/php.md

扩展阅读

  • 3年PHPer的面试总结
  • 垃圾回收机制
  • S.O.L.I.D 面向对象设计
  • 浅谈IOC–说清楚IOC是什么
  • Redis和Memcached的区别
  • CSRF攻击与防御
  • XSS跨站脚本攻击

PHP证书加密和解密

发表于 2019-05-09 | 更新于 2019-08-22

加密签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

/**
* 获取签名
*
* @param string $str 需要加密的字符串
* @param string $rsaPrivateFile 私钥文件地址
*
* @return string
*/
function getRsaSign($str, $rsaPrivateFile)
{
$privateKey = file_get_contents($rsaPrivateFile);
$resource = openssl_get_privatekey($privateKey);
openssl_sign($str, $sign, $resource, OPENSSL_ALGO_SHA256);
openssl_free_key($resource);
return base64_encode($sign);
}

秘钥验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 验证签名
*
* @param string $sign 需要验证的签名字符串
* @param string $str 参与加密的字符串
* @param string $rsaPublicFile 公钥文件地址
*
* @return bool
*/
function checkRsaSign($sign, $str, $rsaPublicFile)
{
$public_key = file_get_contents($rsaPublicFile);
$resource = openssl_get_publickey($public_key);
$result = (bool)openssl_verify($str, $sign, $resource, OPENSSL_ALGO_SHA256);
openssl_free_key($resource);
return $result;
}
12
jinxing.liu@qq.com

jinxing.liu@qq.com

刘金星个人博客
13 日志
8 标签
GitHub E-Mail
© 2019 jinxing.liu@qq.com
由 Hexo 强力驱动 v3.8.0
|
主题 – NexT.Pisces v7.1.1
优秀博客: fifsky的技术笔记 | littlebug