├── Library ├── Model │ └── .gitkeep ├── Core │ ├── IFilter.php │ ├── LazyLoadObject.php │ ├── Request.php │ ├── Response.php │ ├── Filter.php │ ├── DefaultRouter.php │ ├── Database.php │ ├── I18N.php │ ├── Error.php │ ├── Model.php │ ├── ReflectionRouter.php │ └── Template.php ├── Language │ └── en-US.yml ├── Controller │ └── Index.php └── Helper │ ├── Escape.php │ ├── Message.php │ ├── PHPLock.php │ ├── Reflection.php │ ├── PageData.php │ └── JSON.php ├── .webpack ├── dev.js └── prod.js ├── README.md ├── Resource ├── Demo │ └── test.js └── Misc │ └── Error.css ├── index.php ├── composer.json ├── .gitignore ├── Template ├── Demo.htm └── Misc │ ├── Error.htm │ └── Redirect.htm ├── package.json ├── phinx.php ├── Public └── index.php ├── webpack.config.js ├── Data └── Config.php └── composer.lock /Library/Model/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.webpack/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = function (options) { 2 | options.devtool = 'cheap-module-source-map'; 3 | return options; 4 | }; 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## KK-Framework 2 | 3 | KK-Framework is a fast, easy-to-use php framework. 4 | 5 | View all documents on [docs.ikk.me](https://docs.ikk.me/kk-framework/) -------------------------------------------------------------------------------- /Resource/Demo/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * KK-Framework 3 | * Author: kookxiang 4 | */ 5 | "use strict"; 6 | 7 | require('../Misc/Error.css'); 8 | 9 | console.log("Hello world from KK-Framework."); 10 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | if (php_sapi_name() != 'cli') { 8 | exit('Sorry, This page is not available due to incorrect server configuration.'); 9 | } 10 | -------------------------------------------------------------------------------- /Library/Core/IFilter.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Core; 8 | 9 | interface IFilter 10 | { 11 | public function preRoute(&$path); 12 | public function afterRoute(&$className, &$method); 13 | public function preRender(); 14 | public function afterRender(); 15 | public function redirect(&$targetUrl); 16 | } 17 | -------------------------------------------------------------------------------- /.webpack/prod.js: -------------------------------------------------------------------------------- 1 | // env: node 2 | var webpack = require("webpack"); 3 | 4 | module.exports = function (options) { 5 | options.plugins.unshift(new webpack.LoaderOptionsPlugin({ 6 | minimize: true, 7 | debug: false 8 | })); 9 | options.plugins.unshift(new webpack.optimize.UglifyJsPlugin({ 10 | beautify: false, 11 | mangle: { screw_ie8: true }, 12 | compress: { screw_ie8: true }, 13 | comments: false 14 | })); 15 | return options; 16 | }; 17 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kookxiang/KK-Framework", 3 | "description": "A very simple && fast php framework", 4 | "minimum-stability": "beta", 5 | "license": "Apache", 6 | "authors": [ 7 | { 8 | "name": "kookxiang", 9 | "email": "r18@ikk.me" 10 | } 11 | ], 12 | "config": { 13 | "vendor-dir": "Package" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "": "Library/" 18 | } 19 | }, 20 | "require": { 21 | "robmorgan/phinx": "^0.5.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Library/Language/en-US.yml: -------------------------------------------------------------------------------- 1 | Program: 2 | Title: A demo of {framework_name} 3 | 4 | Pages: 5 | Demo: 6 | Title: Welcome 7 | DemoText: Welcome to use KK-Framework. El Psy Congroo. 8 | 9 | Error: 10 | Title: System Error - {code} 11 | Tips: We're now fixing this problem, sorry for the inconveniences. 12 | Messages: 13 | PageNotExists: The request URL is not exists. 14 | 15 | Redirect: 16 | Title: System Message 17 | CountDown: If you didn't redirect in {timeout} seconds, please click here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore files generated by IntelliJ IDEA / PHPStorm 2 | *.iml 3 | .idea/ 4 | 5 | # Phinx Database config file 6 | phinx.yml 7 | 8 | # Deploy script 9 | deploy.sh 10 | 11 | # Logs 12 | logs 13 | *.log 14 | npm-debug.log* 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | 21 | # Dependency directory 22 | node_modules/**/* 23 | 24 | # Composer 25 | composer.phar 26 | Package/**/* 27 | !composer.lock 28 | 29 | # Data folder 30 | Data/**/* 31 | !Data/Config.php 32 | 33 | # Front-end Resources (compiled) 34 | Public/**/* 35 | !Public/**/*.php 36 | -------------------------------------------------------------------------------- /Template/Demo.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | KK-Framework 6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 | {SITE_NAME} 14 |

15 |
16 |

17 |

18 |
19 |

Powered by KK Framework

20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Library/Controller/Index.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Controller; 8 | 9 | use Core\Template; 10 | 11 | class Index 12 | { 13 | /** 14 | * @DynamicRoute /User/{string} 15 | * @param $username 16 | */ 17 | function dynamicRouteTest($username) 18 | { 19 | echo $username; 20 | } 21 | 22 | /** 23 | * @Home 24 | * @Route /Index 25 | */ 26 | function index() 27 | { 28 | include Template::load('Demo'); 29 | } 30 | 31 | /** 32 | * This method can be call by /index/test.json 33 | * @Route /Test 34 | * @JSON 35 | */ 36 | function test() 37 | { 38 | return array( 39 | 'hello' => 1, 40 | 'world' => 2 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kk-framework-sample-app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "dependencies": { 6 | "react-lite": "^0.15.31" 7 | }, 8 | "devDependencies": { 9 | "autoprefixer": "^6.7.2", 10 | "babel-core": "^6.22.1", 11 | "babel-loader": "^6.2.10", 12 | "babel-preset-es2015": "^6.24.1", 13 | "babel-preset-react": "^6.24.1", 14 | "css-loader": "^0.26.1", 15 | "extract-text-webpack-plugin": "^2.0.0-rc.3", 16 | "node-sass": "^4.5.0", 17 | "postcss-loader": "^1.2.2", 18 | "sass-loader": "^4.1.1", 19 | "style-loader": "^0.13.1", 20 | "webpack": "^2.2.1" 21 | }, 22 | "scripts": { 23 | "dev": "webpack --env=dev --progress --colors", 24 | "build": "webpack --env=prod --progress --colors" 25 | }, 26 | "author": "kookxiang ", 27 | "license": "ISC" 28 | } 29 | -------------------------------------------------------------------------------- /phinx.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | if (PHP_SAPI != 'cli') { 8 | exit('This file could not be access directly.'); 9 | } 10 | 11 | define('ROOT_PATH', dirname(__FILE__) . DIRECTORY_SEPARATOR); 12 | define('LIBRARY_PATH', ROOT_PATH . 'Library/'); 13 | define('DATA_PATH', ROOT_PATH . 'Data/'); 14 | @ini_set('display_errors', 'on'); 15 | 16 | require ROOT_PATH . 'Package/autoload.php'; 17 | Core\Error::registerHandler(); 18 | 19 | @include DATA_PATH . 'Config.php'; 20 | 21 | $statement = \Core\Database::getInstance()->query('select database()'); 22 | $statement->execute(); 23 | 24 | return array( 25 | 'paths' => array( 26 | 'migrations' => ROOT_PATH . 'Migrations', 27 | ), 28 | 'environments' => array( 29 | 'default_database' => 'current', 30 | 'current' => array( 31 | 'name' => $statement->fetchColumn(), 32 | 'connection' => \Core\Database::getInstance(), 33 | ), 34 | ), 35 | ); 36 | -------------------------------------------------------------------------------- /Template/Misc/Error.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | $code = $instance->getCode() ? $instance->getCode() : '500' 4 | 5 | 6 | Error - {$code} - {SITE_NAME} 7 | 8 | 9 | 10 | 11 | 12 | 13 |

{SITE_NAME}

14 |
15 |

System Error - {code}

16 |

{$instance->getMessage()}

17 |

We're now fixing this problem, sorry for the inconveniences.

18 | 19 |
BackTrace:
{$instance->formatBackTrace()}
20 |
21 |
22 |

Powered by KK Framework

23 | 24 | 25 | -------------------------------------------------------------------------------- /Public/index.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | // Initialize constants 8 | define('ROOT_PATH', dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR); 9 | define('LIBRARY_PATH', ROOT_PATH . 'Library/'); 10 | define('DATA_PATH', ROOT_PATH . 'Data/'); 11 | @ini_set('display_errors', 'on'); 12 | @ini_set('expose_php', false); 13 | 14 | // Register composer 15 | require ROOT_PATH . 'Package/autoload.php'; 16 | 17 | // Register error handler 18 | Core\Error::registerHandler(); 19 | 20 | // Initialize config 21 | @include DATA_PATH . 'Config.php'; 22 | 23 | if (ini_get('opcache.enable')) { 24 | if (!ini_get('opcache.save_comments')) { 25 | throw new \Core\Error('ZendOpcache is configured not saving PHP DocComments which is required.'); 26 | } 27 | } 28 | 29 | // Initialize i18n engine 30 | Core\I18N::init(); 31 | 32 | // Handler for json request 33 | Core\Filter::register(new Helper\JSON()); 34 | 35 | //$Router = new Core\DefaultRouter(); 36 | $Router = new Core\ReflectionRouter(); 37 | $Router->handleRequest(); 38 | -------------------------------------------------------------------------------- /Template/Misc/Redirect.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <i18n key="Redirect.Title">System Message</i18n> - {SITE_NAME} 6 | 7 | 8 | 9 | 10 | 11 | 12 |

{SITE_NAME}

13 |
14 |

System Message

15 |

{$text}

16 | 17 |

If you didn't redirect in {timeout} seconds, please click here.

18 | 21 |
22 |
23 |

Powered by KK Framework

24 | 25 | 26 | -------------------------------------------------------------------------------- /Library/Helper/Escape.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Helper; 8 | 9 | 10 | use Core\Error; 11 | 12 | class Escape 13 | { 14 | const NONE = 0; 15 | const TEXT = 1; 16 | const TEXT_RECURSIVE = 2; 17 | 18 | public static function perform($variable, $escapeType = self::TEXT_RECURSIVE) 19 | { 20 | switch ($escapeType) { 21 | case self::NONE: 22 | return $variable; 23 | case self::TEXT: 24 | return is_string($variable) ? htmlspecialchars($variable) : $variable; 25 | case self::TEXT_RECURSIVE: 26 | if (is_string($variable)) { 27 | return self::perform($variable, self::TEXT); 28 | } elseif (is_array($variable)) { 29 | foreach ($variable as $key => $value) { 30 | $variable[$key] = self::perform($value); 31 | } 32 | } 33 | return $variable; 34 | default: 35 | throw new Error("Unknown escape type: {$escapeType}"); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Library/Helper/Message.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Helper; 8 | 9 | use Core\Filter; 10 | use Core\I18N; 11 | use Core\Response; 12 | use Core\Template; 13 | 14 | class Message 15 | { 16 | /** 17 | * Show text and redirect to another page 18 | * @param string $text Content 19 | * @param string $link Target page 20 | * @param int $timeout Time before redirect 21 | */ 22 | public static function show($text, $link = null, $timeout = 3) 23 | { 24 | Template::setView('Misc/Redirect'); 25 | if (is_array($text)) { 26 | array_unshift($text, $text[0]); // Set fallback string 27 | Template::putContext('text', call_user_func_array(array('I18N', 'parse'), $text)); 28 | } else { 29 | Template::putContext('text', I18N::parse($text, $text)); 30 | } 31 | Template::putContext('timeout', $timeout); 32 | Template::putContext('link', $link === null ? null : Response::generateURL($link)); 33 | Filter::preRender(); 34 | Template::render(); 35 | Filter::afterRender(); 36 | exit(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Library/Helper/PHPLock.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Helper; 8 | 9 | class PHPLock 10 | { 11 | private $_PHP_LOCK = array(); 12 | private $code; 13 | private $lockId = 1; 14 | 15 | public function __construct(&$code) 16 | { 17 | $this->code = &$code; 18 | $this->_PHP_LOCK = array(); 19 | } 20 | 21 | public function acquire() 22 | { 23 | $this->code = preg_replace_callback('/<\?php.+?\?>/is', array($this, '_lockContent'), $this->code); 24 | } 25 | 26 | public function release() 27 | { 28 | foreach ($this->_PHP_LOCK as $LOCK_ID => $sourceCode) { 29 | $this->code = str_replace($LOCK_ID, $sourceCode, $this->code); 30 | } 31 | $this->_PHP_LOCK = array(); 32 | } 33 | 34 | public function syncContent($newCode) 35 | { 36 | $this->code = $newCode; 37 | return $this; 38 | } 39 | 40 | public function getContent() 41 | { 42 | return $this->code; 43 | } 44 | 45 | private function _lockContent($matches) 46 | { 47 | $LOCK = '___PHP_CODE_LOCK_' . ($this->lockId++) . '___'; 48 | $this->_PHP_LOCK[$LOCK] = $matches[0]; 49 | return $LOCK; 50 | } 51 | } -------------------------------------------------------------------------------- /Library/Core/LazyLoadObject.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Core; 8 | 9 | class LazyLoadObject 10 | { 11 | private $value; 12 | private $object = null; 13 | private $callback = null; 14 | private $hasValue = false; 15 | 16 | public function __construct($value, callable $callback) 17 | { 18 | $this->value = $value; 19 | $this->callback = $callback; 20 | } 21 | 22 | public function __debugInfo() 23 | { 24 | if (!$this->hasValue) { 25 | return array( 26 | 'loaded' => false, 27 | 'value' => $this->value 28 | ); 29 | } else { 30 | return array( 31 | 'loaded' => true, 32 | 'value' => $this->object 33 | ); 34 | } 35 | } 36 | 37 | public function __invoke() 38 | { 39 | if (!$this->hasValue) { 40 | $this->object = ($this->callback)($this->value); 41 | $this->hasValue = true; 42 | } 43 | return $this->object; 44 | } 45 | 46 | public function bindValue(&$newValue) 47 | { 48 | $this->value = $newValue; 49 | $this->object = null; 50 | $this->hasValue = false; 51 | } 52 | 53 | public function updateValue($newValue) 54 | { 55 | $this->value = $newValue; 56 | $this->object = null; 57 | $this->hasValue = false; 58 | } 59 | 60 | public function getValue() 61 | { 62 | return $this->value; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Library/Core/Request.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Core; 8 | 9 | class Request 10 | { 11 | /** 12 | * Detect base url whether it is not defined 13 | * @return string Base URL of this forum 14 | */ 15 | public static function autoDetectBaseURL() 16 | { 17 | if (!defined('BASE_URL')) { 18 | $protocol = self::isSecureRequest() ? 'https' : 'http'; 19 | $host = $_SERVER['SERVER_NAME']; 20 | $subFolder = str_replace('index.php', '', $_SERVER['SCRIPT_NAME']); 21 | define('BASE_URL', "{$protocol}://{$host}{$subFolder}"); 22 | } 23 | return BASE_URL; 24 | } 25 | 26 | /** 27 | * Check whether this is a request via HTTPS 28 | * @return bool True if is HTTPS request 29 | */ 30 | public static function isSecureRequest() 31 | { 32 | if (!defined('IS_HTTPS_REQUEST')) { 33 | define('IS_HTTPS_REQUEST', ($_SERVER['HTTPS'] && $_SERVER['HTTPS'] != 'off')); 34 | } 35 | return IS_HTTPS_REQUEST; 36 | } 37 | 38 | /** 39 | * Get request path from server header 40 | * @return string Path 41 | */ 42 | public static function getRequestPath() 43 | { 44 | if ($_SERVER['PATH_INFO']) { 45 | $path = trim($_SERVER['PATH_INFO'], '/'); 46 | } else { 47 | list($path) = explode('?', $_SERVER['REQUEST_URI']); 48 | $folder = str_replace('/index.php', '', $_SERVER['PHP_SELF']); 49 | $path = str_replace($folder, '', $path); 50 | } 51 | return $path; 52 | } 53 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const ExtractTextPlugin = require("extract-text-webpack-plugin"); 4 | 5 | var options = { 6 | entry: { 7 | Demo: './Resource/Demo/test.js', 8 | Error: './Resource/Misc/Error.css' 9 | }, 10 | resolve: { 11 | alias: { 12 | 'react': 'react-lite', 13 | 'react-dom': 'react-lite' 14 | }, 15 | extensions: [".js", ".jsx"] 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.(sa|sc|c)ss$/, 21 | use: ExtractTextPlugin.extract({ 22 | fallback: "style-loader", 23 | use: [ 24 | "css-loader", 25 | { 26 | loader: "postcss-loader", 27 | options: { 28 | plugins: [ 29 | require("autoprefixer")({ browsers: ["ie >= 9", "> 2%", "last 1 version"] }) 30 | ] 31 | } 32 | }, 33 | "sass-loader" 34 | ] 35 | }) 36 | }, { 37 | test: /\.(jpe?g|gif|png|svg|woff\d*|ttf|eot)(\?.*|#.*)?$/, 38 | use: "file-loader?limit=8192&name=Assert/[hash:hex:6].[ext]" 39 | }, { 40 | test: /\.js|jsx$/, 41 | exclude: /(node_modules|bower_components)/, 42 | loader: "babel-loader", 43 | options: { 44 | presets: ["es2015", "react"] 45 | } 46 | } 47 | ] 48 | }, 49 | output: { 50 | path: path.resolve(__dirname, "Public/Resource"), 51 | filename: '[name].js', 52 | publicPath: '/Resource/' 53 | }, 54 | plugins: [ 55 | new ExtractTextPlugin("[name].css") 56 | ] 57 | }; 58 | 59 | module.exports = function(env){ 60 | return require('./.webpack/' + (env == 'prod' ? 'prod' : 'dev') + '.js')(options); 61 | }; 62 | -------------------------------------------------------------------------------- /Data/Config.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | if (!defined('ROOT_PATH')) { 8 | exit('This file could not be access directly.'); 9 | } 10 | 11 | /** 12 | * Site Name 13 | */ 14 | define('SITE_NAME', 'KK Framework Demo Site'); 15 | 16 | /** 17 | * Rewrite setting: 18 | * remove "index.php" from url, needs to config apache/nginx manually 19 | */ 20 | define('USE_REWRITE', true); 21 | 22 | /** 23 | * Encrypt Key: 24 | * This key is used to encrypt password and other information. 25 | * Don't touch it after application install finished. 26 | */ 27 | define('ENCRYPT_KEY', 'Please generate key and paste here'); 28 | 29 | /** 30 | * HTTPS support: 31 | * Use HTTPS connection when necessary, needs to config apache/nginx manually 32 | */ 33 | define('HTTPS_SUPPORT', true); 34 | 35 | /** 36 | * Enable debug mode: 37 | * Disable debug mode will hide backtrace information, which is helpful for developer 38 | */ 39 | define('DEBUG_ENABLE', true); 40 | 41 | /** 42 | * Real time mode: 43 | * This option will disable i18n / router / template cache, development only. 44 | * DO NOT TURN ON THIS OPTION IN PRODUCTION!!! 45 | */ 46 | define('REAL_TIME_MODE', false); 47 | 48 | /** 49 | * Base URL: 50 | * To manually config this, uncomment the following line and change the URL 51 | * To use auto detect, keep this commented 52 | */ 53 | // define('BASE_URL', 'http://www.kookxiang.com'); 54 | Core\Request::autoDetectBaseURL(); 55 | 56 | /** 57 | * Set i18n translation file 58 | * If you don't need this, simply comment out the following line 59 | */ 60 | Core\I18N::setTranslationFile(LIBRARY_PATH . 'Language/en-US.yml'); 61 | 62 | /** 63 | * Database Connection: 64 | */ 65 | Core\Database::initialize('mysql:dbname=test;host=localhost;charset=UTF8', 'root', ''); 66 | -------------------------------------------------------------------------------- /Library/Core/Response.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Core; 8 | 9 | class Response 10 | { 11 | /** 12 | * Redirect to current page via HTTP 13 | */ 14 | public static function redirectToHttp() 15 | { 16 | if (!Request::isSecureRequest()) { 17 | return; 18 | } 19 | self::redirect('http://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']); 20 | } 21 | 22 | /** 23 | * HTTP 302 redirection 24 | * @param string $target TargetURL 25 | */ 26 | public static function redirect($target) 27 | { 28 | $target = self::generateURL($target); 29 | Filter::redirect($target); 30 | header('Location: ' . $target); 31 | exit(); 32 | } 33 | 34 | /** 35 | * Generate a url, check rewrite setting automatically 36 | * @param string $target Target URL 37 | * @return string 38 | */ 39 | public static function generateURL($target) 40 | { 41 | if (strpos($target, '//') !== false) { 42 | return $target; 43 | } 44 | if (file_exists(ROOT_PATH . $target)) { 45 | return BASE_URL . $target; 46 | } 47 | if (defined('USE_REWRITE') && USE_REWRITE) { 48 | return BASE_URL . $target; 49 | } 50 | return BASE_URL . 'index.php/' . $target; 51 | } 52 | 53 | /** 54 | * Redirect to current page via HTTPS 55 | */ 56 | public static function redirectToHttps() 57 | { 58 | if (!defined('HTTPS_SUPPORT') || !HTTPS_SUPPORT) { 59 | return; 60 | } 61 | if (Request::isSecureRequest()) { 62 | return; 63 | } 64 | self::redirect('https://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Library/Core/Filter.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Core; 8 | 9 | final class Filter 10 | { 11 | private static $registeredFilter = array(); 12 | 13 | public static function register(IFilter $filter) 14 | { 15 | $className = get_class($filter); 16 | foreach (self::$registeredFilter as $existFilter) { 17 | if (get_class($existFilter) == $className) { 18 | return; 19 | } 20 | } 21 | self::$registeredFilter[] = $filter; 22 | } 23 | 24 | public static function remove(IFilter $filter) 25 | { 26 | $className = get_class($filter); 27 | foreach (self::$registeredFilter as $key => $existFilter) { 28 | if (get_class($existFilter) == $className) { 29 | unset(self::$registeredFilter[$key]); 30 | return; 31 | } 32 | } 33 | } 34 | 35 | public static function preRoute(&$path){ 36 | foreach (self::$registeredFilter as $filter) { 37 | $filter->preRoute($path); 38 | } 39 | } 40 | public static function afterRoute(&$className, &$method){ 41 | foreach (self::$registeredFilter as $filter) { 42 | $filter->afterRoute($className, $method); 43 | } 44 | } 45 | public static function preRender(){ 46 | foreach (self::$registeredFilter as $filter) { 47 | $filter->preRender(); 48 | } 49 | } 50 | public static function afterRender(){ 51 | foreach (self::$registeredFilter as $filter) { 52 | $filter->afterRender(); 53 | } 54 | } 55 | public static function redirect(&$targetUrl){ 56 | foreach (self::$registeredFilter as $filter) { 57 | $filter->redirect($targetUrl); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Library/Helper/Reflection.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Helper; 8 | 9 | use ReflectionFunctionAbstract; 10 | 11 | class Reflection 12 | { 13 | public static function parseDocComment(ReflectionFunctionAbstract $reflectionFunction) 14 | { 15 | $markers = array(); 16 | /** 17 | * Ignore standard doc comment 18 | * @link https://phpdoc.org/docs/latest/references/phpdoc/basic-syntax.html 19 | */ 20 | if ($reflectionFunction instanceof \ReflectionMethod) { 21 | $commentBlackList = array('param', 'return', 'throws', 'author', 'version', 'copyright'); 22 | } elseif ($reflectionFunction instanceof \ReflectionClass) { 23 | $commentBlackList = array('category', 'package', 'subpackage', 'author', 'version', 'copyright'); 24 | } elseif ($reflectionFunction instanceof \ReflectionProperty) { 25 | $commentBlackList = array('var', 'author', 'version', 'copyright'); 26 | } else { 27 | $commentBlackList = array('author', 'version', 'copyright'); 28 | } 29 | 30 | // Normalize line-ending, \r\n from Windows, \n from others 31 | $comment = str_replace("\r\n", "\n", $reflectionFunction->getDocComment()); 32 | 33 | // Split to lines 34 | $lines = explode("\n", $comment); 35 | foreach ($lines as $line) { 36 | $line = trim($line, "\t\r\n\0\x0B* "); 37 | if ($line{0} != '@') { 38 | // Only parse comment start with '@' 39 | continue; 40 | } 41 | if (preg_match('/^@([A-Za-z0-9\-_]+) ?(.*)$/', $line, $matches)) { 42 | if (in_array($matches[1], $commentBlackList)) { 43 | continue; 44 | } 45 | $markers[$matches[1]] = $matches[2] !== '' ? $matches[2] : true; 46 | } 47 | } 48 | 49 | return $markers; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Library/Core/DefaultRouter.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Core; 8 | 9 | class DefaultRouter 10 | { 11 | protected $foundController = false; 12 | 13 | public function handleRequest() 14 | { 15 | $requestPath = Request::getRequestPath(); 16 | $requestPath = ltrim($requestPath, '/'); 17 | if (!$requestPath) { 18 | $requestPath = 'Index'; 19 | } 20 | Filter::preRoute($requestPath); 21 | $this->findController($requestPath); 22 | if (!$this->foundController) { 23 | throw new Error(I18N::parse('Error.Messages.PageNotExists', 'The request URL is not exists'), 404); 24 | } 25 | } 26 | 27 | protected function findController($requestPath, $subDir = '') 28 | { 29 | list($controller, $method) = explode('/', $requestPath, 2); 30 | $controller = ucfirst($controller); 31 | if (is_dir(LIBRARY_PATH . "Controller/{$subDir}{$controller}")) { 32 | if (!$method) { 33 | $method = 'Index'; 34 | } 35 | $this->findController($method, $subDir . $controller . '/'); 36 | } elseif (file_exists(LIBRARY_PATH . "Controller/{$subDir}{$controller}.php")) { 37 | if (!$method) { 38 | $method = 'index'; 39 | } else { 40 | $method = lcfirst($method); 41 | } 42 | $className = str_replace('/', '\\', "Controller/{$subDir}{$controller}"); 43 | $controller = new $className(); 44 | if (method_exists($controller, $method)) { 45 | Filter::afterRoute($className, $method); 46 | $context = $controller->$method(); 47 | if ($context) { 48 | Template::setContext($context); 49 | } 50 | Filter::preRender(); 51 | Template::render(); 52 | Filter::afterRender(); 53 | $this->foundController = true; 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Resource/Misc/Error.css: -------------------------------------------------------------------------------- 1 | /* 2 | * KK-Framework 3 | * Author: kookxiang 4 | */ 5 | 6 | ::selection { 7 | background: rgba(78, 176, 248, 0.3); 8 | } 9 | 10 | html, body { 11 | width: 100%; 12 | height: 100%; 13 | cursor: default; 14 | } 15 | 16 | html, body, p, h2, div { 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | body { 22 | background: #2980B9; 23 | text-align: center; 24 | user-select: none; 25 | } 26 | 27 | html { 28 | font: 12px "Segoe UI", "Microsoft YaHei", FreeSans, Arimo, "Droid Sans", "Hiragino Sans GB", "Hiragino Sans GB W3", Arial, sans-serif; 29 | } 30 | 31 | h2 { 32 | margin-bottom: 25px; 33 | font-size: 30px; 34 | font-weight: 300; 35 | color: #e05d6f; 36 | } 37 | 38 | p { 39 | line-height: 1.5em; 40 | font-size: 12px; 41 | color: #95a2a9; 42 | margin-bottom: 5px; 43 | } 44 | 45 | .title { 46 | position: relative; 47 | top: 75px; 48 | margin-bottom: .7em; 49 | line-height: 30px; 50 | font-size: 26px; 51 | font-weight: 300; 52 | color: #fff; 53 | text-shadow: 0 0 4px #666666; 54 | } 55 | 56 | .box { 57 | position: relative; 58 | top: 80px; 59 | width: 600px; 60 | max-width: 85%; 61 | margin: 0 auto; 62 | background: #fff; 63 | padding: 15px; 64 | box-shadow: 0 0 50px #2964B9; 65 | } 66 | 67 | .main { 68 | font-size: 18px; 69 | color: #000; 70 | font-weight: 500; 71 | line-height: 1.7em; 72 | margin: 0 0 10px; 73 | } 74 | 75 | .foot { 76 | position: relative; 77 | top: 80px; 78 | margin: 15px 15px 0; 79 | font-size: 12px; 80 | color: #4eb0f8; 81 | } 82 | 83 | pre { 84 | background: #3498DB; 85 | color: #ffffff; 86 | padding: 15px 20px; 87 | margin: 25px -15px -15px; 88 | line-height: 1.4em; 89 | font-size: 14px; 90 | text-align: left; 91 | word-break: break-all; 92 | white-space: pre-wrap; 93 | } 94 | 95 | .main, pre { 96 | user-select: text; 97 | -webkit-user-select: text; 98 | -moz-user-select: text; 99 | -ms-user-select: text; 100 | cursor: text; 101 | } 102 | 103 | pre::selection { 104 | background: rgba(255, 255, 255, 0.99); 105 | color: #3498DB; 106 | } 107 | 108 | .redirect { 109 | text-decoration: none; 110 | color: #95a2a9; 111 | } 112 | 113 | .redirect:hover { 114 | text-decoration: underline; 115 | } -------------------------------------------------------------------------------- /Library/Core/Database.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Core; 8 | 9 | class Database extends \PDO 10 | { 11 | private static $instance; 12 | 13 | /** 14 | * Initialize database config 15 | * @link http://php.net/manual/pdo.construct.php 16 | * @param string $dsn Data Source Name, contains the information required to connect to the database. 17 | * @param string $username Username for the DSN string 18 | * @param string $password Password for the DSN string 19 | * @param array $options A key=>value array of driver-specific connection options. 20 | * @throws Error 21 | */ 22 | public static function initialize($dsn, $username = null, $password = null, $options = array()) 23 | { 24 | if (self::$instance) { 25 | throw new Error('Cannot re-initialize database'); 26 | } 27 | self::$instance = new Database($dsn, $username, $password, $options); 28 | self::$instance->setAttribute(self::ATTR_ERRMODE, self::ERRMODE_EXCEPTION); 29 | 30 | $currentSqlMode = Database::getInstance()->query('SELECT @@GLOBAL.SQL_MODE')->fetchColumn(); 31 | if (strpos($currentSqlMode, 'STRICT_TRANS_TABLES')) { 32 | $currentSqlMode = explode(',', $currentSqlMode); 33 | $strictTransTable = array_search('STRICT_TRANS_TABLES', $currentSqlMode); 34 | unset($currentSqlMode[$strictTransTable]); 35 | $statement = self::$instance->prepare('SET SESSION sql_mode = ?'); 36 | $statement->bindValue('1', implode(',', $currentSqlMode)); 37 | $statement->execute(); 38 | } 39 | } 40 | 41 | /** 42 | * Prepares a statement for execution and returns a statement object 43 | * @link http://php.net/manual/en/pdo.prepare.php 44 | * @param string $statement 45 | * @param array $driver_options [optional] 46 | * @return \PDOStatement 47 | */ 48 | public static function sql($statement, array $driver_options = array()) 49 | { 50 | return self::getInstance()->prepare($statement, $driver_options); 51 | } 52 | 53 | /** 54 | * Get current database connection 55 | * @return Database 56 | */ 57 | public static function getInstance() 58 | { 59 | return self::$instance; 60 | } 61 | 62 | /** 63 | * Magic method: shorten code Database::getInstance()->xxx() to Database::_xxx() 64 | * @param $name string method name 65 | * @param $arguments array 66 | */ 67 | public static function __callStatic($name, $arguments) 68 | { 69 | if ($name{0} == '_') { 70 | $name = substr($name, 1); 71 | } 72 | if (method_exists(self::$instance, $name)) { 73 | return call_user_func_array(array(self::$instance, $name), $arguments); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Library/Core/I18N.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Core; 8 | 9 | use Symfony\Component\Yaml\Yaml; 10 | 11 | class I18N 12 | { 13 | const CACHE_FILE = DATA_PATH . 'i18n.cache.php'; 14 | 15 | private static $loaded = false; 16 | private static $storage = array(); 17 | private static $translationFile = ''; 18 | 19 | public static function init() 20 | { 21 | if (!self::$translationFile) { 22 | return; 23 | } 24 | self::loadFromCache(); 25 | } 26 | 27 | private static function loadFromCache() 28 | { 29 | $datas = array( 30 | 'FileName' => 'None', 31 | 'Storage' => array() 32 | ); 33 | if (file_exists(self::CACHE_FILE) && !REAL_TIME_MODE) { 34 | /** @noinspection PhpIncludeInspection */ 35 | $datas = include self::CACHE_FILE; 36 | } 37 | if ($datas['FilePath'] == self::$translationFile) { 38 | self::$storage = $datas['Storage']; 39 | self::$loaded = true; 40 | } elseif (file_exists(self::$translationFile)) { 41 | self::loadTranslation(self::$translationFile); 42 | self::saveTranslationCache(); 43 | } 44 | } 45 | 46 | private static function loadTranslation($file) 47 | { 48 | self::$loaded = true; 49 | $translationFile = file_get_contents($file); 50 | $storage = Yaml::parse($translationFile, Yaml::PARSE_OBJECT); 51 | $storage = self::flattenArray($storage); 52 | ksort($storage); 53 | self::$storage = $storage; 54 | unset($storage); 55 | } 56 | 57 | private static function flattenArray($array, $prefix = '') 58 | { 59 | $newArray = array(); 60 | foreach ($array as $key => $value) { 61 | if (!is_array($value)) { 62 | $newArray[$prefix . $key] = $value; 63 | } else { 64 | $newArray = array_merge($newArray, self::flattenArray($value, $prefix . $key . '.')); 65 | } 66 | } 67 | return $newArray; 68 | } 69 | 70 | private static function saveTranslationCache() 71 | { 72 | $datas = array( 73 | 'Storage' => self::$storage, 74 | 'FilePath' => self::$translationFile, 75 | ); 76 | $output = ' 5 | */ 6 | 7 | namespace Helper; 8 | 9 | use Core\Database; 10 | 11 | class PageData 12 | { 13 | protected $recordPrePage = 10; 14 | protected $query; 15 | protected $countQuery; 16 | protected $currentPage = 1; 17 | protected $context = array(); 18 | 19 | /** 20 | * Create a new PageData object. 21 | * @param string $tableName Target table name 22 | * @param string $extras Such as where statement or order statement 23 | * @param array $column Column names needs to be fetch 24 | */ 25 | public function __construct($tableName, $extras = '', $column = array('*')) 26 | { 27 | $columns = '`' . implode('`, `', $column) . '`'; 28 | $this->countQuery = Database::getInstance()->prepare("SELECT COUNT(*) FROM `{$tableName}` {$extras}"); 29 | $this->query = Database::getInstance()->prepare("SELECT {$columns} FROM `{$tableName}` {$extras} LIMIT :pageDataStart,:pageDataRPP"); 30 | 31 | if ($_GET['page']) { 32 | $this->setPage($_GET['page']); 33 | } 34 | } 35 | 36 | /** 37 | * Binds a parameter to the specified variable name 38 | * @link http://php.net/manual/en/pdostatement.bindparam.php 39 | * @see \PDOStatement::bindParam 40 | * @param mixed $parameter 41 | * @param mixed $variable 42 | * @param int $data_type 43 | * @param int $length 44 | * @param mixed $driver_options 45 | */ 46 | public function bindParam( 47 | $parameter, 48 | &$variable, 49 | $data_type = Database::PARAM_STR, 50 | $length = null, 51 | $driver_options = null 52 | ) { 53 | $this->countQuery->bindParam($parameter, $variable, $data_type, $length, $driver_options); 54 | $this->query->bindParam($parameter, $variable, $data_type, $length, $driver_options); 55 | } 56 | 57 | /** 58 | * Binds a value to a parameter 59 | * @link http://php.net/manual/en/pdostatement.bindvalue.php 60 | * @see \PDOStatement::bindValue 61 | * @param mixed $parameter 62 | * @param mixed $value 63 | * @param int $data_type 64 | */ 65 | public function bindValue($parameter, $value, $data_type = Database::PARAM_STR) 66 | { 67 | $this->countQuery->bindValue($parameter, $value, $data_type); 68 | $this->query->bindValue($parameter, $value, $data_type); 69 | } 70 | 71 | public function setPage($currentPage) 72 | { 73 | $this->currentPage = max(1, intval($currentPage)); 74 | } 75 | 76 | public function setRecordPrePage(int $recordPrePage) 77 | { 78 | $this->recordPrePage = $recordPrePage; 79 | } 80 | 81 | public function execute() 82 | { 83 | $this->countQuery->execute(); 84 | $totalRecord = $this->countQuery->fetchColumn(); 85 | $totalPage = max(ceil($totalRecord / $this->recordPrePage), 1); 86 | $this->currentPage = min($this->currentPage, $totalPage); 87 | $this->query->bindValue(':pageDataStart', ($this->currentPage - 1) * $this->recordPrePage, Database::PARAM_INT); 88 | $this->query->bindValue(':pageDataRPP', $this->recordPrePage, Database::PARAM_INT); 89 | $this->query->execute(); 90 | $data = $this->query->fetchAll(Database::FETCH_ASSOC); 91 | return $this->context = array( 92 | 'data' => $data, 93 | 'totalPage' => $totalPage, 94 | 'currentPage' => $this->currentPage, 95 | 'pageSize' => $this->recordPrePage 96 | ); 97 | } 98 | 99 | public function getContext() 100 | { 101 | return $this->context; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Library/Helper/JSON.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Helper; 8 | 9 | use Core\Error; 10 | use Core\IFilter; 11 | use Core\Template; 12 | use Helper\Reflection as ReflectionHelper; 13 | use ReflectionMethod; 14 | 15 | class JSON implements IFilter 16 | { 17 | protected static $statusCode = 200; 18 | protected $handle = false; 19 | protected $allowCallback = false; 20 | 21 | public static function setStatusCode($statusCode) 22 | { 23 | self::$statusCode = $statusCode; 24 | } 25 | 26 | public function preRender() 27 | { 28 | if ($this->handle) { 29 | $context = Template::getContext(); 30 | if (Template::getView() == 'Misc/Error') { 31 | /** @var Error $error */ 32 | $error = $context['instance']; 33 | $this->outputJson(array( 34 | 'code' => $error->getCode() ? $error->getCode() : 500, 35 | 'data' => null, 36 | 'hasError' => true, 37 | 'message' => $error->getMessage(), 38 | )); 39 | } elseif (Template::getView() == 'Misc/Redirect') { 40 | $this->outputJson(array( 41 | 'code' => 302, 42 | 'data' => null, 43 | 'hasError' => true, 44 | 'message' => $context['text'] ? $context['text'] : 'JSON request has been redirected', 45 | 'target' => $context['link'] 46 | )); 47 | } else { 48 | $this->outputJson(array( 49 | 'code' => self::$statusCode, 50 | 'data' => $context, 51 | )); 52 | } 53 | } 54 | } 55 | 56 | private function outputJson($jsonData) 57 | { 58 | header('Content-type: application/json'); 59 | if ($this->allowCallback) { 60 | $callback = $_POST['callback'] ? $_POST['callback'] : $_GET['callback']; 61 | if (preg_match('/[A-Za-z_0-9]+/', $callback)) { 62 | echo "{$callback}("; 63 | header('Content-type: application/javascript'); 64 | } else { 65 | $this->allowCallback = false; 66 | } 67 | } 68 | echo json_encode($jsonData); 69 | if ($this->allowCallback) { 70 | echo ');'; 71 | } 72 | exit(); 73 | } 74 | 75 | public function preRoute(&$path) 76 | { 77 | if ($_SERVER['HTTP_X_REQUESTED_WITH']) { 78 | $this->handle = true; 79 | } 80 | } 81 | 82 | public function afterRoute(&$className, &$method) 83 | { 84 | if ($_SERVER['HTTP_X_REQUESTED_WITH']) { 85 | // Check if method allow json output 86 | $reflection = new ReflectionMethod($className, $method); 87 | $markers = ReflectionHelper::parseDocComment($reflection); 88 | if ($markers['JSONP']) { 89 | $this->allowCallback = true; 90 | } 91 | if (!$markers['JSON'] && !$markers['JSONP']) { 92 | throw new Error('The request URL is not available', 403); 93 | } 94 | } 95 | } 96 | 97 | public function afterRender() 98 | { 99 | // Do nothing. 100 | } 101 | 102 | public function redirect(&$targetUrl) 103 | { 104 | if ($this->handle) { 105 | $this->outputJson(array( 106 | 'code' => 302, 107 | 'data' => null, 108 | 'hasError' => true, 109 | 'message' => 'JSON request has been redirected', 110 | 'target' => $targetUrl 111 | )); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Library/Core/Error.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Core; 8 | 9 | class Error extends \Exception 10 | { 11 | private $trace; 12 | 13 | /** 14 | * Create a Exception 15 | * @param string $message Error message 16 | * @param int $code Error code 17 | * @param \Throwable $previous Previous exception 18 | * @param array $trace Backtrace information 19 | */ 20 | public function __construct($message = 'Internal Server Error', $code = 0, $previous = null, $trace = array()) 21 | { 22 | parent::__construct($message, $code, $previous); 23 | $this->trace = $trace; 24 | if (!$trace) { 25 | $this->trace = debug_backtrace(); 26 | } 27 | } 28 | 29 | /** 30 | * Register custom handler for uncaught exception and errors 31 | */ 32 | public static function registerHandler() 33 | { 34 | set_exception_handler(array(__CLASS__, 'handleUncaughtException')); 35 | set_error_handler(array(__CLASS__, 'handlePHPError'), E_ALL); 36 | } 37 | 38 | public static function handlePHPError($errNo, $errStr, $errFile, $errLine) 39 | { 40 | if ($errNo == E_STRICT) { 41 | return; 42 | } 43 | if ($errNo == E_NOTICE) { 44 | return; 45 | } 46 | //if($errNo == E_DEPRECATED) return; 47 | $trace = debug_backtrace(); 48 | array_unshift($trace, array('file' => $errFile, 'line' => $errLine)); 49 | $exception = new self($errStr, $errNo, null, $trace); 50 | self::handleUncaughtException($exception); 51 | } 52 | 53 | 54 | /** 55 | * @param \Throwable $instance Exception or Error instance 56 | * @throws Error 57 | */ 58 | public static function handleUncaughtException($instance) 59 | { 60 | @ob_end_clean(); 61 | if (Database::getInstance() && Database::getInstance()->inTransaction()) { 62 | Database::getInstance()->rollBack(); 63 | } 64 | if (!($instance instanceof Error)) { 65 | $instance = new self($instance->getMessage(), intval($instance->getCode()), $instance, 66 | $instance->getTrace()); 67 | } 68 | Template::setView('Misc/Error'); 69 | Template::putContext('instance', $instance); 70 | Filter::preRender(); 71 | Template::render(); 72 | Filter::afterRender(); 73 | exit(); 74 | } 75 | 76 | /** 77 | * Format backtrace information 78 | * @return string Formatted backtrace information 79 | */ 80 | public function formatBackTrace() 81 | { 82 | $backtrace = $this->trace; 83 | krsort($backtrace); 84 | $trace = ''; 85 | foreach ($backtrace as $error) { 86 | if ($error['function'] == 'spl_autoload_call') { 87 | continue; 88 | } 89 | if ($error['function'] == 'getBackTrace') { 90 | continue; 91 | } 92 | if ($error['function'] == 'handlePHPError') { 93 | continue; 94 | } 95 | if ($error['function'] == 'handleUncaughtException') { 96 | continue; 97 | } 98 | $error['line'] = $error['line'] ? ":{$error['line']}" : ''; 99 | $log = ''; 100 | if ($error['file']) { 101 | $log = str_replace(str_replace('\\', '/', ROOT_PATH), '', $error['file']) . $error['line']; 102 | } 103 | if ($error['class']) { 104 | $error['class'] = str_replace('\\', '/', $error['class']); 105 | $log .= " ({$error['class']}{$error['type']}{$error['function']})"; 106 | } 107 | $trace .= "{$log}\r\n"; 108 | } 109 | return $trace; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Library/Core/Model.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Core; 8 | 9 | use ReflectionObject; 10 | use ReflectionProperty; 11 | 12 | abstract class Model 13 | { 14 | /** 15 | * @ignore 16 | * @var LazyLoadObject[] $lazyLoadObj 17 | */ 18 | protected $_lazyLoad = array(); 19 | 20 | const SAVE_AUTO = 0; 21 | const SAVE_INSERT = 1; 22 | const SAVE_UPDATE = 2; 23 | 24 | public function __construct() 25 | { 26 | $this->lazyLoad(); 27 | } 28 | 29 | protected function lazyLoad() 30 | { 31 | } 32 | 33 | public function delete() 34 | { 35 | $reflection = new ReflectionObject($this); 36 | $primaryKey = $this->getPrimaryKeyName($reflection); 37 | $property = $reflection->getProperty($primaryKey); 38 | $property->setAccessible(true); 39 | $primaryValue = $property->getValue($this); 40 | if (!$primaryValue) { 41 | throw new Error('Cannot delete object without id'); 42 | } 43 | $tableName = $this->getTableName($reflection); 44 | $statement = Database::getInstance()->prepare("DELETE FROM `{$tableName}` WHERE `{$primaryKey}`=:value"); 45 | $statement->bindValue(':value', $primaryValue); 46 | $statement->execute(); 47 | } 48 | 49 | public function update() 50 | { 51 | $reflection = new ReflectionObject($this); 52 | $map = $this->getTableMap($reflection); 53 | $primaryKey = $this->getPrimaryKeyName($reflection); 54 | $tableName = $this->getTableName($reflection); 55 | if (empty($map[$primaryKey])) { 56 | throw new Error('Cannot update a record without id'); 57 | } 58 | $sql = "UPDATE `{$tableName}` SET "; 59 | foreach ($map as $key => $value) { 60 | $sql .= "`{$key}` = :{$key},"; 61 | } 62 | $sql = rtrim($sql, ','); 63 | $sql .= " WHERE {$primaryKey} = :id"; 64 | $statement = Database::getInstance()->prepare($sql); 65 | $statement->bindValue(':id', $map[$primaryKey]); 66 | foreach ($map as $key => $value) { 67 | $statement->bindValue(":{$key}", $value); 68 | } 69 | $statement->execute(); 70 | } 71 | 72 | public function insert() 73 | { 74 | $reflection = new ReflectionObject($this); 75 | $map = $this->getTableMap($reflection); 76 | $primaryKey = $this->getPrimaryKeyName($reflection); 77 | $tableName = $this->getTableName($reflection); 78 | 79 | $sql = "INSERT INTO `{$tableName}` SET "; 80 | foreach ($map as $key => $value) { 81 | $sql .= "`{$key}` = :{$key},"; 82 | } 83 | $sql = rtrim($sql, ','); 84 | $statement = Database::getInstance()->prepare($sql); 85 | foreach ($map as $key => $value) { 86 | $statement->bindValue(":{$key}", $value); 87 | } 88 | $statement->execute(); 89 | $insertId = Database::getInstance()->lastInsertId(); 90 | if ($insertId) { 91 | $reflection->getProperty($primaryKey)->setValue($this, $insertId); 92 | } 93 | } 94 | 95 | public function save($mode = self::SAVE_AUTO) 96 | { 97 | if ($mode == self::SAVE_UPDATE) { 98 | $this->update(); 99 | } elseif ($mode == self::SAVE_INSERT) { 100 | $this->insert(); 101 | } else { 102 | $reflection = new ReflectionObject($this); 103 | $primaryKeyName = $this->getPrimaryKeyName($reflection); 104 | $primaryKey = $reflection->getProperty($primaryKeyName); 105 | $primaryKey->setAccessible(true); 106 | $identifier = $primaryKey->getValue($this); 107 | if ($identifier) { 108 | $this->update(); 109 | } else { 110 | $this->insert(); 111 | } 112 | } 113 | } 114 | 115 | private function getPrimaryKeyName(ReflectionObject $reflection) 116 | { 117 | $primaryKeyName = 'id'; 118 | $reflectionProp = $reflection->getProperties(ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC); 119 | foreach ($reflectionProp as $property) { 120 | if (stripos($property->getDocComment(), '@PrimaryKey')) { 121 | $primaryKeyName = $property->getName(); 122 | } 123 | } 124 | return $primaryKeyName; 125 | } 126 | 127 | private function getTableName(ReflectionObject $reflection) 128 | { 129 | $docComment = $reflection->getDocComment(); 130 | if (!preg_match('/@table ?([A-Za-z\-_0-9]+)/i', $docComment, $matches) || !$matches[1]) { 131 | return strtolower($reflection->getShortName()); 132 | } else { 133 | return $matches[1]; 134 | } 135 | } 136 | 137 | private function getTableMap(ReflectionObject $reflection) 138 | { 139 | $map = array(); 140 | $reflectionProp = $reflection->getProperties(ReflectionProperty::IS_PRIVATE | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC); 141 | foreach ($reflectionProp as $property) { 142 | if (strpos($property->getDocComment(), '@ignore')) { 143 | continue; 144 | } 145 | $propertyName = $property->getName(); 146 | if (isset($this->_lazyLoad[$propertyName])) { 147 | $map[$propertyName] = $this->_lazyLoad[$propertyName]->getValue(); 148 | continue; 149 | } 150 | $property->setAccessible(true); 151 | $propertyValue = $property->getValue($this); 152 | $map[$propertyName] = $propertyValue; 153 | } 154 | return $map; 155 | } 156 | 157 | protected function setLazyLoad($propertyName, callable $callback) 158 | { 159 | // First, save the value 160 | $value = $this->$propertyName; 161 | // Create LazyLoadObject 162 | $this->_lazyLoad[$propertyName] = new LazyLoadObject($value, $callback); 163 | // Remove the property to active getter and setter 164 | unset($this->$propertyName); 165 | } 166 | 167 | public function __get($propertyName) 168 | { 169 | if (isset($this->_lazyLoad[$propertyName])) { 170 | return ($this->_lazyLoad[$propertyName])(); 171 | } 172 | return null; 173 | } 174 | 175 | public function __set($propertyName, $value) 176 | { 177 | if (isset($this->_lazyLoad[$propertyName])) { 178 | ($this->_lazyLoad[$propertyName])->updateValue($value); 179 | } else { 180 | $this->$propertyName = $value; 181 | } 182 | } 183 | 184 | public function __wakeup() 185 | { 186 | $this->lazyLoad(); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /Library/Core/ReflectionRouter.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Core; 8 | 9 | use Helper\Reflection as ReflectionHelper; 10 | 11 | class ReflectionRouter 12 | { 13 | const CACHE_FILE = DATA_PATH . 'router.cache.php'; 14 | 15 | private $Loaded = false; 16 | private $StaticRoute = array(); 17 | private $DynamicRoute = array(); 18 | private $FallbackRouter = array(); 19 | 20 | private function loadRouterList() 21 | { 22 | if ($this->Loaded) { 23 | return; 24 | } 25 | if (file_exists(self::CACHE_FILE) && !REAL_TIME_MODE) { 26 | /** @noinspection PhpIncludeInspection */ 27 | $router = @include self::CACHE_FILE; 28 | $this->StaticRoute = $router['Static']; 29 | $this->DynamicRoute = $router['Dynamic']; 30 | $this->FallbackRouter = $router['Fallback']; 31 | } else { 32 | $this->generateRouterList(); 33 | $this->saveRouterCache(); 34 | } 35 | $this->Loaded = true; 36 | } 37 | 38 | public function handleRequest() 39 | { 40 | if (!$this->Loaded) { 41 | $this->loadRouterList(); 42 | } 43 | $requestPath = Request::getRequestPath(); 44 | $requestPath = ltrim($requestPath, '/'); 45 | if (!$requestPath) { 46 | $requestPath = '@Home'; 47 | } 48 | Filter::preRoute($requestPath); 49 | $this->findController($requestPath); 50 | } 51 | 52 | private function findController($requestPath) 53 | { 54 | $route = array(); 55 | $parameter = array(); 56 | $context = null; 57 | $key = strtolower($requestPath); 58 | if ($this->StaticRoute[$key]) { 59 | $route = $this->StaticRoute[$key]; 60 | } else { 61 | foreach ($this->DynamicRoute as $router) { 62 | if (!preg_match($router['regexp'], $requestPath, $matches)) { 63 | continue; 64 | } 65 | // Remove the request string 66 | array_shift($matches); 67 | $route = $router['callback']; 68 | $parameter = $matches; 69 | break; 70 | } 71 | } 72 | if (!$route) { 73 | if ($this->FallbackRouter) { 74 | $route = $this->FallbackRouter; 75 | } else { 76 | throw new Error(I18N::parse('Error.Messages.PageNotExists', 'The request URL is not exists'), 404); 77 | } 78 | } 79 | list($className, $method) = $route; 80 | Filter::afterRoute($className, $method); 81 | $controller = new $className(); 82 | if ($parameter) { 83 | $context = call_user_func_array(array($controller, $method), $parameter); 84 | } else { 85 | $context = $controller->$method(); 86 | } 87 | if ($context) { 88 | Template::setContext($context); 89 | } 90 | Filter::preRender(); 91 | Template::render(); 92 | Filter::afterRender(); 93 | } 94 | 95 | private static function includeDir($dir) 96 | { 97 | $files = scandir($dir); 98 | foreach ($files as $file) { 99 | if ($file{0} == '.') { 100 | continue; 101 | } 102 | if (substr($file, -4) == '.php') { 103 | /** @noinspection PhpIncludeInspection */ 104 | include $dir . '/' . $file; 105 | } elseif (is_dir($dir . '/' . $file)) { 106 | self::includeDir($dir . '/' . $file); 107 | } 108 | } 109 | } 110 | 111 | public function generateRouterList() 112 | { 113 | $this->StaticRoute = array(); 114 | $this->DynamicRoute = array(); 115 | $this->FallbackRouter = array(); 116 | try { 117 | self::includeDir(LIBRARY_PATH . 'Controller'); 118 | $classList = get_declared_classes(); 119 | foreach ($classList as $className) { 120 | if (substr($className, 0, 10) != 'Controller') { 121 | continue; 122 | } 123 | $reflectionClass = new \ReflectionClass($className); 124 | $methods = $reflectionClass->getMethods(); 125 | foreach ($methods as $reflectionMethod) { 126 | $markers = ReflectionHelper::parseDocComment($reflectionMethod); 127 | $callback = array($reflectionClass->getName(), $reflectionMethod->getName()); 128 | if ($markers['Home']) { 129 | $this->StaticRoute['@home'] = $callback; 130 | } 131 | if ($markers['Route']) { 132 | $path = strtolower(trim($markers['Route'], ' /')); 133 | $this->StaticRoute[$path] = $callback; 134 | } 135 | if ($markers['DynamicRoute']) { 136 | // Convert wildcard to regular expression 137 | $regexp = '/^' . preg_quote(trim($markers['DynamicRoute'], ' /'), '/') . '$/i'; 138 | $regexp = str_replace('\\{any\\}', '(.+)', $regexp); 139 | $regexp = str_replace('\\{string\\}', '(\w+)', $regexp); 140 | $regexp = str_replace('\\{int\\}', '(\d+)', $regexp); 141 | $this->DynamicRoute[] = array( 142 | 'callback' => $callback, 143 | 'regexp' => $regexp 144 | ); 145 | } 146 | if ($markers['FallbackRoute'] || $markers['FallBackRoute']) { 147 | if ($this->FallbackRouter) { 148 | throw new Error('Fallback route was already defined in ' . $this->FallbackRouter[0] . '->' . $this->FallbackRouter[1]); 149 | } 150 | $this->FallbackRouter = $callback; 151 | } 152 | } 153 | } 154 | } catch (\Exception $e) { 155 | if (DEBUG_ENABLE) { 156 | throw $e; 157 | } else { 158 | throw new Error('Internal Error: Cannot parse router file'); 159 | } 160 | } 161 | } 162 | 163 | public function saveRouterCache() 164 | { 165 | $router = array( 166 | 'Static' => $this->StaticRoute, 167 | 'Dynamic' => $this->DynamicRoute, 168 | 'Fallback' => $this->FallbackRouter, 169 | ); 170 | $output = ' 5 | */ 6 | 7 | namespace Core; 8 | 9 | use Helper\Escape; 10 | use Helper\PHPLock; 11 | 12 | class Template 13 | { 14 | private static $viewName; 15 | private static $context = array(); 16 | 17 | /** 18 | * Load a template from data folder, compile it if it is outdated or not exists 19 | * @param string $templateName 20 | * @param array $context 21 | * @return string 22 | * @throws Error 23 | * @deprecated Please use setView(), putContext(), render() 24 | */ 25 | public static function load($templateName, $context = array()) 26 | { 27 | if ($context) { 28 | self::setContext($context); 29 | } 30 | $templateFileOrigin = self::getPath($templateName); 31 | $templateFile = DATA_PATH . "Template/{$templateName}.php"; 32 | if (!file_exists($templateFile) && !file_exists($templateFileOrigin)) { 33 | throw new Error("Template {$templateName} not exists!", 101); 34 | } 35 | if (!file_exists($templateFile)) { 36 | self::compile($templateName); 37 | } elseif (filemtime($templateFile) <= filemtime($templateFileOrigin)) { 38 | self::compile($templateName); 39 | } elseif (REAL_TIME_MODE) { 40 | self::compile($templateName); 41 | } 42 | return $templateFile; 43 | } 44 | 45 | /** 46 | * Get template file path 47 | * @param string $templateName Template file name 48 | * @return string Absolute path of the template file 49 | */ 50 | public static function getPath($templateName) 51 | { 52 | if (file_exists(ROOT_PATH . "Template/{$templateName}.htm")) { 53 | return ROOT_PATH . "Template/{$templateName}.htm"; 54 | } else { 55 | return ""; 56 | } 57 | } 58 | 59 | public static function render() 60 | { 61 | if (self::$viewName) { 62 | extract(self::$context); 63 | /** @noinspection PhpIncludeInspection, PhpDeprecationInspection */ 64 | include self::load(self::$viewName); 65 | } 66 | } 67 | 68 | public static function setView($viewName) 69 | { 70 | self::$viewName = $viewName; 71 | } 72 | 73 | public static function getView() 74 | { 75 | return self::$viewName; 76 | } 77 | 78 | public static function setContext($context = array()) 79 | { 80 | self::$context = $context; 81 | } 82 | 83 | public static function putContext($name, $value, $escapeType = Escape::TEXT_RECURSIVE) 84 | { 85 | if (!is_array(self::$context)) { 86 | self::$context = array(); 87 | } 88 | self::$context[$name] = Escape::perform($value, $escapeType); 89 | } 90 | 91 | public static function getContext() 92 | { 93 | return self::$context; 94 | } 95 | 96 | private static function compile($templateName) 97 | { 98 | $headers = ''; 99 | $fp = @fopen(self::getPath($templateName), 'rb'); 100 | if (!$fp) { 101 | return; 102 | } 103 | $sourceCode = ''; 104 | while (!feof($fp)) { 105 | $sourceCode .= fread($fp, 8192); 106 | } 107 | $lock = new PHPLock($sourceCode); 108 | $lock->acquire(); 109 | 110 | // i18n replace 111 | $sourceCode = preg_replace_callback('/<(i18n|lang)(.*?)>(.*?)<\/\1>/', array(__CLASS__, 'parseI18nTags'), 112 | $sourceCode); 113 | $sourceCode = preg_replace_callback('/<(i18n|lang)(.*?) ?\/?>/', array(__CLASS__, 'parseI18nTags'), 114 | $sourceCode); 115 | $sourceCode = preg_replace_callback('/#i18n\((.+?)\)/', function ($match) { 116 | return I18N::parse($match[1]); 117 | }, $sourceCode); 118 | $lock->acquire(); 119 | 120 | // variable with braces: 121 | $sourceCode = preg_replace('/\{\$([A-Za-z0-9_\\\[\]\'"]+)(->|::)(\$?[A-Za-z0-9_\-]+)(\(.*\))?}/', 122 | '', $sourceCode); 123 | $sourceCode = preg_replace('/\{\$([A-Za-z0-9_\[\]\'"]+)}/', '', $sourceCode); 124 | $sourceCode = preg_replace('/\{([A-Z][A-Z0-9_\[\]]*)\}/', '', $sourceCode); 125 | $lock->acquire(); 126 | 127 | // PHP code: 128 | $sourceCode = preg_replace('/(.+?)<\/php>/is', '', $sourceCode); 129 | $lock->acquire(); 130 | 131 | // import: 132 | $sourceCode = preg_replace('/\(<\/import>)?/i', 133 | '', $sourceCode); 134 | $lock->acquire(); 135 | 136 | // loop: 137 | $sourceCode = preg_replace_callback('/\/is', array(__CLASS__, 'parseLoop'), $sourceCode); 138 | $sourceCode = preg_replace('/\<\/loop\>/i', '', $sourceCode); 139 | $lock->acquire(); 140 | 141 | // if: 142 | $sourceCode = preg_replace('/\/i', '', $sourceCode); 143 | $sourceCode = preg_replace('/\/i', '', 144 | $sourceCode); 145 | $sourceCode = preg_replace('/\/i', '', $sourceCode); 146 | $sourceCode = preg_replace('/\<\/if\>/i', '', $sourceCode); 147 | $lock->acquire(); 148 | 149 | // header: 150 | preg_match_all('/\/i', $sourceCode, $matches); 151 | foreach ($matches[0] as $offset => $string) { 152 | $headers .= "header('{$matches[1][$offset]}: {$matches[2][$offset]}');" . PHP_EOL; 153 | $sourceCode = str_replace($string, '', $sourceCode); 154 | } 155 | $lock->acquire(); 156 | 157 | // variable without braces 158 | $sourceCode = preg_replace('/\$([a-z][A-Za-z0-9_]+)/', '', $sourceCode); 159 | 160 | // unlock PHP code 161 | $lock->release(); 162 | 163 | // rewrite link 164 | if (!defined('USE_REWRITE') || !USE_REWRITE) { 165 | $sourceCode = preg_replace_callback('/(href|action)="([A-Z0-9_\\.\\-\\/%\\?=&]*?)"/is', 166 | array(__CLASS__, 'parseUrlRewrite'), $sourceCode); 167 | } 168 | 169 | // clear space and tab 170 | $sourceCode = preg_replace('/^[ \t]*(.+)[ \t]*$/m', '\\1', $sourceCode); 171 | 172 | $output = '' . PHP_EOL; 179 | $output .= trim($sourceCode); 180 | $output = preg_replace('/\s*\?\>\s*\<\?php\s*/is', PHP_EOL, $output); 181 | self::createDir(dirname(DATA_PATH . "Template/{$templateName}.php")); 182 | if (!file_exists(DATA_PATH . "Template/{$templateName}.php")) { 183 | @touch(DATA_PATH . "Template/{$templateName}.php"); 184 | } 185 | if (!is_writable(DATA_PATH . "Template/{$templateName}.php")) { 186 | throw new Error('Cannot write template file: ' . DATA_PATH . "Template/{$templateName}.php", 8); 187 | } 188 | file_put_contents(DATA_PATH . "Template/{$templateName}.php", $output); 189 | } 190 | 191 | public static function parseLoop($match) 192 | { 193 | $variable = self::pregGet($match[1], '/variable="([^"]+)"/i'); 194 | if (!$variable) { 195 | $variable = self::pregGet($match[1], '/^\s*"([^"]+)"/i'); 196 | } 197 | if (!$variable) { 198 | throw new Error('Cannot convert loop label: ' . htmlspecialchars($match[0]), 102); 199 | } 200 | $query = self::pregGet($match[1], '/query="([^"]+)"/i'); 201 | if ($query) { 202 | return 'getRow()) { ?>'; 203 | } 204 | $key = self::pregGet($match[1], '/key="([^"]+)"/i'); 205 | $value = self::pregGet($match[1], '/value="([^"]+)"/i'); 206 | return ' ' . ($value ? $value : '$value') . ') { ?>'; 207 | } 208 | 209 | public static function parseUrlRewrite($match) 210 | { 211 | $originText = $match[0]; 212 | $linkTarget = $match[2]; 213 | if (strpos($linkTarget, '//') !== false) { 214 | return $originText; 215 | } 216 | if (file_exists(ROOT_PATH . $linkTarget)) { 217 | return $originText; 218 | } 219 | return str_replace($linkTarget, 'index.php/' . $linkTarget, $originText); 220 | } 221 | 222 | private static function pregGet($subject, $pattern, $offset = 1) 223 | { 224 | if (!preg_match($pattern, $subject, $matches)) { 225 | return null; 226 | } 227 | return $matches[$offset]; 228 | } 229 | 230 | private static function createDir($dir, $permission = 0777) 231 | { 232 | if (is_dir($dir)) { 233 | return; 234 | } 235 | self::createDir(dirname($dir), $permission); 236 | @mkdir($dir, $permission); 237 | } 238 | 239 | private static function parseI18nParams($matchString) 240 | { 241 | $params = array(); 242 | preg_match_all('/(\w+)="(.+?)"/', $matchString, $matches); 243 | foreach ($matches[0] as $key => $value) { 244 | $params[$matches[1][$key]] = $matches[2][$key]; 245 | } 246 | return $params; 247 | } 248 | 249 | private static function parseI18nTags(array $match) 250 | { 251 | $params = self::parseI18nParams($match[2]); 252 | $translation = I18N::parse($params['key'], $match[3]); 253 | if (count($params) > 1) { 254 | foreach ($params as $key => $value) { 255 | $translation = str_replace("{{$key}}", $value, $translation); 256 | } 257 | } 258 | return $translation; 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "fbe0b6da3e694133b19f58743eafce98", 8 | "content-hash": "d990fd9a0076d513584f80a0ac43a76d", 9 | "packages": [ 10 | { 11 | "name": "robmorgan/phinx", 12 | "version": "v0.5.5.1", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/robmorgan/phinx.git", 16 | "reference": "809d403eaee5583efe48b0f24d3ff8f1d893e1a0" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/robmorgan/phinx/zipball/809d403eaee5583efe48b0f24d3ff8f1d893e1a0", 21 | "reference": "809d403eaee5583efe48b0f24d3ff8f1d893e1a0", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.4", 26 | "symfony/config": "~2.8|~3.0", 27 | "symfony/console": "~2.8|~3.0", 28 | "symfony/yaml": "~2.8|~3.0" 29 | }, 30 | "require-dev": { 31 | "phpunit/phpunit": "^3.7|^4.0|^5.0" 32 | }, 33 | "bin": [ 34 | "bin/phinx" 35 | ], 36 | "type": "library", 37 | "autoload": { 38 | "psr-4": { 39 | "Phinx\\": "src/Phinx" 40 | } 41 | }, 42 | "notification-url": "https://packagist.org/downloads/", 43 | "license": [ 44 | "MIT" 45 | ], 46 | "authors": [ 47 | { 48 | "name": "Woody Gilk", 49 | "email": "woody.gilk@gmail.com", 50 | "homepage": "http://shadowhand.me", 51 | "role": "Developer" 52 | }, 53 | { 54 | "name": "Rob Morgan", 55 | "email": "robbym@gmail.com", 56 | "homepage": "https://robmorgan.id.au", 57 | "role": "Lead Developer" 58 | } 59 | ], 60 | "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.", 61 | "homepage": "https://phinx.org", 62 | "keywords": [ 63 | "database", 64 | "database migrations", 65 | "db", 66 | "migrations", 67 | "phinx" 68 | ], 69 | "time": "2016-06-17 15:51:03" 70 | }, 71 | { 72 | "name": "symfony/config", 73 | "version": "v3.1.2", 74 | "source": { 75 | "type": "git", 76 | "url": "https://github.com/symfony/config.git", 77 | "reference": "bcf5aebabc95b56e370e13d78565f74c7d8726dc" 78 | }, 79 | "dist": { 80 | "type": "zip", 81 | "url": "https://api.github.com/repos/symfony/config/zipball/bcf5aebabc95b56e370e13d78565f74c7d8726dc", 82 | "reference": "bcf5aebabc95b56e370e13d78565f74c7d8726dc", 83 | "shasum": "" 84 | }, 85 | "require": { 86 | "php": ">=5.5.9", 87 | "symfony/filesystem": "~2.8|~3.0" 88 | }, 89 | "suggest": { 90 | "symfony/yaml": "To use the yaml reference dumper" 91 | }, 92 | "type": "library", 93 | "extra": { 94 | "branch-alias": { 95 | "dev-master": "3.1-dev" 96 | } 97 | }, 98 | "autoload": { 99 | "psr-4": { 100 | "Symfony\\Component\\Config\\": "" 101 | }, 102 | "exclude-from-classmap": [ 103 | "/Tests/" 104 | ] 105 | }, 106 | "notification-url": "https://packagist.org/downloads/", 107 | "license": [ 108 | "MIT" 109 | ], 110 | "authors": [ 111 | { 112 | "name": "Fabien Potencier", 113 | "email": "fabien@symfony.com" 114 | }, 115 | { 116 | "name": "Symfony Community", 117 | "homepage": "https://symfony.com/contributors" 118 | } 119 | ], 120 | "description": "Symfony Config Component", 121 | "homepage": "https://symfony.com", 122 | "time": "2016-06-29 05:41:56" 123 | }, 124 | { 125 | "name": "symfony/console", 126 | "version": "v3.1.2", 127 | "source": { 128 | "type": "git", 129 | "url": "https://github.com/symfony/console.git", 130 | "reference": "747154aa69b0f83cd02fc9aa554836dee417631a" 131 | }, 132 | "dist": { 133 | "type": "zip", 134 | "url": "https://api.github.com/repos/symfony/console/zipball/747154aa69b0f83cd02fc9aa554836dee417631a", 135 | "reference": "747154aa69b0f83cd02fc9aa554836dee417631a", 136 | "shasum": "" 137 | }, 138 | "require": { 139 | "php": ">=5.5.9", 140 | "symfony/polyfill-mbstring": "~1.0" 141 | }, 142 | "require-dev": { 143 | "psr/log": "~1.0", 144 | "symfony/event-dispatcher": "~2.8|~3.0", 145 | "symfony/process": "~2.8|~3.0" 146 | }, 147 | "suggest": { 148 | "psr/log": "For using the console logger", 149 | "symfony/event-dispatcher": "", 150 | "symfony/process": "" 151 | }, 152 | "type": "library", 153 | "extra": { 154 | "branch-alias": { 155 | "dev-master": "3.1-dev" 156 | } 157 | }, 158 | "autoload": { 159 | "psr-4": { 160 | "Symfony\\Component\\Console\\": "" 161 | }, 162 | "exclude-from-classmap": [ 163 | "/Tests/" 164 | ] 165 | }, 166 | "notification-url": "https://packagist.org/downloads/", 167 | "license": [ 168 | "MIT" 169 | ], 170 | "authors": [ 171 | { 172 | "name": "Fabien Potencier", 173 | "email": "fabien@symfony.com" 174 | }, 175 | { 176 | "name": "Symfony Community", 177 | "homepage": "https://symfony.com/contributors" 178 | } 179 | ], 180 | "description": "Symfony Console Component", 181 | "homepage": "https://symfony.com", 182 | "time": "2016-06-29 07:02:31" 183 | }, 184 | { 185 | "name": "symfony/filesystem", 186 | "version": "v3.1.2", 187 | "source": { 188 | "type": "git", 189 | "url": "https://github.com/symfony/filesystem.git", 190 | "reference": "322da5f0910d8aa0b25fa65ffccaba68dbddb890" 191 | }, 192 | "dist": { 193 | "type": "zip", 194 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/322da5f0910d8aa0b25fa65ffccaba68dbddb890", 195 | "reference": "322da5f0910d8aa0b25fa65ffccaba68dbddb890", 196 | "shasum": "" 197 | }, 198 | "require": { 199 | "php": ">=5.5.9" 200 | }, 201 | "type": "library", 202 | "extra": { 203 | "branch-alias": { 204 | "dev-master": "3.1-dev" 205 | } 206 | }, 207 | "autoload": { 208 | "psr-4": { 209 | "Symfony\\Component\\Filesystem\\": "" 210 | }, 211 | "exclude-from-classmap": [ 212 | "/Tests/" 213 | ] 214 | }, 215 | "notification-url": "https://packagist.org/downloads/", 216 | "license": [ 217 | "MIT" 218 | ], 219 | "authors": [ 220 | { 221 | "name": "Fabien Potencier", 222 | "email": "fabien@symfony.com" 223 | }, 224 | { 225 | "name": "Symfony Community", 226 | "homepage": "https://symfony.com/contributors" 227 | } 228 | ], 229 | "description": "Symfony Filesystem Component", 230 | "homepage": "https://symfony.com", 231 | "time": "2016-06-29 05:41:56" 232 | }, 233 | { 234 | "name": "symfony/polyfill-mbstring", 235 | "version": "v1.2.0", 236 | "source": { 237 | "type": "git", 238 | "url": "https://github.com/symfony/polyfill-mbstring.git", 239 | "reference": "dff51f72b0706335131b00a7f49606168c582594" 240 | }, 241 | "dist": { 242 | "type": "zip", 243 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594", 244 | "reference": "dff51f72b0706335131b00a7f49606168c582594", 245 | "shasum": "" 246 | }, 247 | "require": { 248 | "php": ">=5.3.3" 249 | }, 250 | "suggest": { 251 | "ext-mbstring": "For best performance" 252 | }, 253 | "type": "library", 254 | "extra": { 255 | "branch-alias": { 256 | "dev-master": "1.2-dev" 257 | } 258 | }, 259 | "autoload": { 260 | "psr-4": { 261 | "Symfony\\Polyfill\\Mbstring\\": "" 262 | }, 263 | "files": [ 264 | "bootstrap.php" 265 | ] 266 | }, 267 | "notification-url": "https://packagist.org/downloads/", 268 | "license": [ 269 | "MIT" 270 | ], 271 | "authors": [ 272 | { 273 | "name": "Nicolas Grekas", 274 | "email": "p@tchwork.com" 275 | }, 276 | { 277 | "name": "Symfony Community", 278 | "homepage": "https://symfony.com/contributors" 279 | } 280 | ], 281 | "description": "Symfony polyfill for the Mbstring extension", 282 | "homepage": "https://symfony.com", 283 | "keywords": [ 284 | "compatibility", 285 | "mbstring", 286 | "polyfill", 287 | "portable", 288 | "shim" 289 | ], 290 | "time": "2016-05-18 14:26:46" 291 | }, 292 | { 293 | "name": "symfony/yaml", 294 | "version": "v3.1.2", 295 | "source": { 296 | "type": "git", 297 | "url": "https://github.com/symfony/yaml.git", 298 | "reference": "2884c26ce4c1d61aebf423a8b912950fe7c764de" 299 | }, 300 | "dist": { 301 | "type": "zip", 302 | "url": "https://api.github.com/repos/symfony/yaml/zipball/2884c26ce4c1d61aebf423a8b912950fe7c764de", 303 | "reference": "2884c26ce4c1d61aebf423a8b912950fe7c764de", 304 | "shasum": "" 305 | }, 306 | "require": { 307 | "php": ">=5.5.9" 308 | }, 309 | "type": "library", 310 | "extra": { 311 | "branch-alias": { 312 | "dev-master": "3.1-dev" 313 | } 314 | }, 315 | "autoload": { 316 | "psr-4": { 317 | "Symfony\\Component\\Yaml\\": "" 318 | }, 319 | "exclude-from-classmap": [ 320 | "/Tests/" 321 | ] 322 | }, 323 | "notification-url": "https://packagist.org/downloads/", 324 | "license": [ 325 | "MIT" 326 | ], 327 | "authors": [ 328 | { 329 | "name": "Fabien Potencier", 330 | "email": "fabien@symfony.com" 331 | }, 332 | { 333 | "name": "Symfony Community", 334 | "homepage": "https://symfony.com/contributors" 335 | } 336 | ], 337 | "description": "Symfony Yaml Component", 338 | "homepage": "https://symfony.com", 339 | "time": "2016-06-29 05:41:56" 340 | } 341 | ], 342 | "packages-dev": [], 343 | "aliases": [], 344 | "minimum-stability": "beta", 345 | "stability-flags": [], 346 | "prefer-stable": false, 347 | "prefer-lowest": false, 348 | "platform": [], 349 | "platform-dev": [] 350 | } 351 | --------------------------------------------------------------------------------