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)做爲數據庫訪問層。同時支持有命名 空間,無命名空間兩種格式。方便集成各類類庫、第三方資源。php

目錄結構

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的代碼以下:html

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

用來實現程序能夠經過讀取不一樣環境加載不一樣的配置git

上面的代碼中,經過讀取環境變量MOD_ENV 來判斷當前環境是測試、開發、生產等,而後加載相應的環境配置,與/config/main.php合併github

組織類的加載規則

推薦使用在composer中配置類的加載規則,可使用psr-4等規則數據庫

這裏介紹一下spl_autoload_register函數,自定義類的加載規則json

完整代碼以下:bootstrap

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

首先讀取配置文件,關於配置文件會在啊後面的章節中介紹跨域

而後,處理一下有命名空間、無命名空間的狀況app

PHP中類的加載規則,基本上都是創建命名空間到類文件實際路徑的映射,對於沒有命名空間的類,能夠直接創建類名與實際路徑的映射composer

$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-php https://gitee.com/cc-sponge/Alpaca-PHP-2.0
主頁 Alpaca-Spa http://www.tkc8.com
後臺 Alpaca-Spa-Laravel http://full.tkc8.com
手機端sui Alpaca-Spa-Sui http://full.tkc8.com/app
代碼 oschina http://git.oschina.net/cc-sponge/Alpaca-Spa-Laravel
代碼 github https://github.com/big-sponge/Alpaca-Spa-Laravel

聯繫咱們

QQ羣: 298420174

圖片名稱

做者: Sponge 郵箱: 1796512918@qq.com

相關文章
相關標籤/搜索