Browse Source

init project

xiajianjun 3 years ago
parent
commit
5c3f07193c

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+.idea/
+.phpintel/
+!README.md
+composer.lock

+ 51 - 1
README.md

@@ -1 +1,51 @@
-#slim-extend
+# slim-extend
+
+## Description
+
+## Install
+
+- use composer
+
+edit `composer.json`
+
+_require-dev_ add
+
+```
+"inhere/slim-extend": "dev-master",
+```
+
+_repositories_ add 
+
+```
+"repositories": [
+        {
+            "type": "git",
+            "url": "https://git.oschina.net/inhere/slim-extend"
+        }
+    ]
+```
+
+run: `composer update`
+
+## Usage
+
+- add the middleware into slim application
+
+```
+$app->add(new \inhere\extend\middleware\WhoopsTool());
+```
+
+## Options
+
+- Opening referenced files with your favorite editor or IDE
+
+```
+$app = new App([
+    'settings' => [
+        'debug'         => true,
+        'extend.editor' => 'sublime' // Support click to open editor
+    ]
+]);
+```
+
+>>>>>>> init project

+ 43 - 0
composer.json

@@ -0,0 +1,43 @@
+{
+  "name": "inhere/slim-extensions",
+  "description": "A Slim Framework extension",
+  "keywords": ["Slim","extensions"],
+  "homepage": "http://github.com/inhere/slim-extensions",
+  "license": "MIT",
+  "authors": [
+    {
+      "name": "inhere",
+      "email": "in.798@qq.com",
+      "homepage": "http://www.yzone.net/"
+    }
+  ],
+  "require": {
+    "php": ">=5.5.0",
+    "slim/slim": "^3.1",
+    "symfony/yaml": "^3.0",
+  },
+  "require-dev": {
+  },
+  "autoload":{
+    "files":[
+      "src/functions.php"
+    ],
+    "classmap": ["src/Slim.php"],
+    "psr-4": {
+      "slimExtend\\" : "src/"
+    }
+  },
+  "suggest": {
+    "symfony/yaml": "Allow support parse yml file"
+  },
+  "repositories": [
+    {
+      "type": "git",
+      "url": "https://git.oschina.net/inhere/simple-print-tool"
+    },
+    {
+      "type": "git",
+      "url": "https://git.oschina.net/inhere/slim-whoops"
+    }
+  ]
+}

+ 664 - 0
src/DataCollector.php

@@ -0,0 +1,664 @@
+<?php
+/**
+ * @referee  windwalker-registry {@link https://github.com/ventoviro/windwalker-registry}
+ */
+
+namespace slimExtend;
+
+/**
+ * Class DataCollector - 数据收集器 (数据存储器 - DataStorage)
+ * @package slimExtend
+ * 支持 链式的子节点 设置 和 值获取
+ * e.g:
+ * ```
+ * $data = [
+ *      'foo' => [
+ *          'bar' => [
+ *              'yoo' => 'value'
+ *          ]
+ *       ]
+ * ];
+ * $config = new DataCollector();
+ * $config->get('foo.bar.yoo')` equals to $data['foo']['bar']['yoo'];
+ *
+ * ```
+ *
+ * 简单的数据对象可使用Slim内置的 Collection @see \Slim\Collection
+ * ```
+ * $config = new \Slim\Collection($data)
+ * $config->get('foo');
+ * ```
+ */
+class DataCollector implements \JsonSerializable, \ArrayAccess, \IteratorAggregate, \Countable
+{
+    /**
+     * data
+     * @var array
+     */
+    private $data = [];
+
+    /**
+     * @var array
+     */
+//    protected $files = [];
+
+    /**
+     * Property separator.
+     * @var  string
+     */
+    protected $separator = '.';
+
+    /**
+     * name
+     * @var string
+     */
+    protected $name;
+
+    /**
+     * formats
+     * @var array
+     */
+    protected static $formats = ['json', 'php', 'ini', 'yml'];
+
+    const FORMAT_JSON = 'json';
+    const FORMAT_PHP = 'php';
+    const FORMAT_INI = 'ini';
+    const FORMAT_YML = 'yml';
+
+    /**
+     * __construct
+     * @param mixed $data
+     * @param string $format
+     * @param string $name
+     */
+    public function __construct ($data, $format = self::FORMAT_PHP, $name = 'box1')
+    {
+        // Optionally load supplied data.
+        if (is_array($data) || is_object($data)) {
+            $this->bindData($this->data, $data);
+        } elseif ($data && is_string($data)) {
+            $this->load($data, $format);
+        }
+
+        $this->name = $name;
+    }
+
+    /**
+     * set
+     * @param string $path
+     * @param mixed $value
+     * @return mixed
+     */
+    public function set($path, $value)
+    {
+        if (is_array($value) || is_object($value)) {
+            $value = self::dataToArray($value, true);
+        }
+
+        self::setByPath($this->data, $path, $value, $this->separator);
+
+        return $this;
+    }
+
+    /**
+     * get
+     * @param string $path
+     * @param string $default
+     * @return mixed
+     */
+    public function get($path, $default = null)
+    {
+        $result = self::getByPath($this->data, $path, $this->separator);
+
+        return $result !== null ? $result : $default;
+    }
+
+    public function exists($path)
+    {
+        return $this->get($path) !== null;
+    }
+
+    public function has($path)
+    {
+        return $this->exists($path);
+    }
+
+    public function reset()
+    {
+        $this->data = [];
+
+        return $this;
+    }
+
+    /**
+     * Clear all data.
+     * @return  static
+     */
+    public function clear()
+    {
+        return $this->reset();
+    }
+
+    public function toObject($class = 'stdClass')
+    {
+        return static::dataToObject($this->data, $class);
+    }
+
+    /**
+     * get all Data
+     * @return array
+     */
+    public function all()
+    {
+        return $this->data;
+    }
+    public function toArray()
+    {
+        return $this->all();
+    }
+
+    /**
+     * @return string
+     */
+    public function getSeparator()
+    {
+        return $this->separator;
+    }
+
+    /**
+     * @param string $separator
+     */
+    public function setSeparator($separator)
+    {
+        $this->separator = $separator;
+    }
+
+    /**
+     * @return array
+     */
+    public static function getFormats()
+    {
+        return self::$formats;
+    }
+
+    /**
+     * setName
+     * @param $value
+     * @return void
+     */
+    public function setName($value)
+    {
+        $this->name = $value;
+    }
+
+    /**
+     * getName
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+// @method -v public -p {string|array:file, string:format='php'} -r bool load
+
+    /**
+     * load
+     * @param string|array $data
+     * @param string $format = 'php'
+     * @return static
+     * @throws \RangeException
+     */
+    public function load( $data, $format = self::FORMAT_PHP)
+    {
+        if ( is_string($data) && in_array($format, static::$formats) ) {
+            switch ( $format ) {
+                case self::FORMAT_YML:
+                    $this->loadYaml($data);
+                    break;
+
+                case self::FORMAT_JSON:
+                    $this->loadJson($data);
+                    break;
+
+                case self::FORMAT_INI:
+                    $this->loadIni($data);
+                    break;
+
+                case self::FORMAT_PHP:
+                default:
+                    $this->loadArray($data);
+                    break;
+            }
+
+        } else if ( is_array($data) ) {
+            $this->bindData($this->data, $data);
+        } else {
+            throw new \RangeException('params error!!');
+        }
+
+        return $this;
+    }
+
+    /**
+     * load data form yml file
+     * @param $data
+     * @throws \RuntimeException
+     * @return static
+     */
+    public function loadYaml($data)
+    {
+        $array  = self::parseYaml(trim($data));
+
+        return $this->bindData($this->data, $array);
+    }
+
+    /**
+     * load data form php file or array
+     * @param array|string $data
+     * @return static
+     */
+    public function loadArray($data)
+    {
+        if ( is_string($data) && is_file($data) ) {
+            $data = require $data;
+        }
+
+        if ( !is_array($data) ) {
+            throw new \RuntimeException('param type error!');
+        }
+
+        return $this->bindData($this->data, $data);
+    }
+
+    /**
+     * load data form php file or array
+     * @param array|string $data
+     * @return static
+     */
+    public function loadObject($data)
+    {
+        if ( !is_object($data) ) {
+            throw new \RuntimeException('param type error!');
+        }
+
+        return $this->bindData($this->data, $data);
+    }
+
+    /**
+     * load data form ini file
+     * @param $data
+     * @return static
+     */
+    public function loadIni($data)
+    {
+        if ( !is_string($data) ) {
+            throw new \RuntimeException('param type error! must is string.');
+        }
+
+        if ( file_exists($data) ) {
+            $data = file_get_contents($data);
+        }
+
+        $data = parse_ini_string($data);
+
+        return $this->bindData($this->data, $data);
+    }
+
+    /**
+     * load data form json file
+     * @param $data
+     * @return DataCollector
+     * @throws \RuntimeException
+     */
+    public function loadJson($data)
+    {
+         return $this->bindData($this->data, self::parseJson($data));
+    }
+
+    /**
+     * @param $parent
+     * @param $data
+     * @param bool|false $raw
+     * @return $this
+     */
+    protected function bindData(&$parent, $data, $raw = false)
+    {
+        // Ensure the input data is an array.
+        if (!$raw) {
+            $data = self::dataToArray($data, true);
+        }
+
+        foreach ($data as $key => $value) {
+            if ($value === null) {
+                continue;
+            }
+
+            if (is_array($value)) {
+                if (!isset($parent[$key])) {
+                    $parent[$key] = array();
+                }
+
+                $this->bindData($parent[$key], $value);
+            } else {
+                $parent[$key] = $value;
+            }
+        }
+
+        return $this;
+    }
+
+    public function getKeys()
+    {
+        return array_keys($this->data);
+    }
+
+    public function jsonSerialize()
+    {
+        return $this->data;
+    }
+
+    public function getIterator()
+    {
+        return new \RecursiveArrayIterator($this->data);
+    }
+
+    /**
+     * Count elements of the data object
+     * @return  integer  The custom count as an integer.
+     * @link    http://php.net/manual/en/countable.count.php
+     */
+    public function count()
+    {
+        return count($this->data);
+    }
+
+    public function offsetExists($offset)
+    {
+        return $this->exists($offset);
+    }
+
+    /**
+     * Gets an offset in the iterator.
+     * @param   mixed  $offset  The array offset.
+     * @return  mixed  The array value if it exists, null otherwise.
+     */
+    public function offsetGet($offset)
+    {
+        return $this->get($offset);
+    }
+
+    /**
+     * Sets an offset in the iterator.
+     *
+     * @param mixed $offset
+     * @param mixed $value The array value.
+     */
+    public function offsetSet($offset, $value)
+    {
+        $this->set($offset, $value);
+    }
+
+    /**
+     * Unsets an offset in the iterator.
+     * @param   mixed  $offset  The array offset.
+     * @return  void
+     */
+    public function offsetUnset($offset)
+    {
+        $this->set($offset, null);
+    }
+
+    public function __clone()
+    {
+        $this->data = unserialize(serialize($this->data));
+    }
+
+    public function __get($name)
+    {
+        return $this->get($name);
+    }
+
+    public function __set($name, $value)
+    {
+        return $this->set($name, $value);
+    }
+
+    public function __isset($name)
+    {
+        return $this->exists($name);
+    }
+
+//////
+///////////////////////////// helper /////////////////////////
+//////
+
+/**
+     * Get data from array or object by path.
+     *
+     * Example: `DataCollector::getByPath($array, 'foo.bar.yoo')` equals to $array['foo']['bar']['yoo'].
+     *
+     * @param mixed  $data      An array or object to get value.
+     * @param mixed  $path      The key path.
+     * @param string $separator Separator of paths.
+     *
+     * @return  mixed Found value, null if not exists.
+     */
+    public static function getByPath(array $data, $path, $separator = '.')
+    {
+        $nodes = static::getPathNodes($path, $separator);
+
+        if (!$nodes) {
+            return null;
+        }
+
+        $dataTmp = $data;
+
+        foreach ($nodes as $arg) {
+            if (is_object($dataTmp) && isset($dataTmp->$arg)) {
+                $dataTmp = $dataTmp->$arg;
+            } elseif (
+                ( is_array($dataTmp) || $dataTmp instanceof \ArrayAccess)
+                 AND isset($dataTmp[$arg])
+            ) {
+                $dataTmp = $dataTmp[$arg];
+            } else {
+                return null;
+            }
+        }
+
+        return $dataTmp;
+    }
+
+    /**
+     * setByPath
+     *
+     * @param mixed  &$data
+     * @param string $path
+     * @param mixed  $value
+     * @param string $separator
+     *
+     * @return  boolean
+     */
+    public static function setByPath(array &$data, $path, $value, $separator = '.')
+    {
+        $nodes = static::getPathNodes($path, $separator);
+
+        if (!$nodes) {
+            return false;
+        }
+
+        $dataTmp = &$data;
+
+        foreach ($nodes as $node) {
+            if (is_array($dataTmp)) {
+                if (empty($dataTmp[$node])) {
+                    $dataTmp[$node] = array();
+                }
+
+                $dataTmp = &$dataTmp[$node];
+            } else {
+                // If a node is value but path is not go to the end, we replace this value as a new store.
+                // Then next node can insert new value to this store.
+                $dataTmp = array();
+            }
+        }
+
+        // Now, path go to the end, means we get latest node, set value to this node.
+        $dataTmp = $value;
+
+        return true;
+    }
+
+    /**
+     * @param string $path
+     * @param string $separator
+     * @return  array
+     */
+    public static function getPathNodes($path, $separator = '.')
+    {
+        return array_values(array_filter(explode($separator, $path), 'strlen'));
+    }
+
+    /**
+     * @param $data
+     * @param bool|false $recursive
+     * @return array
+     */
+    public static function dataToArray($data, $recursive = false)
+    {
+        // Ensure the input data is an array.
+        if ($data instanceof \Traversable) {
+            $data = iterator_to_array($data);
+        } elseif (is_object($data)) {
+            $data = get_object_vars($data);
+        } else {
+            $data = (array) $data;
+        }
+
+        if ($recursive) {
+            foreach ($data as &$value) {
+                if (is_array($value) || is_object($value)) {
+                    $value = static::dataToArray($value, $recursive);
+                }
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     * @param $array
+     * @param string $class
+     * @return mixed
+     */
+    public static function dataToObject($array, $class = 'stdClass')
+    {
+        $object = new $class;
+
+        foreach ($array as $k => $v) {
+            $object->$k = is_array($v) ? static::dataToObject($v, $class) : $v;
+        }
+
+        return $object;
+    }
+
+    /**
+     * @param $data
+     * @return array
+     * @throws \RuntimeException
+     */
+    public static function parseJson($data)
+    {
+        if ( !is_string($data) ) {
+            throw new \RuntimeException('param type error! must is string.');
+        }
+
+        if ( !$data ) {
+            return [];
+        }
+
+        if ( file_exists($data) ) {
+            $data = file_get_contents($data);
+            $pattern = [
+                '!/\*[^*]*\*+([^/][^*]*\*+)*/!', //去除文件中的注释
+                '/\/\/.*?[\r\n]/is',             //去掉所有单行注释
+                "/(?!\w)\s*?(?!\w)/is"           // 多个空格 换成一个
+            ];
+            $replace = ['','',''];
+            $data = preg_replace($pattern, $replace, $data);
+        }
+
+        $data = json_decode(trim($data), true);
+        if ( json_last_error() === JSON_ERROR_NONE ) {
+            return $data;
+        } else {
+            throw new \RuntimeException('json config data parse error :'.json_last_error_msg());
+        }
+    }
+
+    /**
+     * parse Yaml
+     * @param string $data              Waiting for the parse data
+     * @param bool $supportImport       Simple support import other config by tag 'import'. must is bool.
+     * @param callable $pathHandler     When the second param is true, this param is valid.
+     * @param string $fileDir           When the second param is true, this param is valid.
+     * @return array
+     */
+    public static function parseYaml($data, $supportImport=false, callable $pathHandler=null, $fileDir = '')
+    {
+        if ( !is_string($data) ) {
+            throw new \RuntimeException('param type error! must is string.');
+        }
+
+        if ( !$data ) {
+            return [];
+        }
+
+        $parserClass = '\Symfony\Component\Yaml\Parser';
+
+        if ( !class_exists($parserClass) ) {
+            throw new \RuntimeException("yml format parser Class $parserClass don't exists! please install package 'symfony/yaml'.");
+        }
+
+        if ( is_file($data) ) {
+            $fileDir = $fileDir ? : dirname($data);
+            $data = file_get_contents($data);
+        }
+
+        /** @var \Symfony\Component\Yaml\Parser $parser */
+        $parser = new $parserClass;
+        $array = $parser->parse(trim($data));
+//        $array  = json_decode(json_encode($array));
+
+        // import other config by tag 'import'
+        if ( $supportImport===true && !empty($array['import']) && is_string($array['import']) ) {
+            $importFile = trim($array['import']);
+
+            // if needed custom handle $importFile path. e.g: Maybe it uses custom alias path
+            if ( $pathHandler && is_callable($pathHandler) ) {
+                $importFile = call_user_func($pathHandler, $importFile);
+            }
+
+            // if $importFile is not exists AND $importFile is not a absolute path AND have $parentFile
+            if ( $fileDir && !file_exists($importFile) && $importFile[0] !== '/') {
+                $importFile = $fileDir . '/' . trim($importFile, './');
+            }
+
+            // $importFile is file
+            if ( is_file($importFile) ) {
+
+                unset($array['import']);
+                $data     = file_get_contents($importFile);
+                $imported = $parser->parse(trim($data));
+                $array    = array_merge($imported, $array);
+            } else {
+                throw new \RuntimeException("needed imported file $importFile don't exists!");
+            }
+        }
+
+        unset($parser);
+
+        return $array;
+    }
+}

+ 32 - 0
src/DataConst.php

@@ -0,0 +1,32 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Inhere
+ * Date: 2016/2/19 0019
+ * Time: 23:35
+ */
+
+namespace slimExtend;
+
+/**
+ * Class DataConst
+ * @package slimExtend
+ */
+abstract class DataConst
+{
+    const FLASH_MSG_KEY         = 'alert_messages';
+    const FLASH_OLD_INPUT_KEY   = 'old_inputs';
+
+    // php data type
+    const TYPE_INT              = 'int';
+    const TYPE_INTEGER          = 'integer';
+    const TYPE_FLOAT            = 'float';
+    const TYPE_DOUBLE           = 'double';
+    const TYPE_BOOL             = 'bool';
+    const TYPE_BOOLEAN          = 'boolean';
+    const TYPE_STRING           = 'string';
+
+    const TYPE_ARRAY            = 'array';
+    const TYPE_OBJECT           = 'object';
+    const TYPE_RESOURCE         = 'resource';
+}

+ 168 - 0
src/Slim.php

@@ -0,0 +1,168 @@
+<?php
+
+/**
+ * Class Slim
+ * @date  2016.2.17
+ *
+ * ---------------
+ * How to quickly get a service instance?
+ * e.g:
+ * get request service instance.
+ *
+ * ```
+ *     Slim::get('request')
+ * equal
+ *     Slim::$app->request // by the magic method { @link \slimExtend\base\App::__get() }
+ * equal
+ *     Slim::$app->request() // by the magic method { @link \Slim\App::__call() }
+ * ```
+ */
+abstract class Slim
+{
+    /**
+     * @var $app \slimExtend\base\App
+     */
+    public static $app;
+
+    /**
+     * path alias
+     * @var array
+     */
+    protected static $aliases = [
+        '@project' => PROJECT_PATH,
+        '@data'    => PROJECT_PATH . DIR_SEP . 'data',
+        '@public'  => PROJECT_PATH . DIR_SEP . 'public',
+        '@assets'  => PROJECT_PATH . DIR_SEP . 'public' . DIR_SEP . 'assets',
+        '@src'     => PROJECT_PATH . DIR_SEP . 'src',
+        '@sources' => PROJECT_PATH . DIR_SEP . 'sources',
+        '@temp'    => PROJECT_PATH . DIR_SEP . 'temp',
+    ];
+
+    /**
+     * set/get path alias
+     * @param array|string $path
+     * @param string|null $value
+     * @return bool|string
+     */
+    public static function alias($path, $value=null)
+    {
+        // get path by alias
+        if ( is_string($path) && !$value ) {
+            // don't use alias
+            if ( $path[0] !== '@' ) {
+                return $path;
+            }
+
+            $path = str_replace(['/','\\'], DIR_SEP , $path);
+
+            // only a alias. e.g. @project
+            if ( !strpos($path, DIR_SEP) ) {
+                return isset(self::$aliases[$path]) ? self::$aliases[$path] : $path;
+            }
+
+            // have other partial. e.g: @project/temp/logs
+            $realPath = $path;
+            list($alias, $other) = explode(DIR_SEP, $path, 2);
+
+            if ( isset(self::$aliases[$alias]) ) {
+                $realPath = self::$aliases[$alias] . DIR_SEP . $other;
+            }
+
+            return $realPath;
+        }
+
+        if ( $path && $value && is_string($path) && is_string($value) ) {
+            $path = [$path => $value];
+        }
+
+        // custom set path's alias. e.g: Slim::alias([ 'alias' => 'path' ]);
+        if ( is_array($path) ) {
+            foreach ($path as $alias => $realPath) {
+                self::$aliases[$alias] = $realPath;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * @return array
+     */
+    public static function getAliases()
+    {
+        return self::$aliases;
+    }
+
+    /**
+     * @param $id
+     * @return mixed
+     */
+    public static function get($id)
+    {
+        if ( !self::$app ) {
+            return null;
+        }
+
+        return self::$app->$id;
+    }
+
+    /**
+     * @param $id
+     * @param $value
+     */
+    public static function set($id, $value)
+    {
+        if ( self::$app ) {
+            self::$app->$id = $value;
+        }
+    }
+
+    /**
+     * @return \slimExtend\DataCollector
+     */
+    public static function config()
+    {
+        return self::$app->getContainer()->get('config');
+    }
+
+    /**
+     * @return \Monolog\Logger
+     */
+    public static function logger($name='logger')
+    {
+        return self::$app->getContainer()->get($name);
+    }
+
+    /**
+     * Allows the use of a static method call registered in container service.
+     * e.g:
+     * ```
+     * // get request service instance.
+     *
+     *     Slim::get('request')
+     * equal
+     *     Slim::request()
+     * equal
+     *     Slim::$di->request
+     * equal
+     *     Slim::$di->get('request')
+     * ```
+     * @param $method
+     * @param array $args
+     * @return mixed
+     */
+//    public static function __callStatic($method, array $args)
+//    {
+//         $prefix = substr($method, 0, 3);
+//         $id = lcfirst(substr($method, 3));
+//
+//        $id = lcfirst($method);
+//
+//         if ( $prefix === 'get' AND self::$di->has($id) ) {
+//        if ( self::$di->has($id) ) {
+//            return self::$di->get($id);
+//        }
+//
+//        throw new \RuntimeException("Called static method [$method] don't exists!");
+//    }
+}

+ 36 - 0
src/ValidateRequest.php

@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Created by PhpStorm.
+ * User: Inhere
+ * Date: 2016/2/19 0019
+ * Time: 23:35
+ */
+
+namespace slimExtend;
+
+use slimExtend\validate\ValidatorTrait;
+use Slim\Http\Request;
+
+/**
+ * Class ValidateRequest
+ * @package slimExtend
+ *
+ */
+abstract class ValidateRequest
+{
+    use ValidatorTrait;
+
+    /**
+     * @var array
+     */
+    protected $data = [];
+
+    /**
+     * @param Request|null $request
+     */
+    public function __construct(Request $request=null)
+    {
+        $this->data = $request->getParams();
+    }
+}

+ 59 - 0
src/base/App.php

@@ -0,0 +1,59 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Inhere
+ * Date: 2016/2/19 0019
+ * Time: 23:35
+ */
+
+namespace slimExtend\base;
+
+use Slim\App as SlimApp;
+
+/**
+ * Class App
+ * @package slimExtend\base
+ *
+ * @property-read Request                    request
+ *
+ * @property \Slim\Container                 container
+ * @property \Monolog\Logger                 logger
+ * @property \slimExtend\base\User       user
+ * @property \Slim\Flash\Messages            flash
+ * @property \slimExtend\base\Language   language
+ *
+ *
+ * @property \slimExtend\DataCollector   config
+ * @property \slimExtend\DataCollector   pageSet
+ * @property \slimExtend\DataCollector   pageAttr
+ *
+ */
+class App extends SlimApp
+{
+    /**
+     * @param $id
+     * @return \Interop\Container\ContainerInterface|mixed
+     */
+    public function __get($id)
+    {
+        if ($id === 'container') {
+            return $this->getContainer();
+        }
+
+        if ( $this->getContainer()->has($id) ) {
+            return $this->getContainer()->get($id);
+        }
+
+        throw new \InvalidArgumentException("Getting a unknown property [$id] in class.");
+    }
+
+    /**
+     * @param $id
+     * @param $value
+     * @return mixed
+     */
+    public function __set($id, $value)
+    {
+        return $this->getContainer()->$id = $value;
+    }
+}

+ 268 - 0
src/base/BaseController.php

@@ -0,0 +1,268 @@
+<?php
+
+namespace slimExtend\base;
+
+use Slim;
+use slimExtend\helpers\TplHelper;
+
+/**
+ * Class BaseController
+ * @package slimExtend\base
+ */
+abstract class BaseController extends RestFulController
+{
+    /**
+     * @var string
+     */
+    public $actionSuffix = 'Action';
+
+    /**
+     * @var string
+     */
+    public $defaultAction = 'index';
+
+    /**
+     * current controller's default templates path.
+     * tpl file: `$this->tplPath . '/' . $view`
+     * @var string
+     */
+    protected $tplPath = '';
+
+    /**
+     * If the files in the child directory, set the child directory.
+     * if not empty,tpl file: `$this->tplPath . '/' . $tplPathPrefix . '/' . $view`
+     * @var string
+     */
+    protected $tplPathPrefix = '';
+
+    /**
+     * templates globals var key name, default is `DEFAULT_VAR_KEY`
+     * @var array
+     */
+    protected $tplGlobalVarKey = '';
+
+    const DEFAULT_VAR_KEY = '_globals';
+
+    /**
+     * templates globals var
+     * @var array
+     */
+    protected $tplGlobalVarList = [];
+
+    /**
+     * __construct
+     */
+    public function __construct()
+    {
+        // save to container
+        Slim::set('controller', $this);
+
+        parent::__construct();
+    }
+
+    /**
+     * php tpl render
+     * @param $view
+     * @param array $args
+     * @param Response|null $response
+     * @return Response
+     */
+    protected function render($view, Response $response, array $args = [])
+    {
+        $response = $response ?: Slim::get('response');
+        $settings = Slim::get('settings')['renderer'];
+        $view  = $this->getViewPath($view, $settings);
+
+        // add tpl global var
+        list($varKey, $varList) = $this->handleGlobalVar([], $settings);
+        $args[$varKey] = $varList;
+
+        return Slim::get('renderer')->render($response, $view, $args);
+    }
+
+    const RETURN_RENDERED = 1;
+    const RETURN_RESPONSE = 2;
+    const RETURN_BOTH     = 3;
+
+    /**
+     * twig tpl render
+     * @param $view
+     * @param Response|null $response
+     * @param array $args
+     * @param int $return
+     * @return Response
+     */
+    protected function renderTwig($view, Response $response, array $args = [], $return=self::RETURN_RESPONSE)
+    {
+//        $response = $response ?: Slim::get('response');
+        $settings = Slim::get('settings')['twigRenderer'];
+        $view  = $this->getViewPath($view, $settings);
+
+        // use twig render
+        $twig = Slim::get('twigRenderer');
+
+        $globalVar = $this->addTwigGlobalVar();
+        list($globalKey, $globalVar) = $this->handleGlobalVar($globalVar, $settings);
+
+        // add tpl global var
+        $twig->getEnvironment()->addGlobal($globalKey, $globalVar);
+
+        // add custom extension
+        // $twig->addExtension(new \slimExtend\twig\TwigExtension( $c['request'], $c['csrf'] ));
+
+        // Fetch rendered template {@see \Slim\Views\Twig::fetch()}
+        $rendered = $twig->fetch($view, $args);
+
+        if ( $return === self::RETURN_RENDERED ) {
+            return $rendered;
+        } elseif ( $return === self::RETURN_BOTH ) {
+            $response->getBody()->write($rendered);
+
+            return [$response, $rendered];
+        }
+
+        // return response
+        $response->getBody()->write($rendered);
+
+        return $response;
+    }
+
+    /**
+     * @return array
+     */
+    protected function addTwigGlobalVar()
+    {
+        return [
+            'user'     => Slim::$app->user,
+            'page'     => Slim::get('pageSet')->loadObject(Slim::get('pageAttr')),
+            'config'   => Slim::get('config'),
+            'lang'     => Slim::get('language'),
+            'helper'   => new TplHelper,
+            'messages' => Slim::$app->request->getMessage(),
+        ];
+    }
+
+    /**
+     * @param array $varList
+     * @param array $settings
+     * @return array
+     */
+    protected function handleGlobalVar(array $varList=[], array $settings)
+    {
+        // form settings
+        if ( !empty($settings['global_var_list']) ) {
+            $varList = $varList ?
+                array_merge($varList, $settings['global_var_list']) :
+                $settings['global_var_list'];
+        }
+
+        // form current controller
+        if ( $this->tplGlobalVarList ) {
+            $varList = $varList ?
+                array_merge($varList, $this->tplGlobalVarList) :
+                $this->tplGlobalVarList;
+        }
+
+        // global var name at template
+        if ( $this->tplGlobalVarKey ) {
+            $varKey = $this->tplGlobalVarKey;
+        } else {
+            $varKey = empty($settings['global_var_key']) ? '' : $settings['global_var_key'];
+
+            if ( !$varKey ) {
+                $varKey = self::DEFAULT_VAR_KEY;
+            }
+        }
+
+        return [$varKey, $varList];
+    }
+
+    /**
+     * @param Request $request
+     * @return string
+     */
+    public function csrfField(Request $request)
+    {
+        // CSRF token name and value
+        $nameKey  = Slim::get('csrf')->getTokenNameKey();
+        $valueKey = Slim::get('csrf')->getTokenValueKey();
+        $name  = $request->getAttribute($nameKey);
+        $value = $request->getAttribute($valueKey);
+
+        return <<<EOF
+<input type="hidden" name="$nameKey" value="$name">
+<input type="hidden" name="$valueKey" value="$value">
+EOF;
+    }
+
+    /**
+     * get current controller's default templates path
+     * @return string
+     */
+    protected function getTplPath()
+    {
+        if ( !$this->tplPath ) {
+            $calledClass = get_class($this);
+            $nodes = explode('\\', trim($calledClass,'\\'));
+            $nodes = array_slice($nodes, 2); // remove `app` e.g: `app\controllers\SimpleAuth`
+            $nodePath = implode('/', array_map(function($node){
+                return lcfirst($node);
+            }, $nodes));
+
+            $prefix = $this->tplPathPrefix ? '/'.$this->tplPathPrefix : '';
+            $this->tplPath = $prefix . '/' . $nodePath;
+        }
+
+        return $this->tplPath;
+    }
+
+    /**
+     * get action's view file path
+     * @param  string $view
+     * @param  array $settings
+     * @return string
+     */
+    protected function getViewPath($view, array $settings)
+    {
+        $tpl_suffix = $settings['tpl_suffix'];
+        $suffix = get_extension($view);
+
+        // no extension
+        if ( !$suffix ||  $suffix !== trim($tpl_suffix,'. ') ) {
+            $view .= '.' . trim($tpl_suffix,'. ');
+        }
+
+        // if only file name, will auto add this tplPath.
+        // e.g: $this->tplPath = '/blog/news'; $view = 'index.twig' --> `/blog/news/index.twig`
+        if ( $view[0] !== '/' ) {
+            $view = $this->getTplPath() . '/' . $view;
+        }
+
+        return $view;
+    }
+
+    /**
+     * beforeInvoke
+     *  Might want to customize to perform the action name
+     * @param  Request $request
+     * @param  Response $response
+     * @param  array $args
+     * @return mixed
+     */
+    protected function beforeInvoke(Request $request, Response $response, array $args)
+    {
+        $action = $this->defaultAction;
+
+        if ( !empty($args['action']) ) {
+            $action = $args['action'];
+        }
+
+        $action .= ucfirst($this->actionSuffix);
+
+        if ( method_exists($this, $action) ) {
+            return $this->$action($request, $response, $args);
+        }
+
+        return false;
+    }
+}

+ 63 - 0
src/base/Collection.php

@@ -0,0 +1,63 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Inhere
+ * Date: 2016/3/3 0003
+ * Time: 23:05
+ */
+
+namespace slimExtend\base;
+
+use Slim\Collection as SlimCollection;
+
+/**
+ * Class Collection
+ * @package slimExtend\base
+ */
+class Collection extends SlimCollection
+{
+    /**
+     * @param array $data
+     * @return $this
+     */
+    public function sets(array $data)
+    {
+        foreach ($data as $key => $value) {
+            $this->set($key, $value);
+        }
+
+        return $this;
+    }
+
+    public function toArray()
+    {
+        return $this->all();
+    }
+
+    /**
+     * @param string $name
+     * @param mixed $value
+     * @return $this
+     */
+    public function set($name, $value)
+    {
+        parent::set($name, $value);
+
+        return $this;
+    }
+
+    public function __get($name)
+    {
+        return $this->get($name);
+    }
+
+    public function __set($name, $value)
+    {
+        return $this->set($name, $value);
+    }
+
+    public function __isset($name)
+    {
+        return $this->has($name);
+    }
+}

+ 274 - 0
src/base/Language.php

@@ -0,0 +1,274 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: inhere
+ * Date: 2016/2/24
+ * Time: 15:04
+ */
+
+namespace slimExtend\base;
+
+use slimExtend\DataCollector;
+use Slim;
+
+/**
+ * Class Language
+ * @package slimExtend\base
+ *
+ * property $type
+ *  if type equal to 1, use monofile. this is default.
+ *
+ *  if type equal to 2, use multifile.
+ *
+ *
+ */
+class Language extends DataCollector
+{
+    /**
+     * current use language
+     * @var string
+     */
+    protected $lang = 'en';
+
+    /**
+     * language config file path
+     * @var string
+     */
+    protected $path = '@src/resources/languages';
+
+    /**
+     * type of language config
+     * @var int
+     */
+    protected $type = 1;
+
+    /**
+     * default file name, when use multifile. (self::type == self::TYPE_MULTIFILE)
+     * @var string
+     */
+    protected $defaultFile = 'default';
+
+    /**
+     * file separator char, when use multifile.
+     * e.g:
+     *  Slim::$app->language->tran('app:createPage');
+     * will fetch `createPage` value at the file `{$this->path}/{$this->lang}/app.yml`
+     * @var string
+     */
+    protected $fileSeparator = ':';
+
+    /**
+     * loaded main language config file, data saved in {@link self::$data}
+     * @var string
+     */
+    protected $mainFile = '';
+
+    /**
+     * loaded other config file list.
+     * @var array
+     */
+    protected $otherFiles = [];
+
+    /**
+     * saved other config file data
+     * @var DataCollector[]
+     */
+    protected $others = [];
+
+    // use monofile. e.g: at config dir `{$this->path}/en.yml`
+    const TYPE_MONOFILE  = 1;
+
+    // use multifile. e.g: at config dir `{$this->path}/en/default.yml` `en/app.yml`
+    const TYPE_MULTIFILE = 2;
+
+    /**
+     * @param array $options
+     */
+    public function __construct(array $options)
+    {
+        parent::__construct(null, self::FORMAT_PHP, 'language');
+
+        $this->prepare($options);
+    }
+
+    protected function prepare($options)
+    {
+        foreach (['lang', 'path', 'defaultFile'] as $key) {
+            if ( isset($options[$key]) ) {
+                $this->$key = $options[$key];
+            }
+        }
+
+        if ( isset($options['type']) && in_array($options['type'], $this->getTypes()) ) {
+            $this->type = (int)$options['type'];
+        }
+
+        // maybe use path alias
+        $this->path = Slim::alias($this->path);
+
+        $this->mainFile = $this->type === self::TYPE_MONOFILE ?
+            $this->path . DIR_SEP . "{$this->lang}.yml" :
+            $this->getDirectoryFile($this->defaultFile);
+
+        // check
+        if ( !is_file($this->mainFile) ) {
+            throw new \RuntimeException("Main language file don't exists! File: {$this->mainFile}");
+        }
+
+        // load main language file data.
+        $this->loadYaml($this->mainFile);
+    }
+
+    /**
+     * language translate
+     *
+     * 1. allow multi arguments. `tran(string $key , mixed $arg1 , mixed $arg2, ...)`
+     *
+     * @example
+     * ```
+     *  // on language config
+     * userNotFound: user [%s] don't exists!
+     *
+     *  // on code
+     * $msg = Slim::$app->language->tran('userNotFound', 'demo');
+     * ```
+     *
+     * 2. allow fetch other config file data, when use multifile. (`self::$type === self::TYPE_MULTIFILE`)
+     *
+     * @example
+     * ```
+     * // on default config file (e.g. `en/default.yml`)
+     * userNotFound: user [%s] don't exists!
+     *
+     * // on app config file (e.g. `en/app.yml`)
+     * userNotFound: the app user [%s] don't exists!
+     *
+     * // on code
+     * // will fetch value at `en/default.yml`
+     * //output: user [demo] don't exists!
+     * $msg = Slim::$app->language->tran('userNotFound', 'demo');
+     *
+     * // will fetch value at `en/app.yml`
+     * //output: the app user [demo] don't exists!
+     * $msg = Slim::$app->language->tran('app:userNotFound', 'demo');
+     *
+     * ```
+     *
+     * @param $key
+     * @return string
+     */
+    public function translate($key)
+    {
+        if ( !$key ) {
+            throw new \InvalidArgumentException('A lack of parameters or error.');
+        }
+
+        $args    = func_get_args();
+        $args[0] = $this->get($key);
+
+        // if use multifile.
+        if ( $this->type === self::TYPE_MULTIFILE ) {
+           $this->handleMultiFile($key, $args);
+        }
+
+        if ( !$args[0] ) {
+            throw new \InvalidArgumentException('No corresponding configuration of the translator. KEY: ' . $key);
+        }
+
+        // There are multiple parameters?
+        return func_num_args()>1 ? call_user_func_array('sprintf', $args) : $args[0];
+    }
+    public function tran($key)
+    {
+        return call_user_func_array([$this,'translate'], func_get_args());
+    }
+
+    /**
+     * @param $key
+     * @param array $args
+     */
+    protected function handleMultiFile($key, array &$args)
+    {
+        $key = trim($key, $this->fileSeparator);
+
+        // Will try to get the value from the other config file
+        if ( ($pos = strpos($key, $this->fileSeparator)) >0 ) {
+            $name    = substr($key, 0, $pos);
+            $realKey = substr($key,$pos+1);
+
+            // check exists
+            if ( $collector = $this->getOther($name) ) {
+                $args[0] = $collector->get($realKey);
+            }
+        }
+    }
+
+    /**
+     * @param $name
+     * @return string
+     */
+    public function getDirectoryFile($name)
+    {
+        return $this->path . DIR_SEP . $this->lang . DIR_SEP . trim($name) . '.yml';
+    }
+
+    /**
+     * @param $name
+     * @return DataCollector
+     */
+    public function getOther($name)
+    {
+        // the first time fetch, instantiate it
+        if ( !isset($this->others[$name]) ) {
+            $otherFile = $this->getDirectoryFile($name);
+
+            if ( is_file($otherFile) ) {
+                $this->otherFiles[$name]  = $otherFile;
+                $this->others[$name] = new DataCollector($otherFile, self::FORMAT_YML, $name);
+            }
+        }
+
+        return isset($this->others[$name]) ? $this->others[$name] : [];
+    }
+
+    /**
+     * @return string
+     */
+    public function getLang()
+    {
+        return $this->lang;
+    }
+
+    /**
+     * @return string
+     */
+    public function getPath()
+    {
+        return $this->path;
+    }
+
+    /**
+     * @return array
+     */
+    public function getTypes()
+    {
+        return [self::TYPE_MONOFILE, self::TYPE_MULTIFILE];
+    }
+
+    /**
+     * @return int
+     */
+    public function getType()
+    {
+        return $this->type;
+    }
+
+    /**
+     * @return string
+     */
+    public function getDefaultFile()
+    {
+        return $this->defaultFile;
+    }
+
+}

+ 282 - 0
src/base/Model.php

@@ -0,0 +1,282 @@
+<?php
+
+/**
+ * Created by PhpStorm.
+ * User: Inhere
+ * Date: 2016/2/19 0019
+ * Time: 23:35
+ */
+
+namespace slimExtend\base;
+
+use Slim;
+use slimExtend\database\AbstractDriver;
+use slimExtend\validate\ValidatorTrait;
+use Windwalker\Query\Query;
+
+/**
+ * Class BaseModel
+ * @package slimExtend
+ *
+ */
+abstract class Model extends Collection
+{
+    use ValidatorTrait;
+
+    /**
+     * @var array
+     */
+    protected $columns = [
+        // 'id'          => 'int',
+        // 'title'       => 'string',
+        // 'createTime'  => 'int',
+        // 'updateTime'  => 'int',
+    ];
+
+    /**
+     * the table primary key name
+     * @var string
+     */
+    protected static $priKey = 'id';
+
+    /**
+     * callable method List at Sqlite
+     * @var array
+     */
+    protected static $callableList = [
+        'insert', 'update', 'save', 'updateBatch'
+    ];
+
+    /**
+     * format column's data type
+     * @param string $key
+     * @param mixed $value
+     * @return $this|void
+     */
+    public function set($key, $value)
+    {
+        if ( isset($this->columns[$key]) ) {
+            $type = $this->columns[$key];
+
+            if ($type === 'int') {
+                $value = (int)$value;
+            }
+
+            return parent::set($key, $value);
+        }
+
+        return false;
+    }
+
+    /**
+     * @param $data
+     * @return $this
+     */
+    public function load($data)
+    {
+        return $this->sets($data);
+    }
+
+    /**
+     * find record by primary key
+     * @param mixed $priValue
+     * @param string $select
+     * @return mixed
+     */
+    public static function find($priValue, $select='*')
+    {
+        if ( is_array($priValue) ) {
+            $condition = static::$priKey . ' in (' . implode(',', $priValue) . ')';
+        } else {
+            $condition = static::$priKey . '=' . $priValue;
+        }
+
+        return static::findOne($condition, $select);
+    }
+
+    /**
+     * find a record by where condition
+     * @param $where
+     * @param string $select
+     * @return mixed
+     */
+    public static function findOne($where, $select='*')
+    {
+        $query = static::getQuery(true)
+                ->select($select)
+                ->from(static::tableName())
+                ->where($where);
+
+        return static::getDb()->setQuery($query)->loadOne(static::class);
+    }
+
+    /**
+     * @param $where
+     * @param string $select
+     * @return array
+     */
+    public static function findAll($where, $select='*')
+    {
+        $query = static::getQuery(true)
+                ->select($select)
+                ->from(static::tableName())
+                ->where($where);
+
+        return static::getDb()->setQuery($query)->loadAll(null, static::class);
+    }
+
+    /**
+     * @return string
+     */
+    public static function tableName()
+    {
+        $className = lcfirst( basename( str_replace('\\', '/', get_called_class()) ) );
+
+        // '@@' -- is table prefix placeholder
+        // return '@@articles';
+        // if no table prefix
+        // return 'articles';
+
+        return '@@' . $className;
+    }
+
+    /**
+     * @return AbstractDriver
+     */
+    public static function getDb()
+    {
+        return Slim::get('db');
+    }
+
+    /**
+     * @param bool $forceNew
+     * @return Query
+     */
+    public static function getQuery($forceNew=false)
+    {
+        return static::getDb()->newQuery($forceNew);
+    }
+
+    /**
+     * @return bool
+     */
+    public function isNew()
+    {
+        return !($this->has(static::$priKey) && $this->get(static::$priKey, false));
+    }
+
+    public function save($updateNulls = false)
+    {
+        $this->beforeSave();
+
+        return static::getDb()->save( static::tableName(), $this->data, static::$priKey, $updateNulls);
+    }
+
+    /**
+     * @return array|bool|int
+     */
+    public function insert()
+    {
+        $this->beforeInsert();
+
+        $priValue = static::getDb()->insert( static::tableName(), $this->all());
+
+        $this->set(static::$priKey, $priValue);
+
+        return $priValue;
+    }
+
+    /**
+     * update by primary key
+     * @param array $updateColumns only update some columns
+     * @param bool|false $updateNulls
+     * @return bool
+     */
+    public function update($updateColumns = [], $updateNulls = false)
+    {
+        $data = $this->all();
+        $priKey = static::$priKey;
+
+        // only update some columns
+        if ( $updateColumns ) {
+            foreach ($data as $column => $value) {
+                if ( !isset($updateColumns[$column]) ) {
+                    unset($data[$column]);
+                }
+            }
+
+            if ( !isset($data[$priKey]) ) {
+                $data[$priKey] = $this->get($priKey);
+            }
+        }
+
+        $this->beforeUpdate();
+
+        return static::getDb()->update( static::tableName(), $data, $priKey, $updateNulls);
+    }
+
+    /**
+     * @param $data
+     * @param array $conditions
+     * @return bool
+     */
+    public function updateBatch($data, $conditions = [])
+    {
+        return static::getDb()->updateBatch( static::tableName(), $data, $conditions);
+    }
+
+    protected function beforeInsert()
+    {
+        $this->beforeSave();
+    }
+
+    protected function beforeUpdate()
+    {
+        $this->beforeSave();
+    }
+
+    protected function beforeSave()
+    {}
+
+    /**
+     * @return array
+     */
+    public static function callableList()
+    {
+        return static::$callableList;
+    }
+
+    /**
+     * @param $method
+     * @param array $args
+     * @return mixed
+     */
+    // public function __call($method, array $args)
+    // {
+    //     $db = static::getDb();
+    //     array_unshift($args, static::tableName());
+
+    //     if ( in_array($method, static::$callableList) AND method_exists($db, $method) ) {
+    //         return call_user_func_array( [$db, $method], $args);
+    //     }
+
+    //     throw new \RuntimeException("Called method [$method] don't exists!");
+    // }
+
+    /**
+     * @param $method
+     * @param array $args
+     * @return mixed
+     */
+    // public static function __callStatic($method, array $args)
+    // {
+    //     $db = static::getDb();
+    //     array_unshift($args, static::tableName());
+
+    //     if ( in_array($method, static::$callableList) AND method_exists($db, $method) ) {
+    //         return call_user_func_array( [$db, $method], $args);
+    //     }
+
+    //     throw new \RuntimeException("Called static method [$method] don't exists!");
+    // }
+}

+ 210 - 0
src/base/Request.php

@@ -0,0 +1,210 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Inhere
+ * Date: 2016/3/3 0003
+ * Time: 23:05
+ */
+
+namespace slimExtend\base;
+
+use slimExtend\DataConst;
+use slimExtend\validate\StrainerList;
+use Slim;
+use Slim\Http\Request as SlimRequest;
+
+/**
+ * extension Slim's Request class
+ * Class Request
+ * @package slimExtend\base
+ *
+ * @method      string   getRaw()       getRaw($name, $default = null)      Get raw data
+ * @method      integer  getInt()       getInt($name, $default = null)      Get a signed integer.
+ * @method      integer  getNumber()    getNumber($name, $default = null)   Get an unsigned integer.
+ * @method      float    getFloat()     getFloat($name, $default = null)    Get a floating-point number.
+ * @method      boolean  getBool()      getBool($name, $default = null)     Get a boolean.
+ * @method      boolean  getBoolean()   getBoolean($name, $default = null)  Get a boolean.
+ * @method      string   getString()    getString($name, $default = null)
+ * @method      string   getEmail()     getEmail($name, $default = null)
+ * @method      string   getUrl()       getUrl($name, $default = null)      Get URL
+ *
+ */
+class Request extends SlimRequest
+{
+    /**
+     * @var array
+     */
+    protected $keywords = [
+      'int', 'float', 'bool', 'boolean', 'array', 'object', 'string'
+    ];
+
+    /**
+     * return raw data
+     */
+    const FILTER_RAW = 'raw';
+
+    /**
+     * @var array
+     */
+    protected $filterList = [
+        'raw'     => '',                                // return raw
+
+        'int'     => 'int',                             // (int)$var
+        'float'   => 'float',                           // (float)$var or floatval($var)
+        'bool'    => 'bool',                            // (bool)$var
+        'boolean' => 'bool',                            // (bool)$var
+        'string'  => 'trim',                            // trim($var)
+
+        'number'  => StrainerList::class . '::abs',     // abs((int)$var)
+        'email'   => StrainerList::class . '::email',   // filter_var($var ,FILTER_SANITIZE_URL);
+        'url'     => StrainerList::class . '::url',     // filter_var($var ,FILTER_SANITIZE_URL);
+    ];
+
+    /**
+     * getParams() alias method
+     * @return array
+     */
+    public function all()
+    {
+        return $this->getParams();
+    }
+
+    /**
+     * @return array
+     */
+    public function getMessage()
+    {
+        $messageList = [];
+        $messages = Slim::$app->flash->getMessage(DataConst::FLASH_MSG_KEY) ?: [];
+
+        foreach ($messages as $alert) {
+            $messageList[] = json_decode($alert, true);
+        }
+
+        return $messageList;
+    }
+
+    /**
+     * @param array $default
+     * @return array
+     */
+    public function getOldInput($default = [])
+    {
+        if ( $data = Slim::get('flash')->getMessage(DataConst::FLASH_OLD_INPUT_KEY) ) {
+            return json_decode($data[0], true);
+        }
+
+        return $default;
+    }
+
+    /**
+     * @param $name
+     * @param null $default
+     * @param string $filter
+     * @return mixed
+     */
+    public function get($name, $default = null, $filter = 'raw')
+    {
+        if ( !isset($this->getParams()[$name]) ) {
+            return $default;
+        }
+
+        $var = $this->getParams()[$name];
+
+        return $this->doFilter($var, $filter, $default);
+    }
+
+    /**
+     * Get part of it - 获取其中的一部分
+     * @param array $needKeys
+     * @return array
+     */
+    public function getPart(array $needKeys=[])
+    {
+        $needed = [];
+
+        foreach ($needKeys as $key) {
+            $needed[$key] = $this->getParam($key);
+        }
+
+        return $needKeys ? $needed : $this->getParams();
+    }
+
+    /**
+     * @param $name
+     * @param array $arguments
+     * @return mixed
+     */
+    public function __call($name, array $arguments)
+    {
+        if (substr($name, 0, 3) === 'get') {
+            $filter = substr($name, 3);
+            $default = null;
+
+            if (isset($arguments[1])) {
+                $default = $arguments[1];
+            }
+
+            return $this->get($arguments[0], $default, lcfirst($filter));
+        }
+
+        throw new \BadMethodCallException("Method $name is not a valid method");
+    }
+
+    /**
+     * @param $var
+     * @param $filter
+     * @param null $default
+     * @return mixed|null
+     */
+    protected function doFilter($var, $filter, $default = null)
+    {
+        if ( $filter === self::FILTER_RAW ) {
+            return $var;
+        }
+
+        if ( !isset($this->filterList[$filter]) ) {
+
+            // is custom callable filter
+            if ( is_callable($filter) ) {
+                return call_user_func($filter, $var);
+            }
+
+            return $default;
+        }
+
+        $filter = $this->filterList[$filter];
+
+        if ( !in_array($filter, $this->keywords) ) {
+            return call_user_func($filter, $var);
+        }
+
+        switch ( lcfirst(trim($filter)) ) {
+            case DataConst::TYPE_ARRAY :
+                $var = (array)$var;
+                break;
+            case DataConst::TYPE_BOOL :
+            case DataConst::TYPE_BOOLEAN :
+                $var = (bool)$var;
+                break;
+            case DataConst::TYPE_DOUBLE :
+            case DataConst::TYPE_FLOAT :
+                $var = (float)$var;
+                break;
+            case DataConst::TYPE_INT :
+            case DataConst::TYPE_INTEGER :
+                $var = (int)$var;
+                break;
+            case DataConst::TYPE_OBJECT :
+                $var = (object)$var;
+                break;
+            case DataConst::TYPE_STRING :
+                $var = trim((string)$var);
+                break;
+            default:
+                break;
+        }
+
+        return $var;
+    }
+}

+ 94 - 0
src/base/Response.php

@@ -0,0 +1,94 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Inhere
+ * Date: 2016/3/3 0003
+ * Time: 23:05
+ */
+
+namespace slimExtend\base;
+
+use slimExtend\DataConst;
+use Slim;
+use Slim\Http\Response as SlimResponse;
+
+/**
+ * extension Slim's Response class
+ *
+ * Class Response
+ * @package slimExtend\base
+ */
+class Response extends SlimResponse
+{
+    /**
+     * @param mixed $data
+     * @param int $code
+     * @param string $msg
+     * @param string $describe
+     * @return SlimResponse|static
+     */
+    public function withJson($data, $code = 0, $msg = '', $describe = '')
+    {
+        $data = format_messages($data, $code, $msg, $describe);
+
+        return parent::withJson($data, 200, 0);
+    }
+
+    /**
+     * @param \Psr\Http\Message\UriInterface|string $url
+     * @param int $status
+     * @return static
+     */
+    public function withRedirect($url, $status = 301)
+    {
+        return $this->withStatus($status)->withHeader('Location', (string)$url);
+    }
+
+    /**
+     * Flash messages.
+     *
+     * Note: This method is not part of the PSR-7 standard.
+     *
+     * This method prepares the response object to return an HTTP Json
+     * response to the client.
+     *
+     * @param  string|array $msg The msg
+     * @return Response
+     * @throws \InvalidArgumentException
+     */
+    public function withMessage($msg)
+    {
+        // add a new alert message
+        $alert = [
+            'type'      => 'info', // info success primary warning danger
+            'title'     => 'Info!',
+            'msg'       => '',
+            'closeBtn'  => true
+        ];
+
+        if ( is_string($msg) ) {
+            $alert['msg'] = $msg;
+        } elseif ( is_array($msg) ) {
+            $alert = array_merge($alert, $msg);
+            $alert['title'] = ucfirst($alert['type']);
+        } else {
+            throw new \InvalidArgumentException('params type error!');
+        }
+
+        Slim::$app->flash->addMessage(DataConst::FLASH_MSG_KEY, json_encode($alert));
+
+        return $this;
+    }
+
+    /**
+     * withInputs
+     * @param  array  $data
+     * @return self
+     */
+    public function withInput(array $data)
+    {
+        Slim::$app->flash->addMessage(DataConst::FLASH_OLD_INPUT_KEY, json_encode($data));
+Slim::$app->logger->info('dev-' . session_id(), $_SESSION);
+        return $this;
+    }
+}

+ 125 - 0
src/base/RestFulController.php

@@ -0,0 +1,125 @@
+<?php
+
+namespace slimExtend\base;
+
+use Slim;
+
+/**
+ * Class RestFulController
+ * @package slimExtend\base
+ *
+ * how to use. e.g:
+ * ```
+ * class Book extends slimExtend\base\RestFulController
+ * {
+ *     public function get($request, $response, $args)
+ *     {}
+ *     public function post($request, $response, $args)
+ *     {}
+ *     public function put($request, $response, $args)
+ *     {}
+ *     public function delete($request, $response, $args)
+ *     {}
+ *     ... ...
+ * }
+ * ```
+ */
+abstract class RestFulController
+{
+    /**
+     * __construct
+     */
+    public function __construct()
+    {
+        $this->init();
+    }
+
+    protected function init()
+    {
+        /*
+        Some init logic
+        */
+    }
+
+    /**
+     * @param array $data
+     * @param array $required
+     * @return null|string return error msg if has error.
+     */
+    protected function collectData(array &$data, array $required=[])
+    {
+        foreach ( $required as $key => $field ) {
+            // 可以检查子级
+            if ( is_array($field) ) {
+                if ( !isset($data[$key]) ) {
+                    return '缺少必要参数 ' . $key;
+                }
+                $subData = $data[$key];
+
+                foreach ($field as $subField) {
+                    if ( is_string($subField)
+                        && (!isset($subData[$subField]) || $subData[$subField] === '')
+                    ) {
+                        return "缺少必要参数  {$key}[{$subField}]";
+                    }
+                }
+            } else if (!isset($data[$field]) || $data[$field] === '') {
+                return '缺少必要参数 ' . $field;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @param Request $request
+     * @param Response $response
+     * @param array $args
+     * @return bool
+     */
+    protected function beforeInvoke(Request $request, Response $response, array $args)
+    {
+        return false;
+    }
+
+    /**
+     * @param Request $request
+     * @param Response $response
+     * @param array $args
+     * @return bool
+     * @throws \RuntimeException
+     */
+    public function __invoke(Request $request, Response $response, array $args)
+    {
+        // Maybe want to do something
+        if ( $result = $this->beforeInvoke($request, $response, $args)) {
+            return $result;
+        }
+
+        // default restFul action name
+        $action = strtolower($request->getMethod());
+
+        if ( method_exists($this, $action) ) {
+            return $this->$action($request, $response, $args);
+        }
+
+        // Might want to customize to perform the action name
+        if ( $result = $this->afterInvoke($request, $response, $args)) {
+            return $result;
+        }
+
+        throw new \RuntimeException('Error Processing Request');
+    }
+
+    /**
+     * @param Request $request
+     * @param Response $response
+     * @param array $args
+     * @return bool
+     */
+    protected function afterInvoke(Request $request, Response $response, array $args)
+    {
+        return false;
+    }
+
+}

+ 95 - 0
src/base/User.php

@@ -0,0 +1,95 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Inhere
+ * Date: 2016/3/6 0006
+ * Time: 21:57
+ */
+
+namespace slimExtend\base;
+
+
+
+/**
+ * Class User
+ * @package slimExtend\base
+ *
+ * @property bool isLogin
+ * @property bool isGuest
+ */
+class User extends Collection
+{
+    /**
+     * @var string
+     */
+    protected static $saveKey = '_mder_auth';
+
+    /**
+     * Exclude fields that don't need to be saved.
+     * @var array
+     */
+    protected $excepted = ['password'];
+
+    /**
+     * don't allow set attribute
+     * @param array $items
+     */
+    public function __construct($items=[])
+    {
+        parent::__construct();
+
+        // if have already login
+        if ( $user = session(self::$saveKey) ){
+            $this->clear();
+            $this->sets($user);
+        }
+    }
+
+    /**
+     * @param array $user
+     * @return static
+     */
+    public function login($user)
+    {
+        // except column at set.
+        foreach ($this->excepted as $column) {
+            if ( isset($user[$column])) {
+                unset($user[$column]);
+            }
+        }
+
+        if ( $user instanceof Collection) {
+            $user = $user->all();
+        }
+
+        $this->clear();
+        $this->sets($user);
+
+        session([static::$saveKey => $user]);
+
+        return $this;
+    }
+
+    public function logout()
+    {
+        $this->clear();
+
+        unset($_SESSION[static::$saveKey]);
+    }
+
+    /**
+     * @return bool
+     */
+    public function isLogin()
+    {
+        return count($this->data) !== 0;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isGuest()
+    {
+        return !$this->isLogin();
+    }
+}

+ 746 - 0
src/database/AbstractDriver.php

@@ -0,0 +1,746 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Inhere
+ * Date: 2016/3/2 0002
+ * Time: 22:33
+ */
+
+namespace slimExtend\database;
+
+use Slim;
+use PDO;
+use PDOStatement;
+use Windwalker\Database\Query\QueryHelper;
+use Windwalker\Query\Query;
+
+/**
+ * Class AbstractDriver
+ * @package slimExtend\database
+ */
+abstract class AbstractDriver
+{
+    protected $name = '';
+
+    /**
+     * @var PDO
+     */
+    protected $pdo;
+
+    /**
+     * @var PDOStatement
+     */
+    private $cursor;
+
+    /**
+     * @var
+     */
+    private $options = [];
+
+    /**
+     * @var string|Query
+     */
+    private $query;
+
+    /**
+     * @var string|Query
+     */
+    protected static $newQueryCache;
+
+    /**
+     * @var string
+     */
+    private $lastQuery = '';
+
+    protected $prefixChar = '@@';
+
+    protected $debug = false;
+
+    /**
+     * @param array $options
+     * @param PDO|null $pdo
+     */
+    public function __construct(array $options = [], PDO $pdo = null)
+    {
+        $defaultOptions = array(
+            'driver'   => 'odbc',
+            'dsn'      => '',
+            'host'     => 'localhost',
+            'database' => '',
+            'user'     => '',
+            'password' => '',
+            'driverOptions' => array()
+        );
+
+        $options = array_merge($defaultOptions, $options);
+
+        $this->options = $options;
+        $this->pdo     = $pdo;
+        $this->driverOptions = $this->getOption('driverOptions');
+
+        $this->debug = $this->getOption('debug', false);
+
+        if ( !$pdo && $this->getOption('autoConnect', false) ) {
+            $this->connect();
+        }
+    }
+
+    /**
+     * [connect description]
+     * @return $this
+     */
+    public function connect()
+    {
+        if ( $this->pdo ) {
+            return $this;
+        }
+
+        $dsn = DsnHelper::getDsn($this->options['driver'], $this->options);
+
+        try {
+            $this->pdo = new \PDO(
+                $dsn,
+                $this->options['user'],
+                $this->options['password'],
+                $this->options['driverOptions']
+            );
+        } catch (\PDOException $e) {
+            throw new \RuntimeException('Could not connect to PDO: ' . $e->getMessage() . '. DSN: ' . $dsn, $e->getCode(), $e);
+        }
+
+        $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+        $this->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, true);
+
+        return $this;
+    }
+
+////////////////////////////////////// Read record //////////////////////////////////////
+
+    public function loadAll($key = null, $class = '\\stdClass')
+    {
+        if (strtolower($class) === 'array') {
+            return $this->loadArrayList($key);
+        }
+
+        if (strtolower($class) === 'assoc') {
+            return $this->loadAssocList($key);
+        }
+
+        return $this->loadObjectList($key, $class);
+    }
+
+    /**
+     * loadOne
+     * @param string $class
+     * @return  mixed
+     */
+    public function loadOne($class = '\\stdClass')
+    {
+        if (strtolower($class) === 'array') {
+            return $this->loadArray();
+        }
+
+        if (strtolower($class) === 'assoc') {
+            return $this->loadAssoc();
+        }
+
+        return $this->loadObject($class);
+    }
+
+    /**
+     * @return array|bool
+     */
+    public function loadResult()
+    {
+        $this->execute();
+
+        // Get the first row from the result set as an array.
+        $row = $this->fetchArray();
+
+        if ($row && is_array($row) && isset($row[0])) {
+            $row = $row[0];
+        }
+
+        // Free up system resources and return.
+        $this->freeResult();
+
+        return $row;
+    }
+
+    /**
+     * @param int $offset
+     * @return array
+     */
+    public function loadColumn($offset = 0)
+    {
+        $this->execute();
+        $array = [];
+
+        // Get all of the rows from the result set as arrays.
+        while ($row = $this->fetchArray()) {
+            if ($row && is_array($row) && isset($row[$offset])) {
+                $array[] = $row[$offset];
+            }
+        }
+
+        // Free up system resources and return.
+        $this->freeResult();
+
+        return $array;
+    }
+
+    public function loadArray()
+    {
+        $this->execute();
+
+        // Get the first row from the result set as an array.
+        $array = $this->fetchArray();
+
+        // Free up system resources and return.
+        $this->freeResult();
+
+        return $array;
+    }
+
+    public function loadArrayList($key = null)
+    {
+        $this->execute();
+        $array = [];
+
+        // Get all of the rows from the result set as arrays.
+        while ($row = $this->fetchArray()) {
+            if ($key !== null && is_array($row) ) {
+                $array[$row[$key]] = $row;
+            } else {
+                $array[] = $row;
+            }
+        }
+
+        // Free up system resources and return.
+        $this->freeResult();
+
+        return $array;
+    }
+
+    public function loadAssoc()
+    {
+        $this->execute();
+
+        // Get the first row from the result set as an associative array.
+        $array = $this->fetchAssoc();
+
+        // Free up system resources and return.
+        $this->freeResult();
+
+        return $array;
+    }
+
+    public function loadAssocList($key = null)
+    {
+        $this->execute();
+        $array = [];
+
+        // Get all of the rows from the result set.
+        while ($row = $this->fetchAssoc()) {
+            if ($key) {
+                $array[$row[$key]] = $row;
+            }
+            else {
+                $array[] = $row;
+            }
+        }
+
+        // Free up system resources and return.
+        $this->freeResult();
+
+        return $array;
+    }
+
+    public function loadObject($class = 'stdClass')
+    {
+        $this->execute();
+
+        // Get the first row from the result set as an object of type $class.
+        $object = $this->fetchObject($class);
+
+        // Free up system resources and return.
+        $this->freeResult();
+
+        return $object;
+    }
+
+    public function loadObjectList($key = null, $class = 'stdClass')
+    {
+        $this->execute();
+        $array = [];
+
+        // Get all of the rows from the result set as objects of type $class.
+        while ($row = $this->fetchObject($class)) {
+            if ($key) {
+                $array[$row->$key] = $row;
+            }
+            else {
+                $array[] = $row;
+            }
+        }
+
+        // Free up system resources and return.
+        $this->freeResult();
+
+        return $array;
+    }
+
+    /**
+     * @return array|bool
+     */
+    public function fetchArray()
+    {
+        return $this->fetch(\PDO::FETCH_NUM);
+    }
+
+    /**
+     * Method to fetch a row from the result set cursor as an associative array.
+     * @return  mixed  Either the next row from the result set or false if there are no more rows.
+     */
+    public function fetchAssoc()
+    {
+        return $this->fetch(\PDO::FETCH_ASSOC);
+    }
+
+    /**
+     * Method to fetch a row from the result set cursor as an object.
+     * @param   string  $class  Unused, only necessary so method signature will be the same as parent.
+     * @return  mixed   Either the next row from the result set or false if there are no more rows.
+     */
+    public function fetchObject($class = '\\stdClass')
+    {
+        return $this->getCursor()->fetchObject($class);
+    }
+
+    /**
+     * fetch
+     * @param int  $type
+     * @param int  $ori
+     * @param int  $offset
+     * @see http://php.net/manual/en/pdostatement.fetch.php
+     * @return  bool|mixed
+     */
+    public function fetch($type = \PDO::FETCH_ASSOC, $ori = null, $offset = 0)
+    {
+        return $this->getCursor()->fetch($type, $ori, $offset);
+    }
+
+    /**
+     * fetchAll
+     * @param int   $type
+     * @param array $args
+     * @param array $ctorArgs
+     * @see http://php.net/manual/en/pdostatement.fetchall.php
+     * @return  array|bool
+     */
+    public function fetchAll($type = \PDO::FETCH_ASSOC, $args = null, $ctorArgs = null)
+    {
+        return $this->getCursor()->fetchAll($type,$args , $ctorArgs);
+    }
+
+
+////////////////////////////////////// insert record //////////////////////////////////////
+
+    /**
+     * SQL:
+     * ```
+     * INSERT INTO "mder_users"
+     * ("username", "password", "createTime", "role", "lastLogin", "avatar")
+     * VALUES
+     * (?, ?, ?, 1, 1, '')
+     * ```
+     * @param string $table
+     * @param array $data
+     * @param string $priKey
+     * @return array|int|bool
+     */
+    public function insert($table, $data, $priKey='')
+    {
+        $query = $this->newQuery(true);
+        $fields = $values = [];
+
+        // Iterate over the object variables to build the query fields and values.
+        foreach ($data as $k => $v) {
+            // Convert stringable object
+            if (is_object($v) && is_callable(array($v, '__toString'))) {
+                $v = (string) $v;
+            }
+
+            // Only process non-null scalars.
+            if (is_array($v) or is_object($v) or $v === null) {
+                continue;
+            }
+
+            // Ignore any internal fields.
+            if ($k && is_string($k) && $k[0] === '_') {
+                continue;
+            }
+
+            // Prepare and sanitize the fields and values for the database query.
+            $fields[] = $query->quoteName($k);
+            $values[] = $query->quote($v);
+        }
+
+        // Create the base insert statement.
+        $query->insert($query->quoteName($table))
+            ->columns($fields)
+            ->values(array($values));
+
+        // Set the query and execute the insert.
+        if (!$this->setQuery($query)->execute()) {
+            return false;
+        }
+
+        // Update the primary key if it exists.
+        $id = $this->pdo->lastInsertId();
+
+        if ($priKey && $id && is_string($priKey)) {
+            if (is_array($data)) {
+                $data[$priKey] = $id;
+            } else {
+                $data->$priKey = $id;
+            }
+            return $data;
+        }
+
+        return $id;
+    }
+
+////////////////////////////////////// update record //////////////////////////////////////
+
+    /**
+     * Updates a row in a table based on an object's properties.
+     * @param   string  $table       The name of the database table to update.
+     * @param   array   $data        A reference to an object whose public properties match the table fields.
+     * @param   array|string  $key         The name of the primary key.
+     * @param   boolean $updateNulls True to update null fields or false to ignore them.
+     * @throws \InvalidArgumentException
+     * @return  boolean  True on success.
+     */
+    public function update($table, $data, $key= 'id', $updateNulls = false)
+    {
+        if (!is_array($data) && !is_object($data)) {
+            throw new \InvalidArgumentException('Please give me array or object to update.');
+        }
+
+        $query = $this->newQuery(true);
+        $key = (array) $key;
+
+        // Create the base update statement.
+        $query->update($query->quoteName($table));
+
+        // Iterate over the object variables to build the query fields/value pairs.
+        foreach (get_object_vars((object) $data) as $k => $v) {
+            // Convert stringable object
+            if (is_object($v) && is_callable(array($v, '__toString'))) {
+                $v = (string) $v;
+            }
+
+            // Only process scalars that are not internal fields.
+            if (is_array($v) || is_object($v) || ( $k && is_string($k) && $k[0] === '_') ) {
+                continue;
+            }
+
+            // Set the primary key to the WHERE clause instead of a field to update.
+            if (in_array($k, $key)) {
+                $query->where($query->quoteName($k) . '=' . $query->quote($v));
+
+                continue;
+            }
+
+            // Prepare and sanitize the fields and values for the database query.
+            if ($v === null || $v === '') {
+                // If the value is null and we want to update nulls then set it.
+                if ($updateNulls) {
+                    $val = 'NULL';
+
+                // If the value is null and we do not want to update nulls then ignore this field.
+                } else {
+                    continue;
+                }
+            } else { // The field is not null so we prep it for update.
+               $val = $query->quote($v);
+            }
+
+            // Add the field to be updated.
+            $query->set($query->quoteName($k) . '=' . $val);
+        }
+
+        // Set the query and execute the update.
+        return $this->setQuery($query)->execute();
+    }
+
+    /**
+     * save
+     * @param   string  $table        The name of the database table to update.
+     * @param   array   &$data        A reference to an object whose public properties match the table fields.
+     * @param   string  $key          The name of the primary key.
+     * @param   boolean $updateNulls  True to update null fields or false to ignore them.
+     * @return  bool|static
+     * @throws \InvalidArgumentException
+     */
+    public function save($table, &$data, $key, $updateNulls = false)
+    {
+        if ( !is_scalar($key) ) {
+            throw new \InvalidArgumentException(__NAMESPACE__ . '::save() dose not support multiple keys, please give me only one key.');
+        }
+
+        if (is_array($data)) {
+            $id = isset($data[$key]) ? $data[$key] : null;
+        } else {