NIUHE

日々私たちが过ごしている日常というのは、実は奇迹の连続なのかもしれんな

记录:用PHP实现简单web框架

这个框架功能比较简单,只有url拦截及自定义、加载静态模板两个功能,之前的博客也是基于这个框架实现的。第一部分是记录实现这个框架所需的php基础知识,第二部分是这两个功能的实现方法。最后还有实现自动登录的最佳实践。

php基础相关

$_SERVER

服务器和执行环境信息

php手册

几个用到的项: * 'REQUEST_METHOD' : 访问页面使用的请求方法;例如,“GET”,“HEAD”“POST”“PUT”。 * 'REQUEST_URI' : URI 用来指定要访问的页面。例如 “/index.html”

Date

1
date(format,timestamp)
参数 | 描述 |
:--- | : --- |
format | 必需。规定如何返回结果。|
timestamp | 可选。 |

格式化方式说明

格式化方式 说明
Y 4位数字年,y为2位数字,如99即1999年
m 数字月份,前面有前导0,如01。n 为无前导0数字月份
F 月份,完整的文本格式,例如 January 或者 March
M 三个字母缩写表示的月份,例如 Jan 或者 Mar
d 月份中的第几天,前面有前导0,如03。j 为无前导0的天数
w 星期中的第几天,以数字表示,0表示星期天
z 年份中的第几天,范围0-366
W 年份中的第几周,如第32周
H 24小时格式,有前导0,h为12小时格式
G 24小时格式,无前导0,g为对应12小时格式
i 分钟格式,有前导0
s 秒格式,有前导0
A 大写上下午,如AM,a为小写

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
echo("Result with date():<br />");
echo(date("l") . "<br />");
echo(date("l dS \of F Y h:i:s A") . "<br />");
echo("Oct 3,1975 was on a ".date("l", mktime(0,0,0,10,3,1975))."<br />");
echo(date(DATE_RFC822) . "<br />");
echo(date(DATE_ATOM,mktime(0,0,0,10,3,1975)) . "<br /><br />");

echo("Result with gmdate():<br />");
echo(gmdate("l") . "<br />");
echo(gmdate("l dS \of F Y h:i:s A") . "<br />");
echo("Oct 3,1975 was on a ".gmdate("l", mktime(0,0,0,10,3,1975))."<br />");
echo(gmdate(DATE_RFC822) . "<br />");
echo(gmdate(DATE_ATOM,mktime(0,0,0,10,3,1975)) . "<br />");
?>

结果: > Result with date(): Tuesday Tuesday 24th of January 2006 02:41:22 PM Oct 3,1975 was on a Friday Tue, 24 Jan 2006 14:41:22 CET 1975-10-03T00:00:00+0100

Result with gmdate(): Tuesday Tuesday 24th of January 2006 01:41:22 PM Oct 3,1975 was on a Thursday Tue, 24 Jan 2006 13:41:22 GMT 1975-10-02T23:00:00+0000

字符串

explode()分割字符串,返回数组。

1
explode(delimiter, string)

随机字符串

  1. 预置一个的字符串 $chars ,包括 a – z,A – Z,0 – 9,以及一些特殊字符。
  2. 在 $chars 字符串中随机取一个字符。
  3. 重复第二步n次,可得长度为n的字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function generate_password( $length = 8 ) {  
// 密码字符集,可任意添加你需要的字符
$chars = ‘abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|’;
$password = ”;
for ( $i = 0; $i < $length; $i++ )
{
// 这里提供两种字符获取方式
// 第一种是使用 substr 截取$chars中的任意一位字符;
// 第二种是取字符数组 $chars 的任意元素
// $password .= substr($chars, mt_rand(0, strlen($chars) – 1), 1);
$password .= $chars[ mt_rand(0, strlen($chars) - 1) ];
}
return $password;
}

加密

sha256

1
$password = hash('sha256', $identifier . $_POST['password']);

Session

每次用之前一定要session_start()

1
2
session_start();
$_SESSION[$var] = xxxx;

Cookie

如何创建 cookie?

setcookie()函数用于设置 cookie。

语法

1
setcookie(name, value, expire, path, domain);

例子 在下面的例子中,我们将创建名为 "user" 的 cookie,把为它赋值 "Alex Porter"。我们也规定了此 cookie 在一小时后过期:

1
2
3
4
5
6
7
8
9
<?php 
setcookie("user", "Alex Porter", time()+3600);
?>

<html>
<body>

</body>
</html>

注释:在发送 cookie 时,cookie 的值会自动进行 URL 编码,在取回时进行自动解码(为防止 URL 编码,请使用 setrawcookie() 取而代之)。

如何取回 Cookie 的值?

PHP 的$_COOKIE变量用于取回 cookie 的值。 在下面的例子中,我们取回了名为 "user"的 cookie 的值,并把它显示在了页面上:

1
2
3
4
5
6
7
<?php
// Print a cookie
echo $_COOKIE["user"];

// A way to view all cookies
print_r($_COOKIE);
?>

如何删除 cookie?

当删除 cookie 时,您应当使过期日期变更为过去的时间点。 删除的例子:

1
2
3
4
<?php 
// set the expiration date to one hour ago
setcookie("user", "", time()-3600);
?>

变参

1
2
3
4
5
6
7
8
9
10
11
<?php
function variable()
{
echo func_num_args(); //输出参数个数
$varArray = func_get_args(); //获取参数,返回参数数组
foreach($varArray as $value)
echo $value;

echo func_get_arg; //获取单个参数
}
?>

反射

参考:PHP的反射机制 php手册

建立反射类

1
2
$class = new ReflectionClass('Person');//建立 Person这个类的反射类  
$instance = $class->newInstanceArgs($args);//相当于实例化Person 类

获取属性(Properties)

1
2
3
4
$properties = $class->getProperties();  
foreach($properties as $property) {
echo $property->getName()."\n";
}

获取注释

1
2
3
4
5
6
7
foreach($properties as $property) {  
if($property->isProtected()) {
$docblock = $property->getDocComment();
preg_match('/ type\=([a-z_]*) /', $property->getDocComment(), $matches);
echo $matches[1]."\n";
}
}

获取类的方法

1
2
3
if($class->hasMethod($methodName)){
$method = $class->getMethod($methodName);
}

执行类的方法

1
2
3
4
5
6
$instance->getBiography(); //执行Person 里的方法getBiography  
//或者:
$ec=$class->getmethod('getName'); //获取Person 类中的getName方法
$ec->invoke($instance); //执行getName 方法
// 或者:
$method->invokeArgs($instance, $params); // $params为数组

URI

uri结构:apppath/class/method/params uri示例 : www.liuhe.website/index.php?/Articles/single/13

实现方法:

  • index.php中通过$_SERVER['REQUEST_URI']获取uri,然后获取className, methodName以及params
  • include相应控制器的文件,即controller/$className.php
  • 建立反射类,调用相关method传入相应参数。
  • 防止直接通过文件目录访问(通过服务器配置rewrite实现)

code:

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
/**
* Parse uri , instantiate the class
* and invoke the method
*/
public function parseRequestUri()
{
if (isset($_SERVER['REQUEST_URI'])) {
$this->uri_string = $_SERVER['REQUEST_URI'];
$this->segments = explode('/', $this->uri_string);
// var_dump($this->segments);
/*
* get the class name, method name and the params
*/
if (count($this->segments) <= 2) {
$className = 'Articles';
$methodName = 'home';
} else {
$className = $this->segments[2];
if (isset($this->segments[3])) {
$methodName = $this->segments[3];
} else {
// 404 Error
$this->_404Error("Empty Method");
}
}
if ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'POST') {
$params = array();
$len = count($this->segments);
if ($len > 4) {
for ($i = 4; $i < $len; $i++) {
array_push($params, $this->segments[$i]);
}
}
$classFile = "controller/$className.php";
if (!file_exists($classFile)) {
// 404 error
$this->_404Error("File Not Found");
return;
}
/*
* build reflection class, invoke the method
*/
require "$classFile";
$rc = new ReflectionClass($className);
if (!$rc->hasMethod($methodName)) {
// 404 error
$this->_404Error("Method Not Found");
return;
}
$instance = $rc->newInstance();
$method = $rc->getMethod($methodName);
$method->invokeArgs($instance, $params);
}
} else {
echo "not set REQUEST_URI";
return;
}
}

加载静态模板(带参数)

实现方法: * 定义参数结构(占位符): * i 为第i个参数 * 把模板文件加载到内存,用字符串替换函数替换上述占位符。

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Load a static template
*
* @var string, array
*/
public function load($page = '', $args = array())
{
if (empty($page)) {
return;
}
$fileName = "static/$page";
$file = fopen($fileName, "r");
$content = fread($file, filesize($fileName));
if (!empty($args)) {
$i = 0;
while (strpos($content, "{{args[$i]}}") !== false) {
$content = str_replace("{{args[$i]}}", $args[$i], $content);
$i += 1;
}
}
echo "$content";
}

实现自动登录

以下部分来自StackOverflow

Improved Persistent Login Cookie Best Practice

  1. When the user successfully logs in with Remember Me checked, a login cookie is issued in addition to the standard session management cookie.
  2. The login cookie contains a series identifier and a token. The series and token are unguessable random numbers from a suitably large space. Both are stored together in a database table, the token is hashed (sha256 is fine).
  3. When a non-logged-in user visits the site and presents a login cookie, the series identifier is looked up in the database.
    1. If the series identifier is present and the hash of the token matches the hash for that series identifier, the user is considered authenticated. A new token is generated, a new hash for the token is stored over the old record, and a new login cookie is issued to the user (it's okay to re-use the series identifier).
    2. If the series is present but the token does not match, a theft is assumed. The user receives a strongly worded warning and all of the user's remembered sessions are deleted.
    3. If the username and series are not present, the login cookie is ignored.

This approach provides defense-in-depth. If someone manages to leak the database table, it does not give an attacker an open door for impersonating users.

Powered by Hexo and Theme by Hacker
© 2019 NIUHE