├── .gitignore ├── .scrutinizer.yml ├── License ├── composer.json ├── core ├── App.php ├── Config.php ├── Container.php ├── Controller.php ├── Db.php ├── Facade.php ├── Model.php ├── Mysql.php ├── Request.php ├── Route.php ├── Validate.php ├── configs │ └── core.php ├── helpers │ ├── Chaos.php │ ├── Curl.php │ ├── Dispatch.php │ ├── Dom.php │ ├── Page.php │ ├── PinYin.php │ └── TimeAgo.php ├── models │ └── BaseDb.php └── tools │ ├── Arr.php │ └── Str.php ├── docs ├── .nojekyll ├── CNAME ├── README.md ├── _asset │ ├── puck.svg │ └── style.css ├── _coverpage.md ├── _sidebar.md ├── chaos.md ├── config.md ├── controller.md ├── curl.md ├── db.md ├── di.md ├── docsify.min.js ├── dom.md ├── exception.md ├── formind.md ├── index.html ├── model.md ├── pinyin.md ├── preface.md ├── quickstart.md ├── router.md ├── validate.md └── view.md ├── function.php ├── phpunit.xml ├── readme.md └── tests ├── .env ├── app └── models │ ├── Test.php │ └── UserTest.php ├── puck ├── AppTest.php ├── ContainerTest.php ├── ControllerTest.php ├── FacadeTest.php ├── ModelTest.php ├── helpers │ ├── CurlTest.php │ ├── DomTest.php │ └── PinYinTest.php └── tools │ └── StrTest.php └── test.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.iml 3 | .idea 4 | .vscode 5 | composer.lock 6 | vendor 7 | cache -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: 3 | - tests/* 4 | 5 | checks: 6 | php: 7 | code_rating: true 8 | 9 | tools: 10 | external_code_coverage: false 11 | php_analyzer: true 12 | php_changetracking: true 13 | php_code_sniffer: 14 | config: 15 | standard: "PSR2" 16 | php_cpd: true 17 | php_mess_detector: true 18 | php_pdepend: true 19 | sensiolabs_security_checker: true 20 | build: 21 | environment: 22 | php: 23 | version: 7.1 24 | tests: 25 | before: 26 | - 'composer install' 27 | override: 28 | - 29 | command: 'vendor/bin/phpunit --coverage-clover=test_result' 30 | coverage: 31 | file: 'test_result' 32 | format: 'clover' -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rozbo/puck", 3 | "description": "The other one magical php framework", 4 | "type": "puck-framework", 5 | "keywords": [ 6 | "framework", 7 | "puck", 8 | "php" 9 | ], 10 | "homepage": "https://puck.zz713.com/", 11 | "license": "Apache-2.0", 12 | "authors": [ 13 | { 14 | "name": "rozbo", 15 | "email": "rozbo@live.com" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=7.0.0", 20 | "filp/whoops": "~2.1.6", 21 | "twig/twig":"~1.0", 22 | "overtrue/pinyin": "~3.0", 23 | "vlucas/phpdotenv": "^2.4", 24 | "imangazaliev/didom": "^1.9.1" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "6.*" 28 | }, 29 | "archive": { 30 | "exclude": ["docs","tests"] 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "puck\\": "core" 35 | }, 36 | "files": [ 37 | "function.php" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/App.php: -------------------------------------------------------------------------------- 1 | basePath=$basePath; 28 | $this->initEnv(); 29 | $this->initContainer(); 30 | $this->initConfig(); 31 | } 32 | 33 | private function initEnv(){ 34 | try{ 35 | $dotEnv = new Dotenv($this->basePath); 36 | $dotEnv->load(); 37 | } 38 | catch (\Dotenv\Exception\InvalidPathException $e){ 39 | 40 | } 41 | date_default_timezone_set(env('APP_TIMEZONE', 'Asia/Shanghai')); 42 | define('IS_CLI', $this->runningInConsole()); 43 | define('IS_DEBUG',env('DEBUG',false)); 44 | if (IS_DEBUG) { 45 | error_reporting(E_ALL); 46 | @ini_set('display_errors', 'On'); 47 | //@ob_start(); 48 | $whoops=new Run; 49 | $handle=IS_CLI ? "PlainTextHandler" : "PrettyPageHandler"; 50 | $handle="\\Whoops\\Handler\\".$handle; 51 | $whoops->pushHandler(new $handle); 52 | $whoops->register(); 53 | } 54 | 55 | } 56 | 57 | /** 58 | * 判断是否是cli模式 59 | * 60 | * @return bool 61 | */ 62 | public function runningInConsole() { 63 | return php_sapi_name() == 'cli'; 64 | } 65 | 66 | /** 67 | * 初始化容器 68 | */ 69 | private function initContainer() { 70 | static::setInstance($this); 71 | $this->instance('app',$this); 72 | $this->instance('config',new Config()); 73 | $this->instance('request',new Request($this->config)); 74 | $this->instance('route',new Route($this->request)); 75 | $this->regexBind('#^(\w+)_model$#', "\\app\\models\\\\$1"); 76 | $this->bind('pinyin','\puck\helpers\PinYin'); 77 | $this->bind('curl','\puck\helpers\Curl'); 78 | $this->bind('dom', '\puck\helpers\Dom'); 79 | $this->bind('db', '\puck\Db'); 80 | } 81 | 82 | private function initConfig() { 83 | $this->configure('core'); 84 | } 85 | 86 | /** 87 | * 加载一个配置文件 88 | * 89 | * @param string $name 90 | * @return void 91 | */ 92 | public function configure($name) 93 | { 94 | if (isset($this->loadedConfigurations[$name])) { 95 | return; 96 | } 97 | //标记为已加载 98 | $this->loadedConfigurations[$name] = true; 99 | $path = $this->getConfigurationPath($name); 100 | if ($path) { 101 | $this->make('config')->set($name, require $path); 102 | } 103 | } 104 | 105 | /** 106 | * 获取配置文件的路径。 107 | * 108 | * 如果没有给定配置文件的名字,则返回目录。 109 | * 110 | * 如果应用目录下有相应配置文件则优先返回。 111 | * 112 | * @param string|null $name 113 | * @return string 114 | */ 115 | public function getConfigurationPath($name = null) 116 | { 117 | if (! $name) { 118 | $appConfigDir = $this->basePath('configs').'/'; 119 | 120 | if (file_exists($appConfigDir)) { 121 | return $appConfigDir; 122 | } elseif (file_exists($path = __DIR__.'/configs/')) { 123 | return $path; 124 | } 125 | } else { 126 | $appConfigPath = $this->basePath('configs').'/'.$name.'.php'; 127 | if (file_exists($appConfigPath)) { 128 | return $appConfigPath; 129 | } elseif (file_exists($path = __DIR__.'/configs/'.$name.'.php')) { 130 | return $path; 131 | } 132 | } 133 | } 134 | 135 | /** 136 | * Get the base path for the application. 137 | * 138 | * @param string|null $path 139 | * @return string 140 | */ 141 | public function basePath($path = null) { 142 | if (isset($this->basePath)) { 143 | return $this->basePath . ($path ? '/' . $path : $path); 144 | } 145 | 146 | if ($this->runningInConsole()) { 147 | $this->basePath = getcwd(); 148 | } else { 149 | $this->basePath = realpath(getcwd() . '/../'); 150 | } 151 | 152 | return $this->basePath($path); 153 | } 154 | 155 | public function run() { 156 | $this->route->dispatch(); 157 | } 158 | } -------------------------------------------------------------------------------- /core/Config.php: -------------------------------------------------------------------------------- 1 | items = $items; 27 | } 28 | 29 | /** 30 | * Determine if the given configuration value exists. 31 | * 32 | * @param string $key 33 | * @return bool 34 | */ 35 | public function has($key) 36 | { 37 | return Arr::has($this->items, $key); 38 | } 39 | 40 | /** 41 | * Get the specified configuration value. 42 | * 43 | * @param string $key 44 | * @param mixed $default 45 | * @return mixed 46 | */ 47 | public function get($key, $default = null) 48 | { 49 | return Arr::get($this->items, $key, $default); 50 | } 51 | 52 | /** 53 | * Set a given configuration value. 54 | * 55 | * @param array|string $key 56 | * @param mixed $value 57 | * @return void 58 | */ 59 | public function set($key, $value = null) 60 | { 61 | $keys = is_array($key) ? $key : [$key => $value]; 62 | 63 | foreach ($keys as $key => $value) { 64 | Arr::set($this->items, $key, $value); 65 | } 66 | } 67 | 68 | /** 69 | * Prepend a value onto an array configuration value. 70 | * 71 | * @param string $key 72 | * @param mixed $value 73 | * @return void 74 | */ 75 | public function prepend($key, $value) 76 | { 77 | $array = $this->get($key); 78 | 79 | array_unshift($array, $value); 80 | 81 | $this->set($key, $array); 82 | } 83 | 84 | /** 85 | * Push a value onto an array configuration value. 86 | * 87 | * @param string $key 88 | * @param mixed $value 89 | * @return void 90 | */ 91 | public function push($key, $value) 92 | { 93 | $array = $this->get($key); 94 | 95 | $array[] = $value; 96 | 97 | $this->set($key, $array); 98 | } 99 | 100 | /** 101 | * Get all of the configuration items for the application. 102 | * 103 | * @return array 104 | */ 105 | public function all() 106 | { 107 | return $this->items; 108 | } 109 | 110 | /** 111 | * Determine if the given configuration option exists. 112 | * 113 | * @param string $key 114 | * @return bool 115 | */ 116 | public function offsetExists($key) 117 | { 118 | return $this->has($key); 119 | } 120 | 121 | /** 122 | * Get a configuration option. 123 | * 124 | * @param string $key 125 | * @return mixed 126 | */ 127 | public function offsetGet($key) 128 | { 129 | return $this->get($key); 130 | } 131 | 132 | /** 133 | * Set a configuration option. 134 | * 135 | * @param string $key 136 | * @param mixed $value 137 | * @return void 138 | */ 139 | public function offsetSet($key, $value) 140 | { 141 | $this->set($key, $value); 142 | } 143 | 144 | /** 145 | * Unset a configuration option. 146 | * 147 | * @param string $key 148 | * @return void 149 | */ 150 | public function offsetUnset($key) 151 | { 152 | $this->set($key, null); 153 | } 154 | } -------------------------------------------------------------------------------- /core/Container.php: -------------------------------------------------------------------------------- 1 | regexBind[$regex] = $class; 51 | } 52 | 53 | /** 54 | * 绑定一个类实例当容器 55 | * @access public 56 | * @param string $abstract 类名或者标识 57 | * @param object $instance 类的实例 58 | * @return void 59 | */ 60 | public function instance($abstract, $instance) 61 | { 62 | $this->instances[$abstract]=$instance; 63 | } 64 | 65 | /** 66 | * 调用反射执行callable 支持参数绑定 67 | * @access public 68 | * @param mixed $callable 69 | * @param array $vars 变量 70 | * @return mixed 71 | */ 72 | public function invoke($callable, $vars=[]) 73 | { 74 | if ($callable instanceof \Closure) { 75 | $result=$this->invokeFunction($callable, $vars); 76 | } else { 77 | $result=$this->invokeMethod($callable, $vars); 78 | } 79 | return $result; 80 | } 81 | 82 | /** 83 | * 执行函数或者闭包方法 支持参数调用 84 | * @access public 85 | * @param \Closure $function 函数或者闭包 86 | * @param array $vars 变量 87 | * @return mixed 88 | */ 89 | public function invokeFunction($function, $vars = []) 90 | { 91 | $reflect = new \ReflectionFunction($function); 92 | $args = $this->bindParams($reflect, $vars); 93 | return $reflect->invokeArgs($args); 94 | } 95 | 96 | /** 97 | * 绑定参数 98 | * @access protected 99 | * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 100 | * @param array $vars 变量 101 | * @return array 102 | */ 103 | protected function bindParams($reflect, $vars=[]) 104 | { 105 | $args=[]; 106 | if ($reflect->getNumberOfParameters() > 0) { 107 | // 判断数组类型 数字数组时按顺序绑定参数 108 | reset($vars); 109 | $type=key($vars) === 0 ? 1 : 0; 110 | $params=$reflect->getParameters(); 111 | foreach ($params as $param) { 112 | $name=$param->getName(); 113 | $class=$param->getClass(); 114 | if ($class) { 115 | $className=$class->getName(); 116 | $args[]=$this->make($className); 117 | } elseif (1 == $type && !empty($vars)) { 118 | $args[]=array_shift($vars); 119 | } elseif (0 == $type && isset($vars[$name])) { 120 | $args[]=$vars[$name]; 121 | } elseif ($param->isDefaultValueAvailable()) { 122 | $args[]=$param->getDefaultValue(); 123 | } else { 124 | throw new \InvalidArgumentException('method param miss:'.$name); 125 | } 126 | } 127 | } 128 | return $args; 129 | } 130 | 131 | /** 132 | * 创建类的实例 133 | * @access public 134 | * @param array $vars 变量 135 | * @return object 136 | */ 137 | public function make($abstract, $vars = [], $newInstance = false) { 138 | if (isset($this->instances[$abstract])) { 139 | $object = $this->instances[$abstract]; 140 | } else { 141 | if (isset($this->bind[$abstract])) { 142 | $concrete = $this->bind[$abstract]; 143 | $object = $this->makeObject($concrete, $vars); 144 | } else { 145 | //正则绑定 146 | $result = preg_filter(array_keys($this->regexBind), array_values($this->regexBind), $abstract); 147 | if ($result) { 148 | $concrete = is_array($result) ? end($result) : $result; 149 | $object = $this->makeObject($concrete, $vars); 150 | } else { 151 | $object = $this->invokeClass($abstract, $vars); 152 | } 153 | } 154 | 155 | if (!$newInstance) { 156 | $this->instances[$abstract] = $object; 157 | } 158 | } 159 | return $object; 160 | } 161 | 162 | private function makeObject($concrete, $vars = []) { 163 | if ($concrete instanceof \Closure) { 164 | $object = $this->invokeFunction($concrete, $vars); 165 | } else { 166 | $object = $this->make($concrete, $vars); 167 | } 168 | return $object; 169 | } 170 | 171 | /** 172 | * 调用反射执行类的实例化 支持依赖注入 173 | * @access public 174 | * @param string $class 类名 175 | * @param array $vars 变量 176 | * @return mixed 177 | */ 178 | public function invokeClass($class, $vars = []) { 179 | try { 180 | $reflect = new \ReflectionClass($class); 181 | } catch (\ReflectionException $e) { 182 | $classArray = explode("\\", $class); 183 | $classSelf = array_pop($classArray); 184 | $classSelf = Str::studly($classSelf); 185 | array_push($classArray, $classSelf); 186 | $class = implode("\\", $classArray); 187 | $reflect = new \ReflectionClass($class); 188 | } 189 | 190 | $constructor = $reflect->getConstructor(); 191 | if ($constructor) { 192 | $args = $this->bindParams($constructor, $vars); 193 | } else { 194 | $args = []; 195 | } 196 | return $reflect->newInstanceArgs($args); 197 | } 198 | 199 | /** 200 | * 调用反射执行类的方法 支持参数绑定 201 | * @access public 202 | * @param string|array $method 方法 203 | * @param array $vars 变量 204 | * @return mixed 205 | */ 206 | public function invokeMethod($method, $vars = []) { 207 | if (is_array($method)) { 208 | $class = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]); 209 | $reflect = new \ReflectionMethod($class, $method[1]); 210 | } else { 211 | // 静态方法 212 | $reflect = new \ReflectionMethod($method); 213 | } 214 | $args = $this->bindParams($reflect, $vars); 215 | return $reflect->invokeArgs(isset($class) ? $class : null, $args); 216 | } 217 | 218 | public function offsetExists($key) 219 | { 220 | return $this->bound($key); 221 | } 222 | 223 | /** 224 | * 判断容器中是否存在类及标识 225 | * @access public 226 | * @param string $abstract 类名或者标识 227 | * @return bool 228 | */ 229 | public function bound($abstract) { 230 | return isset($this->bind[$abstract]) || isset($this->instances[$abstract]); 231 | } 232 | 233 | public function offsetGet($key) 234 | { 235 | return $this->make($key); 236 | } 237 | 238 | public function offsetSet($key, $value) 239 | { 240 | $this->bind($key, $value); 241 | } 242 | 243 | /** 244 | * 绑定一个类到容器 245 | * @access public 246 | * @param string $abstract 类标识、接口 247 | * @param string|\Closure $concrete 要绑定的类或者闭包 248 | * @return void 249 | */ 250 | public function bind($abstract, $concrete = null) { 251 | if (is_array($abstract)) { 252 | $this->bind = array_merge($this->bind, $abstract); 253 | } else { 254 | $this->bind[$abstract] = $concrete; 255 | } 256 | } 257 | 258 | public function offsetUnset($key) 259 | { 260 | $this->__unset($key); 261 | } 262 | 263 | public function __unset($name) 264 | { 265 | unset($this->bind[$name], $this->instances[$name]); 266 | } 267 | 268 | public function __get($name) 269 | { 270 | return $this->make($name); 271 | } 272 | 273 | public function __set($name, $value) { 274 | $this->bind($name, $value); 275 | } 276 | 277 | public function __isset($name) 278 | { 279 | return $this->bound($name); 280 | } 281 | 282 | } -------------------------------------------------------------------------------- /core/Controller.php: -------------------------------------------------------------------------------- 1 | viewPath); 15 | $twig=new \Twig_Environment($loader, array( 16 | 'debug' => DEBUG, 17 | 'cache' => BASE_PATH.'/cache', 18 | )); 19 | $this->twig=$twig; 20 | $this->initTwigFilter(); 21 | $this->initTwigFunction(); 22 | $this->db=\MysqliDb::getInstance(); 23 | } 24 | private function initTwigFilter() { 25 | $filter=new \Twig_SimpleFilter('long2ip', 'long2ip'); 26 | $this->twig->addFilter($filter); 27 | } 28 | private function initTwigFunction() { 29 | $function=new \Twig_SimpleFunction('I', 'I'); 30 | $this->twig->addFunction($function); 31 | } 32 | 33 | protected function show($tmpPath='') 34 | { 35 | if ($tmpPath == '') { 36 | if (defined("CONTROLLER_NAME") && defined("ACTION_NAME")) { 37 | $tmpPath=parse_name(CONTROLLER_NAME).'/'.parse_name(ACTION_NAME); 38 | } else { 39 | show_json($this->tVar); 40 | } 41 | } 42 | header('Content-Type:text/html; charset=utf-8'); 43 | header('Cache-control: private'); // 页面缓存控制 44 | header('X-Powered-By:ViviAnAuthSystem'); 45 | $this->assign('title', $this->title); 46 | echo $this->twig->render($tmpPath.'.'.TempExt, $this->tVar); 47 | die(); 48 | } 49 | 50 | /** 51 | * @param string $name 52 | */ 53 | protected function assign($name, $value = '') { 54 | if (is_array($name)) { 55 | $this->tVar = array_merge($this->tVar, $name); 56 | } else { 57 | $this->tVar[$name] = $value; 58 | } 59 | } 60 | 61 | protected function model($modelName, $vars = []) { 62 | return app($modelName . '_model', $vars); 63 | } 64 | } -------------------------------------------------------------------------------- /core/Db.php: -------------------------------------------------------------------------------- 1 | get($conn); 23 | } elseif (is_array($conn)) { 24 | $dbConf = $conn; 25 | } else { 26 | throw new \InvalidArgumentException("数据库配置必须为字符串或者数组"); 27 | } 28 | if (is_null($dbConf)) { 29 | throw new \InvalidArgumentException("数据库配置异常!"); 30 | } 31 | $confMd5 = md5(serialize($dbConf)); 32 | if (!isset($this->connPool[$confMd5])) { 33 | $obj = new Mysql($dbConf); 34 | $this->connPool[$confMd5] = $obj; 35 | } 36 | return $this->connPool[$confMd5]; 37 | } 38 | 39 | 40 | public function __call($method, $arg) { 41 | $ret = $this; 42 | if (method_exists($this->db, $method)) { 43 | $ret = call_user_func_array(array($this->db, $method), $arg); 44 | } 45 | return $ret == $this->db ? $this : $ret; 46 | } 47 | 48 | public function __get($name) { 49 | if (property_exists($this->db, $name)) { 50 | return $this->db->$name; 51 | } 52 | throw new MemberAccessException('model Property ' . $name . ' not exists'); 53 | } 54 | } -------------------------------------------------------------------------------- /core/Facade.php: -------------------------------------------------------------------------------- 1 | make($class, $args); 57 | } 58 | 59 | protected static function getFacadeClass() 60 | {} 61 | 62 | /** 63 | * 指定某个Facade类进行实例化 64 | * @access public 65 | * @param string $class 类名或者标识 66 | * @param array $args 变量 67 | * @return object 68 | */ 69 | public static function make($class, $args=[]) 70 | { 71 | return self::createFacade($class, $args); 72 | } 73 | 74 | // 调用实际类的方法 75 | public static function __callStatic($method, $params) 76 | { 77 | return call_user_func_array([static::createFacade(), $method], $params); 78 | } 79 | } -------------------------------------------------------------------------------- /core/Route.php: -------------------------------------------------------------------------------- 1 | request = $request; 13 | $this->errorCallback=function (){ 14 | die("404"); 15 | }; 16 | } 17 | 18 | public static $halts = false; 19 | private $routes = array(); 20 | private $regexRoutes = []; 21 | public static $methods = array(); 22 | public static $callbacks = array(); 23 | private $patterns = array( 24 | ':any' => '[^/]+', 25 | ':num' => '[0-9]+', 26 | ':all' => '.*' 27 | ); 28 | public $errorCallback; 29 | 30 | /** 31 | * Defines a route w/ callback and method 32 | */ 33 | public function __call($method, $params) { 34 | $this->addRoute($method, $params[0], $params[1]); 35 | } 36 | 37 | /** 38 | * 添加一个路由 39 | * @param string $method 40 | * @param string $uri 41 | * @param mixed $callBack 42 | */ 43 | public function addRoute($method, $uri, $callBack) { 44 | $method = strtoupper($method); 45 | //预定义正则路由 46 | if (strpos($uri, ':') !== false) { 47 | $searches = array_keys($this->patterns); 48 | $replaces = array_values($this->patterns); 49 | $uri = str_replace($searches, $replaces, $uri); 50 | $this->regexRoutes[] = [ 51 | 'method' => $method, 52 | 'regex' => '#^' . $uri . '$#', 53 | 'callback' => $callBack 54 | ]; 55 | } //自定义正则路由 56 | elseif ($uri[0] == '#' 57 | || (strlen($uri) > 2 && tools\Str::endsWith($uri, '/') && tools\Str::startsWith($uri, '/')) 58 | ) { 59 | $this->regexRoutes[] = [ 60 | 'method' => $method, 61 | 'regex' => $uri, 62 | 'callback' => $callBack 63 | ]; 64 | } //直接定义的路由 65 | else { 66 | $this->routes[$method . $uri] = [ 67 | 'method' => $method, 68 | 'uri' => $uri, 69 | 'callback' => $callBack 70 | ]; 71 | } 72 | } 73 | 74 | /** 75 | * Defines callback if route is not found 76 | */ 77 | public function error($callback) { 78 | $this->errorCallback = $callback; 79 | } 80 | 81 | public static function haltOnMatch($flag = true) { 82 | self::$halts = $flag; 83 | } 84 | 85 | 86 | private function foundRoute($route, $param = []) { 87 | try { 88 | if ($route['callback'] instanceof \Closure) { 89 | app()->invokeFunction($route['callback'],$param); 90 | } else { 91 | // Grab all parts based on a / separator 92 | $parts = explode('/', $route['callback']); 93 | // Collect the last index of the array 94 | $last = end($parts); 95 | // Grab the controller name and method call 96 | $segments = explode('@', $last); 97 | app()->invokeMethod($segments, $param); 98 | } 99 | } catch (\ReflectionException $e) { 100 | return false; 101 | } 102 | catch (\InvalidArgumentException $e) { 103 | return false; 104 | } 105 | return true; 106 | } 107 | 108 | public function foundRegexRoute($current) { 109 | foreach ($this->regexRoutes as $regexRoute) { 110 | if (preg_match($regexRoute['regex'], $current['uri'], $matched)) { 111 | if ($regexRoute['method'] == $current['method'] 112 | || $regexRoute['method'] == 'ANY' 113 | ) { 114 | //将第一个成员,即是全部字符串剔除 115 | array_shift($matched); 116 | return $this->foundRoute($regexRoute, $matched); 117 | } 118 | } 119 | } 120 | return false; 121 | } 122 | 123 | /** 124 | * Runs the callback for the given request 125 | */ 126 | public function dispatch() { 127 | $uri=$this->request->path(); 128 | $current['uri'] = $uri?$uri:'/'; 129 | $current['method'] = $this->request->method(); 130 | # 第一种情况,直接命中 131 | if (isset($this->routes[$current['method'] . $current['uri']])) { 132 | $this->foundRoute($this->routes[$current['method'] . $current['uri']]); 133 | } # 第二种情况,any命中 134 | else if (isset($this->routes['ANY' . $current['uri']])) { 135 | $this->foundRoute($this->routes['ANY' . $current['method']]); 136 | } # 第三种情况,正则命中 137 | else { 138 | if ($this->foundRegexRoute($current)) { 139 | 140 | } else { 141 | $route['callback'] = $this->errorCallback; 142 | $this->foundRoute($route); 143 | } 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /core/configs/core.php: -------------------------------------------------------------------------------- 1 | '1.3.0', 7 | 'url_html_suffix'=>'html|aspx|h5|do' 8 | ]; -------------------------------------------------------------------------------- /core/helpers/Chaos.php: -------------------------------------------------------------------------------- 1 | _pinyin=new PinYin(); 14 | } 15 | 16 | /** 17 | * @param array $prefix 18 | */ 19 | public function addPrefix(array $prefix) { 20 | $this->prefix=$prefix[array_rand($prefix)]; 21 | } 22 | 23 | public function clearPrefix() { 24 | $this->prefix=''; 25 | } 26 | public function get($str) { 27 | $this->cutting($str); 28 | $this->convert(); 29 | $this->clearPrefix(); 30 | return $this->msg; 31 | } 32 | 33 | //分离URL 34 | private function cutting($str) { 35 | 36 | if (preg_match("@(http://[^\\s]+)@i", $str, $result)) { 37 | $this->url=$result[1]; 38 | $this->msg=$str=str_replace($this->url, '%%%%', $str); 39 | } else { 40 | $this->msg=$str; 41 | } 42 | } 43 | 44 | 45 | 46 | private function convert($method='msg') { 47 | if ($method == 'msg' || $method == 'all') { 48 | $this->msg=$this->setPinyin($this->msg); 49 | $this->msg=$this->setRepeat($this->msg); 50 | //$this->msg = $this->GbToBig($this->msg); 51 | $this->msg=$this->setBlankness($this->msg); 52 | 53 | } 54 | if ($method == 'url' || $method == 'all') { 55 | $this->url=$this->setChacha($this->url); 56 | } 57 | $this->msg=$this->prefix.str_replace('%%%%', $this->url, $this->msg); 58 | } 59 | 60 | /** 61 | * @param string $url 62 | */ 63 | function setChacha($url) { 64 | $url=strtolower($url); 65 | $arr=array( 66 | 'a' => array('a', 'A', 'a', 'A', 'Α', 'А', 'α'), 67 | 'b' => array('b', 'B', 'b', 'B', 'Β', 'В', 'Ь'), 68 | 'c' => array('c', 'C', 'c', 'C', 'С', 'с'), 69 | 'd' => array('d', 'D', 'd', 'D'), 70 | 'e' => array('e', 'E', 'e', 'E', 'Ε', 'Е', 'е'), 71 | 'f' => array('f', 'F', 'f', 'F'), 72 | 'g' => array('g', 'G', 'g', 'G'), 73 | 'h' => array('h', 'H', 'h', 'H', 'Η', 'Н', 'н'), 74 | 'i' => array('i', 'I', 'i', 'I', 'Ι', 'Ⅰ'), 75 | 'j' => array('j', 'J', 'j', 'J'), 76 | 'k' => array('k', 'K', 'k', 'K', 'Κ', 'κ', 'к', 'К'), 77 | 'l' => array('l', 'L', 'l', 'L', '︱', '︳', '|'), 78 | 'm' => array('m', 'M', 'm', 'M', 'Μ', 'М', 'м'), 79 | 'n' => array('n', 'N', 'n', 'N', 'Ν', '∩'), 80 | 'o' => array('o', 'O', 'o', 'O', 'Ο', 'О'), 81 | 'p' => array('p', 'P', 'p', 'P', 'Ρ', 'Р', 'р'), 82 | 'q' => array('q', 'Q', 'q', 'Q'), 83 | 'r' => array('r', 'R', 'r', 'R'), 84 | 's' => array('s', 'S', 's', 'S'), 85 | 't' => array('t', 'T', 't', 'T', 'Τ', 'Т', 'ㄒ'), 86 | 'u' => array('u', 'U', 'u', 'U', '∪'), 87 | 'v' => array('v', 'V', 'v', 'V', '∨', 'ν'), 88 | 'w' => array('w', 'W', 'w', 'W'), 89 | 'x' => array('x', 'X', 'x', 'X', 'Χ', 'χ', 'Х', 'х', 'Ⅹ', '×'), 90 | 'y' => array('y', 'Y', 'y', 'Y', 'У'), 91 | 'z' => array('z', 'Z', 'z', 'Z', 'Ζ'), 92 | 93 | '1' => array('1', '1'), 94 | '2' => array('2', '2'), 95 | '3' => array('3', '3'), 96 | '4' => array('4', '4'), 97 | '5' => array('5', '5'), 98 | '6' => array('6', '6'), 99 | '7' => array('7', '7'), 100 | '8' => array('8', '8'), 101 | '9' => array('9', '9'), 102 | '0' => array('0', '0'), 103 | 104 | ':' => array(':', ':', '∶'), 105 | '/' => array('/', '/'), 106 | '.' => array('。', '·', '.', '、', '﹒', ',', '丶') 107 | 108 | ); 109 | $len=strlen($url); 110 | $temp="\n\n"; 111 | for ($i=0; $i < $len; $i++) { 112 | $t_str=substr($url, $i, 1); 113 | $sj=mt_rand(0, count($arr[$t_str]) - 1); 114 | $temp.=$arr[$t_str][$sj]; 115 | } 116 | return $temp; 117 | } 118 | 119 | //随机把一个字符转为拼音 120 | 121 | /** 122 | * @param string $str 123 | */ 124 | function setPinyin($str) { 125 | $py = mt_rand(0, iconv_strlen( $str, 'UTF-8' )-1); 126 | $t_str = iconv_substr( $str, $py, 1, 'UTF-8'); 127 | if(mt_rand(0,10) > 5) { 128 | $pinyin = " "; 129 | } 130 | $pinyin = $this->_pinyin->convert($t_str,PINYIN_UNICODE); 131 | $pinyin=implode(" ",$pinyin); 132 | if(mt_rand(0,10) > 5) { 133 | $pinyin .= " "; 134 | } 135 | if($t_str != "%"){ 136 | $str = preg_replace("'$t_str'", $pinyin, $str, 1); 137 | } 138 | return $str; 139 | } 140 | 141 | //随机重复一个字符 142 | 143 | /** 144 | * @param string $str 145 | */ 146 | function setRepeat($str) { 147 | $len = iconv_strlen( $str, 'UTF-8' ); 148 | $action = 0; 149 | $temp = ''; 150 | for( $i=0; $i<$len; $i++ ){ 151 | $t_str = iconv_substr( $str, $i, 1 ,'UTF-8'); 152 | if( mt_rand( 1, 50 ) > 48 && $action == 0) { 153 | if(!preg_match("@[a-z0-9%\\s]+@i", $t_str)) { 154 | $temp .= $t_str; 155 | $action = 1; 156 | } 157 | } 158 | $temp .= $t_str; 159 | } 160 | return $temp; 161 | } 162 | 163 | //随机插入不影响阅读的字符 164 | 165 | /** 166 | * @param string $str 167 | */ 168 | function setBlankness($str) { 169 | $blankness=array(" ", ' ', '҉', '̅̅', '̲', '̲̲', '̅', '̲̲̅̅'); 170 | $len=iconv_strlen($str, 'UTF-8'); 171 | $temp=''; 172 | for ($i=0; $i < $len; $i++) { 173 | $t_str=iconv_substr($str, $i, 1, 'UTF-8'); 174 | if (mt_rand(1, 50) > 48) { 175 | if (!preg_match("@[a-z0-9%\\s]+@i", $t_str)) { 176 | $temp.=$blankness[mt_rand(0, 7)]; 177 | } 178 | } 179 | $temp.=$t_str; 180 | } 181 | return $temp; 182 | } 183 | 184 | //随机进行繁体中文转换 185 | function GbToBig($str) { 186 | $len=iconv_strlen($str, 'UTF-8'); 187 | $temp=''; 188 | for ($i=0; $i < $len; $i++) { 189 | $t_str=iconv_substr($str, $i, 1, 'UTF-8'); 190 | if (mt_rand(1, 50) > 48) { 191 | $t_str=strtr($t_str, $this->GbToBigArray); 192 | } 193 | $temp.=$t_str; 194 | } 195 | return $temp; 196 | } 197 | } 198 | 199 | 200 | -------------------------------------------------------------------------------- /core/helpers/Dispatch.php: -------------------------------------------------------------------------------- 1 | isPublic() && !$method->isStatic()) { 78 | $refClass=new \ReflectionClass($class); 79 | //前置方法 80 | if ($refClass->hasMethod('_before_'.$function)) { 81 | $before=$refClass->getMethod('_before_'.$function); 82 | if ($before->isPublic()) { 83 | $before->invoke($class); 84 | } 85 | } 86 | //方法本身 87 | $response=$method->invoke($class); 88 | //后置方法 89 | if ($refClass->hasMethod('_after_'.$function)) { 90 | $after=$refClass->getMethod('_after_'.$function); 91 | if ($after->isPublic()) { 92 | $after->invoke($class); 93 | } 94 | } 95 | self::render($response); 96 | } 97 | } 98 | static public function param() { 99 | if (!IS_CLI) { 100 | $vars=array(); 101 | parse_str(parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY), $vars); 102 | $_GET=$vars; 103 | } 104 | 105 | } 106 | static public function render($res) { 107 | $response=$res; 108 | $renderType=self::$ext; 109 | 110 | if($renderType=='json'){ 111 | $response=json($res); 112 | }elseif ($renderType=='xml'){ 113 | //todo:: 支持xml格式化输出 114 | $response='don\'t support xml now!'; 115 | }elseif ($renderType==""){ 116 | if(is_array($res)||is_object($res)){ 117 | $response=json($res); 118 | } 119 | } 120 | 121 | echo $response; 122 | } 123 | } -------------------------------------------------------------------------------- /core/helpers/Dom.php: -------------------------------------------------------------------------------- 1 | init(); 13 | } 14 | 15 | public function init($encoding = 'UTF-8') { 16 | $this->document = new \DOMDocument('1.0', $encoding); 17 | $this->preserveWhiteSpace(false); 18 | return $this; 19 | } 20 | 21 | public function release() { 22 | unset($this->document); 23 | return $this; 24 | } 25 | } -------------------------------------------------------------------------------- /core/helpers/Page.php: -------------------------------------------------------------------------------- 1 | _instance=$instance; 54 | $this->_perPage=$perPage; 55 | $this->set_instance(); 56 | } 57 | 58 | /** 59 | * get_start 60 | * 61 | * creates the starting point for limiting the dataset 62 | * @return numeric 63 | */ 64 | public function get_start() { 65 | return ($this->_page * $this->_perPage) - $this->_perPage; 66 | } 67 | 68 | /** 69 | * set_instance 70 | * 71 | * sets the instance parameter, if numeric value is 0 then set to 1 72 | * 73 | * @var numeric 74 | */ 75 | private function set_instance() { 76 | $this->_page=(int) (!isset($_GET[$this->_instance]) ? 1 : $_GET[$this->_instance]); 77 | $this->_page=($this->_page == 0 ? 1 : $this->_page); 78 | } 79 | 80 | /** 81 | * set_total 82 | * 83 | * collect a numberic value and assigns it to the totalRows 84 | * 85 | * @var numeric 86 | */ 87 | public function set_total($_totalRows) { 88 | $this->_totalRows=$_totalRows; 89 | } 90 | 91 | /** 92 | * get_limit 93 | * 94 | * returns the limit for the data source, calling the get_start method and passing in the number of items perp page 95 | * 96 | * @return string 97 | */ 98 | public function get_limit() { 99 | return "LIMIT ".$this->get_start().",$this->_perPage"; 100 | } 101 | 102 | 103 | private function getParam() { 104 | $vars=$_GET; 105 | if (isset($vars[$this->_instance])) { 106 | unset($vars[$this->_instance]); 107 | } 108 | $str=http_build_query($vars); 109 | $str='&'.$str; 110 | return $str; 111 | } 112 | 113 | /** 114 | * page_links 115 | * 116 | * create the html links for navigating through the dataset 117 | * 118 | * @var sting $path optionally set the path for the link 119 | * @var sting $ext optionally pass in extra parameters to the GET 120 | * @return string returns the html menu 121 | */ 122 | public function page_links($path='?', $ext=null) 123 | { 124 | $adjacents="2"; 125 | $prev=$this->_page - 1; 126 | $next=$this->_page + 1; 127 | $lastpage=ceil($this->_totalRows / $this->_perPage); 128 | $lpm1=$lastpage - 1; 129 | $pagination=""; 130 | if ($lastpage > 1) 131 | { 132 | if ($ext == null) { 133 | $ext=$this->getParam(); 134 | } 135 | $pagination.="\n"; 205 | } 206 | 207 | 208 | return $pagination; 209 | } 210 | } -------------------------------------------------------------------------------- /core/helpers/PinYin.php: -------------------------------------------------------------------------------- 1 | convert($str); 16 | foreach ($pinyinArr as &$pinyin) { 17 | //首字母转大写 18 | $pinyin[0]=strtoupper($pinyin[0]); 19 | } 20 | return implode('', $pinyinArr); 21 | } 22 | } -------------------------------------------------------------------------------- /core/helpers/TimeAgo.php: -------------------------------------------------------------------------------- 1 | "1 天前", 23 | 'aboutOneHour' => "大约 1 小时前", 24 | 'aboutOneMonth' => "大约 1 个月前", 25 | 'aboutOneYear' => "大约 1 年前", 26 | 'days' => "%s 天前", 27 | 'hours' => "%s 小时前", 28 | 'lessThanAMinute' => "1 分钟内", 29 | 'lessThanOneHour' => "%s 分钟前", 30 | 'months' => "%s 个月前", 31 | 'oneMinute' => "1 分钟前", 32 | 'years' => "超过 %s 年前" 33 | ); 34 | 35 | /** 36 | * TimeAgo constructor. 37 | * @param null|DateTimeZone $timezone the timezone to use (uses system if none is given) 38 | */ 39 | public function __construct($timezone=null) 40 | { 41 | $this->timezone=$timezone; 42 | // sets the default timezone 43 | $this->changeTimezone(); 44 | } 45 | 46 | /** 47 | * 析构函数用来恢复时区 48 | */ 49 | public function __destruct() { 50 | $this->restoreTimezone(); 51 | } 52 | 53 | /** 54 | * @param integer $past 55 | * @param integer $now 56 | */ 57 | public function inStamp($past, $now=null) { 58 | if ($now == null) { 59 | $now=time(); 60 | } 61 | // creates the "time ago" string. This always starts with an "about..." 62 | $timeAgo=""; 63 | 64 | // finds the time difference 65 | $timeDifference=$now - $past; 66 | // rule 0 67 | // $past is null or empty or '' 68 | if ($past == 0) { 69 | $timeAgo=$this->translate('never'); 70 | } 71 | // rule 1 72 | // less than 29secs 73 | else if ($this->isLessThan29Seconds($timeDifference)) { 74 | $timeAgo=$this->translate('lessThanAMinute'); 75 | } 76 | // rule 2 77 | // more than 29secs and less than 1min29secss 78 | else if ($this->isLessThan1Min29Seconds($timeDifference)) { 79 | $timeAgo=$this->translate('oneMinute'); 80 | } 81 | // rule 3 82 | // between 1min30secs and 44mins29secs 83 | else if ($this->isLessThan44Min29Secs($timeDifference)) { 84 | $minutes=round($timeDifference / $this->secondsPerMinute); 85 | $timeAgo=$this->translate('lessThanOneHour', $minutes); 86 | } 87 | // rule 4 88 | // between 44mins30secs and 1hour29mins59secs 89 | else if ($this->isLessThan1Hour29Mins59Seconds($timeDifference)) { 90 | $timeAgo=$this->translate('aboutOneHour'); 91 | } 92 | // rule 5 93 | // between 1hour29mins59secs and 23hours59mins29secs 94 | else if ($this->isLessThan23Hours59Mins29Seconds($timeDifference)) { 95 | $hours=round($timeDifference / $this->secondsPerHour); 96 | $timeAgo=$this->translate('hours', $hours); 97 | } 98 | // rule 6 99 | // between 23hours59mins30secs and 47hours59mins29secs 100 | else if ($this->isLessThan47Hours59Mins29Seconds($timeDifference)) { 101 | $timeAgo=$this->translate('aboutOneDay'); 102 | } 103 | // rule 7 104 | // between 47hours59mins30secs and 29days23hours59mins29secs 105 | else if ($this->isLessThan29Days23Hours59Mins29Seconds($timeDifference)) { 106 | $days=round($timeDifference / $this->secondsPerDay); 107 | $timeAgo=$this->translate('days', $days); 108 | } 109 | // rule 8 110 | // between 29days23hours59mins30secs and 59days23hours59mins29secs 111 | else if ($this->isLessThan59Days23Hours59Mins29Secs($timeDifference)) { 112 | $timeAgo=$this->translate('aboutOneMonth'); 113 | } 114 | // rule 9 115 | // between 59days23hours59mins30secs and 1year (minus 1sec) 116 | else if ($this->isLessThan1Year($timeDifference)) { 117 | $months=round($timeDifference / $this->secondsPerMonth); 118 | // if months is 1, then set it to 2, because we are "past" 1 month 119 | if ($months == 1) { 120 | $months=2; 121 | } 122 | 123 | $timeAgo=$this->translate('months', $months); 124 | } 125 | // rule 10 126 | // between 1year and 2years (minus 1sec) 127 | else if ($this->isLessThan2Years($timeDifference)) { 128 | $timeAgo=$this->translate('aboutOneYear'); 129 | } 130 | // rule 11 131 | // 2years or more 132 | else { 133 | $years=floor($timeDifference / $this->secondsPerYear); 134 | $timeAgo=$this->translate('years', $years); 135 | } 136 | 137 | return $timeAgo; 138 | } 139 | /** 140 | * Fetches the different between $past and $now in a spoken format. 141 | * NOTE: both past and now should be parseable by strtotime 142 | * @param string $past the past date to use 143 | * @param string $now the current time, defaults to now (can be an other time though) 144 | * @return string the difference in spoken format, e.g. 1 day ago 145 | */ 146 | public function inWords($past, $now="now") 147 | { 148 | 149 | // finds the past in datetime 150 | $past=strtotime($past); 151 | // finds the current datetime 152 | $now=strtotime($now); 153 | if ($this->isPastEmpty($past)) { 154 | $past=0; 155 | $past=0; 156 | } 157 | return $this->inStamp($past, $now); 158 | } 159 | 160 | /** 161 | * Fetches the date difference between the two given dates. 162 | * NOTE: both past and now should be parseable by strtotime 163 | * 164 | * @param string $past the "past" time to parse 165 | * @param string $now the "now" time to parse 166 | * @return array the difference in dates, using the two dates 167 | */ 168 | public function dateDifference($past, $now="now") 169 | { 170 | // initializes the placeholders for the different "times" 171 | $seconds=0; 172 | $minutes=0; 173 | $hours=0; 174 | $days=0; 175 | $months=0; 176 | $years=0; 177 | 178 | // finds the past in datetime 179 | $past=strtotime($past); 180 | // finds the current datetime 181 | $now=strtotime($now); 182 | 183 | // calculates the difference 184 | $timeDifference=$now - $past; 185 | 186 | // starts determining the time difference 187 | if ($timeDifference >= 0) { 188 | switch ($timeDifference) { 189 | // finds the number of years 190 | case ($timeDifference >= $this->secondsPerYear): 191 | // uses floor to remove decimals 192 | $years=floor($timeDifference / $this->secondsPerYear); 193 | // saves the amount of seconds left 194 | $timeDifference=$timeDifference - ($years * $this->secondsPerYear); 195 | 196 | // finds the number of months 197 | case ($timeDifference >= $this->secondsPerMonth && $timeDifference <= ($this->secondsPerYear - 1)): 198 | // uses floor to remove decimals 199 | $months=floor($timeDifference / $this->secondsPerMonth); 200 | // saves the amount of seconds left 201 | $timeDifference=$timeDifference - ($months * $this->secondsPerMonth); 202 | 203 | // finds the number of days 204 | case ($timeDifference >= $this->secondsPerDay && $timeDifference <= ($this->secondsPerYear - 1)): 205 | // uses floor to remove decimals 206 | $days=floor($timeDifference / $this->secondsPerDay); 207 | // saves the amount of seconds left 208 | $timeDifference=$timeDifference - ($days * $this->secondsPerDay); 209 | 210 | // finds the number of hours 211 | case ($timeDifference >= $this->secondsPerHour && $timeDifference <= ($this->secondsPerDay - 1)): 212 | // uses floor to remove decimals 213 | $hours=floor($timeDifference / $this->secondsPerHour); 214 | // saves the amount of seconds left 215 | $timeDifference=$timeDifference - ($hours * $this->secondsPerHour); 216 | 217 | // finds the number of minutes 218 | case ($timeDifference >= $this->secondsPerMinute && $timeDifference <= ($this->secondsPerHour - 1)): 219 | // uses floor to remove decimals 220 | $minutes=floor($timeDifference / $this->secondsPerMinute); 221 | // saves the amount of seconds left 222 | $timeDifference=$timeDifference - ($minutes * $this->secondsPerMinute); 223 | 224 | // finds the number of seconds 225 | case ($timeDifference <= ($this->secondsPerMinute - 1)): 226 | // seconds is just what there is in the timeDifference variable 227 | $seconds=$timeDifference; 228 | } 229 | } 230 | 231 | $difference=[ 232 | "years" => $years, 233 | "months" => $months, 234 | "days" => $days, 235 | "hours" => $hours, 236 | "minutes" => $minutes, 237 | "seconds" => $seconds, 238 | ]; 239 | 240 | return $difference; 241 | } 242 | 243 | /** 244 | * Translates the given $label, and adds the given $time. 245 | * @param string $label the label to translate 246 | * @param string $time the time to add to the translated text. 247 | * @return string the translated label text including the time. 248 | */ 249 | protected function translate($label, $time='') 250 | { 251 | // handles a usecase introduced in #18, where a new translation was added. 252 | // This would cause an array-out-of-bound exception, since the index does not 253 | // exist in most translations. 254 | if (!isset(self::$timeAgoStrings[$label])) { 255 | return ''; 256 | } 257 | 258 | return sprintf(self::$timeAgoStrings[$label], $time); 259 | } 260 | 261 | 262 | /** 263 | * Changes the timezone 264 | */ 265 | protected function changeTimezone() 266 | { 267 | $this->previousTimezone=false; 268 | if ($this->timezone) { 269 | $this->previousTimezone=date_default_timezone_get(); 270 | date_default_timezone_set($this->timezone); 271 | } 272 | } 273 | 274 | /** 275 | * Restores a previous timezone 276 | */ 277 | protected function restoreTimezone() 278 | { 279 | if ($this->previousTimezone) { 280 | date_default_timezone_set($this->previousTimezone); 281 | $this->previousTimezone=false; 282 | } 283 | } 284 | 285 | /** 286 | * Checks if the given past is empty 287 | * @param string $past the "past" to check 288 | * @return bool true if empty, else false 289 | */ 290 | private function isPastEmpty($past) 291 | { 292 | return $past === '' || is_null($past) || empty($past); 293 | } 294 | 295 | /** 296 | * Checks if the time difference is less than 29seconds 297 | * @param int $timeDifference the time difference in seconds 298 | * @return bool 299 | */ 300 | private function isLessThan29Seconds($timeDifference) 301 | { 302 | return $timeDifference <= 29; 303 | } 304 | 305 | /** 306 | * Checks if the time difference is less than 1min 29seconds 307 | * @param int $timeDifference the time difference in seconds 308 | * @return bool 309 | */ 310 | private function isLessThan1Min29Seconds($timeDifference) 311 | { 312 | return $timeDifference >= 30 && $timeDifference <= 89; 313 | } 314 | 315 | /** 316 | * Checks if the time difference is less than 44mins 29seconds 317 | * @param int $timeDifference the time difference in seconds 318 | * @return bool 319 | */ 320 | private function isLessThan44Min29Secs($timeDifference) 321 | { 322 | return $timeDifference >= 90 && 323 | $timeDifference <= (($this->secondsPerMinute * 44) + 29); 324 | } 325 | 326 | /** 327 | * Checks if the time difference is less than 1hour 29mins 59seconds 328 | * @param int $timeDifference the time difference in seconds 329 | * @return bool 330 | */ 331 | private function isLessThan1Hour29Mins59Seconds($timeDifference) 332 | { 333 | return $timeDifference >= (($this->secondsPerMinute * 44) + 30) 334 | && 335 | $timeDifference <= ($this->secondsPerHour + ($this->secondsPerMinute * 29) + 59); 336 | } 337 | 338 | /** 339 | * Checks if the time difference is less than 23hours 59mins 29seconds 340 | * @param int $timeDifference the time difference in seconds 341 | * @return bool 342 | */ 343 | private function isLessThan23Hours59Mins29Seconds($timeDifference) 344 | { 345 | return $timeDifference >= ( 346 | $this->secondsPerHour + 347 | ($this->secondsPerMinute * 30) 348 | ) 349 | && 350 | $timeDifference <= ( 351 | ($this->secondsPerHour * 23) + 352 | ($this->secondsPerMinute * 59) + 353 | 29 354 | ); 355 | } 356 | 357 | /** 358 | * Checks if the time difference is less than 27hours 59mins 29seconds 359 | * @param int $timeDifference the time difference in seconds 360 | * @return bool 361 | */ 362 | private function isLessThan47Hours59Mins29Seconds($timeDifference) 363 | { 364 | return $timeDifference >= ( 365 | ($this->secondsPerHour * 23) + 366 | ($this->secondsPerMinute * 59) + 367 | 30 368 | ) 369 | && 370 | $timeDifference <= ( 371 | ($this->secondsPerHour * 47) + 372 | ($this->secondsPerMinute * 59) + 373 | 29 374 | ); 375 | } 376 | 377 | /** 378 | * Checks if the time difference is less than 29days 23hours 59mins 29seconds 379 | * @param int $timeDifference the time difference in seconds 380 | * @return bool 381 | */ 382 | private function isLessThan29Days23Hours59Mins29Seconds($timeDifference) 383 | { 384 | return $timeDifference >= ( 385 | ($this->secondsPerHour * 47) + 386 | ($this->secondsPerMinute * 59) + 387 | 30 388 | ) 389 | && 390 | $timeDifference <= ( 391 | ($this->secondsPerDay * 29) + 392 | ($this->secondsPerHour * 23) + 393 | ($this->secondsPerMinute * 59) + 394 | 29 395 | ); 396 | } 397 | 398 | /** 399 | * Checks if the time difference is less than 59days 23hours 59mins 29seconds 400 | * @param int $timeDifference the time difference in seconds 401 | * @return bool 402 | */ 403 | private function isLessThan59Days23Hours59Mins29Secs($timeDifference) 404 | { 405 | return $timeDifference >= ( 406 | ($this->secondsPerDay * 29) + 407 | ($this->secondsPerHour * 23) + 408 | ($this->secondsPerMinute * 59) + 409 | 30 410 | ) 411 | && 412 | $timeDifference <= ( 413 | ($this->secondsPerDay * 59) + 414 | ($this->secondsPerHour * 23) + 415 | ($this->secondsPerMinute * 59) + 416 | 29 417 | ); 418 | } 419 | 420 | /** 421 | * Checks if the time difference is less than 1 year 422 | * @param int $timeDifference the time difference in seconds 423 | * @return bool 424 | */ 425 | private function isLessThan1Year($timeDifference) 426 | { 427 | return $timeDifference >= ( 428 | ($this->secondsPerDay * 59) + 429 | ($this->secondsPerHour * 23) + 430 | ($this->secondsPerMinute * 59) + 431 | 30 432 | ) 433 | && 434 | $timeDifference < $this->secondsPerYear; 435 | } 436 | 437 | /** 438 | * Checks if the time difference is less than 2 years 439 | * @param int $timeDifference the time difference in seconds 440 | * @return bool 441 | */ 442 | private function isLessThan2Years($timeDifference) 443 | { 444 | return $timeDifference >= $this->secondsPerYear 445 | && 446 | $timeDifference < ($this->secondsPerYear * 2); 447 | } 448 | } -------------------------------------------------------------------------------- /core/models/BaseDb.php: -------------------------------------------------------------------------------- 1 | all(); 50 | } elseif (!is_array($values)) { 51 | continue; 52 | } 53 | 54 | $results = array_merge($results, $values); 55 | } 56 | 57 | return $results; 58 | } 59 | 60 | /** 61 | * Divide an array into two arrays. One with keys and the other with values. 62 | * 63 | * @param array $array 64 | * @return array 65 | */ 66 | public static function divide($array) { 67 | return [array_keys($array), array_values($array)]; 68 | } 69 | 70 | /** 71 | * Flatten a multi-dimensional associative array with dots. 72 | * 73 | * @param array $array 74 | * @param string $prepend 75 | * @return array 76 | */ 77 | public static function dot($array, $prepend = '') { 78 | $results = []; 79 | 80 | foreach ($array as $key => $value) { 81 | if (is_array($value) && !empty($value)) { 82 | $results = array_merge($results, static::dot($value, $prepend . $key . '.')); 83 | } else { 84 | $results[$prepend . $key] = $value; 85 | } 86 | } 87 | 88 | return $results; 89 | } 90 | 91 | /** 92 | * Get all of the given array except for a specified array of items. 93 | * 94 | * @param array $array 95 | * @param array|string $keys 96 | * @return array 97 | */ 98 | public static function except($array, $keys) { 99 | static::forget($array, $keys); 100 | 101 | return $array; 102 | } 103 | 104 | /** 105 | * Determine if the given key exists in the provided array. 106 | * 107 | * @param \ArrayAccess|array $array 108 | * @param string|int $key 109 | * @return bool 110 | */ 111 | public static function exists($array, $key) { 112 | if ($array instanceof ArrayAccess) { 113 | return $array->offsetExists($key); 114 | } 115 | 116 | return array_key_exists($key, $array); 117 | } 118 | 119 | /** 120 | * Return the first element in an array passing a given truth test. 121 | * 122 | * @param array $array 123 | * @param callable|null $callback 124 | * @param mixed $default 125 | * @return mixed 126 | */ 127 | public static function first($array, callable $callback = null, $default = null) { 128 | if (is_null($callback)) { 129 | if (empty($array)) { 130 | return value($default); 131 | } 132 | 133 | foreach ($array as $item) { 134 | return $item; 135 | } 136 | } 137 | 138 | foreach ($array as $key => $value) { 139 | if (call_user_func($callback, $value, $key)) { 140 | return $value; 141 | } 142 | } 143 | 144 | return value($default); 145 | } 146 | 147 | /** 148 | * Return the last element in an array passing a given truth test. 149 | * 150 | * @param array $array 151 | * @param callable|null $callback 152 | * @param mixed $default 153 | * @return mixed 154 | */ 155 | public static function last($array, callable $callback = null, $default = null) { 156 | if (is_null($callback)) { 157 | return empty($array) ? value($default) : end($array); 158 | } 159 | 160 | return static::first(array_reverse($array, true), $callback, $default); 161 | } 162 | 163 | /** 164 | * Flatten a multi-dimensional array into a single level. 165 | * 166 | * @param array $array 167 | * @param int $depth 168 | * @return array 169 | */ 170 | public static function flatten($array, $depth = INF) { 171 | return array_reduce($array, function ($result, $item) use ($depth) { 172 | $item = $item instanceof Collection ? $item->all() : $item; 173 | 174 | if (!is_array($item)) { 175 | return array_merge($result, [$item]); 176 | } elseif ($depth === 1) { 177 | return array_merge($result, array_values($item)); 178 | } else { 179 | return array_merge($result, static::flatten($item, $depth - 1)); 180 | } 181 | }, []); 182 | } 183 | 184 | /** 185 | * Remove one or many array items from a given array using "dot" notation. 186 | * 187 | * @param array $array 188 | * @param array|string $keys 189 | * @return void 190 | */ 191 | public static function forget(&$array, $keys) { 192 | $original = &$array; 193 | 194 | $keys = (array)$keys; 195 | 196 | if (count($keys) === 0) { 197 | return; 198 | } 199 | 200 | foreach ($keys as $key) { 201 | // if the exact key exists in the top-level, remove it 202 | if (static::exists($array, $key)) { 203 | unset($array[$key]); 204 | 205 | continue; 206 | } 207 | 208 | $parts = explode('.', $key); 209 | 210 | // clean up before each pass 211 | $array = &$original; 212 | 213 | while (count($parts) > 1) { 214 | $part = array_shift($parts); 215 | 216 | if (isset($array[$part]) && is_array($array[$part])) { 217 | $array = &$array[$part]; 218 | } else { 219 | continue 2; 220 | } 221 | } 222 | 223 | unset($array[array_shift($parts)]); 224 | } 225 | } 226 | 227 | /** 228 | * Get an item from an array using "dot" notation. 229 | * 230 | * @param \ArrayAccess|array $array 231 | * @param string $key 232 | * @param mixed $default 233 | * @return mixed 234 | */ 235 | public static function get($array, $key, $default = null) { 236 | if (!static::accessible($array)) { 237 | return value($default); 238 | } 239 | 240 | if (is_null($key)) { 241 | return $array; 242 | } 243 | 244 | if (static::exists($array, $key)) { 245 | return $array[$key]; 246 | } 247 | 248 | foreach (explode('.', $key) as $segment) { 249 | if (static::accessible($array) && static::exists($array, $segment)) { 250 | $array = $array[$segment]; 251 | } else { 252 | return value($default); 253 | } 254 | } 255 | 256 | return $array; 257 | } 258 | 259 | /** 260 | * Check if an item or items exist in an array using "dot" notation. 261 | * 262 | * @param \ArrayAccess|array $array 263 | * @param string|array $keys 264 | * @return bool 265 | */ 266 | public static function has($array, $keys) { 267 | if (is_null($keys)) { 268 | return false; 269 | } 270 | 271 | $keys = (array)$keys; 272 | 273 | if (!$array) { 274 | return false; 275 | } 276 | 277 | if ($keys === []) { 278 | return false; 279 | } 280 | 281 | foreach ($keys as $key) { 282 | $subKeyArray = $array; 283 | 284 | if (static::exists($array, $key)) { 285 | continue; 286 | } 287 | 288 | foreach (explode('.', $key) as $segment) { 289 | if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) { 290 | $subKeyArray = $subKeyArray[$segment]; 291 | } else { 292 | return false; 293 | } 294 | } 295 | } 296 | 297 | return true; 298 | } 299 | 300 | /** 301 | * Determines if an array is associative. 302 | * 303 | * An array is "associative" if it doesn't have sequential numerical keys beginning with zero. 304 | * 305 | * @param array $array 306 | * @return bool 307 | */ 308 | public static function isAssoc(array $array) { 309 | $keys = array_keys($array); 310 | 311 | return array_keys($keys) !== $keys; 312 | } 313 | /** 314 | * Is Array Multidim 315 | * 316 | * @access public 317 | * @param $array 318 | * 319 | * @return boolean 320 | */ 321 | public static function isMultidim($array) 322 | { 323 | if (!is_array($array)) { 324 | return false; 325 | } 326 | 327 | return (bool)count(array_filter($array, 'is_array')); 328 | } 329 | 330 | /** 331 | * Get a subset of the items from the given array. 332 | * 333 | * @param array $array 334 | * @param array|string $keys 335 | * @return array 336 | */ 337 | public static function only($array, $keys) { 338 | return array_intersect_key($array, array_flip((array)$keys)); 339 | } 340 | 341 | /** 342 | * Pluck an array of values from an array. 343 | * 344 | * @param array $array 345 | * @param string|array $value 346 | * @param string|array|null $key 347 | * @return array 348 | */ 349 | public static function pluck($array, $value, $key = null) { 350 | $results = []; 351 | 352 | list($value, $key) = static::explodePluckParameters($value, $key); 353 | 354 | foreach ($array as $item) { 355 | $itemValue = data_get($item, $value); 356 | 357 | // If the key is "null", we will just append the value to the array and keep 358 | // looping. Otherwise we will key the array using the value of the key we 359 | // received from the developer. Then we'll return the final array form. 360 | if (is_null($key)) { 361 | $results[] = $itemValue; 362 | } else { 363 | $itemKey = data_get($item, $key); 364 | 365 | $results[$itemKey] = $itemValue; 366 | } 367 | } 368 | 369 | return $results; 370 | } 371 | 372 | /** 373 | * Explode the "value" and "key" arguments passed to "pluck". 374 | * 375 | * @param string|array $value 376 | * @param string|array|null $key 377 | * @return array 378 | */ 379 | protected static function explodePluckParameters($value, $key) { 380 | $value = is_string($value) ? explode('.', $value) : $value; 381 | 382 | $key = is_null($key) || is_array($key) ? $key : explode('.', $key); 383 | 384 | return [$value, $key]; 385 | } 386 | 387 | /** 388 | * Push an item onto the beginning of an array. 389 | * 390 | * @param array $array 391 | * @param mixed $value 392 | * @param mixed $key 393 | * @return array 394 | */ 395 | public static function prepend($array, $value, $key = null) { 396 | if (is_null($key)) { 397 | array_unshift($array, $value); 398 | } else { 399 | $array = [$key => $value] + $array; 400 | } 401 | 402 | return $array; 403 | } 404 | 405 | /** 406 | * Get a value from the array, and remove it. 407 | * 408 | * @param array $array 409 | * @param string $key 410 | * @param mixed $default 411 | * @return mixed 412 | */ 413 | public static function pull(&$array, $key, $default = null) { 414 | $value = static::get($array, $key, $default); 415 | 416 | static::forget($array, $key); 417 | 418 | return $value; 419 | } 420 | 421 | /** 422 | * Set an array item to a given value using "dot" notation. 423 | * 424 | * If no key is given to the method, the entire array will be replaced. 425 | * 426 | * @param array $array 427 | * @param string $key 428 | * @param mixed $value 429 | * @return array 430 | */ 431 | public static function set(&$array, $key, $value) { 432 | if (is_null($key)) { 433 | return $array = $value; 434 | } 435 | 436 | $keys = explode('.', $key); 437 | 438 | while (count($keys) > 1) { 439 | $key = array_shift($keys); 440 | 441 | // If the key doesn't exist at this depth, we will just create an empty array 442 | // to hold the next value, allowing us to create the arrays to hold final 443 | // values at the correct depth. Then we'll keep digging into the array. 444 | if (!isset($array[$key]) || !is_array($array[$key])) { 445 | $array[$key] = []; 446 | } 447 | 448 | $array = &$array[$key]; 449 | } 450 | 451 | $array[array_shift($keys)] = $value; 452 | 453 | return $array; 454 | } 455 | 456 | /** 457 | * Shuffle the given array and return the result. 458 | * 459 | * @param array $array 460 | * @return array 461 | */ 462 | public static function shuffle($array) { 463 | shuffle($array); 464 | 465 | return $array; 466 | } 467 | 468 | /** 469 | * Sort the array using the given callback or "dot" notation. 470 | * 471 | * @param array $array 472 | * @param callable|string $callback 473 | * @return array 474 | */ 475 | public static function sort($array, $callback) { 476 | return Collection::make($array)->sortBy($callback)->all(); 477 | } 478 | 479 | /** 480 | * Recursively sort an array by keys and values. 481 | * 482 | * @param array $array 483 | * @return array 484 | */ 485 | public static function sortRecursive($array) { 486 | foreach ($array as &$value) { 487 | if (is_array($value)) { 488 | $value = static::sortRecursive($value); 489 | } 490 | } 491 | 492 | if (static::isAssoc($array)) { 493 | ksort($array); 494 | } else { 495 | sort($array); 496 | } 497 | 498 | return $array; 499 | } 500 | 501 | /** 502 | * Filter the array using the given callback. 503 | * 504 | * @param array $array 505 | * @param callable $callback 506 | * @return array 507 | */ 508 | public static function where($array, callable $callback) { 509 | return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); 510 | } 511 | } -------------------------------------------------------------------------------- /core/tools/Str.php: -------------------------------------------------------------------------------- 1 | 5) { 267 | return static::random($length); 268 | } 269 | 270 | $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 271 | 272 | return substr(str_shuffle(str_repeat($pool, $length)), 0, $length); 273 | } 274 | 275 | /** 276 | * Generate a more truly "random" alpha-numeric string. 277 | * 278 | * @param int $length 279 | * @return string 280 | */ 281 | public static function random($length = 16) { 282 | $string = ''; 283 | 284 | while (($len = strlen($string)) < $length) { 285 | $size = $length - $len; 286 | 287 | $bytes = random_bytes($size); 288 | 289 | $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); 290 | } 291 | 292 | return $string; 293 | } 294 | 295 | /** 296 | * Replace a given value in the string sequentially with an array. 297 | * 298 | * @param string $search 299 | * @param array $replace 300 | * @param string $subject 301 | * @return string 302 | */ 303 | public static function replaceArray($search, array $replace, $subject) { 304 | foreach ($replace as $value) { 305 | $subject = static::replaceFirst($search, $value, $subject); 306 | } 307 | 308 | return $subject; 309 | } 310 | 311 | /** 312 | * Replace the first occurrence of a given value in the string. 313 | * 314 | * @param string $search 315 | * @param string $replace 316 | * @param string $subject 317 | * @return string 318 | */ 319 | public static function replaceFirst($search, $replace, $subject) { 320 | $position = strpos($subject, $search); 321 | 322 | if ($position !== false) { 323 | return substr_replace($subject, $replace, $position, strlen($search)); 324 | } 325 | 326 | return $subject; 327 | } 328 | 329 | /** 330 | * Replace the last occurrence of a given value in the string. 331 | * 332 | * @param string $search 333 | * @param string $replace 334 | * @param string $subject 335 | * @return string 336 | */ 337 | public static function replaceLast($search, $replace, $subject) { 338 | $position = strrpos($subject, $search); 339 | 340 | if ($position !== false) { 341 | return substr_replace($subject, $replace, $position, strlen($search)); 342 | } 343 | 344 | return $subject; 345 | } 346 | 347 | /** 348 | * Convert the given string to title case. 349 | * 350 | * @param string $value 351 | * @return string 352 | */ 353 | public static function title($value) { 354 | return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); 355 | } 356 | 357 | /** 358 | * Get the singular form of an English word. 359 | * 360 | * @param string $value 361 | * @return string 362 | */ 363 | public static function singular($value) { 364 | return Pluralizer::singular($value); 365 | } 366 | 367 | /** 368 | * Generate a URL friendly "slug" from a given string. 369 | * 370 | * @param string $title 371 | * @param string $separator 372 | * @return string 373 | */ 374 | public static function slug($title, $separator = '-') { 375 | $title = static::ascii($title); 376 | 377 | // Convert all dashes/underscores into separator 378 | $flip = $separator == '-' ? '_' : '-'; 379 | 380 | $title = preg_replace('![' . preg_quote($flip) . ']+!u', $separator, $title); 381 | 382 | // Remove all characters that are not the separator, letters, numbers, or whitespace. 383 | $title = preg_replace('![^' . preg_quote($separator) . '\pL\pN\s]+!u', '', mb_strtolower($title)); 384 | 385 | // Replace all separator characters and whitespace by a single separator 386 | $title = preg_replace('![' . preg_quote($separator) . '\s]+!u', $separator, $title); 387 | 388 | return trim($title, $separator); 389 | } 390 | 391 | /** 392 | * Transliterate a UTF-8 value to ASCII. 393 | * 394 | * @param string $value 395 | * @return string 396 | */ 397 | public static function ascii($value) { 398 | foreach (static::charsArray() as $key => $val) { 399 | $value = str_replace($val, $key, $value); 400 | } 401 | 402 | return preg_replace('/[^\x20-\x7E]/u', '', $value); 403 | } 404 | 405 | /** 406 | * Returns the replacements for the ascii method. 407 | * 408 | * Note: Adapted from Stringy\Stringy. 409 | * 410 | * @see https://github.com/danielstjules/Stringy/blob/2.3.1/LICENSE.txt 411 | * 412 | * @return array 413 | */ 414 | protected static function charsArray() { 415 | static $charsArray; 416 | 417 | if (isset($charsArray)) { 418 | return $charsArray; 419 | } 420 | 421 | return $charsArray = [ 422 | '0' => ['°', '₀', '۰'], 423 | '1' => ['¹', '₁', '۱'], 424 | '2' => ['²', '₂', '۲'], 425 | '3' => ['³', '₃', '۳'], 426 | '4' => ['⁴', '₄', '۴', '٤'], 427 | '5' => ['⁵', '₅', '۵', '٥'], 428 | '6' => ['⁶', '₆', '۶', '٦'], 429 | '7' => ['⁷', '₇', '۷'], 430 | '8' => ['⁸', '₈', '۸'], 431 | '9' => ['⁹', '₉', '۹'], 432 | 'a' => ['à', 'á', 'ả', 'ã', 'ạ', 'ă', 'ắ', 'ằ', 'ẳ', 'ẵ', 'ặ', 'â', 'ấ', 'ầ', 'ẩ', 'ẫ', 'ậ', 'ā', 'ą', 'å', 'α', 'ά', 'ἀ', 'ἁ', 'ἂ', 'ἃ', 'ἄ', 'ἅ', 'ἆ', 'ἇ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ὰ', 'ά', 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'а', 'أ', 'အ', 'ာ', 'ါ', 'ǻ', 'ǎ', 'ª', 'ა', 'अ', 'ا'], 433 | 'b' => ['б', 'β', 'Ъ', 'Ь', 'ب', 'ဗ', 'ბ'], 434 | 'c' => ['ç', 'ć', 'č', 'ĉ', 'ċ'], 435 | 'd' => ['ď', 'ð', 'đ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ᵭ', 'ᶁ', 'ᶑ', 'д', 'δ', 'د', 'ض', 'ဍ', 'ဒ', 'დ'], 436 | 'e' => ['é', 'è', 'ẻ', 'ẽ', 'ẹ', 'ê', 'ế', 'ề', 'ể', 'ễ', 'ệ', 'ë', 'ē', 'ę', 'ě', 'ĕ', 'ė', 'ε', 'έ', 'ἐ', 'ἑ', 'ἒ', 'ἓ', 'ἔ', 'ἕ', 'ὲ', 'έ', 'е', 'ё', 'э', 'є', 'ə', 'ဧ', 'ေ', 'ဲ', 'ე', 'ए', 'إ', 'ئ'], 437 | 'f' => ['ф', 'φ', 'ف', 'ƒ', 'ფ'], 438 | 'g' => ['ĝ', 'ğ', 'ġ', 'ģ', 'г', 'ґ', 'γ', 'ဂ', 'გ', 'گ'], 439 | 'h' => ['ĥ', 'ħ', 'η', 'ή', 'ح', 'ه', 'ဟ', 'ှ', 'ჰ'], 440 | 'i' => ['í', 'ì', 'ỉ', 'ĩ', 'ị', 'î', 'ï', 'ī', 'ĭ', 'į', 'ı', 'ι', 'ί', 'ϊ', 'ΐ', 'ἰ', 'ἱ', 'ἲ', 'ἳ', 'ἴ', 'ἵ', 'ἶ', 'ἷ', 'ὶ', 'ί', 'ῐ', 'ῑ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'і', 'ї', 'и', 'ဣ', 'ိ', 'ီ', 'ည်', 'ǐ', 'ი', 'इ'], 441 | 'j' => ['ĵ', 'ј', 'Ј', 'ჯ', 'ج'], 442 | 'k' => ['ķ', 'ĸ', 'к', 'κ', 'Ķ', 'ق', 'ك', 'က', 'კ', 'ქ', 'ک'], 443 | 'l' => ['ł', 'ľ', 'ĺ', 'ļ', 'ŀ', 'л', 'λ', 'ل', 'လ', 'ლ'], 444 | 'm' => ['м', 'μ', 'م', 'မ', 'მ'], 445 | 'n' => ['ñ', 'ń', 'ň', 'ņ', 'ʼn', 'ŋ', 'ν', 'н', 'ن', 'န', 'ნ'], 446 | 'o' => ['ó', 'ò', 'ỏ', 'õ', 'ọ', 'ô', 'ố', 'ồ', 'ổ', 'ỗ', 'ộ', 'ơ', 'ớ', 'ờ', 'ở', 'ỡ', 'ợ', 'ø', 'ō', 'ő', 'ŏ', 'ο', 'ὀ', 'ὁ', 'ὂ', 'ὃ', 'ὄ', 'ὅ', 'ὸ', 'ό', 'о', 'و', 'θ', 'ို', 'ǒ', 'ǿ', 'º', 'ო', 'ओ'], 447 | 'p' => ['п', 'π', 'ပ', 'პ', 'پ'], 448 | 'q' => ['ყ'], 449 | 'r' => ['ŕ', 'ř', 'ŗ', 'р', 'ρ', 'ر', 'რ'], 450 | 's' => ['ś', 'š', 'ş', 'с', 'σ', 'ș', 'ς', 'س', 'ص', 'စ', 'ſ', 'ს'], 451 | 't' => ['ť', 'ţ', 'т', 'τ', 'ț', 'ت', 'ط', 'ဋ', 'တ', 'ŧ', 'თ', 'ტ'], 452 | 'u' => ['ú', 'ù', 'ủ', 'ũ', 'ụ', 'ư', 'ứ', 'ừ', 'ử', 'ữ', 'ự', 'û', 'ū', 'ů', 'ű', 'ŭ', 'ų', 'µ', 'у', 'ဉ', 'ု', 'ူ', 'ǔ', 'ǖ', 'ǘ', 'ǚ', 'ǜ', 'უ', 'उ'], 453 | 'v' => ['в', 'ვ', 'ϐ'], 454 | 'w' => ['ŵ', 'ω', 'ώ', 'ဝ', 'ွ'], 455 | 'x' => ['χ', 'ξ'], 456 | 'y' => ['ý', 'ỳ', 'ỷ', 'ỹ', 'ỵ', 'ÿ', 'ŷ', 'й', 'ы', 'υ', 'ϋ', 'ύ', 'ΰ', 'ي', 'ယ'], 457 | 'z' => ['ź', 'ž', 'ż', 'з', 'ζ', 'ز', 'ဇ', 'ზ'], 458 | 'aa' => ['ع', 'आ', 'آ'], 459 | 'ae' => ['ä', 'æ', 'ǽ'], 460 | 'ai' => ['ऐ'], 461 | 'at' => ['@'], 462 | 'ch' => ['ч', 'ჩ', 'ჭ', 'چ'], 463 | 'dj' => ['ђ', 'đ'], 464 | 'dz' => ['џ', 'ძ'], 465 | 'ei' => ['ऍ'], 466 | 'gh' => ['غ', 'ღ'], 467 | 'ii' => ['ई'], 468 | 'ij' => ['ij'], 469 | 'kh' => ['х', 'خ', 'ხ'], 470 | 'lj' => ['љ'], 471 | 'nj' => ['њ'], 472 | 'oe' => ['ö', 'œ', 'ؤ'], 473 | 'oi' => ['ऑ'], 474 | 'oii' => ['ऒ'], 475 | 'ps' => ['ψ'], 476 | 'sh' => ['ш', 'შ', 'ش'], 477 | 'shch' => ['щ'], 478 | 'ss' => ['ß'], 479 | 'sx' => ['ŝ'], 480 | 'th' => ['þ', 'ϑ', 'ث', 'ذ', 'ظ'], 481 | 'ts' => ['ц', 'ც', 'წ'], 482 | 'ue' => ['ü'], 483 | 'uu' => ['ऊ'], 484 | 'ya' => ['я'], 485 | 'yu' => ['ю'], 486 | 'zh' => ['ж', 'ჟ', 'ژ'], 487 | '(c)' => ['©'], 488 | 'A' => ['Á', 'À', 'Ả', 'Ã', 'Ạ', 'Ă', 'Ắ', 'Ằ', 'Ẳ', 'Ẵ', 'Ặ', 'Â', 'Ấ', 'Ầ', 'Ẩ', 'Ẫ', 'Ậ', 'Å', 'Ā', 'Ą', 'Α', 'Ά', 'Ἀ', 'Ἁ', 'Ἂ', 'Ἃ', 'Ἄ', 'Ἅ', 'Ἆ', 'Ἇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'Ᾰ', 'Ᾱ', 'Ὰ', 'Ά', 'ᾼ', 'А', 'Ǻ', 'Ǎ'], 489 | 'B' => ['Б', 'Β', 'ब'], 490 | 'C' => ['Ç', 'Ć', 'Č', 'Ĉ', 'Ċ'], 491 | 'D' => ['Ď', 'Ð', 'Đ', 'Ɖ', 'Ɗ', 'Ƌ', 'ᴅ', 'ᴆ', 'Д', 'Δ'], 492 | 'E' => ['É', 'È', 'Ẻ', 'Ẽ', 'Ẹ', 'Ê', 'Ế', 'Ề', 'Ể', 'Ễ', 'Ệ', 'Ë', 'Ē', 'Ę', 'Ě', 'Ĕ', 'Ė', 'Ε', 'Έ', 'Ἐ', 'Ἑ', 'Ἒ', 'Ἓ', 'Ἔ', 'Ἕ', 'Έ', 'Ὲ', 'Е', 'Ё', 'Э', 'Є', 'Ə'], 493 | 'F' => ['Ф', 'Φ'], 494 | 'G' => ['Ğ', 'Ġ', 'Ģ', 'Г', 'Ґ', 'Γ'], 495 | 'H' => ['Η', 'Ή', 'Ħ'], 496 | 'I' => ['Í', 'Ì', 'Ỉ', 'Ĩ', 'Ị', 'Î', 'Ï', 'Ī', 'Ĭ', 'Į', 'İ', 'Ι', 'Ί', 'Ϊ', 'Ἰ', 'Ἱ', 'Ἳ', 'Ἴ', 'Ἵ', 'Ἶ', 'Ἷ', 'Ῐ', 'Ῑ', 'Ὶ', 'Ί', 'И', 'І', 'Ї', 'Ǐ', 'ϒ'], 497 | 'K' => ['К', 'Κ'], 498 | 'L' => ['Ĺ', 'Ł', 'Л', 'Λ', 'Ļ', 'Ľ', 'Ŀ', 'ल'], 499 | 'M' => ['М', 'Μ'], 500 | 'N' => ['Ń', 'Ñ', 'Ň', 'Ņ', 'Ŋ', 'Н', 'Ν'], 501 | 'O' => ['Ó', 'Ò', 'Ỏ', 'Õ', 'Ọ', 'Ô', 'Ố', 'Ồ', 'Ổ', 'Ỗ', 'Ộ', 'Ơ', 'Ớ', 'Ờ', 'Ở', 'Ỡ', 'Ợ', 'Ø', 'Ō', 'Ő', 'Ŏ', 'Ο', 'Ό', 'Ὀ', 'Ὁ', 'Ὂ', 'Ὃ', 'Ὄ', 'Ὅ', 'Ὸ', 'Ό', 'О', 'Θ', 'Ө', 'Ǒ', 'Ǿ'], 502 | 'P' => ['П', 'Π'], 503 | 'R' => ['Ř', 'Ŕ', 'Р', 'Ρ', 'Ŗ'], 504 | 'S' => ['Ş', 'Ŝ', 'Ș', 'Š', 'Ś', 'С', 'Σ'], 505 | 'T' => ['Ť', 'Ţ', 'Ŧ', 'Ț', 'Т', 'Τ'], 506 | 'U' => ['Ú', 'Ù', 'Ủ', 'Ũ', 'Ụ', 'Ư', 'Ứ', 'Ừ', 'Ử', 'Ữ', 'Ự', 'Û', 'Ū', 'Ů', 'Ű', 'Ŭ', 'Ų', 'У', 'Ǔ', 'Ǖ', 'Ǘ', 'Ǚ', 'Ǜ'], 507 | 'V' => ['В'], 508 | 'W' => ['Ω', 'Ώ', 'Ŵ'], 509 | 'X' => ['Χ', 'Ξ'], 510 | 'Y' => ['Ý', 'Ỳ', 'Ỷ', 'Ỹ', 'Ỵ', 'Ÿ', 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', 'Ы', 'Й', 'Υ', 'Ϋ', 'Ŷ'], 511 | 'Z' => ['Ź', 'Ž', 'Ż', 'З', 'Ζ'], 512 | 'AE' => ['Ä', 'Æ', 'Ǽ'], 513 | 'CH' => ['Ч'], 514 | 'DJ' => ['Ђ'], 515 | 'DZ' => ['Џ'], 516 | 'GX' => ['Ĝ'], 517 | 'HX' => ['Ĥ'], 518 | 'IJ' => ['IJ'], 519 | 'JX' => ['Ĵ'], 520 | 'KH' => ['Х'], 521 | 'LJ' => ['Љ'], 522 | 'NJ' => ['Њ'], 523 | 'OE' => ['Ö', 'Œ'], 524 | 'PS' => ['Ψ'], 525 | 'SH' => ['Ш'], 526 | 'SHCH' => ['Щ'], 527 | 'SS' => ['ẞ'], 528 | 'TH' => ['Þ'], 529 | 'TS' => ['Ц'], 530 | 'UE' => ['Ü'], 531 | 'YA' => ['Я'], 532 | 'YU' => ['Ю'], 533 | 'ZH' => ['Ж'], 534 | ' ' => ["\xC2\xA0", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80"], 535 | ]; 536 | } 537 | 538 | /** 539 | * Make a string's first character uppercase. 540 | * 541 | * @param string $string 542 | * @return string 543 | */ 544 | public static function ucfirst($string) { 545 | return static::upper(static::substr($string, 0, 1)) . static::substr($string, 1); 546 | } 547 | 548 | /** 549 | * Convert the given string to upper-case. 550 | * 551 | * @param string $value 552 | * @return string 553 | */ 554 | public static function upper($value) { 555 | return mb_strtoupper($value, 'UTF-8'); 556 | } 557 | 558 | /** 559 | * Returns the portion of string specified by the start and length parameters. 560 | * 561 | * @param string $string 562 | * @param int $start 563 | * @param int|null $length 564 | * @return string 565 | */ 566 | public static function substr($string, $start, $length = null) { 567 | return mb_substr($string, $start, $length, 'UTF-8'); 568 | } 569 | } -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohroy/puck/975e8e03c633b4dc9923ca964e3ce5289a4cb6bf/docs/.nojekyll -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | puck.zz173.com -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # puck 2 | 3 | > 我是一个努力干活儿,还不黏人的小妖精。 4 | 5 | 如你所想,这个框架的名字的意思就是一个`妖精`,我最早得知于`仲夏夜之梦`的小精灵,同时,它也是`天王星`的一颗卫星。 6 | 然而这不是本文的重点,因为以上的典故我都不知道,我只知道它来自于`Dota`上面的`仙女龙`。 7 | 8 | 9 | > 近卫军团一直在寻找能够帮助他们对抗天灾的英雄,最终他们发现了生活在失落大陆上的神秘的仙女龙。在解释清楚同女王阿格瑞斯的冲突后,她决定派出她的贴身侍卫Puck来扭转整个战争的局势。虽然Puck身材纤细又调皮捣蛋,不过在战场上却有着足以证明自己的表现。能穿过所有敌人的爆炸性魔法球,仙女粉尘,以及以无与伦比的幻术麻痹所有敌人的能力,给所有和她对抗的敌人上了一课,所谓不可以貌取人。在他们面前出现的,是无穷无尽的欺骗。 10 | 11 | 12 | 13 | > 她们是由龙族和女精灵所生的后代,为龙族中唯一有女性的种族。 14 | 15 | 16 | 17 | 18 | 19 | # 过期声明 20 | 21 | !> 此文档针对`puck 0.*`,目前现行版本已经升级,故此文档仅供参考。 -------------------------------------------------------------------------------- /docs/_asset/puck.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_asset/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.proxy.ustclug.org/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600'); 2 | 3 | *{ 4 | -webkit-font-smoothing: antialiased; 5 | -webkit-overflow-scrolling: touch; 6 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 7 | -webkit-text-size-adjust: none; 8 | -webkit-touch-callout: none; 9 | box-sizing: border-box; 10 | } 11 | 12 | body:not(.ready){ 13 | overflow: hidden; 14 | } 15 | 16 | body:not(.ready) [data-cloak], body:not(.ready) nav{ 17 | display: none; 18 | } 19 | 20 | div#app{ 21 | font-size: 30px; 22 | font-weight: lighter; 23 | margin: 40vh auto; 24 | text-align: center; 25 | } 26 | 27 | div#app:empty::before{ 28 | content: "Loading..."; 29 | } 30 | 31 | .emoji{ 32 | height: 1.2em; 33 | vertical-align: middle; 34 | } 35 | 36 | .progress{ 37 | background-color: #ea6f5a; 38 | background-color: var(--theme-color, #ea6f5a); 39 | height: 2px; 40 | left: 0px; 41 | position: fixed; 42 | right: 0px; 43 | top: 0px; 44 | -webkit-transition: width 0.2s, opacity 0.4s; 45 | transition: width 0.2s, opacity 0.4s; 46 | width: 0%; 47 | z-index: 999999; 48 | } 49 | 50 | .search a:hover{ 51 | color: #ea6f5a; 52 | color: var(--theme-color, #ea6f5a); 53 | } 54 | 55 | .search .search-keyword{ 56 | color: #ea6f5a; 57 | color: var(--theme-color, #ea6f5a); 58 | font-style: normal; 59 | } 60 | 61 | html, body{ 62 | height: 100%; 63 | } 64 | 65 | body{ 66 | -moz-osx-font-smoothing: grayscale; 67 | -webkit-font-smoothing: antialiased; 68 | color: #c8c8c8; 69 | font-family: 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif; 70 | font-size: 15px; 71 | letter-spacing: 0; 72 | margin: 0; 73 | overflow-x: hidden; 74 | } 75 | 76 | img{ 77 | max-width: 100%; 78 | } 79 | 80 | kbd{ 81 | border: solid 1px #ccc; 82 | border-radius: 3px; 83 | display: inline-block; 84 | font-size: 12px !important; 85 | line-height: 12px; 86 | margin-bottom: 3px; 87 | padding: 3px 5px; 88 | vertical-align: middle; 89 | } 90 | 91 | /* navbar */ 92 | 93 | nav.app-nav{ 94 | left: 0; 95 | margin: 25px 60px 0 0; 96 | position: absolute; 97 | right: 0; 98 | text-align: right; 99 | z-index: 10; 100 | } 101 | 102 | nav.app-nav p{ 103 | margin: 0; 104 | } 105 | 106 | nav.app-nav >a{ 107 | margin: 0 1em; 108 | padding: 5px 0; 109 | } 110 | 111 | nav.app-nav ul, nav.app-nav li{ 112 | display: inline-block; 113 | list-style: none; 114 | margin: 0; 115 | } 116 | 117 | nav.app-nav a{ 118 | color: inherit; 119 | font-size: 16px; 120 | text-decoration: none; 121 | -webkit-transition: color .3s; 122 | transition: color .3s; 123 | } 124 | 125 | nav.app-nav a:hover{ 126 | color: #ea6f5a; 127 | color: var(--theme-color, #ea6f5a); 128 | } 129 | 130 | nav.app-nav a.active{ 131 | border-bottom: 2px solid #ea6f5a; 132 | border-bottom: 2px solid var(--theme-color, #ea6f5a); 133 | color: #ea6f5a; 134 | color: var(--theme-color, #ea6f5a); 135 | } 136 | 137 | /* navbar dropdown */ 138 | 139 | nav.app-nav li{ 140 | display: inline-block; 141 | margin: 0 1em; 142 | padding: 5px 0; 143 | position: relative; 144 | } 145 | 146 | nav.app-nav li ul{ 147 | background-color: #fff; 148 | border: 1px solid #ddd; 149 | border-bottom-color: #ccc; 150 | border-radius: 4px; 151 | box-sizing: border-box; 152 | display: none; 153 | max-height: calc(100vh - 61px); 154 | overflow-y: scroll; 155 | padding: 10px 0; 156 | position: absolute; 157 | right: -15px; 158 | text-align: left; 159 | top: 100%; 160 | white-space: nowrap; 161 | } 162 | 163 | nav.app-nav li ul li{ 164 | display: block; 165 | font-size: 14px; 166 | line-height: 1em; 167 | margin: 0; 168 | margin: 8px 14px; 169 | white-space: nowrap; 170 | } 171 | 172 | nav.app-nav li ul a{ 173 | display: block; 174 | font-size: inherit; 175 | margin: 0; 176 | padding: 0; 177 | } 178 | 179 | nav.app-nav li ul a.active{ 180 | border-bottom: 0; 181 | } 182 | 183 | nav.app-nav li:hover ul{ 184 | display: block; 185 | } 186 | 187 | nav.app-nav.no-badge{ 188 | margin-right: 25px; 189 | } 190 | 191 | /* github corner */ 192 | 193 | .github-corner{ 194 | border-bottom: 0; 195 | position: fixed; 196 | right: 0; 197 | text-decoration: none; 198 | top: 0; 199 | z-index: 1; 200 | } 201 | 202 | .github-corner svg{ 203 | color: #3f3f3f; 204 | fill: #ea6f5a; 205 | fill: var(--theme-color, #ea6f5a); 206 | height: 80px; 207 | width: 80px; 208 | } 209 | 210 | .github-corner:hover .octo-arm{ 211 | -webkit-animation: octocat-wave 560ms ease-in-out; 212 | animation: octocat-wave 560ms ease-in-out; 213 | } 214 | 215 | /* main */ 216 | 217 | main{ 218 | display: block; 219 | position: relative; 220 | width: 100vw; 221 | height: 100%; 222 | } 223 | 224 | .anchor{ 225 | display: inline-block; 226 | text-decoration: none; 227 | -webkit-transition: all .3s; 228 | transition: all .3s; 229 | } 230 | 231 | .anchor span{ 232 | color: #c8c8c8; 233 | } 234 | 235 | .anchor:hover{ 236 | text-decoration: underline; 237 | } 238 | 239 | /* sidebar */ 240 | 241 | .sidebar{ 242 | border-right: 1px solid rgba(0, 0, 0, .07); 243 | overflow-y: auto; 244 | padding: 40px 0; 245 | top: 0; 246 | bottom: 0; 247 | left: 0; 248 | position: absolute; 249 | -webkit-transition: -webkit-transform 250ms ease-out; 250 | transition: -webkit-transform 250ms ease-out; 251 | transition: transform 250ms ease-out; 252 | transition: transform 250ms ease-out, -webkit-transform 250ms ease-out; 253 | width: 300px; 254 | z-index: 20; 255 | } 256 | 257 | .sidebar > h1{ 258 | margin: 0 auto 1em; 259 | font-size: 1.5em; 260 | font-weight: 300; 261 | text-align: center; 262 | } 263 | 264 | .sidebar > h1 a{ 265 | color: inherit; 266 | text-decoration: none; 267 | } 268 | 269 | .sidebar ul{ 270 | margin: 0; 271 | padding: 0; 272 | } 273 | 274 | .sidebar li>p{ 275 | font-weight: 700; 276 | margin: 0; 277 | } 278 | 279 | .sidebar ul, .sidebar ul li{ 280 | list-style: none; 281 | } 282 | 283 | .sidebar ul li a{ 284 | border-bottom: none; 285 | display: block; 286 | } 287 | 288 | .sidebar ul li ul{ 289 | padding-left: 20px; 290 | } 291 | 292 | .sidebar::-webkit-scrollbar{ 293 | width: 4px; 294 | } 295 | 296 | .sidebar::-webkit-scrollbar-thumb{ 297 | background: transparent; 298 | border-radius: 4px; 299 | } 300 | 301 | .sidebar:hover::-webkit-scrollbar-thumb{ 302 | background: rgba(136, 136, 136, 0.4); 303 | } 304 | 305 | .sidebar:hover::-webkit-scrollbar-track{ 306 | background: rgba(136, 136, 136, 0.1); 307 | } 308 | 309 | /* sidebar toggle */ 310 | 311 | .sidebar-toggle{ 312 | background-color: transparent; 313 | background-color: rgba(63, 63, 63, 0.8); 314 | border: 0; 315 | outline: none; 316 | outline: none; 317 | padding: 10px; 318 | bottom: 0; 319 | left: 0; 320 | position: absolute; 321 | text-align: center; 322 | -webkit-transition: opacity .3s; 323 | transition: opacity .3s; 324 | width: 30px; 325 | width: 284px; 326 | z-index: 30; 327 | } 328 | 329 | .sidebar-toggle .sidebar-toggle-button:hover{ 330 | opacity: .4; 331 | } 332 | 333 | .sidebar-toggle span{ 334 | background-color: #ea6f5a; 335 | background-color: var(--theme-color, #ea6f5a); 336 | display: block; 337 | margin-bottom: 4px; 338 | width: 16px; 339 | height: 2px; 340 | } 341 | 342 | body.sticky .sidebar, body.sticky .sidebar-toggle{ 343 | position: fixed; 344 | } 345 | 346 | /* main content */ 347 | 348 | .content{ 349 | padding-top: 20px; 350 | top: 0; 351 | right: 0; 352 | bottom: 0; 353 | left: 300px; 354 | position: absolute; 355 | -webkit-transition: left 250ms ease; 356 | transition: left 250ms ease; 357 | } 358 | 359 | /* markdown content found on pages */ 360 | 361 | .markdown-section{ 362 | margin: 0 auto; 363 | max-width: 800px; 364 | padding: 20px 15px 40px 15px; 365 | position: relative; 366 | } 367 | 368 | .markdown-section > *{ 369 | box-sizing: border-box; 370 | font-size: inherit; 371 | } 372 | 373 | .markdown-section >:first-child{ 374 | margin-top: 0!important; 375 | } 376 | 377 | .markdown-section hr{ 378 | border: none; 379 | border-bottom: 1px solid #eee; 380 | margin: 2em 0; 381 | } 382 | 383 | .markdown-section table{ 384 | border-collapse: collapse; 385 | border-spacing: 0; 386 | display: block; 387 | margin-bottom: 1em; 388 | overflow: auto; 389 | width: 100%; 390 | } 391 | 392 | .markdown-section th{ 393 | border: 1px solid #ddd; 394 | font-weight: 700; 395 | padding: 6px 13px; 396 | } 397 | 398 | .markdown-section td{ 399 | border: 1px solid #ddd; 400 | padding: 6px 13px; 401 | } 402 | 403 | .markdown-section tr{ 404 | border-top: 1px solid #ccc; 405 | } 406 | 407 | .markdown-section tr:nth-child(2n){ 408 | background-color: #f8f8f8; 409 | } 410 | 411 | .markdown-section p.tip{ 412 | background-color: #f8f8f8; 413 | border-bottom-right-radius: 2px; 414 | border-left: 4px solid #f66; 415 | border-top-right-radius: 2px; 416 | margin: 2em 0; 417 | padding: 12px 24px 12px 30px; 418 | position: relative; 419 | } 420 | 421 | .markdown-section p.tip code{ 422 | background-color: #efefef; 423 | } 424 | 425 | .markdown-section p.tip em{ 426 | color: #c8c8c8; 427 | } 428 | 429 | .markdown-section p.tip:before{ 430 | background-color: #f66; 431 | border-radius: 100%; 432 | color: #3f3f3f; 433 | content: "!"; 434 | font-family: 'Dosis', 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif; 435 | font-size: 14px; 436 | font-weight: 700; 437 | left: -12px; 438 | line-height: 20px; 439 | position: absolute; 440 | width: 20px; 441 | height: 20px; 442 | text-align: center; 443 | top: 14px; 444 | } 445 | 446 | .markdown-section p.warn{ 447 | background: rgba(234, 111, 90, 0.1); 448 | border-radius: 2px; 449 | padding: 1em; 450 | } 451 | 452 | body.close .sidebar{ 453 | -webkit-transform: translateX(-300px); 454 | transform: translateX(-300px); 455 | } 456 | 457 | body.close .sidebar-toggle{ 458 | width: auto; 459 | } 460 | 461 | body.close .content{ 462 | left: 0; 463 | } 464 | 465 | @media (max-width: 600px){ 466 | 467 | .github-corner, .sidebar-toggle, .sidebar{ 468 | position: fixed; 469 | } 470 | 471 | nav{ 472 | margin-top: 16px; 473 | } 474 | 475 | nav li ul{ 476 | top: 30px; 477 | } 478 | 479 | main{ 480 | height: auto; 481 | overflow-x: hidden; 482 | } 483 | 484 | .sidebar{ 485 | left: -300px; 486 | -webkit-transition: -webkit-transform 250ms ease-out; 487 | transition: -webkit-transform 250ms ease-out; 488 | transition: transform 250ms ease-out; 489 | transition: transform 250ms ease-out, -webkit-transform 250ms ease-out; 490 | } 491 | 492 | .content{ 493 | left: 0; 494 | max-width: 100vw; 495 | position: static; 496 | -webkit-transition: -webkit-transform 250ms ease; 497 | transition: -webkit-transform 250ms ease; 498 | transition: transform 250ms ease; 499 | transition: transform 250ms ease, -webkit-transform 250ms ease; 500 | } 501 | 502 | nav, .github-corner{ 503 | -webkit-transition: -webkit-transform 250ms ease-out; 504 | transition: -webkit-transform 250ms ease-out; 505 | transition: transform 250ms ease-out; 506 | transition: transform 250ms ease-out, -webkit-transform 250ms ease-out; 507 | } 508 | 509 | .sidebar-toggle{ 510 | background-color: transparent; 511 | width: auto; 512 | } 513 | 514 | body.close .sidebar{ 515 | -webkit-transform: translateX(300px); 516 | transform: translateX(300px); 517 | } 518 | 519 | body.close .sidebar-toggle{ 520 | background-color: rgba(63, 63, 63, 0.8); 521 | -webkit-transition: 1s background-color; 522 | transition: 1s background-color; 523 | width: 284px; 524 | } 525 | 526 | body.close .content{ 527 | -webkit-transform: translateX(300px); 528 | transform: translateX(300px); 529 | } 530 | 531 | body.close nav, body.close .github-corner{ 532 | display: none; 533 | } 534 | 535 | .github-corner .octo-arm{ 536 | -webkit-animation: octocat-wave 560ms ease-in-out; 537 | animation: octocat-wave 560ms ease-in-out; 538 | } 539 | 540 | .github-corner:hover .octo-arm{ 541 | -webkit-animation: none; 542 | animation: none; 543 | } 544 | } 545 | 546 | @-webkit-keyframes octocat-wave{ 547 | 548 | 0%, 100%{ 549 | -webkit-transform: rotate(0); 550 | transform: rotate(0); 551 | } 552 | 553 | 20%, 60%{ 554 | -webkit-transform: rotate(-25deg); 555 | transform: rotate(-25deg); 556 | } 557 | 558 | 40%, 80%{ 559 | -webkit-transform: rotate(10deg); 560 | transform: rotate(10deg); 561 | } 562 | } 563 | 564 | @keyframes octocat-wave{ 565 | 566 | 0%, 100%{ 567 | -webkit-transform: rotate(0); 568 | transform: rotate(0); 569 | } 570 | 571 | 20%, 60%{ 572 | -webkit-transform: rotate(-25deg); 573 | transform: rotate(-25deg); 574 | } 575 | 576 | 40%, 80%{ 577 | -webkit-transform: rotate(10deg); 578 | transform: rotate(10deg); 579 | } 580 | } 581 | section.cover{ 582 | -webkit-box-align: center; 583 | -ms-flex-align: center; 584 | align-items: center; 585 | background-position: center center; 586 | background-repeat: no-repeat; 587 | background-size: cover; 588 | height: 100vh; 589 | display: none; 590 | } 591 | section.cover .cover-main{ 592 | -webkit-box-flex: 1; 593 | -ms-flex: 1; 594 | flex: 1; 595 | margin: -20px 16px 0; 596 | text-align: center; 597 | z-index: 1; 598 | } 599 | section.cover a{ 600 | color: inherit; 601 | text-decoration: none; 602 | } 603 | section.cover a:hover{ 604 | text-decoration: none; 605 | } 606 | section.cover p{ 607 | line-height: 24px; 608 | line-height: 1.5rem; 609 | margin: 1em 0; 610 | } 611 | section.cover h1{ 612 | color: inherit; 613 | font-size: 40px; 614 | font-size: 2.5rem; 615 | font-weight: 300; 616 | margin: 10px 0 40px; 617 | margin: .625rem 0 2.5rem; 618 | position: relative; 619 | text-align: center; 620 | } 621 | section.cover h1 a{ 622 | display: block; 623 | } 624 | section.cover h1 small{ 625 | bottom: -7px; 626 | bottom: -.4375rem; 627 | font-size: 16px; 628 | font-size: 1rem; 629 | position: absolute; 630 | } 631 | section.cover blockquote{ 632 | font-size: 24px; 633 | font-size: 1.5rem; 634 | text-align: center; 635 | } 636 | section.cover ul{ 637 | line-height: 1.8; 638 | list-style-type: none; 639 | margin: 1em auto; 640 | max-width: 500px; 641 | padding: 0; 642 | } 643 | section.cover .cover-main > p:last-child a{ 644 | border-color: #ea6f5a; 645 | border-color: var(--theme-color, #ea6f5a); 646 | border-radius: 2em; 647 | border-style: solid; 648 | border-width: 1px; 649 | box-sizing: border-box; 650 | color: #ea6f5a; 651 | color: var(--theme-color, #ea6f5a); 652 | display: inline-block; 653 | font-size: 1.05em; 654 | letter-spacing: 0.1em; 655 | margin-right: 1em; 656 | padding: 0.75em 2em; 657 | text-decoration: none; 658 | -webkit-transition: all 0.15s ease; 659 | transition: all 0.15s ease; 660 | } 661 | section.cover .cover-main > p:last-child a:last-child{ 662 | background-color: #ea6f5a; 663 | background-color: var(--theme-color, #ea6f5a); 664 | color: #fff; 665 | margin-right: 0; 666 | } 667 | section.cover .cover-main > p:last-child a:last-child:hover{ 668 | color: inherit; 669 | opacity: .8; 670 | } 671 | section.cover .cover-main > p:last-child a:hover{ 672 | color: inherit; 673 | } 674 | section.cover blockquote > p > a{ 675 | border-bottom: 2px solid #ea6f5a; 676 | border-bottom: 2px solid var(--theme-color, #ea6f5a); 677 | -webkit-transition: color .3s; 678 | transition: color .3s; 679 | } 680 | section.cover blockquote > p > a:hover{ 681 | color: #ea6f5a; 682 | color: var(--theme-color, #ea6f5a); 683 | } 684 | section.cover.show{ 685 | display: -webkit-box; 686 | display: -ms-flexbox; 687 | display: flex; 688 | } 689 | section.cover.has-mask .mask{ 690 | background-color: #3f3f3f; 691 | opacity: .8; 692 | position: absolute; 693 | width: 100%; 694 | height: 100%; 695 | } 696 | 697 | body { 698 | background-color: #3f3f3f; 699 | } 700 | 701 | /* sidebar */ 702 | .sidebar { 703 | background-color: #3f3f3f; 704 | color: #c8c8c8; 705 | } 706 | .sidebar li { 707 | margin: 6px 15px; 708 | } 709 | .sidebar ul li a { 710 | color: #c8c8c8; 711 | font-size: 14px; 712 | overflow: hidden; 713 | text-decoration: none; 714 | text-overflow: ellipsis; 715 | white-space: nowrap 716 | } 717 | .sidebar ul li a:hover{ 718 | text-decoration: underline; 719 | } 720 | .sidebar ul li ul { 721 | padding: 0; 722 | } 723 | .sidebar ul li.active>a { 724 | color: #ea6f5a; 725 | color: var(--theme-color, #ea6f5a); 726 | font-weight: 600; 727 | } 728 | 729 | /* markdown content found on pages */ 730 | .markdown-section h1, .markdown-section h2, .markdown-section h3, .markdown-section h4, .markdown-section strong { 731 | color: #2c3e50; 732 | font-weight: 600; 733 | } 734 | 735 | .markdown-section a { 736 | color: #ea6f5a; 737 | color: var(--theme-color, #ea6f5a); 738 | font-weight: 600; 739 | } 740 | 741 | .markdown-section h1 { 742 | font-size: 2em; 743 | margin: 0 0 1em; 744 | } 745 | 746 | .markdown-section h2 { 747 | font-size: 1.75em; 748 | margin: 45px 0 0.8em; 749 | } 750 | 751 | .markdown-section h3 { 752 | font-size: 1.5em; 753 | margin: 40px 0 .6em; 754 | } 755 | 756 | .markdown-section h4 { 757 | font-size: 1.25em; 758 | } 759 | 760 | .markdown-section h5 { 761 | font-size: 1em; 762 | } 763 | 764 | .markdown-section h6 { 765 | color: #777; 766 | font-size: 1em; 767 | } 768 | 769 | .markdown-section figure, .markdown-section p, .markdown-section ul, .markdown-section ol { 770 | margin: 1.2em 0; 771 | } 772 | 773 | .markdown-section p, .markdown-section ul, .markdown-section ol { 774 | line-height: 1.6em; 775 | word-spacing: 0.05em; 776 | } 777 | 778 | .markdown-section ul, .markdown-section ol { 779 | padding-left: 1.5em; 780 | } 781 | 782 | .markdown-section blockquote { 783 | border-left: 4px solid #ea6f5a; 784 | border-left: 4px solid var(--theme-color, #ea6f5a); 785 | color: #858585; 786 | margin: 2em 0; 787 | padding-left: 20px; 788 | } 789 | 790 | .markdown-section blockquote p { 791 | font-weight: 600; 792 | margin-left: 0; 793 | } 794 | 795 | .markdown-section iframe { 796 | margin: 1em 0; 797 | } 798 | 799 | .markdown-section em { 800 | color: #7f8c8d; 801 | } 802 | 803 | .markdown-section code { 804 | background-color: #282828; 805 | border-radius: 2px; 806 | color: #657b83; 807 | font-family: 'Roboto Mono', Monaco, courier, monospace; 808 | font-size: 0.8em; 809 | margin: 0 2px; 810 | padding: 3px 5px; 811 | white-space: nowrap; 812 | } 813 | 814 | .markdown-section pre { 815 | -moz-osx-font-smoothing: initial; 816 | -webkit-font-smoothing: initial; 817 | background-color: #282828; 818 | font-family: 'Roboto Mono', Monaco, courier, monospace; 819 | line-height: 1.5em; 820 | margin: 1.2em 0; 821 | overflow: auto; 822 | padding: 0 1.4em; 823 | position: relative; 824 | word-wrap: normal; 825 | } 826 | 827 | /* code highlight */ 828 | .token.comment, .token.prolog, .token.doctype, .token.cdata { 829 | color: #8e908c; 830 | } 831 | 832 | .token.namespace { 833 | opacity: .7; 834 | } 835 | 836 | .token.boolean, .token.number { 837 | color: #c76b29; 838 | } 839 | 840 | .token.punctuation { 841 | color: #525252; 842 | } 843 | 844 | .token.property { 845 | color: #c08b30; 846 | } 847 | 848 | .token.tag { 849 | color: #2973b7; 850 | } 851 | 852 | .token.string { 853 | color: #ea6f5a; 854 | color: var(--theme-color, #ea6f5a); 855 | } 856 | 857 | .token.selector { 858 | color: #6679cc; 859 | } 860 | 861 | .token.attr-name { 862 | color: #2973b7; 863 | } 864 | 865 | .token.entity, .token.url, .language-css .token.string, .style .token.string { 866 | color: #22a2c9; 867 | } 868 | 869 | .token.attr-value, .token.control, .token.directive, .token.unit { 870 | color: #ea6f5a; 871 | color: var(--theme-color, #ea6f5a); 872 | } 873 | 874 | .token.keyword { 875 | color: #e96900; 876 | } 877 | 878 | .token.statement, .token.regex, .token.atrule { 879 | color: #22a2c9; 880 | } 881 | 882 | .token.placeholder, .token.variable { 883 | color: #3d8fd1; 884 | } 885 | 886 | .token.deleted { 887 | text-decoration: line-through; 888 | } 889 | 890 | .token.inserted { 891 | border-bottom: 1px dotted #202746; 892 | text-decoration: none; 893 | } 894 | 895 | .token.italic { 896 | font-style: italic; 897 | } 898 | 899 | .token.important, .token.bold { 900 | font-weight: 700; 901 | } 902 | 903 | .token.important { 904 | color: #c94922; 905 | } 906 | 907 | .token.entity { 908 | cursor: help; 909 | } 910 | 911 | .markdown-section pre>code { 912 | -moz-osx-font-smoothing: initial; 913 | -webkit-font-smoothing: initial; 914 | background-color: #282828; 915 | border-radius: 2px; 916 | color: #657b83; 917 | display: block; 918 | font-family: 'Roboto Mono', Monaco, courier, monospace; 919 | font-size: 0.8em; 920 | line-height: inherit; 921 | margin: 0 2px; 922 | max-width: inherit; 923 | overflow: inherit; 924 | padding: 2.2em 5px; 925 | white-space: inherit; 926 | } 927 | 928 | .markdown-section code::after, .markdown-section code::before { 929 | letter-spacing: 0.05em; 930 | } 931 | 932 | code .token { 933 | -moz-osx-font-smoothing: initial; 934 | -webkit-font-smoothing: initial; 935 | min-height: 1.5em; 936 | } 937 | 938 | pre::after { 939 | color: #ccc; 940 | content: attr(data-lang); 941 | font-size: 0.6em; 942 | font-weight: 600; 943 | height: 15px; 944 | line-height: 15px; 945 | padding: 5px 10px 0; 946 | position: absolute; 947 | right: 0; 948 | text-align: right; 949 | top: 0; 950 | } 951 | 952 | .markdown-section p.tip { 953 | background-color: #282828; 954 | color: #657b83; 955 | } 956 | 957 | input[type="search"] { 958 | background: #4f4f4f; 959 | border-color: #4f4f4f; 960 | color: #c8c8c8; 961 | } -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | ![logo](_asset/puck.svg) 2 | 3 | # puck 0.1.2 4 | 5 | > A magical php framework 6 | 7 | - 轻量的,袖珍的 8 | - 贴合性能需求的 9 | - 逻辑和功能分离的 10 | 11 | 12 | [GitHub](https://github.com/rozbo/puck) 13 | [Get Started](#puck) -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - 入门 2 | - [啰嗦一番](/preface) 3 | - [依赖注入](/di) 4 | - [快速开始](/quickstart) 5 | - [大体了解](/formind) 6 | - 进阶 7 | - [路由](/router) 8 | - [控制器](/controller) 9 | - [配置](/config) 10 | - [数据库](/db) 11 | - [模型](/model) 12 | - [视图](/view) 13 | - [验证](/validate) 14 | - [异常](/exception) 15 | -  拓展 16 | - [拼音转换](/pinyin) 17 | - [字符打乱](/chaos) 18 | - [网络请求](/curl) 19 | - [DOM解析](/dom) -------------------------------------------------------------------------------- /docs/chaos.md: -------------------------------------------------------------------------------- 1 | ## 字符打乱 2 | `puck`可以支持生成添加干扰符号,字符码等的变形字体,以实现个性的效果。  3 | 4 | ## 用法 5 | 6 | ### 基本用法 7 | ```php 8 | use puck\helpers\Chaos; 9 | $s='你好'; 10 | $chaos = new Chaos(); 11 | $prefix=[ 12 | '[微笑]', 13 | '[大笑]', 14 | '[色]', 15 | '[失望]', 16 | '[皱眉]', 17 | '[哭]', 18 | '[惊恐]', 19 | '[亲亲]', 20 | '[大眼]', 21 | '[抓狂]', 22 | '[脸红]', 23 | '[书呆子]', 24 | '[可爱]', 25 | '[吐舌]', 26 | '[眨眼]', 27 | '[好吃]', 28 | '[天使]', 29 | '[得意]', 30 | '[讽刺]', 31 | '[花痴]', 32 | '[惊讶]' 33 | ]; 34 | $chaos->addPrefix($prefix); 35 | echo $chaos->get($s); 36 | //[惊讶]ni好 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/config.md: -------------------------------------------------------------------------------- 1 | # 配置读写 2 | 3 | ?> 待维护。。。 4 | -------------------------------------------------------------------------------- /docs/controller.md: -------------------------------------------------------------------------------- 1 | # 控制器 2 | 控制器作为代码和逻辑主要承载,框架本身对其约束很少。本质上,它就是一个用于被路由调用的类。 3 | 同时,它担负着与模型和视图交互的重要责任。 4 | 5 | ## 定义 6 | 7 | ```php 8 | get('https://www.baidu.com/'); 15 | if ($curl->error) { 16 | echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; 17 | } else { 18 | echo 'Response:' . "\n"; 19 | var_dump($curl->response); 20 | } 21 | ``` 22 | 如果请求出错(包括因为网络原因的,或者网站本身原因的,例如404,或者500等)。`error`成员会被置为`true`,同时`errorCode`则为错误代码,而`errorMessage`成员则是错误的详情。 23 | 24 | 如果一切正常,请求的返回结果则存放在`response`上。 25 | 26 | 值得一提的是,如果返回的`response`头部用`content-type`指明了页面内容类型,框架会自动的进行转化,例如返回头设置为类型为`application/json`,框架则自动会将内容转化为`对象`。同理,如果是`application/xml`,框架则自动转化`xml`对象。 27 | 而不是其本身。 28 | 29 | ## 参数提交 30 | 31 | 当`get`请求的网址有复杂的参数时,虽然仍然可以拼接一个很长的字符串到`url`上,但是总归不够优雅。框架支持传入一个数组类型的参数。 32 | 33 | ```php 34 | $curl = app('curl'); 35 | $curl->get('https://www.baidu.com/search',[ 36 | 'q' => 'keyword', 37 | ]); 38 | ``` 39 | 40 | ## restfull支持 41 | 42 | 如你所想,它当然不止支持`get`请求,`post`请求甚至`put`请求或者`delete`等都不在话下。 43 | 44 | ```php 45 | $curl = app('curl'); 46 | $curl->put('https://api.example.com/user/', array( 47 | 'first_name' => 'Zach', 48 | 'last_name' => 'Borboa', 49 | )); 50 | 51 | $curl->patch('https://api.example.com/profile/', array( 52 | 'image' => '@path/to/file.jpg', 53 | )); 54 | 55 | $curl->patch('https://api.example.com/profile/', array( 56 | 'image' => new CURLFile('path/to/file.jpg'), 57 | )); 58 | 59 | 60 | $curl->delete('https://api.example.com/user/', array( 61 | 'id' => '1234', 62 | )); 63 | 64 | //下载文件 65 | $curl->download(https://down.example.com/xxx.zip, '/path/to/xxx.zip'); 66 | ``` 67 | 68 | ## 请求头设置 69 | 70 | 不同到网站,可能要求的请求头不一样。框架封装了这些方法。 71 | 72 | ```php 73 | $curl=app('curl'); 74 | //设置授权 75 | $curl->setBasicAuthentication('username', 'password'); 76 | $curl->setDigestAuthentication('username', 'password'); 77 | //设置user agent 78 | $curl->setUserAgent('MyUserAgent/0.0.1 (+https://www.example.com/bot.html)'); 79 | //设置来源页面 80 | $curl->setReferrer('https://www.example.com/url?url=https%3A%2F%2Fwww.example.com%2F'); 81 | //设置cookie 82 | $curl->setCookie('key', 'value'); 83 | ``` 84 | 发现了问题?`cookie`设置太麻烦?没有关系,框架支持多种设置cookie的方法 85 | 86 | ```php 87 | $curl=app('curl'); 88 | Curl::setCookie($key, $value) 89 | Curl::setCookieFile($cookie_file) 90 | Curl::setCookieJar($cookie_jar) 91 | Curl::setCookieString($string) 92 | Curl::setCookies($cookies) 93 | ``` 94 | 95 | 还不够?没有关系。框架支持通过`setHeader`来自定以设置http头。 96 | ```php 97 | $curl=app('curl'); 98 | $curl->setHeader('X-Requested-With', 'XMLHttpRequest'); 99 | //批量设置 100 | $headers=[ 101 | 'X-Requested-With'=>'XMLHttpRequest', 102 | 'xxx'=>'yyy' 103 | ]; 104 | $curl->setHeaders($headers) 105 | ``` 106 | 107 | ## 返回头获取 108 | 109 | that is easy! 110 | ```php 111 | $curl=app('curl'); 112 | $curl->get('https://www.example.com/'); 113 | 114 | if ($curl->error) { 115 | echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; 116 | } else { 117 | echo 'Response:' . "\n"; 118 | var_dump($curl->response); 119 | } 120 | //请求头 121 | var_dump($curl->requestHeaders); 122 | //响应头 123 | var_dump($curl->responseHeaders); 124 | ``` 125 | 126 | ## 自定义配置 127 | 128 | 除此之外,框架还支持更多的配置 129 | 130 | ```php 131 | $curl=app('curl'); 132 | //设置超时时间为5秒 133 | $curl->setConnectTimeout(5); 134 | //设置重试次数为5次 135 | $curl->retryCount=5; 136 | //设置json解码函数 137 | $curl->setJsonDecoder(myfunction) 138 | //设置xml解码函数 139 | $curl->setXmlDecoder(myfunction) 140 | ``` 141 | 142 | 如果这都满足不了你,好吧,还有更直接的! 143 | 不过,这可能要求你去查阅curl文档来进行设置了。 144 | 145 | 146 | ```php 147 | $curl=app('curl'); 148 | //直接设置curl选项 149 | $curl->setOpt($option, $value); 150 | //批量设置curl选项 151 | $curl->setOpts($options); 152 | ``` 153 | 154 | 如我们可以设置 155 | ```php 156 | $curl=app('curl'); 157 | $curl->setOpt(CURLOPT_ENCODING , 'gzip'); 158 | ``` 159 | 来解码服务端`gzip`压缩的网页。 160 | 161 | 可以设置 162 | ```php 163 | $curl=app('curl'); 164 | $curl->setOpt(CURLOPT_FOLLOWLOCATION, true); 165 | $curl->get('https://url.cn/xxxxx'); 166 | ``` 167 | 来跟随url跳转。 168 | 169 | ## 回调函数 170 | ```php 171 | $curl=app('curl'); 172 | //发送请求之前 173 | $curl->beforeSend('myfunction'); 174 | //进度回调 175 | $curl->progress('myfunction'); 176 | //完成回调 177 | $curl->complete('myfunction'); 178 | //成功回调 179 | $curl->success('myfunction'); 180 | //失败回调 181 | $curl->error('myfunction'); 182 | ``` -------------------------------------------------------------------------------- /docs/db.md: -------------------------------------------------------------------------------- 1 | # 数据库 2 | 3 | 本框架的数据库仅支持`mysqli`,由[PHP-MySQLi-Database-Class](https://github.com/joshcam/PHP-MySQLi-Database-Class)提供底层支持. 4 | 但框架对其进行了易用性封装,使其语法更容易被接受. 5 | 6 | ## 配置 7 | 只需要在`config/database.php`中直接返回一个数组即可. 8 | 9 | ```php 10 | 'localhost', 13 | 'db' => 'xxx', 14 | 'port' => 3306, 15 | 'username' => 'xxx', 16 | 'password' => 'xxx', 17 | 'charset' => 'utf8', 18 | 'prefix' => 'xx_' 19 | ]; 20 | ``` 21 | 22 | ## 基本使用 23 | 24 | ### 直接查询 25 | 这样简单来说就是直接执行sql语句,这里只是演示,在实际开发过程中,不建议这样做. 26 | 27 | ```php 28 | $users = $this->db->rawQuery('SELECT * from users where id >= ?', Array (10)); 29 | foreach ($users as $user) { 30 | print_r ($user); 31 | } 32 | ``` 33 | 同样的,我们可以直接返回查到的第一个结果 34 | 35 | ```php 36 | $users = $this->db->rawQueryOne('SELECT * from users where id >= ?', Array (10)); 37 | echo $user['login']; 38 | ``` 39 | 甚至,我们可以只要其中一个字段 40 | 41 | ```php 42 | $password = $this->db->rawQueryValue ('select password from users where id=? limit 1', Array(10)); 43 | echo "Password is {$password}"; 44 | ``` 45 | 更甚至,返回字段数组 46 | 47 | ```php 48 | $logins = $this->db->rawQueryValue ('select login from users limit 10'); 49 | foreach ($logins as $login) 50 | echo $login; 51 | ``` 52 | 53 | ### 返回对象 54 | 55 | ```php 56 | //默认情况下,返回一个关联数组 57 | $user = $this->db->rawQueryOne ('select * from users where id=?', Array(10)); 58 | echo $user['login']; 59 | 60 | //我们可以返回一个对象 61 | $user = $this->db->ObjectBuilder()->rawQueryOne ('select * from users where id=?', Array(10)); 62 | echo $user->login; 63 | ``` 64 | 65 | ### 返回json 66 | 更进一步地,我们可以直接返回一个`json`而不是数组,也不是对象. 67 | 68 | ```php 69 | //我们可以返回一个对象 70 | $user = $this->db->JsonBuilder()->rawQueryOne ('select * from users where id=?', Array(10)); 71 | echo $user; 72 | ``` 73 | ### 参数绑定 74 | 75 | ```php 76 | $params = Array(1, 'admin'); 77 | $users = $this->db->rawQuery("SELECT id, firstName, lastName FROM users WHERE id = ? AND login = ?", $params); 78 | print_r($users); 79 | 80 | // 关联查询 81 | $params = Array(10, 1, 10, 11, 2, 10); 82 | $q = "( 83 | SELECT a FROM t1 84 | WHERE a = ? AND B = ? 85 | ORDER BY a LIMIT ? 86 | ) UNION ( 87 | SELECT a FROM t2 88 | WHERE a = ? AND B = ? 89 | ORDER BY a LIMIT ? 90 | )"; 91 | $resutls = $this->db->rawQuery ($q, $params); 92 | print_r ($results); 93 | ``` 94 | 95 | ## 异常追踪 96 | 97 | ```php 98 | $this->db->where('login', 'admin')->update('users', ['firstName' => 'Jack']); 99 | 100 | if ($this->db->getLastErrno() === 0) 101 | echo 'Update succesfull'; 102 | else 103 | echo 'Update failed. Error: '. $this->db->getLastError(); 104 | ``` 105 | ## 性能统计 106 | 可以用来跟踪查询时间和sql语句等. 107 | 108 | ```php 109 | $this->db->setTrace (true); 110 | // As a second parameter it is possible to define prefix of the path which should be striped from filename 111 | // $this->db->setTrace (true, $_SERVER['SERVER_ROOT']); 112 | $this->db->get("users"); 113 | $this->db->get("test"); 114 | print_r ($this->db->trace); 115 | ``` 116 | 117 | 则它可能输出以下 118 | 119 | ```array 120 | [0] => Array 121 | ( 122 | [0] => SELECT * FROM t_users ORDER BY `id` ASC 123 | [1] => 0.0010669231414795 124 | [2] => MysqliDb->get() >> file "/avb/work/PHP-MySQLi-Database-Class/tests.php" line #151 125 | ) 126 | 127 | [1] => Array 128 | ( 129 | [0] => SELECT * FROM t_test 130 | [1] => 0.00069189071655273 131 | [2] => MysqliDb->get() >> file "/avb/work/PHP-MySQLi-Database-Class/tests.php" line #152 132 | ) 133 | 134 | ``` 135 | 136 | ## 辅助函数 137 | ### 自动重连 138 | ```php 139 | if (!$this->db->ping()) 140 | $this->db->connect() 141 | ``` 142 | ### 输出上条语句 143 | ```php 144 | $this->db->get('users'); 145 | echo "Last executed query was ". $this->db->getLastQuery(); 146 | ``` 147 | ### 检查表存在 148 | ```php 149 | if ($this->db->tableExists ('users')) 150 | echo "hooray"; 151 | ``` 152 | ### 转义字符 153 | 实际上它是封装的`mysqli_real_escape_string`. 154 | 155 | ```php 156 | $escaped = $this->db->escape ("' and 1=1"); 157 | ``` 158 | 159 | ## 增 160 | 161 | ### 普通插入 162 | ```php 163 | $data = Array ("login" => "admin", 164 | "firstName" => "John", 165 | "lastName" => 'Doe' 166 | ); 167 | $id = $this->db->insert ('users', $data); 168 | if($id) 169 | echo 'user was created. Id=' . $id; 170 | ``` 171 | 172 | ### 带函数的插入 173 | 174 | ```php 175 | $data = Array ( 176 | 'login' => 'admin', 177 | 'active' => true, 178 | 'firstName' => 'John', 179 | 'lastName' => 'Doe', 180 | 'password' => $this->db->func('SHA1(?)',Array ("secretpassword+salt")), 181 | // password = SHA1('secretpassword+salt') 182 | 'createdAt' => $this->db->now(), 183 | // createdAt = NOW() 184 | 'expires' => $this->db->now('+1Y') 185 | // expires = NOW() + interval 1 year 186 | // Supported intervals [s]econd, [m]inute, [h]hour, [d]day, [M]onth, [Y]ear 187 | ); 188 | 189 | $id = $this->db->insert ('users', $data); 190 | if ($id) 191 | echo 'user was created. Id=' . $id; 192 | else 193 | echo 'insert failed: ' . $this->db->getLastError(); 194 | ``` 195 | 196 | ### 重复键插入 197 | 经常遇到这样的情景,向一个表里插入一条数据,如果已经存在就更新一下,用程序实现麻烦而且在并发的时候可能会有问题,这时用mysql的DUPLICATE KEY 很方便. 198 | 199 | 比如 200 | ```mysql 201 | CREATE TABLE `q_user` ( 202 | `id` int(11) UNSIGNED NOT NULL primary key auto_increment, 203 | `login` varchar(32) NOT NULL UNIQUE KEY, 204 | `firstName` varchar(255) NOT NULL, 205 | `lastName` varchar(255) NOT NULL, 206 | `createdAt` int(11) UNSIGNED NOT NULL, 207 | `updatedAt` int(11) UNSIGNED NOT NULL 208 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 209 | ``` 210 | 这个数据表中,`id`为主键自增,`login`为`UNIQUE`唯一. 在此时如果想要实现存在修改,不存在则添加时,常规的方法是用 211 | php判断下是否存在,然后进行自己的一些逻辑. 212 | 这里我们直接使用`mysql`的`Duplicate`机制. 213 | 214 | ```php 215 | $data = Array ("login" => "admin", 216 | "firstName" => "John", 217 | "lastName" => 'Doe', 218 | "createdAt" => time(), 219 | "updatedAt" => time(), 220 | ); 221 | $updateColumns = Array ("updatedAt"); 222 | $lastInsertId = "id"; 223 | $this->$this->db->onDuplicate($updateColumns, $lastInsertId); 224 | $id = $this->db->insert ('user', $data); 225 | ``` 226 | 在上面这段代码中,第一次执行会成功的插入,第二次则是仅仅修改了`updatedAt`的值. 227 | 其生成的sql语句为 228 | 229 | ```mysql 230 | INSERT INTO q_user (`login`, `firstName`, `lastName`, `createdAt`, `updatedAt`) VALUES ('admin', 'John', 'Doe', '1483171123', '1483171123') ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID (id), `updatedAt` = '1483171123' 231 | ``` 232 | 233 | ### 批量插入 234 | 批量插入不同的字段. 235 | 236 | ```php 237 | $data = Array( 238 | Array ("login" => "admin", 239 | "firstName" => "John", 240 | "lastName" => 'Doe' 241 | ), 242 | Array ("login" => "other", 243 | "firstName" => "Another", 244 | "lastName" => 'User', 245 | "password" => "very_cool_hash" 246 | ) 247 | ); 248 | $ids = $this->db->insertMulti('users', $data); 249 | if(!$ids) { 250 | echo 'insert failed: ' . $this->db->getLastError(); 251 | } else { 252 | echo 'new users inserted with following id\'s: ' . implode(', ', $ids); 253 | } 254 | ``` 255 | 256 | 批量插入相同字段. 257 | 258 | ```php 259 | $data = Array( 260 | Array ("admin", "John", "Doe"), 261 | Array ("other", "Another", "User") 262 | ); 263 | $keys = Array("login", "firstName", "lastName"); 264 | 265 | $ids = $this->db->insertMulti('users', $data, $keys); 266 | if(!$ids) { 267 | echo 'insert failed: ' . $this->db->getLastError(); 268 | } else { 269 | echo 'new users inserted with following id\'s: ' . implode(', ', $ids); 270 | } 271 | ``` 272 | 273 | ## 替换 274 | [Replace()](https://dev.mysql.com/doc/refman/5.7/en/replace.html) 275 | 这个函数和`insert`的使用方法完全一致. 276 | **但是**,这里要对仅仅从里面上意思理解的小伙伴提个醒,`mysql`的所谓替换,可能和你理解的有些不一样. 277 | `mysql`在遇到数据冲突时,删掉了旧记录,再写入新记录,这是使用 REPLACE INTO 时最大的一个误区. 278 | 问题在此时出现了,写入新记录时不会将你期望的没有修改的写进去,而是不去管他.而这个字段就相当于`丢失`了. 279 | 这可能并非你的逻辑上需要的.更常见的需求是当存在`login`时,则更新某些个字段,而其他的保持不变. 280 | 而满足这以需求的则是上面着重的介绍的`ON DUPLICATE KEY UPDATE`. 281 | 也就是说,**如果是想存在时更新,不存在插入,请使用`onDuplicate`方法!** 282 | 283 | ## 导入 284 | 很多时候我们需要把数据从`csv`甚至`xml`中导入到数据库里. 285 | 针对于此,`puck`也提供了简单的方式 286 | 287 | ### 导入csv 288 | 289 | ```php 290 | $path_to_file = "/path/to/file.csv"; 291 | $this->db->loadData("users", $path_to_file); 292 | ``` 293 | 当然,这里还有一些选项,如果这个`csv`并不十分规范: 294 | 295 | ```php 296 | Array( 297 | "fieldChar" => ';', // 分割符 298 | "lineChar" => '\r\n', // 换行符 299 | "linesToIgnore" => 1 // 忽略开头的几行 300 | ); 301 | ``` 302 | 然后就可以 303 | 304 | ```php 305 | $options = Array("fieldChar" => ';', "lineChar" => '\r\n', "linesToIgnore" => 1); 306 | $this->db->loadData("users", "/path/to/file.csv", $options); 307 | ``` 308 | 309 | ### 导入xml 310 | 和上面一样,没有什么大的不同,除了一些细节 311 | 312 | ```php 313 | $path_to_file = "/path/to/file.xml"; 314 | $this->db->loadXML("users", $path_to_file); 315 | ``` 316 | 同样地,这里也有一些选项 317 | 318 | ```php 319 | Array( 320 | "linesToIgnore" => 0, //忽略开头的几行 321 | "rowTag" => "" // 入口标记 322 | ) 323 | ``` 324 | 然后,不出意外地 325 | 326 | ```php 327 | $options = Array("linesToIgnore" => 0, "rowTag" => ""): 328 | $path_to_file = "/path/to/file.xml"; 329 | $this->db->loadXML("users", $path_to_file, $options); 330 | 331 | ``` 332 | 333 | ## 更新 334 | 335 | ```php 336 | $data = Array ( 337 | 'firstName' => 'Bobby', 338 | 'lastName' => 'Tables', 339 | 'editCount' => $this->db->inc(2), 340 | // editCount = editCount + 2; 341 | 'active' => $this->db->not() 342 | // active = !active; 343 | ); 344 | $this->db->where ('id', 1); 345 | if ($this->db->update ('users', $data)) 346 | echo $this->db->count . ' records were updated'; 347 | else 348 | echo 'update failed: ' . $this->db->getLastError(); 349 | ``` 350 | 更新用起来非常的方便,也非常的简单. 351 | 352 | ## 查找 353 | 354 | ### 普通查找 355 | ```php 356 | //查找所有 357 | $users = $this->db->get('users'); 358 | //查找10个 359 | $users = $this->get('users', 10); 360 | ``` 361 | ### 查找指定列 362 | 363 | ```php 364 | $cols = Array ("id", "name", "email"); 365 | $users = $this->db->get ("users", null, $cols); 366 | if ($this->db->count > 0) 367 | foreach ($users as $user) { 368 | print_r ($user); 369 | } 370 | ``` 371 | ### 查找一行 372 | 373 | ```php 374 | $this->db->where ("id", 1); 375 | $user = $this->db->getOne ("users"); 376 | echo $user['id']; 377 | 378 | $stats = $this->db->getOne ("users", "sum(id), count(*) as cnt"); 379 | echo "total ".$stats['cnt']. "users found"; 380 | ``` 381 | 382 | ### 直接获取值 383 | 384 | ```php 385 | $count = $this->db->getValue ("users", "count(*)"); 386 | echo "{$count} users found"; 387 | ``` 388 | 389 | ### 获取多行到数组 390 | ```php 391 | $logins = $this->db->getValue ("users", "login", null); 392 | // select login from users 393 | $logins = $this->db->getValue ("users", "login", 5); 394 | // select login from users limit 5 395 | foreach ($logins as $login) 396 | echo $login; 397 | ``` 398 | 399 | ### 设置返回类型 400 | `puck`支持3中返回类型,分别是`数组`,`对象`,`json`,默认情况下,返回的结构是`数组`,但我们也可以返回其他的数据类型. 401 | 402 | ```php 403 | // Array return type 404 | $= $this->db->getOne("users"); 405 | echo $u['login']; 406 | // Object return type 407 | $u = $this->db->ObjectBuilder()->getOne("users"); 408 | echo $u->login; 409 | // Json return type 410 | $json = $this->db->JsonBuilder()->getOne("users"); 411 | ``` 412 | 413 | ### 分页查询 414 | 如果使用`paginate`而不是`get`进行的查询,则会自动进行分页. 415 | 416 | ```php 417 | $page = 1; 418 | // set page limit to 2 results per page. 20 by default 419 | $this->db->pageLimit = 2; 420 | $products = $this->db->arraybuilder()->paginate("products", $page); 421 | echo "showing $page out of " . $this->db->totalPages; 422 | ``` 423 | 424 | 其实还有另外一种分页的方式,就是利用mysql的`SQL_CALC_FOUND_ROWS`.它可以在limit的同时,顺便算以下不带 425 | limit的条数.这就意味着通常需要两条sql的分页,可以一条来完成. 426 | 但是这样做,将不会对查询结果缓存. 427 | 它适用与传统的`select count(*)`比较耗时的时候,另外计算数量肯定不划算.这时就可以用它来实现一次查询,获得想要的结果. 428 | 但也不要滥用它,比如以某主键或索引为条件的时候,这个时候如果直接`count(*)`是不需要扫描全表的,而用`SQL_CALC_FOUND_ROWS` 429 | 则会强制扫描全表,反而导致性能的下降. 430 | 431 | ```php 432 | $offset = 10; 433 | $count = 15; 434 | $users = $this->db->withTotalCount()->get('users', Array ($offset, $count)); 435 | echo "Showing {$count} from {$this->db->totalCount}"; 436 | ``` 437 | 438 | 439 | ### 结果转换 440 | 很多时候,返回的关联数组并不是我们想要的. 441 | 此时我们可以通过`map`方法将其转为自定义的`key=>value`格式. 442 | 443 | ```php 444 | $user = $this->db->map ('login')->ObjectBuilder()->getOne ('users', 'login, id'); 445 | Array 446 | ( 447 | [user1] => 1 448 | ) 449 | 450 | $user = $this->db->map ('login')->ObjectBuilder()->getOne ('users', 'id,login,createdAt'); 451 | Array 452 | ( 453 | [user1] => stdClass Object 454 | ( 455 | [id] => 1 456 | [login] => user1 457 | [createdAt] => 2015-10-22 22:27:53 458 | ) 459 | 460 | ) 461 | ``` 462 | 463 | ## 条件 464 | 465 | 用于条件查询的几个方法有`where()`,`orwhere()`,`having()`,`orHaving()` 分别用于各种场景.. 466 | 467 | where的两个参数分别列名和值,多个`where`的关系为`and`. 468 | 469 | ### 算术运算 470 | ```php 471 | $this->db->where ('id', 1); 472 | $this->db->where ('login', 'admin'); 473 | $results = $this->db->get ('users'); 474 | // 解析为: SELECT * FROM users WHERE id=1 AND login='admin'; 475 | ``` 476 | 477 | ```php 478 | $this->db->where ('id', 1); 479 | $this->db->having ('login', 'admin'); 480 | $results = $this->db->get ('users'); 481 | // 解析为:SELECT * FROM users WHERE id=1 HAVING login='admin'; 482 | ``` 483 | 如果想要两个列相等, 484 | 485 | ```php 486 | $this->db->where ('lastLogin', 'createdAt'); 487 | ``` 488 | 这个是错误的做法,因为会被解析为 489 | 490 | ```sql 491 | where (lastLogin='createdAt') 492 | ``` 493 | 这明显不是我们所期望的结果.正确做法应该是 494 | 495 | ```php 496 | $this->db->where ('lastLogin = createdAt'); 497 | $results = $this->db->get ('users'); 498 | // 解析为:SELECT * FROM users WHERE lastLogin = createdAt; 499 | ``` 500 | 如果想使用其他的运算符,比如`>`,`<`而非`==`,应该通过第三个参数传入 501 | 502 | ```php 503 | $this->db->where ('id', 50, ">="); 504 | // or $this->db->where ('id', Array ('>=' => 50)); 505 | $results = $this->db->get ('users'); 506 | // 解析为: SELECT * FROM users WHERE id >= 50; 507 | ``` 508 | 509 | ### BETWEEN / NOT BETWEEN 510 | 511 | ```php 512 | $this->db->where('id', Array (4, 20), 'BETWEEN'); 513 | // or $this->db->where ('id', Array ('BETWEEN' => Array(4, 20))); 514 | 515 | $results = $this->db->get('users'); 516 | // 解析为: SELECT * FROM users WHERE id BETWEEN 4 AND 20 517 | ``` 518 | 519 | ### IN / NOT IN 520 | 521 | ```php 522 | $this->db->where('id', Array(1, 5, 27, -1, 'd'), 'IN'); 523 | // or $this->db->where('id', Array( 'IN' => Array(1, 5, 27, -1, 'd') ) ); 524 | 525 | $results = $this->db->get('users'); 526 | // 解析为: SELECT * FROM users WHERE id IN (1, 5, 27, -1, 'd'); 527 | ``` 528 | 529 | ### 或 530 | 531 | ```php 532 | $this->db->where ('firstName', 'John'); 533 | $this->db->orWhere ('firstName', 'Peter'); 534 | $results = $this->db->get ('users'); 535 | // 解析为: SELECT * FROM users WHERE firstName='John' OR firstName='peter' 536 | ``` 537 | 538 | ### 是否为空 539 | 540 | ```php 541 | $this->db->where ("lastName", NULL, 'IS NOT'); 542 | $results = $this->db->get("users"); 543 | // 解析为: SELECT * FROM users where lastName IS NOT NULL 544 | ``` 545 | 546 | ### sql表达式 547 | 如果你是个急性子,并且对sql语法足够了解,你可以.... 548 | 549 | ```php 550 | $this->db->where ("id != companyId"); 551 | $this->db->where ("DATE(createdAt) = DATE(lastLogin)"); 552 | $results = $this->db->get("users"); 553 | ``` 554 | 555 | ### 变量绑定 556 | 如果你感觉上面那种方式太麻烦了,你还可以... 557 | 558 | ```php 559 | $this->db->where ("(id = ? or id = ?)", Array(6,2)); 560 | $this->db->where ("login","mike") 561 | $res = $this->db->get ("users"); 562 | // 解析为: SELECT * FROM users WHERE (id = 6 or id = 2) and login='mike'; 563 | ``` 564 | 565 | ## 删除 566 | 删除的方法非常简单 567 | 568 | ```php 569 | $this->db->where('id', 1); 570 | if($this->db->delete('users')) 571 | echo 'successfully deleted'; 572 | ``` 573 | ## 排序 574 | 其实就是`sql`标准语句的`order by`,并且支持多个排序条件,以调用先后顺序. 575 | 576 | ### 常规排序 577 | 常规排序就是我们常用的按照大小,asc码等. 578 | 579 | ```php 580 | $this->db->orderBy("id","asc"); 581 | $this->db->orderBy("login","Desc"); 582 | $this->db->orderBy("RAND ()"); 583 | $results = $this->db->get('users'); 584 | // 解析为: SELECT * FROM users ORDER BY id ASC,login DESC, RAND (); 585 | ``` 586 | ### 字段排序 587 | 有事我们可能需要按照某个字段的某些值排序.我们可以 588 | 589 | ```php 590 | $this->db->orderBy('userGroup', 'ASC', array('superuser', 'admin', 'users')); 591 | $this->db->get('users'); 592 | // 解析为: SELECT * FROM users ORDER BY FIELD (userGroup, 'superuser', 'admin', 'users') ASC; 593 | ``` 594 | 595 | ## 分组查询 596 | 分组就是标准`sql`语法的`group by`语句. 597 | 598 | ```php 599 | $this->db->groupBy ("name"); 600 | $results = $this->db->get ('users'); 601 | // 解析为: SELECT * FROM users GROUP BY name; 602 | ``` 603 | ## 联表查询 604 | 即标准`sql`语法中的`join`. 605 | 一般的, 606 | 607 | ```php 608 | $this->db->join("users u", "p.tenantID=u.tenantID", "LEFT"); 609 | $this->db->where("u.id", 6); 610 | $products = $this->db->get ("products p", null, "u.login, p.productName"); 611 | print_r ($products); 612 | ``` 613 | 614 | 多条件的, 615 | 616 | ```php 617 | $this->db->join("users u", "p.tenantID=u.tenantID", "LEFT"); 618 | $this->db->joinWhere("users u", "u.tenantID", 5); 619 | $products = $this->db->get ("products p", null, "u.login, p.productName"); 620 | print_r ($products); 621 | // 解析为: SELECT u.login, p.productName FROM products p LEFT JOIN users u ON (p.tenantID=u.tenantID AND u.tenantID = 5) 622 | ``` 623 | 624 | ```php 625 | $this->db->join("users u", "p.tenantID=u.tenantID", "LEFT"); 626 | $this->db->joinOrWhere("users u", "u.tenantID", 5); 627 | $products = $this->db->get ("products p", null, "u.name, p.productName"); 628 | print_r ($products); 629 | // 解析为: SELECT u.login, p.productName FROM products p LEFT JOIN users u ON (p.tenantID=u.tenantID OR u.tenantID = 5) 630 | ``` 631 | 632 | ## 子查询 633 | 子查询也是在开发过程中比较常用的功能. 634 | 635 | ### 筛选 636 | 637 | ```php 638 | $ids = $this->db->subQuery (); 639 | $ids->where ("qty", 2, ">"); 640 | $ids->get ("products", null, "userId"); 641 | 642 | $this->db->where ("id", $ids, 'in'); 643 | $res = $this->db->get ("users"); 644 | // 解析为: SELECT * FROM users WHERE id IN (SELECT userId FROM products WHERE qty > 2) 645 | ``` 646 | 647 | ### 插入 648 | 649 | ```php 650 | $userIdQ = $this->db->subQuery (); 651 | $userIdQ->where ("id", 6); 652 | $userIdQ->getOne ("users", "name"), 653 | 654 | $data = Array ( 655 | "productName" => "test product", 656 | "userId" => $userIdQ, 657 | "lastUpdated" => $this->db->now() 658 | ); 659 | $id = $this->db->insert ("products", $data); 660 | // 解析为: INSERT INTO PRODUCTS (productName, userId, lastUpdated) values ("test product", (SELECT name FROM users WHERE id = 6), NOW()); 661 | ``` 662 | 663 | ### 联表 664 | 665 | ```php 666 | $usersQ = $this->db->subQuery ("u"); 667 | $usersQ->where ("active", 1); 668 | $usersQ->get ("users"); 669 | 670 | $this->db->join($usersQ, "p.userId=u.id", "LEFT"); 671 | $products = $this->db->get ("products p", null, "u.login, p.productName"); 672 | print_r ($products); 673 | // SELECT u.login, p.productName FROM products p LEFT JOIN (SELECT * FROM t_users WHERE active = 1) u on p.userId=u.id; 674 | ``` 675 | 676 | ### 结果判断 677 | 678 | ```php 679 | $sub = $this->db->subQuery(); 680 | $sub->where("company", 'testCompany'); 681 | $sub->get ("users", null, 'userId'); 682 | $this->db->where (null, $sub, 'exists'); 683 | $products = $this->db->get ("products"); 684 | // SELECT * FROM products WHERE EXISTS (select userId from users where company='testCompany') 685 | ``` 686 | ## 存在检测 687 | 688 | 很多时候,我需要判断数据库里面符合某(些)个条件的记录是否存在,一般而言可能需要查询后,再自行判断. 689 | 本框架也提供了此类方法. 690 | 691 | 692 | ```php 693 | $this->db->where("user", $user); 694 | $this->db->where("password", md5($password)); 695 | if($this->db->has("users")) { 696 | return "You are logged"; 697 | } else { 698 | return "Wrong user/password"; 699 | } 700 | ``` 701 | 702 | ## 事务支持 703 | 这个显然是`innodb`所支持的特性了. 704 | 是一个复杂数据的必备了. 705 | 706 | ```php 707 | $this->db->startTransaction(); 708 | ... 709 | if (!$this->db->insert ('myTable', $insertData)) { 710 | //Error while saving, cancel new record 711 | $this->db->rollback(); 712 | } else { 713 | //OK 714 | $this->db->commit(); 715 | } 716 | ``` 717 | 718 | ## 锁表支持 719 | 框架支持`读`|`写`锁,可以自由地,灵活地,对表进行读写锁定和解锁. 720 | 但只有一件事你可能需要注意的是当你锁定了之后,一定要记得解锁.... 721 | 722 | 723 | ```php 724 | //锁定users的写入操作 725 | $this->db->setLockMethod("WRITE")->lock("users"); 726 | //解锁 727 | $this->db->unlock(); 728 | //批量锁定 729 | //同时锁定users log两个表的read操作 730 | $this->db->setLockMethod("READ")->lock(array("users", "log")); 731 | ``` 732 | 733 | 734 | ## 查询关键字 735 | 736 | mysql支持非常多的查询关键字,比如 `LOW PRIORITY` | `DELAYED` | `HIGH PRIORITY` | `IGNORE` 737 | 738 | ```php 739 | $this->db->setQueryOption ('LOW_PRIORITY')->insert ($table, $param); 740 | //INSERT LOW_PRIORITY INTO table ... 741 | ``` 742 | 743 | ```php 744 | $this->db->setQueryOption ('FOR UPDATE')->get ('users'); 745 | //SELECT * FROM USERS FOR UPDATE; 746 | ``` 747 | 748 | 当然,批量设置也是可以的. 749 | 750 | ```php 751 | $this->db->setQueryOption (Array('LOW_PRIORITY', 'IGNORE'))->insert ($table,$param); 752 | // INSERT LOW_PRIORITY IGNORE INTO table ... 753 | ``` 754 | 755 | ```php 756 | $this->db->setQueryOption ('SQL_NO_CACHE'); 757 | $this->db->get("users"); 758 | // SELECT SQL_NO_CACHE * FROM USERS; 759 | ``` 760 | 761 | 唯一可能需要注意的是,你可能需要了解一下这些关键字的作用. 762 | 763 | 764 | ## 连续操作 765 | 766 | 由于这些方法都返回了`$this`,所以我们可以将上述分布操作了进行连续 767 | 768 | ```php 769 | $results = $this->db 770 | ->where('id', 1) 771 | ->where('login', 'admin') 772 | ->get('users'); 773 | 774 | ``` -------------------------------------------------------------------------------- /docs/di.md: -------------------------------------------------------------------------------- 1 | # 依赖注入&容器 2 | 3 | 4 | ## 容器支持 5 | 6 | 与`laravel`等代化的框架一样,`puck`也使用容器来管理依赖解决耦合,在这种前提下,理解`依赖注入`,`容器`等概念就显得尤为重要。 7 | 至少对于一个有追求的程序员来说。 8 | 9 | 从1.0版本起,框架从底层引入了`容器`,并且框架本身的组件实例都由其来管理。 10 | 11 | 如果不了解依赖注入或者容器的概念的,强烈推荐查找资料,或阅读本框架源码来理解。 12 | 13 | ## 简单使用 14 | 15 | 框架在初始化时绑定了必要的核心类到容器中,但也仅仅是绑定,并没有初始化类本身。 16 | 你也可以自行绑定自己的类到容器中去。 17 | 18 | ```php 19 | app()->bind('myxxxz','\app\helpers\xxxz'); 20 | ``` 21 | 此后,当你要使用此类的时候,就可以通过 22 | 23 | ```php 24 | app('myxxxz') 25 | ``` 26 | 来获得这个类的实例,容器会自动初始化这个类。 27 | 28 | ## 依赖注入 29 | 30 | 上面所说的只是一个简单的用法,还远远体现不了依赖注入的优势--因为他没有依赖。 31 | 此处我们用另外一个例子来说明。假设我们现在的动作是`旅游`。而完成这个动作需要一个交通工具,即`旅游`依赖一个`车`,车又分为很多种,而每个车又依赖一个司机。 32 | 按照传统方式,我们想都不用想 33 | ```php 34 | $driver= new \app\drivers\Driver(); 35 | $car =new \app\cars\Car(); 36 | $action= new \app\actions\Travel($car,$driver); 37 | ``` 38 | 事实上,这种方式没有什么不好的,至少目前来说是这样的。好吧,我们只是想把他变的简单一些————依赖自动注入。我们想`Travel`这个类的时候,自动new它的依赖的类,然后传参数进去————就像`apt-get/yum/npm/pip/composer`等依赖管理工具一样,自动分析依赖并加载。我们唯一需要做的就是只关心动作,而忽略过程。 39 | 怎么办呢? 40 | 唯一一件需要做的事情就是需要更改Travel的构造函数,指明依赖参数的类型。 41 | 42 | ```php 43 | bind('travel','\app\actions\Travel'); 60 | app('travel')->go('dali'); 61 | ``` 62 | 这时,就能简单方便的去!大!理!了! 63 | 64 | ## 高级依赖注入 65 | 66 | 在上面的例子中,由于逻辑不够复杂,还不能体现依赖注入的方便之处,假使逻辑更加复杂,比如我们可以更换不同的交通工具,更换不同的司机,执行不同的动作等。 67 | 此时我们就要用php自身的接口`interface`来实现复杂的依赖注入。也可以通过后续的提供更换对象方法来实现,也可以通过闭包绑定来实现。这里以较为自由的闭包绑定为例。 68 | 69 | 70 | ```php 71 | app()->bind('travel',function(){ 72 | $driver=app('config')->get('driver','default_driver'); 73 | $car=app('config')->get('car','default_car'); 74 | return new \app\actions\Travel($driver,$car); 75 | }) 76 | ``` 77 | 78 | 同样地,这样实现了从配置文件里获取当前的司机和汽车,以后如果想更换,只需要更改配置文件即可。 79 | 进一步的,你当然也可以通过数据库进行控制。 80 | 81 | ## 批量绑定 82 | 上面的绑定都针对的一个类,但对于很多同一种类,如`model`。还要依次绑定,岂不是很累,很麻烦? 83 | 本框架原创的一种解决方案是和路由功能相似的`正则绑定`。 84 | 85 | ```php 86 | app()->regexBind('/^(\w+)_model$/',"\\app\\models\\\\$1"); 87 | ``` 88 | 这样即可实现 89 | ```php 90 | app('test_model');// \app\models\Test; 91 | app('user_model');// \app\models\User; 92 | app('detail_model');// \app\models\Detail; 93 | app('user_info_model');// \app\models\UserInfo; 94 | ``` 95 | 96 | -------------------------------------------------------------------------------- /docs/dom.md: -------------------------------------------------------------------------------- 1 | # dom解析 2 | 3 | 嘿嘿嘿。上一节我们说过了网络请求支持,这一节就说dom解析,你们是不是发现了什么? 4 | 5 | 框架支持类似于`jquery`的语法来对dom进行筛选,甚至修改或者添加。 6 | 7 | 框架在初始化时,已经注册过网络请求类的实例。我们可以使用 8 | ```php 9 | app('dom'); 10 | ``` 11 | 来获取它。 12 | 13 | 14 | ## 加载文件 15 | 16 | ```php 17 | //直接从本地文件加载 18 | app('dom')->loadHtmlFile('xxx.html'); 19 | //从url端加载文件 20 | app('dom')->loadHtmlFile('http://www.xxx.com/xxx.html'); 21 | //加载一个字符串格式的html 22 | $html = ' 23 | 24 | 25 | 26 | Document 27 | 28 | 29 | 30 | '; 31 | app('dom')->loadHtml($html); 32 | 33 | ``` 34 | 35 | ## 筛选 36 | 37 | 本来这是最复杂的一部分,但是这里不做详细的介绍了,相信大家都对jquery的元素筛选有所了解,当然,不了解的可以查下资料。 38 | 39 | ```php 40 | //解析给定html的第一个body元素为文本 41 | $test=app('dom')->loadHtml($html)->find('body')[0]->text(); 42 | //解析给定html的第一个body元素为元素对象 43 | $body=app('dom')->loadHtml($html)->find('body')[0]->html(); 44 | //这个元素能接着筛选,筛选所有类名为`post`的元素并遍历输出; 45 | $posts=$body->find('.post'); 46 | foreach($posts as $post) { 47 | echo $post->text(), "\n"; 48 | } 49 | ``` 50 | 51 | ## 更多筛选 52 | 框架像`jquery`那样支持通过`标签`,`类名`,`id`,`name`或者`属性`进行筛选,同样的还支持伪类中筛选。具体如下 53 | 54 | - 标签 55 | - 类名, ID, name,属性的值 56 | - 伪类: 57 | - first-, last-, nth-child 58 | - empty and not-empty 59 | - contains 60 | - has 61 | 62 | ```php 63 | 64 | // 通过元素筛选出所有的a标签 65 | $document->find('a'); 66 | 67 | // 混合筛选出 id = "foo" 并且 类名为 "bar" 的元素 68 | $document->find('#foo.bar'); 69 | 70 | // 筛选出属性有name的元素 71 | $document->find('[name]'); 72 | // 同样的 73 | $document->find('*[name]'); 74 | 75 | // 混合筛选,通过元素,及元素属性的指定值 76 | $document->find('input[name=foo]'); 77 | $document->find('input[name=\'bar\']'); 78 | $document->find('input[name="baz"]'); 79 | 80 | // 筛选出一个拥有data-开头的属性,并且它的值为foo 81 | $document->find('*[^data-=foo]'); 82 | 83 | // 筛选出所有和href=https开头的a元素 84 | $document->find('a[href^=https]'); 85 | 86 | // 所有src值的结尾为png的img元素 87 | $document->find('img[src$=png]'); 88 | 89 | // 所有href的值包含example.com的a元素 90 | $document->find('a[href*=example.com]'); 91 | 92 | // 查找class为foo的a元素的text 93 | $document->find('a.foo::text'); 94 | 95 | // 查找class为bar的a元素的href属性或者title属性的值 96 | $document->find('a.bar::attr(href|title)'); 97 | ``` 98 | 99 | ## 输出 100 | 101 | 可以通过 102 | `html`,`text`,`innerHtml`方法来输出对应的对象或者文本。 103 | 104 | -------------------------------------------------------------------------------- /docs/exception.md: -------------------------------------------------------------------------------- 1 | # 错误和调试 2 | 3 | `puck`使用`whoops`作为错误处理,这也是`Laravel4`默认集成的错误处理. 4 | 它拥有漂亮的界面,来作为一个有趣的提示页. 5 | 6 | 默认地,如果你使用`调试模式`,框架会自动启用`whoops`. 7 | 当程序发生异常时,便可以更改方便的调试. 8 | 9 | ![img](https://camo.githubusercontent.com/31a4e1410e740fd0ccda128cbcab8723f45e7e73/687474703a2f2f692e696d6775722e636f6d2f305651706539362e706e67) -------------------------------------------------------------------------------- /docs/formind.md: -------------------------------------------------------------------------------- 1 | # 大体了解 2 | ## 架构 3 | 和传统的mvc框架一样,`puck`同样基于`(模型-视图-控制器)`的方式来组织逻辑。 4 | 5 | > MVC是一个设计模式,它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型(M)、视图(V)、控制器(C),它们各自处理自己的任务。 6 | 7 | 不同的是,`puck`是一个真正的去重设计,以`路由`为中心,由其来完成对整个系统的派遣和调用。 8 | 大体是: 9 | 10 | * 路由->控制器<->模型 11 | * 路由->控制器->视图 12 | * 路由->控制器[<->模型]->视图 13 | * 路由->控制器[<->模型<->逻辑<->行为<->助手]->视图 14 | 15 | 总之,它们都是受路由驱动,然后由控制器来进行对下层的调用。 16 | 17 | ## 周期 18 | 19 | `index`->`bootstrap`->`composer`->`error handler`->`init`->`router`->[`dispather`->]`controller`->`view` 20 | 21 | 其中,`[]`内代表可选的。 22 | 这是因为,如果没有使用复杂路由,而是直接使用`类映射`,`重定向`,`跳转`,`闭包`等是不需要进行路由分发的。 23 | 24 | 一般情况下,我们推荐后台使用`dispather`,而前台则直接使用自定义的路由规则。 25 | 这是因为后台一般情况下都有`权限控制`,而`路由分发`可以方便的结合它。 26 | 27 | ## url 28 | 29 | 和其他大而全的框架不同的是,`puck`只允许一种访问方式。同时对url进行了必要的规范和约束。 30 | 使用本框架的前提就是必须设置`url rewrite`,参见[这里](#配置url rewrite) 。 31 | `puck`允许`url`和逻辑分离,除非yidabo你使用复杂的`dispather`模式,否则可以自由的定义映射规则。 32 | 33 | ## 目录结构 34 | ```tree 35 | ├─app 36 | │ ├─controllers 37 | │ │ ├─Xxx.php 38 | │ ├─helpers 39 | │ │ ├─Xxx.php 40 | │ ├─models 41 | │ │ ├─Xxx.php 42 | │ ├─views 43 | │ │ ├─Xxx 44 | │ │ │ ├─yyy.twig 45 | ``` 46 | 47 | ## 命名空间 48 | `puck`的架构均建立在遵循`psr-4`的命名空间之上。 49 | 因为,控制器的命名空间必须为 50 | ```php 51 | namespace app\controllers; 52 | ``` 53 | 同理,模型则为 54 | ```php 55 | namespace app\models; 56 | ``` 57 | 58 | 则使用时为 59 | ```php 60 | use \app\controllers\HomeController as HomeController; 61 | ``` 62 | 63 | ## 自动加载 64 | `puck`建立在`compsoer`之上,使用其自动加载逻辑。 65 | 因此,当你的代码需要被自动加载的时候,需要配置`composer.json`. 66 | 67 | ```json 68 | "autoload": { 69 | "psr-4": { 70 | "app\\":"app", 71 | "api\\":"api" 72 | }, 73 | "files": [ 74 | "function.php" 75 | ] 76 | }, 77 | ``` 78 | 79 | 其中`files`为自动载入的文件。为了使函数高可用,我们需要提前加载进去。 -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | puck php framework 7 | 8 | 9 |
loading....
10 | 11 | 27 | 28 | -------------------------------------------------------------------------------- /docs/model.md: -------------------------------------------------------------------------------- 1 | 模型 2 | 虽然上面的篇幅里,用了非常多的口水去讲解数据库的操作,但是我们并不建议在`controller`里直接操作数据库. 3 | 因为那样会带来非常多的维护上的问题. 4 | 我们建议把数据库操作放到`model`层里,严格按照`mvc`的策略,来进行数据库的操作. 5 | 并且,在模型中,框架的基类封装了常规数据的操作,使之可能性更高. 6 | 7 | ## 定义 8 | 9 | 定义个user模型 10 | 11 | ```php 12 | namespace app\models; 13 | use \export\model as model; 14 | 15 | class UserModel extends model 16 | { 17 | protected $table='user'; 18 | } 19 | ``` 20 | 21 | 虽然可以从类名转化为数据表名,但我们不赞成这么做,我们建议在每个模型中都**显式**的声明表名,以便于效率. 22 | 但是这里的表明是不带后缀的,即在数据库中,这个表的真正名字为`puck_user`,如果在配置中配置的表前缀为`puck`的话. 23 | 24 | ## 初始化 25 | 26 | 为了贴近底层,初始化还是使用构造函数. 27 | 例如 28 | 29 | ```php 30 | namespace app\models; 31 | use \export\model as model; 32 | 33 | class UserModel extends model 34 | { 35 | function __construct() 36 | { 37 | parent::__construct(); 38 | $this->table='user'; 39 | } 40 | } 41 | ``` 42 | 本质上,它就是一个类. 43 | 44 | ## 使用 45 | 46 | 使用的时候,同样是当做一个类. 47 | 48 | ```php 49 | add($user); 90 | } 91 | } 92 | 93 | ``` 94 | ## 查询条件 95 | 96 | 在模型里,同样支持查询条件的连续直接调用. 97 | 更多条件语句,请参考上面数据库节点下的->[条件](#条件) 98 | 99 | 100 | ## 更新 101 | 102 | ```php 103 | where('id',1)->update($user); 121 | } 122 | } 123 | 124 | ``` 125 | 126 | ## 删除 127 | 128 | ```php 129 | where('id',1)->delete(); 141 | } 142 | } 143 | 144 | ``` 145 | 146 | ## 字段筛选 147 | 很多时候,比如查找的时候,我们可能不太想查找所有的数据,而是仅仅查找部分字段. 148 | 框架内部提供了`field()`方法来实现这个功能. 149 | 参考下面的例子 150 | 151 | ## 查询 152 | 153 | ### 查询所有 154 | 155 | ```php 156 | field('login,firstName,lastName,id')->where('id',1,'>')->select(); 168 | } 169 | } 170 | 171 | ``` 172 | 173 | ### 查询一条 174 | 当只需要一条的时候,可以使用`find()`方法来快速返回. 175 | 176 | ```php 177 | field('login,firstName,lastName,id')->where('id',1,'>')->find(); 189 | } 190 | } 191 | 192 | ``` 193 | 194 | ## 计数 195 | 196 | 同样地,框架内部提供了`count()`方法来实现统计功能. 197 | 但它并非是mysql内部的`count(*)`,而是查询后的缓存`count` 198 | 199 | 200 | ```php 201 | field('id')->where('id',1,'>')->select(); 213 | //这样获取上次结果的记录数 214 | $count=$user->count(); 215 | } 216 | } 217 | 218 | ``` 219 | 220 | 221 | 如果想要根据条件来获取数据库内部符合条件的记录数,应该使用 222 | 223 | ```php 224 | field('count(*) as count')->where('id',1,'>')->find(); 236 | $count=$info['count']; 237 | //这样获取符合条件的记录数 238 | } 239 | } 240 | 241 | ``` 242 | 243 | ## 分页 244 | 245 | 框架内部对分页做过完善的,甚至带了分页助手函数. 246 | 247 | ```php 248 | field('login,firstName,lastName,id') 260 | ->where('id',1,'>') 261 | ->orderBy("createdAt","Desc") 262 | ->orderBy("id","Desc") 263 | ->page($page,20); 264 | if($user->totalCount>1){ 265 | $pageClass= new PageHelper(20,'page'); 266 | $pageClass->set_total($model->totalCount); 267 | //打印页面的导航,输出到前台即可 268 | $pageLinks=$pageClass->page_links(); 269 | } 270 | } 271 | } 272 | 273 | ``` 274 | 275 | ## 其他 276 | 277 | 除此之外,模型还支持 [数据库](#数据库) 里面的各种的复杂的实现. -------------------------------------------------------------------------------- /docs/pinyin.md: -------------------------------------------------------------------------------- 1 | ## 拼音扩展 2 | `puck`支持中文和拼音间的转换,对地名,人名,句子等均有处理,它甚至能支持音调的携带。 3 | 这个扩展库由[https://github.com/overtrue/pinyin](https://github.com/overtrue/pinyin)提供技术支持。 4 | 5 | ## 用法 6 | 7 | ### 基本用法 8 | ```php 9 | use puck\helpers\Pinyin; 10 | $pinyin = new Pinyin(); 11 | $pinyin->convert('带着希望去旅行,比到达终点更美好'); 12 | // ["dai", "zhe", "xi", "wang", "qu", "lv", "xing", "bi", "dao", "da", "zhong", "dian", "geng", "mei", "hao"] 13 | $pinyin->convert('带着希望去旅行,比到达终点更美好', PINYIN_UNICODE); 14 | // ["dài","zhe","xī","wàng","qù","lǚ","xíng","bǐ","dào","dá","zhōng","diǎn","gèng","měi","hǎo"] 15 | 16 | $pinyin->convert('带着希望去旅行,比到达终点更美好', PINYIN_ASCII); 17 | //["dai4","zhe","xi1","wang4","qu4","lv3","xing2","bi3","dao4","da2","zhong1","dian3","geng4","mei3","hao3"] 18 | 19 | ``` 20 | 21 | ### 生产用于链接 22 | 23 | ```php 24 | $pinyin->permalink('带着希望去旅行'); // dai-zhe-xi-wang-qu-lv-xing 25 | $pinyin->permalink('带着希望去旅行', '.'); // dai.zhe.xi.wang.qu.lv.xing 26 | ``` 27 | 28 | ### 获取首字符 29 | 30 | ```php 31 | $pinyin->abbr('带着希望去旅行'); // dzxwqlx 32 | $pinyin->abbr('带着希望去旅行', '-'); // d-z-x-w-q-l-x 33 | ``` 34 | 35 | ### 生成句子 36 | 37 | ```php 38 | $pinyin->sentence('带着希望去旅行,比到达终点更美好!'); 39 | // dai zhe xi wang qu lv xing, bi dao da zhong dian geng mei hao! 40 | 41 | $pinyin->sentence('带着希望去旅行,比到达终点更美好!', true); 42 | // dài zhe xī wàng qù lǚ xíng, bǐ dào dá zhōng diǎn gèng měi hǎo! 43 | ``` 44 | 45 | ### 翻译姓名 46 | 47 | ```php 48 | $pinyin->name('单某某'); // ['shan', 'mou', 'mou'] 49 | $pinyin->name('单某某', PINYIN_UNICODE); // ["shàn","mǒu","mǒu"] 50 | ``` 51 | 52 | ### 专用名词 53 | 54 | ```php 55 | $pinyin->noun('山西'); // ShanXi 56 | ``` -------------------------------------------------------------------------------- /docs/preface.md: -------------------------------------------------------------------------------- 1 | # 啰嗦一番 2 | ## 为什么 3 | 我为什么要做个框架?这是一开始最先考虑的问题。在开发`php`程序的过程中,我们习惯性的总是在使用各种 4 | 框架,在自己的代码中添加大小和重量是自己代码好几倍的框架----然而,你确信这些框架是必须的吗? 5 | 至少我不确定,在反复的测试之后,我终于放弃了一切现有的框架,因为他是为大家,为大众使用的,注定里面 6 | 有各种的添加,而无论我是否需要。 7 | 其实我始终觉得**适合的才是最好的** ,可是究竟什么才是适合?我想每个项目都是不同的,那么有没有一种两全其美 8 | 的办法,既能敏捷开发,又能性能至上呢? 9 | 受`npm`的启发,我想`php`也可以像积木一样,用不同的模块拼接成不同的框架,应用成不同的逻辑。 10 | 幸运的是,`php`也早就有了包管理系统,`composer`。 11 | 没错,本框架就是基于此进行扩展和灌装的。 12 | 借用`laruence`的一句话, 13 | 14 | > 剑的三层境界:一是手中有剑,心中亦有剑;二是手中无剑,心中有剑;三是手中无剑,心中亦无剑。 15 | 16 | 那么本框架也同样是在第二层,手中无剑,心中有剑。 17 | 这也就是本框架与其它框架的最大的不同----我们允许甚至鼓励修改框架本身! 18 | 希望在使用本框架的过程中,能够真正的做到**心中也无剑**。 19 | 本框架更多的意义在于约定和规范,甚至代码片段的作用。 20 | 21 | ## 用于谁? 22 | 本框架只适用于,追求极致的性能,处女座,有一定动手能力的,强迫症,折腾狂。 23 | 对*得过且过,能用就行,简单就好* 的开发者不够友好。 24 | 25 | ## 约定 26 | 27 | * 各个脚手架都必须适用当下性能最好的部件,无论其是否麻烦。 28 | * 代码风格遵循`psr-4`。 29 | * 零冗余 30 | * 高迭代 31 | * 大巧不工,使用最简单的代码,最巧妙的逻辑 32 | * 尽可能的使用`强类型` 33 | 34 | ## 特性 35 | 36 | * 轻量级,非重型框架。框架逻辑简单明了。 37 | * logic,util分离。 38 | * 执行栈浅,性能优先。 39 | * 高度可定制。 40 | * 拟`thinkphp`语法,对国内用户友好。 41 | 42 | ## 要求 43 | 虽然本框架并没有什么特别的,理论上可运行与5.3+的`php`之上,但是为了性能及减少故障的发生,我们建议 44 | 使用以下环境。 45 | 46 | - php7 + 47 | - hhvm [可选] 48 | - mariadb[推荐]/mysql 49 | - nginx[推荐]/tengine/openresty/apache[不推荐] 50 | - composer 51 | - centos[推荐]/或者其他的linux发行版/windows[不推荐] 52 | - jemalloc 53 | - redis 54 | 55 | 由于php的`zval`改进,产生了巨大的性能提升,而且本框架的理念就是**要用就用最好的**,所以这里强烈推荐使用。 56 | 如果没有使用`php7`而使用的低版本`php`则强烈推荐`facebook`的`hhvm`来对`php`进行优化。 57 | 另外,在使用本框架之前,你可能得花一定的时间去学习[phpcomposer](http://docs.phpcomposer.com/). 58 | 如果你确定满足了以上条件,请继续,否则请先配置以上。不然接下来可能会导致一系列诡异的问题。 -------------------------------------------------------------------------------- /docs/quickstart.md: -------------------------------------------------------------------------------- 1 | # 从部署到运行 2 | 3 | ## 新建项目 4 | 5 | 框架推荐的项目目录格式如下 6 | ```tree 7 | ├── admin --后台项目 8 | │   ├── controllers --控制器 9 | │   ├── models --模型 10 | │   └── views --模版 11 | ├── api --api项目 12 | │   └── controllers 13 | ├── app --主项目 14 | │   ├── conf 15 | │   ├── controllers 16 | │   ├── models 17 | │   └── views 18 | ├── cache ---缓存目录 19 | │   └── xxxx.... 20 | ├── cli ---命令行目录 21 | │   ├── controllers 22 | │   ├── functions 23 | │   ├── helper 24 | │   └── logic 25 | ├── composer.json --composer配置文件 26 | ├── function.php -- 助手函数 27 | ├── .env -- 环境配置文件 28 | ├── public 29 | │   ├── assets --资源文件夹 30 | │   └── index.php --入口文件 31 | ├── config --配置文件目录 32 | │   ├── app.php --各种配置文件 33 | │   ├── some.php 34 | │   ├── other.php 35 | │   └── etc.php 36 | └── vendor 37 | ``` 38 | 39 | 一般而言,我们推荐使用composer来初始化您的项目。 40 | 41 | ```bash 42 | cd /path/to 43 | mkdir balabala 44 | cd balabala 45 | composer init 46 | ``` 47 | 依次来填写各种配置信息即可。 48 | 如果你之前有使用过composer 或者 npm之类的,你可能对这步并不奇怪。 49 | 50 | ## 安装框架 51 | 52 | 正如前文所言,我们使用composer来安装框架。 一般地,我们推荐安装最新release的版本。这能保证体验到越来越好的框架,和越来越少的bug。 53 | 54 | 55 | ```shell 56 | composer require rozbo/puck 57 | ``` 58 | 59 | 稍等即可安装成功。 60 | 61 | ## 然后配置项目 62 | 63 | 我们开始在项目目录里写代码了. 64 | 如上文所言,我们建议在public里建立index.php文件,作为我们项目的入口。 65 | 66 | ```shell 67 | mkdir public 68 | ``` 69 | 70 | 里面的内容大概为 71 | ```php 72 | route->get("/",function(){ 82 | echo "balabala"; 83 | }); 84 | ``` 85 | 86 | 最后,我们让他动起来! 87 | 88 | ```php 89 | $app->run(); 90 | ``` 91 | 92 | 此时,我们访问首页即可看到我们期望输出的 93 | ```html 94 | balabala 95 | ``` 96 | 97 | 98 | 99 | ## 配置url rewrite 100 | 101 | 鉴于我们是一个`mvc框架`,在网址的美化上是第一步我们要做的,为了支持`url rewrite` 102 | 我们得在`web server`进行设置。 103 | 为了缩短更新文件的时间,此处我们使用`nginx`为例,如果有童鞋使用了其他的服务器,欢迎补充。 104 | 105 | ```nginx 106 | autoindex off; 107 | 108 | location / { 109 | try_files $uri $uri/ /index.php?/$uri; 110 | } 111 | ``` 112 | 原理是将 `something.html`映射为`/index.php?/something.html` 113 | 这样便于我们的路由进行后续的逻辑处理。 114 | 115 | 116 | -------------------------------------------------------------------------------- /docs/router.md: -------------------------------------------------------------------------------- 1 | # 路由 2 | `puck`的路由是是一个轻量级的逻辑,原理是把把整个网址当成一个字符串,然后用正则匹配,进行相应处理,十分的简单。 3 | 4 | 框架在初始化时,已经注册过路由的实例。 5 | 我们可以像首页那样,使用 6 | ```php 7 | $app->route->get('/',function(){ 8 | echo "balabala"; 9 | }) 10 | ``` 11 | 也可以通过 12 | ```php 13 | app('route')->get('/',function(){ 14 | echo "balabala"; 15 | }) 16 | ``` 17 | 相信聪明的你,已经发现了 18 | `app('route')`就是用来获取`route`的实例。 19 | 20 | 21 | ## 闭包路由 22 | 23 | ### 一般使用 24 | 我们可以使用闭包的方式定义一些特殊需求的路由,而不需要执行控制器的操作方法了,例如: 25 | 26 | ```php 27 | app('route')->get('/', function() { 28 | echo 'Hello world!'; 29 | }); 30 | ``` 31 | ### 参数传递 32 | 33 | 闭包定义的时候支持参数传递,例如: 34 | 35 | ```php 36 | app('route')->get('/(:any)', function($slug) { 37 | echo 'The slug is: ' . $slug; 38 | }); 39 | ``` 40 | 例如,我们访问的URL地址是: 41 | 42 | ```url 43 | http://xxx.com/hello 44 | ``` 45 | 则浏览器输出的结果是: 46 | 47 | ```text 48 | The slug is: hello 49 | ``` 50 | 51 | ## 正则支持 52 | 53 | 在上面的案例中,你也许会问`(:any)`是什么鬼?实质上`(:any)`本身即为一个正则表达式。 54 | 55 | ```php 56 | public static $patterns = array( 57 | ':any' => '[^/]+', 58 | ':num' => '[0-9]+', 59 | ':all' => '.*' 60 | ); 61 | ``` 62 | 路由默认提供以上三个常用的正则,而其他的怎么办呢,手写即可。 63 | 因此,上面的例子即等同于 64 | 65 | ```php 66 | app('route')->get('/([^/]+)', function($slug) { 67 | echo 'The slug is: ' . $slug; 68 | }); 69 | ``` 70 | 71 | 因此我们可以自行以正则定义各种路由 72 | 73 | ```php 74 | app('route')->get('/^([1][358][0-9]{9})$', function($phoneNum) { 75 | echo 'yeah,the phone num is: ' . $phoneNum; 76 | }); 77 | ``` 78 | ## 控制器支持 79 | 80 | ```php 81 | app('route')->get('/', 'Controllers\demo@index'); 82 | app('route')->get('page', 'Controllers\demo@page'); 83 | app('route')->get('view/(:num)', 'Controllers\demo@view'); 84 | ``` 85 | 则分别对应以下类的各个方法: 86 | 87 | ```php 88 | error(function() { 117 | echo '404 :: Not Found'; 118 | }); 119 | ``` 120 | 或者 121 | 122 | ```php 123 | app('route')->error('\PublicController@notfound'); 124 | ``` 125 | 126 | 再或者 127 | 128 | ```php 129 | app('route')->$error_callback=function(){ 130 | echo '404 :: Not Found'; 131 | } 132 | ``` 133 | 134 | ## RESTful支持 135 | 136 | 由于路由内部使用的`__callstatic`方法,可以适配所有的http操作。而不仅仅是`GET`,`POST`。 137 | 甚至于`PUT`,`DELETE`。 138 | 本框架也推荐使用`RESTful`。 139 | 140 | ```php 141 | app('route')->delete('posts/(:num)', '\app\controller\PostController@delete'); 142 | ``` 143 | 144 | 将适配`DELETE`请求,并完成转发。 145 | 146 | 同样的,使用 147 | ```php 148 | app('route')->put('posts/(:num)', '\app\controller\PostController@put'); 149 | ``` 150 | 将适配`put`操作. 151 | 152 | 以一步,如果你想适配任何请求,只需要 153 | 154 | ```php 155 | app('route')->any('posts/(:num)', '\app\controller\PostController@index'); 156 | ``` 157 | 158 | 159 | ## 控制器路由 160 | 161 | 看完以上的路由用法,相信你心里可能有个疑惑,我网站那个多的页面,难道要一个一个自己去写路由规则吗? 162 | 163 | 当然不是,上面的都是自定义路由,而大多数情况下,我们都是使用的控制器路由,即让让路自动派遣到对应的控制器上。 164 | 165 | ```php 166 | app('route')->any('/([\w\W]*)', function ($slug) { 167 | \puck\helpers\Dispatch::dispatch($slug, '\\app'); 168 | return; 169 | }); 170 | ``` 171 | 第二个参数为项目的命令空间。 172 | 173 | 此时,如果你访问 174 | ```html 175 | xxx.com/a/b 176 | ``` 177 | 则是绑定的 178 | `app\controllers\A`里面的`b`方法。 179 | -------------------------------------------------------------------------------- /docs/validate.md: -------------------------------------------------------------------------------- 1 | # 验证 2 | 3 | 文档待完善... -------------------------------------------------------------------------------- /docs/view.md: -------------------------------------------------------------------------------- 1 | # 视图 2 | 3 | 本框架使用[twig](http://twig.sensiolabs.org/documentation)作为模板引擎. 4 | 可以具体的使用方法,看看去其官网文档了解. 5 | ## 模板位置 6 | 除非特别制定,框架会在`/app/views/CONTROLLER_NAME/ACTION_NAME.twig`读取模板. 7 | 8 | 9 | ## 变量传递 10 | 在`控制器`中,将后台的数据传递到前台,使用`assgin()`方法. 11 | 例如 12 | 13 | 14 | ```php 15 | field('login,firstName,lastName,id') 28 | ->where('id',1,'>') 29 | ->orderBy("createdAt","Desc") 30 | ->orderBy("id","Desc") 31 | ->page($page,20); 32 | if($user->totalCount>1){ 33 | $pageClass= new PageHelper(20,'page'); 34 | $pageClass->set_total($model->totalCount); 35 | //打印页面的导航,输出到前台即可 36 | $pageLinks=$pageClass->page_links(); 37 | } 38 | //传递变量到前台,用于输出 39 | $this->assign('userList',$list); 40 | $this->assign('pageLinks',$pageLinks); 41 | $this->title='使用日志'; 42 | } 43 | } 44 | 45 | ``` 46 | 特别地,`puck`会使用控制器的`title`属性作为页面的`title`. 47 | 48 | 49 | 50 | ## 模板渲染 51 | 52 | 在非`开发者模式`下,第一次渲染的结果被会缓存在`/cache`下. 53 | 在`控制器`中,当变量注册完毕,需要通过`show`方法来渲染模板. 54 | 55 | ```php 56 | field('login,firstName,lastName,id') 69 | ->where('id',1,'>') 70 | ->orderBy("createdAt","Desc") 71 | ->orderBy("id","Desc") 72 | ->page($page,20); 73 | if($user->totalCount>1){ 74 | $pageClass= new PageHelper(20,'page'); 75 | $pageClass->set_total($model->totalCount); 76 | //打印页面的导航,输出到前台即可 77 | $pageLinks=$pageClass->page_links(); 78 | } 79 | //传递变量到前台,用于输出 80 | $this->assign('userList',$list); 81 | $this->assign('pageLinks',$pageLinks); 82 | $this->title='使用日志'; 83 | //渲染模板 84 | $this->show(); 85 | } 86 | } 87 | 88 | ``` -------------------------------------------------------------------------------- /function.php: -------------------------------------------------------------------------------- 1 | 78 | */ 79 | function data_auth_sign($data) { 80 | //数据类型检测 81 | 82 | if (!is_array($data)) { 83 | 84 | $data=(array) $data; 85 | } 86 | ksort($data); //排序 87 | $code=http_build_query($data); //url编码并生成query字符串 88 | $sign=sha1($code); //生成签名 89 | return $sign; 90 | } 91 | /** 92 | * session管理函数 93 | * @param string $name session名称 如果为数组则表示进行session设置 94 | * @param mixed $value session值 95 | * @return mixed 96 | */ 97 | function session($name='', $value='') { 98 | if (is_array($name)) { 99 | 100 | if (isset($name['id'])) { 101 | session_id($name['id']); 102 | } 103 | 104 | if (isset($name['name'])) { 105 | session_name($name['name']); 106 | } 107 | 108 | if (isset($name['path'])) { 109 | session_save_path($name['path']); 110 | } 111 | 112 | if (isset($name['domain'])) { 113 | ini_set('session.cookie_domain', $name['domain']); 114 | } 115 | 116 | if (isset($name['expire'])) { 117 | ini_set('session.gc_maxlifetime', $name['expire']); 118 | ini_set('session.cookie_lifetime', $name['expire']); 119 | } 120 | if (isset($name['use_trans_sid'])) { 121 | ini_set('session.use_trans_sid', $name['use_trans_sid'] ? 1 : 0); 122 | } 123 | 124 | if (isset($name['use_cookies'])) { 125 | ini_set('session.use_cookies', $name['use_cookies'] ? 1 : 0); 126 | } 127 | 128 | if (isset($name['cache_limiter'])) { 129 | session_cache_limiter($name['cache_limiter']); 130 | } 131 | 132 | if (isset($name['cache_expire'])) { 133 | session_cache_expire($name['cache_expire']); 134 | } 135 | session_start(); 136 | } elseif ('' === $value) { 137 | if ('' === $name) { 138 | // 获取全部的session 139 | return $_SESSION; 140 | } elseif (0 === strpos($name, '[')) { 141 | // session 操作 142 | if ('[pause]' == $name) {// 暂停session 143 | session_write_close(); 144 | } elseif ('[start]' == $name) { 145 | // 启动session 146 | session_start(); 147 | } elseif ('[destroy]' == $name) { 148 | // 销毁session 149 | $_SESSION=array(); 150 | session_unset(); 151 | session_destroy(); 152 | } elseif ('[regenerate]' == $name) { 153 | // 重新生成id 154 | session_regenerate_id(); 155 | } 156 | } else { 157 | if (strpos($name, '.')) { 158 | list($name1, $name2)=explode('.', $name); 159 | return isset($_SESSION[$name1][$name2]) ? $_SESSION[$name1][$name2] : null; 160 | } else { 161 | return isset($_SESSION[$name]) ? $_SESSION[$name] : null; 162 | } 163 | } 164 | } elseif (is_null($value)) { 165 | // 删除session 166 | if (strpos($name, '.')) { 167 | list($name1, $name2)=explode('.', $name); 168 | unset($_SESSION[$name1][$name2]); 169 | } else { 170 | unset($_SESSION[$name]); 171 | } 172 | } else { 173 | // 设置session 174 | if (strpos($name, '.')) { 175 | list($name1, $name2)=explode('.', $name); 176 | $_SESSION[$name1][$name2]=$value; 177 | } else { 178 | $_SESSION[$name]=$value; 179 | } 180 | } 181 | return null; 182 | } 183 | 184 | 185 | function admin_is_login() { 186 | $user=session('admin_user_auth'); 187 | if (empty($user)) { 188 | return 0; 189 | } else { 190 | $auth_sign=session('admin_user_auth_sign'); 191 | if (data_auth_sign($user) != $auth_sign) { 192 | return 0; 193 | } 194 | return $user['uid']; 195 | } 196 | } 197 | function json($str) { 198 | $obj=json_encode($str, JSON_UNESCAPED_UNICODE); 199 | header('Content-Type: application/json'); 200 | echo $obj; 201 | } 202 | /** 203 | * 浏览器友好的变量输出 204 | * @param mixed $var 变量 205 | * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 206 | * @param string $label 标签 默认为空 207 | * @param integer $flags htmlspecialchars flags 208 | * @return void|string 209 | */ 210 | function dump($var, $echo=true, $label=null, $flags=ENT_SUBSTITUTE) 211 | { 212 | $label=(null === $label) ? '' : rtrim($label).':'; 213 | ob_start(); 214 | var_dump($var); 215 | $output=ob_get_clean(); 216 | $output=preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output); 217 | if (IS_CLI) { 218 | $output=PHP_EOL.$label.$output.PHP_EOL; 219 | } else { 220 | if (!extension_loaded('xdebug')) { 221 | $output=htmlspecialchars($output, $flags); 222 | } 223 | $output='
'.$label.$output.'
'; 224 | } 225 | if ($echo) { 226 | echo($output); 227 | return; 228 | } else { 229 | return $output; 230 | } 231 | } 232 | 233 | 234 | /** 235 | * 获取输入参数 支持过滤和默认值 236 | * 使用方法: 237 | * 238 | * I('id',0); 获取id参数 自动判断get或者post 239 | * I('post.name','','htmlspecialchars'); 获取$_POST['name'] 240 | * I('get.'); 获取$_GET 241 | * 242 | * @param string $name 变量的名称 支持指定类型 243 | * @param mixed $default 不存在的时候默认值 244 | * @param mixed $filter 参数过滤方法 245 | * @param mixed $datas 要获取的额外数据源 246 | * @return mixed 247 | */ 248 | function I($name, $default='', $filter=null, $datas=null) 249 | { 250 | static $_PUT=null; 251 | if (strpos($name, '/')) { 252 | // 指定修饰符 253 | list($name, $type)=explode('/', $name, 2); 254 | } else { 255 | // 默认强制转换为字符串 256 | $type='s'; 257 | } 258 | if (strpos($name, '.')) { 259 | // 指定参数来源 260 | list($method, $name)=explode('.', $name, 2); 261 | } else { 262 | // 默认为自动判断 263 | $method='param'; 264 | } 265 | switch (strtolower($method)) { 266 | case 'get': 267 | $input=&$_GET; 268 | break; 269 | case 'post': 270 | $input=&$_POST; 271 | break; 272 | case 'put': 273 | if (is_null($_PUT)) { 274 | parse_str(file_get_contents('php://input'), $_PUT); 275 | } 276 | $input=$_PUT; 277 | break; 278 | case 'param': 279 | switch ($_SERVER['REQUEST_METHOD']) { 280 | case 'POST': 281 | $input=$_POST; 282 | break; 283 | case 'PUT': 284 | if (is_null($_PUT)) { 285 | parse_str(file_get_contents('php://input'), $_PUT); 286 | } 287 | $input=$_PUT; 288 | break; 289 | default: 290 | $input=$_GET; 291 | } 292 | break; 293 | case 'path': 294 | $input=array(); 295 | if (!empty($_SERVER['PATH_INFO'])) { 296 | $depr=C('URL_PATHINFO_DEPR'); 297 | $input=explode($depr, trim($_SERVER['PATH_INFO'], $depr)); 298 | } 299 | break; 300 | case 'request': 301 | $input=&$_REQUEST; 302 | break; 303 | case 'session': 304 | $input=&$_SESSION; 305 | break; 306 | case 'cookie': 307 | $input=&$_COOKIE; 308 | break; 309 | case 'server': 310 | $input=&$_SERVER; 311 | break; 312 | case 'globals': 313 | $input=&$GLOBALS; 314 | break; 315 | case 'data': 316 | $input=&$datas; 317 | break; 318 | default: 319 | return null; 320 | } 321 | if ('' == $name) { 322 | // 获取全部变量 323 | $data=$input; 324 | $filters=isset($filter) ? $filter : 'htmlspecialchars'; 325 | if ($filters) { 326 | if (is_string($filters)) { 327 | $filters=explode(',', $filters); 328 | } 329 | else if (is_array($filters)) { 330 | foreach ($filters as $filter) { 331 | $data=array_map_recursive($filter, $data); // 参数过滤 332 | } 333 | } else { 334 | throw new \RuntimeException('$filters must be an array or string.'); 335 | } 336 | } 337 | } elseif (isset($input[$name])) { 338 | // 取值操作 339 | $data=$input[$name]; 340 | $filters=isset($filter) ? $filter : 'htmlspecialchars'; 341 | if ($filters) { 342 | if (is_string($filters)) { 343 | if (0 === strpos($filters, '/')) { 344 | if (1 !== preg_match($filters, (string) $data)) { 345 | // 支持正则验证 346 | return isset($default) ? $default : null; 347 | } 348 | } else { 349 | $filters=explode(',', $filters); 350 | } 351 | } elseif (is_int($filters)) { 352 | $filters=array($filters); 353 | } 354 | 355 | if (is_array($filters)) { 356 | foreach ($filters as $filter) { 357 | $filter=trim($filter); 358 | if (function_exists($filter)) { 359 | $data=is_array($data) ? array_map_recursive($filter, $data) : $filter($data); // 参数过滤 360 | } else { 361 | $data=filter_var($data, is_int($filter) ? $filter : filter_id($filter)); 362 | if (false === $data) { 363 | return isset($default) ? $default : null; 364 | } 365 | } 366 | } 367 | } 368 | } 369 | if (!empty($type)) { 370 | switch (strtolower($type)) { 371 | case 'a': // 数组 372 | $data=(array) $data; 373 | break; 374 | case 'd': // 数字 375 | $data=(int) $data; 376 | break; 377 | case 'f': // 浮点 378 | $data=(float) $data; 379 | break; 380 | case 'b': // 布尔 381 | $data=(boolean) $data; 382 | break; 383 | case 's':// 字符串 384 | default: 385 | $data=(string) $data; 386 | } 387 | } 388 | } else { 389 | // 变量默认值 390 | $data=isset($default) ? $default : null; 391 | } 392 | is_array($data) && array_walk_recursive($data, 'think_filter'); 393 | return $data; 394 | } 395 | function array_map_recursive($filter, $data) 396 | { 397 | $result=array(); 398 | foreach ($data as $key => $val) { 399 | $result[$key]=is_array($val) 400 | ? array_map_recursive($filter, $val) 401 | : call_user_func($filter, $val); 402 | } 403 | return $result; 404 | } 405 | function think_filter(&$value) 406 | { 407 | // TODO 其他安全过滤 408 | 409 | // 过滤查询特殊字符 410 | if (preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i', $value)) { 411 | $value .= ' '; 412 | } 413 | } 414 | /** 415 | * @param string $name 416 | * 417 | * @return string|null 418 | */ 419 | function config($name=null,$value=null,$default=null){ 420 | $config=\puck\Conf::load(); 421 | if ($name===null){ 422 | return $config->all(); 423 | } 424 | if ($value===null){ 425 | return $config->get($name,$default); 426 | } 427 | $config->set($name,$value); 428 | } 429 | /** 430 | * 字符串命名风格转换 431 | * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 432 | * @param string $name 字符串 433 | * @param integer $type 转换类型 434 | * @return string 435 | */ 436 | function parse_name($name, $type=0) { 437 | if ($type) { 438 | return ucfirst(preg_replace_callback('/_([a-zA-Z])/', function($match) {return strtoupper($match[1]); }, $name)); 439 | } else { 440 | return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); 441 | } 442 | } 443 | 444 | if (! function_exists('app')) { 445 | /** 446 | * 获取容器实例 447 | * 448 | * @param string $make 449 | * @return mixed|\puck\App 450 | */ 451 | function app($make = null) 452 | { 453 | if (is_null($make)) { 454 | return Container::getInstance(); 455 | } 456 | return Container::getInstance()->make($make); 457 | } 458 | } 459 | 460 | if (! function_exists('env')) { 461 | /** 462 | * 获取环境变量 463 | * 464 | * @param string $key 465 | * @param mixed $default 466 | * @return mixed 467 | */ 468 | function env($key, $default = null) 469 | { 470 | $value = getenv($key); 471 | if ($value === false) { 472 | return value($default); 473 | } 474 | switch (strtolower($value)) { 475 | case 'true': 476 | case '(true)': 477 | return true; 478 | case 'false': 479 | case '(false)': 480 | return false; 481 | case 'empty': 482 | case '(empty)': 483 | return ''; 484 | case 'null': 485 | case '(null)': 486 | return; 487 | } 488 | if (Str::startsWith($value, '"') && Str::endsWith($value, '"')) { 489 | return substr($value, 1, -1); 490 | } 491 | return $value; 492 | } 493 | } 494 | if (! function_exists('value')) { 495 | /** 496 | * 返回给定的表达式的结果 497 | * 498 | * @param mixed $value 499 | * @return mixed 500 | */ 501 | function value($value) 502 | { 503 | return $value instanceof Closure ? $value() : $value; 504 | } 505 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests/ 6 | 7 | 8 | 9 | 10 | ./core 11 | 12 | 13 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![author](https://img.shields.io/badge/author-rozbo-green.svg)](https://github.com/Rozbo) 2 | [![doc](https://img.shields.io/badge/doc-0.8.16-ff69b4.svg)](https://puck.zz173.com) 3 | [![rating](https://img.shields.io/badge/rating-%F0%9F%94%A5%F0%9F%94%A5%F0%9F%94%A5%F0%9F%94%A5%F0%9F%94%A5-brightgreen.svg)](https://blog.zz173.com) 4 | [![Packagist](https://img.shields.io/packagist/v/rozbo/puck.svg)](https://packagist.org/packages/rozbo/puck) 5 | [![Packagist](https://img.shields.io/packagist/dt/rozbo/puck.svg)](https://packagist.org/packages/rozbo/puck) 6 | [![Scrutinizer Build](https://img.shields.io/scrutinizer/build/g/rozbo/puck.svg)](https://scrutinizer-ci.com/g/Rozbo/puck) 7 | [![Scrutinizer Coverage](https://img.shields.io/scrutinizer/coverage/g/rozbo/puck.svg)](https://scrutinizer-ci.com/g/Rozbo/puck) 8 | [![Scrutinizer](https://img.shields.io/scrutinizer/g/rozbo/puck.svg)](https://scrutinizer-ci.com/g/Rozbo/puck) 9 | 10 | 11 | ## puck 12 | 13 | > 我是一个努力干活儿,还不粘人的小妖精。 14 | 15 | 16 | 虽然取了一个英文名,但是不准备写英文文档了。 17 | 18 | 具体的还是[看文档](https://puck.zz173.com)吧,全中文的。 19 | 20 | ## 鸣谢 21 | 22 | 本框架使用或参考了以下开源组件,并遵循其开源协议。 23 | 排名不分先后。 24 | 25 | - [thinkphp](https://github.com/top-think/framework) 26 | - [laravel](https://github.com/laravel/framework) 27 | - [php-curl-class](https://github.com/php-curl-class/php-curl-class) 28 | -------------------------------------------------------------------------------- /tests/.env: -------------------------------------------------------------------------------- 1 | APP_TIMEZONE=UTC 2 | version=1.1.1 3 | DEBUG=true 4 | -------------------------------------------------------------------------------- /tests/app/models/Test.php: -------------------------------------------------------------------------------- 1 | assertTrue($flag); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/puck/ContainerTest.php: -------------------------------------------------------------------------------- 1 | container = Container::getInstance(); 16 | } 17 | 18 | public function testRegexBind() { 19 | $this->container->regexBind('#^(\w+)_model$#', "\\tests\\app\\models\\\\$1"); 20 | $model = $this->container->make("test_model"); 21 | $this->assertTrue($model instanceof \tests\app\models\Test); 22 | $this->assertTrue($model->flag); 23 | } 24 | 25 | public function testSnakeRegexBind() { 26 | $this->container->regexBind('#^(\w+)_model$#', "\\tests\\app\\models\\\\$1"); 27 | $model = $this->container->make("user_test_model"); 28 | $this->assertTrue($model instanceof \tests\app\models\UserTest); 29 | $this->assertTrue($model->flag); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/puck/ControllerTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(1 === 1); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/puck/FacadeTest.php: -------------------------------------------------------------------------------- 1 | convert('我要你永远爱我'); 18 | $b=['wo','yao','ni','yong','yuan','ai','wo']; 19 | $this->assertEquals($b,$a); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/puck/ModelTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(1 === 1); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/puck/helpers/CurlTest.php: -------------------------------------------------------------------------------- 1 | basePath(); 14 | if(!Str::startsWith($bashPath,'/home/scrutinizer')){ 15 | $this->markTestSkipped( 16 | '非`ci`模式下不做调试' 17 | ); 18 | } 19 | 20 | } 21 | public function testGet() { 22 | $curl=app('curl'); 23 | $curl->setTimeout(10); 24 | $curl->get('http://www.weather.com.cn/data/cityinfo/101010100.html'); 25 | $this->assertFalse($curl->error); 26 | $tmp=json_decode($curl->response); 27 | $this->assertEquals("北京",$tmp->weatherinfo->city); 28 | } 29 | public function testGetByArray() { 30 | $curl=app('curl'); 31 | $curl->setTimeout(10); 32 | $curl->get('http://ip.taobao.com/service/getIpInfo.php',['ip'=>'8.8.8.8']); 33 | $this->assertFalse($curl->error); 34 | $ret=json_decode($curl->response); 35 | $this->assertTrue($ret->code==0); 36 | } 37 | public function testGetByHttps() { 38 | $curl=app('curl'); 39 | $curl->setTimeout(100); 40 | $curl->get('https://api.map.baidu.com/geocoder?location=40,118&output=json'); 41 | $this->assertFalse($curl->error); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/puck/helpers/DomTest.php: -------------------------------------------------------------------------------- 1 | dom = app('dom'); 18 | } 19 | 20 | /** 21 | * @expectedException InvalidArgumentException 22 | */ 23 | public function testLoadWithInvalidArgument() { 24 | $this->dom->load([]); 25 | } 26 | 27 | 28 | public function loadHtmlCharsetTests() { 29 | return array( 30 | array('
English language', 'English language'), 31 | array('
Русский язык', 'Русский язык'), 32 | array('
اللغة العربية', 'اللغة العربية'), 33 | array('
漢語', '漢語'), 34 | array('
Tiếng Việt', 'Tiếng Việt'), 35 | ); 36 | } 37 | 38 | /** 39 | * @dataProvider loadHtmlCharsetTests 40 | */ 41 | public function testLoadHtmlCharset($html, $text) { 42 | $document = $this->dom->load($html, false); 43 | $this->assertEquals($text, $document->first('div')->text()); 44 | } 45 | 46 | 47 | public function testCreateElementBySelector() { 48 | $document = $this->dom; 49 | $element = $document->createElementBySelector('a.external-link[href=http://example.com]'); 50 | $this->assertEquals('a', $element->tag); 51 | $this->assertEquals('', $element->text()); 52 | $this->assertEquals(['href' => 'http://example.com', 'class' => 'external-link'], $element->attributes()); 53 | $element = $document->createElementBySelector('#block', 'Foo'); 54 | $this->assertEquals('div', $element->tag); 55 | $this->assertEquals('Foo', $element->text()); 56 | $this->assertEquals(['id' => 'block'], $element->attributes()); 57 | $element = $document->createElementBySelector('input', null, ['name' => 'name', 'placeholder' => 'Enter your name']); 58 | $this->assertEquals('input', $element->tag); 59 | $this->assertEquals('', $element->text()); 60 | $this->assertEquals(['name' => 'name', 'placeholder' => 'Enter your name'], $element->attributes()); 61 | } 62 | 63 | public function testAppendChild() { 64 | $html = ' 65 | 66 | 67 | 68 | Document 69 | 70 | 71 | 72 | '; 73 | $document = $this->dom; 74 | $this->assertCount(0, $document->find('span')); 75 | $node = $document->createElement('span'); 76 | $appendedChild = $document->appendChild($node); 77 | $this->assertCount(1, $document->find('span')); 78 | $this->assertTrue($appendedChild->is($document->first('span'))); 79 | $appendedChild->remove(); 80 | $this->assertCount(0, $document->find('span')); 81 | $nodes = []; 82 | $nodes[] = $document->createElement('span'); 83 | $nodes[] = $document->createElement('span'); 84 | $appendedChildren = $document->appendChild($nodes); 85 | $nodes = $document->find('span'); 86 | $this->assertCount(2, $appendedChildren); 87 | $this->assertCount(2, $nodes); 88 | foreach ($appendedChildren as $index => $child) { 89 | $this->assertTrue($child->is($nodes[$index])); 90 | } 91 | } 92 | 93 | /** 94 | * @expectedException RuntimeException 95 | */ 96 | public function testLoadWithNotExistingFile() { 97 | $this->dom->load('path/to/file', true); 98 | } 99 | 100 | 101 | public function testFirst() { 102 | $html = '
  • One
  • Two
  • Three
'; 103 | $document = $this->dom; 104 | $this->dom->loadHtml($html); 105 | $items = $document->find('ul > li'); 106 | $this->assertEquals($items[0]->getNode(), $document->first('ul > li')->getNode()); 107 | $this->assertEquals('One', $document->first('ul > li::text')); 108 | $document = $this->dom->release()->init(); 109 | $this->assertNull($document->first('ul > li')); 110 | } 111 | 112 | 113 | public function testCount() { 114 | $html = '
  • One
  • Two
  • Three
'; 115 | $this->assertEquals(3, $this->dom->loadHtml($html)->count('li')); 116 | $this->assertEquals(0, $this->dom->release()->init()->count('li')); 117 | } 118 | 119 | 120 | public function testHtmlWithOptions() { 121 | $html = ''; 122 | $document = $this->dom->loadHtml($html); 123 | $this->assertEquals('', $document->html()); 124 | $this->assertEquals('', $document->html(0)); 125 | } 126 | 127 | 128 | public function testText() { 129 | $html = 'foo'; 130 | $document = $this->dom->loadHtml($html); 131 | $this->assertEquals('foo', $document->text()); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tests/puck/helpers/PinYinTest.php: -------------------------------------------------------------------------------- 1 | make('pinyin')->convert('我要你永远爱我'); 11 | $b=['wo','yao','ni','yong','yuan','ai','wo']; 12 | $this->assertEquals($b,$a); 13 | } 14 | } -------------------------------------------------------------------------------- /tests/puck/tools/StrTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(Str::contains("abc","b")); 15 | $this->assertTrue(Str::endsWith("abc","c")); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/test.php: -------------------------------------------------------------------------------- 1 |