Alpaca-PHP 是一款輕量的PHP-MVC框架,確切的說應該是一款MC框架,由於在2.0版本中,去掉了view層,只提供控制器,模型業務邏輯層。 默認狀況下,每個請求返回一個json數據。Alpaca-PHP框架支持composer,使用Laravel-DB(illuminate/database)做爲數據庫訪問層。同時支持有命名 空間,無命名空間兩種格式。方便集成各類類庫、第三方資源。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類的做用是建立應用實例,調用事件、調用路由分析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類默認位於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"); } }
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