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 框架自定义常量

image

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

"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

编写常用函数

<?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;
    }
}

这里有两个知识点:

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

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

php
本作品采用《CC 协议》,转载必须注明作者和本文链接
分享开发知识,欢迎交流。公众号:开源到
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!