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