系列文章---框架篇:1.框架目录与辅助


那么就利用composer来开始我们的项目吧。

新建目录并进入目录,输入命令:

composer init

命令行会跟你确认以下信息(以下信息可以自行DIY)

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
# 1. 输入项目命名空间
# 注意<vendor>/<name> 必须要符合 [a-z0-9_.-]+/[a-z0-9_.-]+
Package name (<vendor>/<name>) yourname/projectname

# 2. 项目描述
Description []:这是一个测试composer init 项目

# 3. 输入作者信息
Author [13sai <sai0556@qq.com>, n to skip]:

# 4. 输入最低稳定版本,stable, RC, beta, alpha, dev
Minimum Stability []:dev

# 5. 输入项目类型
Package Type (e.g. library, project, metapackage, composer-plugin) []:project

# 6. 输入授权类型
License []:MIT

Define your dependencies.

# 7. 输入依赖信息
Would you like to define your dependencies (require) interactively [yes]?

# 7.1. 如果需要依赖,则输入要安装的依赖
Search for a package:php

# 7.2. 输入版本号
Enter the version constraint to require (or leave blank to use the latest version): >=5.4.0

# 如需多个依赖,则重复以上两个步骤(7.1/7.2)
Search for a package:

# 8. 是否需要require-dev,
Would you like to define your dev dependencies (require-dev) interactively [yes]?


{
"name": "sai/saif",
"description": "php framework",
"type": "project",
"require": {
},
"license": "MIT",
"authors": [
{
"name": "13sai",
"email": "sai0556@qq.com"
}
],
"minimum-stability": "dev"
}

# 9. 是否生成composer.json
Do you confirm generation [yes]?

# 现在安装依赖项吗
Would you like to install dependencies now [yes]?

我们的目录下会生成composer.json。


然后我们来思考一个问题:

你觉得一个基础的API框架需要什么模块呢?

下面是我的思考结果:

  • 路由
  • 请求
  • 数据响应
  • 异常处理
  • 日志系统…

当然,你可能觉得还应该有:

  • 配置
  • session
  • 缓存
  • 验证
  • 模型
  • 服务层
  • 文件上传…

也许你想到更多:

  • 任务调度
  • 队列
  • 用户验证

那么这么些我们如何取舍呢?手心手背都是肉啊。

这里需要做一下说明,我们所做的框架无需考虑太多的功能,是做一个简单可用的API接口框架,我们接受get/post请求,返回json数据,并且路由好用,这是我们的初衷,其他的暂且就“断舍离”吧。

我们先画一个极简的流程图。

简单流程图

我们一切从简,所以我们定义以下几个模块:

  • 路由
  • 控制器
  • 请求
  • 数据响应
  • 配置
  • 异常处理

基于这些,我们新建目录,app和library,public

  • app web应用
  • library 核心代码
  • public 入口目录

出于简单安全考虑,我们的入口单独放在public目录,并在目录下新建index.php作为我们的入口文件。

library下面新建几个目录和文件

  • Components 常用组件
  • Exceptions 异常模块
  • Https http应用模块
  • Sessions session模块
  • Application.php 应用文件
  • Config.php 配置文件
  • Functions.php 常用函数
  • System.php 框架自定义常量

image

对应的我们在composer.json中加入一些autoload配置,用以自动加载,省去我们实现自动加载。

1
2
3
4
5
6
7
8
9
"autoload": {
"psr-4": {
"Library\\": "library/",
"App\\": "app/"
},
"files": [
"library/Functions.php"
]
}

执行一下,composer install或者composer dump-autoload即可。


这里简单说明一下autoload的四种方式:

autoload的四种方式

  1. PSR-4

在psr-4键下,定义了相对于包根目录从名称空间到路径的映射。当自动加载一个类(如foo\bar\baz)时,指向src/目录的名称空间前缀foo\,意味着自动加载程序将查找一个名为src/bar/baz.php的文件,并包括它(如果存在)。注意,与旧的psr-0样式相反,前缀(foo\)不在文件路径中。

命名空间前缀必须以“\”结尾,以避免类似前缀之间的冲突。例如,foo将匹配foobar名称空间中的类,因此后面的反斜杠可以解决问题:foo\,foobar\。

该数组可以在生成的文件vendor/composer/autoload_psr4.php中找到。

  1. PSR-0

在 psr-0 key 下你定义了一个命名空间到实际路径的映射(相对于包的根目录)。注意,这里同样支持 PEAR-style 方式的约定(与命名空间不同,PEAR 类库在类名上采用了下划线分隔)。

请注意,命名空间的申明应该以 \ 结束,以确保 autoloader 能够准确响应。例: Foo 将会与 FooBar 匹配,然而以反斜杠结束就可以解决这样的问题, Foo\ 和 FooBar\ 将会被区分开来。

PSR-0 引用都将被结合为一个单一的键值对数组,存储至 vendor/composer/autoload_namespaces.php 文件中。

image

  1. classmap

你可以用 classmap 生成支持支持自定义加载的不遵循 PSR-0/4 规范的类库。要配置它指向需要的目录,以便能够准确搜索到类文件。

classmap 引用的所有组合会存储到 vendor/composer/autoload_classmap.php 文件中。这个 map 是经过扫描指定目录(同样支持直接精确到文件)中所有的 .php 和 .inc 文件里内置的类而得到的。

  1. files

如果你想要明确的指定,在每次请求时都要载入某些文件,那么你可以使用 ‘files’ autoloading。通常作为函数库的载入方式(而非类库)。files 引用的文件会存储到 vendor/composer/autoload_files.php 文件中


我们先不着急进行核心代码编写,不妨先做一下辅助工作,常用方法,异常处理等。

常用函数Functions

编写常用函数

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
<?php
/**
* 常用函数
*/

if (!function_exists("p")) {
function p($var)
{
if (is_bool($var)) {
var_dump($var);
} elseif (is_null($var)) {
var_dump(null);
} else {
die("<meta charset='utf-8'/>
<pre style='position:relative;
z-index:999;
padding:10px;
border-radius:5px;
background:#f5f5f5;
border:1px solid #aaa;
font-size:14px;
line-height:18px;
opacity:0.8;'>".print_r($var, true)."</pre>");
}
}
}

······


异常处理

在Exceptions目录下,定义一个最基础的异常SaiException:

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
<?php

namespace Library\Exceptions;

class SaiException extends \Exception
{
const CODE_MAPPING = [
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing',

200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-Status',
226 => 'IM Used',

300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
306 => 'Reserved',
307 => 'Temporary Redirect',

400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
422 => 'Unprocessable Entity',
423 => 'Locked',
424 => 'Failed Dependency',
426 => 'Upgrade Required',
429 => 'Too Many Request',

500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates',
507 => 'Insufficient Storage',
510 => 'Not Extended',

1001 => 'LACK PARAMS',
1002 => 'RETRY TOO MANY',
];

/**
* 输出指定HTTP状态码的响应头信息
* @param int $code
* @param $data
* @return void
*/
public function response($code, $data){
$code = array_key_exists($code, self::CODE_MAPPING)? $code : 500;
$desc = self::CODE_MAPPING[$code];

$protocol = $_SERVER['SERVER_PROTOCOL'];
if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol )
$protocol = 'HTTP/1.0';
$header = "$protocol $code $desc";
header($header);
p($data);
}
}

几乎后面所有Exception的类都会继承这个异常类。


在Components目录下新建基础的类Base:

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
<?php

namespace Library\Components;

class Base implements \ArrayAccess
{
private $_container;

public function __get($name)
{
if (method_exists($this, $method = 'get'.ucfirst($name))) {
return $this->$method($name);
}

return null;
}

public function __set($name, $value)
{
if (method_exists($this, $method = 'set'.ucfirst($name))) {
return $this->$method($name, $value);
}
}

public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->_container[] = $value;
} else {
$this->_container[$offset] = $value;
}
}
public function offsetExists($offset)
{
return isset($this->_container[$offset]);
}

public function offsetUnset($offset)
{
unset($this->_container[$offset]);
}

public function offsetGet($offset)
{
return isset($this->_container[$offset]) ? $this->_container[$offset] : null;
}
}

这里有两个知识点:

  1. ArrayAccess数组式访问接口(提供像访问数组一样访问对象的能力的接口。)
  2. 魔术方法__set和__get(在给不可访问属性赋值时__set() 会被调用;读取不可访问属性的值时__get() 会被调用。)

如果想了解更多,可看官方文档: