记几道面试题,面试的时候大概做了9个左右。

后面三道题是算法题,前面六道题是基础题,面试时候有十二道题,最后面三道题是设计算法题,具体的忘记了,不记。(原本计划分两篇记,但是算法部分已经忘记)。

静态方法与魔术常量

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 A {
public static function say() {
echo __CLASS__ ;
}
}

class B extends A {
public static function say() {
echo __CLASS__ ;
}
public static function toSay() {
A::say();
parent::say();
self::say();
}
}

class C extends B {
public static function say() {
echo __CLASS__ ;
}
}

(new C)::toSay();

大概长这样子的。不注意,脑子一抽,下意识地就会填ABC,再想想,这是个坑,静态方法是属于类本身的,是先于对象的存在。所以答案是:AAB

指针的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$arr = [2,3,4];
foreach ($arr as $key => $value) {
var_dump(current($arr));
}

$arr = [2,3,4];
foreach ($arr as $value) {
var_dump(current($arr));
}

$arr = [2,3,4];
$b = &$arr;
foreach ($arr as $key => $value) {
var_dump(current($arr));
}

大概长这样子。这个题考点有两个:

  • 数组指针操作函数使用
  • foreach的原理

php的数组指针操作函数的使用

php的数组指针操作函数见文档PHP:current,就几个:

  • prev()
  • next()
  • current()
  • end()
  • key()
  • each()

current即返回当前指针指向的元素,默认指向数组第一个元素。相比较而言,each使用的场景更多些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$arr = [2,3,4,"sb"];
var_dump(current($arr));
var_dump(next($arr));
var_dump(current($arr));
var_dump(prev($arr));
var_dump(end($arr));
var_dump(current($arr));

$arr = [2, 3, 4, 5, 6, "12", "sb", 'dd' => 'zz'];
echo each($arr)['key'] . ":" . prev($arr) . "\n";
while (list($key, $value) = each($arr)) {
var_dump("$key : $value");
}

foreach的原理

说到foreach这东西,就离不开Iterator,说到Iterator,就离不开Collection。PHP里没有List、Set、Map之类的东西,全部用Array实现,所以有人说PHP的核心在于底层的HashTable。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Traversable {

}

Iterator extends Traversable {
abstract public mixed current ( void )
abstract public scalar key ( void )
abstract public void next ( void )
abstract public void rewind ( void )
abstract public boolean valid ( void )
}

IteratorAggregate extends Traversable {
abstract public Traversable getInterator ( void );
}

Traversable是一个内建抽象接口,无法被实现,仅用来判断类是否可以被遍历。通常是拿Iterator和IteratorAggregate做实现。php.net上的例子:

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
<?php
class myIterator implements Iterator {
private $position = 0;
private $array = array(
"firstelement",
"secondelement",
"lastelement",
);

public function __construct() {
$this->position = 0;
}

function rewind() {
var_dump(__METHOD__);
$this->position = 0;
}

function current() {
var_dump(__METHOD__);
return $this->array[$this->position];
}

function key() {
var_dump(__METHOD__);
return $this->position;
}

function next() {
var_dump(__METHOD__);
++$this->position;
}

function valid() {
var_dump(__METHOD__);
return isset($this->array[$this->position]);
}
}

$it = new myIterator;

foreach($it as $key => $value) {
var_dump($key, $value);
echo "\n";
}
?>

//result
string(18) "myIterator::rewind"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(0)
string(12) "firstelement"

string(16) "myIterator::next"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(1)
string(13) "secondelement"

string(16) "myIterator::next"
string(17) "myIterator::valid"
string(19) "myIterator::current"
string(15) "myIterator::key"
int(2)
string(11) "lastelement"

string(16) "myIterator::next"
string(17) "myIterator::valid"

扯远了,这里不是对类进行遍历,而是对数组进行变量。而本题的重点在于foreach遍历数组的过程中会对指针状态进行一个临时保存,也就是说,你在foreach块里面是改不了指针状态的。写个例子:

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
$arr = array(1,2,3,4,5);
foreach($arr as $key => &$row) {
$row = $row *2;
echo key($arr), '=>', current($arr), "\r\n";
end($arr);
echo '$key:' . $key . " => " . '$value:' .$row . "\r\n";
}
unset($row);
var_dump($arr);

//result
0=>2
$key:0 => $value:2
4=>5
$key:1 => $value:4
4=>5
$key:2 => $value:6
4=>5
$key:3 => $value:8
4=>10
$key:4 => $value:10
array(5) {
[0]=>
int(2)
[1]=>
int(4)
[2]=>
int(6)
[3]=>
int(8)
[4]=>
int(10)
}

这个例子里,可以看到,你可以对数组的值进行引用,然后改变元素的值,但是你无法改变被临时保存的指针的指向。详细的可以看TIPI-foreach的实现,注意后面的指针的意外行为的内容,再结合面试题的第三个例子,想想面试官可能是想考察这方面内容,但是我在实际实践的过程中,并没有出现指向第二个元素的情况出现,这里不做深入了解。

魔术方法

第三题应该是整个面试题里最简单的一道题,就是考察应试者对魔术方法的记忆

简述下列方法的使用场景:
construct(),destruct(),call(), callStatic(),set(),get(),isset(), unset(),autoload(),toString(),
clone(),sleep(),__wakeup()

几个不常用的:

  1. __destruct()即析构函数,在类被释放的时候,触发的方法,通常在关闭文件,释放结果集的时候使用。
  2. isset()与unset(),平时不注意就不知道这两个是干嘛的,其实就是尝试判断或者删除变量的时候触发
  3. __autoload(), 这个常常记成实例化时候的自动加载,其实是尝试加载未定义的类
  4. __clone(),被克隆时候用
  5. sleep(),wakeup(),在序列化、反序列化的时候触发

HTTP协议

第四题应该是最难的一道题,因为如果应试者不是太关注HTTP协议的话很难写出HTTP报文格式。

HTTP协议有那些请求方法,有什么区别,写出请求报文与响应报文格式,写出常见的HTTP请求头与响应头字段,写出常见的响应码与对应的含义。

常见6种请求方法

  • GET
  • PUT
  • HEAD
  • POST
  • DELETE
  • OPTIONS

GET和POST就不说了,用得最多。PUT与POST、GET的区别在于PUT会指定资源的位置,HTML表单也不支持这个协议,所以这个方法基本上不会被使用。HEAD仅用来判断资源是否存在,不含数据。DELETE即删除某一个资源。OPTIONS用来判断一个资源支持的请求方法。

请求报文与响应报文格式

请求报文一般这个格式:

1
2
3
4
请求行,例如:GET /images/logo.png HTTP/1.1
请求头,例如:Accept: text/plain
空行
可选择的请求体

响应报文一般这个格式:

1
2
3
4
包含状态码的状态行,例如:HTTP/1.1 200 OK
响应头,例如:Content-Type: text/html
空行
可选择的消息体

这玩意得动手弄弄才记得清楚。先把本地服务器(ArchLinux)的Telnet-server给打开,socket是啥呢?可以看看systemd.socket

1
2
3
4
5
6
7
systemctl start telnet.socket

netstat -nlp | grep :23
tcp6 0 0 :::23 :::* LISTEN 1/systemd

//测试
telnet localhost

不要问我为什么多此一举地打开服务器的telnet-server,直接本地telnet不就可以了么,因为我习惯在linux下操作,而且本地服务器是可以穿wall的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//先连接到本地服务器
telnet lan

//连接谷狗的http
telnet www.google.com 80

//请求
GET /index.html HTTP/1.1
HOST: www.google.com

//回应
HTTP/1.1 302 Found
Location: http://www.google.com.hk/url?sa=p&hl=zh-CN&pref=hkredirect&pval=yes&q=http://www.google.com.hk/index.html%3Fgws_rd%3Dcr&ust=1501472919811303&usg=AFQjCNHkemqeUOExzBFPRxWdDKSV-FutIg
Cache-Control: private
Content-Type: text/html; charset=UTF-8
P3P: CP="This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info."
Date: Mon, 31 Jul 2017 03:48:09 GMT
Server: gws
Content-Length: 400
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Set-Cookie: NID=108=gFvAS2787nuBm2FKhba2zGXv4RpYeNj3I3nXCrQ7XigPUTE-FUEh7gIdyHfG3XqLEvILb9fD2C8FFZdvXRauMQ8Js8tJ2zjc2OaGk4wT4MqsLJP-UgiG9EyBC6eqNtwx; expires=Tue, 30-Jan-2018 03:48:09 GMT; path=/; domain=.google.com; HttpOnly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//请求
POST / HTTP/1.1
Accept: */*
Host: google.com
Referer: www.qq.com
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
Content-Length:12

content=good

//回应
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD
Date: Mon, 31 Jul 2017 08:40:20 GMT
Content-Type: text/html; charset=UTF-8
Server: gws
Content-Length: 1589
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN

常见HTTP的头部字段

  • Accept
  • Accept-Charset
  • Accept-Encoding
  • Accept-Language
  • Cache-Control
  • Content-Length
  • Content-Type
  • Cookie
  • Date
  • Expect
  • ….

具体的见Github上整理的List_of_HTTP_header_fields.xlsx

常见的响应码

  • 200:正常状态,表示成功回应
  • 301:永久重定向
  • 302:临时重定向
  • 304:被告知可以使用本地缓存
  • 400:语法错误
  • 401:未授权
  • 403:资源不可用
  • 404:无法访问指定路径资源
  • 500:服务器意外,不能完成请求
  • 502:网关错误,即服务器访问下一个服务器或者应用的时候获取了非法的回复
  • 504:网关超时,代理无法完成

Web安全

这题也算简单,算是web安全的常识。

解释什么是会话劫持、XSS、CSRF。

  • 会话劫持: 就是拿到的了cookie验证了session,获取了用户的登录状态或者其他状态。
  • xss:xss分两种:
    1. 反射型xss:非持久化,需要欺骗用户自己去点击链接才能触发XSS代码,达到会话劫持的目的。
    2. 存储型xss:持续化的,一般存储在数据库或者页面里,用户一旦访问就触发。
  • CSRF:即跨站点请求伪造,即用户带着源站的cookie访问了钓鱼网站,钓鱼网站会要求用户携带恶意代码访问源站。

SQL

这题考的是应试者对mysql的date系列函数的应用。

一个数据表里有三个字段:idapp_iptime,这个表里有一个月的数据,要求按每天统计数量并且按数量与天数排序。

注意:这里的time是没有给数据类型的,默认认为是timestamp类型。建个表,结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mysql> desc sale \G
*************************** 1. row ***************************
Field: id
Type: int(10)
Null: NO
Key: PRI
Default: NULL
Extra: auto_increment
*************************** 2. row ***************************
Field: app_id
Type: int(10)
Null: NO
Key:
Default: 0
Extra:
*************************** 3. row ***************************
Field: time
Type: timestamp
Null: NO
Key:
Default: CURRENT_TIMESTAMP
Extra: on update CURRENT_TIMESTAMP
3 rows in set (0.00 sec)

简单写个栗子:

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
class InserRandomData {

private $SaleEntity;

public function __construct() {
$this->SaleEntity = (new SQLConnect("sale"))->getSqlResource();
}

/*
* @param Integer $min,$max
* @param String $startTime, $endTime like: $startTime=2017/7/1 $endTime=2017/7/31
* @param Integer $count
*/
public function execute($count, $min, $max, $startTime, $endTime) {
list($startYear, $startMonth, $startDay) = explode('/', $startTime);
list($endYear, $endMonth, $endDay) = explode('/', $endTime);
$day = round(
(
mktime(0, 0, 0, $endMonth, $endDay, $endYear)
-
mktime(0, 0, 0, $startMonth, $startDay, $startYear)
) / 3600 / 24) + 1;
for ($i=0; $i < $day; $i++) {
$countEverday = $count;
while ($countEverday-- > 0) {
$timeStamp = date('Y-m-d H:i:s', mktime(
mt_rand(0, 24),
mt_rand(0, 60),
mt_rand(0, 60),
$startMonth, $startDay + $i, $startYear
)
);
$app_id_count = mt_rand($min, $max);
$sqlString =
"insert into sale(`app_id`, `time`)
values
(:app_id_count, :timeStamp);";
try {
$sth = $this->SaleEntity->prepare($sqlString);
$sth->execute([
':app_id_count' => $app_id_count,
':timeStamp' => $timeStamp
]);
} catch (PDOException $e) {
echo $e->getCode(), "\n", $e->getMessage();
}
}
}
}
}

(new InserRandomData)->execute(100, 0, 100, "2017/7/1", "2017/7/31");

执行之后sale表里多了3000条数据。回到正题,这里我们需要写一条sql语句,完成题目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//1.先统计,按时间分组
select sum(`app_id`) as app_id_count, time from sale \
group by time \
order by time asc;

//2.选择月份后按照每天分组排序
select \
sum(`app_id`) as app_id_count, \
date_format(`time`, '%Y%m%d') as day \
from sale \
where month(`time`) = '7' \
group by day \
order by app_id_count, day asc;

//3.最后还可以筛选数量和日期
select \
sum(`app_id`) as app_id_count, \
date_format(`time`, '%Y%m%d') as day \
from sale \
where month(`time`) = '7' \
group by day \
having app_id_count > '5000' && day(`day`) > 20 \
order by app_id_count, day asc;