PHP DIY 系列------框架篇:1. 框架目录与辅助
那么就利用composer来开始我们的项目吧。
新建目录并进入目录,输入命令:
composer init
命令行会跟你确认以下信息(以下信息可以自行DIY)
# 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 框架自定义常量
对应的我们在composer.json中加入一些autoload配置,用以自动加载,省去我们实现自动加载。
"autoload": {
"psr-4": {
"Library\\": "library/",
"App\\": "app/"
},
"files": [
"library/Functions.php"
]
}
执行一下,composer install或者composer dump-autoload即可。
这里简单说明一下autoload的四种方式:
autoload的四种方式
- 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中找到。
- PSR-0
在 psr-0 key 下你定义了一个命名空间到实际路径的映射(相对于包的根目录)。注意,这里同样支持 PEAR-style 方式的约定(与命名空间不同,PEAR 类库在类名上采用了下划线分隔)。
请注意,命名空间的申明应该以 \ 结束,以确保 autoloader 能够准确响应。例: Foo 将会与 FooBar 匹配,然而以反斜杠结束就可以解决这样的问题, Foo\ 和 FooBar\ 将会被区分开来。
PSR-0 引用都将被结合为一个单一的键值对数组,存储至 vendor/composer/autoload_namespaces.php 文件中。
- classmap
你可以用 classmap 生成支持支持自定义加载的不遵循 PSR-0/4 规范的类库。要配置它指向需要的目录,以便能够准确搜索到类文件。
classmap 引用的所有组合会存储到 vendor/composer/autoload_classmap.php 文件中。这个 map 是经过扫描指定目录(同样支持直接精确到文件)中所有的 .php 和 .inc 文件里内置的类而得到的。
- files
如果你想要明确的指定,在每次请求时都要载入某些文件,那么你可以使用 ‘files’ autoloading。通常作为函数库的载入方式(而非类库)。files 引用的文件会存储到 vendor/composer/autoload_files.php 文件中
我们先不着急进行核心代码编写,不妨先做一下辅助工作,常用方法,异常处理等。
常用函数Functions
编写常用函数
<?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:
<?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:
<?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;
}
}
这里有两个知识点:
- ArrayAccess数组式访问接口(提供像访问数组一样访问对象的能力的接口。)
- 魔术方法set和get(在给不可访问属性赋值时set() 会被调用;读取不可访问属性的值时get() 会被调用。)
如果想了解更多,可看官方文档:
本作品采用《CC 协议》,转载必须注明作者和本文链接