├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── README.zh.md ├── composer.json ├── docs ├── Makefile ├── _static │ ├── WX20170809-184015.png │ └── db.gif ├── advanced │ ├── cli.md │ ├── custom-annotation.md │ ├── docgen.md │ ├── hook.md │ ├── orm.md │ ├── rpc.md │ └── workflow.md ├── basic │ ├── annotation.md │ ├── cache.md │ ├── db.md │ ├── di.md │ ├── params-bind.md │ ├── route.md │ └── validation.md ├── conf.py ├── faq.md ├── index.rst ├── make.bat ├── quick-start │ ├── example.md │ ├── install.md │ ├── requirements.md │ └── webserver-config.md └── requirements.txt ├── phpunit.xml ├── src ├── Annotation │ ├── AnnotationBlock.php │ ├── AnnotationReader.php │ ├── AnnotationTag.php │ └── ContainerBuilder.php ├── Application.php ├── Cache │ ├── CheckableCache.php │ ├── ClassModifiedChecker.php │ └── FileModifiedChecker.php ├── Console.php ├── Console │ ├── Annotations │ │ ├── ClassAnnotationHandler.php │ │ ├── CommandAnnotationHandler.php │ │ ├── CommandNameAnnotationHandler.php │ │ ├── ParamAnnotationHandler.php │ │ └── ValidateAnnotationHandler.php │ ├── Command.php │ ├── ConsoleContainer.php │ └── ConsoleContainerBuilder.php ├── Controller │ ├── Annotations │ │ ├── BindAnnotationHandler.php │ │ ├── ClassAnnotationHandler.php │ │ ├── HookAnnotationHandler.php │ │ ├── ParamAnnotationHandler.php │ │ ├── PathAnnotationHandler.php │ │ ├── ReturnAnnotationHandler.php │ │ ├── RouteAnnotationHandler.php │ │ ├── ThrowsAnnotationHandler.php │ │ └── ValidateAnnotationHandler.php │ ├── ControllerContainer.php │ ├── ControllerContainerBuilder.php │ ├── ExceptionHandler.php │ ├── ExceptionRenderer.php │ ├── HookInterface.php │ ├── Hooks │ │ └── Cors.php │ ├── RequestHandler.php │ ├── ResponseHandler.php │ ├── ResponseRenderer.php │ └── Route.php ├── DB │ ├── Context.php │ ├── DB.php │ ├── Exceptions │ │ └── DBException.php │ ├── NestedStringCut.php │ ├── README.md │ ├── Raw.php │ ├── Rows.php │ ├── impls.php │ └── rules │ │ ├── basic.php │ │ ├── delete.php │ │ ├── insert.php │ │ ├── replace.php │ │ ├── select.php │ │ └── update.php ├── DI │ ├── AnnotationReader.php │ ├── Annotations │ │ ├── InjectAnnotationHandler.php │ │ └── VarAnnotationHandler.php │ ├── DIContainerBuilder.php │ ├── DIMetaLoader.php │ ├── ObjectDefinitionContext.php │ └── Traits │ │ └── EnableDIAnnotations.php ├── Docgen │ └── Swagger │ │ ├── Schemas │ │ ├── ArraySchemaObject.php │ │ ├── BodyParameterObject.php │ │ ├── ContactObject.php │ │ ├── ExternalDocumentationObject.php │ │ ├── HeaderObject.php │ │ ├── InfoObject.php │ │ ├── LicenseObject.php │ │ ├── OperationObject.php │ │ ├── OtherParameterObject.php │ │ ├── ParameterObject.php │ │ ├── PathItemObject.php │ │ ├── PathObject.php │ │ ├── PrimitiveSchemaObject.php │ │ ├── RefSchemaObject.php │ │ ├── ResponseObject.php │ │ ├── SchemaObject.php │ │ ├── SecurityRequirementObject.php │ │ ├── SecuritySchemeObject.php │ │ ├── SimpleModelSchemaObject.php │ │ ├── SwaggerObject.php │ │ └── TagObject.php │ │ ├── Swagger.php │ │ └── SwaggerProvider.php ├── Entity │ ├── Annotations │ │ ├── ClassAnnotationHandler.php │ │ ├── PropertyAnnotationHandler.php │ │ ├── ValidateAnnotationHandler.php │ │ └── VarAnnotationHandler.php │ ├── ArrayContainer.php │ ├── ContainerFactory.php │ ├── EntityContainer.php │ ├── EntityContainerBuilder.php │ ├── MixedTypeContainer.php │ ├── ScalarTypeContainer.php │ └── TypeContainerInterface.php ├── Exceptions │ ├── AnnotationSyntaxException.php │ └── RpcException.php ├── Lock │ ├── ApcLock.php │ ├── FileLock.php │ ├── LocalAutoLock.php │ └── LockInterface.php ├── Metas │ ├── ParamMeta.php │ ├── PropertyMeta.php │ └── ReturnMeta.php ├── ORM │ ├── Annotations │ │ ├── PKAnnotationHandler.php │ │ └── TableAnnotationHandler.php │ ├── ModelContainer.php │ ├── ModelContainerBuilder.php │ ├── ModelWithClass.php │ └── ModelWithObject.php ├── RPC │ ├── MultiRequest.php │ ├── MultiRequestCore.php │ ├── MultiRpc.php │ └── RpcProxy.php ├── Utils │ ├── AnnotationParams.php │ ├── ArrayAdaptor.php │ ├── ArrayHelper.php │ ├── Logger.php │ ├── SafeFileWriter.php │ ├── SerializableFunc.php │ ├── TypeCast.php │ └── TypeHint.php ├── Validator │ └── Validator.php └── functions.php └── tests ├── AnnotationParamsTest.php ├── AnnotationReaderTest.php ├── ApplicationTest.php ├── ArrayAdaptorTest.php ├── ArrayHelperTest.php ├── ConsoleTest.php ├── ControllerMetaLoaderTest.php ├── DBTest.php ├── EntityMetaLoaderTest.php ├── Mocks └── DBMock.php ├── ModelWithClassTest.php ├── ModelWithObjectTest.php ├── MultiRequestTest.php ├── RpcProxyTest.php ├── ScalarTypeContainerTest.php ├── SwaggerTest.php ├── TestCase.php ├── Utils ├── RpcTestController.php ├── RpcTestEntity1.php └── RpcTestEntity2.php ├── ValidateTest.php └── travis └── php.ini /.coveralls.yml: -------------------------------------------------------------------------------- 1 | coverage_clover: build/logs/clover.xml 2 | json_path: build/logs/coveralls-upload.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node rules: 2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 3 | .grunt 4 | 5 | ## Dependency directory 6 | ## Commenting this out is preferred by some people, see 7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 8 | node_modules 9 | 10 | # Book build output 11 | _book 12 | 13 | # eBook build output 14 | *.epub 15 | *.mobi 16 | *.pdf 17 | *.idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | #cache: 4 | # directories: 5 | # - vendor 6 | # - $HOME/.composer/cache 7 | 8 | php: 9 | - '5.5.9' 10 | - '5.6' 11 | - '7.0' 12 | # - hhvm blocked by phpdocumentor/type-resolver/src/Types/Integer 13 | 14 | #before_install: 15 | # - if [[ $TRAVIS_PHP_VERSION != 'hhvm' ]] ; then pecl channel-update pecl.php.net; fi; 16 | # - if [[ $TRAVIS_PHP_VERSION != 'hhvm' && $TRAVIS_PHP_VERSION != '7.0' ]]; then pecl install riak-beta; fi; 17 | # - if [[ $TRAVIS_PHP_VERSION =~ 5.[6] ]] ; then echo yes | pecl install apcu-4.0.10; fi; 18 | # - if [[ $TRAVIS_PHP_VERSION = 7.* ]] ; then pecl config-set preferred_state beta; echo yes | pecl install apcu; fi; 19 | # - if [[ $TRAVIS_PHP_VERSION != 'hhvm' ]]; then phpenv config-add ./tests/travis/php.ini; fi; 20 | 21 | install: 22 | - travis_retry composer update 23 | 24 | 25 | script: 26 | - ./vendor/bin/phpunit -c ./phpunit.xml -v 27 | 28 | after_script: 29 | - if [ $TRAVIS_PHP_VERSION = '5.6' ]; then wget https://scrutinizer-ci.com/ocular.phar; php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi 30 | 31 | after_script: 32 | - php vendor/bin/coveralls -v 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 caoyangmin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "caoym/phpboot", 3 | "description": "PHP RESTful API framework.", 4 | "keywords": [ 5 | "phpboot", 6 | "restful", 7 | "boot" 8 | ], 9 | "license": "MIT", 10 | "require": { 11 | "php":">=5.5.9", 12 | "monolog/monolog": "~1.0", 13 | "symfony/http-foundation": "^3.3", 14 | "vlucas/valitron": "^1.4", 15 | "doctrine/cache": "^1.6", 16 | "phpdocumentor/reflection-docblock": "^3.1,!=3.2.1|^4.3", 17 | "mtdowling/jmespath.php": "^2.4", 18 | "nikic/fast-route": "^1.2", 19 | "symfony/http-kernel": "^3.3", 20 | "php-di/php-di": "^5.4", 21 | "guzzlehttp/guzzle": "^6.2", 22 | "symfony/console": "~3.4|~4.0" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "~4.0", 26 | "satooshi/php-coveralls": "~0.6" 27 | }, 28 | "authors": [ 29 | { 30 | "name": "Yangmin Cao", 31 | "email": "caoyangmin@gmail.com" 32 | } 33 | ], 34 | "autoload": { 35 | "psr-4": { 36 | "PhpBoot\\": "src/", 37 | "PhpBoot\\Tests\\": "tests/" 38 | }, 39 | "files": [ 40 | "src/functions.php", 41 | "src/DB/rules/basic.php", 42 | "src/DB/rules/delete.php", 43 | "src/DB/rules/insert.php", 44 | "src/DB/rules/replace.php", 45 | "src/DB/rules/select.php", 46 | "src/DB/rules/update.php" 47 | ] 48 | }, 49 | "autoload-dev": { 50 | "classmap": [ 51 | "tests/" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = phpboot 8 | SOURCEDIR = . 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/_static/WX20170809-184015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoym/phpboot/15086967b35cde60edf39c88a967ce238135a8c8/docs/_static/WX20170809-184015.png -------------------------------------------------------------------------------- /docs/_static/db.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoym/phpboot/15086967b35cde60edf39c88a967ce238135a8c8/docs/_static/db.gif -------------------------------------------------------------------------------- /docs/advanced/cli.md: -------------------------------------------------------------------------------- 1 | # 命令行 2 | 3 | 使用PHP开发非http服务时,如定时任务等,常需要通过命令行模式启动php脚本,PhpBoot的CLI支持可以让你快速完成这方面工作。 4 | 5 | 6 | ## 1. 实现命令行 7 | 8 | ```php 9 | /** 10 | * @command test //可选 @command指定命令的前缀 11 | */ 12 | namespace App\Commands 13 | 14 | class TestCommand 15 | { 16 | /** 17 | * run test 18 | * 19 | * the run test 20 | * @command run // 命令唯一标识 21 | * 22 | * @param int $arg0 arg 0 23 | * @param string $arg1 arg 1 24 | * @param string[] $arg2 arg 2 25 | */ 26 | public function runTest($arg0, $arg1, $arg2){ 27 | print_r([$arg0, $arg1, $arg2]); 28 | return 0; // 返回进程的exit code 29 | } 30 | } 31 | 32 | ``` 33 | 34 | ## 2. 编写入口文件 cli.php 35 | 36 | ```php 37 | 38 | use \PhpBoot\Application; 39 | use \PhpBoot\Console; 40 | 41 | ini_set('date.timezone','Asia/Shanghai'); 42 | require __DIR__.'/../vendor/autoload.php'; 43 | 44 | // 加载配置 45 | $app = Application::createByDefault(__DIR__ . '/../config/config.php'); 46 | // 加载命令行 47 | $console = Console::create($app); 48 | $console->loadCommandsFromPath(__DIR__.'/../App/Commands', 'App\\Commands'); 49 | // 执行命令行 50 | $console->run(); 51 | 52 | ``` 53 | 54 | ## 3. 执行命令 55 | 56 | > 执行 php ./cli.php 57 | ```shell 58 | 59 | $ php ./cli.php 60 | Console Tool 61 | 62 | Usage: 63 | command [options] [arguments] 64 | 65 | Options: 66 | -h, --help Display this help message 67 | -q, --quiet Do not output any message 68 | -V, --version Display this application version 69 | --ansi Force ANSI output 70 | --no-ansi Disable ANSI output 71 | -n, --no-interaction Do not ask any interactive question 72 | -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug 73 | 74 | Available commands: 75 | help Displays help for a command 76 | list Lists commands 77 | my.test : run test 78 | 79 | 80 | ``` 81 | 82 | > 执行 php ./cli.php my.test 1 2 a b 83 | 84 | ```shell 85 | 86 | $ php ./cli.php my.test 1 2 a b 87 | array(3) { 88 | [0]=> 89 | int(1) 90 | [1]=> 91 | string(1) "2" 92 | [2]=> 93 | array(2) { 94 | [0]=> 95 | string(1) "a" 96 | [1]=> 97 | string(1) "b" 98 | } 99 | } 100 | ``` -------------------------------------------------------------------------------- /docs/advanced/custom-annotation.md: -------------------------------------------------------------------------------- 1 | # 自定义 Annotation 2 | 待完善... -------------------------------------------------------------------------------- /docs/advanced/docgen.md: -------------------------------------------------------------------------------- 1 | # 文档输出 2 | 3 | ## 1. Swagger 文档 4 | 5 | Swagger 是流行的 HTTP API 描述规范,同时 Swagger 官方还提供了丰富的工具,比如用于文档展示和接口测试的 Swagger UI, 相关资料请阅读[官方文档](https://swagger.io)。 6 | 7 | 以 phpboot-example 为例,生成的文档如下。文档中除了描述了接口的路由、参数定义、参数校验,还提供了接口测试工具。[点击这里查看在线 Demo](http://118.190.86.50:8007/index.html?url=http://118.190.86.50:8009/docs/swagger.json) 8 | 9 | ![](/_static/WX20170809-184015.png) 10 | 11 | **PhpBoot 项目可以很方便的生成 Swagger 文档,无需添加额外的 Annotation**(很多框架为支持 Swagger,通常需要增加很多额外的注释,而这些注释只用于 Swagger。PhpBoot 生成 Swagger 的信息来自路由的标准注释,包括@route, @param, @return,@throws 等) 12 | 13 | 如需开启 Swagger 文档,只需在在 Application 初始化时 添加以下代码: 14 | 15 | ```php 16 | PhpBoot\Docgen\Swagger\SwaggerProvider::register($app , function(Swagger $swagger){ 17 | $swagger->host = 'example.com'; 18 | $swagger->info->description = 'this is the description of the apis'; 19 | ... 20 | }); 21 | ``` 22 | 23 | 然后访问你的项目 url+/docs/swagger.json```如( http://localhost/docs/swagger.json)```,即可获取 json 格式的 Swagger 文档。 24 | 25 | ## 2. MarkDown 文档 26 | 27 | 开发中... -------------------------------------------------------------------------------- /docs/advanced/hook.md: -------------------------------------------------------------------------------- 1 | # Hook 2 | 3 | 4 | ## 1. 定义 Hook 5 | 6 | 演示如何通过 Hook 实现 Basic Authorization 登录校验 7 | ```php 8 | /** 9 | * 简单登录校验 10 | * 11 | * 实现了 Basic Authorization 12 | * @package App\Hooks 13 | */ 14 | class BasicAuth implements HookInterface 15 | { 16 | /** 17 | * @param Request $request 18 | * @param callable $next 19 | * @return Response 20 | */ 21 | public function handle(Request $request, callable $next) 22 | { 23 | $auth = $request->headers->get('Authorization'); 24 | $auth or \PhpBoot\abort(new UnauthorizedHttpException('Basic realm="PhpBoot Example"', 'Please login...')); 25 | $auth = explode(' ', $auth); 26 | $auth[1] == md5("{$this->username}:{$this->password}") or fail(new UnauthorizedHttpException('Basic realm="PhpBoot Example", "Invalid username or password!"')); 27 | return $next($request); 28 | } 29 | 30 | /** 31 | * @var string 32 | */ 33 | public $username; 34 | /** 35 | * @var string 36 | */ 37 | public $password; 38 | } 39 | ``` 40 | 可以看到,Hook 只需要继承HookInterface,实现 handle 方法。 41 | 42 | ## 2. 使用 Hook 43 | 44 | 为需要的接口添加此 Hook 45 | 46 | ### 2.1. 通过 @hook 添加 Hook 47 | 48 | ```php 49 | /** 50 | * @route POST /books/ 51 | * @param Book $book {@bind request.request} 52 | * @hook \App\Hooks\BasicAuth 指定此接口需要BasicAuth校验 53 | */ 54 | public function createBook(Book $bok) 55 | ``` 56 | 57 | 一个接口可以指定多个 Hook,执行的顺序依照@hook 定义的顺序。 58 | 59 | ### 2.2. 添加路由时指定 Hook 60 | 61 | Application::addRoute()、Application::loadRoutes*() 方法添加路由时,可以指定 Hook ,如: 62 | 63 | ```php 64 | $app->loadRoutesFromPath($path, [BaseAuth::class]); 65 | ``` 66 | 67 | ### 2.3. 设置全局 Hook 68 | 69 | Application::setGlobalHooks 用于设置全局 Hook, 如: 70 | 71 | ```php 72 | Application::setGlobalHooks([BaseAuth::class]); 73 | ``` 74 | 75 | 全局 Hook 不依赖于是否存在路由,即就算没有请求对应的路由,全局 Hook 还是会被执行。 76 | 77 | 关于 Hook 的更多细节, 可以参考\PhpBoot\Controller\Hooks\Cors的实现。 78 | 79 | -------------------------------------------------------------------------------- /docs/advanced/orm.md: -------------------------------------------------------------------------------- 1 | # ORM 2 | 3 | 目前 PhpBoot 提供基本的 ORM 支持,包括: 4 | 5 | ## 1. 定义实体 6 | 7 | 实体对应数据库的表, 实体的属性名和数据库的列名一致。下面是一个典型的实体定义: 8 | 9 | ```php 10 | /** 11 | * 图书信息 12 | * @table books 13 | * @pk id 14 | */ 15 | class Book 16 | { 17 | /** 18 | * @var int 19 | */ 20 | public $id; 21 | 22 | /** 23 | * 书名 24 | * @var string 25 | */ 26 | public $name=''; 27 | 28 | /** 29 | * 图片url 30 | * @var string[] 31 | */ 32 | public $pictures=[]; 33 | } 34 | ``` 35 | 其中: 36 | * @table 指定表名 37 | * @pk 指定表的主键 38 | * @var 定义列的类型,如果类型为对象或者数组,则保存到数据库是将被序列化为 json 39 | 40 | 可以看到,ORM 中的实体和接口中的实体很类似,事实上,我们**鼓励在 ORM 和接口中复用实体类**。 41 | 42 | ## 2. 操作数据库 43 | 44 | PhpBoot 提供量个组方法,```model``` 和 ```models```, 分别用于操作实体“实例”和实体“类”。 45 | 46 | ### 2.1. model 方法 47 | 48 | ```model()``` 方法用于操作实体“实例”,或者说操作单个实体对象。 49 | 50 | #### 2.1.1 create 51 | 52 | 存储指定实体实例(对应 SQL 的 insert) 53 | 54 | ```php 55 | $book = new Book(); 56 | $book->name = ... 57 | ... 58 | 59 | \PhpBoot\model($this->db, $book)->create(); 60 | echo $book->id; //获取自增主键的值 61 | ``` 62 | 63 | #### 2.1.2 update 64 | 65 | 更新实体对应的数据库记录(对应 SQL 的 update) 66 | 67 | ```php 68 | $book = new Book(); 69 | $book->id = ... 70 | ... 71 | 72 | \PhpBoot\model($this->db, $book)->update(); 73 | ``` 74 | 75 | #### 2.1.3 delete 76 | 77 | 删除实体对应的数据库记录(对应 SQL 的 delete ) 78 | 79 | ```php 80 | $book = new Book(); 81 | $book->id = ... 82 | 83 | \PhpBoot\model($this->db, book)->delete(); 84 | ``` 85 | 86 | ### 2.2 models 方法 87 | 88 | ```models()``` 方法用于操作实体“类”,或者说操作一组实体。 89 | 90 | #### 2.2.1. find 91 | 92 | 根据主键查找(对应 SQL 的 select ) 93 | 94 | ```php 95 | $book = \PhpBoot\models($this->db, Book::class)->find($id); 96 | ``` 97 | 98 | #### 2.2.2. findWhere 99 | 100 | 根据组合查询条件查找(对应 SQL 的 select ) 101 | 102 | ```php 103 | $books = \PhpBoot\models($this->db, Book::class) 104 | ->findWhere(['name'=>'abc']) 105 | ->get(); 106 | ``` 107 | 108 | #### 2.2.3. update 109 | 110 | 根据主键更新(对应 SQL 的 update ) 111 | 112 | ```php 113 | \PhpBoot\models($this->db, Book::class) 114 | ->update(1, ['name'=>'abc']); 115 | ``` 116 | 117 | #### 2.2.4. updateWhere 118 | 119 | 根据组合查询条件更新(对应 SQL 的 update ) 120 | 121 | ```php 122 | \PhpBoot\models($this->db, Book::class) 123 | ->updateWhere(['name'=>'abc'], ['id'=>1]) 124 | ->exec(); 125 | ``` 126 | 127 | #### 2.2.6. delete 128 | 129 | 根据主键删除(对应 SQL 的 delete ) 130 | 131 | ```php 132 | \PhpBoot\models($this->db, Book::class) 133 | ->delete(1); 134 | ``` 135 | 136 | #### 2.2.7. deleteWhere 137 | 138 | 根据组合查询条件删除(对应 SQL 的 delete ) 139 | 140 | ```php 141 | \PhpBoot\models($this->db, Book::class) 142 | ->deleteWhere(['id'=>1]) 143 | ->exec(); 144 | ``` 145 | -------------------------------------------------------------------------------- /docs/advanced/workflow.md: -------------------------------------------------------------------------------- 1 | # 工作流 2 | 3 | 这将是一个简单、轻量、健壮、可扩展、适用于自动化为主的、支持主要的BPMN要素(活动、网关、事件)、可持久化、但不准备支持所有BPMN2特性, 的工作流引擎,目前还在 Workflow 分支上开发... 4 | 5 | ## 1. 将可以通过如下方式定义流程 6 | 7 | ```php 8 | $engine = new ProcessEngine(); 9 | $process = new Process(); 10 | $builder = new ProcessBuilder($process); 11 | 12 | //定义流程 13 | $builder 14 | ->begin 15 | ->task(null, CreateOrderTask::class, '创建订单') 16 | ->eFork('eFork1', '事件网关') 17 | ->listener(null, 'paid', '等待支付') 18 | ->task(null, ShipTask::cass, '发货') 19 | ->xJoin('xJoin1', '排他网关') 20 | ->end; 21 | 22 | $builder 23 | ->eFork1 24 | ->timer(null, 3600, '支付超时') 25 | ->task(null, CloseOrderTask::class, '关闭订单') 26 | ->xJoin1 27 | 28 | 29 | //执行流程 30 | $process->run($engine); 31 | 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/basic/annotation.md: -------------------------------------------------------------------------------- 1 | # Annotation 2 | 3 | PhpBoot 框架较多的使用了 Annotation。当然原生 PHP 语言并不支持此项特性,所以实际是通过Reflection提取注释并解析实现,类似很多主流 PHP 框架的做法(如 symfony、doctrine 等)。但又有所不同的是,主流的Annotation 语法基本沿用了 java 中的形式,如: 4 | 5 | ```php 6 | /** 7 | * @Route("/books/{id}", name="book_info") 8 | * @Method("GET") 9 | */ 10 | public function getBook($id)... 11 | ``` 12 | 语法严谨,易于扩展,但稍显啰嗦(PhpBoot 1.x 版本也使用此语法)。特别是PHP 由于先天不足(原生不支持Annotation),通过注释,在没有IDE语法提示和运行时检查机制的情况下。如果写 Annotation 过于复杂,那还不然直接写原生代码。所以 PhpBoot 使用了更简单的 Annotation 语法。如: 13 | 14 | ```php 15 | /** 16 | * @route GET /books/{id} 17 | */ 18 | public function getBook($id)... 19 | 20 | ``` 21 | 22 | ## 1. 语法 23 | 24 | ```@ [param0] [param1] [param2] ...``` 25 | 26 | 1. name 只能是连续的字母、数字、斜杠'\'、中横杠'-' 组成的字符串,建议全小写,单词间用'-'分割,如```@myapp\my-ann```。 27 | 2. name和参数,参数和参数见,用空白符(一个或多个连续的空格、制表符)分割。 28 | 3. 参数中如果包含空格,应将参数用双引号""包围,包围内的双引号用\转义,如 ```@my-ann "the param \"0\"" param1``` 第一个参数为```the param "0"``` 29 | 30 | **分割参数、转义的语法和linux 命令行的语法类似** 31 | 32 | ## 2. 嵌套 33 | 34 | 嵌套注释,用{}包围, 比如```@param int size {@v min:0|max:10}``` 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/basic/cache.md: -------------------------------------------------------------------------------- 1 | # 缓存 2 | 3 | PhpBoot 使用[doctrine/cache](http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/reference/caching.html)作为底层缓存实现。doctrine/cache 支持的缓存类型有: APC、APCu、Memcache、Xcache、Redis。 4 | 5 | ## 业务缓存 6 | 7 | 如果需要在业务代码中使用缓存, 此处以Redis为例, 演示基本用法。 8 | 9 | 1. 在修改配置文件 config.php, 增加以下代码: 10 | 11 | ```php 12 | 13 | 'redis' => \DI\object(\Doctrine\Common\Cache\RedisCache::class) 14 | ->method('setRedis', \DI\factory(function(){ 15 | $redis = new \Redis(); 16 | $redis->connect('127.0.0.1', 6379); 17 | return $redis; 18 | })), 19 | ``` 20 | 21 | 2. 在控制器中需要 redis 的地方, 注入 redis 实例 22 | 23 | ```php 24 | 25 | /** 26 | * @inject redis 27 | * @var \Doctrine\Common\Cache\RedisCache 28 | */ 29 | private $redis; 30 | ``` 31 | 32 | ## 系统缓存 33 | 34 | PhpBoot 框架为提高性能, 会将路由及Annotation 分析后的其他元信息进行缓存。生产环境建议使用 APC 扩展, 开发环境可以用文件缓存代替 apc, 方法是在 config.php 里加一个配置。 35 | 36 | ```php 37 | Cache::class => \DI\object(FilesystemCache::class) 38 | ->constructorParameter('directory', sys_get_temp_dir()) 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/basic/di.md: -------------------------------------------------------------------------------- 1 | # 依赖注入 2 | PhpBoot 使用开源项目 [PHP-DI](http://php-di.org/) 作为依赖注入的基础实现。 3 | 4 | ## 1. 手动注入 5 | 手动注入是只通过配置文件显式的指定注入方式。详见[PHP-DI文档](http://php-di.org/) 6 | 7 | ## 2. 自动注入 8 | 9 | 10 | ### 2.1. 构造函数注入 11 | 12 | ```php 13 | class Books 14 | { 15 | /** 16 | * @param LoggerInterface $logger 通过依赖注入传入 17 | */ 18 | public function __construct(LoggerInterface $logger) 19 | { 20 | $this->logger; 21 | } 22 | ... 23 | } 24 | ``` 25 | 26 | 27 | 28 | ### 2.2. 属性注入 29 | 30 | ```php 31 | class Books 32 | { 33 | use EnableDIAnnotations; //启用通过@inject标记注入依赖 34 | /** 35 | * @inject 36 | * @var DB 37 | */ 38 | private $db; 39 | } 40 | ``` 41 | 42 | **注意:PhpBoot 禁用了PHP-DI的 Annotation 注入方式,@inject 方式是 PhpBoot 实现的** 43 | 44 | -------------------------------------------------------------------------------- /docs/basic/route.md: -------------------------------------------------------------------------------- 1 | # 路由 2 | 3 | PhpBoot 支持两种形式的路由定义, 分别是通过加载 Controller 类,分析 Annotation ,自动加载路由,和通过 Application::addRoute 方法手动添加路由。 4 | 5 | ## 1. 自动加载路由 6 | 7 | 你可以通过 Application::loadRoutesFromClass 或者 Application::loadRoutesFromPath 添加路由。框架扫描每个类的每个方法,如果方法标记了@route,将被自动添加为路由。被加载类的形式如下: 8 | 9 | ```php 10 | /** 11 | * @path /books 12 | */ 13 | class Books 14 | { 15 | /** 16 | * @route GET /{id} 17 | */ 18 | public function getBook($id) 19 | } 20 | ``` 21 | 以上代码表示 http 请求 ```GET /books/{id}``` 其实现为 Books::getBook, 其中{id}为url 的可变部分。 22 | 23 | ### 1.1. @path 24 | 25 | **语法:** ```@path ``` 26 | 27 | 标注在类的注释里,用于指定 Controller 类中所定义的全部接口的uri 的前缀。 28 | 29 | 30 | ### 1.2. @route 31 | 32 | **语法:** ```@path ``` 33 | 34 | 标注在方法的注释里,用于指定接口的路由。method为指定的 http 方法,可以是 GET、HEAD、POST、PUT、PATCH、DELETE、OPTION、DELETE。uri 中可以带变量,用{}包围。 35 | 36 | ## 2. 手动加载路由 37 | 38 | 你可以使用 Application::addRoute 手动加载路由,方法如下: 39 | 40 | ```php 41 | $app->addRoute('GET', '/books/{id}', function(Request $request){ 42 | $books = new Books(); 43 | return $books->getBook($request->get('id')); 44 | }); 45 | 46 | ``` 47 | 48 | **需要注意的是,此方法添加的路由,将不能自动生成接口文档。** 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /docs/basic/validation.md: -------------------------------------------------------------------------------- 1 | # 参数校验 2 | 3 | 在"参数绑定"时,起始已经支持了两项基本的校验(类型和是否必选),如果要支持更复杂的校验规则,可以通过 @v 指定,如: 4 | 5 | ```php 6 | /** 7 | * @route GET /books/ 8 | * @param int $offsit {@v min:0} 9 | * @param int $limit {@v min:1|max:100} 10 | */ 11 | public function getBooks($offsit=0, $limit=10) 12 | ``` 13 | ## 1. 语法 14 | 15 | ```@v [:param0[,param1...]][|...]``` 16 | * 多个规则间用```|```分割。 17 | * 规则和其参数间用```:```分割, 如果有多个参数,参数间用```,```分割。 18 | 19 | ## 2. 支持的规则 20 | 21 | * required - Required field 22 | * equals - Field must match another field (email/password confirmation) 23 | * different - Field must be different than another field 24 | * accepted - Checkbox or Radio must be accepted (yes, on, 1, true) 25 | * numeric - Must be numeric 26 | * integer - Must be integer number 27 | * boolean - Must be boolean 28 | * array - Must be array 29 | * length - String must be certain length 30 | * lengthBetween - String must be between given lengths 31 | * lengthMin - String must be greater than given length 32 | * lengthMax - String must be less than given length 33 | * min - Minimum 34 | * max - Maximum 35 | * in - Performs in_array check on given array values 36 | * notIn - Negation of in rule (not in array of values) 37 | * ip - Valid IP address 38 | * email - Valid email address 39 | * url - Valid URL 40 | * urlActive - Valid URL with active DNS record 41 | * alpha - Alphabetic characters only 42 | * alphaNum - Alphabetic and numeric characters only 43 | * slug - URL slug characters (a-z, 0-9, -, _) 44 | * regex - Field matches given regex pattern 45 | * date - Field is a valid date 46 | * dateFormat - Field is a valid date in the given format 47 | * dateBefore - Field is a valid date and is before the given date 48 | * dateAfter - Field is a valid date and is after the given date 49 | * contains - Field is a string and contains the given string 50 | * creditCard - Field is a valid credit card number 51 | * optional - Value does not need to be included in data array. If it is however, it must pass validation. -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## 是否必须使用 APC 扩展 4 | 5 | PhpBoot 框架为提高性能, 会将路由及Annotation 分析后的其他元信息进行缓存。生产环境建议使用 APC 扩展, 开发环境可以用文件缓存代替 apc, 方法是在 config.php 里加一个配置 6 | 7 | ```php 8 | Cache::class => \DI\object(FilesystemCache::class) 9 | ->constructorParameter('directory', sys_get_temp_dir()) 10 | ``` 11 | 12 | ## composer 更新失败怎么办 13 | 14 | packagist.org 国内访问不稳定,可以翻墙试试,或者用国内的镜像[phpcomposer](phpcomposer.com), 执行下面命令 15 | 16 | ``` 17 | composer config repo.packagist composer https://packagist.phpcomposer.com 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=build 12 | set SPHINXPROJ=phpboot 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/quick-start/install.md: -------------------------------------------------------------------------------- 1 | # 安装 2 | 3 | ## 1. 安装 Composer 4 | 5 | PhpBoot 框架使用 Composer 来管理其依赖包。所以,在你使用 PhpBoot 之前,你必须确认在你电脑上是否安装了 Composer。 6 | ``` 7 | curl -s http://getcomposer.org/installer | php 8 | ``` 9 | ## 2. 安装 PhpBoot 10 | 11 | 完成 Composer 安装后,在你的项目目录下执行 composer,即可添加 PhpBoot 依赖。 12 | 13 | ``` 14 | composer require "caoym/phpboot" 15 | ``` -------------------------------------------------------------------------------- /docs/quick-start/requirements.md: -------------------------------------------------------------------------------- 1 | # 环境要求 2 | 3 | PhpBoot 框架有一些系统上的需求: 4 | 5 | * PHP 版本 >= 5.5.9 6 | * APC 扩展启用 7 | 8 | ``` 9 | apc.enable=1 10 | ``` 11 | 12 | * 如果启用了OPcache,应同时配置以下选项: 13 | 14 | ``` 15 | opcache.save_comments=1 16 | opcache.load_comments=1 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /docs/quick-start/webserver-config.md: -------------------------------------------------------------------------------- 1 | # WebServer 配置 2 | 3 | 为了使用PhpBoot,你需要配置 WebServer,将所有动态请求指向 index.php 4 | 5 | ## 1. Nginx 6 | 7 | 若使用 Nginx ,修改你的项目对应的配置: 8 | 9 | ``` 10 | server { 11 | listen 80; 12 | server_name example.com; 13 | index index.php; 14 | error_log /path/to/example.error.log; 15 | access_log /path/to/example.access.log; 16 | root /path/to/public; 17 | 18 | location / { 19 | try_files $uri /index.php$is_args$args; 20 | } 21 | 22 | location ~ \.php { 23 | try_files $uri =404; 24 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 25 | include fastcgi_params; 26 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 27 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 28 | fastcgi_index index.php; 29 | fastcgi_pass 127.0.0.1:9000; 30 | } 31 | } 32 | ``` 33 | 34 | ## 2. Apache 35 | 36 | Apache 的配置稍微复杂,首先你需要启 mod_rewrite 模块,然后在 index.php 目录下添加 .htaccess 文件: 37 | 38 | ``` 39 | Options +FollowSymLinks 40 | RewriteEngine On 41 | 42 | RewriteCond %{REQUEST_FILENAME} !-d 43 | RewriteCond %{REQUEST_FILENAME} !-f 44 | RewriteRule ^ index.php [L] 45 | ``` 46 | 47 | 另外还需要修改虚拟主机的AllowOverride配置 48 | 49 | ``` 50 | AllowOverride All 51 | ``` 52 | 53 | **注意:由于 WebServer 版本的差异, 以上配置可能不能按预期工作,但这是使用多数 PHP 框架第一步需要解决的问题, 网上有会有很多解决方案,用好搜索引擎即可** 54 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx-rtd-theme 2 | sphinx 3 | recommonmark -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | tests/ 7 | 8 | 9 | 10 | 11 | 12 | src/ 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/Annotation/AnnotationBlock.php: -------------------------------------------------------------------------------- 1 | name = $name; 23 | $this->summary = $summary; 24 | $this->description = $description; 25 | $this->children = $children; 26 | $this->parent = $parent; 27 | } 28 | 29 | /** 30 | * @var string 31 | */ 32 | public $name = ''; 33 | /** 34 | * @var string 35 | */ 36 | public $summary = ''; 37 | /** 38 | * @var string 39 | */ 40 | public $description=''; 41 | /** 42 | * @var AnnotationTag[] 43 | */ 44 | public $children=[]; 45 | 46 | /** 47 | * @var AnnotationTag|null 48 | */ 49 | public $parent; 50 | 51 | 52 | public function offsetExists($offset) 53 | { 54 | return isset($this->$offset); 55 | } 56 | 57 | 58 | public function offsetGet($offset) 59 | { 60 | return $this->$offset; 61 | } 62 | 63 | 64 | public function offsetSet($offset, $value) 65 | { 66 | $this->$offset = $value; 67 | } 68 | 69 | 70 | public function offsetUnset($offset) 71 | { 72 | unset($this->$offset); 73 | } 74 | } -------------------------------------------------------------------------------- /src/Annotation/AnnotationTag.php: -------------------------------------------------------------------------------- 1 | name = $name; 22 | $this->description = $description; 23 | $this->children = $children; 24 | $this->parent = $parent; 25 | } 26 | 27 | /** 28 | * @var string 29 | */ 30 | public $name = ''; 31 | /** 32 | * @var string 33 | */ 34 | public $description=''; 35 | /** 36 | * @var AnnotationBlock[] 37 | */ 38 | public $children=[]; 39 | 40 | /** 41 | * @var AnnotationBlock|null 42 | */ 43 | public $parent; 44 | 45 | /** 46 | * Whether a offset exists 47 | * @link http://php.net/manual/en/arrayaccess.offsetexists.php 48 | * @param mixed $offset

49 | * An offset to check for. 50 | *

51 | * @return boolean true on success or false on failure. 52 | *

53 | *

54 | * The return value will be casted to boolean if non-boolean was returned. 55 | * @since 5.0.0 56 | */ 57 | public function offsetExists($offset) 58 | { 59 | return isset($this->$offset); 60 | } 61 | 62 | /** 63 | * Offset to retrieve 64 | * @link http://php.net/manual/en/arrayaccess.offsetget.php 65 | * @param mixed $offset

66 | * The offset to retrieve. 67 | *

68 | * @return mixed Can return all value types. 69 | * @since 5.0.0 70 | */ 71 | public function offsetGet($offset) 72 | { 73 | return $this->$offset; 74 | } 75 | 76 | /** 77 | * Offset to set 78 | * @link http://php.net/manual/en/arrayaccess.offsetset.php 79 | * @param mixed $offset

80 | * The offset to assign the value to. 81 | *

82 | * @param mixed $value

83 | * The value to set. 84 | *

85 | * @return void 86 | * @since 5.0.0 87 | */ 88 | public function offsetSet($offset, $value) 89 | { 90 | $this->$offset = $value; 91 | } 92 | 93 | /** 94 | * Offset to unset 95 | * @link http://php.net/manual/en/arrayaccess.offsetunset.php 96 | * @param mixed $offset

97 | * The offset to unset. 98 | *

99 | * @return void 100 | * @since 5.0.0 101 | */ 102 | public function offsetUnset($offset) 103 | { 104 | unset($this->$offset); 105 | } 106 | } -------------------------------------------------------------------------------- /src/Annotation/ContainerBuilder.php: -------------------------------------------------------------------------------- 1 | annotations = $annotations; 27 | $this->cache = $cache; 28 | } 29 | 30 | public function setCache(Cache $cache) 31 | { 32 | $this->cache = $cache; 33 | } 34 | /** 35 | * load from class with local cache 36 | * @param string $className 37 | * @return object 38 | */ 39 | public function build($className) 40 | { 41 | //TODO【重要】 使用全局的缓存版本号, 而不是针对每个文件判断缓存过期与否 42 | $rfl = new \ReflectionClass($className) or \PhpBoot\abort("load class $className failed"); 43 | $fileName = $rfl->getFileName(); 44 | $key = str_replace('\\','.',get_class($this)).md5(serialize($this->annotations).$fileName.$className); 45 | $cache = new CheckableCache($this->cache); 46 | $res = $cache->get($key, $this); 47 | if($res === $this){ 48 | try{ 49 | $meta = $this->buildWithoutCache($className); 50 | $cache->set($key, $meta, 0, $fileName?new ClassModifiedChecker($className):null); 51 | return $meta; 52 | }catch (\Exception $e){ 53 | Logger::warning(__METHOD__.' failed with '.$e->getMessage()); 54 | $cache->set($key, $e->getMessage(), 0, $fileName?new ClassModifiedChecker($className):null); 55 | throw $e; 56 | } 57 | }elseif(is_string($res)){ 58 | \PhpBoot\abort($res); 59 | }else{ 60 | return $res; 61 | } 62 | } 63 | 64 | /** 65 | * @param string $className 66 | * @return object 67 | */ 68 | abstract protected function createContainer($className); 69 | 70 | protected function postCreateContainer($object) 71 | { 72 | 73 | } 74 | protected function handleAnnotation($handlerName, $container, $ann){ 75 | $handler = new $handlerName(); 76 | return $handler($container, $ann); 77 | } 78 | /** 79 | * @param $className 80 | * @return object 81 | */ 82 | public function buildWithoutCache($className) 83 | { 84 | $container = $this->createContainer($className); 85 | $anns = AnnotationReader::read($className, $this->cache); 86 | foreach ($this->annotations as $i){ 87 | list($class, $target) = $i; 88 | 89 | $found = \JmesPath\search($target, $anns); 90 | if(is_array($found)){ 91 | foreach ($found as $f){ 92 | $this->handleAnnotation($class, $container,$f); //TODO 支持 93 | } 94 | }else{ 95 | $this->handleAnnotation($class, $container, $found); 96 | } 97 | } 98 | $this->postCreateContainer($container); 99 | return $container; 100 | } 101 | 102 | /** 103 | * @var array 104 | */ 105 | private $annotations=[]; 106 | /** 107 | * @var Cache 108 | */ 109 | private $cache; 110 | } -------------------------------------------------------------------------------- /src/Cache/CheckableCache.php: -------------------------------------------------------------------------------- 1 | impl = $impl; 14 | } 15 | 16 | /** 17 | * 设置cache 18 | * 19 | * @param string $name 20 | * @param mixed $var 21 | * @param int 22 | * @param callable $expireCheck 23 | * @return boolean 24 | * 缓存过期检查方法, 缓存过期(超过ttl)后, get时调用, 返回true表示缓存继续可用. 25 | * 如checker($got_var, $time) 26 | * 27 | */ 28 | public function set($name, $var, $ttl = 0, callable $expireCheck = null) 29 | { 30 | $res = $this->impl->save($name, serialize(array( 31 | $var, 32 | $ttl, 33 | $expireCheck, 34 | time(), 35 | )), is_null($expireCheck) ? $ttl : 0); 36 | return $res; 37 | } 38 | 39 | /** 40 | * @param string $name 41 | * @param mixed|null $default 42 | * @param mixed|null $expiredData 43 | * @param bool $deleteExpiredData 44 | * @return mixed 45 | */ 46 | public function get($name, $default = null, &$expiredData=null, $deleteExpiredData=true) 47 | { 48 | $expiredData = null; 49 | $res = $this->impl->fetch($name); 50 | if ($res) { 51 | list ($data, $ttl, $checker, $createdTime) = unserialize($res); 52 | // 如果指定了checker, ttl代表每次检查的间隔时间, 0表示每次get都需要经过checker检查 53 | // 如果没有指定checker, ttl表示缓存过期时间, 为0表示永不过期 54 | if ($checker !== null) { 55 | if ($ttl == 0 || ($createdTime + $ttl < time())) { 56 | $valid = $checker($data, $createdTime); 57 | if(!$valid){ 58 | $expiredData = $data; 59 | if($deleteExpiredData){ 60 | $this->impl->delete($name); 61 | } 62 | return $default; 63 | } 64 | 65 | } 66 | }else if ($ttl != 0 && ($createdTime + $ttl < time())) { 67 | $this->impl->delete($name); 68 | return $default; 69 | } 70 | return $data; 71 | } 72 | return $default; 73 | } 74 | /** 75 | * 删除 76 | * @param string $name 77 | */ 78 | public function del($name){ 79 | return $this->impl->delete($name); 80 | } 81 | public function getImpl() 82 | { 83 | return $this->impl; 84 | } 85 | /** 86 | * @var Cache 87 | */ 88 | private $impl; 89 | } 90 | -------------------------------------------------------------------------------- /src/Cache/ClassModifiedChecker.php: -------------------------------------------------------------------------------- 1 | getFileName()){ 12 | $files[] = $class->getFileName(); 13 | self::getParentFileName($class, $files); 14 | } 15 | parent::__construct($files); 16 | } 17 | 18 | static public function getParentFileName(\ReflectionClass $class, array &$files) 19 | { 20 | $parent = $class->getParentClass(); 21 | if(!$parent){ 22 | return; 23 | } 24 | if($parent->getFileName()){ 25 | $files[] = $parent->getFileName(); 26 | self::getParentFileName($parent, $files); 27 | } 28 | foreach ($class->getInterfaces() as $interface){ 29 | if($interface->getFileName()){ 30 | $files[] = $interface->getFileName(); 31 | self::getParentFileName($interface, $files); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Cache/FileModifiedChecker.php: -------------------------------------------------------------------------------- 1 | fileName[$fileName] = @filemtime($fileName); 24 | }else { 25 | $this->fileName[$fileName] = @filemtime($fileName); 26 | if(!is_dir($fileName)){ 27 | continue; 28 | } 29 | $files = @dir($fileName) or \PhpBoot\abort("open dir $fileName failed"); 30 | while (!!($file = $files->read())){ 31 | if($file == '.' || $file == '..') { 32 | continue; 33 | } 34 | $this->fileName[$fileName.'/'.$file] = @filemtime($fileName.'/'.$file); 35 | } 36 | $files->close(); 37 | } 38 | } 39 | } 40 | /** 41 | * 判断是否过期 42 | * @param mixed $data 从缓存中得到的数据 43 | * @param int $createdTime 44 | * @return boolean 45 | */ 46 | public function __invoke($data, $createdTime){ 47 | foreach ($this->fileName as $name => $time){ 48 | if(@filemtime($name) != $time){ 49 | return false; 50 | } 51 | } 52 | return true; 53 | } 54 | private $fileName=[]; //文件全路径 55 | } 56 | -------------------------------------------------------------------------------- /src/Console.php: -------------------------------------------------------------------------------- 1 | make(self::class); 45 | } 46 | /** 47 | * @param $className 48 | * @throws \Exception 49 | */ 50 | public function loadCommandsFromClass($className) 51 | { 52 | $console = null; 53 | $container = null; 54 | 55 | $container = $this->consoleContainerBuilder->build($className); 56 | /**@var ConsoleContainer $container*/ 57 | foreach ($container->getCommands() as $name => $command) { 58 | $command->setCode(function (InputInterface $input, OutputInterface $output)use ($container, $command){ 59 | return $this->diInvoker->call([$command, 'invoke'], ['container'=>$container, 'input'=>$input, 'output'=>$output]); 60 | }); 61 | $this->add($command); 62 | } 63 | } 64 | 65 | /** 66 | * @param $fromPath 67 | * @param string $namespace 68 | * @throws \Exception 69 | */ 70 | public function loadCommandsFromPath($fromPath, $namespace = '') 71 | { 72 | $dir = @dir($fromPath) or abort("dir $fromPath not exist"); 73 | 74 | $getEach = function () use ($dir) { 75 | $name = $dir->read(); 76 | if (!$name) { 77 | return $name; 78 | } 79 | return $name; 80 | }; 81 | 82 | while (!!($entry = $getEach())) { 83 | if ($entry == '.' || $entry == '..') { 84 | continue; 85 | } 86 | $path = $fromPath . '/' . str_replace('\\', '/', $entry); 87 | if (is_file($path) && substr_compare($entry, '.php', strlen($entry) - 4, 4, true) == 0) { 88 | $class_name = $namespace . '\\' . substr($entry, 0, strlen($entry) - 4); 89 | $this->loadCommandsFromClass($class_name); 90 | } else { 91 | //\Log::debug($path.' ignored'); 92 | } 93 | } 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /src/Console/Annotations/ClassAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | getClassName()); 20 | $container->setDescription($ann->description); 21 | $container->setSummary($ann->summary); 22 | 23 | } 24 | } -------------------------------------------------------------------------------- /src/Console/Annotations/CommandAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | description, 2); 24 | $target = $ann->parent->name; 25 | $name = $params->getParam(0, $target); 26 | 27 | //获取方法参数信息 28 | $rfl = new \ReflectionClass($container->getClassName()); 29 | $method = $rfl->getMethod($target); 30 | $methodParams = $method->getParameters(); 31 | 32 | $command = new Command($target, $name); 33 | $command->setDescription($container->getSummary().' : '.$ann->parent->summary); 34 | $command->setHelp($ann->parent->description); 35 | 36 | //设置参数列表 37 | $paramsMeta = []; 38 | foreach ($methodParams as $param){ 39 | $paramName = $param->getName(); 40 | $source = "argv.$paramName"; 41 | $paramClass = $param->getClass(); 42 | if($paramClass){ 43 | $paramClass = $paramClass->getName(); 44 | } 45 | $entityContainer = ContainerFactory::create($entityBuilder, $paramClass); 46 | $meta = new ParamMeta($paramName, 47 | $source, 48 | $paramClass?:'mixed', 49 | $param->isOptional(), 50 | $param->isOptional()?$param->getDefaultValue():null, 51 | $param->isPassedByReference(), 52 | null, 53 | '', 54 | $entityContainer 55 | ); 56 | $paramsMeta[] = $meta; 57 | } 58 | $command->setParamMetas($paramsMeta); 59 | $container->addCommand($target, $command); 60 | } 61 | } -------------------------------------------------------------------------------- /src/Console/Annotations/CommandNameAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | description, 2); 19 | $container->setModuleName($params->getParam(0, '')); 20 | } 21 | } -------------------------------------------------------------------------------- /src/Console/Annotations/ParamAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | getParam(0), 1); 28 | $paramDoc = $params->getRawParam(1, ''); 29 | }else{ 30 | $params = new AnnotationParams($text, 3); 31 | if ($params->count() >=2 && substr($params->getParam(1), 0, 1) == '$'){ 32 | $paramType = $params->getParam(0); //TODO 检测类型是否合法 33 | $paramName = substr($params->getParam(1), 1); 34 | $paramDoc = $params->getRawParam(2, ''); 35 | }else{ 36 | \PhpBoot\abort(new AnnotationSyntaxException("@param $text syntax error")); 37 | } 38 | } 39 | return [$paramType, $paramName, $paramDoc]; 40 | } 41 | /** 42 | * @param ConsoleContainer $container 43 | * @param AnnotationBlock|AnnotationTag $ann 44 | * @param EntityContainerBuilder $entityBuilder 45 | */ 46 | public function __invoke(ConsoleContainer $container, $ann, EntityContainerBuilder $entityBuilder) 47 | { 48 | if(!$ann->parent){ 49 | //Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()} should be used with parent route"); 50 | return; 51 | } 52 | $target = $ann->parent->name; 53 | $command = $container->getCommand($target); 54 | if(!$command){ 55 | //Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target should be used with parent route"); 56 | return ; 57 | } 58 | $className = $container->getClassName(); 59 | 60 | list($paramType, $paramName, $paramDoc) = self::getParamInfo($ann->description); 61 | 62 | $paramMeta = $command->getParamMeta($paramName); 63 | $paramMeta or \PhpBoot\abort(new AnnotationSyntaxException("$className::$target param $paramName not exist ")); 64 | //TODO 检测声明的类型和注释的类型是否匹配 65 | if($paramType){ 66 | $paramMeta->type = TypeHint::normalize($paramType, $className);//or \PhpBoot\abort(new AnnotationSyntaxException("{$container->getClassName()}::{$ann->parent->name} @{$ann->name} syntax error, param $paramName unknown type:$paramType ")); 67 | $container = ContainerFactory::create($entityBuilder, $paramMeta->type); 68 | $paramMeta->container = $container; 69 | } 70 | $paramMeta->description = $paramDoc; 71 | } 72 | } -------------------------------------------------------------------------------- /src/Console/Annotations/ValidateAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | parent || !$ann->parent->parent){ 22 | Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()} should be used with parent parent"); 23 | return; 24 | } 25 | $target = $ann->parent->parent->name; 26 | $command = $container->getCommand($target); 27 | if(!$command){ 28 | Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target should be used with parent parent"); 29 | return ; 30 | } 31 | $params = new AnnotationParams($ann->description, 2); 32 | 33 | count($params)>0 or \PhpBoot\abort(new AnnotationSyntaxException("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target require 1 param, {$params->count()} given")); 34 | 35 | if($ann->parent->name == 'param'){ 36 | list($paramType, $paramName, $paramDoc) = ParamAnnotationHandler::getParamInfo($ann->parent->description); 37 | 38 | $paramMeta = $command->getParamMeta($paramName); 39 | if($params->count()>1){ 40 | $paramMeta->validation = [$params[0], $params[1]]; 41 | }else{ 42 | $paramMeta->validation = $params[0]; 43 | if($paramMeta->validation) { 44 | $v = new Validator(); 45 | $v->rule($paramMeta->validation, $paramMeta->name); 46 | if ($v->hasRule('optional', $paramMeta->name)) { 47 | $paramMeta->isOptional = true; 48 | } 49 | } 50 | } 51 | 52 | return; 53 | } 54 | Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target should be used with parent parent"); 55 | } 56 | } -------------------------------------------------------------------------------- /src/Console/ConsoleContainer.php: -------------------------------------------------------------------------------- 1 | className = $className; 44 | } 45 | 46 | public function getClassName() 47 | { 48 | return $this->className; 49 | } 50 | 51 | /** 52 | * @return Command[] 53 | */ 54 | public function getCommands() 55 | { 56 | return $this->commands; 57 | } 58 | 59 | public function getCommand($target) 60 | { 61 | return isset($this->commands[$target])?$this->commands[$target]:null; 62 | } 63 | /** 64 | * @return string 65 | */ 66 | public function getDescription() 67 | { 68 | return $this->description; 69 | } 70 | 71 | /** 72 | * @return string 73 | */ 74 | public function getSummary() 75 | { 76 | return $this->summary; 77 | } 78 | 79 | public function setDescription($description) 80 | { 81 | $this->description = $description; 82 | } 83 | 84 | public function setSummary($summary) 85 | { 86 | $this->summary = $summary; 87 | } 88 | 89 | public function setModuleName($name) 90 | { 91 | $this->moduleName = $name; 92 | } 93 | 94 | public function addCommand($target, Command $command) 95 | { 96 | $this->commands[$target] = $command; 97 | } 98 | public function postCreate() 99 | { 100 | foreach ($this->commands as $command){ 101 | $command->postCreate($this); 102 | } 103 | } 104 | public function getModuleName() 105 | { 106 | return $this->moduleName; 107 | } 108 | 109 | /** 110 | * @return mixed|object 111 | * @throws \DI\DependencyException 112 | * @throws \DI\NotFoundException 113 | */ 114 | public function getInstance(FactoryInterface $factory) 115 | { 116 | if(!$this->instance ){ 117 | $this->instance = $factory->make($this->getClassName()); 118 | } 119 | return $this->instance; 120 | } 121 | } -------------------------------------------------------------------------------- /src/Console/ConsoleContainerBuilder.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 52 | $this->diInvoker = $diInvoker; 53 | } 54 | 55 | /** 56 | * @param string $className 57 | * @return ConsoleContainer 58 | */ 59 | protected function createContainer($className) 60 | { 61 | return $this->factory->make(ConsoleContainer::class, ['className'=>$className]); 62 | } 63 | 64 | protected function handleAnnotation($handlerName, $container, $ann) 65 | { 66 | $handler = $this->factory->make($handlerName); 67 | return $this->diInvoker->call($handler, [$container, $ann]); 68 | } 69 | 70 | protected function postCreateContainer($object) 71 | { 72 | parent::postCreateContainer($object); 73 | /**@var ConsoleContainer $object*/ 74 | $object->postCreate(); 75 | } 76 | } -------------------------------------------------------------------------------- /src/Controller/Annotations/BindAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | parent || !$ann->parent->parent){ 25 | Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()} should be used with parent param/return"); 26 | return; 27 | } 28 | $target = $ann->parent->parent->name; 29 | $route = $container->getRoute($target); 30 | if(!$route){ 31 | Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target should be used with parent param/return"); 32 | return ; 33 | } 34 | 35 | $params = new AnnotationParams($ann->description, 2); 36 | 37 | $params->count()>0 or \PhpBoot\abort(new AnnotationSyntaxException("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target require 1 param, {$params->count()} given")); 38 | 39 | $handler = $route->getResponseHandler(); 40 | 41 | if ($ann->parent->name == 'return'){ 42 | list($target, $return) = $handler->getMappingBySource('return'); 43 | if($return){ 44 | $handler->eraseMapping($target); 45 | $handler->setMapping($params[0], $return); 46 | } 47 | 48 | }elseif($ann->parent->name == 'param'){ 49 | list($paramType, $paramName, $paramDoc) = ParamAnnotationHandler::getParamInfo($ann->parent->description); 50 | 51 | $paramMeta = $route->getRequestHandler()->getParamMeta($paramName); 52 | if($paramMeta->isPassedByReference){ 53 | list($target, $ori) = $handler->getMappingBySource('params.'.$paramName); 54 | if($ori){ 55 | $handler->eraseMapping($target); 56 | } 57 | //输出绑定 58 | $handler->setMapping( 59 | $params[0], 60 | new ReturnMeta( 61 | 'params.'.$paramMeta->name, 62 | $paramMeta->type, $paramDoc, 63 | ContainerFactory::create($entityBuilder, $paramMeta->type) 64 | ) 65 | ); 66 | }else{ 67 | $paramMeta->source = $params[0]; 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/Controller/Annotations/ClassAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | getClassName()); 19 | $container->getClassName(); 20 | 21 | $container->setDescription($ann->description); 22 | $container->setSummary($ann->summary); 23 | $container->setFileName($ref->getFileName()); 24 | } 25 | } -------------------------------------------------------------------------------- /src/Controller/Annotations/HookAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | parent){ 22 | Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()} should be used with parent route"); 23 | return; 24 | } 25 | $target = $ann->parent->name; 26 | $route = $container->getRoute($target); 27 | if(!$route){ 28 | Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target should be used with parent route"); 29 | return ; 30 | } 31 | $params = new AnnotationParams($ann->description, 2); 32 | count($params)>0 or \PhpBoot\abort("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target require at least one param, 0 given"); 33 | $className = $params[0]; 34 | $className = TypeHint::normalize($className, $container->getClassName()); 35 | is_subclass_of($className, HookInterface::class) or \PhpBoot\abort("$className is not a HookInterface on the annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target"); 36 | $route->addHook($className); 37 | } 38 | } -------------------------------------------------------------------------------- /src/Controller/Annotations/ParamAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | getParam(0), 1); 27 | $paramDoc = $params->getRawParam(1, ''); 28 | }else{ 29 | $params = new AnnotationParams($text, 3); 30 | if ($params->count() >=2 && substr($params->getParam(1), 0, 1) == '$'){ 31 | $paramType = $params->getParam(0); //TODO 检测类型是否合法 32 | $paramName = substr($params->getParam(1), 1); 33 | $paramDoc = $params->getRawParam(2, ''); 34 | }else{ 35 | \PhpBoot\abort(new AnnotationSyntaxException("@param $text syntax error")); 36 | } 37 | } 38 | return [$paramType, $paramName, $paramDoc]; 39 | } 40 | /** 41 | * @param ControllerContainer $container 42 | * @param AnnotationBlock|AnnotationTag $ann 43 | * @param EntityContainerBuilder $entityBuilder 44 | */ 45 | public function __invoke(ControllerContainer $container, $ann, EntityContainerBuilder $entityBuilder) 46 | { 47 | if(!$ann->parent){ 48 | //Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()} should be used with parent route"); 49 | return; 50 | } 51 | $target = $ann->parent->name; 52 | $route = $container->getRoute($target); 53 | if(!$route){ 54 | //Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target should be used with parent route"); 55 | return ; 56 | } 57 | $className = $container->getClassName(); 58 | 59 | list($paramType, $paramName, $paramDoc) = self::getParamInfo($ann->description); 60 | 61 | $paramMeta = $route->getRequestHandler()->getParamMeta($paramName); 62 | $paramMeta or \PhpBoot\abort(new AnnotationSyntaxException("$className::$target param $paramName not exist ")); 63 | //TODO 检测声明的类型和注释的类型是否匹配 64 | if($paramType){ 65 | $paramMeta->type = TypeHint::normalize($paramType, $className);//or \PhpBoot\abort(new AnnotationSyntaxException("{$container->getClassName()}::{$ann->parent->name} @{$ann->name} syntax error, param $paramName unknown type:$paramType ")); 66 | $container = ContainerFactory::create($entityBuilder, $paramMeta->type); 67 | $paramMeta->container = $container; 68 | } 69 | $paramMeta->description = $paramDoc; 70 | 71 | $responseHandler = $route->getResponseHandler(); 72 | if($paramMeta->isPassedByReference && $responseHandler){ 73 | $mappings = $responseHandler->getMappings(); 74 | foreach ($mappings as $k => $v){ 75 | if($v->source == 'params.'.$paramMeta->name){ 76 | $v->description = $paramMeta->description; 77 | $v->type = $paramMeta->type; 78 | $v->container = $paramMeta->container; 79 | } 80 | } 81 | } 82 | 83 | } 84 | } -------------------------------------------------------------------------------- /src/Controller/Annotations/PathAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | description, 2); 20 | $container->setUriPrefix($params->getParam(0, '')); 21 | } 22 | } -------------------------------------------------------------------------------- /src/Controller/Annotations/ReturnAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | parent){ 25 | //Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()} should be used with parent route"); 26 | return; 27 | } 28 | $target = $ann->parent->name; 29 | $route = $container->getRoute($target); 30 | if(!$route){ 31 | //Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target should be used with parent route"); 32 | return ; 33 | } 34 | 35 | $params = new AnnotationParams($ann->description, 2); 36 | $type = $doc = null; 37 | if(count($params)>0){ 38 | $type = TypeHint::normalize($params[0], $container->getClassName()); 39 | } 40 | $doc = $params->getRawParam(1, ''); 41 | 42 | list($_, $meta) = $route 43 | ->getResponseHandler() 44 | ->getMappingBySource('return'); 45 | if($meta){ 46 | $meta->description = $doc; 47 | $meta->type = $type; 48 | $meta->container = $type == 'void'?null:ContainerFactory::create($entityBuilder, $type); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/Controller/Annotations/ThrowsAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | parent){ 23 | //Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()} should be used with parent route"); 24 | return; 25 | } 26 | $target = $ann->parent->name; 27 | $route = $container->getRoute($target); 28 | if(!$route){ 29 | //Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target should be used with parent route"); 30 | return ; 31 | } 32 | $params = new AnnotationParams($ann->description, 2); 33 | count($params)>0 or \PhpBoot\abort(new AnnotationSyntaxException("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target require at least one param, {$params->count()} given")); 34 | 35 | $type = TypeHint::normalize($params[0], $container->getClassName()); // TODO 缺少类型时忽略错误 36 | $doc = $params->getRawParam(1, ''); 37 | 38 | $route->getExceptionHandler()->addExceptions($type, $doc); 39 | } 40 | } -------------------------------------------------------------------------------- /src/Controller/Annotations/ValidateAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | parent || !$ann->parent->parent){ 22 | Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()} should be used with parent parent"); 23 | return; 24 | } 25 | $target = $ann->parent->parent->name; 26 | $route = $container->getRoute($target); 27 | if(!$route){ 28 | Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target should be used with parent parent"); 29 | return ; 30 | } 31 | $params = new AnnotationParams($ann->description, 2); 32 | 33 | count($params)>0 or \PhpBoot\abort(new AnnotationSyntaxException("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target require 1 param, {$params->count()} given")); 34 | 35 | if($ann->parent->name == 'param'){ 36 | list($paramType, $paramName, $paramDoc) = ParamAnnotationHandler::getParamInfo($ann->parent->description); 37 | 38 | $paramMeta = $route->getRequestHandler()->getParamMeta($paramName); 39 | if($params->count()>1){ 40 | $paramMeta->validation = [$params[0], $params[1]]; 41 | }else{ 42 | $paramMeta->validation = $params[0]; 43 | if($paramMeta->validation) { 44 | $v = new Validator(); 45 | $v->rule($paramMeta->validation, $paramMeta->name); 46 | if ($v->hasRule('optional', $paramMeta->name)) { 47 | $paramMeta->isOptional = true; 48 | } 49 | } 50 | } 51 | 52 | return; 53 | } 54 | Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::$target should be used with parent parent"); 55 | } 56 | } -------------------------------------------------------------------------------- /src/Controller/ControllerContainerBuilder.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 53 | $this->diInvoker = $diInvoker; 54 | } 55 | /** 56 | * load from class with local cache 57 | * @param string $className 58 | * @return ControllerContainer 59 | */ 60 | public function build($className) 61 | { 62 | return parent::build($className); 63 | } 64 | 65 | /** 66 | * @param $className 67 | * @return ControllerContainer 68 | */ 69 | public function buildWithoutCache($className) 70 | { 71 | return parent::buildWithoutCache($className); 72 | } 73 | 74 | /** 75 | * @param string $className 76 | * @return object 77 | */ 78 | protected function createContainer($className) 79 | { 80 | return $this->factory->make(ControllerContainer::class, ['className'=>$className]); 81 | } 82 | 83 | protected function handleAnnotation($handlerName, $container, $ann) 84 | { 85 | $handler = $this->factory->make($handlerName); 86 | return $this->diInvoker->call($handler, [$container, $ann]); 87 | } 88 | 89 | 90 | /** 91 | * @var FactoryInterface 92 | */ 93 | private $factory; 94 | /** 95 | * @var DIInvokerInterface 96 | */ 97 | private $diInvoker; 98 | } -------------------------------------------------------------------------------- /src/Controller/ExceptionHandler.php: -------------------------------------------------------------------------------- 1 | renderer = new ExceptionRenderer(); 13 | } 14 | 15 | /** 16 | * @param string $name 17 | * @param string $doc 18 | */ 19 | public function addExceptions($name, $doc) 20 | { 21 | $this->exceptions[] = [$name, $doc]; 22 | } 23 | 24 | /* 25 | * @return array 26 | * 返回包含异常类型和描述的数组 27 | * 示例 28 | * [ 29 | * ['NotFoundHttpException', '这是说明'], 30 | * ['ForbiddenHttpException', '这是说明'], 31 | * ] 32 | */ 33 | public function getExceptions() 34 | { 35 | return $this->exceptions; 36 | } 37 | 38 | /** 39 | * @var array 40 | * 示例 41 | * [ 42 | * ['NotFoundHttpException', '这是说明'], 43 | * ['ForbiddenHttpException', '这是说明'], 44 | * ] 45 | */ 46 | private $exceptions = []; 47 | 48 | /** 49 | * @param Application $app 50 | * @param callable $call 51 | * @return \Symfony\Component\HttpFoundation\Response 52 | */ 53 | public function handler(Application $app, callable $call){ 54 | try{ 55 | return $call(); 56 | }catch (\Exception $e){ 57 | $renderer = $app->get(ExceptionRenderer::class); //TODO 放在这里是否合适 58 | return $renderer->render($e); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/Controller/ExceptionRenderer.php: -------------------------------------------------------------------------------- 1 | getMessage(), $e->getStatusCode(), $e->getHeaders()); 19 | } if($e instanceof \InvalidArgumentException){ 20 | return new Response($e->getMessage(), Response::HTTP_BAD_REQUEST); 21 | }else{ 22 | return new Response($e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Controller/HookInterface.php: -------------------------------------------------------------------------------- 1 | paramMetas = $paramMates; 20 | } 21 | 22 | /** 23 | * @param Application $app 24 | * @param Request $request 25 | * @param array $params 26 | * @param array $reference 27 | * @return void 28 | */ 29 | public function handle(Application $app, Request $request, array &$params, array &$reference){ 30 | 31 | $vld = new Validator(); 32 | $req = ['request'=>$request]; 33 | $requestArray = new ArrayAdaptor($req); 34 | $inputs = []; 35 | foreach ($this->paramMetas as $k=>$meta){ 36 | if($meta->isPassedByReference){ 37 | // param PassedByReference is used to output 38 | continue; 39 | } 40 | $source = \JmesPath\search($meta->source, $requestArray); 41 | if ($source !== null){ 42 | $source = ArrayAdaptor::strip($source); 43 | if($source instanceof ParameterBag){ 44 | $source = $source->all(); 45 | } 46 | if($meta->container){ 47 | $inputs[$meta->name] = $meta->container->make($source); 48 | }else{ 49 | $inputs[$meta->name] = $source; 50 | } 51 | if($meta->validation){ 52 | $vld->rule($meta->validation, $meta->name); 53 | } 54 | }else{ 55 | $meta->isOptional or \PhpBoot\abort(new BadRequestHttpException("the parameter \"{$meta->source}\" is missing")); 56 | $inputs[$meta->name] = $meta->default; 57 | } 58 | } 59 | $vld = $vld->withData($inputs); 60 | $vld->validate() or \PhpBoot\abort( 61 | new \InvalidArgumentException( 62 | json_encode( 63 | $vld->errors(), 64 | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE 65 | ) 66 | ) 67 | ); 68 | 69 | $pos = 0; 70 | foreach ($this->paramMetas as $meta){ 71 | if($meta->isPassedByReference){ 72 | $params[$pos] = &$reference[$meta->name]; 73 | }else{ 74 | $params[$pos] = $inputs[$meta->name]; 75 | } 76 | $pos++; 77 | 78 | } 79 | } 80 | 81 | public function getParamNames(){ 82 | return array_map(function($meta){return $meta->name;}, $this->paramMetas); 83 | } 84 | 85 | /** 86 | * 获取参数列表 87 | * @return ParamMeta[] 88 | */ 89 | public function getParamMetas(){ 90 | return $this->paramMetas; 91 | } 92 | 93 | /** 94 | * 获取指定参数信息 95 | * @param $name 96 | * @return ParamMeta|null 97 | */ 98 | public function getParamMeta($name){ 99 | foreach ($this->paramMetas as $meta){ 100 | if($meta->name == $name){ 101 | return $meta; 102 | } 103 | } 104 | return null; 105 | } 106 | 107 | /** 108 | * @param \PhpBoot\Metas\ParamMeta[] $paramMetas 109 | */ 110 | public function setParamMetas($paramMetas) 111 | { 112 | $this->paramMetas = $paramMetas; 113 | } 114 | /** 115 | * @var ParamMeta[] 116 | */ 117 | private $paramMetas = []; 118 | } -------------------------------------------------------------------------------- /src/Controller/ResponseHandler.php: -------------------------------------------------------------------------------- 1 | mappings[$target] = $src; 22 | } 23 | 24 | /** 25 | * @param $target 26 | * @return ReturnMeta 27 | */ 28 | public function eraseMapping($target) 29 | { 30 | if(!isset($this->mappings[$target])){ 31 | return null; 32 | } 33 | $ori = $this->mappings[$target]; 34 | unset($this->mappings[$target]); 35 | return $ori; 36 | } 37 | 38 | /** 39 | * @param string $target 40 | * @return ReturnMeta 41 | */ 42 | public function getMapping($target) 43 | { 44 | if(!array_key_exists($target, $this->mappings)){ 45 | return null; 46 | } 47 | return $this->mappings[$target]; 48 | } 49 | 50 | /** 51 | * @param string $source 52 | * @return array [string,ReturnMeta] 53 | */ 54 | public function getMappingBySource($source) 55 | { 56 | foreach ($this->mappings as $k=>$v){ 57 | if($v->source == $source){ 58 | return [$k, $v]; 59 | } 60 | } 61 | return [null,null]; 62 | } 63 | 64 | 65 | /** 66 | * @param Application $app 67 | * @param $return 68 | * @param $params 69 | * @return Response 70 | */ 71 | public function handle(Application $app, $return, $params) 72 | { 73 | $input = [ 74 | 'return'=>$return, 75 | 'params'=>$params 76 | ]; 77 | 78 | if($return instanceof Response){ //直接返回Response时, 对return不再做映射 79 | return $return; 80 | } 81 | $mappings = $this->getMappings(); 82 | 83 | $output = []; 84 | foreach($mappings as $key=>$map){ 85 | $val = \JmesPath\search($map->source, $input); 86 | if(substr($key, 0, strlen('response.')) == 'response.'){ 87 | $key = substr($key, strlen('response.')); 88 | } 89 | ArrayHelper::set($output, $key, $val); 90 | } 91 | $renderer = $app->get(ResponseRenderer::class); 92 | return $renderer->render($output); 93 | } 94 | /** 95 | * @return ReturnMeta[] 96 | */ 97 | public function getMappings() 98 | { 99 | return $this->mappings; 100 | } 101 | /** 102 | * @var array 103 | */ 104 | private $mappings; 105 | } -------------------------------------------------------------------------------- /src/Controller/ResponseRenderer.php: -------------------------------------------------------------------------------- 1 | headers->set('Content-Type', 'application/json'); 18 | foreach ($output as $key=>$value){ 19 | //TODO 支持自定义格式输出 20 | //TODO 支持更多的输出目标 21 | if($key == 'content'){ 22 | //if(is_array($value) || is_object($value)){ 23 | $value = json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); 24 | //} 25 | $response->setContent($value); 26 | }elseif($key == 'headers'){ 27 | foreach ($value as $k=>$v){ 28 | $response->headers->set($k, $v); 29 | } 30 | }else{ 31 | \PhpBoot\abort(new \UnexpectedValueException("Unexpected output target $key")); 32 | } 33 | 34 | } 35 | return $response; 36 | } 37 | } -------------------------------------------------------------------------------- /src/DB/Context.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 11 | } 12 | 13 | /** 14 | * 拼接sql语句,并自动插入空格 15 | * @param string $sql 表达式 16 | */ 17 | public function appendSql($sql, $addSpace=true){ 18 | if($this->sql == ''){ 19 | $this->sql = $sql; 20 | }else{ 21 | if($addSpace){ 22 | $this->sql = $this->sql.' '.$sql; 23 | }else{ 24 | $this->sql = $this->sql.$sql; 25 | } 26 | } 27 | } 28 | /** 29 | * 增加绑定变量值 30 | * @param array $params 变量 31 | */ 32 | public function appendParams($params){ 33 | $this->params = array_merge($this->params, $params); 34 | } 35 | 36 | public function handleResult($result) 37 | { 38 | if($resultHandler = $this->resultHandler){ 39 | return $resultHandler($result); 40 | }else{ 41 | return $result; 42 | } 43 | } 44 | /** 45 | * @var callable 46 | */ 47 | public $resultHandler; 48 | public $sql=''; 49 | public $params=[]; 50 | /** 51 | * @var \PDO 52 | */ 53 | public $connection; 54 | } -------------------------------------------------------------------------------- /src/DB/Exceptions/DBException.php: -------------------------------------------------------------------------------- 1 | sql = $context->sql; 30 | $this->params = $context->params; 31 | } 32 | 33 | /** 34 | * @return string 35 | */ 36 | public function getSql() 37 | { 38 | return $this->sql; 39 | } 40 | 41 | /** 42 | * @return array 43 | */ 44 | public function getParams() 45 | { 46 | return $this->params; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/DB/NestedStringCut.php: -------------------------------------------------------------------------------- 1 | $state($str, $pos, $state); 18 | if($pos === false){ 19 | break; 20 | } 21 | }; 22 | return false; 23 | } 24 | 25 | public function getSnippets(){ 26 | return $this->snippets; 27 | } 28 | 29 | public function getText(){ 30 | return implode('', $this->snippets); 31 | } 32 | /** 33 | * 将剪切后的字符串位置转换成原始字符串位置 34 | * @param int $pos 35 | * @param int 36 | */ 37 | public function mapPos($pos){ 38 | 39 | foreach ($this->snippets as $k => $v){ 40 | $pos += $k; 41 | if($pos < $k + strlen($v)){ 42 | break; 43 | } 44 | $pos -= ($k + strlen($v)); 45 | 46 | } 47 | return $pos; 48 | } 49 | /** 50 | * 普通状态 51 | */ 52 | private function stateNormal($str, $pos, &$next){ 53 | $ori = $pos; 54 | $posSQ = strpos($str, '\'', $pos); 55 | $posDQ = strpos($str, '"', $pos); 56 | $pos = $posSQ; 57 | $this->subStateQ = '\''; 58 | $next = 'stateQ'; 59 | if($posDQ !== false && (($posDQ < $pos) || ($pos === false)) ){ 60 | $pos = $posDQ; 61 | $this->subStateQ = '"'; 62 | } 63 | if($pos !== false){ 64 | $this->snippets[$ori] = substr($str, $ori, $pos-$ori); 65 | $pos ++; 66 | }else{ 67 | $this->snippets[$ori] = substr($str, $ori); 68 | } 69 | return $pos; 70 | } 71 | 72 | /** 73 | * 进入引号状态 74 | */ 75 | private function stateQ($str, $pos, &$next){ 76 | $posESC = strpos($str, '\\', $pos); 77 | $posQ = strpos($str, $this->subStateQ, $pos); 78 | $pos = $posESC; 79 | $next = 'stateESC'; 80 | 81 | if($posQ !== false && (($posQ<$posESC) || ($posESC === false))){ 82 | $pos = $posQ; 83 | $next = 'stateNormal'; 84 | } 85 | if($pos !== false){ 86 | $pos ++; 87 | } 88 | return $pos; 89 | } 90 | /** 91 | * 进入转义状态 92 | */ 93 | private function stateESC($str, $pos, &$next){ 94 | $pos++; 95 | if($pos >= strlen($str)){ 96 | return false; 97 | } 98 | $next = 'stateQ'; 99 | return $pos; 100 | } 101 | /** 102 | * 去掉嵌套字符串后的内容 103 | * @var array 104 | */ 105 | private $snippets=array(); 106 | 107 | private $subStateQ; 108 | } 109 | -------------------------------------------------------------------------------- /src/DB/README.md: -------------------------------------------------------------------------------- 1 | # ezsql 2 | An an easy-to-use SQL builder. 3 | 4 | ## HOW TO USE 5 | 6 | $db = DB::connect($dsn, $username, $passwd); 7 | 8 | ### SELECT 9 | 10 | $res = $db->select('a, b') 11 | ->from('table') 12 | ->leftJoin('table1')->on('table.id=table1.id') 13 | ->where('a=?',1) 14 | ->groupBy('b')->having('sum(b)=?', 2) 15 | ->orderBy('c', Sql::ORDER_BY_ASC) 16 | ->limit(0,1) 17 | ->forUpdate()->of('d') 18 | ->get(); 19 | ### UPDATE 20 | 21 | $rows = $db->update('table') 22 | ->set('a', 1) 23 | ->where('b=?', 2) 24 | ->orderBy('c', Sql::ORDER_BY_ASC) 25 | ->limit(1) 26 | ->exec() 27 | ->rows 28 | 29 | ### INSERT 30 | 31 | $newId = $db->insertInto('table') 32 | ->values(['a'=>1]) 33 | ->exec() 34 | ->lastInsertId() 35 | 36 | ### DELETE 37 | 38 | $rows = $db->deleteFrom('table') 39 | ->where('b=?', 2) 40 | ->orderBy('c', Sql::ORDER_BY_ASC) 41 | ->limit(1) 42 | ->exec() 43 | ->rows 44 | 45 | -------------------------------------------------------------------------------- /src/DB/Raw.php: -------------------------------------------------------------------------------- 1 | str = $str; 15 | } 16 | public function __toString(){ 17 | return $this->str; 18 | } 19 | public function get(){ 20 | return $this->str; 21 | } 22 | private $str; 23 | } 24 | -------------------------------------------------------------------------------- /src/DB/Rows.php: -------------------------------------------------------------------------------- 1 | "DELETE FROM table" 14 | * @param string $table 15 | * @return \PhpBoot\DB\rules\basic\WhereRule 16 | */ 17 | public function deleteFrom($table) { 18 | DeleteImpl::deleteFrom($this->context, $table); 19 | return new WhereRule($this->context); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/DB/rules/insert.php: -------------------------------------------------------------------------------- 1 | values([1,2]) => "INSERT INTO table VALUES(1,2)" 19 | * @param string $table 20 | * @return \PhpBoot\DB\rules\insert\ValuesRule 21 | */ 22 | public function insertInto($table) { 23 | InsertImpl::insertInto($this->context, $table); 24 | return new ValuesRule($this->context); 25 | } 26 | } 27 | class ValuesRule extends BasicRule 28 | { 29 | /** 30 | * 31 | * insertInto('table')->values([1,2]) => "INSERT INTO table VALUES(1,2)" 32 | * insertInto('table')->values(['a'=>1, 'b'=>Sql::raw('now()')]) => "INSERT INTO table(a,b) VALUES(1,now())" 33 | * @param array $values 34 | * @return \PhpBoot\DB\rules\insert\OnDuplicateKeyUpdateRule 35 | */ 36 | public function values(array $values) { 37 | ValuesImpl::values($this->context, $values); 38 | return new OnDuplicateKeyUpdateRule($this->context); 39 | } 40 | 41 | /** 42 | * insertInto('table')->batchValues([[1,2],[3,4]]) => "INSERT INTO table VALUES(1,2), (3,2)" 43 | * 44 | * @param array $values 45 | * @return \PhpBoot\DB\rules\insert\OnDuplicateKeyUpdateRule 46 | */ 47 | public function batchValues(array $values){ 48 | ValuesImpl::batchValues($this->context, $values); 49 | return new OnDuplicateKeyUpdateRule($this->context); 50 | } 51 | } 52 | 53 | class OnDuplicateKeyUpdateRule extends ExecRule 54 | { 55 | public function __construct($context) 56 | { 57 | parent::__construct($context); 58 | $this->impl = new OnDuplicateKeyUpdateImpl(); 59 | } 60 | 61 | // /** 62 | // * 63 | // * insertInto('table') 64 | // * ->values(['a'=>1, 'b'=>Sql::raw('now()')]) 65 | // * ->onDuplicateKeyUpdate('a', Sql::raw('a+1')) 66 | // * => "INSERT INTO table(a,b) VALUES(1,now()) ON DUPLICATE KEY UPDATE a=a+1" 67 | // * 68 | // * @param string $column 69 | // * @param mixed $value 70 | // * @return \PhpBoot\DB\rules\basic\ExecRule 71 | // */ 72 | // public function onDuplicateKeyUpdate($column, $value) { 73 | // $this->impl->set($this->context, $column, $value); 74 | // return new ExecRule($this->context); 75 | // } 76 | 77 | // /** 78 | // * 79 | // * insertInto('table') 80 | // * ->values(['a'=>1, 'b'=>Sql::raw('now()')]) 81 | // * ->onDuplicateKeyUpdateArgs(['a'=>Sql::raw('a+1')]) 82 | // * => "INSERT INTO table(a,b) VALUES(1,now()) ON DUPLICATE KEY UPDATE a=a+1" 83 | // * 84 | // * @param string $column 85 | // * @param mixed $value 86 | // * @return \PhpBoot\DB\rules\basic\ExecRule 87 | // */ 88 | // public function onDuplicateKeyUpdateArgs($values) { 89 | // $this->impl->setArgs($this->context, $values); 90 | // return new ExecRule($this->context); 91 | // } 92 | 93 | /** 94 | * 95 | * insertInto('table') 96 | * ->values(['a'=>1, 'b'=>Sql::raw('now()')]) 97 | * ->onDuplicateKeyUpdate(['a'=>Sql::raw('a+1')]) 98 | * => "INSERT INTO table(a,b) VALUES(1,now()) ON DUPLICATE KEY UPDATE a=a+1" 99 | * 100 | * insertInto('table') 101 | * ->values(['a'=>1, 'b'=>Sql::raw('now()')]) 102 | * ->onDuplicateKeyUpdate('a=a+1') 103 | * => "INSERT INTO table(a,b) VALUES(1,now()) ON DUPLICATE KEY UPDATE a=a+1" 104 | * 105 | * @param string $column 106 | * @param mixed $value 107 | * @return \PhpBoot\DB\rules\basic\ExecRule 108 | */ 109 | public function onDuplicateKeyUpdate($expr, $_=null) { 110 | $this->impl->set($this->context, $expr, array_slice(func_get_args(), 1)); 111 | return new ExecRule($this->context); 112 | } 113 | private $impl; 114 | } -------------------------------------------------------------------------------- /src/DB/rules/replace.php: -------------------------------------------------------------------------------- 1 | values([1,2]) => "REPLACE INTO table VALUES(1,2)" 16 | * @param string $table 17 | * @return \PhpBoot\DB\rules\replace\ValuesRule 18 | */ 19 | public function replaceInto($table) { 20 | ReplaceImpl::replaceInto($this->context, $table); 21 | return new ValuesRule($this->context); 22 | } 23 | } 24 | class ValuesRule extends BasicRule 25 | { 26 | /** 27 | * replaceInto('table')->values([1,2]) => "REPLACE INTO table VALUES(1,2)" 28 | * replaceInto('table')->values(['a'=>1, 'b'=>Sql::raw('now()')]) => "REPLACE INTO table(a,b) VALUES(1,now())" 29 | * @param array $values 30 | * @return \PhpBoot\DB\rules\basic\ExecRule 31 | */ 32 | public function values($values) { 33 | ValuesImpl::values($this->context, $values); 34 | return new ExecRule($this->context); 35 | } 36 | } -------------------------------------------------------------------------------- /src/DB/rules/update.php: -------------------------------------------------------------------------------- 1 | set('a', 1) => "UPDATE table SET a=1" 17 | * @param string $table 18 | * @return \PhpBoot\DB\rules\update\UpdateSetRule 19 | */ 20 | public function update($table) { 21 | UpdateImpl::update($this->context, $table); 22 | return new UpdateSetRule($this->context); 23 | } 24 | } 25 | 26 | class UpdateSetRule extends BasicRule 27 | { 28 | public function __construct($context){ 29 | parent::__construct($context); 30 | $this->impl = new UpdateSetImpl(); 31 | } 32 | /** 33 | * update('table')->set(['a'=>1]) => "UPDATE table SET a=1" 34 | 35 | * update('table')->set('a=?',1) => "UPDATE table SET a=1" 36 | * @param array|string $expr 37 | * @param mixed $_ 38 | * @return UpdateSetWhereRule 39 | */ 40 | public function set($expr, $_=null) { 41 | $this->impl->set($this->context, $expr, array_slice(func_get_args(), 1)); 42 | return new UpdateSetWhereRule($this->context, $this->impl); 43 | } 44 | private $impl; 45 | } 46 | 47 | class UpdateSetWhereRule extends WhereRule 48 | { 49 | public function __construct(Context $context, UpdateSetImpl $impl){ 50 | parent::__construct($context); 51 | $this->impl = $impl; 52 | } 53 | /** 54 | * update('table')->set(['a'=>1]) => "UPDATE table SET a=1" 55 | * update('table')->set('a=?',1) => "UPDATE table SET a=1" 56 | * @param array|string $expr 57 | * @param mixed $_ 58 | * @return UpdateSetWhereRule 59 | */ 60 | public function set($expr, $_=null) { 61 | $this->impl->set($this->context, $expr, array_slice(func_get_args(), 1)); 62 | return new UpdateSetWhereRule($this->context, $this->impl); 63 | } 64 | private $impl; 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/DI/AnnotationReader.php: -------------------------------------------------------------------------------- 1 | loader->build($name); 27 | /**@var $context ObjectDefinitionContext */ 28 | $definition = $context->definition; 29 | }else{ 30 | $definition = new ObjectDefinition($name); 31 | } 32 | 33 | $constructor = $class->getConstructor(); 34 | if ($constructor && $constructor->isPublic()) { 35 | $definition->setConstructorInjection( 36 | MethodInjection::constructor($this->getParametersDefinition($constructor)) 37 | ); 38 | } 39 | 40 | return $definition; 41 | } 42 | 43 | /** 44 | * Read the type-hinting from the parameters of the function. 45 | */ 46 | private function getParametersDefinition(\ReflectionFunctionAbstract $constructor) 47 | { 48 | $parameters = []; 49 | 50 | foreach ($constructor->getParameters() as $index => $parameter) { 51 | // Skip optional parameters 52 | if ($parameter->isOptional()) { 53 | continue; 54 | } 55 | 56 | $parameterClass = $parameter->getClass(); 57 | 58 | if ($parameterClass) { 59 | $parameters[$index] = new EntryReference($parameterClass->getName()); 60 | } 61 | } 62 | 63 | return $parameters; 64 | } 65 | 66 | /** 67 | * @param DIMetaLoader $loader 68 | */ 69 | public function setLoader(DIMetaLoader $loader) 70 | { 71 | $this->loader = $loader; 72 | } 73 | /** 74 | * @var DIMetaLoader 75 | */ 76 | private $loader; 77 | } -------------------------------------------------------------------------------- /src/DI/Annotations/InjectAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | definition->getClassName(); 23 | if(!$ann->parent){ 24 | Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of $className should be used with parent"); 25 | } 26 | $target = $ann->parent->name; 27 | // @inject a.b.c 28 | $params = new AnnotationParams($ann->description, 3); 29 | if(count($params) == 0){ 30 | //查找@var 定义的变量 31 | if(!isset($context->vars[$target])){ 32 | Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of $className should be used with a param or @var"); 33 | } 34 | $entryName = $context->vars[$target]; 35 | }else{ 36 | $entryName = $params[0]; 37 | } 38 | 39 | $context->definition->addPropertyInjection( 40 | new PropertyInjection($target, new EntryReference($entryName), $className) 41 | ); 42 | } 43 | } -------------------------------------------------------------------------------- /src/DI/Annotations/VarAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | definition->getClassName(); 26 | if(!$ann->parent){ 27 | Logger::debug("The annotation \"@{$ann->name} {$ann->description}\" of $className should be used with parent"); 28 | } 29 | $target = $ann->parent->name; 30 | // 31 | $params = new AnnotationParams($ann->description, 2); 32 | 33 | count($params)>0 or \PhpBoot\abort(new AnnotationSyntaxException("The annotation \"@{$ann->name} {$ann->description}\" of $className::$target require at least one param, 0 given")); 34 | 35 | $context->vars[$target] = TypeHint::normalize($params[0], $className); 36 | } 37 | } -------------------------------------------------------------------------------- /src/DI/DIContainerBuilder.php: -------------------------------------------------------------------------------- 1 | annReader = new AnnotationReader(); 16 | $this->addDefinitions($this->annReader); 17 | } 18 | /** 19 | * Build and return a container. 20 | * 21 | * @return Container 22 | */ 23 | public function build() 24 | { 25 | 26 | $this->useAutowiring(false); 27 | $this->useAnnotations(false); 28 | $container = parent::build(); 29 | $container->call([$this->annReader, 'setLoader']); 30 | return $container; 31 | } 32 | 33 | /** 34 | * @var AnnotationReader 35 | */ 36 | private $annReader; 37 | } -------------------------------------------------------------------------------- /src/DI/DIMetaLoader.php: -------------------------------------------------------------------------------- 1 | definition = new ObjectDefinition($className); 32 | return $res; 33 | } 34 | 35 | protected function getHandler($handlerName, $container) 36 | { 37 | return new $handlerName($container); 38 | } 39 | } -------------------------------------------------------------------------------- /src/DI/ObjectDefinitionContext.php: -------------------------------------------------------------------------------- 1 | {'$ref'} = $ref; 15 | } 16 | public function getRef() 17 | { 18 | return $this->{'$ref'}; 19 | } 20 | public function setRef($ref) 21 | { 22 | $this->{'$ref'} = $ref; 23 | } 24 | } -------------------------------------------------------------------------------- /src/Docgen/Swagger/Schemas/ResponseObject.php: -------------------------------------------------------------------------------- 1 | host = 'api.example.com', 14 | * $swagger->info->description = '...'; 15 | * ... 16 | * }, 17 | * '/docs') 18 | * 19 | * @param Application $app 20 | * @param string $prefix 21 | * @param callable $callback 22 | * @return void 23 | */ 24 | static public function register(Application $app, 25 | callable $callback = null, 26 | $prefix='/docs') 27 | { 28 | $app->addRoute('GET', $prefix.'/swagger.json', function (Application $app)use($callback){ 29 | $swagger = new Swagger(); 30 | $swagger->appendControllers($app, $app->getControllers()); 31 | if($callback){ 32 | $callback($swagger); 33 | } 34 | return new Response($swagger->toJson()); 35 | }); 36 | } 37 | } -------------------------------------------------------------------------------- /src/Entity/Annotations/ClassAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | getClassName()); 20 | $container->getClassName(); 21 | $properties = $ref->getProperties(\ReflectionProperty::IS_PUBLIC); 22 | $default = $ref->getDefaultProperties(); 23 | $container->setFileName($ref->getFileName()); 24 | 25 | $container->setDescription($ann->description); 26 | $container->setSummary($ann->summary); 27 | 28 | foreach ($properties as $i){ 29 | $isOption = array_key_exists($i->getName(), $default) && $default[$i->getName()] !==null; 30 | $container->setProperty($i->getName(), new PropertyMeta( 31 | $i->getName(), 32 | null, 33 | $isOption, 34 | $isOption?$default[$i->getName()]:null 35 | )); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/Entity/Annotations/PropertyAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | getProperty($ann->name); 19 | if(!$meta){ 20 | $meta = new PropertyMeta($ann->name); 21 | $container->setProperty($ann->name, $meta); 22 | } 23 | $meta->description = $ann->description; 24 | $meta->summary = $ann->summary; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Entity/Annotations/ValidateAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | description, 3); 22 | if($params->count()){ 23 | 24 | $target = $ann->parent->name; 25 | $property = $container->getProperty($target); 26 | $property or \PhpBoot\abort($container->getClassName()." property $target not exist "); 27 | if($params->count()>1){ 28 | $property->validation = [$params->getParam(0), $params->getParam(1)]; 29 | }else{ 30 | $property->validation = $params->getParam(0); 31 | if($property->validation){ 32 | $v = new Validator(); 33 | $v->rule($property->validation, $property->name); 34 | if($v->hasRule('optional', $property->name)){ 35 | $property->isOptional = true; 36 | } 37 | } 38 | } 39 | 40 | }else{ 41 | \PhpBoot\abort(new AnnotationSyntaxException( 42 | "The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::{$ann->parent->name} require 1 param, 0 given" 43 | )); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/Entity/Annotations/VarAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | description, 3); 26 | if($params->count()){ 27 | $type = $params->getParam(0); 28 | //TODO 校验type类型 29 | $target = $ann->parent->name; 30 | $property = $container->getProperty($target); 31 | $property or \PhpBoot\abort($container->getClassName()." property $target not exist "); 32 | if($type == null || $type == 'mixed'){ 33 | $property->container = new MixedTypeContainer(); 34 | } else{ 35 | // TODO 判断$type是否匹配 36 | $property->type = TypeHint::normalize($type, $container->getClassName()); 37 | // TODO 防止递归死循环 38 | $property->container = ContainerFactory::create($builder, $property->type); 39 | } 40 | }else{ 41 | \PhpBoot\abort(new AnnotationSyntaxException( 42 | "The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()}::{$ann->parent->name} require 1 param, 0 given" 43 | )); 44 | } 45 | 46 | } 47 | } -------------------------------------------------------------------------------- /src/Entity/ArrayContainer.php: -------------------------------------------------------------------------------- 1 | container = $elementContainer; 34 | } 35 | 36 | /** 37 | * @param mixed $data 38 | * @param bool $validate 39 | * @return mixed 40 | */ 41 | public function make($data, $validate = true) 42 | { 43 | is_array($data) or \PhpBoot\abort(new \InvalidArgumentException('the first param is required to be array')); 44 | $res = []; 45 | foreach ($data as $k=>$v){ 46 | $res[$k] = $this->container->make($v, $validate); 47 | } 48 | return $res; 49 | } 50 | public function makeExample() 51 | { 52 | return [$this->container->makeExample()]; 53 | } 54 | /** 55 | * @return TypeContainerInterface 56 | */ 57 | public function getContainer() 58 | { 59 | return $this->container; 60 | } 61 | 62 | /** 63 | * @var TypeContainerInterface 64 | */ 65 | private $container; 66 | } -------------------------------------------------------------------------------- /src/Entity/ContainerFactory.php: -------------------------------------------------------------------------------- 1 | build($type); 20 | } 21 | }; 22 | if(TypeHint::isArray($type)){ 23 | $container = ArrayContainer::create($type, $getter); 24 | }else{ 25 | $container = $getter($type); 26 | } 27 | return $container; 28 | } 29 | } -------------------------------------------------------------------------------- /src/Entity/EntityContainerBuilder.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 43 | $this->diInvoker = $diInvoker; 44 | } 45 | /** 46 | * load from class with local cache 47 | * @param string $className 48 | * @return EntityContainer 49 | */ 50 | public function build($className) 51 | { 52 | return parent::build($className); 53 | } 54 | 55 | /** 56 | * @param $className 57 | * @return EntityContainer 58 | */ 59 | public function buildWithoutCache($className) 60 | { 61 | return parent::buildWithoutCache($className); 62 | } 63 | 64 | /** 65 | * @param string $className 66 | * @return EntityContainer 67 | */ 68 | protected function createContainer($className) 69 | { 70 | return $this->factory->make(EntityContainer::class, ['className'=>$className]); 71 | } 72 | 73 | protected function handleAnnotation($handlerName, $container, $ann) 74 | { 75 | $handler = $this->factory->make($handlerName); 76 | return $this->diInvoker->call($handler, [$container, $ann]); 77 | } 78 | 79 | 80 | /** 81 | * @var FactoryInterface 82 | */ 83 | protected $factory; 84 | /** 85 | * @var DIInvokerInterface 86 | */ 87 | protected $diInvoker; 88 | } -------------------------------------------------------------------------------- /src/Entity/MixedTypeContainer.php: -------------------------------------------------------------------------------- 1 | type = $type; 13 | !$type || TypeHint::isScalarType($type) or \PhpBoot\abort(new \InvalidArgumentException("$type is not scalar type")); 14 | } 15 | 16 | public function make($data, $validate = true){ 17 | return TypeCast::cast($data, $this->type, $validate); 18 | } 19 | 20 | public function makeExample() 21 | { 22 | return TypeCast::cast(null, $this->type, false); 23 | } 24 | 25 | /** 26 | * @return mixed 27 | */ 28 | public function getType() 29 | { 30 | return $this->type; 31 | } 32 | private $type; 33 | } -------------------------------------------------------------------------------- /src/Entity/TypeContainerInterface.php: -------------------------------------------------------------------------------- 1 | locked = true; 11 | return true; 12 | }else{ 13 | return false; 14 | } 15 | } 16 | 17 | public function unlock($key) 18 | { 19 | $this->locked or \PhpBoot\abort("unlock unlocked $key"); 20 | $res = apc_delete($key); 21 | $this->locked = false; 22 | return $res; 23 | } 24 | private $locked=false; 25 | } -------------------------------------------------------------------------------- /src/Lock/FileLock.php: -------------------------------------------------------------------------------- 1 | locked){ 12 | \PhpBoot\abort("relock $key"); 13 | } 14 | $path = sys_get_temp_dir().'/lock_252a8fdc9b944af99a9bc53d2aea08f1_'.$key; 15 | $this->file = @fopen($path, 'a'); 16 | if (!$this->file || !flock($this->file, LOCK_EX | LOCK_NB)) { 17 | if($this->file){ 18 | fclose($this->file); 19 | } 20 | return false; 21 | } else { 22 | $this->locked = true; 23 | } 24 | return true; 25 | } 26 | 27 | public function unlock($key) 28 | { 29 | $this->locked or \PhpBoot\abort("unlock unlocked $key"); 30 | flock($this->file, LOCK_UN); 31 | fclose($this->file); 32 | $this->file = null; 33 | $this->locked = false; 34 | return true; 35 | } 36 | private $locked = false; 37 | } -------------------------------------------------------------------------------- /src/Lock/LocalAutoLock.php: -------------------------------------------------------------------------------- 1 | lock($key, $seconds)){ //加锁失败 24 | if($error){ 25 | return $error(); 26 | } 27 | return; 28 | } 29 | } 30 | //嵌套加锁 31 | self::$currentLock[$key]++; 32 | }catch (\Exception $e){ 33 | if($error){ 34 | return $error(); 35 | } 36 | return; 37 | } 38 | $res = null; 39 | try{ 40 | $res = $success(); 41 | }catch (\Exception $e){ 42 | self::$currentLock[$key]--; 43 | if(self::$currentLock[$key] == 0){ 44 | try{ 45 | $lock->unlock($key); 46 | }catch (\Exception $e){ 47 | 48 | } 49 | } 50 | throw $e; 51 | } 52 | self::$currentLock[$key]--; 53 | if(self::$currentLock[$key] == 0){ 54 | try{ 55 | $lock->unlock($key); 56 | }catch (\Exception $e){ 57 | 58 | } 59 | } 60 | return $res; 61 | } 62 | 63 | private $cache=[]; 64 | static private $currentLock=[]; 65 | } -------------------------------------------------------------------------------- /src/Lock/LockInterface.php: -------------------------------------------------------------------------------- 1 | name = $name; 31 | $this->source = $source; 32 | $this->type = $type; 33 | $this->default = $default; 34 | $this->isOptional = $isOptional; 35 | $this->isPassedByReference = $isPassedByReference; 36 | $this->validation = $validation; 37 | $this->description = $description; 38 | $this->container = $container; 39 | } 40 | public $name; 41 | public $source; 42 | public $type; 43 | public $default; 44 | public $isOptional; 45 | public $isPassedByReference; 46 | public $validation; 47 | public $description; 48 | /** 49 | * @var TypeContainerInterface|null 50 | */ 51 | public $container; 52 | } -------------------------------------------------------------------------------- /src/Metas/PropertyMeta.php: -------------------------------------------------------------------------------- 1 | name = $name; 25 | $this->type = $type; 26 | $this->default = $default; 27 | $this->isOptional = $isOptional; 28 | $this->validation = $validation; 29 | $this->summary = $summary; 30 | $this->description = $description; 31 | $this->container = $container; 32 | } 33 | 34 | /** 35 | * @var TypeContainerInterface|null 36 | */ 37 | public $container; 38 | public $name; 39 | public $type; 40 | public $default; 41 | public $isOptional; 42 | /** 43 | * 如 44 | * "in:0,1,2" 45 | * [*.num, "in:0,1,2"] 46 | * 47 | * @var array|string 48 | */ 49 | public $validation; 50 | /** 51 | * @var string 52 | */ 53 | public $summary = ''; 54 | /** 55 | * @var string 56 | */ 57 | public $description=''; 58 | } -------------------------------------------------------------------------------- /src/Metas/ReturnMeta.php: -------------------------------------------------------------------------------- 1 | source = $source; 13 | $this->type = $type; 14 | $this->description = $description; 15 | $this->container = $container; 16 | } 17 | 18 | /** 19 | * @var string 20 | * 返回值来源,语法 http://jmespath.org/tutorial.html 21 | * 目前支持的返回值来源包括: return的返回值, &引用变量的输出, 常量 22 | * 分别用return 和params, `常量` 23 | */ 24 | public $source; 25 | 26 | /** 27 | * @var string 返回值类型 28 | */ 29 | public $type; 30 | 31 | /** 32 | * @var string 33 | */ 34 | public $description; 35 | 36 | /** 37 | * @var TypeContainerInterface|null 38 | */ 39 | public $container; 40 | } -------------------------------------------------------------------------------- /src/ORM/Annotations/PKAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | description, 2); 20 | $table = $params->getParam(0) or \PhpBoot\abort(new AnnotationSyntaxException("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()} require at least one param, 0 given")); 21 | 22 | $container->setPk($table); 23 | } 24 | } -------------------------------------------------------------------------------- /src/ORM/Annotations/TableAnnotationHandler.php: -------------------------------------------------------------------------------- 1 | description, 2); 19 | $table = $params->getParam(0) or \PhpBoot\abort(new AnnotationSyntaxException("The annotation \"@{$ann->name} {$ann->description}\" of {$container->getClassName()} require at least one param, 0 given")); 20 | 21 | $container->setTable($table); 22 | } 23 | } -------------------------------------------------------------------------------- /src/ORM/ModelContainer.php: -------------------------------------------------------------------------------- 1 | table = $table; 17 | } 18 | 19 | /** 20 | * @return string 21 | */ 22 | public function getTable() 23 | { 24 | return $this->table; 25 | } 26 | 27 | /** 28 | * @return string 29 | */ 30 | public function getPK() 31 | { 32 | return $this->pk; 33 | } 34 | 35 | /** 36 | * @param string $pk 37 | */ 38 | public function setPK($pk) 39 | { 40 | $this->pk = $pk; 41 | } 42 | 43 | /** 44 | * @var string 45 | */ 46 | private $table; 47 | 48 | /** 49 | * @var string 50 | */ 51 | private $pk = 'id'; 52 | } -------------------------------------------------------------------------------- /src/ORM/ModelContainerBuilder.php: -------------------------------------------------------------------------------- 1 | container = DIContainerBuilder::buildDevContainer(); 33 | parent::__construct($factory, $diInvoker, $cache, self::$DEFAULT_ANNOTATIONS); 34 | } 35 | /** 36 | * load from class with local cache 37 | * @param string $className 38 | * @return ModelContainer 39 | */ 40 | public function build($className) 41 | { 42 | return parent::build($className); 43 | } 44 | 45 | /** 46 | * @param $className 47 | * @return ModelContainer 48 | */ 49 | public function buildWithoutCache($className) 50 | { 51 | return parent::buildWithoutCache($className); 52 | } 53 | /** 54 | * @param string $className 55 | * @return ModelContainer 56 | */ 57 | protected function createContainer($className) 58 | { 59 | return $this->factory->make(ModelContainer::class, ['className'=>$className]); 60 | } 61 | } -------------------------------------------------------------------------------- /src/ORM/ModelWithObject.php: -------------------------------------------------------------------------------- 1 | db = $db; 21 | $builder = $db->getApp()->get(ModelContainerBuilder::class); 22 | $this->entity = $builder->build($entityName); 23 | $this->object = $entity; 24 | } 25 | /** 26 | * @return void 27 | */ 28 | public function create() 29 | { 30 | $data = []; 31 | foreach ($this->getColumns() as $column){ 32 | if(isset($this->object->$column)){ 33 | if(is_array($this->object->$column) || is_object($this->object->$column)){ 34 | $data[$column] = json_encode($this->object->$column); 35 | }else{ 36 | $data[$column] = $this->object->$column; 37 | } 38 | 39 | } 40 | } 41 | $id = $this->db->insertInto($this->entity->getTable()) 42 | ->values($data) 43 | ->exec()->lastInsertId(); 44 | $this->object->{$this->entity->getPK()} = $id; 45 | } 46 | 47 | /** 48 | * @param array $columns columns to update. if columns is empty array, update all of the columns 49 | * @return int rows updated 50 | */ 51 | public function update(array $columns=[]) 52 | { 53 | $data = []; 54 | $pk = $this->entity->getPK(); 55 | foreach ($this->getColumns() as $column){ 56 | if(count($columns) && !in_array($column, $columns)){ 57 | continue; 58 | } 59 | if($pk != $column && isset($this->object->$column)){ 60 | if(is_array($this->object->$column) || is_object($this->object->$column)){ 61 | $data[$column] = json_encode($this->object->$column); 62 | }else{ 63 | $data[$column] = $this->object->$column; 64 | } 65 | } 66 | } 67 | 68 | return $this->db->update($this->entity->getTable()) 69 | ->set($data) 70 | ->where("`{$pk}` = ?", $this->object->$pk) 71 | ->exec()->rows; 72 | } 73 | 74 | /** 75 | * @return int rows deleted 76 | */ 77 | public function delete() 78 | { 79 | $pk = $this->entity->getPK(); 80 | return $this->db->deleteFrom($this->entity->getTable()) 81 | ->where([$pk => $this->object->$pk]) 82 | ->exec()->rows; 83 | } 84 | 85 | /** 86 | * set entity table name 87 | * @param string $tableName 88 | * @return $this 89 | */ 90 | public function withTable($tableName) 91 | { 92 | $this->entity->setTable($tableName); 93 | return $this; 94 | } 95 | 96 | protected function getColumns() 97 | { 98 | $columns = []; 99 | foreach ($this->entity->getProperties() as $p){ 100 | $columns[] = $p->name; 101 | } 102 | return $columns; 103 | } 104 | 105 | /** 106 | * @var object 107 | */ 108 | protected $object; 109 | 110 | /** 111 | * @var ModelContainer 112 | */ 113 | protected $entity; 114 | /** 115 | * @var DB 116 | */ 117 | protected $db; 118 | } -------------------------------------------------------------------------------- /src/RPC/MultiRequest.php: -------------------------------------------------------------------------------- 1 | run(); 19 | self::$currentContext = $oriId; 20 | return $request->getResults(); 21 | } 22 | 23 | public static function isRunning(){ 24 | return !!self::$currentContext; 25 | } 26 | 27 | public static function wait($waitAble) 28 | { 29 | self::isRunning() or \PhpBoot\abort("can not call wait() out of MultiRequest::run"); 30 | $request = self::$contexts[self::$currentContext]; 31 | return $request->wait($waitAble); 32 | } 33 | 34 | /** 35 | * @var MultiRequestCore[] 36 | */ 37 | protected static $contexts; 38 | protected static $currentContext; 39 | 40 | 41 | } -------------------------------------------------------------------------------- /src/RPC/MultiRequestCore.php: -------------------------------------------------------------------------------- 1 | threadResults); 18 | $this->threadResults[] = [null,null]; 19 | $this->threads[] = function ()use($thread, $pos){ 20 | try{ 21 | $this->threadResults[$pos][0] = $thread(); 22 | }catch (\Exception $e){ 23 | $this->threadResults[$pos][1] = $e; 24 | } 25 | }; 26 | } 27 | $this->waitAll = $waitAll; 28 | } 29 | 30 | public function run() 31 | { 32 | while ($thread = array_pop($this->threads)){ 33 | $thread(); 34 | }; 35 | } 36 | 37 | public function wait($waitAble){ 38 | array_push($this->waits, $waitAble); 39 | $this->run(); 40 | 41 | if(count($this->waits)){ 42 | $waitAll = $this->waitAll; 43 | $this->waitResults = $waitAll($this->waits); 44 | $this->waits = []; 45 | } 46 | 47 | $res = array_pop($this->waitResults); 48 | if(isset($res[1])){ 49 | \PhpBoot\abort(new RpcException($res['reason'])); 50 | }else{ 51 | return $res[0]; 52 | } 53 | } 54 | 55 | /** 56 | * @return array 57 | */ 58 | public function getResults() 59 | { 60 | return $this->threadResults; 61 | } 62 | 63 | /** 64 | * @var callable[] 65 | */ 66 | protected $waits = []; 67 | 68 | /** 69 | * @var callable[] 70 | */ 71 | protected $threads = []; 72 | 73 | protected $threadResults = []; 74 | /** 75 | * @var callable 76 | */ 77 | protected $waitAll; 78 | 79 | /** 80 | * @var callable 81 | */ 82 | protected $waitResults = []; 83 | } -------------------------------------------------------------------------------- /src/RPC/MultiRpc.php: -------------------------------------------------------------------------------- 1 | wait() as $i){ 27 | if(isset($i['reason'])){ 28 | $res[] = [null, new RpcException($i['reason'])]; 29 | }else{ 30 | $res[] = [$i['value'], null]; 31 | } 32 | } 33 | return $res; 34 | }); 35 | } 36 | 37 | public static function isRunning(){ 38 | return MultiRequest::isRunning(); 39 | } 40 | 41 | public static function wait(Promise\Promise $waitAble) 42 | { 43 | return MultiRequest::wait($waitAble); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/Utils/ArrayHelper.php: -------------------------------------------------------------------------------- 1 | func = $func; 20 | $this->bind = array_slice($args,1); 21 | } 22 | 23 | /** 24 | * 25 | * 调用时,将bind参数加在方法的最前面 26 | * @return mixed 27 | */ 28 | public function __invoke(){ 29 | $args = func_get_args(); 30 | $params = $this->bind; 31 | foreach ($args as $arg){ 32 | array_push($params, $arg); 33 | } 34 | $res = call_user_func_array($this->func, $params); 35 | foreach ($this->next as $next){ 36 | call_user_func_array($next,$args); 37 | } 38 | return $res; 39 | } 40 | /** 41 | * 串行调用 42 | * @var callable[] 43 | */ 44 | public $next=array(); 45 | private $bind; 46 | private $func; 47 | } 48 | 49 | ?> -------------------------------------------------------------------------------- /src/Utils/TypeCast.php: -------------------------------------------------------------------------------- 1 | '', 22 | 'bool'=>false, 23 | 'int'=>0, 24 | 'float'=>0, 25 | ]; 26 | if(isset($map[$type])){ 27 | $val = $map[$type]; 28 | } 29 | } 30 | if(is_object($val)){ 31 | try{ 32 | $val = (string)$val; 33 | }catch (\Exception $e){ 34 | $className = get_class($val); 35 | \PhpBoot\abort(new \InvalidArgumentException("could not cast value from class $className to {$type}")); 36 | } 37 | 38 | } 39 | if(is_array($val)){ 40 | $type == 'array' || $type =='mixed' || !$type or \PhpBoot\abort(new \InvalidArgumentException("could not cast value from resource to {$type}")); 41 | } 42 | if(is_resource($val)) { 43 | \PhpBoot\abort(new \InvalidArgumentException("could not cast value from resource to {$type}")); 44 | } 45 | if(!$validate){ 46 | settype($val, $type) or \PhpBoot\abort(new \InvalidArgumentException("cast value($val) to {$type} failed")); 47 | }else{ 48 | $ori = $val; 49 | $oriType = gettype($val); 50 | settype($val, $type) or \PhpBoot\abort(new \InvalidArgumentException("cast value($ori) to type {$type} failed")); 51 | $newData = $val; 52 | if(is_bool($newData)){ 53 | $newData = intval($newData); 54 | } 55 | settype($newData, $oriType) or \PhpBoot\abort(new \InvalidArgumentException("cast value($ori) to type {$type} failed")); 56 | if($ori != $newData){ 57 | \PhpBoot\abort(new \InvalidArgumentException("could not cast value($ori) to type {$type}")); 58 | } 59 | } 60 | return $val; 61 | } 62 | } -------------------------------------------------------------------------------- /src/Utils/TypeHint.php: -------------------------------------------------------------------------------- 1 | float, integer -> int 13 | * 2. 对象类型, 补全namespace 14 | * @param string $type 需要标准化的字符串 15 | * @param string $contextClass 当前上下文所在的类,一般传__CLASS__, 用于扫描当前文件的use信息, 以便拼上namespace 16 | */ 17 | static function normalize($type, $contextClass=null){ 18 | $resolver = new TypeResolver(); 19 | $context = null; 20 | if($contextClass){ 21 | //TODO 优化性能 22 | $contextFactory = new ContextFactory(); 23 | $context = $contextFactory->createFromReflector(new \ReflectionClass($contextClass)); 24 | } 25 | $type = $resolver->resolve($type, $context); 26 | $type = ltrim($type, '\\'); 27 | return (string)$type; 28 | } 29 | /** 30 | * 是否是基本类型 31 | * @param string $type 32 | */ 33 | static function isScalarType($type){ 34 | return in_array($type, [ 35 | 'bool', 36 | 'int', 37 | 'float', 38 | 'string' 39 | ]); 40 | } 41 | 42 | /** 43 | * @param $type 44 | * @return bool 45 | */ 46 | static function isArray($type){ 47 | return ($type == 'array' || substr($type, -2) == '[]'); 48 | } 49 | 50 | /** 51 | * 获取数组的类型 52 | * @param $type 53 | * @return string|null 54 | */ 55 | static function getArrayType($type){ 56 | self::isArray($type) or \PhpBoot\abort(new \InvalidArgumentException("$type is not array")); 57 | if($type == 'array') { 58 | return 'mixed'; 59 | }else{ 60 | return substr($type,0,-2); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | getMessage()}"; 24 | }else{ 25 | $e = new \RuntimeException($error); 26 | $message = $error; 27 | } 28 | $trace = $e->getTrace(); 29 | 30 | if($e->getFile() == __FILE__){ 31 | $file = $trace[0]['file']; 32 | $line = $trace[0]['line']; 33 | }else{ 34 | $file = $e->getFile(); 35 | $line = $e->getLine(); 36 | } 37 | if($level){ 38 | Logger::$level($message, $context +['@file'=>$file, '@line'=>$line]); 39 | } 40 | throw $e; 41 | } 42 | 43 | } 44 | 45 | if (!function_exists('PhpBoot\model')) { 46 | 47 | /** 48 | * @param DB $db 49 | * @param @param object 50 | * @return ModelWithObject 51 | */ 52 | function model(DB $db, $entity) 53 | { 54 | return $db->getApp()->make(ModelWithObject::class, ['db'=>$db, 'entity'=>$entity]); 55 | } 56 | 57 | /** 58 | * @param DB $db 59 | * @param @param string $entity 60 | * @return ModelWithClass 61 | */ 62 | function models(DB $db, $entity) 63 | { 64 | if(is_object($entity)){ 65 | return $db->getApp()->make(ModelWithObject::class, ['db'=>$db, 'entity'=>$entity]); 66 | }else{ 67 | return $db->getApp()->make(ModelWithClass::class, ['db'=>$db, 'entityName'=>$entity]); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /tests/AnnotationParamsTest.php: -------------------------------------------------------------------------------- 1 | count(), 0); 14 | 15 | $params = new AnnotationParams($testStr, 1); 16 | self::assertEquals($params->count(), 1); 17 | self::assertEquals($params->getRawParam(0), $testStr); 18 | self::assertEquals($params->getRawParam(0), $testStr); 19 | self::assertNull($params->getRawParam(1)); 20 | 21 | $params = new AnnotationParams($testStr, 2); 22 | self::assertEquals($params->count(), 2); 23 | self::assertEquals($params->getRawParam(0), 'a'); 24 | self::assertEquals($params->getRawParam(1), 'b"bb ccc \" c"c ddd "e e '); 25 | self::assertNull($params->getRawParam(2)); 26 | 27 | $params = new AnnotationParams($testStr, 3); 28 | self::assertEquals($params->count(), 3); 29 | self::assertEquals($params->getRawParam(0), 'a'); 30 | self::assertEquals($params->getRawParam(1), 'b"bb ccc \" c"c'); 31 | self::assertEquals($params->getRawParam(2), 'ddd "e e '); 32 | self::assertNull($params->getRawParam(3)); 33 | 34 | $params = new AnnotationParams($testStr, 4); 35 | self::assertEquals($params->count(), 4); 36 | self::assertEquals($params->getRawParam(0), 'a'); 37 | self::assertEquals($params->getRawParam(1), 'b"bb ccc \" c"c'); 38 | self::assertEquals($params->getRawParam(2), 'ddd'); 39 | self::assertEquals($params->getRawParam(3), '"e e '); 40 | self::assertNull($params->getRawParam(4)); 41 | 42 | $params = new AnnotationParams($testStr, 5); 43 | self::assertEquals($params->count(), 4); 44 | self::assertEquals($params->getRawParam(0), 'a'); 45 | self::assertEquals($params->getRawParam(1), 'b"bb ccc \" c"c'); 46 | self::assertEquals($params->getRawParam(2), 'ddd'); 47 | self::assertEquals($params->getRawParam(3), '"e e '); 48 | self::assertNull($params->getRawParam(4)); 49 | } 50 | 51 | public function testStripSlashes() 52 | { 53 | $testStr = 'abc'; 54 | $params = new AnnotationParams($testStr, 1); 55 | self::assertEquals($params->getParam(0), 'abc'); 56 | 57 | $testStr = '"abc\""'; 58 | $params = new AnnotationParams($testStr, 1); 59 | self::assertEquals($params->getParam(0), 'abc"'); 60 | 61 | $testStr = '"abc\"'; 62 | $params = new AnnotationParams($testStr, 1); 63 | self::assertEquals($params->getParam(0, null, true), '"abc\"'); 64 | 65 | $testStr = '"abc\"'; 66 | $params = new AnnotationParams($testStr, 1); 67 | self::assertException(function()use($params){ 68 | $params->getParam(0, null, false); 69 | }); 70 | 71 | 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /tests/AnnotationReaderTest.php: -------------------------------------------------------------------------------- 1 | app->getCache()); 44 | 45 | $expected = new AnnotationReader(); 46 | $expected->class = new AnnotationBlock( 47 | TestClass::class, 48 | 'class summary', 49 | "class description line1", 50 | [ 51 | $tagP1 = new AnnotationTag( 52 | 'classAnn1',"class Ann 1 {@childAnn1 child Ann 1} {@childAnn2 child Ann 2}", [ 53 | $tagC1=new AnnotationTag('childAnn1', 'child Ann 1'), 54 | $tagC2=new AnnotationTag('childAnn2', "child Ann 2") 55 | ]), 56 | $tagP2 = new AnnotationTag('classAnn2',"class Ann 2"), 57 | ] 58 | ); 59 | $tagP1->parent = $tagP2->parent = $expected->class; 60 | $tagC2->parent = $tagC1->parent = $tagP1; 61 | 62 | $expected->methods = [ 63 | 'method1'=>new AnnotationBlock('method1','method summary', 'method description', [ 64 | new AnnotationTag('methodAnn1','method Ann 1') 65 | ]) 66 | ]; 67 | $expected->methods['method1']->children[0]->parent = $expected->methods['method1']; 68 | 69 | $expected->properties = [ 70 | 'property1'=>new AnnotationBlock('property1', 'property1 summary', '', [ 71 | new AnnotationTag('propertyAnn1') 72 | ]), 73 | 'property2'=>new AnnotationBlock('property2'), 74 | ]; 75 | 76 | $expected->properties['property1']->children[0]->parent = $expected->properties['property1']; 77 | 78 | self::assertEquals($expected, $actual); 79 | } 80 | } -------------------------------------------------------------------------------- /tests/ApplicationTest.php: -------------------------------------------------------------------------------- 1 | setContent($res->getContent()."TestHook1"); 19 | return $res; 20 | } 21 | } 22 | 23 | class TestHook2 implements HookInterface 24 | { 25 | 26 | public function handle(Request $request, callable $next) 27 | { 28 | $res = $next($request); 29 | /**@var Response $res*/ 30 | $res->setContent($res->getContent()."TestHook2"); 31 | return $res; 32 | } 33 | } 34 | 35 | /** 36 | * Class HookControllerTest 37 | * @path / 38 | */ 39 | class HookControllerTest 40 | { 41 | /** 42 | * @route GET /testAnnotationHooks 43 | * @hook TestHook1 44 | * @hook TestHook2 45 | * @return string 46 | */ 47 | public function test() 48 | { 49 | return "route"; 50 | } 51 | } 52 | 53 | class ApplicationTest extends TestCase 54 | { 55 | public function testAddHooks() 56 | { 57 | $this->app->addRoute('GET', '/testHooks', 58 | function(Application $app, Request $req){ 59 | $res = new Response(); 60 | $res->setContent("route"); 61 | return $res; 62 | }, 63 | [TestHook1::class, TestHook2::class] 64 | ); 65 | $req = new Request([], [], [], [], [], ['REQUEST_METHOD'=>'GET', 'REQUEST_URI'=>'/testHooks'], []); 66 | $res = $this->app->dispatch($req, false); 67 | self::assertEquals($res->getContent(), "routeTestHook2TestHook1"); 68 | } 69 | 70 | public function testAnnotationHooks() 71 | { 72 | $this->app->loadRoutesFromClass(HookControllerTest::class); 73 | $req = new Request([], [], [], [], [], ['REQUEST_METHOD'=>'GET', 'REQUEST_URI'=>'/testAnnotationHooks'], []); 74 | $res = $this->app->dispatch($req, false); 75 | self::assertEquals($res->getContent(), "\"route\"TestHook2TestHook1"); 76 | } 77 | 78 | public function testWithBadRequest() 79 | { 80 | 81 | } 82 | 83 | public function testWithValidation() 84 | { 85 | 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /tests/ArrayAdaptorTest.php: -------------------------------------------------------------------------------- 1 | $name = $val; 15 | } 16 | public function get($name){ 17 | return $this->$name; 18 | } 19 | public function has($name){ 20 | return isset($this->$name); 21 | } 22 | public function remove($name){ 23 | unset($this->$name); 24 | } 25 | } 26 | 27 | class AccessByMethod2{ 28 | public function setKey($val) 29 | { 30 | $this->key = $val; 31 | } 32 | public function getKey(){ 33 | return $this->key; 34 | } 35 | public function hasKey(){ 36 | return isset($this->key); 37 | } 38 | public function removeKey() 39 | { 40 | unset($this->key); 41 | } 42 | private $key; 43 | } 44 | 45 | 46 | 47 | class ArrayAdaptorTest extends TestCase 48 | { 49 | public function testArray() 50 | { 51 | $src = []; 52 | $adt = new ArrayAdaptor($src); 53 | self::assertFalse(isset($adt[0])); 54 | 55 | $adt[0]=1; 56 | self::assertTrue(isset($adt[0])); 57 | self::assertEquals($adt[0], $src[0]); 58 | self::assertEquals($adt[0], 1); 59 | 60 | unset($adt[0]); 61 | self::assertFalse(isset($adt[0])); 62 | self::assertFalse(isset($arr[0])); 63 | } 64 | 65 | public function testAccessByProperty() 66 | { 67 | $src = new AccessByProperty(); 68 | $adt = new ArrayAdaptor($src); 69 | 70 | $adt['key']=1; 71 | self::assertEquals($adt['key'], $src->key); 72 | self::assertEquals($adt['key'], 1); 73 | } 74 | 75 | public function testAccessByMethod1() 76 | { 77 | $src = new AccessByMethod1(); 78 | $adt = new ArrayAdaptor($src); 79 | self::assertFalse(isset($adt['key'])); 80 | 81 | $adt['key']=1; 82 | self::assertTrue(isset($adt['key'])); 83 | self::assertEquals($adt['key'], $src->get('key')); 84 | self::assertEquals($adt['key'], 1); 85 | 86 | unset($adt['key']); 87 | self::assertFalse(isset($adt['key'])); 88 | self::assertFalse($src->has('key')); 89 | } 90 | 91 | public function testAccessByMethod2() 92 | { 93 | $src = new AccessByMethod2(); 94 | $adt = new ArrayAdaptor($src); 95 | self::assertFalse(isset($adt['key'])); 96 | 97 | $adt['key']=1; 98 | self::assertTrue(isset($adt['key'])); 99 | self::assertEquals($adt['key'], $src->getKey()); 100 | self::assertEquals($adt['key'], 1); 101 | 102 | unset($adt['key']); 103 | self::assertFalse(isset($adt['key'])); 104 | self::assertFalse($src->hasKey()); 105 | } 106 | } -------------------------------------------------------------------------------- /tests/ArrayHelperTest.php: -------------------------------------------------------------------------------- 1 | ['b'=>['c'=>1]]], $test); 15 | 16 | ArrayHelper::set($test, 'a.b.c', 2); 17 | self::assertEquals(['a'=>['b'=>['c'=>2]]], $test); 18 | 19 | self::assertException(function()use($test){ 20 | ArrayHelper::set($test, 'a.b.c.d', 1); 21 | }); 22 | 23 | ArrayHelper::set($test, 'a.b.d', 3); 24 | self::assertEquals(['a'=>['b'=>['c'=>2, 'd'=>3]]], $test); 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /tests/ConsoleTest.php: -------------------------------------------------------------------------------- 1 | app->make(Console::class); 45 | /**@var Console $console*/ 46 | $console->loadCommandsFromClass(TestCommand::class); 47 | $console->setAutoExit(false); 48 | ob_start(); 49 | $console->run(new StringInput("test.run 11 22 33")); 50 | $output = ob_get_contents(); 51 | ob_end_clean(); 52 | self::assertEquals($output, print_r(["11", "22", ["33"]], true)); 53 | } 54 | } -------------------------------------------------------------------------------- /tests/ControllerMetaLoaderTest.php: -------------------------------------------------------------------------------- 1 | app->make(ControllerContainerBuilder::class); 67 | $actual = $builder->build(ControllerTest::class); 68 | $expected = new ControllerContainer(ControllerTest::class); 69 | //TODO $this->assertEquals($expected, $actual); 70 | } 71 | } -------------------------------------------------------------------------------- /tests/Mocks/DBMock.php: -------------------------------------------------------------------------------- 1 | test = $test; 7 | } 8 | public function __destruct() 9 | { 10 | $this->test->assertTrue($this->prepared); 11 | $this->test->assertTrue($this->executed); 12 | } 13 | 14 | public function setAttribute($key, $value){ 15 | 16 | } 17 | public function prepare($sql){ 18 | $this->prepared = true; 19 | print ".............\n"; 20 | print $this->expectedSql." , "; 21 | print_r($this->expectedParams); 22 | print $sql."\n"; 23 | 24 | $this->test->assertEquals($this->expectedSql, $sql); 25 | return $this; 26 | } 27 | public function rowCount(){ 28 | 29 | } 30 | public function execute($params){ 31 | $this->executed = true; 32 | print_r($params); 33 | $this->test->assertEquals($this->expectedParams, $params); 34 | } 35 | public function fetchAll($arg){ 36 | 37 | } 38 | public function lastInsertId() 39 | { 40 | 41 | } 42 | public function setExpected($sql, $_=null){ 43 | $this->expectedSql = $sql; 44 | $this->expectedParams = array_slice(func_get_args(), 1); 45 | } 46 | private $expectedSql; 47 | private $expectedParams; 48 | /** 49 | * 50 | * @var \PHPUnit_Framework_TestCase 51 | */ 52 | private $test; 53 | 54 | private $executed = false; 55 | private $prepared = false; 56 | } -------------------------------------------------------------------------------- /tests/ModelWithClassTest.php: -------------------------------------------------------------------------------- 1 | setExpected('UPDATE `test_table` SET `name`=? WHERE (`id` = ?)', 'abc', 1); 24 | $db = new DB($this->app, $mock); 25 | \PhpBoot\models($db, ModelWithObjectForTest::class)->update(1, ['name'=>'abc']); 26 | } 27 | 28 | public function testUpdateWhere() 29 | { 30 | $mock = new DBMock($this); 31 | $mock->setExpected('UPDATE `test_table` SET `name`=? WHERE (`id` = ?)', 'abc', 1); 32 | $db = new DB($this->app, $mock); 33 | \PhpBoot\models($db, ModelWithObjectForTest::class)->updateWhere( 34 | ['name'=>'abc'], 35 | ['id'=>1] 36 | )->exec(); 37 | } 38 | 39 | public function testFind() 40 | { 41 | $mock = new DBMock($this); 42 | $mock->setExpected('SELECT `id`,`name`,`type` FROM `test_table` WHERE (`id` = ?)', 1); 43 | $db = new DB($this->app, $mock); 44 | \PhpBoot\models($db, ModelWithObjectForTest::class)->find(1); 45 | } 46 | 47 | public function testFindWhere() 48 | { 49 | $mock = new DBMock($this); 50 | $mock->setExpected('SELECT `id`,`name`,`type` FROM `test_table` WHERE (`name` = ?)', 'abc'); 51 | $db = new DB($this->app, $mock); 52 | \PhpBoot\models($db, ModelWithObjectForTest::class)->findWhere(['name'=>'abc'])->get(); 53 | } 54 | 55 | public function testFindEmptyWhere() 56 | { 57 | $mock = new DBMock($this); 58 | $mock->setExpected('SELECT `id`,`name`,`type` FROM `test_table`'); 59 | $db = new DB($this->app, $mock); 60 | \PhpBoot\models($db, ModelWithObjectForTest::class)->findWhere()->get(); 61 | } 62 | 63 | public function testDelete() 64 | { 65 | $mock = new DBMock($this); 66 | $mock->setExpected('DELETE FROM `test_table` WHERE (`id` = ?) LIMIT 1', 1); 67 | $db = new DB($this->app, $mock); 68 | \PhpBoot\models($db, ModelWithObjectForTest::class)->delete(1); 69 | } 70 | 71 | public function testDeleteWhere() 72 | { 73 | $mock = new DBMock($this); 74 | $mock->setExpected('DELETE FROM `test_table` WHERE (`name` = ?)', 'abc'); 75 | $db = new DB($this->app, $mock); 76 | \PhpBoot\models($db, ModelWithObjectForTest::class)->deleteWhere(['name'=>'abc'])->exec(); 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /tests/ModelWithObjectTest.php: -------------------------------------------------------------------------------- 1 | id = 1; 24 | $obj->name = 'abc'; 25 | $obj->type = '123'; 26 | 27 | $mock = new DBMock($this); 28 | $mock->setExpected('UPDATE `test_table` SET `name`=?,`type`=? WHERE (`id` = ?)', 'abc', '123', 1); 29 | $db = new DB($this->app, $mock); 30 | \PhpBoot\model($db, $obj)->update(); 31 | } 32 | 33 | public function testUpdateWithColumn() 34 | { 35 | $obj = new ModelWithObjectForTest(); 36 | $obj->id = 1; 37 | $obj->name = 'abc'; 38 | $obj->type = '123'; 39 | 40 | $mock = new DBMock($this); 41 | $mock->setExpected('UPDATE `test_table` SET `name`=? WHERE (`id` = ?)', 'abc', 1); 42 | $db = new DB($this->app, $mock); 43 | \PhpBoot\model($db, $obj)->update(['name']); 44 | } 45 | 46 | public function testCreate() 47 | { 48 | $obj = new ModelWithObjectForTest(); 49 | $obj->id = 1; 50 | $obj->name = 'abc'; 51 | $obj->type = '123'; 52 | 53 | $mock = new DBMock($this); 54 | $mock->setExpected('INSERT INTO `test_table`(`id`,`name`,`type`) VALUES(?,?,?)',1, 'abc', '123'); 55 | $db = new DB($this->app, $mock); 56 | \PhpBoot\model($db, $obj)->create(); 57 | } 58 | 59 | public function testDelete() 60 | { 61 | $obj = new ModelWithObjectForTest(); 62 | $obj->id = 1; 63 | $obj->name = 'abc'; 64 | $obj->type = '123'; 65 | 66 | $mock = new DBMock($this); 67 | $mock->setExpected('DELETE FROM `test_table` WHERE (`id` = ?)',1); 68 | $db = new DB($this->app, $mock); 69 | \PhpBoot\model($db, $obj)->delete(); 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /tests/MultiRequestTest.php: -------------------------------------------------------------------------------- 1 | getMessage()); 91 | self::assertEquals([3, null],$res[2]); 92 | self::assertEquals('e4', $res[3][1]->getMessage()); 93 | self::assertEquals([5, null], $res[4]); 94 | } 95 | } -------------------------------------------------------------------------------- /tests/ScalarTypeContainerTest.php: -------------------------------------------------------------------------------- 1 | make('not int', true); 15 | }, \InvalidArgumentException::class); 16 | 17 | self::assertEquals($container->make('not int', false), 0); 18 | 19 | self::assertEquals($container->make('123', true), 123); 20 | 21 | self::assertEquals($container->make('000', true), 0); 22 | 23 | self::assertEquals($container->make(null, true), 0); 24 | 25 | self::assertEquals($container->make(true, true), 1); 26 | 27 | self::assertEquals($container->make(false, true), 0); 28 | } 29 | 30 | public function testCastToBool() 31 | { 32 | $container = new ScalarTypeContainer('bool'); 33 | self::assertException(function ()use($container){ 34 | $container->make('not bool', true); 35 | }, \InvalidArgumentException::class); 36 | 37 | self::assertException(function ()use($container){ 38 | $container->make('true', true); 39 | }, \InvalidArgumentException::class); 40 | 41 | self::assertException(function ()use($container){ 42 | $container->make('false', true); 43 | }, \InvalidArgumentException::class); 44 | 45 | self::assertEquals($container->make(1, true), true); 46 | self::assertEquals($container->make('1', true), true); 47 | self::assertEquals($container->make('0', true), false); 48 | } 49 | 50 | public function testCastToString() 51 | { 52 | $container = new ScalarTypeContainer('string'); 53 | self::assertException(function ()use($container){ 54 | $container->make([1], true); 55 | }, \InvalidArgumentException::class); 56 | 57 | $container = new ScalarTypeContainer('string'); 58 | self::assertException(function ()use($container){ 59 | $container->make(new \stdClass(), true); 60 | }, \InvalidArgumentException::class); 61 | 62 | self::assertEquals($container->make(1, true), '1'); 63 | self::assertEquals($container->make(1.9, true), '1.9'); 64 | self::assertEquals($container->make(0, true), '0'); 65 | self::assertEquals($container->make(true, true), '1'); 66 | self::assertEquals($container->make(false, true), '0'); 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | app = Application::createByDefault([ 19 | Cache::class => $cache 20 | ]); 21 | } 22 | protected static function assertException(callable $fun, $expectedClass = null, $expectedMessage = null){ 23 | $throw = false; 24 | try{ 25 | $fun(); 26 | }catch (\Exception $e){ 27 | $throw = true; 28 | if($expectedClass){ 29 | self::assertInstanceOf($expectedClass, $e); 30 | } 31 | if($expectedMessage !== null){ 32 | self::assertEquals($expectedMessage, $e->getMessage()); 33 | } 34 | } 35 | self::assertTrue($throw); 36 | } 37 | /** 38 | * @var Application 39 | */ 40 | protected $app; 41 | } -------------------------------------------------------------------------------- /tests/Utils/RpcTestController.php: -------------------------------------------------------------------------------- 1 | objArg = new RpcTestEntity1(); 9 | } 10 | /** 11 | * @var int 12 | */ 13 | public $intArg; 14 | /** 15 | * @var bool 16 | */ 17 | public $boolArg; 18 | /** 19 | * @var float 20 | */ 21 | public $floatArg; 22 | /** 23 | * @var string 24 | */ 25 | public $strArg; 26 | /** 27 | * @var RpcTestEntity1 28 | */ 29 | public $objArg; 30 | 31 | /** 32 | * @var RpcTestEntity1[] 33 | */ 34 | public $arrArg; 35 | 36 | /** 37 | * @var string 38 | */ 39 | public $defaultArg = 'default'; 40 | } -------------------------------------------------------------------------------- /tests/ValidateTest.php: -------------------------------------------------------------------------------- 1 | rule('in:1,2,3', 'a'); 20 | $res = $v->withData(['a'=>'0'])->validate(); 21 | self::assertFalse($res); 22 | 23 | $res = $v->withData(['a'=>1])->validate(); 24 | self::assertTrue($res); 25 | 26 | } 27 | 28 | public function testRuleMinMax() 29 | { 30 | $v = new Validator(); 31 | $v->rule('min:1|max:3', 'a'); 32 | $res = $v->withData(['a'=>'1'])->validate(); 33 | self::assertTrue($res); 34 | 35 | $res = $v->withData(['a'=>1])->validate(); 36 | self::assertTrue($res); 37 | $res = $v->withData(['a'=>3])->validate(); 38 | self::assertTrue($res); 39 | $res = $v->withData(['a'=>'3'])->validate(); 40 | self::assertTrue($res); 41 | 42 | $res = $v->withData(['a'=>'0'])->validate(); 43 | self::assertFalse($res); 44 | 45 | $res = $v->withData(['a'=>'4'])->validate(); 46 | self::assertFalse($res); 47 | 48 | 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /tests/travis/php.ini: -------------------------------------------------------------------------------- 1 | extension="apcu.so" 2 | apc.enabled=1 3 | apc.enable_cli=1 4 | --------------------------------------------------------------------------------