├── docs
├── advanced
│ ├── custom-annotation.md
│ ├── workflow.md
│ ├── docgen.md
│ ├── hook.md
│ ├── cli.md
│ └── orm.md
├── requirements.txt
├── _static
│ ├── db.gif
│ └── WX20170809-184015.png
├── quick-start
│ ├── requirements.md
│ ├── install.md
│ └── webserver-config.md
├── faq.md
├── Makefile
├── basic
│ ├── di.md
│ ├── cache.md
│ ├── annotation.md
│ ├── route.md
│ └── validation.md
└── make.bat
├── tests
├── travis
│ └── php.ini
├── Utils
│ ├── RpcTestEntity1.php
│ ├── RpcTestEntity2.php
│ └── RpcTestController.php
├── ArrayHelperTest.php
├── TestCase.php
├── ValidateTest.php
├── ConsoleTest.php
├── Mocks
│ └── DBMock.php
├── ControllerMetaLoaderTest.php
├── ModelWithObjectTest.php
├── ApplicationTest.php
├── AnnotationReaderTest.php
├── ScalarTypeContainerTest.php
├── ModelWithClassTest.php
├── MultiRequestTest.php
├── ArrayAdaptorTest.php
└── AnnotationParamsTest.php
├── .coveralls.yml
├── src
├── Docgen
│ └── Swagger
│ │ ├── Schemas
│ │ ├── PathObject.php
│ │ ├── SecuritySchemeObject.php
│ │ ├── SecurityRequirementObject.php
│ │ ├── SchemaObject.php
│ │ ├── ArraySchemaObject.php
│ │ ├── BodyParameterObject.php
│ │ ├── LicenseObject.php
│ │ ├── SimpleModelSchemaObject.php
│ │ ├── ExternalDocumentationObject.php
│ │ ├── RefSchemaObject.php
│ │ ├── ContactObject.php
│ │ ├── TagObject.php
│ │ ├── InfoObject.php
│ │ ├── ParameterObject.php
│ │ ├── PathItemObject.php
│ │ ├── ResponseObject.php
│ │ ├── PrimitiveSchemaObject.php
│ │ ├── HeaderObject.php
│ │ ├── OtherParameterObject.php
│ │ └── OperationObject.php
│ │ └── SwaggerProvider.php
├── Exceptions
│ ├── RpcException.php
│ └── AnnotationSyntaxException.php
├── DI
│ ├── Traits
│ │ └── EnableDIAnnotations.php
│ ├── ObjectDefinitionContext.php
│ ├── DIContainerBuilder.php
│ ├── DIMetaLoader.php
│ ├── Annotations
│ │ ├── VarAnnotationHandler.php
│ │ └── InjectAnnotationHandler.php
│ └── AnnotationReader.php
├── Lock
│ ├── LockInterface.php
│ ├── ApcLock.php
│ ├── FileLock.php
│ └── LocalAutoLock.php
├── Entity
│ ├── TypeContainerInterface.php
│ ├── MixedTypeContainer.php
│ ├── Annotations
│ │ ├── PropertyAnnotationHandler.php
│ │ ├── ClassAnnotationHandler.php
│ │ ├── ValidateAnnotationHandler.php
│ │ └── VarAnnotationHandler.php
│ ├── ScalarTypeContainer.php
│ ├── ContainerFactory.php
│ ├── ArrayContainer.php
│ └── EntityContainerBuilder.php
├── Controller
│ ├── HookInterface.php
│ ├── Annotations
│ │ ├── PathAnnotationHandler.php
│ │ ├── ClassAnnotationHandler.php
│ │ ├── ThrowsAnnotationHandler.php
│ │ ├── HookAnnotationHandler.php
│ │ ├── ReturnAnnotationHandler.php
│ │ ├── ValidateAnnotationHandler.php
│ │ ├── BindAnnotationHandler.php
│ │ └── ParamAnnotationHandler.php
│ ├── ExceptionRenderer.php
│ ├── ResponseRenderer.php
│ ├── ExceptionHandler.php
│ ├── ResponseHandler.php
│ ├── ControllerContainerBuilder.php
│ └── RequestHandler.php
├── DB
│ ├── Raw.php
│ ├── rules
│ │ ├── delete.php
│ │ ├── replace.php
│ │ ├── update.php
│ │ └── insert.php
│ ├── Rows.php
│ ├── Exceptions
│ │ └── DBException.php
│ ├── README.md
│ ├── Context.php
│ └── NestedStringCut.php
├── Console
│ ├── Annotations
│ │ ├── CommandNameAnnotationHandler.php
│ │ ├── ClassAnnotationHandler.php
│ │ ├── CommandAnnotationHandler.php
│ │ ├── ValidateAnnotationHandler.php
│ │ └── ParamAnnotationHandler.php
│ ├── ConsoleContainerBuilder.php
│ └── ConsoleContainer.php
├── ORM
│ ├── Annotations
│ │ ├── PKAnnotationHandler.php
│ │ └── TableAnnotationHandler.php
│ ├── ModelContainer.php
│ ├── ModelContainerBuilder.php
│ └── ModelWithObject.php
├── Metas
│ ├── ReturnMeta.php
│ ├── PropertyMeta.php
│ └── ParamMeta.php
├── Utils
│ ├── SafeFileWriter.php
│ ├── SerializableFunc.php
│ ├── ArrayHelper.php
│ ├── TypeHint.php
│ └── TypeCast.php
├── RPC
│ ├── MultiRequest.php
│ ├── MultiRpc.php
│ └── MultiRequestCore.php
├── Cache
│ ├── ClassModifiedChecker.php
│ ├── FileModifiedChecker.php
│ └── CheckableCache.php
├── Annotation
│ ├── AnnotationBlock.php
│ ├── AnnotationTag.php
│ └── ContainerBuilder.php
├── functions.php
└── Console.php
├── .gitignore
├── phpunit.xml
├── LICENSE
├── .travis.yml
└── composer.json
/docs/advanced/custom-annotation.md:
--------------------------------------------------------------------------------
1 | # 自定义 Annotation
2 | 待完善...
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | sphinx-rtd-theme
2 | sphinx
3 | recommonmark
--------------------------------------------------------------------------------
/tests/travis/php.ini:
--------------------------------------------------------------------------------
1 | extension="apcu.so"
2 | apc.enabled=1
3 | apc.enable_cli=1
4 |
--------------------------------------------------------------------------------
/docs/_static/db.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/caoym/phpboot/HEAD/docs/_static/db.gif
--------------------------------------------------------------------------------
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | coverage_clover: build/logs/clover.xml
2 | json_path: build/logs/coveralls-upload.json
--------------------------------------------------------------------------------
/docs/_static/WX20170809-184015.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/caoym/phpboot/HEAD/docs/_static/WX20170809-184015.png
--------------------------------------------------------------------------------
/src/Docgen/Swagger/Schemas/PathObject.php:
--------------------------------------------------------------------------------
1 | = 5.5.9
6 | * APC 扩展启用
7 |
8 | ```
9 | apc.enable=1
10 | ```
11 |
12 | * 如果启用了OPcache,应同时配置以下选项:
13 |
14 | ```
15 | opcache.save_comments=1
16 | opcache.load_comments=1
17 | ```
18 |
19 |
--------------------------------------------------------------------------------
/src/Entity/TypeContainerInterface.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/Docgen/Swagger/Schemas/SimpleModelSchemaObject.php:
--------------------------------------------------------------------------------
1 | \DI\object(FilesystemCache::class)
9 | ->constructorParameter('directory', sys_get_temp_dir())
10 | ```
11 |
12 | ## composer 更新失败怎么办
13 |
14 | packagist.org 国内访问不稳定,可以翻墙试试,或者用国内的镜像[phpcomposer](phpcomposer.com), 执行下面命令
15 |
16 | ```
17 | composer config repo.packagist composer https://packagist.phpcomposer.com
18 | ```
19 |
20 |
--------------------------------------------------------------------------------
/src/Docgen/Swagger/Schemas/ExternalDocumentationObject.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/Lock/ApcLock.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/Docgen/Swagger/Schemas/ContactObject.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/Console/Annotations/CommandNameAnnotationHandler.php:
--------------------------------------------------------------------------------
1 | description, 2);
19 | $container->setModuleName($params->getParam(0, ''));
20 | }
21 | }
--------------------------------------------------------------------------------
/src/Controller/Annotations/PathAnnotationHandler.php:
--------------------------------------------------------------------------------
1 | description, 2);
20 | $container->setUriPrefix($params->getParam(0, ''));
21 | }
22 | }
--------------------------------------------------------------------------------
/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)
--------------------------------------------------------------------------------
/src/Docgen/Swagger/Schemas/TagObject.php:
--------------------------------------------------------------------------------
1 | getClassName());
20 | $container->setDescription($ann->description);
21 | $container->setSummary($ann->summary);
22 |
23 | }
24 | }
--------------------------------------------------------------------------------
/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/Utils/RpcTestEntity2.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 | }
--------------------------------------------------------------------------------
/src/DB/Rows.php:
--------------------------------------------------------------------------------
1 | logger;
21 | }
22 | ...
23 | }
24 | ```
25 |
26 |
27 |
28 | ### 2.2. 属性注入
29 |
30 | ```php
31 | class Books
32 | {
33 | use EnableDIAnnotations; //启用通过@inject标记注入依赖
34 | /**
35 | * @inject
36 | * @var DB
37 | */
38 | private $db;
39 | }
40 | ```
41 |
42 | **注意:PhpBoot 禁用了PHP-DI的 Annotation 注入方式,@inject 方式是 PhpBoot 实现的**
43 |
44 |
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/ScalarTypeContainer.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/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/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/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 | }
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=python -msphinx
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=build
12 | set SPHINXPROJ=phpboot
13 |
14 | if "%1" == "" goto help
15 |
16 | %SPHINXBUILD% >NUL 2>NUL
17 | if errorlevel 9009 (
18 | echo.
19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed,
20 | echo.then set the SPHINXBUILD environment variable to point to the full
21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the
22 | echo.Sphinx directory to PATH.
23 | echo.
24 | echo.If you don't have Sphinx installed, grab it from
25 | echo.http://sphinx-doc.org/
26 | exit /b 1
27 | )
28 |
29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
30 | goto end
31 |
32 | :help
33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
34 |
35 | :end
36 | popd
37 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | 开发中...
--------------------------------------------------------------------------------
/src/Docgen/Swagger/Schemas/InfoObject.php:
--------------------------------------------------------------------------------
1 | \DI\object(\Doctrine\Common\Cache\RedisCache::class)
14 | ->method('setRedis', \DI\factory(function(){
15 | $redis = new \Redis();
16 | $redis->connect('127.0.0.1', 6379);
17 | return $redis;
18 | })),
19 | ```
20 |
21 | 2. 在控制器中需要 redis 的地方, 注入 redis 实例
22 |
23 | ```php
24 |
25 | /**
26 | * @inject redis
27 | * @var \Doctrine\Common\Cache\RedisCache
28 | */
29 | private $redis;
30 | ```
31 |
32 | ## 系统缓存
33 |
34 | PhpBoot 框架为提高性能, 会将路由及Annotation 分析后的其他元信息进行缓存。生产环境建议使用 APC 扩展, 开发环境可以用文件缓存代替 apc, 方法是在 config.php 里加一个配置。
35 |
36 | ```php
37 | Cache::class => \DI\object(FilesystemCache::class)
38 | ->constructorParameter('directory', sys_get_temp_dir())
39 | ```
40 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | tests/
7 |
8 |
9 |
10 |
11 |
12 | src/
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/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/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/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 | }
--------------------------------------------------------------------------------
/docs/basic/annotation.md:
--------------------------------------------------------------------------------
1 | # Annotation
2 |
3 | PhpBoot 框架较多的使用了 Annotation。当然原生 PHP 语言并不支持此项特性,所以实际是通过Reflection提取注释并解析实现,类似很多主流 PHP 框架的做法(如 symfony、doctrine 等)。但又有所不同的是,主流的Annotation 语法基本沿用了 java 中的形式,如:
4 |
5 | ```php
6 | /**
7 | * @Route("/books/{id}", name="book_info")
8 | * @Method("GET")
9 | */
10 | public function getBook($id)...
11 | ```
12 | 语法严谨,易于扩展,但稍显啰嗦(PhpBoot 1.x 版本也使用此语法)。特别是PHP 由于先天不足(原生不支持Annotation),通过注释,在没有IDE语法提示和运行时检查机制的情况下。如果写 Annotation 过于复杂,那还不然直接写原生代码。所以 PhpBoot 使用了更简单的 Annotation 语法。如:
13 |
14 | ```php
15 | /**
16 | * @route GET /books/{id}
17 | */
18 | public function getBook($id)...
19 |
20 | ```
21 |
22 | ## 1. 语法
23 |
24 | ```@ [param0] [param1] [param2] ...```
25 |
26 | 1. name 只能是连续的字母、数字、斜杠'\'、中横杠'-' 组成的字符串,建议全小写,单词间用'-'分割,如```@myapp\my-ann```。
27 | 2. name和参数,参数和参数见,用空白符(一个或多个连续的空格、制表符)分割。
28 | 3. 参数中如果包含空格,应将参数用双引号""包围,包围内的双引号用\转义,如 ```@my-ann "the param \"0\"" param1``` 第一个参数为```the param "0"```
29 |
30 | **分割参数、转义的语法和linux 命令行的语法类似**
31 |
32 | ## 2. 嵌套
33 |
34 | 嵌套注释,用{}包围, 比如```@param int size {@v min:0|max:10}```
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/src/Utils/SerializableFunc.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/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/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 | }
--------------------------------------------------------------------------------
/docs/basic/route.md:
--------------------------------------------------------------------------------
1 | # 路由
2 |
3 | PhpBoot 支持两种形式的路由定义, 分别是通过加载 Controller 类,分析 Annotation ,自动加载路由,和通过 Application::addRoute 方法手动添加路由。
4 |
5 | ## 1. 自动加载路由
6 |
7 | 你可以通过 Application::loadRoutesFromClass 或者 Application::loadRoutesFromPath 添加路由。框架扫描每个类的每个方法,如果方法标记了@route,将被自动添加为路由。被加载类的形式如下:
8 |
9 | ```php
10 | /**
11 | * @path /books
12 | */
13 | class Books
14 | {
15 | /**
16 | * @route GET /{id}
17 | */
18 | public function getBook($id)
19 | }
20 | ```
21 | 以上代码表示 http 请求 ```GET /books/{id}``` 其实现为 Books::getBook, 其中{id}为url 的可变部分。
22 |
23 | ### 1.1. @path
24 |
25 | **语法:** ```@path ```
26 |
27 | 标注在类的注释里,用于指定 Controller 类中所定义的全部接口的uri 的前缀。
28 |
29 |
30 | ### 1.2. @route
31 |
32 | **语法:** ```@path ```
33 |
34 | 标注在方法的注释里,用于指定接口的路由。method为指定的 http 方法,可以是 GET、HEAD、POST、PUT、PATCH、DELETE、OPTION、DELETE。uri 中可以带变量,用{}包围。
35 |
36 | ## 2. 手动加载路由
37 |
38 | 你可以使用 Application::addRoute 手动加载路由,方法如下:
39 |
40 | ```php
41 | $app->addRoute('GET', '/books/{id}', function(Request $request){
42 | $books = new Books();
43 | return $books->getBook($request->get('id'));
44 | });
45 |
46 | ```
47 |
48 | **需要注意的是,此方法添加的路由,将不能自动生成接口文档。**
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/Utils/ArrayHelper.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/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/Docgen/Swagger/Schemas/ParameterObject.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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/docs/quick-start/webserver-config.md:
--------------------------------------------------------------------------------
1 | # WebServer 配置
2 |
3 | 为了使用PhpBoot,你需要配置 WebServer,将所有动态请求指向 index.php
4 |
5 | ## 1. Nginx
6 |
7 | 若使用 Nginx ,修改你的项目对应的配置:
8 |
9 | ```
10 | server {
11 | listen 80;
12 | server_name example.com;
13 | index index.php;
14 | error_log /path/to/example.error.log;
15 | access_log /path/to/example.access.log;
16 | root /path/to/public;
17 |
18 | location / {
19 | try_files $uri /index.php$is_args$args;
20 | }
21 |
22 | location ~ \.php {
23 | try_files $uri =404;
24 | fastcgi_split_path_info ^(.+\.php)(/.+)$;
25 | include fastcgi_params;
26 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
27 | fastcgi_param SCRIPT_NAME $fastcgi_script_name;
28 | fastcgi_index index.php;
29 | fastcgi_pass 127.0.0.1:9000;
30 | }
31 | }
32 | ```
33 |
34 | ## 2. Apache
35 |
36 | Apache 的配置稍微复杂,首先你需要启 mod_rewrite 模块,然后在 index.php 目录下添加 .htaccess 文件:
37 |
38 | ```
39 | Options +FollowSymLinks
40 | RewriteEngine On
41 |
42 | RewriteCond %{REQUEST_FILENAME} !-d
43 | RewriteCond %{REQUEST_FILENAME} !-f
44 | RewriteRule ^ index.php [L]
45 | ```
46 |
47 | 另外还需要修改虚拟主机的AllowOverride配置
48 |
49 | ```
50 | AllowOverride All
51 | ```
52 |
53 | **注意:由于 WebServer 版本的差异, 以上配置可能不能按预期工作,但这是使用多数 PHP 框架第一步需要解决的问题, 网上有会有很多解决方案,用好搜索引擎即可**
54 |
--------------------------------------------------------------------------------
/src/Docgen/Swagger/Schemas/ResponseObject.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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/ParamMeta.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/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/Annotation/AnnotationBlock.php:
--------------------------------------------------------------------------------
1 | name = $name;
23 | $this->summary = $summary;
24 | $this->description = $description;
25 | $this->children = $children;
26 | $this->parent = $parent;
27 | }
28 |
29 | /**
30 | * @var string
31 | */
32 | public $name = '';
33 | /**
34 | * @var string
35 | */
36 | public $summary = '';
37 | /**
38 | * @var string
39 | */
40 | public $description='';
41 | /**
42 | * @var AnnotationTag[]
43 | */
44 | public $children=[];
45 |
46 | /**
47 | * @var AnnotationTag|null
48 | */
49 | public $parent;
50 |
51 |
52 | public function offsetExists($offset)
53 | {
54 | return isset($this->$offset);
55 | }
56 |
57 |
58 | public function offsetGet($offset)
59 | {
60 | return $this->$offset;
61 | }
62 |
63 |
64 | public function offsetSet($offset, $value)
65 | {
66 | $this->$offset = $value;
67 | }
68 |
69 |
70 | public function offsetUnset($offset)
71 | {
72 | unset($this->$offset);
73 | }
74 | }
--------------------------------------------------------------------------------
/src/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/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/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/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/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/Docgen/Swagger/Schemas/PrimitiveSchemaObject.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/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/docs/basic/validation.md:
--------------------------------------------------------------------------------
1 | # 参数校验
2 |
3 | 在"参数绑定"时,起始已经支持了两项基本的校验(类型和是否必选),如果要支持更复杂的校验规则,可以通过 @v 指定,如:
4 |
5 | ```php
6 | /**
7 | * @route GET /books/
8 | * @param int $offsit {@v min:0}
9 | * @param int $limit {@v min:1|max:100}
10 | */
11 | public function getBooks($offsit=0, $limit=10)
12 | ```
13 | ## 1. 语法
14 |
15 | ```@v [:param0[,param1...]][|...]```
16 | * 多个规则间用```|```分割。
17 | * 规则和其参数间用```:```分割, 如果有多个参数,参数间用```,```分割。
18 |
19 | ## 2. 支持的规则
20 |
21 | * required - Required field
22 | * equals - Field must match another field (email/password confirmation)
23 | * different - Field must be different than another field
24 | * accepted - Checkbox or Radio must be accepted (yes, on, 1, true)
25 | * numeric - Must be numeric
26 | * integer - Must be integer number
27 | * boolean - Must be boolean
28 | * array - Must be array
29 | * length - String must be certain length
30 | * lengthBetween - String must be between given lengths
31 | * lengthMin - String must be greater than given length
32 | * lengthMax - String must be less than given length
33 | * min - Minimum
34 | * max - Maximum
35 | * in - Performs in_array check on given array values
36 | * notIn - Negation of in rule (not in array of values)
37 | * ip - Valid IP address
38 | * email - Valid email address
39 | * url - Valid URL
40 | * urlActive - Valid URL with active DNS record
41 | * alpha - Alphabetic characters only
42 | * alphaNum - Alphabetic and numeric characters only
43 | * slug - URL slug characters (a-z, 0-9, -, _)
44 | * regex - Field matches given regex pattern
45 | * date - Field is a valid date
46 | * dateFormat - Field is a valid date in the given format
47 | * dateBefore - Field is a valid date and is before the given date
48 | * dateAfter - Field is a valid date and is after the given date
49 | * contains - Field is a string and contains the given string
50 | * creditCard - Field is a valid credit card number
51 | * optional - Value does not need to be included in data array. If it is however, it must pass validation.
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 | ```
--------------------------------------------------------------------------------
/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/Docgen/Swagger/Schemas/HeaderObject.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/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/Docgen/Swagger/Schemas/OtherParameterObject.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/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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/src/Annotation/AnnotationTag.php:
--------------------------------------------------------------------------------
1 | name = $name;
22 | $this->description = $description;
23 | $this->children = $children;
24 | $this->parent = $parent;
25 | }
26 |
27 | /**
28 | * @var string
29 | */
30 | public $name = '';
31 | /**
32 | * @var string
33 | */
34 | public $description='';
35 | /**
36 | * @var AnnotationBlock[]
37 | */
38 | public $children=[];
39 |
40 | /**
41 | * @var AnnotationBlock|null
42 | */
43 | public $parent;
44 |
45 | /**
46 | * Whether a offset exists
47 | * @link http://php.net/manual/en/arrayaccess.offsetexists.php
48 | * @param mixed $offset
49 | * An offset to check for.
50 | *
51 | * @return boolean true on success or false on failure.
52 | *
53 | *
54 | * The return value will be casted to boolean if non-boolean was returned.
55 | * @since 5.0.0
56 | */
57 | public function offsetExists($offset)
58 | {
59 | return isset($this->$offset);
60 | }
61 |
62 | /**
63 | * Offset to retrieve
64 | * @link http://php.net/manual/en/arrayaccess.offsetget.php
65 | * @param mixed $offset
66 | * The offset to retrieve.
67 | *
68 | * @return mixed Can return all value types.
69 | * @since 5.0.0
70 | */
71 | public function offsetGet($offset)
72 | {
73 | return $this->$offset;
74 | }
75 |
76 | /**
77 | * Offset to set
78 | * @link http://php.net/manual/en/arrayaccess.offsetset.php
79 | * @param mixed $offset
80 | * The offset to assign the value to.
81 | *
82 | * @param mixed $value
83 | * The value to set.
84 | *
85 | * @return void
86 | * @since 5.0.0
87 | */
88 | public function offsetSet($offset, $value)
89 | {
90 | $this->$offset = $value;
91 | }
92 |
93 | /**
94 | * Offset to unset
95 | * @link http://php.net/manual/en/arrayaccess.offsetunset.php
96 | * @param mixed $offset
97 | * The offset to unset.
98 | *
99 | * @return void
100 | * @since 5.0.0
101 | */
102 | public function offsetUnset($offset)
103 | {
104 | unset($this->$offset);
105 | }
106 | }
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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/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 | }
--------------------------------------------------------------------------------
/tests/Utils/RpcTestController.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/Docgen/Swagger/Schemas/OperationObject.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/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/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/RequestHandler.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/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 | }
--------------------------------------------------------------------------------