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