Alpaca-PHP-2.0 框架介绍 - 从零开始编写PHP-MVC框架

Alpaca-PHP-2.0

简介

Alpaca-PHP-2.0 简介

  Alpaca-PHP 是一款轻量的PHP-MVC框架,确切的说应该是一款MC框架,因为在2.0版本中,去掉了view层,只提供控制器,模型业务逻辑层。 默认情况下,每一个请求返回一个json数据。Alpaca-PHP框架支持composer,使用Laravel-DB(illuminate/database)作为数据库访问层。同时支持有命名 空间,无命名空间两种格式。方便集成各种类库、第三方资源。

目录结构

1. Alpaca-PHP默认的目录结构

-application
  -modules
  -resource
  -service
   Bootstrap.php
-config
   main.php
-library
  -Alpaca
  -vendor
   composer.json
   composer.lock
-public
   index.php
-runtime
  -log
  -cache
1. 示例中的application一个具体项目应用的目录。

2. application目录下面有三个子目录,1个php文件。
   modules         存放模块相关信息,里面包含控制器,业务逻辑等
   resource        存放资源信息,例如数据库实体类等
   service         存放底层或者公用的业务逻辑、方法,类等
   Bootstrap.php   每一个请求开始执行时候,Bootstrap中每一个以_init开头的方法,会依次调用

3.config存放配置文件
   main.php         存放主要配置信息,任何环境都会使用这个配置
   development.php  存放开发环境配置信息,开发环境会使用这个配置,并且与main.php合并(环境变量MOD_ENV = DEVELOPMENT)
   production.php   存放开生产境配置信息,生产环境会使用这个配置,并且与main.php合并(环境变量MOD_ENV = PRODUCTION时)
   test.php         存放测试环境配置信息,测试环境会使用这个配置,并且与main.php合并(环境变量MOD_ENV = TEST)

4.library中 存放类库,第三方资源等

5.public中 存放应用的入口文件

6.runtime中 存放应用运行时候的文件,例如log,cache等

实现设置环境与配置

init_environment.php的代码如下:

class Environment
{
    //单例模式
    private static $instance;

    //运行环境
    private  $mode;

    //配置文件
    private  $env_config;

    //env单例
    public static function env()
    {
        return self::getInstance();
    }

    //创建单例
    private static function getInstance()
    {
        if(!self::$instance){
            self::$instance = new self();
            self::$instance->init();
        }
        return self::$instance;
    }

    /* init 函数,初始化系统环境 */
    public function init()
    {
        //运行环境
        $mode = getenv('MOD_ENV');
        if(empty($mode)){
            $this->mode="DEVELOPMENT";
        }

        //配置文件
        if(empty($this->env_config)){
            $this->env_config = require_once APP_PATH."/config/main.php";
        }

        $config_ex = [];
        if( $this->mode == "TEST"){
            $config_ex = require_once APP_PATH."/config/test.php";
        }elseif($this->mode == "PRODUCTION"){
            $config_ex = require_once APP_PATH."/config/production.php";
        }else{
            $config_ex = require_once APP_PATH."/config/development.php";
        }

        //合并配置文件
        foreach($config_ex as $key => $value){
            $this->env_config[$key] = $value;
        }

    }

    /* config 函数,返回配置信息 */
    public function config()
    {
        return $this->env_config;
    }
}

用来实现程序可以通过读取不同环境加载不同的配置

上面的代码中,通过读取环境变量MOD_ENV 来判断当前环境是测试、开发、生产等,然后加载相应的环境配置,与/config/main.php合并

组织类的加载规则

推荐使用在composer中配置类的加载规则,可以使用psr-4等规则

这里介绍一下spl_autoload_register函数,自定义类的加载规则

完整代码如下:

spl_autoload_register(function($class){

    $config = Environment::env()->config();
    //有命名空间
    $className = str_replace("\\", "/", $class);
    //无命名空间
    $className = str_replace("_", "/", $className);

    //加载模块modules中的类
    if(!empty($config['application']['modules'])){
        $moduleNames = str_replace(",", "|", $config['application']['modules']);
    }else{
        $moduleNames = null;
    }
    if($moduleNames){
        $preg_moduleNames ="/(^({$moduleNames}))/";
        if(preg_match($preg_moduleNames,$className)){
            $className = APP_PATH . "/application/modules/".$className.".php";
            if(file_exists($className)){
                require_once ($className);
            }
            return;
        }
    }

    //加载Resources中的类
    if(!empty($config['application']['resource'])){
        $resourceNames = str_replace(",", "|", $config['application']['resource']);
    }else{
        $resourceNames = null;
    }
    $resourceNames=str_replace(",", "|", $config['application']['resource']);
    if($resourceNames){
        $preg_resourceNames ="/(^({$resourceNames}))/";
        if(preg_match($preg_resourceNames,$className)){
            $className =  APP_PATH . "/application/resource/".$className.".php";
            require_once($className);
            return;
        }
    }
               
    //加载library中的类
    if(!empty($config['application']['library'])){
        $resourceNames = str_replace(",", "|", $config['application']['library']);
    }else{
        $resourceNames = null;
    }
    $libraryNames = str_replace(",", "|", $config['application']['library']);
    if($libraryNames){
        $preg_libraryNames ="/(^({$libraryNames}))/";
        if(preg_match($preg_libraryNames,$className)){
            $className =  APP_PATH . "/library/".$className.".php";
            require_once($className);
            return;
        }
    }
});

首先读取配置文件,关于配置文件会在啊后面的章节中介绍

然后,处理一下有命名空间、无命名空间的情况

PHP中类的加载规则,基本上都是建立命名空间到类文件实际路径的映射,对于没有命名空间的类,可以直接建立类名与实际路径的映射

    $config = Environment::env()->config();
    //有命名空间
    $className = str_replace("\\", "/", $class);
    //无命名空间
    $className = str_replace("_", "/", $className);

然后加载模块modules中的类

 if(!empty($config['application']['modules'])){
        $moduleNames = str_replace(",", "|", $config['application']['modules']);
    }else{
        $moduleNames = null;
    }
    if($moduleNames){
        $preg_moduleNames ="/(^({$moduleNames}))/";
        if(preg_match($preg_moduleNames,$className)){
            $className = APP_PATH . "/application/modules/".$className.".php";
            if(file_exists($className)){
                require_once ($className);
            }
            return;
        }
    }

同理可以加载 Resources 、Service 、library中的类

实现路由功能

路由功能的作用是将给定规则的url隐射到对应的模块、控制起、方法

代码如下:

class Router
{
    public $application = null;

    public $ModulePostfix = 'Module';

    public $ControllerPostfix = 'Controller';

    public $ActionPostfix = 'Action';

    public $DefaultModule = 'index';

    public $DefaultController = 'index';

    public $DefaultAction = 'index';

    public $Module = null;

    public $Controller = null;

    public $Action = null;

    public $ModuleName = null;

    public $ControllerName = null;

    public $ActionName = null;

    public $ModuleClassName = null;

    public $ControllerClassName = null;
    
    public $Params = Array();
    
    public $ControllerClass = null;
    
    public $ModuleClass = null;
    
    private $pathSegments = null;
    
    private static $instance;

    public function setAsGlobal()
    {
        self::$instance = $this;
        return $this;
    }

    public static function router()
    {
        return self::getInstance();
    }

    private static function getInstance()
    {
        if(!self::$instance){
            self::$instance = new Router();
        }
        return self::$instance;
    }

    public function init()
    {                
        $request_url = $_SERVER['REQUEST_URI'];

        $this->forward($request_url);

        return $this;
    }

    public function forward($path)
    {        
        //处理请求路由路径,去掉参数
        $pos = stripos($path, '?');
        if ($pos) {
            $path = substr($path, 0, $pos);
        }
        
        //解析路由,生成Module、Controller、Action
        $parserResult = $this->parser($path);
        if($parserResult != true)
        {
            return null;
        }

        return $this;
    }

    //解析路由
    public function parser($path)
    {
        $segments = explode('/', $path);
 
        if (empty($segments[1])) {
            array_splice($segments, 1, 0, $this->DefaultModule);
        }
        
        if (empty($segments[2])) {
            array_splice($segments, 2, 0, $this->DefaultController);
        }

        if (empty($segments[3])) {
            array_splice($segments, 3, 0, $this->DefaultAction);
        }

        $this->Params = array_slice($segments, 4);
        
        if($this->pathSegments == $segments){
            echo "Endless Loop ! Do not redirect in the same action.";
            return false;
        }
        
        $this->pathSegments = $segments;

        // Module
        $this->Module = str_replace(array('.', '-', '_'), ' ', $segments[1]);
        $this->Module = ucwords($this->Module);
        $this->Module = str_replace(' ', '', $this->Module);              
        $this->ModuleName = $this->Module.$this->ModulePostfix;
        $this->ModuleClassName = $this->Module.'\\Module';
                               
        // Controller
        $this->Controller = str_replace(array('.', '-', '_'), ' ', $segments[2]);
        $this->Controller = ucwords($this->Controller);
        $this->Controller = str_replace(' ', '', $this->Controller);   
        $this->ControllerName = $this->Controller.$this->ControllerPostfix;
        $this->ControllerClassName = $this->Module.'\\Controller\\'.$this->ControllerName;
        
        // Action
        $this->Action = $segments[3];
        $this->Action = str_replace(array('.', '-', '_'), ' ', $segments[3]);
        $this->Action = ucwords($this->Action);
        $this->Action = str_replace(' ', '', $this->Action);
        $this->Action = lcfirst($this->Action);        
        $this->ActionName = $this->Action.$this->ActionPostfix;
        
        if(!in_array($this->Module,$this->application->getModules())){
            throw new \Exception("Module:$this->Module not found!");
        }
        
        if(!class_exists($this->ControllerClassName)){
            throw new \Exception("Controller:$this->ControllerClassName not found!");
        }
        
        if(!method_exists(new $this->ControllerClassName(), $this->ActionName))
        {
            throw new \Exception("Action:$this->ActionName not found!");
        }

        return $this;
    }

}

Alpaca类,用来创建路由,调用事件,执行动作

Alpaca类的作用是创建应用实例,调用事件、调用路由分析url,执行路由分析出来的动作,返回结果(Json或者View等)。

namespace Alpaca;

//Alpaca类,创建路由,调用事件,执行动作
use Monolog\Handler\StreamHandler;
use Monolog\Logger;

class Alpaca
{
    /**
     * 支持JSONP
     * Whether support Jsonp . Support if true, nonsupport if false.
     */
    protected $_isJsonp = true;

    //配置文件
    public $config;

    //路由router
    public $router;

    //日志对象
    public $log;

    //单例
    private static $instance;

    //构造函数
    public function __construct(array $config = null)
    {
        $this->config = $config;
        return $this;
    }

    //单例
    public static function app(array $config = null)
    {
        return self::getInstance($config);
    }

    //创建 - 单例
    private static function getInstance(array $config = null)
    {
        if (!self::$instance) {
            self::$instance = new self($config);
        }
        return self::$instance;
    }

    //路由 单例
    public static function router()
    {
        return self::$instance->router;
    }

    //日志 单例
    public static function log()
    {
        return self::$instance->log;
    }

    //运行 alpaca
    public function run()
    {
        //加载配置文件
        $this->config = \Environment::env()->config();

        //创建日志
        $this->createLog();

        //过滤用户输入参数
        $this->paramFilter();

        //调用bootstrap
        $this->bootstrap();

        //启动路由
        Router::router()->application = $this;
        $this->router                 = Router::router()->init();

        //创建模块实例
        $moduleClassName = $this->router->ModuleClassName;
        $module          = new $moduleClassName();

        //创建控制器实例
        $controllerClassName = $this->router->ControllerClassName;
        $controller          = new $controllerClassName();

        //执行事件init,release
        $init    = "init";
        $release = "release";
        $result  = null;
        //执行模块中的init方法,如果该方法存在
        if (method_exists($module, $init)) {
            $result = $module->$init();
        }
        //执行控制器中的init方法,如果该方法存在
        if (method_exists($controller, $init) && $result !== false) {
            $result = $controller->$init();
        }

        //执行action方法
        $action = $this->router->ActionName;
        if (method_exists($controller, $action) && $result !== false) {
            $controller->$action();
        }

        //执行控制器中的release方法
        if (method_exists($controller, $release)) {
            $controller->$release();
        }

        //执行模块中的release方法
        if (method_exists($module, $release)) {
            $module->$release();
        }
    }

    //创建日志
    public function createLog()
    {
        if ($this->config['log']) {
            $this->log = new Logger('[LOG]');
            $dir       = $this->config['log']['dir'] . date('Ymd');
            if (!is_dir($dir)) {
                @mkdir($dir, '0777');
            }
            $file = $dir . '/' . $this->config['log']['file'];

            $levelName = $this->config['log']['levels'] ? $this->config['log']['levels'] : "INFO";
            $levelCode = 100;
            if ($levelName == "INFO") {
                $levelCode = 200;
            } elseif ($levelName == "ERROR") {
                $levelCode = 300;
            }
            $this->log->pushHandler(new StreamHandler($file, $levelCode));
        }
    }

    //运行 bootstrap, bootstrap中_init开头的方法回依次被执行
    public function bootstrap()
    {

        require_once APP_PATH . '/application/Bootstrap.php';

        $bootstrap = new \Bootstrap();

        $methods = get_class_methods($bootstrap);
        if (!$methods) {
            return $this;
        }

        foreach ($methods as $method) {
            if (preg_match("/(^(_init))/", $method)) {
                $bootstrap->$method();
            }
        }
        return $this;
    }

    //获取模块
    public function getModules()
    {
        if (empty($this->config['application']['modules'])) {
            return null;
        }
        return array_map("trim", explode(',', $this->config['application']['modules']));
    }

    //过滤传入参数,防止xss注入,SQL注入
    public function paramFilter()
    {
        if (isset($_GET) && !empty($_GET)) {
            $this->filterChars($_GET);
        }
        if (isset($_POST) && !empty($_POST)) {
            $this->filterChars($_POST);
        }
        if (isset($_REQUEST) && !empty($_REQUEST)) {
            $this->filterChars($_REQUEST);
        }
        if (isset($_COOKIE) && !empty($_COOKIE)) {
            $this->filterChars($_COOKIE);
        }
    }

    /**
     * Strips slashes from input data.
     * This method is applied when magic quotes is enabled.
     * @param string|mixed $string input data to be processed
     * @param bool         $low input data to be processed
     * @return mixed processed data
     */
    public function filterChars(&$string, $low = false)
    {
        if (!is_array($string)) {
            $string = trim($string);
            //$string = strip_tags ( $string );
            $string = htmlspecialchars($string);
            if ($low) {
                return true;
            }
            $string = str_replace(array('"', "\\", "'", "/", "..", "../", "./", "//"), '', $string);
            $no     = '/%0[0-8bcef]/';
            $string = preg_replace($no, '', $string);
            $no     = '/%1[0-9a-f]/';
            $string = preg_replace($no, '', $string);
            $no     = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S';
            $string = preg_replace($no, '', $string);
            return true;
        }
        $keys = array_keys($string);
        foreach ($keys as $key) {
            $this->filterChars($string [$key]);
        }
    }

    //获取输入参数 get,post
    public function getParam($name, $defaultValue = null)
    {
        return isset($_GET[$name]) ? $_GET[$name] : (isset($_POST[$name]) ? $_POST[$name] : $defaultValue);
    }

    //获取输入参数 get
    public function getQuery($name, $defaultValue = null)
    {
        return isset($_GET[$name]) ? $_GET[$name] : $defaultValue;
    }

    //获取输入参数 post
    public function getPost($name, $defaultValue = null)
    {
        return isset($_POST[$name]) ? $_POST[$name] : $defaultValue;
    }

    /**
     * 返回json
     * @author Chengcheng
     * @date 2016年10月21日 17:04:44
     * @param array $jsonData
     * @return boolean
     */
    public function toJson($jsonData)
    {
        header('Content-Type: application/json;charset=utf-8');
        header('Access-Control-Allow-Origin: *');

        if ($this->_isJsonp) {
            //JSONP格式-支持跨域
            $cb = isset($_GET['callback']) ? $_GET['callback'] : null;
            if ($cb) {
                $result = "{$cb}(" . json_encode($jsonData, JSON_UNESCAPED_UNICODE) . ")";
            } else {
                $result = json_encode($jsonData, JSON_UNESCAPED_UNICODE);
            }
        } else {
            //JSON格式-普通
            $result = json_encode($jsonData, JSON_UNESCAPED_UNICODE);
        }

        //打印结果
        echo $result;

        return $result;
    }
}

Bootstrap类

Bootstrap类默认位于application目录下面, 其作用是所有在Bootstrap类中, 以_init开头的方法, 都会被Alpaca调用,例如初始化数据库配置、处理定义异常处理等

class Bootstrap
{
    //初始化数据库
    public function _initDatabase()
    {
        $config = Alpaca::app()->config;
        $capsule = new Capsule;
        $capsule->addConnection($config['db']);
        $capsule->setAsGlobal();
        $capsule->bootEloquent();
    }

    //定义异常处理,错误处理
    public function _initException()
    {
        function myException(\Exception $exception)
        {
            $result['code'] = '9900';
            $result['msg'] = "Exception:" . $exception->getMessage();
            Alpaca::app()->toJson($result);
            die();
        }

        function customError($no,$str)
        {
            $result['code'] = '9900';
            $result['msg'] =  "Error:[$no] $str";
            Alpaca::app()->toJson($result);
            die();
        }

        set_exception_handler('myException');
        set_error_handler("customError");
    }
}

View类

2.0版本中去掉了VIew类,因为推荐采用前后台分离、独立的方式开发,所以后台服务不会返回页面、只返回json数据

输入参数过滤

过滤输入的参数、用来防止注入攻击等。

public function filterChars(&$string, $low = false)
    {
        if (!is_array($string)) {
            $string = trim($string);
            //$string = strip_tags ( $string );
            $string = htmlspecialchars($string);
            if ($low) {
                return true;
            }
            $string = str_replace(array('"', "\\", "'", "/", "..", "../", "./", "//"), '', $string);
            $no     = '/%0[0-8bcef]/';
            $string = preg_replace($no, '', $string);
            $no     = '/%1[0-9a-f]/';
            $string = preg_replace($no, '', $string);
            $no     = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S';
            $string = preg_replace($no, '', $string);
            return true;
        }
        $keys = array_keys($string);
        foreach ($keys as $key) {
            $this->filterChars($string [$key]);
        }
    }

异常处理

重新定义异常处理,让所有的异常、错误也返回json数据

在Bootstrap中编写_initException方法

 //定义异常处理,错误处理
    public function _initException()
    {
        function myException(\Exception $exception)
        {
            $result['code'] = '9900';
            $result['msg'] = "Exception:" . $exception->getMessage();
            Alpaca::app()->toJson($result);
            die();
        }

        function customError($no,$str)
        {
            $result['code'] = '9900';
            $result['msg'] =  "Error:[$no] $str";
            Alpaca::app()->toJson($result);
            die();
        }

        set_exception_handler('myException');
        set_error_handler("customError");
    }

相关源码连接等

内容说明地址
Alpaca-PHP代码Alpaca-phphttps://gitee.com/cc-sponge/Alpaca-PHP-2.0
主页Alpaca-Spahttp://www.tkc8.com
后台Alpaca-Spa-Laravelhttp://full.tkc8.com
手机端suiAlpaca-Spa-Suihttp://full.tkc8.com/app
代码oschinahttp://git.oschina.net/cc-sponge/Alpaca-Spa-Laravel
代码githubhttps://github.com/big-sponge/Alpaca-Spa-Laravel

联系我们

QQ群: 298420174

图片名称

作者: Sponge 邮箱: 1796512918@qq.com

转载于:https://my.oschina.net/u/3381391/blog/868937

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值