├── .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 | 
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 | ```@
49 | * An offset to check for.
50 | *
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 $offset80 | * The offset to assign the value to. 81 | *
82 | * @param mixed $value83 | * 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 $offset97 | * 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 | --------------------------------------------------------------------------------