quguofeng 2 年之前
父節點
當前提交
958eb9ccfe
共有 100 個文件被更改,包括 11208 次插入9 次删除
  1. 1 1
      composer.json
  2. 6 2
      vendor/composer/autoload_psr4.php
  3. 27 4
      vendor/composer/autoload_static.php
  4. 302 0
      vendor/composer/installed.json
  5. 38 2
      vendor/composer/installed.php
  6. 19 0
      vendor/doctrine/annotations/LICENSE
  7. 24 0
      vendor/doctrine/annotations/README.md
  8. 72 0
      vendor/doctrine/annotations/composer.json
  9. 252 0
      vendor/doctrine/annotations/docs/en/annotations.rst
  10. 443 0
      vendor/doctrine/annotations/docs/en/custom.rst
  11. 110 0
      vendor/doctrine/annotations/docs/en/index.rst
  12. 6 0
      vendor/doctrine/annotations/docs/en/sidebar.rst
  13. 57 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php
  14. 21 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php
  15. 15 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php
  16. 69 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php
  17. 43 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php
  18. 13 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php
  19. 13 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php
  20. 101 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php
  21. 167 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php
  22. 389 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php
  23. 190 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php
  24. 266 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php
  25. 139 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php
  26. 1485 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php
  27. 315 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php
  28. 178 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php
  29. 100 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php
  30. 14 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php
  31. 92 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php
  32. 232 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php
  33. 80 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php
  34. 114 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php
  35. 206 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php
  36. 15 0
      vendor/doctrine/annotations/psalm.xml
  37. 19 0
      vendor/doctrine/deprecations/LICENSE
  38. 154 0
      vendor/doctrine/deprecations/README.md
  39. 32 0
      vendor/doctrine/deprecations/composer.json
  40. 266 0
      vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php
  41. 66 0
      vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php
  42. 22 0
      vendor/doctrine/deprecations/phpcs.xml
  43. 19 0
      vendor/doctrine/lexer/LICENSE
  44. 9 0
      vendor/doctrine/lexer/README.md
  45. 14 0
      vendor/doctrine/lexer/UPGRADE.md
  46. 56 0
      vendor/doctrine/lexer/composer.json
  47. 336 0
      vendor/doctrine/lexer/src/AbstractLexer.php
  48. 129 0
      vendor/doctrine/lexer/src/Token.php
  49. 1 0
      vendor/hg/apidoc/.gitignore
  50. 21 0
      vendor/hg/apidoc/LICENSE
  51. 86 0
      vendor/hg/apidoc/README.md
  52. 49 0
      vendor/hg/apidoc/composer.json
  53. 191 0
      vendor/hg/apidoc/src/Auth.php
  54. 24 0
      vendor/hg/apidoc/src/ConfigProvider.php
  55. 278 0
      vendor/hg/apidoc/src/Controller.php
  56. 56 0
      vendor/hg/apidoc/src/annotation/AddField.php
  57. 29 0
      vendor/hg/apidoc/src/annotation/After.php
  58. 14 0
      vendor/hg/apidoc/src/annotation/Author.php
  59. 23 0
      vendor/hg/apidoc/src/annotation/Before.php
  60. 14 0
      vendor/hg/apidoc/src/annotation/ContentType.php
  61. 16 0
      vendor/hg/apidoc/src/annotation/Desc.php
  62. 67 0
      vendor/hg/apidoc/src/annotation/EventBase.php
  63. 14 0
      vendor/hg/apidoc/src/annotation/Field.php
  64. 14 0
      vendor/hg/apidoc/src/annotation/Group.php
  65. 40 0
      vendor/hg/apidoc/src/annotation/Header.php
  66. 20 0
      vendor/hg/apidoc/src/annotation/Md.php
  67. 14 0
      vendor/hg/apidoc/src/annotation/Method.php
  68. 33 0
      vendor/hg/apidoc/src/annotation/Param.php
  69. 74 0
      vendor/hg/apidoc/src/annotation/ParamBase.php
  70. 14 0
      vendor/hg/apidoc/src/annotation/ParamType.php
  71. 40 0
      vendor/hg/apidoc/src/annotation/Query.php
  72. 23 0
      vendor/hg/apidoc/src/annotation/ResponseError.php
  73. 20 0
      vendor/hg/apidoc/src/annotation/ResponseErrorMd.php
  74. 33 0
      vendor/hg/apidoc/src/annotation/ResponseSuccess.php
  75. 20 0
      vendor/hg/apidoc/src/annotation/ResponseSuccessMd.php
  76. 34 0
      vendor/hg/apidoc/src/annotation/Returned.php
  77. 45 0
      vendor/hg/apidoc/src/annotation/RouteParam.php
  78. 77 0
      vendor/hg/apidoc/src/annotation/Rule.php
  79. 14 0
      vendor/hg/apidoc/src/annotation/Sort.php
  80. 14 0
      vendor/hg/apidoc/src/annotation/Tag.php
  81. 14 0
      vendor/hg/apidoc/src/annotation/Title.php
  82. 14 0
      vendor/hg/apidoc/src/annotation/Url.php
  83. 14 0
      vendor/hg/apidoc/src/annotation/WithoutField.php
  84. 92 0
      vendor/hg/apidoc/src/config.php
  85. 62 0
      vendor/hg/apidoc/src/exception/ErrorException.php
  86. 33 0
      vendor/hg/apidoc/src/exception/HttpException.php
  87. 348 0
      vendor/hg/apidoc/src/generator/Index.php
  88. 339 0
      vendor/hg/apidoc/src/generator/ParseTemplate.php
  89. 17 0
      vendor/hg/apidoc/src/middleware/LaravelMiddleware.php
  90. 17 0
      vendor/hg/apidoc/src/middleware/ThinkPHPMiddleware.php
  91. 35 0
      vendor/hg/apidoc/src/parses/ParseAnnotation.php
  92. 852 0
      vendor/hg/apidoc/src/parses/ParseApiDetail.php
  93. 353 0
      vendor/hg/apidoc/src/parses/ParseApiMenus.php
  94. 104 0
      vendor/hg/apidoc/src/parses/ParseCodeTemplate.php
  95. 136 0
      vendor/hg/apidoc/src/parses/ParseMarkdown.php
  96. 277 0
      vendor/hg/apidoc/src/parses/ParseModel.php
  97. 133 0
      vendor/hg/apidoc/src/providers/BaseService.php
  98. 56 0
      vendor/hg/apidoc/src/providers/CommonService.php
  99. 80 0
      vendor/hg/apidoc/src/providers/HyperfService.php
  100. 84 0
      vendor/hg/apidoc/src/providers/LaravelService.php

+ 1 - 1
composer.json

@@ -23,7 +23,7 @@
     "zoujingli/weopen-developer": "dev-master",
     "overtrue/wechat": "~4.0",
     "firebase/php-jwt": "^5.2",
-    "hg/apidoc": "1.1.x",
+    "hg/apidoc": "^4.1",
     "alibabacloud/client": "^1.5",
     "alipaysdk/easysdk": "^2.2",
     "alibabacloud/dingtalk": "^1.4"

+ 6 - 2
vendor/composer/autoload_psr4.php

@@ -8,12 +8,13 @@ $baseDir = dirname($vendorDir);
 return array(
     'think\\composer\\' => array($vendorDir . '/topthink/think-installer/src'),
     'library\\' => array($vendorDir . '/zoujingli/think-library/src'),
+    'hg\\apidoc\\' => array($vendorDir . '/hg/apidoc/src'),
     'clagiordano\\weblibs\\configmanager\\' => array($vendorDir . '/clagiordano/weblibs-configmanager/src'),
     'WePay\\' => array($vendorDir . '/zoujingli/wechat-developer/WePay'),
     'WePayV3\\' => array($vendorDir . '/zoujingli/wechat-developer/WePayV3'),
     'WeOpen\\' => array($vendorDir . '/zoujingli/weopen-developer/WeOpen'),
-    'WeMini\\' => array($vendorDir . '/zoujingli/weopen-developer/WeMini', $vendorDir . '/zoujingli/wechat-developer/WeMini'),
-    'WeChat\\' => array($vendorDir . '/zoujingli/weopen-developer/WeChat', $vendorDir . '/zoujingli/wechat-developer/WeChat'),
+    'WeMini\\' => array($vendorDir . '/zoujingli/wechat-developer/WeMini', $vendorDir . '/zoujingli/weopen-developer/WeMini'),
+    'WeChat\\' => array($vendorDir . '/zoujingli/wechat-developer/WeChat', $vendorDir . '/zoujingli/weopen-developer/WeChat'),
     'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
     'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
     'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
@@ -49,6 +50,9 @@ return array(
     'Endroid\\QrCode\\' => array($vendorDir . '/endroid/qr-code/src'),
     'EasyWeChat\\' => array($vendorDir . '/overtrue/wechat/src'),
     'EasyWeChatComposer\\' => array($vendorDir . '/easywechat-composer/easywechat-composer/src'),
+    'Doctrine\\Deprecations\\' => array($vendorDir . '/doctrine/deprecations/lib/Doctrine/Deprecations'),
+    'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/src'),
+    'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'),
     'Darabonba\\OpenApi\\' => array($vendorDir . '/alibabacloud/darabonba-openapi/src'),
     'Darabonba\\GatewaySpi\\' => array($vendorDir . '/alibabacloud/gateway-spi/src'),
     'Alipay\\EasySDK\\' => array($vendorDir . '/alipaysdk/easysdk/php/src'),

+ 27 - 4
vendor/composer/autoload_static.php

@@ -33,6 +33,10 @@ class ComposerStaticInit4d241e9f8bb10d006cd7432f945fdb5b
         array (
             'library\\' => 8,
         ),
+        'h' => 
+        array (
+            'hg\\apidoc\\' => 10,
+        ),
         'c' => 
         array (
             'clagiordano\\weblibs\\configmanager\\' => 34,
@@ -109,6 +113,9 @@ class ComposerStaticInit4d241e9f8bb10d006cd7432f945fdb5b
         ),
         'D' => 
         array (
+            'Doctrine\\Deprecations\\' => 22,
+            'Doctrine\\Common\\Lexer\\' => 22,
+            'Doctrine\\Common\\Annotations\\' => 28,
             'Darabonba\\OpenApi\\' => 18,
             'Darabonba\\GatewaySpi\\' => 21,
         ),
@@ -214,6 +221,10 @@ class ComposerStaticInit4d241e9f8bb10d006cd7432f945fdb5b
         array (
             0 => __DIR__ . '/..' . '/zoujingli/think-library/src',
         ),
+        'hg\\apidoc\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/hg/apidoc/src',
+        ),
         'clagiordano\\weblibs\\configmanager\\' => 
         array (
             0 => __DIR__ . '/..' . '/clagiordano/weblibs-configmanager/src',
@@ -232,13 +243,13 @@ class ComposerStaticInit4d241e9f8bb10d006cd7432f945fdb5b
         ),
         'WeMini\\' => 
         array (
-            0 => __DIR__ . '/..' . '/zoujingli/weopen-developer/WeMini',
-            1 => __DIR__ . '/..' . '/zoujingli/wechat-developer/WeMini',
+            0 => __DIR__ . '/..' . '/zoujingli/wechat-developer/WeMini',
+            1 => __DIR__ . '/..' . '/zoujingli/weopen-developer/WeMini',
         ),
         'WeChat\\' => 
         array (
-            0 => __DIR__ . '/..' . '/zoujingli/weopen-developer/WeChat',
-            1 => __DIR__ . '/..' . '/zoujingli/wechat-developer/WeChat',
+            0 => __DIR__ . '/..' . '/zoujingli/wechat-developer/WeChat',
+            1 => __DIR__ . '/..' . '/zoujingli/weopen-developer/WeChat',
         ),
         'Symfony\\Polyfill\\Php80\\' => 
         array (
@@ -381,6 +392,18 @@ class ComposerStaticInit4d241e9f8bb10d006cd7432f945fdb5b
         array (
             0 => __DIR__ . '/..' . '/easywechat-composer/easywechat-composer/src',
         ),
+        'Doctrine\\Deprecations\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/doctrine/deprecations/lib/Doctrine/Deprecations',
+        ),
+        'Doctrine\\Common\\Lexer\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/doctrine/lexer/src',
+        ),
+        'Doctrine\\Common\\Annotations\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations',
+        ),
         'Darabonba\\OpenApi\\' => 
         array (
             0 => __DIR__ . '/..' . '/alibabacloud/darabonba-openapi/src',

+ 302 - 0
vendor/composer/installed.json

@@ -989,6 +989,230 @@
             "install-path": "../clagiordano/weblibs-configmanager"
         },
         {
+            "name": "doctrine/annotations",
+            "version": "1.14.0",
+            "version_normalized": "1.14.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/annotations.git",
+                "reference": "3587ab58646bc515b2e03bbd3cfcd3682e8df5bf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/annotations/zipball/3587ab58646bc515b2e03bbd3cfcd3682e8df5bf",
+                "reference": "3587ab58646bc515b2e03bbd3cfcd3682e8df5bf",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "doctrine/lexer": "^1 || ^2",
+                "ext-tokenizer": "*",
+                "php": "^7.1 || ^8.0",
+                "psr/cache": "^1 || ^2 || ^3"
+            },
+            "require-dev": {
+                "doctrine/cache": "^1.11 || ^2.0",
+                "doctrine/coding-standard": "^9 || ^10",
+                "phpstan/phpstan": "~1.4.10 || ^1.8.0",
+                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+                "symfony/cache": "^4.4 || ^5.4 || ^6",
+                "vimeo/psalm": "^4.10"
+            },
+            "suggest": {
+                "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations"
+            },
+            "time": "2022-12-11T18:25:48+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
+                {
+                    "name": "Roman Borschel",
+                    "email": "roman@code-factory.org"
+                },
+                {
+                    "name": "Benjamin Eberlei",
+                    "email": "kontakt@beberlei.de"
+                },
+                {
+                    "name": "Jonathan Wage",
+                    "email": "jonwage@gmail.com"
+                },
+                {
+                    "name": "Johannes Schmitt",
+                    "email": "schmittjoh@gmail.com"
+                }
+            ],
+            "description": "Docblock Annotations Parser",
+            "homepage": "https://www.doctrine-project.org/projects/annotations.html",
+            "keywords": [
+                "annotations",
+                "docblock",
+                "parser"
+            ],
+            "support": {
+                "issues": "https://github.com/doctrine/annotations/issues",
+                "source": "https://github.com/doctrine/annotations/tree/1.14.0"
+            },
+            "install-path": "../doctrine/annotations"
+        },
+        {
+            "name": "doctrine/deprecations",
+            "version": "v1.0.0",
+            "version_normalized": "1.0.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/deprecations.git",
+                "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
+                "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.1|^8.0"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^9",
+                "phpunit/phpunit": "^7.5|^8.5|^9.5",
+                "psr/log": "^1|^2|^3"
+            },
+            "suggest": {
+                "psr/log": "Allows logging deprecations via PSR-3 logger implementation"
+            },
+            "time": "2022-05-02T15:47:09+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
+            "homepage": "https://www.doctrine-project.org/",
+            "support": {
+                "issues": "https://github.com/doctrine/deprecations/issues",
+                "source": "https://github.com/doctrine/deprecations/tree/v1.0.0"
+            },
+            "install-path": "../doctrine/deprecations"
+        },
+        {
+            "name": "doctrine/lexer",
+            "version": "2.0.0",
+            "version_normalized": "2.0.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/lexer.git",
+                "reference": "3cf140b81e55d5d640f73367d829db7e3023ef69"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/lexer/zipball/3cf140b81e55d5d640f73367d829db7e3023ef69",
+                "reference": "3cf140b81e55d5d640f73367d829db7e3023ef69",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "doctrine/deprecations": "^1.0",
+                "php": "^7.1 || ^8.0"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^9 || ^10",
+                "phpstan/phpstan": "^1.3",
+                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+                "psalm/plugin-phpunit": "^0.18.3",
+                "vimeo/psalm": "^4.11 || ^5.0"
+            },
+            "time": "2022-12-11T10:51:23+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Common\\Lexer\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
+                {
+                    "name": "Roman Borschel",
+                    "email": "roman@code-factory.org"
+                },
+                {
+                    "name": "Johannes Schmitt",
+                    "email": "schmittjoh@gmail.com"
+                }
+            ],
+            "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
+            "homepage": "https://www.doctrine-project.org/projects/lexer.html",
+            "keywords": [
+                "annotations",
+                "docblock",
+                "lexer",
+                "parser",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/doctrine/lexer/issues",
+                "source": "https://github.com/doctrine/lexer/tree/2.0.0"
+            },
+            "funding": [
+                {
+                    "url": "https://www.doctrine-project.org/sponsorship.html",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://www.patreon.com/phpdoctrine",
+                    "type": "patreon"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer",
+                    "type": "tidelift"
+                }
+            ],
+            "install-path": "../doctrine/lexer"
+        },
+        {
             "name": "easywechat-composer/easywechat-composer",
             "version": "1.4.1",
             "version_normalized": "1.4.1.0",
@@ -1543,6 +1767,84 @@
             "install-path": "../guzzlehttp/psr7"
         },
         {
+            "name": "hg/apidoc",
+            "version": "v4.1.5",
+            "version_normalized": "4.1.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/HGthecode/apidoc-php.git",
+                "reference": "2daa881bce690d055bed0c86a882f0f6a3dd47aa"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/HGthecode/apidoc-php/zipball/2daa881bce690d055bed0c86a882f0f6a3dd47aa",
+                "reference": "2daa881bce690d055bed0c86a882f0f6a3dd47aa",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "doctrine/annotations": "^1.6",
+                "php": ">=7.1"
+            },
+            "time": "2022-12-07T01:10:51+00:00",
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "providers": [
+                        "hg\\apidoc\\providers\\LaravelService"
+                    ]
+                },
+                "think": {
+                    "services": [
+                        "hg\\apidoc\\providers\\ThinkPHPService"
+                    ],
+                    "config": {
+                        "apidoc": "src/config.php"
+                    }
+                },
+                "hyperf": {
+                    "config": "hg\\apidoc\\ConfigProvider"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "hg\\apidoc\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "hg-code",
+                    "email": "376401263@qq.com"
+                }
+            ],
+            "description": "注释自动生成API文档、在线调试、Markdown文档、代码生成器",
+            "keywords": [
+                "apidoc",
+                "api文档",
+                "markdown",
+                "php api文档",
+                "php接口文档",
+                "接口文档",
+                "注释生成",
+                "自动生成api"
+            ],
+            "support": {
+                "issues": "https://github.com/HGthecode/apidoc-php/issues",
+                "source": "https://github.com/HGthecode/apidoc-php/tree/v4.1.5"
+            },
+            "install-path": "../hg/apidoc"
+        },
+        {
             "name": "lizhichao/one-sm",
             "version": "1.10",
             "version_normalized": "1.10.0.0",

+ 38 - 2
vendor/composer/installed.php

@@ -5,7 +5,7 @@
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
-        'reference' => 'de839c3a91cfe5c71d64cf3553a84b60563fcb87',
+        'reference' => '3a04b1f76489516e051c2d92f4b0c192acbde037',
         'name' => 'zoujingli/thinkadmin',
         'dev' => true,
     ),
@@ -145,6 +145,33 @@
             'reference' => '8802c7396d61a923c9a73e37ead062b24bb1b273',
             'dev_requirement' => false,
         ),
+        'doctrine/annotations' => array(
+            'pretty_version' => '1.14.0',
+            'version' => '1.14.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../doctrine/annotations',
+            'aliases' => array(),
+            'reference' => '3587ab58646bc515b2e03bbd3cfcd3682e8df5bf',
+            'dev_requirement' => false,
+        ),
+        'doctrine/deprecations' => array(
+            'pretty_version' => 'v1.0.0',
+            'version' => '1.0.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../doctrine/deprecations',
+            'aliases' => array(),
+            'reference' => '0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de',
+            'dev_requirement' => false,
+        ),
+        'doctrine/lexer' => array(
+            'pretty_version' => '2.0.0',
+            'version' => '2.0.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../doctrine/lexer',
+            'aliases' => array(),
+            'reference' => '3cf140b81e55d5d640f73367d829db7e3023ef69',
+            'dev_requirement' => false,
+        ),
         'easywechat-composer/easywechat-composer' => array(
             'pretty_version' => '1.4.1',
             'version' => '1.4.1.0',
@@ -199,6 +226,15 @@
             'reference' => '67c26b443f348a51926030c83481b85718457d3d',
             'dev_requirement' => false,
         ),
+        'hg/apidoc' => array(
+            'pretty_version' => 'v4.1.5',
+            'version' => '4.1.5.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../hg/apidoc',
+            'aliases' => array(),
+            'reference' => '2daa881bce690d055bed0c86a882f0f6a3dd47aa',
+            'dev_requirement' => false,
+        ),
         'lizhichao/one-sm' => array(
             'pretty_version' => '1.10',
             'version' => '1.10.0.0',
@@ -583,7 +619,7 @@
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
-            'reference' => 'de839c3a91cfe5c71d64cf3553a84b60563fcb87',
+            'reference' => '3a04b1f76489516e051c2d92f4b0c192acbde037',
             'dev_requirement' => false,
         ),
         'zoujingli/wechat-developer' => array(

+ 19 - 0
vendor/doctrine/annotations/LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2006-2013 Doctrine Project
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 24 - 0
vendor/doctrine/annotations/README.md

@@ -0,0 +1,24 @@
+⚠️ PHP 8 introduced
+[attributes](https://www.php.net/manual/en/language.attributes.overview.php),
+which are a native replacement for annotations. As such, this library is
+considered feature complete, and should receive exclusively bugfixes and
+security fixes.
+
+# Doctrine Annotations
+
+[![Build Status](https://github.com/doctrine/annotations/workflows/Continuous%20Integration/badge.svg?label=build)](https://github.com/doctrine/persistence/actions)
+[![Dependency Status](https://www.versioneye.com/package/php--doctrine--annotations/badge.png)](https://www.versioneye.com/package/php--doctrine--annotations)
+[![Reference Status](https://www.versioneye.com/php/doctrine:annotations/reference_badge.svg)](https://www.versioneye.com/php/doctrine:annotations/references)
+[![Total Downloads](https://poser.pugx.org/doctrine/annotations/downloads.png)](https://packagist.org/packages/doctrine/annotations)
+[![Latest Stable Version](https://img.shields.io/packagist/v/doctrine/annotations.svg?label=stable)](https://packagist.org/packages/doctrine/annotations)
+
+Docblock Annotations Parser library (extracted from [Doctrine Common](https://github.com/doctrine/common)).
+
+## Documentation
+
+See the [doctrine-project website](https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html).
+
+## Contributing
+
+When making a pull request, make sure your changes follow the
+[Coding Standard Guidelines](https://www.doctrine-project.org/projects/doctrine-coding-standard/en/current/reference/index.html#introduction).

+ 72 - 0
vendor/doctrine/annotations/composer.json

@@ -0,0 +1,72 @@
+{
+    "name": "doctrine/annotations",
+    "description": "Docblock Annotations Parser",
+    "license": "MIT",
+    "type": "library",
+    "keywords": [
+        "annotations",
+        "docblock",
+        "parser"
+    ],
+    "authors": [
+        {
+            "name": "Guilherme Blanco",
+            "email": "guilhermeblanco@gmail.com"
+        },
+        {
+            "name": "Roman Borschel",
+            "email": "roman@code-factory.org"
+        },
+        {
+            "name": "Benjamin Eberlei",
+            "email": "kontakt@beberlei.de"
+        },
+        {
+            "name": "Jonathan Wage",
+            "email": "jonwage@gmail.com"
+        },
+        {
+            "name": "Johannes Schmitt",
+            "email": "schmittjoh@gmail.com"
+        }
+    ],
+    "homepage": "https://www.doctrine-project.org/projects/annotations.html",
+    "require": {
+        "php": "^7.1 || ^8.0",
+        "ext-tokenizer": "*",
+        "doctrine/lexer": "^1 || ^2",
+        "psr/cache": "^1 || ^2 || ^3"
+    },
+    "require-dev": {
+        "doctrine/cache": "^1.11 || ^2.0",
+        "doctrine/coding-standard": "^9 || ^10",
+        "phpstan/phpstan": "~1.4.10 || ^1.8.0",
+        "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+        "symfony/cache": "^4.4 || ^5.4 || ^6",
+        "vimeo/psalm": "^4.10"
+    },
+    "suggest": {
+        "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations"
+    },
+    "autoload": {
+        "psr-4": {
+            "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Doctrine\\Performance\\Common\\Annotations\\": "tests/Doctrine/Performance/Common/Annotations",
+            "Doctrine\\Tests\\Common\\Annotations\\": "tests/Doctrine/Tests/Common/Annotations"
+        },
+        "files": [
+            "tests/Doctrine/Tests/Common/Annotations/Fixtures/functions.php",
+            "tests/Doctrine/Tests/Common/Annotations/Fixtures/SingleClassLOC1000.php"
+        ]
+    },
+    "config": {
+        "allow-plugins": {
+            "dealerdirect/phpcodesniffer-composer-installer": true
+        },
+        "sort-packages": true
+    }
+}

+ 252 - 0
vendor/doctrine/annotations/docs/en/annotations.rst

@@ -0,0 +1,252 @@
+Handling Annotations
+====================
+
+There are several different approaches to handling annotations in PHP.
+Doctrine Annotations maps docblock annotations to PHP classes. Because
+not all docblock annotations are used for metadata purposes a filter is
+applied to ignore or skip classes that are not Doctrine annotations.
+
+Take a look at the following code snippet:
+
+.. code-block:: php
+
+    namespace MyProject\Entities;
+
+    use Doctrine\ORM\Mapping AS ORM;
+    use Symfony\Component\Validator\Constraints AS Assert;
+
+    /**
+     * @author Benjamin Eberlei
+     * @ORM\Entity
+     * @MyProject\Annotations\Foobarable
+     */
+    class User
+    {
+        /**
+         * @ORM\Id @ORM\Column @ORM\GeneratedValue
+         * @dummy
+         * @var int
+         */
+        private $id;
+
+        /**
+         * @ORM\Column(type="string")
+         * @Assert\NotEmpty
+         * @Assert\Email
+         * @var string
+         */
+        private $email;
+    }
+
+In this snippet you can see a variety of different docblock annotations:
+
+- Documentation annotations such as ``@var`` and ``@author``. These
+  annotations are ignored and never considered for throwing an
+  exception due to wrongly used annotations.
+- Annotations imported through use statements. The statement ``use
+  Doctrine\ORM\Mapping AS ORM`` makes all classes under that namespace
+  available as ``@ORM\ClassName``. Same goes for the import of
+  ``@Assert``.
+- The ``@dummy`` annotation. It is not a documentation annotation and
+  not ignored. For Doctrine Annotations it is not entirely clear how
+  to handle this annotation. Depending on the configuration an exception
+  (unknown annotation) will be thrown when parsing this annotation.
+- The fully qualified annotation ``@MyProject\Annotations\Foobarable``.
+  This is transformed directly into the given class name.
+
+How are these annotations loaded? From looking at the code you could
+guess that the ORM Mapping, Assert Validation and the fully qualified
+annotation can just be loaded using
+the defined PHP autoloaders. This is not the case however: For error
+handling reasons every check for class existence inside the
+``AnnotationReader`` sets the second parameter $autoload
+of ``class_exists($name, $autoload)`` to false. To work flawlessly the
+``AnnotationReader`` requires silent autoloaders which many autoloaders are
+not. Silent autoloading is NOT part of the `PSR-0 specification
+<https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md>`_
+for autoloading.
+
+This is why Doctrine Annotations uses its own autoloading mechanism
+through a global registry. If you are wondering about the annotation
+registry being global, there is no other way to solve the architectural
+problems of autoloading annotation classes in a straightforward fashion.
+Additionally if you think about PHP autoloading then you recognize it is
+a global as well.
+
+To anticipate the configuration section, making the above PHP class work
+with Doctrine Annotations requires this setup:
+
+.. code-block:: php
+
+    use Doctrine\Common\Annotations\AnnotationReader;
+    use Doctrine\Common\Annotations\AnnotationRegistry;
+
+    AnnotationRegistry::registerFile("/path/to/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php");
+    AnnotationRegistry::registerAutoloadNamespace("Symfony\Component\Validator\Constraint", "/path/to/symfony/src");
+    AnnotationRegistry::registerAutoloadNamespace("MyProject\Annotations", "/path/to/myproject/src");
+
+    $reader = new AnnotationReader();
+    AnnotationReader::addGlobalIgnoredName('dummy');
+
+The second block with the annotation registry calls registers all the
+three different annotation namespaces that are used.
+Doctrine Annotations saves all its annotations in a single file, that is
+why ``AnnotationRegistry#registerFile`` is used in contrast to
+``AnnotationRegistry#registerAutoloadNamespace`` which creates a PSR-0
+compatible loading mechanism for class to file names.
+
+In the third block, we create the actual ``AnnotationReader`` instance.
+Note that we also add ``dummy`` to the global list of ignored
+annotations for which we do not throw exceptions. Setting this is
+necessary in our example case, otherwise ``@dummy`` would trigger an
+exception to be thrown during the parsing of the docblock of
+``MyProject\Entities\User#id``.
+
+Setup and Configuration
+-----------------------
+
+To use the annotations library is simple, you just need to create a new
+``AnnotationReader`` instance:
+
+.. code-block:: php
+
+    $reader = new \Doctrine\Common\Annotations\AnnotationReader();
+
+This creates a simple annotation reader with no caching other than in
+memory (in php arrays). Since parsing docblocks can be expensive you
+should cache this process by using a caching reader.
+
+To cache annotations, you can create a ``Doctrine\Common\Annotations\PsrCachedReader``.
+This reader decorates the original reader and stores all annotations in a PSR-6
+cache:
+
+.. code-block:: php
+
+    use Doctrine\Common\Annotations\AnnotationReader;
+    use Doctrine\Common\Annotations\PsrCachedReader;
+
+    $cache = ... // instantiate a PSR-6 Cache pool
+
+    $reader = new PsrCachedReader(
+        new AnnotationReader(),
+        $cache,
+        $debug = true
+    );
+
+The ``debug`` flag is used here as well to invalidate the cache files
+when the PHP class with annotations changed and should be used during
+development.
+
+.. warning ::
+
+    The ``AnnotationReader`` works and caches under the
+    assumption that all annotations of a doc-block are processed at
+    once. That means that annotation classes that do not exist and
+    aren't loaded and cannot be autoloaded (using the
+    AnnotationRegistry) would never be visible and not accessible if a
+    cache is used unless the cache is cleared and the annotations
+    requested again, this time with all annotations defined.
+
+By default the annotation reader returns a list of annotations with
+numeric indexes. If you want your annotations to be indexed by their
+class name you can wrap the reader in an ``IndexedReader``:
+
+.. code-block:: php
+
+    use Doctrine\Common\Annotations\AnnotationReader;
+    use Doctrine\Common\Annotations\IndexedReader;
+
+    $reader = new IndexedReader(new AnnotationReader());
+
+.. warning::
+
+    You should never wrap the indexed reader inside a cached reader,
+    only the other way around. This way you can re-use the cache with
+    indexed or numeric keys, otherwise your code may experience failures
+    due to caching in a numerical or indexed format.
+
+Registering Annotations
+~~~~~~~~~~~~~~~~~~~~~~~
+
+As explained in the introduction, Doctrine Annotations uses its own
+autoloading mechanism to determine if a given annotation has a
+corresponding PHP class that can be autoloaded. For annotation
+autoloading you have to configure the
+``Doctrine\Common\Annotations\AnnotationRegistry``. There are three
+different mechanisms to configure annotation autoloading:
+
+- Calling ``AnnotationRegistry#registerFile($file)`` to register a file
+  that contains one or more annotation classes.
+- Calling ``AnnotationRegistry#registerNamespace($namespace, $dirs =
+  null)`` to register that the given namespace contains annotations and
+  that their base directory is located at the given $dirs or in the
+  include path if ``NULL`` is passed. The given directories should *NOT*
+  be the directory where classes of the namespace are in, but the base
+  directory of the root namespace. The AnnotationRegistry uses a
+  namespace to directory separator approach to resolve the correct path.
+- Calling ``AnnotationRegistry#registerLoader($callable)`` to register
+  an autoloader callback. The callback accepts the class as first and
+  only parameter and has to return ``true`` if the corresponding file
+  was found and included.
+
+.. note::
+
+    Loaders have to fail silently, if a class is not found even if it
+    matches for example the namespace prefix of that loader. Never is a
+    loader to throw a warning or exception if the loading failed
+    otherwise parsing doc block annotations will become a huge pain.
+
+A sample loader callback could look like:
+
+.. code-block:: php
+
+    use Doctrine\Common\Annotations\AnnotationRegistry;
+    use Symfony\Component\ClassLoader\UniversalClassLoader;
+
+    AnnotationRegistry::registerLoader(function($class) {
+        $file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php";
+
+        if (file_exists("/my/base/path/" . $file)) {
+            // file_exists() makes sure that the loader fails silently
+            require "/my/base/path/" . $file;
+        }
+    });
+
+    $loader = new UniversalClassLoader();
+    AnnotationRegistry::registerLoader(array($loader, "loadClass"));
+
+
+Ignoring missing exceptions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default an exception is thrown from the ``AnnotationReader`` if an
+annotation was found that:
+
+- is not part of the list of ignored "documentation annotations";
+- was not imported through a use statement;
+- is not a fully qualified class that exists.
+
+You can disable this behavior for specific names if your docblocks do
+not follow strict requirements:
+
+.. code-block:: php
+
+    $reader = new \Doctrine\Common\Annotations\AnnotationReader();
+    AnnotationReader::addGlobalIgnoredName('foo');
+
+PHP Imports
+~~~~~~~~~~~
+
+By default the annotation reader parses the use-statement of a php file
+to gain access to the import rules and register them for the annotation
+processing. Only if you are using PHP Imports can you validate the
+correct usage of annotations and throw exceptions if you misspelled an
+annotation. This mechanism is enabled by default.
+
+To ease the upgrade path, we still allow you to disable this mechanism.
+Note however that we will remove this in future versions:
+
+.. code-block:: php
+
+    $reader = new \Doctrine\Common\Annotations\AnnotationReader();
+    $reader->setEnabledPhpImports(false);

+ 443 - 0
vendor/doctrine/annotations/docs/en/custom.rst

@@ -0,0 +1,443 @@
+Custom Annotation Classes
+=========================
+
+If you want to define your own annotations, you just have to group them
+in a namespace and register this namespace in the ``AnnotationRegistry``.
+Annotation classes have to contain a class-level docblock with the text
+``@Annotation``:
+
+.. code-block:: php
+
+    namespace MyCompany\Annotations;
+
+    /** @Annotation */
+    class Bar
+    {
+        // some code
+    }
+
+Inject annotation values
+------------------------
+
+The annotation parser checks if the annotation constructor has arguments,
+if so then it will pass the value array, otherwise it will try to inject
+values into public properties directly:
+
+
+.. code-block:: php
+
+    namespace MyCompany\Annotations;
+
+    /**
+     * @Annotation
+     *
+     * Some Annotation using a constructor
+     */
+    class Bar
+    {
+        private $foo;
+
+        public function __construct(array $values)
+        {
+            $this->foo = $values['foo'];
+        }
+    }
+
+    /**
+     * @Annotation
+     *
+     * Some Annotation without a constructor
+     */
+    class Foo
+    {
+        public $bar;
+    }
+
+Optional: Constructors with Named Parameters
+--------------------------------------------
+
+Starting with Annotations v1.11 a new annotation instantiation strategy
+is available that aims at compatibility of Annotation classes with the PHP 8
+attribute feature. You need to declare a constructor with regular parameter 
+names that match the named arguments in the annotation syntax.
+
+To enable this feature, you can tag your annotation class with 
+``@NamedArgumentConstructor`` (available from v1.12) or implement the
+``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` interface
+(available from v1.11 and deprecated as of v1.12).
+When using the ``@NamedArgumentConstructor`` tag, the first argument of the
+constructor is considered as the default one.
+
+
+Usage with the ``@NamedArgumentConstructor`` tag
+
+.. code-block:: php
+
+    namespace MyCompany\Annotations;
+
+    /** 
+     * @Annotation 
+     * @NamedArgumentConstructor
+     */
+    class Bar implements NamedArgumentConstructorAnnotation
+    {
+        private $foo;
+
+        public function __construct(string $foo)
+        {
+            $this->foo = $foo;
+        }
+    }
+
+    /** Usable with @Bar(foo="baz") */
+    /** Usable with @Bar("baz") */
+
+In combination with PHP 8's constructor property promotion feature
+you can simplify this to:
+
+.. code-block:: php
+
+    namespace MyCompany\Annotations;
+
+    /** 
+     * @Annotation 
+     * @NamedArgumentConstructor
+     */
+    class Bar implements NamedArgumentConstructorAnnotation
+    {
+        public function __construct(private string $foo) {}
+    }
+
+
+Usage with the 
+``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation``
+interface (v1.11, deprecated as of v1.12):
+.. code-block:: php
+
+    namespace MyCompany\Annotations;
+
+    use Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation;
+
+    /** @Annotation */
+    class Bar implements NamedArgumentConstructorAnnotation
+    {
+        private $foo;
+
+        public function __construct(private string $foo) {}
+    }
+
+    /** Usable with @Bar(foo="baz") */
+
+Annotation Target
+-----------------
+
+``@Target`` indicates the kinds of class elements to which an annotation
+type is applicable. Then you could define one or more targets:
+
+-  ``CLASS`` Allowed in class docblocks
+-  ``PROPERTY`` Allowed in property docblocks
+-  ``METHOD`` Allowed in the method docblocks
+-  ``FUNCTION`` Allowed in function dockblocks
+-  ``ALL`` Allowed in class, property, method and function docblocks
+-  ``ANNOTATION`` Allowed inside other annotations
+
+If the annotations is not allowed in the current context, an
+``AnnotationException`` is thrown.
+
+.. code-block:: php
+
+    namespace MyCompany\Annotations;
+
+    /**
+     * @Annotation
+     * @Target({"METHOD","PROPERTY"})
+     */
+    class Bar
+    {
+        // some code
+    }
+
+    /**
+     * @Annotation
+     * @Target("CLASS")
+     */
+    class Foo
+    {
+        // some code
+    }
+
+Attribute types
+---------------
+
+The annotation parser checks the given parameters using the phpdoc
+annotation ``@var``, The data type could be validated using the ``@var``
+annotation on the annotation properties or using the ``@Attributes`` and
+``@Attribute`` annotations.
+
+If the data type does not match you get an ``AnnotationException``
+
+.. code-block:: php
+
+    namespace MyCompany\Annotations;
+
+    /**
+     * @Annotation
+     * @Target({"METHOD","PROPERTY"})
+     */
+    class Bar
+    {
+        /** @var mixed */
+        public $mixed;
+
+        /** @var boolean */
+        public $boolean;
+
+        /** @var bool */
+        public $bool;
+
+        /** @var float */
+        public $float;
+
+        /** @var string */
+        public $string;
+
+        /** @var integer */
+        public $integer;
+
+        /** @var array */
+        public $array;
+
+        /** @var SomeAnnotationClass */
+        public $annotation;
+
+        /** @var array<integer> */
+        public $arrayOfIntegers;
+
+        /** @var array<SomeAnnotationClass> */
+        public $arrayOfAnnotations;
+    }
+
+    /**
+     * @Annotation
+     * @Target({"METHOD","PROPERTY"})
+     * @Attributes({
+     *   @Attribute("stringProperty", type = "string"),
+     *   @Attribute("annotProperty",  type = "SomeAnnotationClass"),
+     * })
+     */
+    class Foo
+    {
+        public function __construct(array $values)
+        {
+            $this->stringProperty = $values['stringProperty'];
+            $this->annotProperty = $values['annotProperty'];
+        }
+
+        // some code
+    }
+
+Annotation Required
+-------------------
+
+``@Required`` indicates that the field must be specified when the
+annotation is used. If it is not used you get an ``AnnotationException``
+stating that this value can not be null.
+
+Declaring a required field:
+
+.. code-block:: php
+
+    /**
+     * @Annotation
+     * @Target("ALL")
+     */
+    class Foo
+    {
+        /** @Required */
+        public $requiredField;
+    }
+
+Usage:
+
+.. code-block:: php
+
+    /** @Foo(requiredField="value") */
+    public $direction;                  // Valid
+
+     /** @Foo */
+    public $direction;                  // Required field missing, throws an AnnotationException
+
+
+Enumerated values
+-----------------
+
+- An annotation property marked with ``@Enum`` is a field that accepts a
+  fixed set of scalar values.
+- You should use ``@Enum`` fields any time you need to represent fixed
+  values.
+- The annotation parser checks the given value and throws an
+  ``AnnotationException`` if the value does not match.
+
+
+Declaring an enumerated property:
+
+.. code-block:: php
+
+    /**
+     * @Annotation
+     * @Target("ALL")
+     */
+    class Direction
+    {
+        /**
+         * @Enum({"NORTH", "SOUTH", "EAST", "WEST"})
+         */
+        public $value;
+    }
+
+Annotation usage:
+
+.. code-block:: php
+
+    /** @Direction("NORTH") */
+    public $direction;                  // Valid value
+
+     /** @Direction("NORTHEAST") */
+    public $direction;                  // Invalid value, throws an AnnotationException
+
+
+Constants
+---------
+
+The use of constants and class constants is available on the annotations
+parser.
+
+The following usages are allowed:
+
+.. code-block:: php
+
+    namespace MyCompany\Entity;
+
+    use MyCompany\Annotations\Foo;
+    use MyCompany\Annotations\Bar;
+    use MyCompany\Entity\SomeClass;
+
+    /**
+     * @Foo(PHP_EOL)
+     * @Bar(Bar::FOO)
+     * @Foo({SomeClass::FOO, SomeClass::BAR})
+     * @Bar({SomeClass::FOO_KEY = SomeClass::BAR_VALUE})
+     */
+    class User
+    {
+    }
+
+
+Be careful with constants and the cache !
+
+.. note::
+
+    The cached reader will not re-evaluate each time an annotation is
+    loaded from cache. When a constant is changed the cache must be
+    cleaned.
+
+
+Usage
+-----
+
+Using the library API is simple. Using the annotations described in the
+previous section, you can now annotate other classes with your
+annotations:
+
+.. code-block:: php
+
+    namespace MyCompany\Entity;
+
+    use MyCompany\Annotations\Foo;
+    use MyCompany\Annotations\Bar;
+
+    /**
+     * @Foo(bar="foo")
+     * @Bar(foo="bar")
+     */
+    class User
+    {
+    }
+
+Now we can write a script to get the annotations above:
+
+.. code-block:: php
+
+    $reflClass = new ReflectionClass('MyCompany\Entity\User');
+    $classAnnotations = $reader->getClassAnnotations($reflClass);
+
+    foreach ($classAnnotations AS $annot) {
+        if ($annot instanceof \MyCompany\Annotations\Foo) {
+            echo $annot->bar; // prints "foo";
+        } else if ($annot instanceof \MyCompany\Annotations\Bar) {
+            echo $annot->foo; // prints "bar";
+        }
+    }
+
+You have a complete API for retrieving annotation class instances from a
+class, property or method docblock:
+
+
+Reader API
+~~~~~~~~~~
+
+Access all annotations of a class
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: php
+
+    public function getClassAnnotations(\ReflectionClass $class);
+
+Access one annotation of a class
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: php
+
+    public function getClassAnnotation(\ReflectionClass $class, $annotationName);
+
+Access all annotations of a method
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: php
+
+    public function getMethodAnnotations(\ReflectionMethod $method);
+
+Access one annotation of a method
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: php
+
+    public function getMethodAnnotation(\ReflectionMethod $method, $annotationName);
+
+Access all annotations of a property
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: php
+
+    public function getPropertyAnnotations(\ReflectionProperty $property);
+
+Access one annotation of a property
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: php
+
+    public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName);
+
+Access all annotations of a function
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: php
+
+    public function getFunctionAnnotations(\ReflectionFunction $property);
+
+Access one annotation of a function
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: php
+
+    public function getFunctionAnnotation(\ReflectionFunction $property, $annotationName);

+ 110 - 0
vendor/doctrine/annotations/docs/en/index.rst

@@ -0,0 +1,110 @@
+Deprecation notice
+==================
+
+PHP 8 introduced `attributes
+<https://www.php.net/manual/en/language.attributes.overview.php>`_,
+which are a native replacement for annotations. As such, this library is
+considered feature complete, and should receive exclusively bugfixes and
+security fixes.
+
+Introduction
+============
+
+Doctrine Annotations allows to implement custom annotation
+functionality for PHP classes and functions.
+
+.. code-block:: php
+
+    class Foo
+    {
+        /**
+         * @MyAnnotation(myProperty="value")
+         */
+        private $bar;
+    }
+
+Annotations aren't implemented in PHP itself which is why this component
+offers a way to use the PHP doc-blocks as a place for the well known
+annotation syntax using the ``@`` char.
+
+Annotations in Doctrine are used for the ORM configuration to build the
+class mapping, but it can be used in other projects for other purposes
+too.
+
+Installation
+============
+
+You can install the Annotation component with composer:
+
+.. code-block::
+
+    $ composer require doctrine/annotations
+
+Create an annotation class
+==========================
+
+An annotation class is a representation of the later used annotation
+configuration in classes. The annotation class of the previous example
+looks like this:
+
+.. code-block:: php
+
+    /**
+     * @Annotation
+     */
+    final class MyAnnotation
+    {
+        public $myProperty;
+    }
+
+The annotation class is declared as an annotation by ``@Annotation``.
+
+:ref:`Read more about custom annotations. <custom>`
+
+Reading annotations
+===================
+
+The access to the annotations happens by reflection of the class or function
+containing them. There are multiple reader-classes implementing the
+``Doctrine\Common\Annotations\Reader`` interface, that can access the
+annotations of a class. A common one is
+``Doctrine\Common\Annotations\AnnotationReader``:
+
+.. code-block:: php
+
+    use Doctrine\Common\Annotations\AnnotationReader;
+    use Doctrine\Common\Annotations\AnnotationRegistry;
+
+    // Deprecated and will be removed in 2.0 but currently needed
+    AnnotationRegistry::registerLoader('class_exists');
+
+    $reflectionClass = new ReflectionClass(Foo::class);
+    $property = $reflectionClass->getProperty('bar');
+
+    $reader = new AnnotationReader();
+    $myAnnotation = $reader->getPropertyAnnotation(
+        $property,
+        MyAnnotation::class
+    );
+
+    echo $myAnnotation->myProperty; // result: "value"
+
+Note that ``AnnotationRegistry::registerLoader('class_exists')`` only works
+if you already have an autoloader configured (i.e. composer autoloader).
+Otherwise, :ref:`please take a look to the other annotation autoload mechanisms <annotations>`.
+
+A reader has multiple methods to access the annotations of a class or
+function.
+
+:ref:`Read more about handling annotations. <annotations>`
+
+IDE Support
+-----------
+
+Some IDEs already provide support for annotations:
+
+- Eclipse via the `Symfony2 Plugin <https://github.com/pulse00/Symfony-2-Eclipse-Plugin>`_
+- PhpStorm via the `PHP Annotations Plugin <https://plugins.jetbrains.com/plugin/7320-php-annotations>`_ or the `Symfony Plugin <https://plugins.jetbrains.com/plugin/7219-symfony-support>`_
+
+.. _Read more about handling annotations.: annotations
+.. _Read more about custom annotations.: custom

+ 6 - 0
vendor/doctrine/annotations/docs/en/sidebar.rst

@@ -0,0 +1,6 @@
+.. toctree::
+    :depth: 3
+
+    index
+    annotations
+    custom

+ 57 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php

@@ -0,0 +1,57 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use BadMethodCallException;
+
+use function sprintf;
+
+/**
+ * Annotations class.
+ */
+class Annotation
+{
+    /**
+     * Value property. Common among all derived classes.
+     *
+     * @var mixed
+     */
+    public $value;
+
+    /** @param array<string, mixed> $data Key-value for properties to be defined in this class. */
+    final public function __construct(array $data)
+    {
+        foreach ($data as $key => $value) {
+            $this->$key = $value;
+        }
+    }
+
+    /**
+     * Error handler for unknown property accessor in Annotation class.
+     *
+     * @param string $name Unknown property name.
+     *
+     * @throws BadMethodCallException
+     */
+    public function __get($name)
+    {
+        throw new BadMethodCallException(
+            sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class)
+        );
+    }
+
+    /**
+     * Error handler for unknown property mutator in Annotation class.
+     *
+     * @param string $name  Unknown property name.
+     * @param mixed  $value Property value.
+     *
+     * @throws BadMethodCallException
+     */
+    public function __set($name, $value)
+    {
+        throw new BadMethodCallException(
+            sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class)
+        );
+    }
+}

+ 21 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace Doctrine\Common\Annotations\Annotation;
+
+/**
+ * Annotation that can be used to signal to the parser
+ * to check the attribute type during the parsing process.
+ *
+ * @Annotation
+ */
+final class Attribute
+{
+    /** @var string */
+    public $name;
+
+    /** @var string */
+    public $type;
+
+    /** @var bool */
+    public $required = false;
+}

+ 15 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Doctrine\Common\Annotations\Annotation;
+
+/**
+ * Annotation that can be used to signal to the parser
+ * to check the types of all declared attributes during the parsing process.
+ *
+ * @Annotation
+ */
+final class Attributes
+{
+    /** @var array<Attribute> */
+    public $value;
+}

+ 69 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace Doctrine\Common\Annotations\Annotation;
+
+use InvalidArgumentException;
+
+use function get_class;
+use function gettype;
+use function in_array;
+use function is_object;
+use function is_scalar;
+use function sprintf;
+
+/**
+ * Annotation that can be used to signal to the parser
+ * to check the available values during the parsing process.
+ *
+ * @Annotation
+ * @Attributes({
+ *    @Attribute("value",   required = true,  type = "array"),
+ *    @Attribute("literal", required = false, type = "array")
+ * })
+ */
+final class Enum
+{
+    /** @phpstan-var list<scalar> */
+    public $value;
+
+    /**
+     * Literal target declaration.
+     *
+     * @var mixed[]
+     */
+    public $literal;
+
+    /**
+     * @phpstan-param array{literal?: mixed[], value: list<scalar>} $values
+     *
+     * @throws InvalidArgumentException
+     */
+    public function __construct(array $values)
+    {
+        if (! isset($values['literal'])) {
+            $values['literal'] = [];
+        }
+
+        foreach ($values['value'] as $var) {
+            if (! is_scalar($var)) {
+                throw new InvalidArgumentException(sprintf(
+                    '@Enum supports only scalar values "%s" given.',
+                    is_object($var) ? get_class($var) : gettype($var)
+                ));
+            }
+        }
+
+        foreach ($values['literal'] as $key => $var) {
+            if (! in_array($key, $values['value'])) {
+                throw new InvalidArgumentException(sprintf(
+                    'Undefined enumerator value "%s" for literal "%s".',
+                    $key,
+                    $var
+                ));
+            }
+        }
+
+        $this->value   = $values['value'];
+        $this->literal = $values['literal'];
+    }
+}

+ 43 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace Doctrine\Common\Annotations\Annotation;
+
+use RuntimeException;
+
+use function is_array;
+use function is_string;
+use function json_encode;
+use function sprintf;
+
+/**
+ * Annotation that can be used to signal to the parser to ignore specific
+ * annotations during the parsing process.
+ *
+ * @Annotation
+ */
+final class IgnoreAnnotation
+{
+    /** @phpstan-var list<string> */
+    public $names;
+
+    /**
+     * @phpstan-param array{value: string|list<string>} $values
+     *
+     * @throws RuntimeException
+     */
+    public function __construct(array $values)
+    {
+        if (is_string($values['value'])) {
+            $values['value'] = [$values['value']];
+        }
+
+        if (! is_array($values['value'])) {
+            throw new RuntimeException(sprintf(
+                '@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.',
+                json_encode($values['value'])
+            ));
+        }
+
+        $this->names = $values['value'];
+    }
+}

+ 13 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/NamedArgumentConstructor.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace Doctrine\Common\Annotations\Annotation;
+
+/**
+ * Annotation that indicates that the annotated class should be constructed with a named argument call.
+ *
+ * @Annotation
+ * @Target("CLASS")
+ */
+final class NamedArgumentConstructor
+{
+}

+ 13 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace Doctrine\Common\Annotations\Annotation;
+
+/**
+ * Annotation that can be used to signal to the parser
+ * to check if that attribute is required during the parsing process.
+ *
+ * @Annotation
+ */
+final class Required
+{
+}

+ 101 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace Doctrine\Common\Annotations\Annotation;
+
+use InvalidArgumentException;
+
+use function array_keys;
+use function get_class;
+use function gettype;
+use function implode;
+use function is_array;
+use function is_object;
+use function is_string;
+use function sprintf;
+
+/**
+ * Annotation that can be used to signal to the parser
+ * to check the annotation target during the parsing process.
+ *
+ * @Annotation
+ */
+final class Target
+{
+    public const TARGET_CLASS      = 1;
+    public const TARGET_METHOD     = 2;
+    public const TARGET_PROPERTY   = 4;
+    public const TARGET_ANNOTATION = 8;
+    public const TARGET_FUNCTION   = 16;
+    public const TARGET_ALL        = 31;
+
+    /** @var array<string, int> */
+    private static $map = [
+        'ALL'        => self::TARGET_ALL,
+        'CLASS'      => self::TARGET_CLASS,
+        'METHOD'     => self::TARGET_METHOD,
+        'PROPERTY'   => self::TARGET_PROPERTY,
+        'FUNCTION'   => self::TARGET_FUNCTION,
+        'ANNOTATION' => self::TARGET_ANNOTATION,
+    ];
+
+    /** @phpstan-var list<string> */
+    public $value;
+
+    /**
+     * Targets as bitmask.
+     *
+     * @var int
+     */
+    public $targets;
+
+    /**
+     * Literal target declaration.
+     *
+     * @var string
+     */
+    public $literal;
+
+    /**
+     * @phpstan-param array{value?: string|list<string>} $values
+     *
+     * @throws InvalidArgumentException
+     */
+    public function __construct(array $values)
+    {
+        if (! isset($values['value'])) {
+            $values['value'] = null;
+        }
+
+        if (is_string($values['value'])) {
+            $values['value'] = [$values['value']];
+        }
+
+        if (! is_array($values['value'])) {
+            throw new InvalidArgumentException(
+                sprintf(
+                    '@Target expects either a string value, or an array of strings, "%s" given.',
+                    is_object($values['value']) ? get_class($values['value']) : gettype($values['value'])
+                )
+            );
+        }
+
+        $bitmask = 0;
+        foreach ($values['value'] as $literal) {
+            if (! isset(self::$map[$literal])) {
+                throw new InvalidArgumentException(
+                    sprintf(
+                        'Invalid Target "%s". Available targets: [%s]',
+                        $literal,
+                        implode(', ', array_keys(self::$map))
+                    )
+                );
+            }
+
+            $bitmask |= self::$map[$literal];
+        }
+
+        $this->targets = $bitmask;
+        $this->value   = $values['value'];
+        $this->literal = implode(', ', $this->value);
+    }
+}

+ 167 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php

@@ -0,0 +1,167 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use Exception;
+use Throwable;
+
+use function get_class;
+use function gettype;
+use function implode;
+use function is_object;
+use function sprintf;
+
+/**
+ * Description of AnnotationException
+ */
+class AnnotationException extends Exception
+{
+    /**
+     * Creates a new AnnotationException describing a Syntax error.
+     *
+     * @param string $message Exception message
+     *
+     * @return AnnotationException
+     */
+    public static function syntaxError($message)
+    {
+        return new self('[Syntax Error] ' . $message);
+    }
+
+    /**
+     * Creates a new AnnotationException describing a Semantical error.
+     *
+     * @param string $message Exception message
+     *
+     * @return AnnotationException
+     */
+    public static function semanticalError($message)
+    {
+        return new self('[Semantical Error] ' . $message);
+    }
+
+    /**
+     * Creates a new AnnotationException describing an error which occurred during
+     * the creation of the annotation.
+     *
+     * @param string $message
+     *
+     * @return AnnotationException
+     */
+    public static function creationError($message, ?Throwable $previous = null)
+    {
+        return new self('[Creation Error] ' . $message, 0, $previous);
+    }
+
+    /**
+     * Creates a new AnnotationException describing a type error.
+     *
+     * @param string $message
+     *
+     * @return AnnotationException
+     */
+    public static function typeError($message)
+    {
+        return new self('[Type Error] ' . $message);
+    }
+
+    /**
+     * Creates a new AnnotationException describing a constant semantical error.
+     *
+     * @param string $identifier
+     * @param string $context
+     *
+     * @return AnnotationException
+     */
+    public static function semanticalErrorConstants($identifier, $context = null)
+    {
+        return self::semanticalError(sprintf(
+            "Couldn't find constant %s%s.",
+            $identifier,
+            $context ? ', ' . $context : ''
+        ));
+    }
+
+    /**
+     * Creates a new AnnotationException describing an type error of an attribute.
+     *
+     * @param string $attributeName
+     * @param string $annotationName
+     * @param string $context
+     * @param string $expected
+     * @param mixed  $actual
+     *
+     * @return AnnotationException
+     */
+    public static function attributeTypeError($attributeName, $annotationName, $context, $expected, $actual)
+    {
+        return self::typeError(sprintf(
+            'Attribute "%s" of @%s declared on %s expects %s, but got %s.',
+            $attributeName,
+            $annotationName,
+            $context,
+            $expected,
+            is_object($actual) ? 'an instance of ' . get_class($actual) : gettype($actual)
+        ));
+    }
+
+    /**
+     * Creates a new AnnotationException describing an required error of an attribute.
+     *
+     * @param string $attributeName
+     * @param string $annotationName
+     * @param string $context
+     * @param string $expected
+     *
+     * @return AnnotationException
+     */
+    public static function requiredError($attributeName, $annotationName, $context, $expected)
+    {
+        return self::typeError(sprintf(
+            'Attribute "%s" of @%s declared on %s expects %s. This value should not be null.',
+            $attributeName,
+            $annotationName,
+            $context,
+            $expected
+        ));
+    }
+
+    /**
+     * Creates a new AnnotationException describing a invalid enummerator.
+     *
+     * @param string $attributeName
+     * @param string $annotationName
+     * @param string $context
+     * @param mixed  $given
+     * @phpstan-param list<string>        $available
+     *
+     * @return AnnotationException
+     */
+    public static function enumeratorError($attributeName, $annotationName, $context, $available, $given)
+    {
+        return new self(sprintf(
+            '[Enum Error] Attribute "%s" of @%s declared on %s accepts only [%s], but got %s.',
+            $attributeName,
+            $annotationName,
+            $context,
+            implode(', ', $available),
+            is_object($given) ? get_class($given) : $given
+        ));
+    }
+
+    /** @return AnnotationException */
+    public static function optimizerPlusSaveComments()
+    {
+        return new self(
+            'You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1.'
+        );
+    }
+
+    /** @return AnnotationException */
+    public static function optimizerPlusLoadComments()
+    {
+        return new self(
+            'You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.'
+        );
+    }
+}

+ 389 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php

@@ -0,0 +1,389 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use Doctrine\Common\Annotations\Annotation\IgnoreAnnotation;
+use Doctrine\Common\Annotations\Annotation\Target;
+use ReflectionClass;
+use ReflectionFunction;
+use ReflectionMethod;
+use ReflectionProperty;
+
+use function array_merge;
+use function class_exists;
+use function extension_loaded;
+use function ini_get;
+
+/**
+ * A reader for docblock annotations.
+ */
+class AnnotationReader implements Reader
+{
+    /**
+     * Global map for imports.
+     *
+     * @var array<string, class-string>
+     */
+    private static $globalImports = [
+        'ignoreannotation' => Annotation\IgnoreAnnotation::class,
+    ];
+
+    /**
+     * A list with annotations that are not causing exceptions when not resolved to an annotation class.
+     *
+     * The names are case sensitive.
+     *
+     * @var array<string, true>
+     */
+    private static $globalIgnoredNames = ImplicitlyIgnoredAnnotationNames::LIST;
+
+    /**
+     * A list with annotations that are not causing exceptions when not resolved to an annotation class.
+     *
+     * The names are case sensitive.
+     *
+     * @var array<string, true>
+     */
+    private static $globalIgnoredNamespaces = [];
+
+    /**
+     * Add a new annotation to the globally ignored annotation names with regard to exception handling.
+     *
+     * @param string $name
+     */
+    public static function addGlobalIgnoredName($name)
+    {
+        self::$globalIgnoredNames[$name] = true;
+    }
+
+    /**
+     * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling.
+     *
+     * @param string $namespace
+     */
+    public static function addGlobalIgnoredNamespace($namespace)
+    {
+        self::$globalIgnoredNamespaces[$namespace] = true;
+    }
+
+    /**
+     * Annotations parser.
+     *
+     * @var DocParser
+     */
+    private $parser;
+
+    /**
+     * Annotations parser used to collect parsing metadata.
+     *
+     * @var DocParser
+     */
+    private $preParser;
+
+    /**
+     * PHP parser used to collect imports.
+     *
+     * @var PhpParser
+     */
+    private $phpParser;
+
+    /**
+     * In-memory cache mechanism to store imported annotations per class.
+     *
+     * @psalm-var array<'class'|'function', array<string, array<string, class-string>>>
+     */
+    private $imports = [];
+
+    /**
+     * In-memory cache mechanism to store ignored annotations per class.
+     *
+     * @psalm-var array<'class'|'function', array<string, array<string, true>>>
+     */
+    private $ignoredAnnotationNames = [];
+
+    /**
+     * Initializes a new AnnotationReader.
+     *
+     * @throws AnnotationException
+     */
+    public function __construct(?DocParser $parser = null)
+    {
+        if (
+            extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === '0' ||
+            ini_get('opcache.save_comments') === '0')
+        ) {
+            throw AnnotationException::optimizerPlusSaveComments();
+        }
+
+        if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') === 0) {
+            throw AnnotationException::optimizerPlusSaveComments();
+        }
+
+        // Make sure that the IgnoreAnnotation annotation is loaded
+        class_exists(IgnoreAnnotation::class);
+
+        $this->parser = $parser ?: new DocParser();
+
+        $this->preParser = new DocParser();
+
+        $this->preParser->setImports(self::$globalImports);
+        $this->preParser->setIgnoreNotImportedAnnotations(true);
+        $this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames);
+
+        $this->phpParser = new PhpParser();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getClassAnnotations(ReflectionClass $class)
+    {
+        $this->parser->setTarget(Target::TARGET_CLASS);
+        $this->parser->setImports($this->getImports($class));
+        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
+        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
+
+        return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getClassAnnotation(ReflectionClass $class, $annotationName)
+    {
+        $annotations = $this->getClassAnnotations($class);
+
+        foreach ($annotations as $annotation) {
+            if ($annotation instanceof $annotationName) {
+                return $annotation;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getPropertyAnnotations(ReflectionProperty $property)
+    {
+        $class   = $property->getDeclaringClass();
+        $context = 'property ' . $class->getName() . '::$' . $property->getName();
+
+        $this->parser->setTarget(Target::TARGET_PROPERTY);
+        $this->parser->setImports($this->getPropertyImports($property));
+        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
+        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
+
+        return $this->parser->parse($property->getDocComment(), $context);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
+    {
+        $annotations = $this->getPropertyAnnotations($property);
+
+        foreach ($annotations as $annotation) {
+            if ($annotation instanceof $annotationName) {
+                return $annotation;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMethodAnnotations(ReflectionMethod $method)
+    {
+        $class   = $method->getDeclaringClass();
+        $context = 'method ' . $class->getName() . '::' . $method->getName() . '()';
+
+        $this->parser->setTarget(Target::TARGET_METHOD);
+        $this->parser->setImports($this->getMethodImports($method));
+        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
+        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
+
+        return $this->parser->parse($method->getDocComment(), $context);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
+    {
+        $annotations = $this->getMethodAnnotations($method);
+
+        foreach ($annotations as $annotation) {
+            if ($annotation instanceof $annotationName) {
+                return $annotation;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Gets the annotations applied to a function.
+     *
+     * @phpstan-return list<object> An array of Annotations.
+     */
+    public function getFunctionAnnotations(ReflectionFunction $function): array
+    {
+        $context = 'function ' . $function->getName();
+
+        $this->parser->setTarget(Target::TARGET_FUNCTION);
+        $this->parser->setImports($this->getImports($function));
+        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($function));
+        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
+
+        return $this->parser->parse($function->getDocComment(), $context);
+    }
+
+    /**
+     * Gets a function annotation.
+     *
+     * @return object|null The Annotation or NULL, if the requested annotation does not exist.
+     */
+    public function getFunctionAnnotation(ReflectionFunction $function, string $annotationName)
+    {
+        $annotations = $this->getFunctionAnnotations($function);
+
+        foreach ($annotations as $annotation) {
+            if ($annotation instanceof $annotationName) {
+                return $annotation;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the ignored annotations for the given class or function.
+     *
+     * @param ReflectionClass|ReflectionFunction $reflection
+     *
+     * @return array<string, true>
+     */
+    private function getIgnoredAnnotationNames($reflection): array
+    {
+        $type = $reflection instanceof ReflectionClass ? 'class' : 'function';
+        $name = $reflection->getName();
+
+        if (isset($this->ignoredAnnotationNames[$type][$name])) {
+            return $this->ignoredAnnotationNames[$type][$name];
+        }
+
+        $this->collectParsingMetadata($reflection);
+
+        return $this->ignoredAnnotationNames[$type][$name];
+    }
+
+    /**
+     * Retrieves imports for a class or a function.
+     *
+     * @param ReflectionClass|ReflectionFunction $reflection
+     *
+     * @return array<string, class-string>
+     */
+    private function getImports($reflection): array
+    {
+        $type = $reflection instanceof ReflectionClass ? 'class' : 'function';
+        $name = $reflection->getName();
+
+        if (isset($this->imports[$type][$name])) {
+            return $this->imports[$type][$name];
+        }
+
+        $this->collectParsingMetadata($reflection);
+
+        return $this->imports[$type][$name];
+    }
+
+    /**
+     * Retrieves imports for methods.
+     *
+     * @return array<string, class-string>
+     */
+    private function getMethodImports(ReflectionMethod $method)
+    {
+        $class        = $method->getDeclaringClass();
+        $classImports = $this->getImports($class);
+
+        $traitImports = [];
+
+        foreach ($class->getTraits() as $trait) {
+            if (
+                ! $trait->hasMethod($method->getName())
+                || $trait->getFileName() !== $method->getFileName()
+            ) {
+                continue;
+            }
+
+            $traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait));
+        }
+
+        return array_merge($classImports, $traitImports);
+    }
+
+    /**
+     * Retrieves imports for properties.
+     *
+     * @return array<string, class-string>
+     */
+    private function getPropertyImports(ReflectionProperty $property)
+    {
+        $class        = $property->getDeclaringClass();
+        $classImports = $this->getImports($class);
+
+        $traitImports = [];
+
+        foreach ($class->getTraits() as $trait) {
+            if (! $trait->hasProperty($property->getName())) {
+                continue;
+            }
+
+            $traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait));
+        }
+
+        return array_merge($classImports, $traitImports);
+    }
+
+    /**
+     * Collects parsing metadata for a given class or function.
+     *
+     * @param ReflectionClass|ReflectionFunction $reflection
+     */
+    private function collectParsingMetadata($reflection): void
+    {
+        $type = $reflection instanceof ReflectionClass ? 'class' : 'function';
+        $name = $reflection->getName();
+
+        $ignoredAnnotationNames = self::$globalIgnoredNames;
+        $annotations            = $this->preParser->parse($reflection->getDocComment(), $type . ' ' . $name);
+
+        foreach ($annotations as $annotation) {
+            if (! ($annotation instanceof IgnoreAnnotation)) {
+                continue;
+            }
+
+            foreach ($annotation->names as $annot) {
+                $ignoredAnnotationNames[$annot] = true;
+            }
+        }
+
+        $this->imports[$type][$name] = array_merge(
+            self::$globalImports,
+            $this->phpParser->parseUseStatements($reflection),
+            [
+                '__NAMESPACE__' => $reflection->getNamespaceName(),
+                'self' => $name,
+            ]
+        );
+
+        $this->ignoredAnnotationNames[$type][$name] = $ignoredAnnotationNames;
+    }
+}

+ 190 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php

@@ -0,0 +1,190 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use function array_key_exists;
+use function array_merge;
+use function class_exists;
+use function in_array;
+use function is_file;
+use function str_replace;
+use function stream_resolve_include_path;
+use function strpos;
+
+use const DIRECTORY_SEPARATOR;
+
+final class AnnotationRegistry
+{
+    /**
+     * A map of namespaces to use for autoloading purposes based on a PSR-0 convention.
+     *
+     * Contains the namespace as key and an array of directories as value. If the value is NULL
+     * the include path is used for checking for the corresponding file.
+     *
+     * This autoloading mechanism does not utilize the PHP autoloading but implements autoloading on its own.
+     *
+     * @var string[][]|string[]|null[]
+     */
+    private static $autoloadNamespaces = [];
+
+    /**
+     * A map of autoloader callables.
+     *
+     * @var callable[]
+     */
+    private static $loaders = [];
+
+    /**
+     * An array of classes which cannot be found
+     *
+     * @var null[] indexed by class name
+     */
+    private static $failedToAutoload = [];
+
+    /**
+     * Whenever registerFile() was used. Disables use of standard autoloader.
+     *
+     * @var bool
+     */
+    private static $registerFileUsed = false;
+
+    public static function reset(): void
+    {
+        self::$autoloadNamespaces = [];
+        self::$loaders            = [];
+        self::$failedToAutoload   = [];
+        self::$registerFileUsed   = false;
+    }
+
+    /**
+     * Registers file.
+     *
+     * @deprecated This method is deprecated and will be removed in
+     *             doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
+     */
+    public static function registerFile(string $file): void
+    {
+        self::$registerFileUsed = true;
+
+        require_once $file;
+    }
+
+    /**
+     * Adds a namespace with one or many directories to look for files or null for the include path.
+     *
+     * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm.
+     *
+     * @deprecated This method is deprecated and will be removed in
+     *             doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
+     *
+     * @phpstan-param string|list<string>|null $dirs
+     */
+    public static function registerAutoloadNamespace(string $namespace, $dirs = null): void
+    {
+        self::$autoloadNamespaces[$namespace] = $dirs;
+    }
+
+    /**
+     * Registers multiple namespaces.
+     *
+     * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm.
+     *
+     * @deprecated This method is deprecated and will be removed in
+     *             doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
+     *
+     * @param string[][]|string[]|null[] $namespaces indexed by namespace name
+     */
+    public static function registerAutoloadNamespaces(array $namespaces): void
+    {
+        self::$autoloadNamespaces = array_merge(self::$autoloadNamespaces, $namespaces);
+    }
+
+    /**
+     * Registers an autoloading callable for annotations, much like spl_autoload_register().
+     *
+     * NOTE: These class loaders HAVE to be silent when a class was not found!
+     * IMPORTANT: Loaders have to return true if they loaded a class that could contain the searched annotation class.
+     *
+     * @deprecated This method is deprecated and will be removed in
+     *             doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
+     */
+    public static function registerLoader(callable $callable): void
+    {
+        // Reset our static cache now that we have a new loader to work with
+        self::$failedToAutoload = [];
+        self::$loaders[]        = $callable;
+    }
+
+    /**
+     * Registers an autoloading callable for annotations, if it is not already registered
+     *
+     * @deprecated This method is deprecated and will be removed in
+     *             doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
+     */
+    public static function registerUniqueLoader(callable $callable): void
+    {
+        if (in_array($callable, self::$loaders, true)) {
+            return;
+        }
+
+        self::registerLoader($callable);
+    }
+
+    /**
+     * Autoloads an annotation class silently.
+     */
+    public static function loadAnnotationClass(string $class): bool
+    {
+        if (class_exists($class, false)) {
+            return true;
+        }
+
+        if (array_key_exists($class, self::$failedToAutoload)) {
+            return false;
+        }
+
+        foreach (self::$autoloadNamespaces as $namespace => $dirs) {
+            if (strpos($class, $namespace) !== 0) {
+                continue;
+            }
+
+            $file = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
+
+            if ($dirs === null) {
+                $path = stream_resolve_include_path($file);
+                if ($path) {
+                    require $path;
+
+                    return true;
+                }
+            } else {
+                foreach ((array) $dirs as $dir) {
+                    if (is_file($dir . DIRECTORY_SEPARATOR . $file)) {
+                        require $dir . DIRECTORY_SEPARATOR . $file;
+
+                        return true;
+                    }
+                }
+            }
+        }
+
+        foreach (self::$loaders as $loader) {
+            if ($loader($class) === true) {
+                return true;
+            }
+        }
+
+        if (
+            self::$loaders === [] &&
+            self::$autoloadNamespaces === [] &&
+            self::$registerFileUsed === false &&
+            class_exists($class)
+        ) {
+            return true;
+        }
+
+        self::$failedToAutoload[$class] = null;
+
+        return false;
+    }
+}

+ 266 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php

@@ -0,0 +1,266 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use Doctrine\Common\Cache\Cache;
+use ReflectionClass;
+use ReflectionMethod;
+use ReflectionProperty;
+
+use function array_map;
+use function array_merge;
+use function assert;
+use function filemtime;
+use function max;
+use function time;
+
+/**
+ * A cache aware annotation reader.
+ *
+ * @deprecated the CachedReader is deprecated and will be removed
+ *             in version 2.0.0 of doctrine/annotations. Please use the
+ *             {@see \Doctrine\Common\Annotations\PsrCachedReader} instead.
+ */
+final class CachedReader implements Reader
+{
+    /** @var Reader */
+    private $delegate;
+
+    /** @var Cache */
+    private $cache;
+
+    /** @var bool */
+    private $debug;
+
+    /** @var array<string, array<object>> */
+    private $loadedAnnotations = [];
+
+    /** @var int[] */
+    private $loadedFilemtimes = [];
+
+    /** @param bool $debug */
+    public function __construct(Reader $reader, Cache $cache, $debug = false)
+    {
+        $this->delegate = $reader;
+        $this->cache    = $cache;
+        $this->debug    = (bool) $debug;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getClassAnnotations(ReflectionClass $class)
+    {
+        $cacheKey = $class->getName();
+
+        if (isset($this->loadedAnnotations[$cacheKey])) {
+            return $this->loadedAnnotations[$cacheKey];
+        }
+
+        $annots = $this->fetchFromCache($cacheKey, $class);
+        if ($annots === false) {
+            $annots = $this->delegate->getClassAnnotations($class);
+            $this->saveToCache($cacheKey, $annots);
+        }
+
+        return $this->loadedAnnotations[$cacheKey] = $annots;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getClassAnnotation(ReflectionClass $class, $annotationName)
+    {
+        foreach ($this->getClassAnnotations($class) as $annot) {
+            if ($annot instanceof $annotationName) {
+                return $annot;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getPropertyAnnotations(ReflectionProperty $property)
+    {
+        $class    = $property->getDeclaringClass();
+        $cacheKey = $class->getName() . '$' . $property->getName();
+
+        if (isset($this->loadedAnnotations[$cacheKey])) {
+            return $this->loadedAnnotations[$cacheKey];
+        }
+
+        $annots = $this->fetchFromCache($cacheKey, $class);
+        if ($annots === false) {
+            $annots = $this->delegate->getPropertyAnnotations($property);
+            $this->saveToCache($cacheKey, $annots);
+        }
+
+        return $this->loadedAnnotations[$cacheKey] = $annots;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
+    {
+        foreach ($this->getPropertyAnnotations($property) as $annot) {
+            if ($annot instanceof $annotationName) {
+                return $annot;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMethodAnnotations(ReflectionMethod $method)
+    {
+        $class    = $method->getDeclaringClass();
+        $cacheKey = $class->getName() . '#' . $method->getName();
+
+        if (isset($this->loadedAnnotations[$cacheKey])) {
+            return $this->loadedAnnotations[$cacheKey];
+        }
+
+        $annots = $this->fetchFromCache($cacheKey, $class);
+        if ($annots === false) {
+            $annots = $this->delegate->getMethodAnnotations($method);
+            $this->saveToCache($cacheKey, $annots);
+        }
+
+        return $this->loadedAnnotations[$cacheKey] = $annots;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
+    {
+        foreach ($this->getMethodAnnotations($method) as $annot) {
+            if ($annot instanceof $annotationName) {
+                return $annot;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Clears loaded annotations.
+     *
+     * @return void
+     */
+    public function clearLoadedAnnotations()
+    {
+        $this->loadedAnnotations = [];
+        $this->loadedFilemtimes  = [];
+    }
+
+    /**
+     * Fetches a value from the cache.
+     *
+     * @param string $cacheKey The cache key.
+     *
+     * @return mixed The cached value or false when the value is not in cache.
+     */
+    private function fetchFromCache($cacheKey, ReflectionClass $class)
+    {
+        $data = $this->cache->fetch($cacheKey);
+        if ($data !== false) {
+            if (! $this->debug || $this->isCacheFresh($cacheKey, $class)) {
+                return $data;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Saves a value to the cache.
+     *
+     * @param string $cacheKey The cache key.
+     * @param mixed  $value    The value.
+     *
+     * @return void
+     */
+    private function saveToCache($cacheKey, $value)
+    {
+        $this->cache->save($cacheKey, $value);
+        if (! $this->debug) {
+            return;
+        }
+
+        $this->cache->save('[C]' . $cacheKey, time());
+    }
+
+    /**
+     * Checks if the cache is fresh.
+     *
+     * @param string $cacheKey
+     *
+     * @return bool
+     */
+    private function isCacheFresh($cacheKey, ReflectionClass $class)
+    {
+        $lastModification = $this->getLastModification($class);
+        if ($lastModification === 0) {
+            return true;
+        }
+
+        return $this->cache->fetch('[C]' . $cacheKey) >= $lastModification;
+    }
+
+    /**
+     * Returns the time the class was last modified, testing traits and parents
+     */
+    private function getLastModification(ReflectionClass $class): int
+    {
+        $filename = $class->getFileName();
+
+        if (isset($this->loadedFilemtimes[$filename])) {
+            return $this->loadedFilemtimes[$filename];
+        }
+
+        $parent = $class->getParentClass();
+
+        $lastModification =  max(array_merge(
+            [$filename ? filemtime($filename) : 0],
+            array_map(function (ReflectionClass $reflectionTrait): int {
+                return $this->getTraitLastModificationTime($reflectionTrait);
+            }, $class->getTraits()),
+            array_map(function (ReflectionClass $class): int {
+                return $this->getLastModification($class);
+            }, $class->getInterfaces()),
+            $parent ? [$this->getLastModification($parent)] : []
+        ));
+
+        assert($lastModification !== false);
+
+        return $this->loadedFilemtimes[$filename] = $lastModification;
+    }
+
+    private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int
+    {
+        $fileName = $reflectionTrait->getFileName();
+
+        if (isset($this->loadedFilemtimes[$fileName])) {
+            return $this->loadedFilemtimes[$fileName];
+        }
+
+        $lastModificationTime = max(array_merge(
+            [$fileName ? filemtime($fileName) : 0],
+            array_map(function (ReflectionClass $reflectionTrait): int {
+                return $this->getTraitLastModificationTime($reflectionTrait);
+            }, $reflectionTrait->getTraits())
+        ));
+
+        assert($lastModificationTime !== false);
+
+        return $this->loadedFilemtimes[$fileName] = $lastModificationTime;
+    }
+}

+ 139 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php

@@ -0,0 +1,139 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use Doctrine\Common\Lexer\AbstractLexer;
+
+use function ctype_alpha;
+use function is_numeric;
+use function str_replace;
+use function stripos;
+use function strlen;
+use function strpos;
+use function strtolower;
+use function substr;
+
+/**
+ * Simple lexer for docblock annotations.
+ *
+ * @template-extends AbstractLexer<DocLexer::T_*>
+ */
+final class DocLexer extends AbstractLexer
+{
+    public const T_NONE    = 1;
+    public const T_INTEGER = 2;
+    public const T_STRING  = 3;
+    public const T_FLOAT   = 4;
+
+    // All tokens that are also identifiers should be >= 100
+    public const T_IDENTIFIER          = 100;
+    public const T_AT                  = 101;
+    public const T_CLOSE_CURLY_BRACES  = 102;
+    public const T_CLOSE_PARENTHESIS   = 103;
+    public const T_COMMA               = 104;
+    public const T_EQUALS              = 105;
+    public const T_FALSE               = 106;
+    public const T_NAMESPACE_SEPARATOR = 107;
+    public const T_OPEN_CURLY_BRACES   = 108;
+    public const T_OPEN_PARENTHESIS    = 109;
+    public const T_TRUE                = 110;
+    public const T_NULL                = 111;
+    public const T_COLON               = 112;
+    public const T_MINUS               = 113;
+
+    /** @var array<string, self::T*> */
+    protected $noCase = [
+        '@'  => self::T_AT,
+        ','  => self::T_COMMA,
+        '('  => self::T_OPEN_PARENTHESIS,
+        ')'  => self::T_CLOSE_PARENTHESIS,
+        '{'  => self::T_OPEN_CURLY_BRACES,
+        '}'  => self::T_CLOSE_CURLY_BRACES,
+        '='  => self::T_EQUALS,
+        ':'  => self::T_COLON,
+        '-'  => self::T_MINUS,
+        '\\' => self::T_NAMESPACE_SEPARATOR,
+    ];
+
+    /** @var array<string, self::T*> */
+    protected $withCase = [
+        'true'  => self::T_TRUE,
+        'false' => self::T_FALSE,
+        'null'  => self::T_NULL,
+    ];
+
+    /**
+     * Whether the next token starts immediately, or if there were
+     * non-captured symbols before that
+     */
+    public function nextTokenIsAdjacent(): bool
+    {
+        return $this->token === null
+            || ($this->lookahead !== null
+                && ($this->lookahead['position'] - $this->token['position']) === strlen($this->token['value']));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getCatchablePatterns()
+    {
+        return [
+            '[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*',
+            '(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?',
+            '"(?:""|[^"])*+"',
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getNonCatchablePatterns()
+    {
+        return ['\s+', '\*+', '(.)'];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getType(&$value)
+    {
+        $type = self::T_NONE;
+
+        if ($value[0] === '"') {
+            $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2));
+
+            return self::T_STRING;
+        }
+
+        if (isset($this->noCase[$value])) {
+            return $this->noCase[$value];
+        }
+
+        if ($value[0] === '_' || $value[0] === '\\' || ctype_alpha($value[0])) {
+            return self::T_IDENTIFIER;
+        }
+
+        $lowerValue = strtolower($value);
+
+        if (isset($this->withCase[$lowerValue])) {
+            return $this->withCase[$lowerValue];
+        }
+
+        // Checking numeric value
+        if (is_numeric($value)) {
+            return strpos($value, '.') !== false || stripos($value, 'e') !== false
+                ? self::T_FLOAT : self::T_INTEGER;
+        }
+
+        return $type;
+    }
+
+    /** @return array{value: int|string, type:self::T_*|null, position:int} */
+    public function peek(): array
+    {
+        $token = parent::peek();
+
+        return (array) $token;
+    }
+}

+ 1485 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php

@@ -0,0 +1,1485 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use Doctrine\Common\Annotations\Annotation\Attribute;
+use Doctrine\Common\Annotations\Annotation\Attributes;
+use Doctrine\Common\Annotations\Annotation\Enum;
+use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
+use Doctrine\Common\Annotations\Annotation\Target;
+use ReflectionClass;
+use ReflectionException;
+use ReflectionProperty;
+use RuntimeException;
+use stdClass;
+use Throwable;
+
+use function array_keys;
+use function array_map;
+use function array_pop;
+use function array_values;
+use function class_exists;
+use function constant;
+use function count;
+use function defined;
+use function explode;
+use function gettype;
+use function implode;
+use function in_array;
+use function interface_exists;
+use function is_array;
+use function is_object;
+use function json_encode;
+use function ltrim;
+use function preg_match;
+use function reset;
+use function rtrim;
+use function sprintf;
+use function stripos;
+use function strlen;
+use function strpos;
+use function strrpos;
+use function strtolower;
+use function substr;
+use function trim;
+
+use const PHP_VERSION_ID;
+
+/**
+ * A parser for docblock annotations.
+ *
+ * It is strongly discouraged to change the default annotation parsing process.
+ */
+final class DocParser
+{
+    /**
+     * An array of all valid tokens for a class name.
+     *
+     * @phpstan-var list<int>
+     */
+    private static $classIdentifiers = [
+        DocLexer::T_IDENTIFIER,
+        DocLexer::T_TRUE,
+        DocLexer::T_FALSE,
+        DocLexer::T_NULL,
+    ];
+
+    /**
+     * The lexer.
+     *
+     * @var DocLexer
+     */
+    private $lexer;
+
+    /**
+     * Current target context.
+     *
+     * @var int
+     */
+    private $target;
+
+    /**
+     * Doc parser used to collect annotation target.
+     *
+     * @var DocParser
+     */
+    private static $metadataParser;
+
+    /**
+     * Flag to control if the current annotation is nested or not.
+     *
+     * @var bool
+     */
+    private $isNestedAnnotation = false;
+
+    /**
+     * Hashmap containing all use-statements that are to be used when parsing
+     * the given doc block.
+     *
+     * @var array<string, class-string>
+     */
+    private $imports = [];
+
+    /**
+     * This hashmap is used internally to cache results of class_exists()
+     * look-ups.
+     *
+     * @var array<class-string, bool>
+     */
+    private $classExists = [];
+
+    /**
+     * Whether annotations that have not been imported should be ignored.
+     *
+     * @var bool
+     */
+    private $ignoreNotImportedAnnotations = false;
+
+    /**
+     * An array of default namespaces if operating in simple mode.
+     *
+     * @var string[]
+     */
+    private $namespaces = [];
+
+    /**
+     * A list with annotations that are not causing exceptions when not resolved to an annotation class.
+     *
+     * The names must be the raw names as used in the class, not the fully qualified
+     *
+     * @var bool[] indexed by annotation name
+     */
+    private $ignoredAnnotationNames = [];
+
+    /**
+     * A list with annotations in namespaced format
+     * that are not causing exceptions when not resolved to an annotation class.
+     *
+     * @var bool[] indexed by namespace name
+     */
+    private $ignoredAnnotationNamespaces = [];
+
+    /** @var string */
+    private $context = '';
+
+    /**
+     * Hash-map for caching annotation metadata.
+     *
+     * @var array<class-string, mixed[]>
+     */
+    private static $annotationMetadata = [
+        Annotation\Target::class => [
+            'is_annotation'                  => true,
+            'has_constructor'                => true,
+            'has_named_argument_constructor' => false,
+            'properties'                     => [],
+            'targets_literal'                => 'ANNOTATION_CLASS',
+            'targets'                        => Target::TARGET_CLASS,
+            'default_property'               => 'value',
+            'attribute_types'                => [
+                'value'  => [
+                    'required'   => false,
+                    'type'       => 'array',
+                    'array_type' => 'string',
+                    'value'      => 'array<string>',
+                ],
+            ],
+        ],
+        Annotation\Attribute::class => [
+            'is_annotation'                  => true,
+            'has_constructor'                => false,
+            'has_named_argument_constructor' => false,
+            'targets_literal'                => 'ANNOTATION_ANNOTATION',
+            'targets'                        => Target::TARGET_ANNOTATION,
+            'default_property'               => 'name',
+            'properties'                     => [
+                'name'      => 'name',
+                'type'      => 'type',
+                'required'  => 'required',
+            ],
+            'attribute_types'                => [
+                'value'  => [
+                    'required'  => true,
+                    'type'      => 'string',
+                    'value'     => 'string',
+                ],
+                'type'  => [
+                    'required'  => true,
+                    'type'      => 'string',
+                    'value'     => 'string',
+                ],
+                'required'  => [
+                    'required'  => false,
+                    'type'      => 'boolean',
+                    'value'     => 'boolean',
+                ],
+            ],
+        ],
+        Annotation\Attributes::class => [
+            'is_annotation'                  => true,
+            'has_constructor'                => false,
+            'has_named_argument_constructor' => false,
+            'targets_literal'                => 'ANNOTATION_CLASS',
+            'targets'                        => Target::TARGET_CLASS,
+            'default_property'               => 'value',
+            'properties'                     => ['value' => 'value'],
+            'attribute_types'                => [
+                'value' => [
+                    'type'      => 'array',
+                    'required'  => true,
+                    'array_type' => Annotation\Attribute::class,
+                    'value'     => 'array<' . Annotation\Attribute::class . '>',
+                ],
+            ],
+        ],
+        Annotation\Enum::class => [
+            'is_annotation'                  => true,
+            'has_constructor'                => true,
+            'has_named_argument_constructor' => false,
+            'targets_literal'                => 'ANNOTATION_PROPERTY',
+            'targets'                        => Target::TARGET_PROPERTY,
+            'default_property'               => 'value',
+            'properties'                     => ['value' => 'value'],
+            'attribute_types'                => [
+                'value' => [
+                    'type'      => 'array',
+                    'required'  => true,
+                ],
+                'literal' => [
+                    'type'      => 'array',
+                    'required'  => false,
+                ],
+            ],
+        ],
+        Annotation\NamedArgumentConstructor::class => [
+            'is_annotation'                  => true,
+            'has_constructor'                => false,
+            'has_named_argument_constructor' => false,
+            'targets_literal'                => 'ANNOTATION_CLASS',
+            'targets'                        => Target::TARGET_CLASS,
+            'default_property'               => null,
+            'properties'                     => [],
+            'attribute_types'                => [],
+        ],
+    ];
+
+    /**
+     * Hash-map for handle types declaration.
+     *
+     * @var array<string, string>
+     */
+    private static $typeMap = [
+        'float'     => 'double',
+        'bool'      => 'boolean',
+        // allow uppercase Boolean in honor of George Boole
+        'Boolean'   => 'boolean',
+        'int'       => 'integer',
+    ];
+
+    /**
+     * Constructs a new DocParser.
+     */
+    public function __construct()
+    {
+        $this->lexer = new DocLexer();
+    }
+
+    /**
+     * Sets the annotation names that are ignored during the parsing process.
+     *
+     * The names are supposed to be the raw names as used in the class, not the
+     * fully qualified class names.
+     *
+     * @param bool[] $names indexed by annotation name
+     *
+     * @return void
+     */
+    public function setIgnoredAnnotationNames(array $names)
+    {
+        $this->ignoredAnnotationNames = $names;
+    }
+
+    /**
+     * Sets the annotation namespaces that are ignored during the parsing process.
+     *
+     * @param bool[] $ignoredAnnotationNamespaces indexed by annotation namespace name
+     *
+     * @return void
+     */
+    public function setIgnoredAnnotationNamespaces($ignoredAnnotationNamespaces)
+    {
+        $this->ignoredAnnotationNamespaces = $ignoredAnnotationNamespaces;
+    }
+
+    /**
+     * Sets ignore on not-imported annotations.
+     *
+     * @param bool $bool
+     *
+     * @return void
+     */
+    public function setIgnoreNotImportedAnnotations($bool)
+    {
+        $this->ignoreNotImportedAnnotations = (bool) $bool;
+    }
+
+    /**
+     * Sets the default namespaces.
+     *
+     * @param string $namespace
+     *
+     * @return void
+     *
+     * @throws RuntimeException
+     */
+    public function addNamespace($namespace)
+    {
+        if ($this->imports) {
+            throw new RuntimeException('You must either use addNamespace(), or setImports(), but not both.');
+        }
+
+        $this->namespaces[] = $namespace;
+    }
+
+    /**
+     * Sets the imports.
+     *
+     * @param array<string, class-string> $imports
+     *
+     * @return void
+     *
+     * @throws RuntimeException
+     */
+    public function setImports(array $imports)
+    {
+        if ($this->namespaces) {
+            throw new RuntimeException('You must either use addNamespace(), or setImports(), but not both.');
+        }
+
+        $this->imports = $imports;
+    }
+
+    /**
+     * Sets current target context as bitmask.
+     *
+     * @param int $target
+     *
+     * @return void
+     */
+    public function setTarget($target)
+    {
+        $this->target = $target;
+    }
+
+    /**
+     * Parses the given docblock string for annotations.
+     *
+     * @param string $input   The docblock string to parse.
+     * @param string $context The parsing context.
+     *
+     * @phpstan-return list<object> Array of annotations. If no annotations are found, an empty array is returned.
+     *
+     * @throws AnnotationException
+     * @throws ReflectionException
+     */
+    public function parse($input, $context = '')
+    {
+        $pos = $this->findInitialTokenPosition($input);
+        if ($pos === null) {
+            return [];
+        }
+
+        $this->context = $context;
+
+        $this->lexer->setInput(trim(substr($input, $pos), '* /'));
+        $this->lexer->moveNext();
+
+        return $this->Annotations();
+    }
+
+    /**
+     * Finds the first valid annotation
+     *
+     * @param string $input The docblock string to parse
+     */
+    private function findInitialTokenPosition($input): ?int
+    {
+        $pos = 0;
+
+        // search for first valid annotation
+        while (($pos = strpos($input, '@', $pos)) !== false) {
+            $preceding = substr($input, $pos - 1, 1);
+
+            // if the @ is preceded by a space, a tab or * it is valid
+            if ($pos === 0 || $preceding === ' ' || $preceding === '*' || $preceding === "\t") {
+                return $pos;
+            }
+
+            $pos++;
+        }
+
+        return null;
+    }
+
+    /**
+     * Attempts to match the given token with the current lookahead token.
+     * If they match, updates the lookahead token; otherwise raises a syntax error.
+     *
+     * @param int $token Type of token.
+     *
+     * @return bool True if tokens match; false otherwise.
+     *
+     * @throws AnnotationException
+     */
+    private function match(int $token): bool
+    {
+        if (! $this->lexer->isNextToken($token)) {
+            throw $this->syntaxError($this->lexer->getLiteral($token));
+        }
+
+        return $this->lexer->moveNext();
+    }
+
+    /**
+     * Attempts to match the current lookahead token with any of the given tokens.
+     *
+     * If any of them matches, this method updates the lookahead token; otherwise
+     * a syntax error is raised.
+     *
+     * @phpstan-param list<mixed[]> $tokens
+     *
+     * @throws AnnotationException
+     */
+    private function matchAny(array $tokens): bool
+    {
+        if (! $this->lexer->isNextTokenAny($tokens)) {
+            throw $this->syntaxError(implode(' or ', array_map([$this->lexer, 'getLiteral'], $tokens)));
+        }
+
+        return $this->lexer->moveNext();
+    }
+
+    /**
+     * Generates a new syntax error.
+     *
+     * @param string       $expected Expected string.
+     * @param mixed[]|null $token    Optional token.
+     */
+    private function syntaxError(string $expected, ?array $token = null): AnnotationException
+    {
+        if ($token === null) {
+            $token = $this->lexer->lookahead;
+        }
+
+        $message  = sprintf('Expected %s, got ', $expected);
+        $message .= $this->lexer->lookahead === null
+            ? 'end of string'
+            : sprintf("'%s' at position %s", $token['value'], $token['position']);
+
+        if (strlen($this->context)) {
+            $message .= ' in ' . $this->context;
+        }
+
+        $message .= '.';
+
+        return AnnotationException::syntaxError($message);
+    }
+
+    /**
+     * Attempts to check if a class exists or not. This never goes through the PHP autoloading mechanism
+     * but uses the {@link AnnotationRegistry} to load classes.
+     *
+     * @param class-string $fqcn
+     */
+    private function classExists(string $fqcn): bool
+    {
+        if (isset($this->classExists[$fqcn])) {
+            return $this->classExists[$fqcn];
+        }
+
+        // first check if the class already exists, maybe loaded through another AnnotationReader
+        if (class_exists($fqcn, false)) {
+            return $this->classExists[$fqcn] = true;
+        }
+
+        // final check, does this class exist?
+        return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn);
+    }
+
+    /**
+     * Collects parsing metadata for a given annotation class
+     *
+     * @param class-string $name The annotation name
+     *
+     * @throws AnnotationException
+     * @throws ReflectionException
+     */
+    private function collectAnnotationMetadata(string $name): void
+    {
+        if (self::$metadataParser === null) {
+            self::$metadataParser = new self();
+
+            self::$metadataParser->setIgnoreNotImportedAnnotations(true);
+            self::$metadataParser->setIgnoredAnnotationNames($this->ignoredAnnotationNames);
+            self::$metadataParser->setImports([
+                'enum'                     => Enum::class,
+                'target'                   => Target::class,
+                'attribute'                => Attribute::class,
+                'attributes'               => Attributes::class,
+                'namedargumentconstructor' => NamedArgumentConstructor::class,
+            ]);
+
+            // Make sure that annotations from metadata are loaded
+            class_exists(Enum::class);
+            class_exists(Target::class);
+            class_exists(Attribute::class);
+            class_exists(Attributes::class);
+            class_exists(NamedArgumentConstructor::class);
+        }
+
+        $class      = new ReflectionClass($name);
+        $docComment = $class->getDocComment();
+
+        // Sets default values for annotation metadata
+        $constructor = $class->getConstructor();
+        $metadata    = [
+            'default_property' => null,
+            'has_constructor'  => $constructor !== null && $constructor->getNumberOfParameters() > 0,
+            'constructor_args' => [],
+            'properties'       => [],
+            'property_types'   => [],
+            'attribute_types'  => [],
+            'targets_literal'  => null,
+            'targets'          => Target::TARGET_ALL,
+            'is_annotation'    => strpos($docComment, '@Annotation') !== false,
+        ];
+
+        $metadata['has_named_argument_constructor'] = $metadata['has_constructor']
+            && $class->implementsInterface(NamedArgumentConstructorAnnotation::class);
+
+        // verify that the class is really meant to be an annotation
+        if ($metadata['is_annotation']) {
+            self::$metadataParser->setTarget(Target::TARGET_CLASS);
+
+            foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) {
+                if ($annotation instanceof Target) {
+                    $metadata['targets']         = $annotation->targets;
+                    $metadata['targets_literal'] = $annotation->literal;
+
+                    continue;
+                }
+
+                if ($annotation instanceof NamedArgumentConstructor) {
+                    $metadata['has_named_argument_constructor'] = $metadata['has_constructor'];
+                    if ($metadata['has_named_argument_constructor']) {
+                        // choose the first argument as the default property
+                        $metadata['default_property'] = $constructor->getParameters()[0]->getName();
+                    }
+                }
+
+                if (! ($annotation instanceof Attributes)) {
+                    continue;
+                }
+
+                foreach ($annotation->value as $attribute) {
+                    $this->collectAttributeTypeMetadata($metadata, $attribute);
+                }
+            }
+
+            // if not has a constructor will inject values into public properties
+            if ($metadata['has_constructor'] === false) {
+                // collect all public properties
+                foreach ($class->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
+                    $metadata['properties'][$property->name] = $property->name;
+
+                    $propertyComment = $property->getDocComment();
+                    if ($propertyComment === false) {
+                        continue;
+                    }
+
+                    $attribute = new Attribute();
+
+                    $attribute->required = (strpos($propertyComment, '@Required') !== false);
+                    $attribute->name     = $property->name;
+                    $attribute->type     = (strpos($propertyComment, '@var') !== false &&
+                        preg_match('/@var\s+([^\s]+)/', $propertyComment, $matches))
+                        ? $matches[1]
+                        : 'mixed';
+
+                    $this->collectAttributeTypeMetadata($metadata, $attribute);
+
+                    // checks if the property has @Enum
+                    if (strpos($propertyComment, '@Enum') === false) {
+                        continue;
+                    }
+
+                    $context = 'property ' . $class->name . '::$' . $property->name;
+
+                    self::$metadataParser->setTarget(Target::TARGET_PROPERTY);
+
+                    foreach (self::$metadataParser->parse($propertyComment, $context) as $annotation) {
+                        if (! $annotation instanceof Enum) {
+                            continue;
+                        }
+
+                        $metadata['enum'][$property->name]['value']   = $annotation->value;
+                        $metadata['enum'][$property->name]['literal'] = (! empty($annotation->literal))
+                            ? $annotation->literal
+                            : $annotation->value;
+                    }
+                }
+
+                // choose the first property as default property
+                $metadata['default_property'] = reset($metadata['properties']);
+            } elseif ($metadata['has_named_argument_constructor']) {
+                foreach ($constructor->getParameters() as $parameter) {
+                    $metadata['constructor_args'][$parameter->getName()] = [
+                        'position' => $parameter->getPosition(),
+                        'default' => $parameter->isOptional() ? $parameter->getDefaultValue() : null,
+                    ];
+                }
+            }
+        }
+
+        self::$annotationMetadata[$name] = $metadata;
+    }
+
+    /**
+     * Collects parsing metadata for a given attribute.
+     *
+     * @param mixed[] $metadata
+     */
+    private function collectAttributeTypeMetadata(array &$metadata, Attribute $attribute): void
+    {
+        // handle internal type declaration
+        $type = self::$typeMap[$attribute->type] ?? $attribute->type;
+
+        // handle the case if the property type is mixed
+        if ($type === 'mixed') {
+            return;
+        }
+
+        // Evaluate type
+        $pos = strpos($type, '<');
+        if ($pos !== false) {
+            // Checks if the property has array<type>
+            $arrayType = substr($type, $pos + 1, -1);
+            $type      = 'array';
+
+            if (isset(self::$typeMap[$arrayType])) {
+                $arrayType = self::$typeMap[$arrayType];
+            }
+
+            $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType;
+        } else {
+            // Checks if the property has type[]
+            $pos = strrpos($type, '[');
+            if ($pos !== false) {
+                $arrayType = substr($type, 0, $pos);
+                $type      = 'array';
+
+                if (isset(self::$typeMap[$arrayType])) {
+                    $arrayType = self::$typeMap[$arrayType];
+                }
+
+                $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType;
+            }
+        }
+
+        $metadata['attribute_types'][$attribute->name]['type']     = $type;
+        $metadata['attribute_types'][$attribute->name]['value']    = $attribute->type;
+        $metadata['attribute_types'][$attribute->name]['required'] = $attribute->required;
+    }
+
+    /**
+     * Annotations ::= Annotation {[ "*" ]* [Annotation]}*
+     *
+     * @phpstan-return list<object>
+     *
+     * @throws AnnotationException
+     * @throws ReflectionException
+     */
+    private function Annotations(): array
+    {
+        $annotations = [];
+
+        while ($this->lexer->lookahead !== null) {
+            if ($this->lexer->lookahead['type'] !== DocLexer::T_AT) {
+                $this->lexer->moveNext();
+                continue;
+            }
+
+            // make sure the @ is preceded by non-catchable pattern
+            if (
+                $this->lexer->token !== null &&
+                $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen(
+                    $this->lexer->token['value']
+                )
+            ) {
+                $this->lexer->moveNext();
+                continue;
+            }
+
+            // make sure the @ is followed by either a namespace separator, or
+            // an identifier token
+            $peek = $this->lexer->glimpse();
+            if (
+                ($peek === null)
+                || ($peek['type'] !== DocLexer::T_NAMESPACE_SEPARATOR && ! in_array(
+                    $peek['type'],
+                    self::$classIdentifiers,
+                    true
+                ))
+                || $peek['position'] !== $this->lexer->lookahead['position'] + 1
+            ) {
+                $this->lexer->moveNext();
+                continue;
+            }
+
+            $this->isNestedAnnotation = false;
+            $annot                    = $this->Annotation();
+            if ($annot === false) {
+                continue;
+            }
+
+            $annotations[] = $annot;
+        }
+
+        return $annotations;
+    }
+
+    /**
+     * Annotation     ::= "@" AnnotationName MethodCall
+     * AnnotationName ::= QualifiedName | SimpleName
+     * QualifiedName  ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName
+     * NameSpacePart  ::= identifier | null | false | true
+     * SimpleName     ::= identifier | null | false | true
+     *
+     * @return object|false False if it is not a valid annotation.
+     *
+     * @throws AnnotationException
+     * @throws ReflectionException
+     */
+    private function Annotation()
+    {
+        $this->match(DocLexer::T_AT);
+
+        // check if we have an annotation
+        $name = $this->Identifier();
+
+        if (
+            $this->lexer->isNextToken(DocLexer::T_MINUS)
+            && $this->lexer->nextTokenIsAdjacent()
+        ) {
+            // Annotations with dashes, such as "@foo-" or "@foo-bar", are to be discarded
+            return false;
+        }
+
+        // only process names which are not fully qualified, yet
+        // fully qualified names must start with a \
+        $originalName = $name;
+
+        if ($name[0] !== '\\') {
+            $pos          = strpos($name, '\\');
+            $alias        = ($pos === false) ? $name : substr($name, 0, $pos);
+            $found        = false;
+            $loweredAlias = strtolower($alias);
+
+            if ($this->namespaces) {
+                foreach ($this->namespaces as $namespace) {
+                    if ($this->classExists($namespace . '\\' . $name)) {
+                        $name  = $namespace . '\\' . $name;
+                        $found = true;
+                        break;
+                    }
+                }
+            } elseif (isset($this->imports[$loweredAlias])) {
+                $namespace = ltrim($this->imports[$loweredAlias], '\\');
+                $name      = ($pos !== false)
+                    ? $namespace . substr($name, $pos)
+                    : $namespace;
+                $found     = $this->classExists($name);
+            } elseif (
+                ! isset($this->ignoredAnnotationNames[$name])
+                && isset($this->imports['__NAMESPACE__'])
+                && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name)
+            ) {
+                $name  = $this->imports['__NAMESPACE__'] . '\\' . $name;
+                $found = true;
+            } elseif (! isset($this->ignoredAnnotationNames[$name]) && $this->classExists($name)) {
+                $found = true;
+            }
+
+            if (! $found) {
+                if ($this->isIgnoredAnnotation($name)) {
+                    return false;
+                }
+
+                throw AnnotationException::semanticalError(sprintf(
+                    <<<'EXCEPTION'
+The annotation "@%s" in %s was never imported. Did you maybe forget to add a "use" statement for this annotation?
+EXCEPTION
+                    ,
+                    $name,
+                    $this->context
+                ));
+            }
+        }
+
+        $name = ltrim($name, '\\');
+
+        if (! $this->classExists($name)) {
+            throw AnnotationException::semanticalError(sprintf(
+                'The annotation "@%s" in %s does not exist, or could not be auto-loaded.',
+                $name,
+                $this->context
+            ));
+        }
+
+        // at this point, $name contains the fully qualified class name of the
+        // annotation, and it is also guaranteed that this class exists, and
+        // that it is loaded
+
+        // collects the metadata annotation only if there is not yet
+        if (! isset(self::$annotationMetadata[$name])) {
+            $this->collectAnnotationMetadata($name);
+        }
+
+        // verify that the class is really meant to be an annotation and not just any ordinary class
+        if (self::$annotationMetadata[$name]['is_annotation'] === false) {
+            if ($this->isIgnoredAnnotation($originalName) || $this->isIgnoredAnnotation($name)) {
+                return false;
+            }
+
+            throw AnnotationException::semanticalError(sprintf(
+                <<<'EXCEPTION'
+The class "%s" is not annotated with @Annotation.
+Are you sure this class can be used as annotation?
+If so, then you need to add @Annotation to the _class_ doc comment of "%s".
+If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s.
+EXCEPTION
+                ,
+                $name,
+                $name,
+                $originalName,
+                $this->context
+            ));
+        }
+
+        //if target is nested annotation
+        $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target;
+
+        // Next will be nested
+        $this->isNestedAnnotation = true;
+
+        //if annotation does not support current target
+        if ((self::$annotationMetadata[$name]['targets'] & $target) === 0 && $target) {
+            throw AnnotationException::semanticalError(
+                sprintf(
+                    <<<'EXCEPTION'
+Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s.
+EXCEPTION
+                    ,
+                    $originalName,
+                    $this->context,
+                    self::$annotationMetadata[$name]['targets_literal']
+                )
+            );
+        }
+
+        $arguments = $this->MethodCall();
+        $values    = $this->resolvePositionalValues($arguments, $name);
+
+        if (isset(self::$annotationMetadata[$name]['enum'])) {
+            // checks all declared attributes
+            foreach (self::$annotationMetadata[$name]['enum'] as $property => $enum) {
+                // checks if the attribute is a valid enumerator
+                if (isset($values[$property]) && ! in_array($values[$property], $enum['value'])) {
+                    throw AnnotationException::enumeratorError(
+                        $property,
+                        $name,
+                        $this->context,
+                        $enum['literal'],
+                        $values[$property]
+                    );
+                }
+            }
+        }
+
+        // checks all declared attributes
+        foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) {
+            if (
+                $property === self::$annotationMetadata[$name]['default_property']
+                && ! isset($values[$property]) && isset($values['value'])
+            ) {
+                $property = 'value';
+            }
+
+            // handle a not given attribute or null value
+            if (! isset($values[$property])) {
+                if ($type['required']) {
+                    throw AnnotationException::requiredError(
+                        $property,
+                        $originalName,
+                        $this->context,
+                        'a(n) ' . $type['value']
+                    );
+                }
+
+                continue;
+            }
+
+            if ($type['type'] === 'array') {
+                // handle the case of a single value
+                if (! is_array($values[$property])) {
+                    $values[$property] = [$values[$property]];
+                }
+
+                // checks if the attribute has array type declaration, such as "array<string>"
+                if (isset($type['array_type'])) {
+                    foreach ($values[$property] as $item) {
+                        if (gettype($item) !== $type['array_type'] && ! $item instanceof $type['array_type']) {
+                            throw AnnotationException::attributeTypeError(
+                                $property,
+                                $originalName,
+                                $this->context,
+                                'either a(n) ' . $type['array_type'] . ', or an array of ' . $type['array_type'] . 's',
+                                $item
+                            );
+                        }
+                    }
+                }
+            } elseif (gettype($values[$property]) !== $type['type'] && ! $values[$property] instanceof $type['type']) {
+                throw AnnotationException::attributeTypeError(
+                    $property,
+                    $originalName,
+                    $this->context,
+                    'a(n) ' . $type['value'],
+                    $values[$property]
+                );
+            }
+        }
+
+        if (self::$annotationMetadata[$name]['has_named_argument_constructor']) {
+            if (PHP_VERSION_ID >= 80000) {
+                return $this->instantiateAnnotiation($originalName, $this->context, $name, $values);
+            }
+
+            $positionalValues = [];
+            foreach (self::$annotationMetadata[$name]['constructor_args'] as $property => $parameter) {
+                $positionalValues[$parameter['position']] = $parameter['default'];
+            }
+
+            foreach ($values as $property => $value) {
+                if (! isset(self::$annotationMetadata[$name]['constructor_args'][$property])) {
+                    throw AnnotationException::creationError(sprintf(
+                        <<<'EXCEPTION'
+The annotation @%s declared on %s does not have a property named "%s"
+that can be set through its named arguments constructor.
+Available named arguments: %s
+EXCEPTION
+                        ,
+                        $originalName,
+                        $this->context,
+                        $property,
+                        implode(', ', array_keys(self::$annotationMetadata[$name]['constructor_args']))
+                    ));
+                }
+
+                $positionalValues[self::$annotationMetadata[$name]['constructor_args'][$property]['position']] = $value;
+            }
+
+            return $this->instantiateAnnotiation($originalName, $this->context, $name, $positionalValues);
+        }
+
+        // check if the annotation expects values via the constructor,
+        // or directly injected into public properties
+        if (self::$annotationMetadata[$name]['has_constructor'] === true) {
+            return $this->instantiateAnnotiation($originalName, $this->context, $name, [$values]);
+        }
+
+        $instance = $this->instantiateAnnotiation($originalName, $this->context, $name, []);
+
+        foreach ($values as $property => $value) {
+            if (! isset(self::$annotationMetadata[$name]['properties'][$property])) {
+                if ($property !== 'value') {
+                    throw AnnotationException::creationError(sprintf(
+                        <<<'EXCEPTION'
+The annotation @%s declared on %s does not have a property named "%s".
+Available properties: %s
+EXCEPTION
+                        ,
+                        $originalName,
+                        $this->context,
+                        $property,
+                        implode(', ', self::$annotationMetadata[$name]['properties'])
+                    ));
+                }
+
+                // handle the case if the property has no annotations
+                $property = self::$annotationMetadata[$name]['default_property'];
+                if (! $property) {
+                    throw AnnotationException::creationError(sprintf(
+                        'The annotation @%s declared on %s does not accept any values, but got %s.',
+                        $originalName,
+                        $this->context,
+                        json_encode($values)
+                    ));
+                }
+            }
+
+            $instance->{$property} = $value;
+        }
+
+        return $instance;
+    }
+
+    /**
+     * MethodCall ::= ["(" [Values] ")"]
+     *
+     * @return mixed[]
+     *
+     * @throws AnnotationException
+     * @throws ReflectionException
+     */
+    private function MethodCall(): array
+    {
+        $values = [];
+
+        if (! $this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) {
+            return $values;
+        }
+
+        $this->match(DocLexer::T_OPEN_PARENTHESIS);
+
+        if (! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) {
+            $values = $this->Values();
+        }
+
+        $this->match(DocLexer::T_CLOSE_PARENTHESIS);
+
+        return $values;
+    }
+
+    /**
+     * Values ::= Array | Value {"," Value}* [","]
+     *
+     * @return mixed[]
+     *
+     * @throws AnnotationException
+     * @throws ReflectionException
+     */
+    private function Values(): array
+    {
+        $values = [$this->Value()];
+
+        while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
+            $this->match(DocLexer::T_COMMA);
+
+            if ($this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) {
+                break;
+            }
+
+            $token = $this->lexer->lookahead;
+            $value = $this->Value();
+
+            $values[] = $value;
+        }
+
+        $namedArguments      = [];
+        $positionalArguments = [];
+        foreach ($values as $k => $value) {
+            if (is_object($value) && $value instanceof stdClass) {
+                $namedArguments[$value->name] = $value->value;
+            } else {
+                $positionalArguments[$k] = $value;
+            }
+        }
+
+        return ['named_arguments' => $namedArguments, 'positional_arguments' => $positionalArguments];
+    }
+
+    /**
+     * Constant ::= integer | string | float | boolean
+     *
+     * @return mixed
+     *
+     * @throws AnnotationException
+     */
+    private function Constant()
+    {
+        $identifier = $this->Identifier();
+
+        if (! defined($identifier) && strpos($identifier, '::') !== false && $identifier[0] !== '\\') {
+            [$className, $const] = explode('::', $identifier);
+
+            $pos          = strpos($className, '\\');
+            $alias        = ($pos === false) ? $className : substr($className, 0, $pos);
+            $found        = false;
+            $loweredAlias = strtolower($alias);
+
+            switch (true) {
+                case ! empty($this->namespaces):
+                    foreach ($this->namespaces as $ns) {
+                        if (class_exists($ns . '\\' . $className) || interface_exists($ns . '\\' . $className)) {
+                            $className = $ns . '\\' . $className;
+                            $found     = true;
+                            break;
+                        }
+                    }
+
+                    break;
+
+                case isset($this->imports[$loweredAlias]):
+                    $found     = true;
+                    $className = ($pos !== false)
+                        ? $this->imports[$loweredAlias] . substr($className, $pos)
+                        : $this->imports[$loweredAlias];
+                    break;
+
+                default:
+                    if (isset($this->imports['__NAMESPACE__'])) {
+                        $ns = $this->imports['__NAMESPACE__'];
+
+                        if (class_exists($ns . '\\' . $className) || interface_exists($ns . '\\' . $className)) {
+                            $className = $ns . '\\' . $className;
+                            $found     = true;
+                        }
+                    }
+
+                    break;
+            }
+
+            if ($found) {
+                $identifier = $className . '::' . $const;
+            }
+        }
+
+        /**
+         * Checks if identifier ends with ::class and remove the leading backslash if it exists.
+         */
+        if (
+            $this->identifierEndsWithClassConstant($identifier) &&
+            ! $this->identifierStartsWithBackslash($identifier)
+        ) {
+            return substr($identifier, 0, $this->getClassConstantPositionInIdentifier($identifier));
+        }
+
+        if ($this->identifierEndsWithClassConstant($identifier) && $this->identifierStartsWithBackslash($identifier)) {
+            return substr($identifier, 1, $this->getClassConstantPositionInIdentifier($identifier) - 1);
+        }
+
+        if (! defined($identifier)) {
+            throw AnnotationException::semanticalErrorConstants($identifier, $this->context);
+        }
+
+        return constant($identifier);
+    }
+
+    private function identifierStartsWithBackslash(string $identifier): bool
+    {
+        return $identifier[0] === '\\';
+    }
+
+    private function identifierEndsWithClassConstant(string $identifier): bool
+    {
+        return $this->getClassConstantPositionInIdentifier($identifier) === strlen($identifier) - strlen('::class');
+    }
+
+    /** @return int|false */
+    private function getClassConstantPositionInIdentifier(string $identifier)
+    {
+        return stripos($identifier, '::class');
+    }
+
+    /**
+     * Identifier ::= string
+     *
+     * @throws AnnotationException
+     */
+    private function Identifier(): string
+    {
+        // check if we have an annotation
+        if (! $this->lexer->isNextTokenAny(self::$classIdentifiers)) {
+            throw $this->syntaxError('namespace separator or identifier');
+        }
+
+        $this->lexer->moveNext();
+
+        $className = $this->lexer->token['value'];
+
+        while (
+            $this->lexer->lookahead !== null &&
+            $this->lexer->lookahead['position'] === ($this->lexer->token['position'] +
+            strlen($this->lexer->token['value'])) &&
+            $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)
+        ) {
+            $this->match(DocLexer::T_NAMESPACE_SEPARATOR);
+            $this->matchAny(self::$classIdentifiers);
+
+            $className .= '\\' . $this->lexer->token['value'];
+        }
+
+        return $className;
+    }
+
+    /**
+     * Value ::= PlainValue | FieldAssignment
+     *
+     * @return mixed
+     *
+     * @throws AnnotationException
+     * @throws ReflectionException
+     */
+    private function Value()
+    {
+        $peek = $this->lexer->glimpse();
+
+        if ($peek['type'] === DocLexer::T_EQUALS) {
+            return $this->FieldAssignment();
+        }
+
+        return $this->PlainValue();
+    }
+
+    /**
+     * PlainValue ::= integer | string | float | boolean | Array | Annotation
+     *
+     * @return mixed
+     *
+     * @throws AnnotationException
+     * @throws ReflectionException
+     */
+    private function PlainValue()
+    {
+        if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) {
+            return $this->Arrayx();
+        }
+
+        if ($this->lexer->isNextToken(DocLexer::T_AT)) {
+            return $this->Annotation();
+        }
+
+        if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
+            return $this->Constant();
+        }
+
+        switch ($this->lexer->lookahead['type']) {
+            case DocLexer::T_STRING:
+                $this->match(DocLexer::T_STRING);
+
+                return $this->lexer->token['value'];
+
+            case DocLexer::T_INTEGER:
+                $this->match(DocLexer::T_INTEGER);
+
+                return (int) $this->lexer->token['value'];
+
+            case DocLexer::T_FLOAT:
+                $this->match(DocLexer::T_FLOAT);
+
+                return (float) $this->lexer->token['value'];
+
+            case DocLexer::T_TRUE:
+                $this->match(DocLexer::T_TRUE);
+
+                return true;
+
+            case DocLexer::T_FALSE:
+                $this->match(DocLexer::T_FALSE);
+
+                return false;
+
+            case DocLexer::T_NULL:
+                $this->match(DocLexer::T_NULL);
+
+                return null;
+
+            default:
+                throw $this->syntaxError('PlainValue');
+        }
+    }
+
+    /**
+     * FieldAssignment ::= FieldName "=" PlainValue
+     * FieldName ::= identifier
+     *
+     * @throws AnnotationException
+     * @throws ReflectionException
+     */
+    private function FieldAssignment(): stdClass
+    {
+        $this->match(DocLexer::T_IDENTIFIER);
+        $fieldName = $this->lexer->token['value'];
+
+        $this->match(DocLexer::T_EQUALS);
+
+        $item        = new stdClass();
+        $item->name  = $fieldName;
+        $item->value = $this->PlainValue();
+
+        return $item;
+    }
+
+    /**
+     * Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}"
+     *
+     * @return mixed[]
+     *
+     * @throws AnnotationException
+     * @throws ReflectionException
+     */
+    private function Arrayx(): array
+    {
+        $array = $values = [];
+
+        $this->match(DocLexer::T_OPEN_CURLY_BRACES);
+
+        // If the array is empty, stop parsing and return.
+        if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) {
+            $this->match(DocLexer::T_CLOSE_CURLY_BRACES);
+
+            return $array;
+        }
+
+        $values[] = $this->ArrayEntry();
+
+        while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
+            $this->match(DocLexer::T_COMMA);
+
+            // optional trailing comma
+            if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) {
+                break;
+            }
+
+            $values[] = $this->ArrayEntry();
+        }
+
+        $this->match(DocLexer::T_CLOSE_CURLY_BRACES);
+
+        foreach ($values as $value) {
+            [$key, $val] = $value;
+
+            if ($key !== null) {
+                $array[$key] = $val;
+            } else {
+                $array[] = $val;
+            }
+        }
+
+        return $array;
+    }
+
+    /**
+     * ArrayEntry ::= Value | KeyValuePair
+     * KeyValuePair ::= Key ("=" | ":") PlainValue | Constant
+     * Key ::= string | integer | Constant
+     *
+     * @phpstan-return array{mixed, mixed}
+     *
+     * @throws AnnotationException
+     * @throws ReflectionException
+     */
+    private function ArrayEntry(): array
+    {
+        $peek = $this->lexer->glimpse();
+
+        if (
+            $peek['type'] === DocLexer::T_EQUALS
+                || $peek['type'] === DocLexer::T_COLON
+        ) {
+            if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
+                $key = $this->Constant();
+            } else {
+                $this->matchAny([DocLexer::T_INTEGER, DocLexer::T_STRING]);
+                $key = $this->lexer->token['value'];
+            }
+
+            $this->matchAny([DocLexer::T_EQUALS, DocLexer::T_COLON]);
+
+            return [$key, $this->PlainValue()];
+        }
+
+        return [null, $this->Value()];
+    }
+
+    /**
+     * Checks whether the given $name matches any ignored annotation name or namespace
+     */
+    private function isIgnoredAnnotation(string $name): bool
+    {
+        if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) {
+            return true;
+        }
+
+        foreach (array_keys($this->ignoredAnnotationNamespaces) as $ignoredAnnotationNamespace) {
+            $ignoredAnnotationNamespace = rtrim($ignoredAnnotationNamespace, '\\') . '\\';
+
+            if (stripos(rtrim($name, '\\') . '\\', $ignoredAnnotationNamespace) === 0) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Resolve positional arguments (without name) to named ones
+     *
+     * @param array<string,mixed> $arguments
+     *
+     * @return array<string,mixed>
+     */
+    private function resolvePositionalValues(array $arguments, string $name): array
+    {
+        $positionalArguments = $arguments['positional_arguments'] ?? [];
+        $values              = $arguments['named_arguments'] ?? [];
+
+        if (
+            self::$annotationMetadata[$name]['has_named_argument_constructor']
+            && self::$annotationMetadata[$name]['default_property'] !== null
+        ) {
+            // We must ensure that we don't have positional arguments after named ones
+            $positions    = array_keys($positionalArguments);
+            $lastPosition = null;
+            foreach ($positions as $position) {
+                if (
+                    ($lastPosition === null && $position !== 0) ||
+                    ($lastPosition !== null && $position !== $lastPosition + 1)
+                ) {
+                    throw $this->syntaxError('Positional arguments after named arguments is not allowed');
+                }
+
+                $lastPosition = $position;
+            }
+
+            foreach (self::$annotationMetadata[$name]['constructor_args'] as $property => $parameter) {
+                $position = $parameter['position'];
+                if (isset($values[$property]) || ! isset($positionalArguments[$position])) {
+                    continue;
+                }
+
+                $values[$property] = $positionalArguments[$position];
+            }
+        } else {
+            if (count($positionalArguments) > 0 && ! isset($values['value'])) {
+                if (count($positionalArguments) === 1) {
+                    $value = array_pop($positionalArguments);
+                } else {
+                    $value = array_values($positionalArguments);
+                }
+
+                $values['value'] = $value;
+            }
+        }
+
+        return $values;
+    }
+
+    /**
+     * Try to instantiate the annotation and catch and process any exceptions related to failure
+     *
+     * @param class-string        $name
+     * @param array<string,mixed> $arguments
+     *
+     * @return object
+     *
+     * @throws AnnotationException
+     */
+    private function instantiateAnnotiation(string $originalName, string $context, string $name, array $arguments)
+    {
+        try {
+            return new $name(...$arguments);
+        } catch (Throwable $exception) {
+            throw AnnotationException::creationError(
+                sprintf(
+                    'An error occurred while instantiating the annotation @%s declared on %s: "%s".',
+                    $originalName,
+                    $context,
+                    $exception->getMessage()
+                ),
+                $exception
+            );
+        }
+    }
+}

+ 315 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php

@@ -0,0 +1,315 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use InvalidArgumentException;
+use ReflectionClass;
+use ReflectionMethod;
+use ReflectionProperty;
+use RuntimeException;
+
+use function chmod;
+use function file_put_contents;
+use function filemtime;
+use function gettype;
+use function is_dir;
+use function is_file;
+use function is_int;
+use function is_writable;
+use function mkdir;
+use function rename;
+use function rtrim;
+use function serialize;
+use function sha1;
+use function sprintf;
+use function strtr;
+use function tempnam;
+use function uniqid;
+use function unlink;
+use function var_export;
+
+/**
+ * File cache reader for annotations.
+ *
+ * @deprecated the FileCacheReader is deprecated and will be removed
+ *             in version 2.0.0 of doctrine/annotations. Please use the
+ *             {@see \Doctrine\Common\Annotations\PsrCachedReader} instead.
+ */
+class FileCacheReader implements Reader
+{
+    /** @var Reader */
+    private $reader;
+
+    /** @var string */
+    private $dir;
+
+    /** @var bool */
+    private $debug;
+
+    /** @phpstan-var array<string, list<object>> */
+    private $loadedAnnotations = [];
+
+    /** @var array<string, string> */
+    private $classNameHashes = [];
+
+    /** @var int */
+    private $umask;
+
+    /**
+     * @param string $cacheDir
+     * @param bool   $debug
+     * @param int    $umask
+     *
+     * @throws InvalidArgumentException
+     */
+    public function __construct(Reader $reader, $cacheDir, $debug = false, $umask = 0002)
+    {
+        if (! is_int($umask)) {
+            throw new InvalidArgumentException(sprintf(
+                'The parameter umask must be an integer, was: %s',
+                gettype($umask)
+            ));
+        }
+
+        $this->reader = $reader;
+        $this->umask  = $umask;
+
+        if (! is_dir($cacheDir) && ! @mkdir($cacheDir, 0777 & (~$this->umask), true)) {
+            throw new InvalidArgumentException(sprintf(
+                'The directory "%s" does not exist and could not be created.',
+                $cacheDir
+            ));
+        }
+
+        $this->dir   = rtrim($cacheDir, '\\/');
+        $this->debug = $debug;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getClassAnnotations(ReflectionClass $class)
+    {
+        if (! isset($this->classNameHashes[$class->name])) {
+            $this->classNameHashes[$class->name] = sha1($class->name);
+        }
+
+        $key = $this->classNameHashes[$class->name];
+
+        if (isset($this->loadedAnnotations[$key])) {
+            return $this->loadedAnnotations[$key];
+        }
+
+        $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php';
+        if (! is_file($path)) {
+            $annot = $this->reader->getClassAnnotations($class);
+            $this->saveCacheFile($path, $annot);
+
+            return $this->loadedAnnotations[$key] = $annot;
+        }
+
+        $filename = $class->getFilename();
+        if (
+            $this->debug
+            && $filename !== false
+            && filemtime($path) < filemtime($filename)
+        ) {
+            @unlink($path);
+
+            $annot = $this->reader->getClassAnnotations($class);
+            $this->saveCacheFile($path, $annot);
+
+            return $this->loadedAnnotations[$key] = $annot;
+        }
+
+        return $this->loadedAnnotations[$key] = include $path;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getPropertyAnnotations(ReflectionProperty $property)
+    {
+        $class = $property->getDeclaringClass();
+        if (! isset($this->classNameHashes[$class->name])) {
+            $this->classNameHashes[$class->name] = sha1($class->name);
+        }
+
+        $key = $this->classNameHashes[$class->name] . '$' . $property->getName();
+
+        if (isset($this->loadedAnnotations[$key])) {
+            return $this->loadedAnnotations[$key];
+        }
+
+        $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php';
+        if (! is_file($path)) {
+            $annot = $this->reader->getPropertyAnnotations($property);
+            $this->saveCacheFile($path, $annot);
+
+            return $this->loadedAnnotations[$key] = $annot;
+        }
+
+        $filename = $class->getFilename();
+        if (
+            $this->debug
+            && $filename !== false
+            && filemtime($path) < filemtime($filename)
+        ) {
+            @unlink($path);
+
+            $annot = $this->reader->getPropertyAnnotations($property);
+            $this->saveCacheFile($path, $annot);
+
+            return $this->loadedAnnotations[$key] = $annot;
+        }
+
+        return $this->loadedAnnotations[$key] = include $path;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMethodAnnotations(ReflectionMethod $method)
+    {
+        $class = $method->getDeclaringClass();
+        if (! isset($this->classNameHashes[$class->name])) {
+            $this->classNameHashes[$class->name] = sha1($class->name);
+        }
+
+        $key = $this->classNameHashes[$class->name] . '#' . $method->getName();
+
+        if (isset($this->loadedAnnotations[$key])) {
+            return $this->loadedAnnotations[$key];
+        }
+
+        $path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php';
+        if (! is_file($path)) {
+            $annot = $this->reader->getMethodAnnotations($method);
+            $this->saveCacheFile($path, $annot);
+
+            return $this->loadedAnnotations[$key] = $annot;
+        }
+
+        $filename = $class->getFilename();
+        if (
+            $this->debug
+            && $filename !== false
+            && filemtime($path) < filemtime($filename)
+        ) {
+            @unlink($path);
+
+            $annot = $this->reader->getMethodAnnotations($method);
+            $this->saveCacheFile($path, $annot);
+
+            return $this->loadedAnnotations[$key] = $annot;
+        }
+
+        return $this->loadedAnnotations[$key] = include $path;
+    }
+
+    /**
+     * Saves the cache file.
+     *
+     * @param string $path
+     * @param mixed  $data
+     *
+     * @return void
+     */
+    private function saveCacheFile($path, $data)
+    {
+        if (! is_writable($this->dir)) {
+            throw new InvalidArgumentException(sprintf(
+                <<<'EXCEPTION'
+The directory "%s" is not writable. Both the webserver and the console user need access.
+You can manage access rights for multiple users with "chmod +a".
+If your system does not support this, check out the acl package.,
+EXCEPTION
+                ,
+                $this->dir
+            ));
+        }
+
+        $tempfile = tempnam($this->dir, uniqid('', true));
+
+        if ($tempfile === false) {
+            throw new RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir));
+        }
+
+        @chmod($tempfile, 0666 & (~$this->umask));
+
+        $written = file_put_contents(
+            $tempfile,
+            '<?php return unserialize(' . var_export(serialize($data), true) . ');'
+        );
+
+        if ($written === false) {
+            throw new RuntimeException(sprintf('Unable to write cached file to: %s', $tempfile));
+        }
+
+        @chmod($tempfile, 0666 & (~$this->umask));
+
+        if (rename($tempfile, $path) === false) {
+            @unlink($tempfile);
+
+            throw new RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getClassAnnotation(ReflectionClass $class, $annotationName)
+    {
+        $annotations = $this->getClassAnnotations($class);
+
+        foreach ($annotations as $annotation) {
+            if ($annotation instanceof $annotationName) {
+                return $annotation;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
+    {
+        $annotations = $this->getMethodAnnotations($method);
+
+        foreach ($annotations as $annotation) {
+            if ($annotation instanceof $annotationName) {
+                return $annotation;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
+    {
+        $annotations = $this->getPropertyAnnotations($property);
+
+        foreach ($annotations as $annotation) {
+            if ($annotation instanceof $annotationName) {
+                return $annotation;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Clears loaded annotations.
+     *
+     * @return void
+     */
+    public function clearLoadedAnnotations()
+    {
+        $this->loadedAnnotations = [];
+    }
+}

+ 178 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php

@@ -0,0 +1,178 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\Common\Annotations;
+
+/**
+ *  A list of annotations that are implicitly ignored during the parsing process.
+ *
+ *  All names are case sensitive.
+ */
+final class ImplicitlyIgnoredAnnotationNames
+{
+    private const Reserved = [
+        'Annotation'               => true,
+        'Attribute'                => true,
+        'Attributes'               => true,
+        /* Can we enable this? 'Enum' => true, */
+        'Required'                 => true,
+        'Target'                   => true,
+        'NamedArgumentConstructor' => true,
+    ];
+
+    private const WidelyUsedNonStandard = [
+        'fix'      => true,
+        'fixme'    => true,
+        'override' => true,
+    ];
+
+    private const PhpDocumentor1 = [
+        'abstract'   => true,
+        'access'     => true,
+        'code'       => true,
+        'deprec'     => true,
+        'endcode'    => true,
+        'exception'  => true,
+        'final'      => true,
+        'ingroup'    => true,
+        'inheritdoc' => true,
+        'inheritDoc' => true,
+        'magic'      => true,
+        'name'       => true,
+        'private'    => true,
+        'static'     => true,
+        'staticvar'  => true,
+        'staticVar'  => true,
+        'toc'        => true,
+        'tutorial'   => true,
+        'throw'      => true,
+    ];
+
+    private const PhpDocumentor2 = [
+        'api'            => true,
+        'author'         => true,
+        'category'       => true,
+        'copyright'      => true,
+        'deprecated'     => true,
+        'example'        => true,
+        'filesource'     => true,
+        'global'         => true,
+        'ignore'         => true,
+        /* Can we enable this? 'index' => true, */
+        'internal'       => true,
+        'license'        => true,
+        'link'           => true,
+        'method'         => true,
+        'package'        => true,
+        'param'          => true,
+        'property'       => true,
+        'property-read'  => true,
+        'property-write' => true,
+        'return'         => true,
+        'see'            => true,
+        'since'          => true,
+        'source'         => true,
+        'subpackage'     => true,
+        'throws'         => true,
+        'todo'           => true,
+        'TODO'           => true,
+        'usedby'         => true,
+        'uses'           => true,
+        'var'            => true,
+        'version'        => true,
+    ];
+
+    private const PHPUnit = [
+        'author'                         => true,
+        'after'                          => true,
+        'afterClass'                     => true,
+        'backupGlobals'                  => true,
+        'backupStaticAttributes'         => true,
+        'before'                         => true,
+        'beforeClass'                    => true,
+        'codeCoverageIgnore'             => true,
+        'codeCoverageIgnoreStart'        => true,
+        'codeCoverageIgnoreEnd'          => true,
+        'covers'                         => true,
+        'coversDefaultClass'             => true,
+        'coversNothing'                  => true,
+        'dataProvider'                   => true,
+        'depends'                        => true,
+        'doesNotPerformAssertions'       => true,
+        'expectedException'              => true,
+        'expectedExceptionCode'          => true,
+        'expectedExceptionMessage'       => true,
+        'expectedExceptionMessageRegExp' => true,
+        'group'                          => true,
+        'large'                          => true,
+        'medium'                         => true,
+        'preserveGlobalState'            => true,
+        'requires'                       => true,
+        'runTestsInSeparateProcesses'    => true,
+        'runInSeparateProcess'           => true,
+        'small'                          => true,
+        'test'                           => true,
+        'testdox'                        => true,
+        'testWith'                       => true,
+        'ticket'                         => true,
+        'uses'                           => true,
+    ];
+
+    private const PhpCheckStyle = ['SuppressWarnings' => true];
+
+    private const PhpStorm = ['noinspection' => true];
+
+    private const PEAR = ['package_version' => true];
+
+    private const PlainUML = [
+        'startuml' => true,
+        'enduml'   => true,
+    ];
+
+    private const Symfony = ['experimental' => true];
+
+    private const PhpCodeSniffer = [
+        'codingStandardsIgnoreStart' => true,
+        'codingStandardsIgnoreEnd'   => true,
+    ];
+
+    private const SlevomatCodingStandard = ['phpcsSuppress' => true];
+
+    private const Phan = ['suppress' => true];
+
+    private const Rector = ['noRector' => true];
+
+    private const StaticAnalysis = [
+        // PHPStan, Psalm
+        'extends' => true,
+        'implements' => true,
+        'readonly' => true,
+        'template' => true,
+        'use' => true,
+
+        // Psalm
+        'pure' => true,
+        'immutable' => true,
+    ];
+
+    public const LIST = self::Reserved
+        + self::WidelyUsedNonStandard
+        + self::PhpDocumentor1
+        + self::PhpDocumentor2
+        + self::PHPUnit
+        + self::PhpCheckStyle
+        + self::PhpStorm
+        + self::PEAR
+        + self::PlainUML
+        + self::Symfony
+        + self::SlevomatCodingStandard
+        + self::PhpCodeSniffer
+        + self::Phan
+        + self::Rector
+        + self::StaticAnalysis;
+
+    private function __construct()
+    {
+    }
+}

+ 100 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php

@@ -0,0 +1,100 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use ReflectionClass;
+use ReflectionMethod;
+use ReflectionProperty;
+
+use function call_user_func_array;
+use function get_class;
+
+/**
+ * Allows the reader to be used in-place of Doctrine's reader.
+ */
+class IndexedReader implements Reader
+{
+    /** @var Reader */
+    private $delegate;
+
+    public function __construct(Reader $reader)
+    {
+        $this->delegate = $reader;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getClassAnnotations(ReflectionClass $class)
+    {
+        $annotations = [];
+        foreach ($this->delegate->getClassAnnotations($class) as $annot) {
+            $annotations[get_class($annot)] = $annot;
+        }
+
+        return $annotations;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getClassAnnotation(ReflectionClass $class, $annotationName)
+    {
+        return $this->delegate->getClassAnnotation($class, $annotationName);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMethodAnnotations(ReflectionMethod $method)
+    {
+        $annotations = [];
+        foreach ($this->delegate->getMethodAnnotations($method) as $annot) {
+            $annotations[get_class($annot)] = $annot;
+        }
+
+        return $annotations;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
+    {
+        return $this->delegate->getMethodAnnotation($method, $annotationName);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getPropertyAnnotations(ReflectionProperty $property)
+    {
+        $annotations = [];
+        foreach ($this->delegate->getPropertyAnnotations($property) as $annot) {
+            $annotations[get_class($annot)] = $annot;
+        }
+
+        return $annotations;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
+    {
+        return $this->delegate->getPropertyAnnotation($property, $annotationName);
+    }
+
+    /**
+     * Proxies all methods to the delegate.
+     *
+     * @param string  $method
+     * @param mixed[] $args
+     *
+     * @return mixed
+     */
+    public function __call($method, $args)
+    {
+        return call_user_func_array([$this->delegate, $method], $args);
+    }
+}

+ 14 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+/**
+ * Marker interface for PHP7/PHP8 compatible support
+ * for named arguments (and constructor property promotion).
+ *
+ * @deprecated Implementing this interface is deprecated
+ *             Use the Annotation @NamedArgumentConstructor instead
+ */
+interface NamedArgumentConstructorAnnotation
+{
+}

+ 92 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use ReflectionClass;
+use ReflectionFunction;
+use SplFileObject;
+
+use function is_file;
+use function method_exists;
+use function preg_quote;
+use function preg_replace;
+
+/**
+ * Parses a file for namespaces/use/class declarations.
+ */
+final class PhpParser
+{
+    /**
+     * Parses a class.
+     *
+     * @deprecated use parseUseStatements instead
+     *
+     * @param ReflectionClass $class A <code>ReflectionClass</code> object.
+     *
+     * @return array<string, class-string> A list with use statements in the form (Alias => FQN).
+     */
+    public function parseClass(ReflectionClass $class)
+    {
+        return $this->parseUseStatements($class);
+    }
+
+    /**
+     * Parse a class or function for use statements.
+     *
+     * @param ReflectionClass|ReflectionFunction $reflection
+     *
+     * @psalm-return array<string, string> a list with use statements in the form (Alias => FQN).
+     */
+    public function parseUseStatements($reflection): array
+    {
+        if (method_exists($reflection, 'getUseStatements')) {
+            return $reflection->getUseStatements();
+        }
+
+        $filename = $reflection->getFileName();
+
+        if ($filename === false) {
+            return [];
+        }
+
+        $content = $this->getFileContent($filename, $reflection->getStartLine());
+
+        if ($content === null) {
+            return [];
+        }
+
+        $namespace = preg_quote($reflection->getNamespaceName());
+        $content   = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content);
+        $tokenizer = new TokenParser('<?php ' . $content);
+
+        return $tokenizer->parseUseStatements($reflection->getNamespaceName());
+    }
+
+    /**
+     * Gets the content of the file right up to the given line number.
+     *
+     * @param string $filename   The name of the file to load.
+     * @param int    $lineNumber The number of lines to read from file.
+     *
+     * @return string|null The content of the file or null if the file does not exist.
+     */
+    private function getFileContent($filename, $lineNumber)
+    {
+        if (! is_file($filename)) {
+            return null;
+        }
+
+        $content = '';
+        $lineCnt = 0;
+        $file    = new SplFileObject($filename);
+        while (! $file->eof()) {
+            if ($lineCnt++ === $lineNumber) {
+                break;
+            }
+
+            $content .= $file->fgets();
+        }
+
+        return $content;
+    }
+}

+ 232 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php

@@ -0,0 +1,232 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use Psr\Cache\CacheItemPoolInterface;
+use ReflectionClass;
+use ReflectionMethod;
+use ReflectionProperty;
+use Reflector;
+
+use function array_map;
+use function array_merge;
+use function assert;
+use function filemtime;
+use function max;
+use function rawurlencode;
+use function time;
+
+/**
+ * A cache aware annotation reader.
+ */
+final class PsrCachedReader implements Reader
+{
+    /** @var Reader */
+    private $delegate;
+
+    /** @var CacheItemPoolInterface */
+    private $cache;
+
+    /** @var bool */
+    private $debug;
+
+    /** @var array<string, array<object>> */
+    private $loadedAnnotations = [];
+
+    /** @var int[] */
+    private $loadedFilemtimes = [];
+
+    public function __construct(Reader $reader, CacheItemPoolInterface $cache, bool $debug = false)
+    {
+        $this->delegate = $reader;
+        $this->cache    = $cache;
+        $this->debug    = (bool) $debug;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getClassAnnotations(ReflectionClass $class)
+    {
+        $cacheKey = $class->getName();
+
+        if (isset($this->loadedAnnotations[$cacheKey])) {
+            return $this->loadedAnnotations[$cacheKey];
+        }
+
+        $annots = $this->fetchFromCache($cacheKey, $class, 'getClassAnnotations', $class);
+
+        return $this->loadedAnnotations[$cacheKey] = $annots;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getClassAnnotation(ReflectionClass $class, $annotationName)
+    {
+        foreach ($this->getClassAnnotations($class) as $annot) {
+            if ($annot instanceof $annotationName) {
+                return $annot;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getPropertyAnnotations(ReflectionProperty $property)
+    {
+        $class    = $property->getDeclaringClass();
+        $cacheKey = $class->getName() . '$' . $property->getName();
+
+        if (isset($this->loadedAnnotations[$cacheKey])) {
+            return $this->loadedAnnotations[$cacheKey];
+        }
+
+        $annots = $this->fetchFromCache($cacheKey, $class, 'getPropertyAnnotations', $property);
+
+        return $this->loadedAnnotations[$cacheKey] = $annots;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
+    {
+        foreach ($this->getPropertyAnnotations($property) as $annot) {
+            if ($annot instanceof $annotationName) {
+                return $annot;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMethodAnnotations(ReflectionMethod $method)
+    {
+        $class    = $method->getDeclaringClass();
+        $cacheKey = $class->getName() . '#' . $method->getName();
+
+        if (isset($this->loadedAnnotations[$cacheKey])) {
+            return $this->loadedAnnotations[$cacheKey];
+        }
+
+        $annots = $this->fetchFromCache($cacheKey, $class, 'getMethodAnnotations', $method);
+
+        return $this->loadedAnnotations[$cacheKey] = $annots;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
+    {
+        foreach ($this->getMethodAnnotations($method) as $annot) {
+            if ($annot instanceof $annotationName) {
+                return $annot;
+            }
+        }
+
+        return null;
+    }
+
+    public function clearLoadedAnnotations(): void
+    {
+        $this->loadedAnnotations = [];
+        $this->loadedFilemtimes  = [];
+    }
+
+    /** @return mixed[] */
+    private function fetchFromCache(
+        string $cacheKey,
+        ReflectionClass $class,
+        string $method,
+        Reflector $reflector
+    ): array {
+        $cacheKey = rawurlencode($cacheKey);
+
+        $item = $this->cache->getItem($cacheKey);
+        if (($this->debug && ! $this->refresh($cacheKey, $class)) || ! $item->isHit()) {
+            $this->cache->save($item->set($this->delegate->{$method}($reflector)));
+        }
+
+        return $item->get();
+    }
+
+    /**
+     * Used in debug mode to check if the cache is fresh.
+     *
+     * @return bool Returns true if the cache was fresh, or false if the class
+     * being read was modified since writing to the cache.
+     */
+    private function refresh(string $cacheKey, ReflectionClass $class): bool
+    {
+        $lastModification = $this->getLastModification($class);
+        if ($lastModification === 0) {
+            return true;
+        }
+
+        $item = $this->cache->getItem('[C]' . $cacheKey);
+        if ($item->isHit() && $item->get() >= $lastModification) {
+            return true;
+        }
+
+        $this->cache->save($item->set(time()));
+
+        return false;
+    }
+
+    /**
+     * Returns the time the class was last modified, testing traits and parents
+     */
+    private function getLastModification(ReflectionClass $class): int
+    {
+        $filename = $class->getFileName();
+
+        if (isset($this->loadedFilemtimes[$filename])) {
+            return $this->loadedFilemtimes[$filename];
+        }
+
+        $parent = $class->getParentClass();
+
+        $lastModification =  max(array_merge(
+            [$filename ? filemtime($filename) : 0],
+            array_map(function (ReflectionClass $reflectionTrait): int {
+                return $this->getTraitLastModificationTime($reflectionTrait);
+            }, $class->getTraits()),
+            array_map(function (ReflectionClass $class): int {
+                return $this->getLastModification($class);
+            }, $class->getInterfaces()),
+            $parent ? [$this->getLastModification($parent)] : []
+        ));
+
+        assert($lastModification !== false);
+
+        return $this->loadedFilemtimes[$filename] = $lastModification;
+    }
+
+    private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int
+    {
+        $fileName = $reflectionTrait->getFileName();
+
+        if (isset($this->loadedFilemtimes[$fileName])) {
+            return $this->loadedFilemtimes[$fileName];
+        }
+
+        $lastModificationTime = max(array_merge(
+            [$fileName ? filemtime($fileName) : 0],
+            array_map(function (ReflectionClass $reflectionTrait): int {
+                return $this->getTraitLastModificationTime($reflectionTrait);
+            }, $reflectionTrait->getTraits())
+        ));
+
+        assert($lastModificationTime !== false);
+
+        return $this->loadedFilemtimes[$fileName] = $lastModificationTime;
+    }
+}

+ 80 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use ReflectionClass;
+use ReflectionMethod;
+use ReflectionProperty;
+
+/**
+ * Interface for annotation readers.
+ */
+interface Reader
+{
+    /**
+     * Gets the annotations applied to a class.
+     *
+     * @param ReflectionClass $class The ReflectionClass of the class from which
+     * the class annotations should be read.
+     *
+     * @return array<object> An array of Annotations.
+     */
+    public function getClassAnnotations(ReflectionClass $class);
+
+    /**
+     * Gets a class annotation.
+     *
+     * @param ReflectionClass $class          The ReflectionClass of the class from which
+     *          the class annotations should be read.
+     * @param class-string<T> $annotationName The name of the annotation.
+     *
+     * @return T|null The Annotation or NULL, if the requested annotation does not exist.
+     *
+     * @template T
+     */
+    public function getClassAnnotation(ReflectionClass $class, $annotationName);
+
+    /**
+     * Gets the annotations applied to a method.
+     *
+     * @param ReflectionMethod $method The ReflectionMethod of the method from which
+     * the annotations should be read.
+     *
+     * @return array<object> An array of Annotations.
+     */
+    public function getMethodAnnotations(ReflectionMethod $method);
+
+    /**
+     * Gets a method annotation.
+     *
+     * @param ReflectionMethod $method         The ReflectionMethod to read the annotations from.
+     * @param class-string<T>  $annotationName The name of the annotation.
+     *
+     * @return T|null The Annotation or NULL, if the requested annotation does not exist.
+     *
+     * @template T
+     */
+    public function getMethodAnnotation(ReflectionMethod $method, $annotationName);
+
+    /**
+     * Gets the annotations applied to a property.
+     *
+     * @param ReflectionProperty $property The ReflectionProperty of the property
+     * from which the annotations should be read.
+     *
+     * @return array<object> An array of Annotations.
+     */
+    public function getPropertyAnnotations(ReflectionProperty $property);
+
+    /**
+     * Gets a property annotation.
+     *
+     * @param ReflectionProperty $property       The ReflectionProperty to read the annotations from.
+     * @param class-string<T>    $annotationName The name of the annotation.
+     *
+     * @return T|null The Annotation or NULL, if the requested annotation does not exist.
+     *
+     * @template T
+     */
+    public function getPropertyAnnotation(ReflectionProperty $property, $annotationName);
+}

+ 114 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php

@@ -0,0 +1,114 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use ReflectionClass;
+use ReflectionMethod;
+use ReflectionProperty;
+
+/**
+ * Simple Annotation Reader.
+ *
+ * This annotation reader is intended to be used in projects where you have
+ * full-control over all annotations that are available.
+ *
+ * @deprecated Deprecated in favour of using AnnotationReader
+ */
+class SimpleAnnotationReader implements Reader
+{
+    /** @var DocParser */
+    private $parser;
+
+    /**
+     * Initializes a new SimpleAnnotationReader.
+     */
+    public function __construct()
+    {
+        $this->parser = new DocParser();
+        $this->parser->setIgnoreNotImportedAnnotations(true);
+    }
+
+    /**
+     * Adds a namespace in which we will look for annotations.
+     *
+     * @param string $namespace
+     *
+     * @return void
+     */
+    public function addNamespace($namespace)
+    {
+        $this->parser->addNamespace($namespace);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getClassAnnotations(ReflectionClass $class)
+    {
+        return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMethodAnnotations(ReflectionMethod $method)
+    {
+        return $this->parser->parse(
+            $method->getDocComment(),
+            'method ' . $method->getDeclaringClass()->name . '::' . $method->getName() . '()'
+        );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getPropertyAnnotations(ReflectionProperty $property)
+    {
+        return $this->parser->parse(
+            $property->getDocComment(),
+            'property ' . $property->getDeclaringClass()->name . '::$' . $property->getName()
+        );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getClassAnnotation(ReflectionClass $class, $annotationName)
+    {
+        foreach ($this->getClassAnnotations($class) as $annot) {
+            if ($annot instanceof $annotationName) {
+                return $annot;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
+    {
+        foreach ($this->getMethodAnnotations($method) as $annot) {
+            if ($annot instanceof $annotationName) {
+                return $annot;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
+    {
+        foreach ($this->getPropertyAnnotations($property) as $annot) {
+            if ($annot instanceof $annotationName) {
+                return $annot;
+            }
+        }
+
+        return null;
+    }
+}

+ 206 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php

@@ -0,0 +1,206 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use function array_merge;
+use function count;
+use function explode;
+use function strtolower;
+use function token_get_all;
+
+use const PHP_VERSION_ID;
+use const T_AS;
+use const T_COMMENT;
+use const T_DOC_COMMENT;
+use const T_NAME_FULLY_QUALIFIED;
+use const T_NAME_QUALIFIED;
+use const T_NAMESPACE;
+use const T_NS_SEPARATOR;
+use const T_STRING;
+use const T_USE;
+use const T_WHITESPACE;
+
+/**
+ * Parses a file for namespaces/use/class declarations.
+ */
+class TokenParser
+{
+    /**
+     * The token list.
+     *
+     * @phpstan-var list<mixed[]>
+     */
+    private $tokens;
+
+    /**
+     * The number of tokens.
+     *
+     * @var int
+     */
+    private $numTokens;
+
+    /**
+     * The current array pointer.
+     *
+     * @var int
+     */
+    private $pointer = 0;
+
+    /** @param string $contents */
+    public function __construct($contents)
+    {
+        $this->tokens = token_get_all($contents);
+
+        // The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it
+        // saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored
+        // doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a
+        // docblock. If the first thing in the file is a class without a doc block this would cause calls to
+        // getDocBlock() on said class to return our long lost doc_comment. Argh.
+        // To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least
+        // it's harmless to us.
+        token_get_all("<?php\n/**\n *\n */");
+
+        $this->numTokens = count($this->tokens);
+    }
+
+    /**
+     * Gets the next non whitespace and non comment token.
+     *
+     * @param bool $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped.
+     * If FALSE then only whitespace and normal comments are skipped.
+     *
+     * @return mixed[]|string|null The token if exists, null otherwise.
+     */
+    public function next($docCommentIsComment = true)
+    {
+        for ($i = $this->pointer; $i < $this->numTokens; $i++) {
+            $this->pointer++;
+            if (
+                $this->tokens[$i][0] === T_WHITESPACE ||
+                $this->tokens[$i][0] === T_COMMENT ||
+                ($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)
+            ) {
+                continue;
+            }
+
+            return $this->tokens[$i];
+        }
+
+        return null;
+    }
+
+    /**
+     * Parses a single use statement.
+     *
+     * @return array<string, string> A list with all found class names for a use statement.
+     */
+    public function parseUseStatement()
+    {
+        $groupRoot     = '';
+        $class         = '';
+        $alias         = '';
+        $statements    = [];
+        $explicitAlias = false;
+        while (($token = $this->next())) {
+            if (! $explicitAlias && $token[0] === T_STRING) {
+                $class .= $token[1];
+                $alias  = $token[1];
+            } elseif ($explicitAlias && $token[0] === T_STRING) {
+                $alias = $token[1];
+            } elseif (
+                PHP_VERSION_ID >= 80000 &&
+                ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)
+            ) {
+                $class .= $token[1];
+
+                $classSplit = explode('\\', $token[1]);
+                $alias      = $classSplit[count($classSplit) - 1];
+            } elseif ($token[0] === T_NS_SEPARATOR) {
+                $class .= '\\';
+                $alias  = '';
+            } elseif ($token[0] === T_AS) {
+                $explicitAlias = true;
+                $alias         = '';
+            } elseif ($token === ',') {
+                $statements[strtolower($alias)] = $groupRoot . $class;
+                $class                          = '';
+                $alias                          = '';
+                $explicitAlias                  = false;
+            } elseif ($token === ';') {
+                $statements[strtolower($alias)] = $groupRoot . $class;
+                break;
+            } elseif ($token === '{') {
+                $groupRoot = $class;
+                $class     = '';
+            } elseif ($token === '}') {
+                continue;
+            } else {
+                break;
+            }
+        }
+
+        return $statements;
+    }
+
+    /**
+     * Gets all use statements.
+     *
+     * @param string $namespaceName The namespace name of the reflected class.
+     *
+     * @return array<string, string> A list with all found use statements.
+     */
+    public function parseUseStatements($namespaceName)
+    {
+        $statements = [];
+        while (($token = $this->next())) {
+            if ($token[0] === T_USE) {
+                $statements = array_merge($statements, $this->parseUseStatement());
+                continue;
+            }
+
+            if ($token[0] !== T_NAMESPACE || $this->parseNamespace() !== $namespaceName) {
+                continue;
+            }
+
+            // Get fresh array for new namespace. This is to prevent the parser to collect the use statements
+            // for a previous namespace with the same name. This is the case if a namespace is defined twice
+            // or if a namespace with the same name is commented out.
+            $statements = [];
+        }
+
+        return $statements;
+    }
+
+    /**
+     * Gets the namespace.
+     *
+     * @return string The found namespace.
+     */
+    public function parseNamespace()
+    {
+        $name = '';
+        while (
+            ($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || (
+            PHP_VERSION_ID >= 80000 &&
+            ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)
+            ))
+        ) {
+            $name .= $token[1];
+        }
+
+        return $name;
+    }
+
+    /**
+     * Gets the class name.
+     *
+     * @return string The found class name.
+     */
+    public function parseClass()
+    {
+        // Namespaces and class names are tokenized the same: T_STRINGs
+        // separated by T_NS_SEPARATOR so we can use one function to provide
+        // both.
+        return $this->parseNamespace();
+    }
+}

+ 15 - 0
vendor/doctrine/annotations/psalm.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<psalm
+    errorLevel="7"
+    resolveFromConfigFile="true"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns="https://getpsalm.org/schema/config"
+    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
+>
+    <projectFiles>
+        <directory name="lib/Doctrine/Common/Annotations" />
+        <ignoreFiles>
+            <directory name="vendor" />
+        </ignoreFiles>
+    </projectFiles>
+</psalm>

+ 19 - 0
vendor/doctrine/deprecations/LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2020-2021 Doctrine Project
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 154 - 0
vendor/doctrine/deprecations/README.md

@@ -0,0 +1,154 @@
+# Doctrine Deprecations
+
+A small (side-effect free by default) layer on top of
+`trigger_error(E_USER_DEPRECATED)` or PSR-3 logging.
+
+- no side-effects by default, making it a perfect fit for libraries that don't know how the error handler works they operate under
+- options to avoid having to rely on error handlers global state by using PSR-3 logging
+- deduplicate deprecation messages to avoid excessive triggering and reduce overhead
+
+We recommend to collect Deprecations using a PSR logger instead of relying on
+the global error handler.
+
+## Usage from consumer perspective:
+
+Enable Doctrine deprecations to be sent to a PSR3 logger:
+
+```php
+\Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger);
+```
+
+Enable Doctrine deprecations to be sent as `@trigger_error($message, E_USER_DEPRECATED)`
+messages.
+
+```php
+\Doctrine\Deprecations\Deprecation::enableWithTriggerError();
+```
+
+If you only want to enable deprecation tracking, without logging or calling `trigger_error` then call:
+
+```php
+\Doctrine\Deprecations\Deprecation::enableTrackingDeprecations();
+```
+
+Tracking is enabled with all three modes and provides access to all triggered
+deprecations and their individual count:
+
+```php
+$deprecations = \Doctrine\Deprecations\Deprecation::getTriggeredDeprecations();
+
+foreach ($deprecations as $identifier => $count) {
+    echo $identifier . " was triggered " . $count . " times\n";
+}
+```
+
+### Suppressing Specific Deprecations
+
+Disable triggering about specific deprecations:
+
+```php
+\Doctrine\Deprecations\Deprecation::ignoreDeprecations("https://link/to/deprecations-description-identifier");
+```
+
+Disable all deprecations from a package
+
+```php
+\Doctrine\Deprecations\Deprecation::ignorePackage("doctrine/orm");
+```
+
+### Other Operations
+
+When used within PHPUnit or other tools that could collect multiple instances of the same deprecations
+the deduplication can be disabled:
+
+```php
+\Doctrine\Deprecations\Deprecation::withoutDeduplication();
+```
+
+Disable deprecation tracking again:
+
+```php
+\Doctrine\Deprecations\Deprecation::disable();
+```
+
+## Usage from a library/producer perspective:
+
+When you want to unconditionally trigger a deprecation even when called
+from the library itself then the `trigger` method is the way to go:
+
+```php
+\Doctrine\Deprecations\Deprecation::trigger(
+    "doctrine/orm",
+    "https://link/to/deprecations-description",
+    "message"
+);
+```
+
+If variable arguments are provided at the end, they are used with `sprintf` on
+the message.
+
+```php
+\Doctrine\Deprecations\Deprecation::trigger(
+    "doctrine/orm",
+    "https://github.com/doctrine/orm/issue/1234",
+    "message %s %d",
+    "foo",
+    1234
+);
+```
+
+When you want to trigger a deprecation only when it is called by a function
+outside of the current package, but not trigger when the package itself is the cause,
+then use:
+
+```php
+\Doctrine\Deprecations\Deprecation::triggerIfCalledFromOutside(
+    "doctrine/orm",
+    "https://link/to/deprecations-description",
+    "message"
+);
+```
+
+Based on the issue link each deprecation message is only triggered once per
+request.
+
+A limited stacktrace is included in the deprecation message to find the
+offending location.
+
+Note: A producer/library should never call `Deprecation::enableWith` methods
+and leave the decision how to handle deprecations to application and
+frameworks.
+
+## Usage in PHPUnit tests
+
+There is a `VerifyDeprecations` trait that you can use to make assertions on
+the occurrence of deprecations within a test.
+
+```php
+use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
+
+class MyTest extends TestCase
+{
+    use VerifyDeprecations;
+
+    public function testSomethingDeprecation()
+    {
+        $this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234');
+
+        triggerTheCodeWithDeprecation();
+    }
+
+    public function testSomethingDeprecationFixed()
+    {
+        $this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234');
+
+        triggerTheCodeWithoutDeprecation();
+    }
+}
+```
+
+## What is a deprecation identifier?
+
+An identifier for deprecations is just a link to any resource, most often a
+Github Issue or Pull Request explaining the deprecation and potentially its
+alternative.

+ 32 - 0
vendor/doctrine/deprecations/composer.json

@@ -0,0 +1,32 @@
+{
+    "name": "doctrine/deprecations",
+    "type": "library",
+    "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
+    "homepage": "https://www.doctrine-project.org/",
+    "license": "MIT",
+    "require": {
+        "php": "^7.1|^8.0"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "^7.5|^8.5|^9.5",
+        "psr/log": "^1|^2|^3",
+        "doctrine/coding-standard": "^9"
+    },
+    "suggest": {
+        "psr/log": "Allows logging deprecations via PSR-3 logger implementation"
+    },
+    "autoload": {
+        "psr-4": {"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"}
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "DeprecationTests\\": "test_fixtures/src",
+            "Doctrine\\Foo\\": "test_fixtures/vendor/doctrine/foo"
+        }
+    },
+    "config": {
+        "allow-plugins": {
+            "dealerdirect/phpcodesniffer-composer-installer": true
+        }
+    }
+}

+ 266 - 0
vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php

@@ -0,0 +1,266 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\Deprecations;
+
+use Psr\Log\LoggerInterface;
+
+use function array_key_exists;
+use function array_reduce;
+use function debug_backtrace;
+use function sprintf;
+use function strpos;
+use function strrpos;
+use function substr;
+use function trigger_error;
+
+use const DEBUG_BACKTRACE_IGNORE_ARGS;
+use const DIRECTORY_SEPARATOR;
+use const E_USER_DEPRECATED;
+
+/**
+ * Manages Deprecation logging in different ways.
+ *
+ * By default triggered exceptions are not logged.
+ *
+ * To enable different deprecation logging mechanisms you can call the
+ * following methods:
+ *
+ *  - Minimal collection of deprecations via getTriggeredDeprecations()
+ *    \Doctrine\Deprecations\Deprecation::enableTrackingDeprecations();
+ *
+ *  - Uses @trigger_error with E_USER_DEPRECATED
+ *    \Doctrine\Deprecations\Deprecation::enableWithTriggerError();
+ *
+ *  - Sends deprecation messages via a PSR-3 logger
+ *    \Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger);
+ *
+ * Packages that trigger deprecations should use the `trigger()` or
+ * `triggerIfCalledFromOutside()` methods.
+ */
+class Deprecation
+{
+    private const TYPE_NONE               = 0;
+    private const TYPE_TRACK_DEPRECATIONS = 1;
+    private const TYPE_TRIGGER_ERROR      = 2;
+    private const TYPE_PSR_LOGGER         = 4;
+
+    /** @var int */
+    private static $type = self::TYPE_NONE;
+
+    /** @var LoggerInterface|null */
+    private static $logger;
+
+    /** @var array<string,bool> */
+    private static $ignoredPackages = [];
+
+    /** @var array<string,int> */
+    private static $ignoredLinks = [];
+
+    /** @var bool */
+    private static $deduplication = true;
+
+    /**
+     * Trigger a deprecation for the given package and identfier.
+     *
+     * The link should point to a Github issue or Wiki entry detailing the
+     * deprecation. It is additionally used to de-duplicate the trigger of the
+     * same deprecation during a request.
+     *
+     * @param mixed $args
+     */
+    public static function trigger(string $package, string $link, string $message, ...$args): void
+    {
+        if (self::$type === self::TYPE_NONE) {
+            return;
+        }
+
+        if (array_key_exists($link, self::$ignoredLinks)) {
+            self::$ignoredLinks[$link]++;
+        } else {
+            self::$ignoredLinks[$link] = 1;
+        }
+
+        if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) {
+            return;
+        }
+
+        if (isset(self::$ignoredPackages[$package])) {
+            return;
+        }
+
+        $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
+
+        $message = sprintf($message, ...$args);
+
+        self::delegateTriggerToBackend($message, $backtrace, $link, $package);
+    }
+
+    /**
+     * Trigger a deprecation for the given package and identifier when called from outside.
+     *
+     * "Outside" means we assume that $package is currently installed as a
+     * dependency and the caller is not a file in that package. When $package
+     * is installed as a root package then deprecations triggered from the
+     * tests folder are also considered "outside".
+     *
+     * This deprecation method assumes that you are using Composer to install
+     * the dependency and are using the default /vendor/ folder and not a
+     * Composer plugin to change the install location. The assumption is also
+     * that $package is the exact composer packge name.
+     *
+     * Compared to {@link trigger()} this method causes some overhead when
+     * deprecation tracking is enabled even during deduplication, because it
+     * needs to call {@link debug_backtrace()}
+     *
+     * @param mixed $args
+     */
+    public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void
+    {
+        if (self::$type === self::TYPE_NONE) {
+            return;
+        }
+
+        $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
+
+        // first check that the caller is not from a tests folder, in which case we always let deprecations pass
+        if (strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) {
+            $path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package . DIRECTORY_SEPARATOR;
+
+            if (strpos($backtrace[0]['file'], $path) === false) {
+                return;
+            }
+
+            if (strpos($backtrace[1]['file'], $path) !== false) {
+                return;
+            }
+        }
+
+        if (array_key_exists($link, self::$ignoredLinks)) {
+            self::$ignoredLinks[$link]++;
+        } else {
+            self::$ignoredLinks[$link] = 1;
+        }
+
+        if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) {
+            return;
+        }
+
+        if (isset(self::$ignoredPackages[$package])) {
+            return;
+        }
+
+        $message = sprintf($message, ...$args);
+
+        self::delegateTriggerToBackend($message, $backtrace, $link, $package);
+    }
+
+    /**
+     * @param array<mixed> $backtrace
+     */
+    private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void
+    {
+        if ((self::$type & self::TYPE_PSR_LOGGER) > 0) {
+            $context = [
+                'file' => $backtrace[0]['file'],
+                'line' => $backtrace[0]['line'],
+                'package' => $package,
+                'link' => $link,
+            ];
+
+            self::$logger->notice($message, $context);
+        }
+
+        if (! ((self::$type & self::TYPE_TRIGGER_ERROR) > 0)) {
+            return;
+        }
+
+        $message .= sprintf(
+            ' (%s:%d called by %s:%d, %s, package %s)',
+            self::basename($backtrace[0]['file']),
+            $backtrace[0]['line'],
+            self::basename($backtrace[1]['file']),
+            $backtrace[1]['line'],
+            $link,
+            $package
+        );
+
+        @trigger_error($message, E_USER_DEPRECATED);
+    }
+
+    /**
+     * A non-local-aware version of PHPs basename function.
+     */
+    private static function basename(string $filename): string
+    {
+        $pos = strrpos($filename, DIRECTORY_SEPARATOR);
+
+        if ($pos === false) {
+            return $filename;
+        }
+
+        return substr($filename, $pos + 1);
+    }
+
+    public static function enableTrackingDeprecations(): void
+    {
+        self::$type |= self::TYPE_TRACK_DEPRECATIONS;
+    }
+
+    public static function enableWithTriggerError(): void
+    {
+        self::$type |= self::TYPE_TRIGGER_ERROR;
+    }
+
+    public static function enableWithPsrLogger(LoggerInterface $logger): void
+    {
+        self::$type  |= self::TYPE_PSR_LOGGER;
+        self::$logger = $logger;
+    }
+
+    public static function withoutDeduplication(): void
+    {
+        self::$deduplication = false;
+    }
+
+    public static function disable(): void
+    {
+        self::$type          = self::TYPE_NONE;
+        self::$logger        = null;
+        self::$deduplication = true;
+
+        foreach (self::$ignoredLinks as $link => $count) {
+            self::$ignoredLinks[$link] = 0;
+        }
+    }
+
+    public static function ignorePackage(string $packageName): void
+    {
+        self::$ignoredPackages[$packageName] = true;
+    }
+
+    public static function ignoreDeprecations(string ...$links): void
+    {
+        foreach ($links as $link) {
+            self::$ignoredLinks[$link] = 0;
+        }
+    }
+
+    public static function getUniqueTriggeredDeprecationsCount(): int
+    {
+        return array_reduce(self::$ignoredLinks, static function (int $carry, int $count) {
+            return $carry + $count;
+        }, 0);
+    }
+
+    /**
+     * Returns each triggered deprecation link identifier and the amount of occurrences.
+     *
+     * @return array<string,int>
+     */
+    public static function getTriggeredDeprecations(): array
+    {
+        return self::$ignoredLinks;
+    }
+}

+ 66 - 0
vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php

@@ -0,0 +1,66 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\Deprecations\PHPUnit;
+
+use Doctrine\Deprecations\Deprecation;
+
+use function sprintf;
+
+trait VerifyDeprecations
+{
+    /** @var array<string,int> */
+    private $doctrineDeprecationsExpectations = [];
+
+    /** @var array<string,int> */
+    private $doctrineNoDeprecationsExpectations = [];
+
+    public function expectDeprecationWithIdentifier(string $identifier): void
+    {
+        $this->doctrineDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
+    }
+
+    public function expectNoDeprecationWithIdentifier(string $identifier): void
+    {
+        $this->doctrineNoDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
+    }
+
+    /**
+     * @before
+     */
+    public function enableDeprecationTracking(): void
+    {
+        Deprecation::enableTrackingDeprecations();
+    }
+
+    /**
+     * @after
+     */
+    public function verifyDeprecationsAreTriggered(): void
+    {
+        foreach ($this->doctrineDeprecationsExpectations as $identifier => $expectation) {
+            $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
+
+            $this->assertTrue(
+                $actualCount > $expectation,
+                sprintf(
+                    "Expected deprecation with identifier '%s' was not triggered by code executed in test.",
+                    $identifier
+                )
+            );
+        }
+
+        foreach ($this->doctrineNoDeprecationsExpectations as $identifier => $expectation) {
+            $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
+
+            $this->assertTrue(
+                $actualCount === $expectation,
+                sprintf(
+                    "Expected deprecation with identifier '%s' was triggered by code executed in test, but expected not to.",
+                    $identifier
+                )
+            );
+        }
+    }
+}

+ 22 - 0
vendor/doctrine/deprecations/phpcs.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<ruleset>
+    <arg name="basepath" value="."/>
+    <arg name="extensions" value="php"/>
+    <arg name="parallel" value="80"/>
+    <arg name="cache" value=".phpcs-cache"/>
+    <arg name="colors"/>
+
+    <!-- Ignore warnings, show progress of the run and show sniff names -->
+    <arg value="nps"/>
+
+    <config name="php_version" value="70100"/>
+
+    <!-- Directories to be checked -->
+    <file>lib</file>
+    <file>tests</file>
+
+    <!-- Include full Doctrine Coding Standard -->
+    <rule ref="Doctrine">
+        <exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint" />
+    </rule>
+</ruleset>

+ 19 - 0
vendor/doctrine/lexer/LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2006-2018 Doctrine Project
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 9 - 0
vendor/doctrine/lexer/README.md

@@ -0,0 +1,9 @@
+# Doctrine Lexer
+
+[![Build Status](https://github.com/doctrine/lexer/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/lexer/actions)
+
+Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.
+
+This lexer is used in Doctrine Annotations and in Doctrine ORM (DQL).
+
+https://www.doctrine-project.org/projects/lexer.html

+ 14 - 0
vendor/doctrine/lexer/UPGRADE.md

@@ -0,0 +1,14 @@
+Note about upgrading: Doctrine uses static and runtime mechanisms to raise
+awareness about deprecated code.
+
+- Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or
+  Static Analysis tools (like Psalm, phpstan)
+- Use of our low-overhead runtime deprecation API, details:
+  https://github.com/doctrine/deprecations/
+
+# Upgrade to 2.0.0
+
+`AbstractLexer::glimpse()` and `AbstractLexer::peek()` now return
+instances of `Doctrine\Common\Lexer\Token`, which is an array-like class
+Using it as an array is deprecated in favor of using properties of that class.
+Using `count()` on it is deprecated with no replacement.

+ 56 - 0
vendor/doctrine/lexer/composer.json

@@ -0,0 +1,56 @@
+{
+    "name": "doctrine/lexer",
+    "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
+    "license": "MIT",
+    "type": "library",
+    "keywords": [
+        "php",
+        "parser",
+        "lexer",
+        "annotations",
+        "docblock"
+    ],
+    "authors": [
+        {
+            "name": "Guilherme Blanco",
+            "email": "guilhermeblanco@gmail.com"
+        },
+        {
+            "name": "Roman Borschel",
+            "email": "roman@code-factory.org"
+        },
+        {
+            "name": "Johannes Schmitt",
+            "email": "schmittjoh@gmail.com"
+        }
+    ],
+    "homepage": "https://www.doctrine-project.org/projects/lexer.html",
+    "require": {
+        "php": "^7.1 || ^8.0",
+        "doctrine/deprecations": "^1.0"
+    },
+    "require-dev": {
+        "doctrine/coding-standard": "^9 || ^10",
+        "phpstan/phpstan": "^1.3",
+        "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+        "psalm/plugin-phpunit": "^0.18.3",
+        "vimeo/psalm": "^4.11 || ^5.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Doctrine\\Common\\Lexer\\": "src"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Doctrine\\Tests\\Common\\Lexer\\": "tests"
+        }
+    },
+    "config": {
+        "allow-plugins": {
+            "composer/package-versions-deprecated": true,
+            "dealerdirect/phpcodesniffer-composer-installer": true
+        },
+        "sort-packages": true
+    }
+}

+ 336 - 0
vendor/doctrine/lexer/src/AbstractLexer.php

@@ -0,0 +1,336 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\Common\Lexer;
+
+use ReflectionClass;
+use UnitEnum;
+
+use function get_class;
+use function implode;
+use function preg_split;
+use function sprintf;
+use function substr;
+
+use const PREG_SPLIT_DELIM_CAPTURE;
+use const PREG_SPLIT_NO_EMPTY;
+use const PREG_SPLIT_OFFSET_CAPTURE;
+
+/**
+ * Base class for writing simple lexers, i.e. for creating small DSLs.
+ *
+ * @template T of UnitEnum|string|int
+ */
+abstract class AbstractLexer
+{
+    /**
+     * Lexer original input string.
+     *
+     * @var string
+     */
+    private $input;
+
+    /**
+     * Array of scanned tokens.
+     *
+     * @var list<Token<T>>
+     */
+    private $tokens = [];
+
+    /**
+     * Current lexer position in input string.
+     *
+     * @var int
+     */
+    private $position = 0;
+
+    /**
+     * Current peek of current lexer position.
+     *
+     * @var int
+     */
+    private $peek = 0;
+
+    /**
+     * The next token in the input.
+     *
+     * @var mixed[]|null
+     * @psalm-var Token<T>|null
+     */
+    public $lookahead;
+
+    /**
+     * The last matched/seen token.
+     *
+     * @var mixed[]|null
+     * @psalm-var Token<T>|null
+     */
+    public $token;
+
+    /**
+     * Composed regex for input parsing.
+     *
+     * @var string|null
+     */
+    private $regex;
+
+    /**
+     * Sets the input data to be tokenized.
+     *
+     * The Lexer is immediately reset and the new input tokenized.
+     * Any unprocessed tokens from any previous input are lost.
+     *
+     * @param string $input The input to be tokenized.
+     *
+     * @return void
+     */
+    public function setInput($input)
+    {
+        $this->input  = $input;
+        $this->tokens = [];
+
+        $this->reset();
+        $this->scan($input);
+    }
+
+    /**
+     * Resets the lexer.
+     *
+     * @return void
+     */
+    public function reset()
+    {
+        $this->lookahead = null;
+        $this->token     = null;
+        $this->peek      = 0;
+        $this->position  = 0;
+    }
+
+    /**
+     * Resets the peek pointer to 0.
+     *
+     * @return void
+     */
+    public function resetPeek()
+    {
+        $this->peek = 0;
+    }
+
+    /**
+     * Resets the lexer position on the input to the given position.
+     *
+     * @param int $position Position to place the lexical scanner.
+     *
+     * @return void
+     */
+    public function resetPosition($position = 0)
+    {
+        $this->position = $position;
+    }
+
+    /**
+     * Retrieve the original lexer's input until a given position.
+     *
+     * @param int $position
+     *
+     * @return string
+     */
+    public function getInputUntilPosition($position)
+    {
+        return substr($this->input, 0, $position);
+    }
+
+    /**
+     * Checks whether a given token matches the current lookahead.
+     *
+     * @param T $type
+     *
+     * @return bool
+     */
+    public function isNextToken($type)
+    {
+        return $this->lookahead !== null && $this->lookahead->isA($type);
+    }
+
+    /**
+     * Checks whether any of the given tokens matches the current lookahead.
+     *
+     * @param list<T> $types
+     *
+     * @return bool
+     */
+    public function isNextTokenAny(array $types)
+    {
+        return $this->lookahead !== null && $this->lookahead->isA(...$types);
+    }
+
+    /**
+     * Moves to the next token in the input string.
+     *
+     * @return bool
+     */
+    public function moveNext()
+    {
+        $this->peek      = 0;
+        $this->token     = $this->lookahead;
+        $this->lookahead = isset($this->tokens[$this->position])
+            ? $this->tokens[$this->position++] : null;
+
+        return $this->lookahead !== null;
+    }
+
+    /**
+     * Tells the lexer to skip input tokens until it sees a token with the given value.
+     *
+     * @param T $type The token type to skip until.
+     *
+     * @return void
+     */
+    public function skipUntil($type)
+    {
+        while ($this->lookahead !== null && ! $this->lookahead->isA($type)) {
+            $this->moveNext();
+        }
+    }
+
+    /**
+     * Checks if given value is identical to the given token.
+     *
+     * @param string     $value
+     * @param int|string $token
+     *
+     * @return bool
+     */
+    public function isA($value, $token)
+    {
+        return $this->getType($value) === $token;
+    }
+
+    /**
+     * Moves the lookahead token forward.
+     *
+     * @return mixed[]|null The next token or NULL if there are no more tokens ahead.
+     * @psalm-return Token<T>|null
+     */
+    public function peek()
+    {
+        if (isset($this->tokens[$this->position + $this->peek])) {
+            return $this->tokens[$this->position + $this->peek++];
+        }
+
+        return null;
+    }
+
+    /**
+     * Peeks at the next token, returns it and immediately resets the peek.
+     *
+     * @return mixed[]|null The next token or NULL if there are no more tokens ahead.
+     * @psalm-return Token<T>|null
+     */
+    public function glimpse()
+    {
+        $peek       = $this->peek();
+        $this->peek = 0;
+
+        return $peek;
+    }
+
+    /**
+     * Scans the input string for tokens.
+     *
+     * @param string $input A query string.
+     *
+     * @return void
+     */
+    protected function scan($input)
+    {
+        if (! isset($this->regex)) {
+            $this->regex = sprintf(
+                '/(%s)|%s/%s',
+                implode(')|(', $this->getCatchablePatterns()),
+                implode('|', $this->getNonCatchablePatterns()),
+                $this->getModifiers()
+            );
+        }
+
+        $flags   = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE;
+        $matches = preg_split($this->regex, $input, -1, $flags);
+
+        if ($matches === false) {
+            // Work around https://bugs.php.net/78122
+            $matches = [[$input, 0]];
+        }
+
+        foreach ($matches as $match) {
+            // Must remain before 'value' assignment since it can change content
+            $type = $this->getType($match[0]);
+
+            $this->tokens[] = new Token(
+                $match[0],
+                $type,
+                $match[1]
+            );
+        }
+    }
+
+    /**
+     * Gets the literal for a given token.
+     *
+     * @param T $token
+     *
+     * @return int|string
+     */
+    public function getLiteral($token)
+    {
+        if ($token instanceof UnitEnum) {
+            return get_class($token) . '::' . $token->name;
+        }
+
+        $className = static::class;
+
+        $reflClass = new ReflectionClass($className);
+        $constants = $reflClass->getConstants();
+
+        foreach ($constants as $name => $value) {
+            if ($value === $token) {
+                return $className . '::' . $name;
+            }
+        }
+
+        return $token;
+    }
+
+    /**
+     * Regex modifiers
+     *
+     * @return string
+     */
+    protected function getModifiers()
+    {
+        return 'iu';
+    }
+
+    /**
+     * Lexical catchable patterns.
+     *
+     * @return string[]
+     */
+    abstract protected function getCatchablePatterns();
+
+    /**
+     * Lexical non-catchable patterns.
+     *
+     * @return string[]
+     */
+    abstract protected function getNonCatchablePatterns();
+
+    /**
+     * Retrieve token type. Also processes the token value if necessary.
+     *
+     * @param string $value
+     *
+     * @return T|null
+     */
+    abstract protected function getType(&$value);
+}

+ 129 - 0
vendor/doctrine/lexer/src/Token.php

@@ -0,0 +1,129 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\Common\Lexer;
+
+use ArrayAccess;
+use Doctrine\Deprecations\Deprecation;
+use ReturnTypeWillChange;
+use UnitEnum;
+
+use function in_array;
+
+/**
+ * @template T of UnitEnum|string|int
+ * @implements ArrayAccess<string,mixed>
+ */
+final class Token implements ArrayAccess
+{
+    /**
+     * The string value of the token in the input string
+     *
+     * @readonly
+     * @var string|int
+     */
+    public $value;
+
+    /**
+     * The type of the token (identifier, numeric, string, input parameter, none)
+     *
+     * @readonly
+     * @var T|null
+     */
+    public $type;
+
+    /**
+     * The position of the token in the input string
+     *
+     * @readonly
+     * @var int
+     */
+    public $position;
+
+    /**
+     * @param string|int $value
+     * @param T|null     $type
+     */
+    public function __construct($value, $type, int $position)
+    {
+        $this->value    = $value;
+        $this->type     = $type;
+        $this->position = $position;
+    }
+
+    /** @param T ...$types */
+    public function isA(...$types): bool
+    {
+        return in_array($this->type, $types, true);
+    }
+
+    /**
+     * @deprecated Use the value, type or position property instead
+     * {@inheritDoc}
+     */
+    public function offsetExists($offset): bool
+    {
+        Deprecation::trigger(
+            'doctrine/lexer',
+            'https://github.com/doctrine/lexer/pull/79',
+            'Accessing %s properties via ArrayAccess is deprecated, use the value, type or position property instead',
+            self::class
+        );
+
+        return in_array($offset, ['value', 'type', 'position'], true);
+    }
+
+    /**
+     * @deprecated Use the value, type or position property instead
+     * {@inheritDoc}
+     *
+     * @param array-key $offset
+     *
+     * @return mixed
+     */
+    #[ReturnTypeWillChange]
+    public function offsetGet($offset)
+    {
+        Deprecation::trigger(
+            'doctrine/lexer',
+            'https://github.com/doctrine/lexer/pull/79',
+            'Accessing %s properties via ArrayAccess is deprecated, use the value, type or position property instead',
+            self::class
+        );
+
+        return $this->$offset;
+    }
+
+    /**
+     * @deprecated no replacement planned
+     * {@inheritDoc}
+     */
+    public function offsetSet($offset, $value): void
+    {
+        Deprecation::trigger(
+            'doctrine/lexer',
+            'https://github.com/doctrine/lexer/pull/79',
+            'Setting %s properties via ArrayAccess is deprecated',
+            self::class
+        );
+
+        $this->$offset = $value;
+    }
+
+    /**
+     * @deprecated no replacement planned
+     * {@inheritDoc}
+     */
+    public function offsetUnset($offset): void
+    {
+        Deprecation::trigger(
+            'doctrine/lexer',
+            'https://github.com/doctrine/lexer/pull/79',
+            'Setting %s properties via ArrayAccess is deprecated',
+            self::class
+        );
+
+        $this->$offset = null;
+    }
+}

+ 1 - 0
vendor/hg/apidoc/.gitignore

@@ -0,0 +1 @@
+.idea

+ 21 - 0
vendor/hg/apidoc/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 HG
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 86 - 0
vendor/hg/apidoc/README.md

@@ -0,0 +1,86 @@
+<div align="center">
+    <img width="160"  src="https://docs.apidoc.icu/logo.png">
+</div>
+
+<h1 align="center" style="margin-top: 0;padding-top: 0;">
+  Apidoc
+</h1>
+
+<div align="center">
+ 基于PHP的注解生成API文档及Api接口开发工具
+</div>
+
+<div align="center" style="margin-top:10px;margin-bottom:50px;">
+<a href="https://packagist.org/packages/hg/apidoc"><img src="https://img.shields.io/packagist/v/hg/apidoc"></a>
+<a href="https://packagist.org/packages/hg/apidoc"><img src="https://img.shields.io/packagist/dt/hg/apidoc"></a>
+<a href="https://packagist.org/packages/hg/apidoc"><img src="https://img.shields.io/packagist/l/hg/apidoc"></a>
+<a href="https://github.com/HGthecode/apidoc-php"><img src="https://img.shields.io/github/issues/HGthecode/apidoc-php"></a>
+<a href="https://github.com/HGthecode/apidoc-php"><img src="https://img.shields.io/github/forks/HGthecode/apidoc-php"></a>
+
+</div>
+
+
+## 🤷‍♀️ Apidoc是什么?
+
+Apidoc是一个通过解析注解生成Api接口文档的PHP composer扩展,兼容Laravel、ThinkPHP、Hyperf、Webman等框架;
+全面的注解引用、数据表字段引用,简单的注解即可生成Api文档,而Apidoc不仅于接口文档,在线接口调试、Mock调试数据、调试事件处理、Json/TypeScript生成、接口生成器、代码生成器等诸多实用功能,致力于提高Api接口开发效率。
+
+
+## ✨特性
+
+- 开箱即用:无繁杂的配置、安装后按文档编写注释即可自动生成API文档。
+- 轻松编写:支持通用注释引用、业务逻辑层、数据表字段的引用,几句注释即可完成。
+- 在线调试:在线文档可直接调试,并支持全局请求/Mock参数/事件处理,接口调试省时省力。
+- 安全高效:支持访问密码验证、应用/版本独立密码;支持文档缓存。
+- 多应用/多版本:可适应各种单应用、多应用、多版本的项目的Api管理。
+- 分组/Tag:可对控制器/接口进行多级分组或定义Tag。
+- Markdown文档:支持.md文件的文档展示。
+- Json/TypeScript生成:文档自动生成接口的Json及TypeScript。
+- 代码生成器:配置+模板即可快速生成代码及数据表的创建,大大提高工作效率。
+
+
+
+## 📌兼容
+
+以下框架已内置兼容,可开箱即用。其它框架可参考[其它框架安装说明](https://docs.apidoc.icu/guide/install/other.html)进行手动兼容。
+
+|框架|版本|说明|
+|-|-|-|
+|ThinkPHP|5.1、6.x||
+|Laravel|8.x、9.x|其它版本未测试|
+|Hyperf|2.x|其它版本未测试|
+|Webman|2.x|需手动配置兼容|
+
+
+## 📖使用文档
+
+[ApiDoc 文档](https://docs.apidoc.icu/)
+
+
+## 🏆支持我们
+
+如果本项目对您有所帮助,请点个Star支持我们
+
+- [Github](https://github.com/HGthecode/apidoc-php) -> <a href="https://github.com/HGthecode/apidoc-php" target="_blank">
+  <img height="22" src="https://img.shields.io/github/stars/HGthecode/apidoc-php?style=social" class="attachment-full size-full" alt="Star me on GitHub" data-recalc-dims="1" /></a>
+
+- [Gitee](https://gitee.com/hg-code/apidoc-php) -> <a href="https://gitee.com/hg-code/apidoc-php/stargazers"><img src="https://gitee.com/hg-code/apidoc-php/badge/star.svg" alt="star"></a>
+
+
+## 🌐交流群
+
+![QQ群](https://docs.apidoc.icu/qq-qun.png)
+
+
+
+## 💡鸣谢
+
+<a href="https://github.com/doctrine/annotations" target="_blank">doctrine/annotations</a>
+
+
+## 🔗链接
+ <a href="https://github.com/HGthecode/apidoc-ui" target="_blank">ApiDoc UI</a>
+ 
+ <a href="https://github.com/HGthecode/apidoc-demos" target="_blank">ApiDoc Demo</a>
+
+

+ 49 - 0
vendor/hg/apidoc/composer.json

@@ -0,0 +1,49 @@
+{
+    "name": "hg/apidoc",
+    "description": "注释自动生成API文档、在线调试、Markdown文档、代码生成器",
+    "keywords": [
+        "apidoc",
+        "api文档",
+        "接口文档",
+        "自动生成api",
+        "注释生成",
+        "php接口文档",
+        "php api文档",
+        "Markdown"
+      ],
+    "require": {
+        "php": ">=7.1",
+        "doctrine/annotations": "^1.6"
+    },
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "hg-code",
+            "email": "376401263@qq.com"
+        }
+    ],
+    "autoload": {
+        "psr-4": {
+            "hg\\apidoc\\": "src/"
+        }
+    },
+    "extra": {
+        "laravel": {
+            "providers": [
+                "hg\\apidoc\\providers\\LaravelService"
+            ]
+        },
+        "think": {
+            "services": [
+                "hg\\apidoc\\providers\\ThinkPHPService"
+            ],
+            "config": {
+                "apidoc": "src/config.php"
+            }
+        },
+        "hyperf": {
+            "config": "hg\\apidoc\\ConfigProvider"
+        }
+    },
+    "minimum-stability": "dev"
+}

+ 191 - 0
vendor/hg/apidoc/src/Auth.php

@@ -0,0 +1,191 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc;
+
+use hg\apidoc\exception\ErrorException;
+use hg\apidoc\utils\ConfigProvider;
+use hg\apidoc\utils\Helper;
+
+class Auth
+{
+    protected $authConfig = [];
+
+    public function __construct($config)
+    {
+        $authConfig = !empty($config['auth'])?$config['auth']:[];
+        if (empty($authConfig['secret_key'])){
+            $authConfig['secret_key'] = "apidoc#hgcode";
+        }
+        if (empty($authConfig['expire'])){
+            $authConfig['expire'] = 86400;
+        }
+        $this->authConfig = $authConfig;
+    }
+
+    /**
+     * 验证密码是否正确
+     * @param $password
+     * @return false|string
+     */
+    public function verifyAuth(string $password, string $appKey)
+    {
+        $authConfig = $this->authConfig;
+        if (!empty($appKey)) {
+            $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+            $currentApp  = $currentAppConfig['appConfig'];
+            if (!empty($currentApp) && !empty($currentApp['password'])) {
+                // 应用密码
+                if (md5($currentApp['password']) === $password) {
+                    return $this->createToken($currentApp['password'],$authConfig['expire']);
+                }
+                throw new ErrorException("password error");
+            }
+        }
+        if ($authConfig['enable']) {
+            // 密码验证
+            if (md5($authConfig['password']) === $password) {
+                return $this->createToken($authConfig['password'],$authConfig['expire']);
+            }
+            throw new ErrorException("password error");
+        }
+        return false;
+    }
+
+    /**
+     * 验证token是否可用
+     * @param $request
+     * @return bool
+     */
+    public function checkAuth($params=[]): bool
+    {
+        $authConfig = $this->authConfig;
+        $token = !empty($params['token'])?$params['token']:"";
+        $appKey = !empty($params['appKey'])?$params['appKey']:"";
+        if (!empty($appKey)) {
+            $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+            $currentApp  = $currentAppConfig['appConfig'];
+            if (!empty($currentApp) && !empty($currentApp['password'])) {
+                if (empty($token)) {
+                    throw new ErrorException("token not found");
+                }
+                // 应用密码
+                if ($this->checkToken($token, $currentApp['password'])) {
+                    return true;
+                } else {
+                    throw new ErrorException("token error");
+                }
+            } else if (empty($authConfig['enable'])) {
+                return true;
+            }
+        }
+        if($authConfig['enable'] && empty($token)){
+            throw new ErrorException("token not found");
+        }else if (!empty($token) && !$this->checkToken($token, "")  && $authConfig['enable']) {
+            throw new ErrorException("token error");
+        }
+        return true;
+    }
+
+
+    /**
+     * 获取tokencode
+     * @param string $password
+     * @return string
+     */
+    protected static function getTokenCode(string $password): string
+    {
+        return md5(md5($password));
+    }
+
+
+    /**
+     * 创建token
+     * @param string $password
+     * @return string
+     */
+    protected function createToken(string $password): string
+    {
+        $authConfig = $this->authConfig;
+        $data = [
+            'key'=>static::getTokenCode($password),
+            'expire'=>time()+$authConfig['expire']
+        ];
+        $code = json_encode($data);
+        return static::handleToken($code, "CE",$authConfig['secret_key']);
+    }
+
+    /**
+     * 验证token是否可用
+     * @param $token
+     * @return bool
+     */
+    protected function checkToken(string $token, string $password): bool
+    {
+        $authConfig = $this->authConfig;
+        if (empty($password)){
+            $password = $authConfig['password'];
+        }
+        $decode = static::handleToken($token, "DE",$authConfig['secret_key']);
+        $deData = json_decode($decode,true);
+
+        if (!empty($deData['key']) && $deData['key'] === static::getTokenCode($password) && !empty($deData['expire']) && $deData['expire']>time()){
+            return true;
+        }
+
+        return false;
+    }
+
+
+
+    /**
+     * 处理token
+     * @param $string
+     * @param string $operation
+     * @param string $key
+     * @param int $expiry
+     * @return false|string
+     */
+    protected static function handleToken(string $string, string $operation = 'DE', string $key = '', int $expiry = 0):string
+    {
+        $ckey_length   = 4;
+        $key           = md5($key);
+        $keya          = md5(substr($key, 0, 16));
+        $keyb          = md5(substr($key, 16, 16));
+        $keyc          = $ckey_length ? ($operation == 'DE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : '';
+        $cryptkey      = $keya . md5($keya . $keyc);
+        $key_length    = strlen($cryptkey);
+        $string        = $operation == 'DE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
+        $string_length = strlen($string);
+        $result        = '';
+        $box           = range(0, 255);
+        $rndkey        = array();
+        for ($i = 0; $i <= 255; $i++) {
+            $rndkey[$i] = ord($cryptkey[$i % $key_length]);
+        }
+        for ($j = $i = 0; $i < 256; $i++) {
+            $j       = ($j + $box[$i] + $rndkey[$i]) % 256;
+            $tmp     = $box[$i];
+            $box[$i] = $box[$j];
+            $box[$j] = $tmp;
+        }
+        for ($a = $j = $i = 0; $i < $string_length; $i++) {
+            $a       = ($a + 1) % 256;
+            $j       = ($j + $box[$a]) % 256;
+            $tmp     = $box[$a];
+            $box[$a] = $box[$j];
+            $box[$j] = $tmp;
+            $result  .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
+        }
+        if ($operation == 'DE') {
+            if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
+                return substr($result, 26);
+            } else {
+                return '';
+            }
+        } else {
+            return $keyc . str_replace('=', '', base64_encode($result));
+        }
+    }
+
+}

+ 24 - 0
vendor/hg/apidoc/src/ConfigProvider.php

@@ -0,0 +1,24 @@
+<?php
+declare(strict_types=1);
+/**
+ * Hyperf the Config Provider
+ */
+namespace hg\apidoc;
+
+class ConfigProvider
+{
+    public function __invoke(): array
+    {
+        return [
+            'dependencies' => [],
+            'publish' => [
+                [
+                    'id' => 'config',
+                    'description' => 'The config of apidoc.',
+                    'source' => __DIR__ . '/config.php',
+                    'destination' => BASE_PATH . '/config/autoload/apidoc.php',
+                ],
+            ],
+        ];
+    }
+}

+ 278 - 0
vendor/hg/apidoc/src/Controller.php

@@ -0,0 +1,278 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc;
+
+use hg\apidoc\parses\ParseApiDetail;
+use hg\apidoc\parses\ParseCodeTemplate;
+use hg\apidoc\parses\ParseApiMenus;
+use hg\apidoc\parses\ParseMarkdown;
+use hg\apidoc\utils\Cache;
+use hg\apidoc\utils\ConfigProvider;
+use hg\apidoc\utils\DirAndFile;
+use hg\apidoc\utils\Helper;
+use hg\apidoc\utils\Lang;
+use hg\apidoc\utils\Request;
+use hg\apidoc\exception\ErrorException;
+
+
+class Controller
+{
+
+    protected $config;
+
+    protected $requestParams=[];
+
+    protected $lang="";
+
+
+    public function init($checkAuth=false){
+        $this->config = ConfigProvider::get();
+        if (isset($this->config['enable']) && $this->config['enable']===false){
+            throw new ErrorException("apidoc close");
+        }
+        if (!empty($this->config['request_params'])){
+            $this->requestParams = $this->config['request_params'];
+        }else{
+            $this->requestParams = (new Request())->param();
+        }
+        if (!empty($this->requestParams['lang']) && !empty($this->config['lang_register_function'])){
+            $this->lang = $this->requestParams['lang'];
+            $this->config['lang_register_function']($this->lang);
+        }
+        if($checkAuth){
+            (new Auth($this->config))->checkAuth($this->requestParams);
+        }
+    }
+
+
+
+    /**
+     * 获取配置
+     * @return \think\response\Json
+     */
+    public function getConfig(){
+        $this->init(true);
+        $config = ConfigProvider::getFeConfig();
+        return Helper::showJson(0,"",$config);
+    }
+
+    /**
+     * 验证密码
+     */
+    public function verifyAuth(){
+        $this->init();
+        $config = $this->config;
+        $params = $this->requestParams;
+        if (empty($params['password'])){
+            throw new ErrorException( "password not found");
+        }
+        $appKey = !empty($params['appKey'])?$params['appKey']:"";
+
+        if (!$appKey && !(!empty($config['auth']) && $config['auth']['enable'])) {
+            return false;
+        }
+        $hasAuth = (new Auth($config))->verifyAuth($params['password'],$appKey);
+        $res = [
+            "token"=>$hasAuth
+        ];
+        return Helper::showJson(0,"",$res);
+
+
+    }
+
+    /**
+     * 获取api文档菜单
+     */
+    public function getApiMenus(){
+        $this->init(true);
+        $config = $this->config;
+        $params = $this->requestParams;
+
+
+        if (!empty($params['appKey'])){
+            $appKey = $params['appKey'];
+        }
+        $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+        $currentApp  = $currentAppConfig['appConfig'];
+
+        if (!empty($config['cache']) && $config['cache']['enable']){
+            $cacheKey = Helper::getCacheKey('apiMenu',$appKey,$this->lang);
+            $cacheData = (new Cache())->get($cacheKey);
+            if ($cacheData && empty($params['reload'])){
+                $apiData = $cacheData;
+            }else{
+                // 生成数据并缓存
+                $apiData = (new ParseApiMenus($config))->renderApiMenus($appKey);
+                (new Cache())->set($cacheKey,$apiData);
+            }
+        }else{
+            // 生成数据
+            $apiData = (new ParseApiMenus($config))->renderApiMenus($appKey);
+        }
+
+
+        // 接口分组
+        if (!empty($currentApp['groups'])){
+            $data = ParseApiMenus::mergeApiGroup($apiData['data'],$currentApp['groups']);
+        }else{
+            $data = $apiData['data'];
+        }
+        $groups=!empty($currentApp['groups'])?$currentApp['groups']:[];
+        $json=[
+            'data'=>$data,
+            'app'=>$currentApp,
+            'groups'=>$groups,
+            'tags'=>$apiData['tags'],
+        ];
+
+        return Helper::showJson(0,"",$json);
+    }
+
+
+    public function getApiDetail(){
+        $this->init(true);
+        $config = $this->config;
+        $params = $this->requestParams;
+        if (empty($params['path'])){
+            throw new ErrorException("path not found");
+        }
+        $appKey = $params['appKey'];
+        $apiKey = urldecode($params['path']);
+        $pathArr   = explode("@", $apiKey);
+        $classPath = $pathArr[0];
+        $method = $pathArr[1];
+
+        if (!empty($config['cache']) && $config['cache']['enable']){
+            $cacheKey = Helper::getCacheKey('apiDetail',$appKey,$this->lang,$params['path']);
+            $cacheData = (new Cache())->get($cacheKey);
+            if ($cacheData && empty($params['reload'])){
+                $res = $cacheData;
+            }else{
+                // 生成数据并缓存
+                $res = (new ParseApiDetail($config))->renderApiDetail($appKey,$classPath,$method);
+                (new Cache())->set($cacheKey,$res);
+            }
+        }else{
+            // 生成数据
+            $res = (new ParseApiDetail($config))->renderApiDetail($appKey,$classPath,$method);
+        }
+        $res['appKey']=$appKey;
+        return Helper::showJson(0,"",$res);
+    }
+
+
+    public function getMdMenus(){
+        $this->init(true);
+        $config = $this->config;
+        $params = $this->requestParams;
+        $appKey = "";
+        if (!empty($params['appKey'])){
+            // 获取指定应用
+            $appKey = $params['appKey'];
+        }
+        $docs = (new ParseMarkdown($config))->getDocsMenu($appKey,$this->lang);
+        return Helper::showJson(0,"",$docs);
+
+    }
+
+    /**
+     * 获取md文档内容
+     * @return \think\response\Json
+     */
+    public function getMdDetail(){
+        $this->init(true);
+        $config = $this->config;
+        $params = $this->requestParams;
+        try {
+            if (empty($params['path'])){
+                throw new ErrorException("mdPath not found");
+            }
+            if (empty($params['appKey'])){
+                throw new ErrorException("appkey not found");
+            }
+
+            $path = urldecode($params['path']);
+            $content = (new ParseMarkdown($config))->getContent($params['appKey'],$path,$this->lang);
+            $res = [
+                'content'=>$content,
+            ];
+            return Helper::showJson(0,"",$res);
+
+        } catch (ErrorException $e) {
+            return Helper::showJson($e->getCode(),$e->getMessage());
+        }
+    }
+
+
+    public function createGenerator(){
+        $this->init(true);
+        $config = $this->config;
+        $params = $this->requestParams;
+        $res = (new generator\Index($config))->create($params);
+        return Helper::showJson(0,"",$res);
+    }
+
+    public function cancelAllCache(){
+        $this->init(true);
+        $config = $this->config;
+        $res = DirAndFile::deleteDir(APIDOC_STORAGE_PATH . $config['cache']['folder'].'\\'.'apis');
+        return Helper::showJson(0,"",$res);
+    }
+
+    public function createAllCache(){
+        $this->init(true);
+        $config = $this->config;
+        $params = $this->requestParams;
+        $apps = Helper::getAllApps($config['apps']);
+        $cache = new Cache();
+        DirAndFile::deleteDir(APIDOC_STORAGE_PATH . $config['cache']['folder'].'\\'.'apis');
+        if (!empty($apps) && count($apps)){
+            try {
+                foreach ($apps as $app) {
+                    // 缓存菜单
+                    $appKey = $app['appKey'];
+                    $controllerData = (new ParseApiMenus($config))->renderApiMenus($appKey);
+                    if (!empty($controllerData['data']) && count($controllerData['data'])){
+                        foreach ($controllerData['data'] as $controller) {
+                            if (!empty($controller['children']) && count($controller['children'])){
+                                foreach ($controller['children'] as $item) {
+                                    if (!empty($item['url']) && !empty($item['menuKey'])){
+                                        $pathArr   = explode("@", urldecode($item['menuKey']));
+                                        $classPath = $pathArr[0];
+                                        $method = $pathArr[1];
+                                        $apiDetail = (new ParseApiDetail($config))->renderApiDetail($appKey,$classPath,$method);
+                                        $apiDetailCacheKey = Helper::getCacheKey('apiDetail',$appKey,$this->lang,$item['menuKey']);
+                                        $cache->set($apiDetailCacheKey,$apiDetail);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    $cacheKey = Helper::getCacheKey('apiMenu',$appKey,$this->lang);
+                    $cache->set($cacheKey,$controllerData);
+                }
+            } catch (\ReflectionException $e) {
+                DirAndFile::deleteDir(APIDOC_STORAGE_PATH . $config['cache']['folder'].'\\'.'apis');
+                throw new ErrorException($e->getMessage());
+            }
+        }
+
+        return Helper::showJson(0,"",true);
+    }
+
+    public function renderCodeTemplate(){
+        $this->init(true);
+        $config = $this->config;
+        $params = $this->requestParams;
+
+        $code = (new ParseCodeTemplate($config))->renderCode($params);
+
+        return Helper::showJson(0,"",[
+            'code'=>$code
+        ]);
+
+    }
+
+
+}

+ 56 - 0
vendor/hg/apidoc/src/annotation/AddField.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 添加模型的字段
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class AddField extends Annotation
+{
+    /**
+     * 字段名
+     * @var string
+     */
+    public $name;
+    /**
+     * 类型
+     * @var string
+     */
+    public $type = 'string';
+
+
+    /**
+     * 默认值
+     * @var string
+     */
+    public $default;
+
+    /**
+     * 描述
+     * @var string
+     */
+    public $desc;
+
+    /**
+     * 必须
+     * @var bool
+     */
+    public $require = false;
+
+    /**
+     * 说明md内容
+     * @var string
+     */
+    public $md;
+
+    /**
+     * 引入说明md内容
+     * @var string
+     */
+    public $mdRef;
+}

+ 29 - 0
vendor/hg/apidoc/src/annotation/After.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 接口调试前置事件
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+final class After extends EventBase
+{
+
+
+
+    /**
+     * 事件
+     * @Enum({"setGlobalHeader", "setGlobalQuery", "setGlobalBody","clearGlobalHeader","clearGlobalQuery","clearGlobalBody","ajax"})
+     * @var string
+     */
+    public $event;
+
+
+
+
+}

+ 14 - 0
vendor/hg/apidoc/src/annotation/Author.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 作者
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class Author extends Annotation
+{}

+ 23 - 0
vendor/hg/apidoc/src/annotation/Before.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 接口调试前置事件
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+final class Before extends EventBase
+{
+    /**
+     * 事件
+     * @Enum({"setHeader","setQuery","setBody", "clearHeader", "clearQuery", "clearBody", "setGlobalHeader", "setGlobalQuery", "setGlobalBody","clearGlobalHeader","clearGlobalQuery","clearGlobalBody","ajax"})
+     * @var string
+     */
+    public $event;
+
+}

+ 14 - 0
vendor/hg/apidoc/src/annotation/ContentType.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 调试时请求内容
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class ContentType extends Annotation
+{}

+ 16 - 0
vendor/hg/apidoc/src/annotation/Desc.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 描述
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","CLASS"})
+ */
+class Desc extends Annotation
+{
+
+}

+ 67 - 0
vendor/hg/apidoc/src/annotation/EventBase.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+abstract class EventBase extends Annotation
+{
+
+    /**
+     * 名称
+     * @var string
+     */
+    public $name;
+
+    /**
+     * key
+     * @var string
+     */
+    public $key;
+
+    /**
+     * 处理值的方法
+     * @var string
+     */
+    public $handleValue;
+
+    /**
+     * ajax时的url
+     * @var string
+     */
+    public $url;
+
+    /**
+     * ajax时的Method
+     * @Enum({"GET", "POST", "PUT", "DELETE"})
+     * @var string
+     */
+    public $method;
+
+    /**
+     * ajax时的 content-type
+     * @var string
+     */
+    public $contentType;
+
+    /**
+     * 字段描述
+     * @var string
+     */
+    public $desc;
+
+    /**
+     * 引用
+     * @var string
+     */
+    public $ref;
+
+    /**
+     * 设置全局参数setGlobalHeader、setGlobalParam时指定应用
+     * @var string
+     */
+    public $appKey;
+
+
+
+}

+ 14 - 0
vendor/hg/apidoc/src/annotation/Field.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 指定获取模型的字段
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class Field extends Annotation
+{}

+ 14 - 0
vendor/hg/apidoc/src/annotation/Group.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 分组
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"CLASS"})
+ */
+class Group extends Annotation
+{}

+ 40 - 0
vendor/hg/apidoc/src/annotation/Header.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+
+/**
+ * 请求头
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class Header extends ParamBase
+{
+    /**
+     * 必须
+     * @var bool
+     */
+    public $require = false;
+
+    /**
+     * 类型
+     * @var string
+     */
+    public $type;
+
+    /**
+     * 引入
+     * @var string
+     */
+    public $ref;
+
+
+    /**
+     * 描述
+     * @var string
+     */
+    public $desc;
+
+
+}

+ 20 - 0
vendor/hg/apidoc/src/annotation/Md.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * Markdown
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class Md extends Annotation
+{
+    /**
+     * 引入md内容
+     * @var string
+     */
+    public $ref;
+}

+ 14 - 0
vendor/hg/apidoc/src/annotation/Method.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 请求类型
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class Method extends Annotation
+{}

+ 33 - 0
vendor/hg/apidoc/src/annotation/Param.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+
+/**
+ * 请求参数
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+final class Param extends ParamBase
+{
+
+
+    /**
+     * 必须
+     * @var bool
+     */
+    public $require;
+    
+    /**
+     * 引入
+     * @var string
+     */
+    public $ref;
+
+    /**
+     * mock
+     * @var string
+     */
+    public $mock;
+}

+ 74 - 0
vendor/hg/apidoc/src/annotation/ParamBase.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+abstract class ParamBase extends Annotation
+{
+
+    /**
+     * 类型
+     * @Enum({"string", "integer", "int", "boolean", "array", "double", "object", "tree", "file","float","date","time","datetime"})
+     * @var string
+     */
+    public $type;
+
+
+    /**
+     * 默认值
+     * @var string
+     */
+    public $default;
+
+    /**
+     * 描述
+     * @var string
+     */
+    public $desc;
+
+    /**
+     * 为tree类型时指定children字段
+     * @var string
+     */
+    public $childrenField = '';
+
+    /**
+     * 为tree类型时指定children字段说明
+     * @var string
+     */
+    public $childrenDesc = 'children';
+
+    /**
+     * 为array类型时指定子节点类型
+     *  @Enum({"string", "int", "boolean", "array", "object"})
+     * @var string
+     */
+    public $childrenType = '';
+
+    /**
+     * 指定引入的字段
+     * @var string
+     */
+    public $field;
+
+    /**
+     * 指定从引入中过滤的字段
+     * @var string
+     */
+    public $withoutField;
+
+    /**
+     * 说明md内容
+     * @var string
+     */
+    public $md;
+
+    /**
+     * 引入说明md内容
+     * @var string
+     */
+    public $mdRef;
+
+
+}

+ 14 - 0
vendor/hg/apidoc/src/annotation/ParamType.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 参数类型
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class ParamType extends Annotation
+{}

+ 40 - 0
vendor/hg/apidoc/src/annotation/Query.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+
+/**
+ * Query参数
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+final class Query extends ParamBase
+{
+
+
+    /**
+     * 类型
+     * @Enum({"string", "integer", "number", "boolean", "double", "float","date","time","datetime","timeStamp","int","object","array"})
+     * @var string
+     */
+    public $type;
+
+    /**
+     * 必须
+     * @var bool
+     */
+    public $require;
+    
+    /**
+     * 引入
+     * @var string
+     */
+    public $ref;
+
+    /**
+     * mock
+     * @var string
+     */
+    public $mock;
+}

+ 23 - 0
vendor/hg/apidoc/src/annotation/ResponseError.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 异常响应体
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+final class ResponseError extends ParamBase
+{
+
+    /**
+     * 引入
+     * @var string
+     */
+    public $ref;
+
+
+}

+ 20 - 0
vendor/hg/apidoc/src/annotation/ResponseErrorMd.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 异常响应体的Markdown内容
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class ResponseErrorMd extends Annotation
+{
+    /**
+     * 引入md内容
+     * @var string
+     */
+    public $ref;
+}

+ 33 - 0
vendor/hg/apidoc/src/annotation/ResponseSuccess.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+
+/**
+ * 成功响应体
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+final class ResponseSuccess extends ParamBase
+{
+
+    /**
+     * 必须
+     * @var bool
+     */
+    public $require;
+
+    /**
+     * 引入
+     * @var string
+     */
+    public $ref;
+
+    /**
+     * 数据挂载节点
+     * @var boolean
+     */
+    public $main;
+
+}

+ 20 - 0
vendor/hg/apidoc/src/annotation/ResponseSuccessMd.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 成功响应体Markdown内容
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class ResponseSuccessMd extends Annotation
+{
+    /**
+     * 引入md内容
+     * @var string
+     */
+    public $ref;
+}

+ 34 - 0
vendor/hg/apidoc/src/annotation/Returned.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 返回参数
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","ANNOTATION"})
+ */
+final class Returned extends ParamBase
+{
+
+    /**
+     * 必须
+     * @var bool
+     */
+    public $require = false;
+
+    /**
+     * 引入
+     * @var string
+     */
+    public $ref;
+
+    /**
+     * 是否替换全局响应体中的参数
+     * @var bool
+     */
+    public $replaceGlobal = false;
+
+}

+ 45 - 0
vendor/hg/apidoc/src/annotation/RouteParam.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+
+/**
+ * 路由参数
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class RouteParam extends ParamBase
+{
+    /**
+     * 必须
+     * @var bool
+     */
+    public $require = false;
+
+    /**
+     * 类型
+     * @var string
+     */
+    public $type;
+
+    /**
+     * 引入
+     * @var string
+     */
+    public $ref;
+
+
+    /**
+     * 描述
+     * @var string
+     */
+    public $desc;
+
+    /**
+     * mock
+     * @var string
+     */
+    public $mock;
+
+}

+ 77 - 0
vendor/hg/apidoc/src/annotation/Rule.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+abstract class Rule extends Annotation
+{
+    /**
+     * @var string|array
+     */
+    public $middleware;
+
+    /**
+     * 后缀
+     * @var string
+     */
+    public $ext;
+
+    /**
+     * @var string
+     */
+    public $deny_ext;
+
+    /**
+     * @var bool
+     */
+    public $https;
+
+    /**
+     * @var string
+     */
+    public $domain;
+
+    /**
+     * @var bool
+     */
+    public $complete_match;
+
+    /**
+     * @var string|array
+     */
+    public $cache;
+
+    /**
+     * @var bool
+     */
+    public $ajax;
+
+    /**
+     * @var bool
+     */
+    public $pjax;
+
+    /**
+     * @var bool
+     */
+    public $json;
+
+    /**
+     * @var array
+     */
+    public $filter;
+
+    /**
+     * @var array
+     */
+    public $append;
+
+    public function getOptions()
+    {
+        return array_intersect_key(get_object_vars($this), array_flip([
+            'middleware', 'ext', 'deny_ext', 'https', 'domain', 'complete_match', 'cache', 'ajax', 'pjax', 'json', 'filter', 'append',
+        ]));
+    }
+
+}

+ 14 - 0
vendor/hg/apidoc/src/annotation/Sort.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 排序
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"CLASS"})
+ */
+class Sort extends Annotation
+{}

+ 14 - 0
vendor/hg/apidoc/src/annotation/Tag.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * Tag
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class Tag extends Annotation
+{}

+ 14 - 0
vendor/hg/apidoc/src/annotation/Title.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 标题
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD","CLASS"})
+ */
+class Title extends Annotation
+{}

+ 14 - 0
vendor/hg/apidoc/src/annotation/Url.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 接口Url
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class Url extends Annotation
+{}

+ 14 - 0
vendor/hg/apidoc/src/annotation/WithoutField.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace hg\apidoc\annotation;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/**
+ * 排除模型的字段
+ * @package hg\apidoc\annotation
+ * @Annotation
+ * @Target({"METHOD"})
+ */
+class WithoutField extends Annotation
+{}

+ 92 - 0
vendor/hg/apidoc/src/config.php

@@ -0,0 +1,92 @@
+<?php
+return [
+    // (选配)文档标题,显示在左上角与首页
+    'title'              => 'Apidoc',
+    // (选配)文档描述,显示在首页
+    'desc'               => '',
+    // (必须)设置文档的应用/版本
+    'apps'           => [
+        [
+            // (必须)标题
+            'title'=>'Api接口',
+            // (必须)控制器目录地址
+            'path'=>'app\controller',
+            // (必须)唯一的key
+            'key'=>'api',
+        ]
+    ],
+    // (必须)指定通用注释定义的文件地址
+    'definitions'        => "app\common\controller\Definitions",
+    // (必须)自动生成url规则,当接口不添加@Apidoc\Url ("xxx")注解时,使用以下规则自动生成
+    'auto_url' => [
+        // 字母规则,lcfirst=首字母小写;ucfirst=首字母大写;
+        'letter_rule' => "lcfirst",
+        // url前缀
+        'prefix'=>"",
+    ],
+    // (必须)缓存配置
+    'cache'              => [
+        // 是否开启缓存
+        'enable' => false,
+    ],
+    // (必须)权限认证配置
+    'auth'               => [
+        // 是否启用密码验证
+        'enable'     => false,
+        // 全局访问密码
+        'password'   => "123456",
+        // 密码加密盐
+        'secret_key' => "apidoc#hg_code",
+        // 授权访问后的有效期
+        'expire' => 24*60*60
+    ],
+    // 全局参数
+    'params'=>[
+        // (选配)全局的请求Header
+        'header'=>[
+            // name=字段名,type=字段类型,require=是否必须,default=默认值,desc=字段描述
+            ['name'=>'Authorization','type'=>'string','require'=>true,'desc'=>'身份令牌Token'],
+        ],
+        // (选配)全局的请求Query
+        'query'=>[
+            // 同上 header
+        ],
+        // (选配)全局的请求Body
+        'body'=>[
+            // 同上 header
+        ],
+    ],
+    // 全局响应体
+    'responses'=>[
+        // 成功响应体
+        'success'=>[
+            ['name'=>'code','desc'=>'业务代码','type'=>'int','require'=>1],
+            ['name'=>'message','desc'=>'业务信息','type'=>'string','require'=>1],
+            //参数同上 headers;main=true来指定接口Returned参数挂载节点
+            ['name'=>'data','desc'=>'业务数据','main'=>true,'type'=>'object','require'=>1],
+        ],
+        // 异常响应体
+        'error'=>[
+            ['name'=>'code','desc'=>'业务代码','type'=>'int','require'=>1,'md'=>'/docs/HttpError.md'],
+            ['name'=>'message','desc'=>'业务信息','type'=>'string','require'=>1],
+        ]
+    ],
+    //(选配)默认作者
+    'default_author'=>'',
+    //(选配)默认请求类型
+    'default_method'=>'GET',
+    //(选配)允许跨域访问
+    'allowCrossDomain'=>false,
+    /**
+     * (选配)解析时忽略带@注解的关键词,当注解中存在带@字符并且非Apidoc注解,如 @key test,此时Apidoc页面报类似以下错误时:
+     * [Semantical Error] The annotation "@key" in method xxx() was never imported. Did you maybe forget to add a "use" statement for this annotation?
+     */
+    'ignored_annitation'=>[],
+
+    // (选配)数据库配置
+    'database'=>[],
+    // (选配)Markdown文档
+    'docs'              => [],
+    // (选配)代码生成器配置 注意:是一个二维数组
+    'generator' =>[]
+];

+ 62 - 0
vendor/hg/apidoc/src/exception/ErrorException.php

@@ -0,0 +1,62 @@
+<?php
+
+
+namespace hg\apidoc\exception;
+
+
+use hg\apidoc\utils\ConfigProvider;
+use hg\apidoc\utils\Helper;
+
+class ErrorException extends HttpException
+{
+
+    protected $exceptions = [
+        'apidoc close'     => ['status'=>404,'code' => 4004, 'msg' => '文档已关闭'],
+        'password error'     => ['status'=>402,'code' => 4002, 'msg' => '密码不正确,请重新输入'],
+        'password not found' => ['status'=>402,'code' => 4002, 'msg' => '密码不可为空'],
+        'token error'        => ['status'=>401,'code' => 4001, 'msg' => '不合法的Token'],
+        'token not found'    => ['status'=>401,'code' => 4001, 'msg' => '不存在Token'],
+        'appkey not found'     => ['status'=>412,'code' => 4005, 'msg' => '缺少必要参数appKey'],
+        'mdPath not found'     => ['status'=>412,'code' => 4006, 'msg' => '缺少必要参数path'],
+        'appKey error'         => ['status'=>412,'code' => 4007, 'msg' => '不存在 key为${appKey}的apps配置'],
+        'template not found'   => ['status'=>412,'code' => 4008, 'msg' => '${template}模板不存在'],
+        'path not found'       => ['status'=>412,'code' => 4009, 'msg' => '${path}目录不存在'],
+        'classname error'      => ['status'=>412,'code' => 4010, 'msg' => '${classname}文件名不合法'],
+        'apiKey not found'     => ['status'=>412,'code' => 4011, 'msg' => '缺少必要参数apiKey'],
+        'no config apps'       => ['status'=>412,'code' => 5000, 'msg' => 'apps配置不可为空'],
+        'unknown error'       => ['status'=>501,'code' => 5000, 'msg' => '未知异常'],
+        'no debug'             => ['status'=>412,'code' => 5001, 'msg' => '请在debug模式下,使用该功能'],
+        'no config crud'       => ['status'=>412,'code' => 5002, 'msg' => 'crud未配置'],
+        'datatable crud error' => ['status'=>412,'code' => 5003, 'msg' => '数据表创建失败,请检查配置'],
+        'file already exists' => ['status'=>412,'code' => 5004, 'msg' => '${filepath}文件已存在'],
+        'file not exists' => ['status'=>412,'code' => 5005, 'msg' => '${filepath}文件不存在'],
+        'datatable already exists' => ['status'=>412,'code' => 5004, 'msg' => '数据表${table}已存在'],
+        'datatable not exists' => ['status'=>412,'code' => 5004, 'msg' => '数据表${table}不存在'],
+        'ref file not exists' => ['status'=>412,'code' => 5005, 'msg' => 'ref引入 ${path} 文件不存在'],
+        'ref method not exists' => ['status'=>412,'code' => 5005, 'msg' => 'ref引入${path} 中 ${method} 方法不存在'],
+        'datatable create error' => ['status'=>412,'code' => 5006, 'msg' => '数据表[${table}]创建失败,error:${message},sql:${sql}'],
+    ];
+
+    public function __construct(string $exceptionCode, array $data = [])
+    {
+        $config = ConfigProvider::get();
+        $exception = $this->getException($exceptionCode);
+        if ($exception){
+            $msg       = Helper::replaceTemplate($exception['msg'], $data);
+        }else{
+            $exception = $this->exceptions['unknown error'];
+            $msg = $exceptionCode;
+        }
+        parent::__construct($exception['status'], $msg, null, [], $exception['code']);
+
+    }
+
+    public function getException($exceptionCode)
+    {
+        if (isset($this->exceptions[$exceptionCode])) {
+            return $this->exceptions[$exceptionCode];
+        }
+        return null;
+    }
+
+}

+ 33 - 0
vendor/hg/apidoc/src/exception/HttpException.php

@@ -0,0 +1,33 @@
+<?php
+declare (strict_types = 1);
+
+namespace hg\apidoc\exception;
+
+use Exception;
+
+/**
+ * HTTP异常
+ */
+class HttpException extends \RuntimeException
+{
+    private $statusCode;
+    private $headers;
+
+    public function __construct(int $statusCode, string $message = '', Exception $previous = null, array $headers = [], $code = 0)
+    {
+        $this->statusCode = $statusCode;
+        $this->headers    = $headers;
+
+        parent::__construct($message, $code, $previous);
+    }
+
+    public function getStatusCode()
+    {
+        return $this->statusCode;
+    }
+
+    public function getHeaders()
+    {
+        return $this->headers;
+    }
+}

+ 348 - 0
vendor/hg/apidoc/src/generator/Index.php

@@ -0,0 +1,348 @@
+<?php
+
+namespace hg\apidoc\generator;
+use hg\apidoc\exception\ErrorException;
+use hg\apidoc\generator\ParseTemplate;
+use hg\apidoc\utils\DirAndFile;
+use hg\apidoc\utils\Helper;
+
+class Index
+{
+    protected $config = [];
+
+    protected $middlewares = [];
+
+    protected $databaseConfig = [
+        // 数据库表前缀
+        'prefix'          => '',
+        // 数据库编码,默认为utf8
+        'charset'         =>  'utf8',
+        // 数据库引擎,默认为 InnoDB
+        'engine'          => 'InnoDB',
+    ];
+
+    protected  $systemDefaultValues = [
+        'CURRENT_TIMESTAMP'
+    ];
+
+    public function __construct($config)
+    {
+        $this->config = $config;
+        if (!empty($config['database'])){
+            if (!empty($config['database']['prefix'])){
+                $this->databaseConfig['prefix'] = $config['database']['prefix'];
+            }
+            if (!empty($config['database']['charset'])){
+                $this->databaseConfig['charset'] = $config['database']['charset'];
+            }
+            if (!empty($config['database']['engine'])){
+                $this->databaseConfig['engine'] = $config['database']['engine'];
+            }
+        }
+    }
+
+    public function create($params){
+        $appKey = $params['form']['appKey'];
+        $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+        $currentApps = $currentAppConfig['apps'];
+        $currentApp  = $currentAppConfig['appConfig'];
+        $generatorItem = $this->config['generator'][$params['index']];
+
+        $checkParams = $this->checkFilesAndHandleParams($generatorItem,$params,$currentApps);
+        $tplParams = $checkParams['tplParams'];
+        // 注册中间件并执行before
+        if (!empty($generatorItem['middleware']) && count($generatorItem['middleware'])){
+            foreach ($generatorItem['middleware'] as $middleware) {
+                $instance = new $middleware;
+                $this->middlewares[] = $instance;
+                if (method_exists($instance, 'before')) {
+                    $middlewareRes = $instance->before($tplParams);
+                    if (!empty($middlewareRes)){
+                        $tplParams = $middlewareRes;
+                    }
+                }
+            }
+        }
+
+        $this->createModels($checkParams['createModels'],$tplParams);
+        $this->createFiles($checkParams['createFiles'],$tplParams);
+         //执行after
+        if (count($this->middlewares)){
+            foreach ($this->middlewares as $middleware) {
+                if (method_exists($instance, 'after')) {
+                    $instance->after($tplParams);
+                }
+            }
+        }
+        return $tplParams;
+    }
+
+    /**
+     * 验证文件及处理模板数据
+     * @param $generatorItem
+     * @param $params
+     * @param $currentApps
+     * @return array
+     */
+    protected function checkFilesAndHandleParams($generatorItem,$params,$currentApps){
+        // 组成模板参数
+        $tplParams=[
+            'form'=>$params['form'],
+            'tables'=>$params['tables'],
+            'app'=>$currentApps
+        ];
+        $createFiles = [];
+        if (!empty($params['files']) && count($params['files'])>0) {
+            $files = $params['files'];
+            foreach ($files as $file) {
+                $fileConfig = Helper::getArrayFind($generatorItem['files'], function ($item) use ($file) {
+                    if ($file['name'] === $item['name']) {
+                        return true;
+                    }
+                    return false;
+                });
+
+                $filePath = Helper::replaceCurrentAppTemplate($fileConfig['path'], $currentApps);
+                if (!empty($fileConfig['namespace'])) {
+                    $fileNamespace = Helper::replaceCurrentAppTemplate($fileConfig['namespace'], $currentApps);
+                } else {
+                    $fileNamespace = $filePath;
+                }
+                $fileNamespaceEndStr = substr($fileNamespace, -1);
+                if ($fileNamespaceEndStr == '\\') {
+                    $fileNamespace = substr($fileNamespace, 0, strlen($fileNamespace) - 1);
+                }
+                $template = Helper::replaceCurrentAppTemplate($fileConfig['template'], $currentApps);
+                $tplParams[$file['name']] = [
+                    'class_name' => $file['value'],
+                    'path' => $filePath,
+                    'namespace' => $fileNamespace,
+                    'template' => $template
+                ];
+
+                // 验证模板是否存在
+                $templatePath =DirAndFile::formatPath( APIDOC_ROOT_PATH . $template,"/");
+                if (is_readable($templatePath) == false) {
+                    throw new ErrorException("template not found",  [
+                        'template' => $template
+                    ]);
+                }
+                // 验证是否已存在生成的文件
+                $fileFullPath = DirAndFile::formatPath(APIDOC_ROOT_PATH . $filePath, "/");
+                $type = "folder";
+                if (strpos($fileFullPath, '.php') !== false) {
+                    // 路径为php文件,则验证文件是否存在
+                    if (is_readable($fileFullPath) == false) {
+                        throw new ErrorException("file not exists",  [
+                            'filepath' => $filePath
+                        ]);
+                    }
+                    $type = "file";
+                } else {
+                    $fileName = !empty($file['value']) ? $file['value'] : "";
+                    $fileFullPath = $fileFullPath . "/" . $fileName . ".php";
+                    if (is_readable($fileFullPath)) {
+                        throw new ErrorException("file already exists",[
+                            'filepath' => DirAndFile::formatPath($filePath) . $fileName . ".php"
+                        ]);
+                    }
+                }
+                $createFiles[] = [
+                    'fileFullPath' => $fileFullPath,
+                    'template' => $template,
+                    'templatePath'=>$templatePath,
+                    'type' => $type
+                ];
+            }
+        }
+
+        $createModels = $this->checkModels($generatorItem,$tplParams);
+        return [
+            'tplParams'=>$tplParams,
+            'createFiles'=>$createFiles,
+            'createModels' =>$createModels
+        ];
+    }
+
+    /**
+     * 验证模型及表
+     * @param $generatorItem
+     * @param $tplParams
+     * @return array
+     */
+    protected function checkModels($generatorItem,$tplParams){
+        if (empty($this->config['database_query_function'])){
+            throw new ErrorException("not datatable_query_function config");
+        }
+
+        $res="";
+        $tabls = $tplParams['tables'];
+        $createModels = [];
+        if (!empty($tabls) && count($tabls)){
+            foreach ($tabls as $k=>$table) {
+                $tableConfig = $generatorItem['table'];
+                $fileFullPath="";
+                if (!empty($table['model_name'])){
+                    $namespace = $tableConfig['items'][$k]['namespace'];
+                    $template = $tableConfig['items'][$k]['template'];
+                    $path = $tableConfig['items'][$k]['path'];
+
+                    // 验证模板是否存在
+                    $templatePath = DirAndFile::formatPath(APIDOC_ROOT_PATH . $template,"/");
+                    if (is_readable($templatePath) == false) {
+                        throw new ErrorException("template not found", [
+                            'template' => $template
+                        ]);
+                    }
+                    $tplParams['tables'][$k]['class_name'] =$table['model_name'];
+                    // 验证模型是否已存在
+                    $fileName = $table['model_name'];
+                    $fileFullPath = DirAndFile::formatPath(APIDOC_ROOT_PATH.$path). "/" . $fileName . ".php";
+                    if (is_readable($fileFullPath)) {
+                        throw new ErrorException("file already exists", [
+                            'filepath' => DirAndFile::formatPath($path) . "/" . $fileName . ".php"
+                        ]);
+                    }
+                }
+                // 验证表是否存在
+                if ($table['table_name']){
+                    $table_name = $this->databaseConfig['prefix'].$table['table_name'];
+                    $isTable = $this->config['database_query_function']('SHOW TABLES LIKE '."'".$table_name."'");
+                    if ($isTable){
+                        throw new ErrorException("datatable already exists",  [
+                            'table' => $table_name
+                        ]);
+                    }
+                }
+                $createModels[]=[
+                    'namespace'=>$namespace,
+                    'template'=>$template,
+                    'path'=>$path,
+                    'templatePath' =>$templatePath,
+                    'table'=>$table,
+                    'fileFullPath'=>$fileFullPath
+                ];
+            }
+        }
+        return $createModels;
+
+    }
+
+    /**
+     * 创建文件
+     * @param $createFiles
+     * @param $tplParams
+     * @return mixed
+     */
+    protected function createFiles($createFiles,$tplParams){
+
+        if (!empty($createFiles) && count($createFiles)>0){
+            foreach ($createFiles as $fileItem) {
+                $html = (new ParseTemplate())->compile($fileItem['templatePath'],$tplParams);
+                if ($fileItem['type'] === "file"){
+                    // 路径为文件,则添加到该文件
+                    $pathFileContent = DirAndFile::getFileContent($fileItem['fileFullPath']);
+                    $content = $pathFileContent."\r\n".$html;
+                    DirAndFile::createFile($fileItem['fileFullPath'],$content);
+                }else{
+                    DirAndFile::createFile($fileItem['fileFullPath'],$html);
+                }
+            }
+        }
+        return $tplParams;
+    }
+
+    /**
+     * 创建模型文件
+     * @param $createModels
+     * @param $tplParams
+     */
+    protected function createModels($createModels,$tplParams){
+        if (!empty($createModels) && count($createModels)>0){
+            foreach ($createModels as $k=>$item) {
+                $table = $item['table'];
+                if (!empty($table['table_name'])){
+                    $res =  $this->createTable($table);
+                }
+                if (!empty($table['model_name'])){
+                    $tplParams['tables'][$k]['class_name'] =$table['model_name'];
+                    $html = (new ParseTemplate())->compile($item['templatePath'],$tplParams);
+                    DirAndFile::createFile($item['fileFullPath'],$html);
+                }
+
+            }
+        }
+    }
+
+    /**
+     * 创建数据表
+     * @return mixed
+     */
+    protected function createTable($table){
+        $datas = $table['datas'];
+        $comment= "";
+        if (!empty($table['table_comment'])){
+            $comment =$table['table_comment'];
+        }
+        $table_name = $this->databaseConfig['prefix'].$table['table_name'];
+        $table_data = '';
+        $main_keys = '';
+        $defaultNullTypes = ['timestamp'];
+        foreach ($datas as $k=>$item){
+            if (!empty($item['not_table_field'])){
+                continue;
+            }
+            $table_field="`".$item['field']."` ".$item['type'];
+            if (!empty($item['length'])){
+                $table_field.="(".$item['length'].")";
+            }
+
+            if (!empty($item['main_key'])){
+                $main_keys.=$item['field'];
+                $table_field.=" NOT NULL";
+            }else if (!empty($item['not_null'])){
+                $table_field.=" NOT NULL";
+            }
+            if (!empty($item['incremental']) && !empty($item['main_key'])){
+                $table_field.=" AUTO_INCREMENT";
+            }
+            if (!empty($item['default']) || (isset($item['default']) && $item['default']=="0")){
+                $defaultValue = "'".$item['default']."'";
+                if (in_array($item['default'],$this->systemDefaultValues)){
+                    $defaultValue = $item['default'];
+                }
+                $table_field.=" DEFAULT ".$defaultValue."";
+            }else if (!empty($item['main_key']) && !$item['not_null']){
+                $table_field.=" DEFAULT NULL";
+            }else if (in_array($item['type'],$defaultNullTypes) && empty($item['not_null'])){
+                $table_field.=" NULL DEFAULT NULL";
+            }
+            $fh = $k < (count($datas)-1)?",":"";
+            $table_field.=" COMMENT '".$item['desc']."'".$fh;
+            $table_data.=$table_field;
+        }
+        $primaryKey = "";
+        if (!empty($main_keys)){
+            $table_data.=",";
+            $primaryKey = "PRIMARY KEY (`$main_keys`)";
+        }
+
+        $charset = $this->databaseConfig['charset'];
+        $engine = $this->databaseConfig['engine'];
+        $sql = "CREATE TABLE IF NOT EXISTS `$table_name` (
+        $table_data
+        $primaryKey
+        ) ENGINE=$engine DEFAULT CHARSET=$charset COMMENT='$comment' AUTO_INCREMENT=1 ;";
+
+        try {
+            $this->config['database_query_function']($sql);
+            return true;
+        } catch (\Exception $e) {
+            throw new ErrorException("datatable create error",  [
+                'table' => $table_name,
+                'message'=>$e->getMessage(),
+                'sql'=>$sql
+            ]);
+        }
+    }
+}

+ 339 - 0
vendor/hg/apidoc/src/generator/ParseTemplate.php

@@ -0,0 +1,339 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\generator;
+use hg\apidoc\Utils;
+use hg\apidoc\utils\DirAndFile;
+use hg\apidoc\utils\Helper;
+use think\facade\App;
+
+class ParseTemplate
+{
+
+    public function compile($path,$params)
+    {
+        $filePath    =  $path;
+        $tplContent = DirAndFile::getFileContent($filePath);
+        $tplContent = $this->replaceForeach($tplContent,$params);
+        $tplContent = $this->replaceParams($tplContent,$params);
+        $tplContent = $this->replaceIf($tplContent,$params);
+        $tplContent = preg_replace("/\s+\r\n/is", "\r\n", $tplContent);
+        return $tplContent;
+    }
+
+    /**
+     * 替换变量
+     * @param $tplContent
+     * @param $params
+     * @return array|string|string[]|null
+     */
+    protected function replaceParams($tplContent,$params){
+        $key = '{$%%}';
+        $pattern = '#' . str_replace('%%', '(.+?)' , preg_quote($key, '#')) . '#';
+        $tplContent = preg_replace_callback($pattern, function ($matches)use ($params){
+            $k = $matches[1];
+            if (strpos($k, '(') !== false){
+                $tagArr = explode("(", $k);
+                $fun = $tagArr[0];
+                $k = str_replace(")", "",$tagArr[1] );
+                $v = $this->getObjectValueByKeys($params,$k);
+                if (empty($v)){
+                    return "";
+                }
+                if ($fun === "lower"){
+                   return Helper::lower($v);
+                }else if ($fun === "snake"){
+                    return Helper::snake($v);
+                }else if ($fun === "lcfirst"){
+                    return lcfirst($v);
+                }else if ($fun === "count"){
+                    return count($v);
+                }
+            }
+            $value = $this->getObjectValueByKeys($params,$k);
+            if (is_bool($value)){
+               return $value==true?'true':'false';
+            }else if (is_array($value)){
+               return $k;
+            }
+            return $value;
+        }, $tplContent);
+        return $tplContent;
+    }
+
+    /**
+     * 替换if内容
+     * @param $tplContent
+     * @param $params
+     * @return array|mixed|string|string[]
+     * @throws \Exception
+     */
+    protected function replaceIf($tplContent,$params){
+        $res = [];
+        $label = "if";
+        $labelList = $this->parseLabel($tplContent,$label);
+        if (!empty($labelList) && count($labelList)>0){
+            foreach ($labelList as $item) {
+                $itemStr =$item;
+                $ifChildren= $this->parseLabel($itemStr,$label,"children");
+
+                if (!empty($ifChildren) && count($ifChildren)>0){
+                    foreach ($ifChildren as $ifChild){
+                        $itemChildrenContent= $this->getIfContent($ifChild);
+                        $itemStr = str_replace($ifChild, $itemChildrenContent,$itemStr );
+                    }
+               }
+                $itemContent= $this->getIfContent($itemStr);
+                $tplContent =   str_replace($item, $itemContent,$tplContent );
+            }
+        }
+
+        return $tplContent;
+    }
+    protected function parseForeach($str,$params){
+        if (preg_match('#{foreach (.+?) as (.+?)=>(.+?)}#s', $str, $matches)){
+            $complete = $matches[0];
+            $condition = $matches[1];
+            $keyField = str_replace("$", "",$matches[2] );
+            $itemField = str_replace("$", "",$matches[3] );
+            $conditionKey = str_replace("$", "",$condition );
+            $forListData = $this->getObjectValueByKeys($params,$conditionKey);
+            $contentStr = str_replace($complete, "",$str);
+            $contentStr = substr($contentStr,0,-10);
+            return [
+                'list'=>$forListData,
+                'keyField'=>$keyField,
+                'itemField'=>$itemField,
+                'content'=>$contentStr
+            ];
+        }
+        return [];
+    }
+
+    /**
+     * 获取所有foreach标签
+     * @param $str
+     * @return array
+     * @throws \Exception
+     */
+    protected function getAllForeachLabel($str){
+        $tree = [];
+        $label = "foreach";
+        $labelList = $this->parseLabel($str,$label);
+        if (!empty($labelList) && count($labelList)>0){
+            foreach ($labelList as $itemLabel) {
+                $labelChildrenList = $this->parseLabel($itemLabel,$label,"children");
+                if (!empty($labelChildrenList) && count($labelChildrenList)>0){
+                    $childrenList = [];
+                    foreach ($labelChildrenList as $item) {
+                        $childrenList[]=[
+                            'str'=>$item,
+                            'children' => []
+                        ];
+                    }
+                    $tree[]=[
+                        'str'=>$itemLabel,
+                        'children' => $childrenList
+                    ];
+                }else{
+                    $tree[]=[
+                        'str'=>$itemLabel,
+                        'children' => []
+                    ];
+                }
+            }
+        }
+        return $tree;
+    }
+    // 解析foreach
+    protected function replaceForeach($html,$params,$level=""){
+        $allLabelData= $this->getAllForeachLabel($html);
+        $res = [];
+        if (count($allLabelData)>0){
+            // 遍历每个foreach标签
+            foreach ($allLabelData as $labelItem) {
+                $itemStr = $labelItem['str'];
+                $forOption = $this->parseForeach($labelItem['str'],$params);
+                $itemContent="";
+                if (!empty($forOption['list']) && count($forOption['list'])>0){
+                    // 处理每行数据
+                    foreach ($forOption['list'] as $rowKey=>$row) {
+                        $rowData = [$forOption['itemField']=>$row,$forOption['keyField']=>$rowKey];
+                        $rowParams = array_merge($params,$rowData);
+                        // 存在子标签,处理子标签
+                        if (!empty($labelItem['children']) && count($labelItem['children'])>0){
+                            $itemStrContent = "";
+                            foreach ($labelItem['children'] as $childLabel){
+                                $childContents = "";
+                                $childStr = $childLabel['str'];
+                                $childDataList = $this->parseForeach($childLabel['str'],$rowParams);
+                                // 处理子标签数据
+                                if (!empty($childDataList['list']) && count($childDataList['list'])>0){
+                                    foreach ($childDataList['list'] as $childDataKey=>$childDataItem) {
+                                        // 子标签每行数据
+                                        $childDataItemData = [$childDataList['itemField']=>$childDataItem,$childDataList['keyField']=>$childDataKey,];
+                                        $contentsStr= $this->getForContent($childDataList['content'],array_merge($rowParams,$childDataItemData));
+                                        $contentsStr =ltrim($contentsStr,"\r\n");
+                                        if (!empty(Helper::trimEmpty($contentsStr))){
+                                            $childContents.= $contentsStr;
+                                        }
+                                    }
+                                }
+                                $itemStrContent.= str_replace($childLabel['str'], $childContents,$forOption['content']);
+                            }
+                            $rowContent=$this->replaceParams($itemStrContent,$rowParams);
+                            $itemContentStr=$this->replaceIf($rowContent,$rowParams);
+                            if (!empty(Helper::trimEmpty($itemContentStr))){
+                                $itemContent.= $itemContentStr;
+                            }
+                        }else{
+                            $rowContent=$this->getForContent($forOption['content'],$rowParams);
+                            if (empty(Helper::trimEmpty($rowContent))){
+                                $rowContent= "";
+                            }
+                            $itemContent.= $rowContent;
+                        }
+                        $itemContent =trim($itemContent,"\r\n");
+                    }
+                }
+                $html = str_replace($labelItem['str'], $itemContent,$html );
+            }
+        }
+        return $html;
+
+    }
+
+    /**
+     * 获取foreach内容
+     * @param $str
+     * @param $params
+     * @return array|mixed|string|string[]
+     * @throws \Exception
+     */
+    protected function getForContent($str,$params){
+            $content = $str;
+            if (!empty($params)){
+                $content = $this->replaceParams($content,$params);
+                $content = $this->replaceIf($content,$params);
+            }
+            return $content;
+    }
+
+
+    /**
+     * 获取if条件的内容
+     * @param $str
+     * @return mixed|string
+     */
+    protected function getIfContent($str){
+        if (preg_match('#{if (.+?)}(.*?){/if}#s', $str, $matches)){
+            if (eval("return $matches[1];")){
+                // 条件成立
+               return $matches[2];
+            }
+        }
+        return "";
+    }
+
+
+    /**
+     * 解析指定标签
+     * @param $str
+     * @param $label
+     * @param string $level
+     * @return array
+     * @throws \Exception
+     */
+    protected function parseLabel($str,$label,$level=""){
+        // 后面的 flag 表示记录偏移量
+        preg_match_all('!({/?'.$label.' ?}?)!', $str, $matches, PREG_OFFSET_CAPTURE);
+        // 用数组来模拟栈
+        $stack  = [];
+        $top    = null;
+        $result = [];
+        foreach ($matches[0] as $k=>[$match, $offset]) {
+            // 当取标签内容时,排除第一个和最后一个标签
+            if ($level === 'children' && ($k==0 || $k>=count($matches[0])-1)){
+                continue;
+            }
+            // 判断匹配到的如果是 开始标签
+            if ($match === '{'.$label.' ') {
+                $stack[] = $offset;
+                // 记录开始的位置
+                if ($top === null) {
+                    $top = $offset;
+                }
+                // 如果不是
+            } else {
+                // 从栈底部拿一个出来
+                $pop = array_pop($stack);
+                // 如果取出来的是 null 就说明存在多余的 标签
+                if ($pop === null) {
+                    throw new \Exception('语法错误,存在多余的 {/'.$label.'} 标签');
+                }
+                // 如果取完后栈空了
+                if (empty($stack)) {
+                    // offset 是匹配到的开始标签(前面)位置,加上内容的长度
+                    $newOffset = $offset + strlen($match)-$top;
+                    // 从顶部到当前的偏移就是这个标签里的内容
+                    $result[] = substr($str, $top, $newOffset);
+                    // 重置 top 开始下一轮
+                    $top = null;
+                }
+            }
+        }
+        // 如果运行完了,栈里面还有东西,那就说明缺少闭合标签。
+        if (!empty($stack)) {
+            throw new \Exception('语法错误,存在未闭合的 {/'.$label.'} 标签');
+        }
+        return $result;
+    }
+
+    /**
+     * 根据keys获取对象中的值
+     * @param $array
+     * @param $keyStr
+     * @param string $delimiter
+     * @return mixed|null
+     */
+    public function getObjectValueByKeys($array, $keyStr, $delimiter = '.')
+    {
+        $keys = explode($delimiter, $keyStr);
+        if (preg_match_all('#\[(.+?)]#s', $keyStr, $matches)){
+            $value = $array;
+            if (!empty($matches[1])){
+                $matchesIndex=0;
+                foreach ($keys as $keyItem) {
+                    if (strpos($keyItem, '[') !== false) {
+                        $tagArr = explode("[", $keyItem);
+                        if (!empty($value[$tagArr[0]]) && !empty($value[$tagArr[0]][$matches[1][$matchesIndex]])){
+                            $value =$value[$tagArr[0]][$matches[1][$matchesIndex]];
+                        }else{
+                            $value =null;
+                            break;
+                        }
+                        $matchesIndex=$matchesIndex+1;
+                    }else{
+                        $value =$value[$keyItem];
+                    }
+                }
+            }
+            return $value;
+        }else if (sizeof($keys) > 1) {
+            $value = $array;
+            foreach ($keys as $key){
+                if (!empty($value[$key])){
+                    $value = $value[$key];
+                }else{
+                    $value =null;
+                    break;
+                }
+            }
+            return $value;
+        } else {
+            return $array[$keyStr] ?? null;
+        }
+    }
+
+
+}

+ 17 - 0
vendor/hg/apidoc/src/middleware/LaravelMiddleware.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace hg\apidoc\middleware;
+
+use hg\apidoc\utils\ConfigProvider;
+
+class LaravelMiddleware
+{
+    public function handle($request, \Closure $next)
+    {
+        $params = $request->all();
+        $config =  ConfigProvider::get();
+        $config['request_params'] = $params;
+        ConfigProvider::set($config);
+        return $next($request);
+    }
+}

+ 17 - 0
vendor/hg/apidoc/src/middleware/ThinkPHPMiddleware.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace hg\apidoc\middleware;
+
+use hg\apidoc\utils\ConfigProvider;
+
+class ThinkPHPMiddleware
+{
+    public function handle($request, \Closure $next)
+    {
+        $params = $request->param();
+        $config =  ConfigProvider::get();
+        $config['request_params'] = $params;
+        ConfigProvider::set($config);
+        return $next($request);
+    }
+}

+ 35 - 0
vendor/hg/apidoc/src/parses/ParseAnnotation.php

@@ -0,0 +1,35 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parses;
+
+class ParseAnnotation
+{
+
+    /**
+     * 解析非@注解的文本注释
+     * @param $refMethod
+     * @return array|false
+     */
+    public static function parseTextAnnotation($refMethod): array
+    {
+        $annotation = $refMethod->getDocComment();
+        if (empty($annotation)) {
+            return [];
+        }
+        if (preg_match('#^/\*\*(.*)\*/#s', $annotation, $comment) === false)
+            return [];
+        $comment = trim($comment [1]);
+        if (preg_match_all('#^\s*\*(.*)#m', $comment, $lines) === false)
+            return [];
+        $data = [];
+        foreach ($lines[1] as $line) {
+            $line = trim($line);
+            if (!empty ($line) && strpos($line, '@') !== 0) {
+                $data[] = $line;
+            }
+        }
+        return $data;
+    }
+
+}

+ 852 - 0
vendor/hg/apidoc/src/parses/ParseApiDetail.php

@@ -0,0 +1,852 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parses;
+
+use ReflectionClass;
+use Doctrine\Common\Annotations\AnnotationReader;
+use hg\apidoc\exception\ErrorException;
+use hg\apidoc\utils\DirAndFile;
+use hg\apidoc\utils\Helper;
+use hg\apidoc\utils\Lang;
+use hg\apidoc\annotation\Group;
+use hg\apidoc\annotation\Sort;
+use hg\apidoc\annotation\Param;
+use hg\apidoc\annotation\Query;
+use hg\apidoc\annotation\ResponseSuccess;
+use hg\apidoc\annotation\ResponseSuccessMd;
+use hg\apidoc\annotation\ResponseError;
+use hg\apidoc\annotation\ResponseErrorMd;
+use hg\apidoc\annotation\Title;
+use hg\apidoc\annotation\Desc;
+use hg\apidoc\annotation\Md;
+use hg\apidoc\annotation\RouteParam;
+use hg\apidoc\annotation\Author;
+use hg\apidoc\annotation\Tag;
+use hg\apidoc\annotation\Header;
+use hg\apidoc\annotation\Returned;
+use hg\apidoc\annotation\ParamType;
+use hg\apidoc\annotation\Url;
+use hg\apidoc\annotation\Method;
+use hg\apidoc\annotation\Before;
+use hg\apidoc\annotation\After;
+use hg\apidoc\annotation\ContentType;
+
+class ParseApiDetail
+{
+
+    protected $config = [];
+
+    protected $reader;
+
+
+    protected $currentApp = [];
+
+    protected $basePath = "";
+
+    protected $parseModel;
+
+    protected $appKey;
+
+    public function __construct($config)
+    {
+        $this->reader = new AnnotationReader();
+        if (!empty($config['ignored_annitation'])){
+            foreach ($config['ignored_annitation'] as $item) {
+                AnnotationReader::addGlobalIgnoredName($item);
+            }
+        }
+        $this->config = $config;
+        $this->basePath = APIDOC_ROOT_PATH;
+        $this->parseModel = new ParseModel($this->reader,$config);
+
+    }
+
+    /**
+     * 生成api接口数据
+     * @param string $classPath
+     * @param string $methodName
+     * @return array
+     */
+    public function renderApiDetail(string $appKey,string $classPath,string $methodName)
+    {
+
+        $this->appKey = $appKey;
+        $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+        $this->currentApp  = $currentAppConfig['appConfig'];
+        $json =DirAndFile::formatPath( $this->basePath . $classPath,"/");
+
+        try {
+            $refClass  = new ReflectionClass($classPath);
+            $classTextAnnotations =ParseAnnotation::parseTextAnnotation($refClass);
+
+            $refMethod= $refClass->getMethod($methodName);
+            $methodItem = $this->parseApiMethod($refClass,$refMethod);
+            if ($methodItem===false){
+                return "no";
+            }
+            if (in_array("NotDebug", $classTextAnnotations)) {
+                $methodItem['notDebug'] = true;
+            }
+            $methodItem['menuKey'] =Helper::createApiKey($refClass->name,$refMethod->name);
+            return $methodItem;
+        } catch (\ReflectionException $e) {
+            throw new ErrorException($e->getMessage());
+        }
+    }
+
+    protected function mergeGlobalOrAppParams($params,$paramType='param'){
+        $config  = $this->config;
+        $globalParams = [];
+        if (!empty($this->currentApp['params']) && !empty($this->currentApp['params'][$paramType])){
+            // 不合并global的处理方式
+            $globalParams = $this->currentApp['params'][$paramType];
+            // 合并global的处理方式
+            // $globalHeaders = Helper::arrayMergeAndUnique("name", $globalHeaders, $this->currentApp['params'][$paramType]);
+        }else if(!empty($config['params']) && !empty($config['params'][$paramType])){
+            $globalParams = $config['params'][$paramType];
+        }
+        $mergeParams = [];
+        foreach ($globalParams as $item){
+            if (!empty($item['desc'])){
+                $item['desc'] = Lang::getLang($item['desc']);
+            }
+            if (!empty($item['md'])){
+                $item['md'] = ParseMarkdown::getContent($this->appKey,$item['md']);
+            }
+            $mergeParams[] = $item;
+        }
+        if (!empty($params) && count($params)) {
+            return Helper::arrayMergeAndUnique("name", $mergeParams, $params);
+        }
+        return $mergeParams;
+    }
+
+    protected function mergeGlobalOrAppEvents($events,$eventType='after'){
+        $config  = $this->config;
+        $globalEvents = [];
+        if (!empty($this->currentApp['debug_events']) && !empty($this->currentApp['debug_events'][$eventType])){
+            $globalEvents = $this->currentApp['debug_events'][$eventType];
+        }else if(!empty($config['debug_events']) && !empty($config['debug_events'][$eventType])){
+            $globalEvents = $config['debug_events'][$eventType];
+        }
+        $mergeEvents = [];
+        foreach ($globalEvents as $item){
+            if (!empty($item['desc'])){
+                $item['desc'] = Lang::getLang($item['desc']);
+            }
+            $mergeEvents[] = $item;
+        }
+        if (!empty($events) && count($events)){
+            foreach ($events as $event) {
+                $mergeEvents[] = $event;
+            }
+        }
+        return $mergeEvents;
+    }
+
+    /**
+     * 处理接口成功响应参数
+     * @param $apiInfo
+     * @param $textAnnotations
+     * @return array|mixed
+     */
+    protected function handleApiResponseSuccess($apiInfo,$textAnnotations){
+        $returned = $apiInfo['returned'];
+        if (
+            in_array("NotResponses", $textAnnotations) ||
+            in_array("NotResponseSuccess", $textAnnotations)
+        ) {
+            return $returned;
+        }
+        $config  = $this->config;
+        $mergeParams = [];
+        $paramType='success';
+        if (!empty($apiInfo['responseSuccess']) && count($apiInfo['responseSuccess'])){
+            $mergeParams = $apiInfo['responseSuccess'];
+        }else if (!empty($this->currentApp['responses']) && !empty($this->currentApp['responses'][$paramType])){
+            $mergeParams = $this->currentApp['params'][$paramType];
+        }else if(!empty($config['responses']) && !empty($config['responses'][$paramType])){
+            $mergeParams = $config['responses'][$paramType];
+        }
+        if (!empty($mergeParams) && count($mergeParams)){
+            $resData = [];
+            foreach ($mergeParams as $item) {
+                if (!empty($item['main']) && $item['main'] === true){
+                    $item['children'] = $returned;
+                }
+                $item['desc'] = Lang::getLang($item['desc']);
+                if (!empty($item['md'])){
+                    $item['md'] = ParseMarkdown::getContent($this->appKey,$item['md']);
+                }
+                $resData[]=$item;
+            }
+            return $resData;
+        }
+        return $returned;
+
+    }
+
+    /**
+     * 处理接口异常响应参数
+     * @param $apiInfo
+     * @param $textAnnotations
+     * @return array|mixed|void
+     */
+    protected function handleApiResponseError($apiInfo,$textAnnotations){
+        $config  = $this->config;
+        $responseErrors = [];
+        if (
+            in_array("NotResponses", $textAnnotations) ||
+            in_array("NotResponseError", $textAnnotations)
+        ){
+            $responseErrors = [];
+        }else if (!empty($apiInfo['responseError']) && count($apiInfo['responseError'])){
+            $responseErrors = $apiInfo['responseError'];
+        }else if (
+            !empty($this->currentApp['responses']) &&
+            !empty($this->currentApp['responses']['error']) &&
+            count($this->currentApp['responses']['error'])
+        ){
+            $responseErrors = $this->currentApp['responses']['error'];
+        }else if (
+            !empty($config['responses']) &&
+            !empty($config['responses']['error']) &&
+            count($config['responses']['error'])
+        ){
+            $responseErrors = $config['responses']['error'];
+        }
+
+        $data = [];
+        foreach ($responseErrors as $item) {
+            $item['desc'] = Lang::getLang($item['desc']);
+            if (!empty($item['md'])){
+                $item['md'] = ParseMarkdown::getContent($this->appKey,$item['md']);
+            }
+            $data[]=$item;
+        }
+        return $data;
+    }
+
+
+    protected function parseApiMethod($refClass,$refMethod){
+        $config  = $this->config;
+        if (empty($refMethod->name)) {
+            return false;
+        }
+
+        $textAnnotations = ParseAnnotation::parseTextAnnotation($refMethod);
+        // 标注不解析的方法
+        if (in_array("NotParse", $textAnnotations)) {
+            return false;
+        }
+        $methodInfo = $this->parseAnnotation($refMethod, true,"controller");
+        if (empty($methodInfo)){
+            return false;
+        }
+        $methodInfo = $this->handleApiBaseInfo($methodInfo,$refClass,$refMethod,$textAnnotations);
+
+        // 是否开启debug
+        if (
+            in_array("NotDebug", $textAnnotations) ||
+            (isset($config['notDebug']) && $config['notDebug']===true) ||
+            (isset($this->currentApp['notDebug']) && $this->currentApp['notDebug']===true)
+        ) {
+            $methodInfo['notDebug'] = true;
+        }
+
+
+        // 合并全局请求头参数
+        if (
+            (
+                (!empty($config['params']) && !empty($config['params']['header']))  ||
+                (!empty($this->currentApp['params']) && !empty($this->currentApp['params']['header']))
+            ) &&
+            !in_array("NotHeaders", $textAnnotations))
+        {
+            $headers = !empty($methodInfo['header'])?$methodInfo['header']:[];
+            $methodInfo['header'] = $this->mergeGlobalOrAppParams($headers,'header');
+        }
+
+        // 合并全局请求参数-query
+        if (
+            (
+                (!empty($config['params']) && !empty($config['params']['query']))  ||
+                (!empty($this->currentApp['params']) && !empty($this->currentApp['params']['query']))
+            ) &&
+            !in_array("NotQuerys", $textAnnotations))
+        {
+            $querys = !empty($methodInfo['query'])?$methodInfo['query']:[];
+            $methodInfo['query'] = $this->mergeGlobalOrAppParams($querys,'query');
+        }
+
+        // 合并全局请求参数-body
+        if (
+            (
+                (!empty($config['params']) && !empty($config['params']['body']))  ||
+                (!empty($this->currentApp['params']) && !empty($this->currentApp['params']['body']))
+            ) &&
+            !in_array("NotParams", $textAnnotations))
+        {
+            $params = !empty($methodInfo['param'])?$methodInfo['param']:[];
+            $methodInfo['param'] = $this->mergeGlobalOrAppParams($params,'body');
+        }
+
+        //添加成功响应体
+        $methodInfo['responseSuccess'] = $this->handleApiResponseSuccess($methodInfo,$textAnnotations);
+        //添加异常响应体
+        $methodInfo['responseError'] = $this->handleApiResponseError($methodInfo,$textAnnotations);
+
+        // 合并全局事件after
+        if (
+            (
+                (!empty($config['debug_events']) && !empty($config['debug_events']['after']))  ||
+                (!empty($this->currentApp['debug_events']) && !empty($this->currentApp['debug_events']['after']))
+            ) &&
+            !in_array("NotEvent", $textAnnotations))
+        {
+            $debugAfterEvents = !empty($methodInfo['after'])?$methodInfo['after']:[];
+            $methodInfo['after'] = $this->mergeGlobalOrAppEvents($debugAfterEvents,'after');
+        }
+
+        // 合并全局事件before
+        if (
+            (
+                (!empty($config['debug_events']) && !empty($config['debug_events']['before']))  ||
+                (!empty($this->currentApp['debug_events']) && !empty($this->currentApp['debug_events']['before']))
+            ) &&
+            !in_array("NotEvent", $textAnnotations))
+        {
+            $debugBeforeEvents = !empty($methodInfo['before'])?$methodInfo['before']:[];
+            $methodInfo['before'] = $this->mergeGlobalOrAppEvents($debugBeforeEvents,'before');
+        }
+
+        return $methodInfo;
+    }
+
+
+    public function handleApiBaseInfo($methodInfo,$refClass,$refMethod,$textAnnotations){
+        $config  = $this->config;
+        // 无标题,且有文本注释
+        if (empty($methodInfo['title']) && !empty($textAnnotations) && count($textAnnotations) > 0) {
+            $methodInfo['title'] = Lang::getLang($textAnnotations[0]);
+        }
+
+        // 默认method
+        if (empty($methodInfo['method'])) {
+            $methodInfo['method'] = !empty($config['default_method']) ? strtoupper($config['default_method']) : 'GET';
+        }
+
+        // 默认default_author
+        if (empty($methodInfo['author']) && !empty($config['default_author']) && !in_array("NotDefaultAuthor", $textAnnotations)) {
+            $methodInfo['author'] = $config['default_author'];
+        }
+
+        if (!empty($methodInfo['tag'])){
+            $methodInfo['tag'] = static::handleTags($methodInfo['tag']);
+        }
+        // 无url,自动生成
+        if (empty($methodInfo['url'])) {
+            $methodInfo['url'] = static::autoCreateUrl($refClass->name,$refMethod,$config);
+        } else if (!empty($methodInfo['url']) && substr($methodInfo['url'], 0, 1) != "/") {
+            $methodInfo['url'] = "/" . $methodInfo['url'];
+        }
+        $methodInfo['name']     = $refMethod->name;
+        $methodInfo['menuKey'] = Helper::createApiKey($refClass->name,$refMethod->name);
+        return $methodInfo;
+    }
+
+    public static function handleTags($tagStr){
+        if (!empty($tagStr)) {
+            $tagStr = Lang::getLang($tagStr);
+            $tagList = [];
+            if (strpos($tagStr, ',') !== false) {
+                $tagArr = explode(",", $tagStr);
+                foreach ($tagArr as $tag) {
+                    $tagList[]=Lang::getLang($tag);
+                }
+            } else {
+                $tagList = [Lang::getLang($tagStr)];
+            }
+            return $tagList;
+        }
+        return $tagStr;
+    }
+
+    /**
+     * 自动生成url
+     * @param $method
+     * @return string
+     */
+    public static function autoCreateUrl($classPath,$method,$config): string
+    {
+
+        $pathArr = explode("\\", $classPath);
+        $filterPathNames = !empty($config['auto_url']) && !empty($config['auto_url']['filter_keys'])?$config['auto_url']['filter_keys']:[];
+        $classUrlArr = [];
+        foreach ($pathArr as $item) {
+            if (!in_array($item, $filterPathNames)) {
+                if (!empty($config['auto_url'])){
+                    $key = $item;
+                    if (!empty($config['auto_url']['letter_rule'])){
+                        switch ($config['auto_url']['letter_rule']) {
+                            case 'lcfirst':
+                                $key = lcfirst($item);
+                                break;
+                            case 'ucfirst':
+                                $key = ucfirst($item);
+                                break;
+                            default:
+                                $key = $item;
+                        }
+                    }
+                    if (!empty($config['auto_url']['handle_key'])){
+                        $classUrlArr[] = $config['auto_url']['handle_key']($key);
+                    }else{
+                        $classUrlArr[] = $key;
+                    }
+                }else{
+                    $classUrlArr[] = $item;
+                }
+            }
+        }
+        $classUrl = implode('/', $classUrlArr);
+        $prefix = !empty($config['auto_url']) && !empty($config['auto_url']['prefix'])?$config['auto_url']['prefix']:"";
+        $url = $prefix . '/' . $classUrl . '/' . $method->name;
+        if (!empty($config['auto_url']) && !empty($config['auto_url']['custom']) && is_callable($config['auto_url']['custom'])){
+            return $config['auto_url']['custom']($classPath,$method->name,$url);
+        }
+        return $url;
+    }
+
+    /**
+     * ref引用
+     * @param $refPath
+     * @param bool $enableRefService
+     * @return false|string[]
+     */
+    protected function renderRef(string $refPath, bool $enableRefService = true): array
+    {
+        $res = ['type' => 'model'];
+        $config      = $this->config;
+        // 通用定义引入
+        if (strpos($refPath, '\\') === false) {
+            $refPath     = $config['definitions'] . '\\' . $refPath;
+            $data        = $this->renderService($refPath);
+            $res['type'] = "service";
+            $res['data'] = $data;
+            return $res;
+        }
+        // 模型引入
+        $modelData = $this->parseModel->renderModel($refPath);
+        if ($modelData !== false) {
+            $res['data'] = $modelData;
+            return $res;
+        }
+        if ($enableRefService === false) {
+            return false;
+        }
+        $data        = $this->renderService($refPath);
+        $res['type'] = "service";
+        $res['data'] = $data;
+        return $res;
+    }
+
+    /**
+     * 解析注释引用
+     * @param $refPath
+     * @return array
+     * @throws \ReflectionException
+     */
+    protected function renderService(string $refPath)
+    {
+        $pathArr    = explode("\\", $refPath);
+        $methodName = $pathArr[count($pathArr) - 1];
+        unset($pathArr[count($pathArr) - 1]);
+        $classPath    = implode("\\", $pathArr);
+        if (!class_exists($classPath)){
+            throw new ErrorException("ref file not exists",  [
+                'path' => $classPath
+            ]);
+        }
+        try {
+            $classReflect = new \ReflectionClass($classPath);
+            $methodName   = trim($methodName);
+            $refMethod    = $classReflect->getMethod($methodName);
+            $res          = $this->parseAnnotation($refMethod, true);
+            return $res;
+        } catch (\ReflectionException $e) {
+            throw new ErrorException('Class '.$classPath.' '.$e->getMessage());
+        }
+
+    }
+
+    /**
+     * 处理Param/Returned的字段名name、params子级参数
+     * @param $values
+     * @return array
+     */
+    protected function handleParamValue($values, string $field = 'param'): array
+    {
+        $name   = "";
+        $params = [];
+        if (!empty($values) && is_array($values) && count($values) > 0) {
+            foreach ($values as $item) {
+                if (is_string($item)) {
+                    $name = $item;
+                } else if (is_object($item)) {
+                    if (!empty($item->ref)) {
+                        $refRes = $this->renderRef($item->ref, true);
+                        $params = $this->handleRefData($params, $refRes, $item, $field);
+                    } else {
+                        $param         = [
+                            "name"    => "",
+                            "type"    => $item->type,
+                            "desc"    => Lang::getLang($item->desc),
+                            "default" => $item->default,
+                            "require" => $item->require,
+                            "childrenType"=> $item->childrenType
+                        ];
+                        if (!empty($item->mock)){
+                            $param['mock']=$item->mock;
+                        }
+                        $children      = $this->handleParamValue($item->value);
+                        $param['name'] = $children['name'];
+                        if (count($children['params']) > 0) {
+                            $param['children'] = $children['params'];
+                        }
+                        $params[] = $param;
+                    }
+                }
+            }
+        } else if(!empty($values) && is_object($values)) {
+            $item = $values;
+            if (!empty($item->ref)) {
+                $refRes = $this->renderRef($item->ref, true);
+                $params = $this->handleRefData($params, $refRes, $item, $field);
+            } else {
+                $param         = [
+                    "name"    => "",
+                    "type"    => $item->type,
+                    "desc"    => Lang::getLang($item->desc),
+                    "default" => $item->default,
+                    "require" => $item->require,
+                    "childrenType"=> $item->childrenType
+                ];
+                if (!empty($item->mock)){
+                    $param['mock']=$item->mock;
+                }
+                $children      = $this->handleParamValue($item->value);
+                $param['name'] = $children['name'];
+                if (count($children['params']) > 0) {
+                    $param['children'] = $children['params'];
+                }
+                $params[] = $param;
+            }
+        } else {
+            $name = $values;
+        }
+        return ['name' => $name, 'params' => $params];
+    }
+
+    /**
+     * 解析注释
+     * @param $refMethod
+     * @param bool $enableRefService 是否终止service的引入
+     * @param string $source 注解来源
+     * @return array
+     */
+    protected function parseAnnotation($refMethod, bool $enableRefService = true,$source=""): array
+    {
+
+        $data = [];
+        if ($annotations = $this->reader->getMethodAnnotations($refMethod)) {
+            $headers = [];
+            $querys = [];
+            $params  = [];
+            $returns = [];
+            $before = [];
+            $after = [];
+            $responseErrors=[];
+            $routeParams=[];
+            $responseSuccess=[];
+
+            foreach ($annotations as $annotation) {
+                switch (true) {
+                    case $annotation instanceof ResponseSuccess:
+                        $responseSuccess = $this->handleParamAndReturned($responseSuccess,$annotation,'responseSuccess',$enableRefService);
+                        break;
+
+                    case $annotation instanceof Query:
+                        $querys = $this->handleParamAndReturned($querys,$annotation,'query',$enableRefService);
+                        break;
+                    case $annotation instanceof Param:
+                        $params = $this->handleParamAndReturned($params,$annotation,'param',$enableRefService);
+                        break;
+                    case $annotation instanceof Returned:
+                        $returns = $this->handleParamAndReturned($returns,$annotation,'returned',$enableRefService,$source);
+                        break;
+                    case $annotation instanceof ResponseError:
+                        $responseErrors = $this->handleParamAndReturned($responseErrors,$annotation,'responseError',$enableRefService,$source);
+                        break;
+                    case $annotation instanceof RouteParam:
+                        $routeParams = $this->handleParamAndReturned($routeParams,$annotation,'routeParam',$enableRefService,$source);
+                        break;
+                    case $annotation instanceof Header:
+                        if (!empty($annotation->ref)) {
+                            $refRes  = $this->renderRef($annotation->ref, $enableRefService);
+                            $headers = $this->handleRefData($headers, $refRes, $annotation, 'header');
+                        } else {
+                            $param     = [
+                                "name"    => $annotation->value,
+                                "desc"    => Lang::getLang($annotation->desc),
+                                "require" => $annotation->require,
+                                "type"    => $annotation->type,
+                                "default" => $annotation->default,
+                            ];
+                            $headers[] = $param;
+                        }
+                        break;
+
+                    case $annotation instanceof Author:
+                        $data['author'] = $annotation->value;
+                        break;
+
+                    case $annotation instanceof Title:
+                        $data['title'] = Lang::getLang($annotation->value);
+                        break;
+                    case $annotation instanceof Desc:
+                        $data['desc'] = Lang::getLang($annotation->value);
+//                        if (!empty($annotation->mdRef)){
+//                            $data['md'] = $annotation->mdRef;
+//                        }
+                        break;
+                    case $annotation instanceof Md:
+                        $data['md'] = $annotation->value;
+                        if (!empty($annotation->ref)){
+                            $data['md'] = ParseMarkdown::getContent("",$annotation->ref);
+                        }
+                        break;
+                    case $annotation instanceof ResponseSuccessMd:
+                        $data['responseSuccessMd'] = $annotation->value;
+                        if (!empty($annotation->ref)){
+                            $data['responseSuccessMd'] = ParseMarkdown::getContent("",$annotation->ref);
+                        }
+                        break;
+                    case $annotation instanceof ResponseErrorMd:
+                        $data['responseErrorMd'] = $annotation->value;
+                        if (!empty($annotation->ref)){
+                            $data['responseErrorMd'] = ParseMarkdown::getContent("",$annotation->ref);
+                        }
+                        break;
+                    case $annotation instanceof ParamType:
+                        $data['paramType'] = $annotation->value;
+                        break;
+                    case $annotation instanceof Url:
+                        $data['url'] = $annotation->value;
+                        break;
+                    case $annotation instanceof Method:
+                        if ($annotation->value && strpos($annotation->value, ',') !== false){
+                            $data['method'] =  explode(",", $annotation->value);
+                        }else{
+                            $data['method'] = strtoupper($annotation->value);
+                        }
+                        break;
+                    case $annotation instanceof Tag:
+                        $data['tag'] = $annotation->value;
+                        break;
+                    case $annotation instanceof ContentType:
+                    $data['contentType'] = $annotation->value;
+                    break;
+                    case $annotation instanceof Before:
+                        $beforeAnnotation = $this->handleEventAnnotation($annotation,'before');
+                        $before =  array_merge($before,$beforeAnnotation);
+                        break;
+                    case $annotation instanceof After:
+                        $afterAnnotation = $this->handleEventAnnotation($annotation,'after');
+                        $after =array_merge($after,$afterAnnotation);
+                        break;
+                }
+            }
+            if ($headers && count($headers) > 0) {
+                $data['header'] = $headers;
+            }
+            $data['query'] = $querys;
+            $data['param']  = $params;
+            $data['returned'] = $returns;
+            $data['responseSuccess'] = $responseSuccess;
+            $data['responseError'] = $responseErrors;
+            $data['routeParam'] = $routeParams;
+            $data['before'] = $before;
+            $data['after'] = $after;
+        }
+        return $data;
+    }
+
+    public function handleEventAnnotation($annotation,$type){
+        $config      = $this->config;
+        if (!empty($annotation->ref)){
+            if (strpos($annotation->ref, '\\') === false && !empty($config['definitions']) ) {
+                $refPath     = $config['definitions'] . '\\' . $annotation->ref;
+                $data        = $this->renderService($refPath);
+                if (!empty($data[$type])){
+                    return $data[$type];
+                }
+                return [];
+            }
+        }
+        if (!empty($annotation->value) && is_array($annotation->value)){
+            $beforeInfo = Helper::objectToArray($annotation);
+            $valueList = [];
+            foreach ($annotation->value as $valueItem){
+                $valueItemInfo = Helper::objectToArray($valueItem);
+                if ($valueItem instanceof Before){
+                    $valueItemInfo['type'] = "before";
+                }else if ($valueItem instanceof After){
+                    $valueItemInfo['type'] = "after";
+                }
+                $valueList[] = $valueItemInfo;
+            }
+            $beforeInfo['value'] = $valueList;
+            return [$beforeInfo];
+        }else{
+            return [$annotation];
+        }
+    }
+
+
+    /**
+     * 处理请求参数与返回参数
+     * @param $params
+     * @param $annotation
+     * @param string $type
+     * @param false $enableRefService
+     * @param string $source 注解来源
+     * @return array
+     */
+    protected function handleParamAndReturned($params,$annotation,$type="param",$enableRefService=false,$source=""){
+        if (!empty($annotation->ref)) {
+            $refRes = $this->renderRef($annotation->ref, $enableRefService);
+            $params = $this->handleRefData($params, $refRes, $annotation, $type,$source);
+        } else {
+
+            $param =  Helper::objectToArray($annotation);
+            $param["source"] = $source;
+            $param["desc"] = Lang::getLang($param['desc']);
+
+            $children      = $this->handleParamValue($annotation->value, $type);
+            $param['name'] = $children['name'];
+            if (count($children['params']) > 0) {
+                $param['children'] = $children['params'];
+            }
+            if (!empty($param['mdRef'])){
+                $param['md'] = ParseMarkdown::getContent("",$param['mdRef']);
+            }
+            if ($annotation->type === 'tree' ) {
+                // 类型为tree的
+                $param['children'][] = [
+                    'children' => $children['params'],
+                    'name'   => !empty($annotation->childrenField)?$annotation->childrenField:"children",
+                    'type'   => 'array<object>',
+                    'desc'   => Lang::getLang($annotation->childrenDesc),
+                ];
+            }
+            // 合并同级已有的字段
+            $params = Helper::arrayMergeAndUnique("name", $params, [$param]);
+        }
+            return $params;
+    }
+
+
+
+
+    /**
+     * 处理param、returned 参数
+     * @param $params
+     * @param $refRes
+     * @param $annotation
+     * @param string|null $source 注解来源
+     * @return array
+     */
+    protected function handleRefData($params, $refRes, $annotation, string $field,$source=""): array
+    {
+        if ($refRes['type'] === "model" && count($refRes['data']) > 0) {
+            // 模型引入
+            $data = $refRes['data'];
+        } else if ($refRes['type'] === "service" && !empty($refRes['data']) && !empty($refRes['data'][$field])) {
+            // service引入
+            $data = $refRes['data'][$field];
+        } else {
+            return $params;
+        }
+        // 过滤field
+        if (!empty($annotation->field)) {
+            $data = static::filterParamsField($data, $annotation->field, 'field');
+        }
+        // 过滤withoutField
+        if (!empty($annotation->withoutField)) {
+            $data = static::filterParamsField($data, $annotation->withoutField, 'withoutField');
+        }
+
+        if (!empty($annotation->value)) {
+            $item =  Helper::objectToArray($annotation);
+            $item['children'] = $data;
+            $item['source'] = $source;
+            $param["desc"] = Lang::getLang($item['desc']);
+
+            $children      = $this->handleParamValue($annotation->value, 'param');
+            $item['name'] = $children['name'];
+            if (count($children['params']) > 0) {
+                $item['children'] = Helper::arrayMergeAndUnique("name",$data,$children['params']);
+            }
+            if ($annotation->type === 'tree' ) {
+                // 类型为tree的
+                $item['children'][] = [
+                    'children' => $item['children'],
+                    'name'   =>!empty($annotation->childrenField) ?$annotation->childrenField:'children',
+                    'type'   => 'array',
+                    'desc'   => Lang::getLang($annotation->childrenDesc),
+                ];
+            }
+            if (!empty($item['name']) ){
+                $params[] = $item;
+            }else{
+                if (count($children['params']) > 0) {
+                    $data = Helper::arrayMergeAndUnique("name",$data,$children['params']);
+                }
+                $params = Helper::arrayMergeAndUnique("name",$params,$data);
+            }
+        } else {
+            $params = Helper::arrayMergeAndUnique("name",$params,$data);
+        }
+        return $params;
+    }
+
+    /**
+     * Params、Returned过滤指定字段、或只取指定字段
+     * @param $data 参数
+     * @param $fields 指定字段
+     * @param string $type 处理类型
+     * @return array
+     */
+    public static function filterParamsField(array $data, $fields, string $type = "field"): array
+    {
+        if ($fields && strpos($fields, ',') !== false){
+            $fieldArr = explode(',', $fields);
+        }else{
+            $fieldArr = [$fields];
+        }
+        $dataList = [];
+        foreach ($data as $item) {
+            if (!empty($item['name']) && in_array($item['name'], $fieldArr) && $type === 'field') {
+                $dataList[] = $item;
+            } else if (!(!empty($item['name']) && in_array($item['name'], $fieldArr)) && $type == "withoutField") {
+                $dataList[] = $item;
+            }
+        }
+        return $dataList;
+    }
+
+
+
+}

+ 353 - 0
vendor/hg/apidoc/src/parses/ParseApiMenus.php

@@ -0,0 +1,353 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parses;
+
+use Doctrine\Common\Annotations\AnnotationReader;
+use hg\apidoc\utils\DirAndFile;
+use hg\apidoc\utils\Helper;
+use hg\apidoc\utils\Lang;
+use ReflectionClass;
+use Symfony\Component\ClassLoader\ClassMapGenerator;
+use hg\apidoc\annotation\Group;
+use hg\apidoc\annotation\Sort;
+use hg\apidoc\annotation\Title;
+use hg\apidoc\annotation\Desc;
+use hg\apidoc\annotation\Author;
+use hg\apidoc\annotation\Tag;
+use hg\apidoc\annotation\Header;
+use hg\apidoc\annotation\ParamType;
+use hg\apidoc\annotation\Url;
+use hg\apidoc\annotation\Method;
+use hg\apidoc\annotation\ContentType;
+
+class ParseApiMenus
+{
+
+    protected $config = [];
+
+    protected $reader;
+
+    //tags,当前应用/版本所有的tag
+    protected $tags = array();
+
+    //groups,当前应用/版本的分组name
+    protected $groups = array();
+
+    protected $controller_layer = "App";
+
+    protected $currentApp = [];
+
+    protected $parseApiDetail;
+
+    public function __construct($config)
+    {
+        $this->reader = new AnnotationReader();
+        if (!empty($config['ignored_annitation'])){
+            foreach ($config['ignored_annitation'] as $item) {
+                AnnotationReader::addGlobalIgnoredName($item);
+            }
+        }
+        $this->config = $config;
+    }
+
+    /**
+     * 生成api接口数据
+     * @param string $appKey
+     * @return array
+     */
+    public function renderApiMenus(string $appKey): array
+    {
+        $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+        $currentApp = $currentAppConfig['appConfig'];
+        $this->currentApp  = $currentApp;
+
+        if (!empty($currentApp['controllers']) && count($currentApp['controllers']) > 0) {
+            // 配置的控制器列表
+            $controllers = $this->getConfigControllers($currentApp['path'],$currentApp['controllers']);
+        } else {
+            // 默认读取所有的
+            $controllers = $this->getDirControllers($currentApp['path']);
+        }
+
+
+        $apiData = [];
+        if (!empty($controllers) && count($controllers) > 0) {
+            foreach ($controllers as $class) {
+                $classData = $this->parseController($class);
+                if ($classData !== false) {
+                    $apiData[] = $classData;
+                }
+            }
+        }
+        // 排序
+        $apiList = Helper::arraySortByKey($apiData);
+        $json = array(
+            "data"   => $apiList,
+            "tags"   => $this->tags,
+            "groups" => $this->groups,
+        );
+        return $json;
+    }
+
+    /**
+     * 获取生成文档的控制器列表
+     * @param string $path
+     * @return array
+     */
+    protected function getConfigControllers(string $path,$appControllers): array
+    {
+        $controllers = [];
+        $configControllers = $appControllers;
+        if (!empty($configControllers) && count($configControllers) > 0) {
+            foreach ($configControllers as $item) {
+                if ( strpos($item, $path) !== false && class_exists($item)) {
+                    $controllers[] = $item;
+                }
+            }
+        }
+        return $controllers;
+    }
+
+    /**
+     * 获取目录下的控制器列表
+     * @param string $path
+     * @return array
+     */
+    protected function getDirControllers(string $path): array
+    {
+
+        if ($path) {
+            if (strpos(APIDOC_ROOT_PATH, '/') !== false) {
+                $pathStr = str_replace("\\", "/", $path);
+            } else {
+                $pathStr = $path;
+            }
+            $dir = APIDOC_ROOT_PATH . $pathStr;
+        } else {
+            $dir = APIDOC_ROOT_PATH . $this->controller_layer;
+        }
+        $controllers = [];
+        if (is_dir($dir)) {
+            $controllers = $this->scanDir($dir, $path);
+        }
+        return $controllers;
+    }
+
+
+
+    protected function scanDir($dir) {
+
+        $classList= DirAndFile::getClassList($dir);
+        $list=[];
+
+        $configFilterController = !empty($this->config['filter_controllers']) ? $this->config['filter_controllers'] : [];
+        $currentAppFilterController =  !empty($this->currentApp['filter_controllers']) ? $this->currentApp['filter_controllers'] : [];
+        $filterControllers = array_merge($configFilterController,$currentAppFilterController);
+
+        $configFilterDir = !empty($this->config['filter_dirs']) ? $this->config['filter_dirs'] : [];
+        $currentAppFilterDir =  !empty($this->currentApp['filter_dirs']) ? $this->currentApp['filter_dirs'] : [];
+        $filterDirList = array_merge($configFilterDir,$currentAppFilterDir);
+        $filterDirs=[];
+        foreach ($filterDirList as $dirItem) {
+            $dirItemPath = DirAndFile::formatPath($dirItem,"/");
+            $filterDirs[]=$dirItemPath;
+        }
+
+        foreach ($classList as $item) {
+            $classNamespace = $item['name'];
+
+            $isFilterDir = false;
+            foreach ($filterDirs as $dirItem) {
+                if (strpos($item['path'], $dirItem) !== false){
+                    $isFilterDir=true;
+                }
+            }
+            if ($isFilterDir){
+                continue;
+            }else if (
+                !in_array($classNamespace, $filterControllers) &&
+                $this->config['definitions'] != $classNamespace
+
+            ) {
+                $list[] = $classNamespace;
+            }
+        }
+
+        return $list;
+    }
+
+    public function parseController($class)
+    {
+
+        $data                 = [];
+        $refClass             = new ReflectionClass($class);
+        $classTextAnnotations = ParseAnnotation::parseTextAnnotation($refClass);
+        if (in_array("NotParse", $classTextAnnotations)) {
+            return false;
+        }
+        $title = $this->reader->getClassAnnotation($refClass, Title::class);
+        $group = $this->reader->getClassAnnotation($refClass, Group::class);
+        $sort = $this->reader->getClassAnnotation($refClass, Sort::class);
+        $routeGroup         = $this->reader->getClassAnnotation($refClass, RouteGroup::class);
+
+        $controllersNameArr = explode("\\", $class);
+        $controllersName    = $controllersNameArr[count($controllersNameArr) - 1];
+        $data['controller'] = $controllersName;
+        $data['path'] = $class;
+        $data['group']      = !empty($group->value) ? $group->value : null;
+        $data['sort']      = !empty($sort->value) ? $sort->value : null;
+        if (!empty($data['group']) && !in_array($data['group'], $this->groups)) {
+            $this->groups[] = $data['group'];
+        }
+        $data['title'] = !empty($title) && !empty($title->value) ? $title->value : "";
+
+        if (empty($title)) {
+            if (!empty($classTextAnnotations) && count($classTextAnnotations) > 0) {
+                $data['title'] = $classTextAnnotations[0];
+            } else {
+                $data['title'] = $controllersName;
+            }
+        }
+        $data['title'] = Lang::getLang($data['title']);
+        $methodList       = [];
+        $data['menuKey'] = Helper::createRandKey($data['controller']);
+        $isNotDebug = in_array("NotDebug", $classTextAnnotations);
+
+        foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $refMethod) {
+            $methodItem = $this->parseApiMethod($refClass,$refMethod,$routeGroup);
+            if ($methodItem===false){
+                continue;
+            }
+            if ($isNotDebug) {
+                $methodItem['notDebug'] = true;
+            }
+            $methodList[] = $methodItem;
+        }
+        $data['children'] = $methodList;
+        if (count($methodList)===0){
+            return false;
+        }
+        return $data;
+    }
+
+
+    protected function parseApiMethod($refClass,$refMethod,$routeGroup){
+        $config               = $this->config;
+        if (empty($refMethod->name)) {
+            return false;
+        }
+        if (empty($this->parseApiDetail)){
+            $this->parseApiDetail = new ParseApiDetail($config);
+        }
+        $textAnnotations = ParseAnnotation::parseTextAnnotation($refMethod);
+        $methodInfo = $this->parseMethodAnnotation($refMethod);
+        if (empty($methodInfo)){
+            return false;
+        }
+        $methodInfo = $this->parseApiDetail->handleApiBaseInfo($methodInfo,$refClass,$refMethod,$textAnnotations);
+        return $methodInfo;
+
+    }
+
+
+    /**
+     * 解析方法注释
+     * @param $refMethod
+     * @return array
+     */
+    protected function parseMethodAnnotation($refMethod): array
+    {
+        $data = [];
+        if ($annotations = $this->reader->getMethodAnnotations($refMethod)) {
+            foreach ($annotations as $annotation) {
+                switch (true) {
+                    case $annotation instanceof Author:
+                        $data['author'] = $annotation->value;
+                        break;
+
+                    case $annotation instanceof Title:
+                        $data['title'] = Lang::getLang($annotation->value);
+                        break;
+                    case $annotation instanceof ParamType:
+                        $data['paramType'] = $annotation->value;
+                        break;
+                    case $annotation instanceof Url:
+                        $data['url'] = $annotation->value;
+                        break;
+                    case $annotation instanceof Method:
+                        if ($annotation->value && strpos($annotation->value, ',') !== false){
+                            $data['method'] =  explode(",", $annotation->value);
+                        }else{
+                            $data['method'] = strtoupper($annotation->value);
+                        }
+                        break;
+                    case $annotation instanceof Tag:
+                        $data['tag'] = $annotation->value;
+                        break;
+                    case $annotation instanceof ContentType:
+                    $data['contentType'] = $annotation->value;
+                    break;
+                }
+            }
+
+        }
+        return $data;
+    }
+
+
+
+    /**
+     * 对象分组到tree
+     * @param $tree
+     * @param $objectData
+     * @param string $childrenField
+     * @return array
+     */
+    public static function objtctGroupByTree($tree,$objectData,$childrenField='children'){
+        $data = [];
+        foreach ($tree as $node){
+            if (!empty($node[$childrenField])){
+                $node[$childrenField] = static::objtctGroupByTree($node[$childrenField],$objectData);
+            }else if (!empty($objectData[$node['name']])){
+                $node[$childrenField] =  $objectData[$node['name']];
+            }
+            $node['menuKey'] = Helper::createRandKey( $node['name']);
+            $data[] = $node;
+        }
+        return $data;
+    }
+
+    /**
+     * 合并接口到应用分组
+     * @param $apiData
+     * @param $groups
+     * @return array
+     */
+    public static function mergeApiGroup($apiData,$groups){
+        if (empty($groups) || count($apiData)<1){
+            return $apiData;
+        }
+        $apiObject = [];
+        foreach ($apiData as $controller){
+            if (!empty($controller['group'])){
+                if (!empty($apiObject[$controller['group']])){
+                    $apiObject[$controller['group']][] = $controller;
+                }else{
+                    $apiObject[$controller['group']] = [$controller];
+                }
+            }else{
+                if (!empty($apiObject['notGroup'])){
+                    $apiObject['notGroup'][] = $controller;
+                }else{
+                    $apiObject['notGroup'] = [$controller];
+                }
+            }
+        }
+        if (!empty($apiObject['notGroup'])){
+            array_unshift($groups,['title'=>'未分组','name'=>'notGroup']);
+        }
+        $res = static::objtctGroupByTree($groups,$apiObject);
+        return $res;
+    }
+}

+ 104 - 0
vendor/hg/apidoc/src/parses/ParseCodeTemplate.php

@@ -0,0 +1,104 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parses;
+
+use Doctrine\Common\Annotations\AnnotationReader;
+use hg\apidoc\exception\ErrorException;
+use hg\apidoc\generator\ParseTemplate;
+use hg\apidoc\utils\DirAndFile;
+use hg\apidoc\utils\Helper;
+use hg\apidoc\utils\Lang;
+
+class ParseCodeTemplate
+{
+
+    protected $config = [];
+
+    protected $currentApp = [];
+
+
+    public function __construct($config)
+    {
+
+        $this->config = $config;
+    }
+
+    public function renderCode($params)
+    {
+        $appKey = $params['appKey'];
+        $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+        $currentApp = $currentAppConfig['appConfig'];
+        $this->currentApp  = $currentApp;
+
+        $codeTemplate = $params['template'];
+
+        //验证参数
+
+        //验证模板文件是否存在
+
+        //解析接口数据
+        $tplData = [];
+        if ($codeTemplate['select_mode'] == 'controller'){
+            $parseApiMenusService = new ParseApiMenus($this->config);
+            $controllers = $params['selected'];
+            if (!empty($controllers) && count($controllers) > 0) {
+                $controllerList = [];
+                foreach ($controllers as $class) {
+                    $classData = $parseApiMenusService->parseController($class);
+                    if ($classData !== false) {
+                        $controllerList[] = $classData;
+                    }
+                }
+                if (empty($codeTemplate['multiple'])){
+                    $tplData = $controllerList[0];
+                }else{
+                    $tplData = $controllerList;
+                }
+            }
+        }else{
+            // api
+            $apis = $params['selected'];
+            if (!empty($apis) && count($apis) > 0) {
+                $parseApiDetailService = new ParseApiDetail($this->config);
+                $apiList = [];
+                foreach ($apis as $key) {
+                    $apiKey = urldecode($key);
+                    $pathArr   = explode("@", $apiKey);
+                    $classPath = $pathArr[0];
+                    $method = $pathArr[1];
+
+                    $apiDetail = $parseApiDetailService->renderApiDetail($appKey,$classPath,$method);
+                    if ($apiDetail !== false) {
+                        $apiList[] = $apiDetail;
+                    }
+                }
+                if (empty($codeTemplate['multiple'])){
+                    $tplData = $apiList[0];
+                }else{
+                    $tplData = $apiList;
+                }
+            }
+        }
+
+
+        // 读取模板
+        $templatePath =DirAndFile::formatPath( APIDOC_ROOT_PATH . $codeTemplate['template'],"/");
+        if (is_readable($templatePath) == false) {
+            throw new ErrorException("template not found",  [
+                'template' => $template
+            ]);
+        }
+        $tplParams = [
+            'form'=>  $params['form'],
+            'data'=>$tplData
+        ];
+        $html = (new ParseTemplate())->compile($templatePath,$tplParams);
+
+
+
+        return $html;
+    }
+
+
+}

+ 136 - 0
vendor/hg/apidoc/src/parses/ParseMarkdown.php

@@ -0,0 +1,136 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parses;
+
+
+use hg\apidoc\utils\DirAndFile;
+use hg\apidoc\utils\Helper;
+use hg\apidoc\utils\Lang;
+
+class ParseMarkdown
+{
+    protected $config = [];
+
+    public function __construct($config)
+    {
+        $this->config = $config;
+    }
+
+    /**
+     * 获取md文档菜单
+     * @return array
+     */
+    public function getDocsMenu($appKey,string $lang): array
+    {
+        $config  = $this->config;
+        $docData = [];
+        if (!empty($config['docs']) && count($config['docs']) > 0) {
+            $docData = $this->handleDocsMenuData($config['docs'],$appKey,$lang);
+        }
+        return $docData;
+    }
+
+    /**
+     * 处理md文档菜单数据
+     * @param array $menus
+     * @return array
+     */
+    protected function handleDocsMenuData(array $menus,$appKey,string $lang): array
+    {
+        $list = [];
+        foreach ($menus as $item) {
+            $item['title']     = Lang::getLang($item['title']);
+            if (!empty($item['appKey']) && $item['appKey'] != $appKey){
+                continue;
+            }
+
+            if (!empty($item['children']) && count($item['children']) > 0) {
+                $item['children']    = $this->handleDocsMenuData($item['children'],$appKey,$lang);
+                $item['menuKey'] = Helper::createRandKey("md_group");
+            } else {
+                $filePath    = static::getFilePath($appKey,$item['path'],$lang);
+                if (!file_exists($filePath['filePath'])) {
+                    continue;
+                }
+
+                if(!empty($item['path'])){
+                    $item['path'] = Helper::replaceTemplate($item['path'],['lang'=>$lang]);
+                }
+                $item['type']     = 'md';
+                $item['menuKey'] = Helper::createApiKey($item['path']);
+            }
+            $list[]           = $item;
+        }
+        return $list;
+    }
+
+    public static function getFilePath(string $appKey, string $path,$lang=""){
+        if (!empty($appKey)){
+            $currentAppConfig = Helper::getCurrentAppConfig($appKey);
+            $currentApps = $currentAppConfig['apps'];
+            $fullPath      = Helper::replaceCurrentAppTemplate($path, $currentApps);
+        }else{
+            $fullPath = $path;
+        }
+        $fullPath = Helper::replaceTemplate($fullPath,[
+            'lang'=>$lang
+        ]);
+
+        if (strpos($fullPath, '#') !== false) {
+            $mdPathArr = explode("#", $fullPath);
+            $mdPath=$mdPathArr[0];
+            $mdAnchor =$mdPathArr[1];
+        } else {
+            $mdPath = $fullPath;
+            $mdAnchor="";
+        }
+        $fileSuffix = "";
+        if (strpos($fullPath, '.md') === false) {
+            $fileSuffix = ".md";
+        }
+        $filePath    = APIDOC_ROOT_PATH . $mdPath . $fileSuffix;
+        return [
+            'filePath'=>$filePath,
+            'anchor'=>$mdAnchor
+        ];
+    }
+
+
+    /**
+     * 获取md文档内容
+     * @param string $appKey
+     * @param string $path
+     * @return string
+     */
+    public static function getContent(string $appKey, string $path,$lang="")
+    {
+        $filePathArr    = static::getFilePath($appKey,$path,$lang);
+        $mdAnchor = $filePathArr['anchor'];
+        $filePath = $filePathArr['filePath'];
+        $contents    = DirAndFile::getFileContent($filePath);
+        // 获取指定h2标签内容
+        if (!empty($mdAnchor)){
+            if (strpos($contents, '## ') !== false) {
+                $contentArr = explode("\r\n", $contents);
+                $contentText = "";
+                foreach ($contentArr as $line){
+                    $contentText.="\r\n".trim($line);
+                }
+                $contentArr = explode("\r\n## ", $contentText);
+                $content="";
+                foreach ($contentArr as $item){
+                    $itemArr = explode("\r\n", $item);
+                    if (!empty($itemArr) && $itemArr[0] && $mdAnchor===$itemArr[0]){
+                        $content = str_replace($itemArr[0]."\r\n", '', $item);
+                        break;
+                    }
+                }
+                return $content;
+            }
+        }
+        return $contents;
+    }
+
+
+}

+ 277 - 0
vendor/hg/apidoc/src/parses/ParseModel.php

@@ -0,0 +1,277 @@
+<?php
+declare(strict_types = 1);
+
+namespace hg\apidoc\parses;
+
+use Doctrine\Common\Annotations\Reader;
+use hg\apidoc\exception\ErrorException;
+use hg\apidoc\annotation\Field;
+use hg\apidoc\annotation\WithoutField;
+use hg\apidoc\annotation\AddField;
+use hg\apidoc\utils\Helper;
+use hg\apidoc\utils\Lang;
+
+
+class ParseModel
+{
+    protected static $reader;
+    protected static $config=[];
+
+    public function __construct(Reader $reader,$config=[])
+    {
+         self::$reader = $reader;
+         self::$config = $config;
+    }
+
+    /**
+     * 生成模型数据
+     * @param string $path
+     * @return array|false
+     * @throws \ReflectionException
+     */
+    public function renderModel(string $path)
+    {
+            if (class_exists($path)) {
+
+                $model = static::getModelClass($path);
+                return static::parseModelTable($model,$path,"");
+            } else {
+                if (strpos($path, '@') !== false){
+                    $pathArr   = explode("@", $path);
+                    $modelClassPath = $pathArr[0];
+                    $methodName =  $pathArr[1];
+                }else{
+                    $modelClassArr   = explode("\\", $path);
+                    $methodName = $modelClassArr[count($modelClassArr) - 1];
+                    unset($modelClassArr[count($modelClassArr) - 1]);
+                    $modelClassPath  = implode("\\", $modelClassArr);
+                }
+                if (class_exists($modelClassPath)){
+                    $model = static::getModelClass($modelClassPath);
+                    return static::parseModelTable($model,$modelClassPath,$methodName);
+                }else{
+                    throw new ErrorException("ref file not exists",  [
+                        'path' => $path
+                    ]);
+                }
+            }
+    }
+
+    protected static function parseModelTable($model,$path,$methodName=""){
+        if (!is_callable(array($model, 'getTable'))) {
+            return false;
+        }
+        $reader=static::$reader;
+        try {
+            $classReflect    = new \ReflectionClass($path);
+            // 获取所有模型属性
+            $propertys = $classReflect->getDefaultProperties();
+            $table =static::getTableDocument($model, $propertys);
+            if (empty($methodName)){
+                return $table;
+            }
+
+            $methodAction    = $classReflect->getMethod($methodName);
+            // 模型注释-field
+            if ($fieldAnnotations = $reader->getMethodAnnotation($methodAction, Field::class)) {
+                $table = ParseApiDetail::filterParamsField($table, $fieldAnnotations->value, 'field');
+            }
+            // 模型注释-withoutField
+            if ($fieldAnnotations = $reader->getMethodAnnotation($methodAction, WithoutField::class)) {
+                $table = ParseApiDetail::filterParamsField($table, $fieldAnnotations->value, 'withoutField');
+            }
+            // 模型注释-addField
+            if ($annotations = $reader->getMethodAnnotations($methodAction)) {
+                foreach ($annotations as $annotation) {
+                    switch (true) {
+                        case $annotation instanceof AddField:
+                            $param         = [
+                                "name"    => "",
+                                "desc"    => $annotation->desc,
+                                "require" => $annotation->require,
+                                "type"    => $annotation->type,
+                                "default" => $annotation->default,
+                            ];
+                            if (!empty($annotation->mdRef)){
+                                $param['md'] = ParseMarkdown::getContent("",$annotation->mdRef);
+                            }
+                            if (!empty($annotation->md)){
+                                $param['md'] = $annotation->md;
+                            }
+                            $children      = static::handleParamValue($annotation->value);
+                            $param['name'] = $children['name'];
+                            if (count($children['params']) > 0) {
+                                $param['children'] = $children['params'];
+                            }
+                            $isExists = false;
+                            $newTable = [];
+                            foreach ($table as $item) {
+                                if ($param['name'] === $item['name']) {
+                                    $isExists   = true;
+                                    $newTable[] = $param;
+                                } else {
+                                    $newTable[] = $item;
+                                }
+                            }
+                            $table = $newTable;
+                            if (!$isExists) {
+                                $table[] = $param;
+                            }
+                            break;
+                    }
+                }
+            }
+            return $table;
+        } catch (\ReflectionException $e) {
+            throw new ErrorException('Class '.$path.' '.$e->getMessage());
+        }
+
+    }
+    /**
+     * 处理字段参数
+     * @param $values
+     * @return array
+     */
+    protected static function handleParamValue($values): array
+    {
+        $name   = "";
+        $params = [];
+        if (!empty($values) && is_array($values) && count($values) > 0) {
+            foreach ($values as $item) {
+                if (is_string($item)) {
+                    $name = $item;
+                } else if (is_object($item)) {
+                    $param         = [
+                        "name"    => "",
+                        "type"    => $item->type,
+                        "desc"    => $item->desc,
+                        "default" => $item->default,
+                        "require" => $item->require,
+                    ];
+                    $children      = static::handleParamValue($item->value);
+                    $param['name'] = $children['name'];
+                    if (count($children['params']) > 0) {
+                        $param['children'] = $children['params'];
+                    }
+                    $params[] = $param;
+                }
+            }
+        } else {
+            $name = $values;
+        }
+        return ['name' => $name, 'params' => $params];
+    }
+
+    /**
+     * 获取模型实例
+     * @param $method
+     * @return mixed|null
+     */
+    public static function getModelClass($namespaceName)
+    {
+        if (!empty($namespaceName) && class_exists($namespaceName)) {
+            $modelInstance = new $namespaceName();
+            return $modelInstance;
+        } else {
+            return null;
+        }
+    }
+
+
+
+
+    /**
+     * 获取模型注解数据
+     * @param $model
+     * @param $propertys
+     * @return array
+     */
+    public static function getTableDocument($model,array $propertys):array
+    {
+        $config = static::$config;
+        if (empty($config['database_query_function'])){
+            throw new ErrorException("not datatable_query_function config");
+        }
+        $sqlRes = $config['database_query_function']("show create table " . $model->getTable());
+        $createTableObj = $sqlRes[0];
+        $createTableArray = Helper::objectToArray($createTableObj);
+        $createTable = "";
+        if (!empty($createTableArray['Create Table'])){
+            $createTable = $createTableArray['Create Table'];
+        }else  if(!empty($createTableArray['create table'])){
+            $createTable = $createTableArray['create table'];
+        }else{
+            throw new ErrorException("datatable not exists",  $createTableArray);
+        }
+        preg_match_all("#[^KEY]`(.*?)` (.*?) (.*?),\n#", $createTable, $matches);
+        $fields       = $matches[1];
+        $types        = $matches[2];
+        $contents     = $matches[3];
+        $fieldComment = [];
+        //组织注释
+        for ($i = 0; $i < count($matches[0]); $i++) {
+            $key     = $fields[$i];
+            $type    = $types[$i];
+            $default = "";
+            $require = "";
+            $desc    = "";
+            $mock = "";
+            $md="";
+            $content = $contents[$i];
+            if (strpos($type, '(`') !== false) {
+                continue;
+            }
+            if (strpos($content, 'COMMENT') !== false) {
+                // 存在字段注释
+                preg_match_all("#COMMENT\s*'(.*?)'#", $content, $edscs);
+                if (!empty($edscs[1]) && !empty($edscs[1][0])){
+                    $desc = Lang::getLang($edscs[1][0]);
+                    if (strpos($desc, 'mock(') !== false){
+                        // 存在mock
+                        preg_match('#mock\((.*)\)#s', $content, $mocks);
+                        if (!empty($mocks[1])) {
+                            $mock = $mocks[1];
+                            $desc = str_replace($mocks[0],"",$desc);
+                        }
+                    }
+                    if (strpos($desc, 'mdRef="') !== false){
+                        // 存在mdRef
+                        preg_match('#mdRef="(.*)"#s', $content, $mdRefs);
+                        if (!empty($mdRefs[1])) {
+                            $md = ParseMarkdown::getContent("",$mdRefs[1]);
+                            $desc = str_replace($mdRefs[0],"",$desc);
+                        }
+                    }
+                }
+
+            }
+            if (strpos($content, 'DEFAULT') !== false) {
+                // 存在字段默认值
+                preg_match_all("#DEFAULT\s*'(.*?)'#", $content, $defaults);
+                $default = $defaults[1] && is_array($defaults[1])?$defaults[1][0]:"";
+            }
+
+            if (strpos($content, 'NOT NULL') !== false) {
+                // 必填字段
+                $require = "1";
+            }
+
+            $name = $key;
+            // 转换字段名为驼峰命名(用于输出)
+            if (isset($propertys['convertNameToCamel']) && $propertys['convertNameToCamel'] === true) {
+                $name = Helper::camel($key);
+            }
+            $fieldComment[] = [
+                "name"    => $name,
+                "type"    => $type,
+                "desc"    => $desc,
+                "default" => $default,
+                "require" => $require,
+                "mock"=>$mock,
+                "md"=>$md
+            ];
+        }
+        return $fieldComment;
+    }
+
+}

+ 133 - 0
vendor/hg/apidoc/src/providers/BaseService.php

@@ -0,0 +1,133 @@
+<?php
+
+namespace hg\apidoc\providers;
+
+use hg\apidoc\utils\ConfigProvider;
+
+trait BaseService
+{
+
+    static $routes = [
+        ['rule'=>'config','route'=>'getConfig','method'=>'GET'],
+        ['rule'=>'apiMenus','route'=>'getApiMenus','method'=>'POST'],
+        ['rule'=>'apiDetail','route'=>'getApiDetail','method'=>'POST'],
+        ['rule'=>'docMenus','route'=>'getMdMenus','method'=>'POST'],
+        ['rule'=>'docDetail','route'=>'getMdDetail','method'=>'POST'],
+        ['rule'=>'verifyAuth','route'=>'verifyAuth','method'=>'POST'],
+        ['rule'=>'generator','route'=>'createGenerator','method'=>'POST'],
+        ['rule'=>'cancelAllCache','route'=>'cancelAllCache','method'=>'POST'],
+        ['rule'=>'createAllCache','route'=>'createAllCache','method'=>'POST'],
+        ['rule'=>'renderCodeTemplate','route'=>'renderCodeTemplate','method'=>'POST'],
+    ];
+
+
+
+    /**
+     * 获取apidoc配置
+     * @return array 返回apidoc配置
+     */
+    abstract static function getApidocConfig();
+
+
+    /**
+     * 注册apidoc路由
+     * @param $route 路由参数
+     * @return mixed
+     */
+    abstract static function registerRoute($route);
+
+    /**
+     * 执行Sql语句
+     * @return mixed
+     */
+    abstract static function databaseQuery($sql);
+
+    /**
+     * 获取项目根目录
+     * @return string 返回项目根目录
+     */
+    abstract static function getRootPath();
+
+    /**
+     * 获取缓存目录
+     * @return string 返回项目缓存目录
+     */
+    abstract static function getRuntimePath();
+
+
+    /**
+     * 设置当前语言
+     * @param $locale 语言标识
+     * @return mixed
+     */
+    abstract static function setLang($locale);
+
+    /**
+     * 获取语言定义
+     * @param $lang
+     * @return string
+     */
+    abstract static function getLang($lang);
+
+
+    /**
+     * 处理apidoc接口响应的数据
+     * @return mixed
+     */
+    abstract static function handleResponseJson($res);
+
+    abstract static function getTablePrefix();
+
+
+    public function initConfig(){
+        ! defined('APIDOC_ROOT_PATH') && define('APIDOC_ROOT_PATH', $this->getRootPath());
+        ! defined('APIDOC_STORAGE_PATH') && define('APIDOC_STORAGE_PATH', $this->getRuntimePath());
+        $config = self::getApidocConfig();
+        $config['database_query_function'] = function ($sql){
+            return self::databaseQuery($sql);
+        };
+        $config['lang_register_function'] = function ($sql){
+            return self::setLang($sql);
+        };
+        $config['lang_get_function'] = function ($lang){
+            return self::getLang($lang);
+        };
+        $config['handle_response_json'] = function ($res){
+            return self::handleResponseJson($res);
+        };
+        $table_prefix = self::getTablePrefix();
+        if (!empty($config['database'])){
+            if (empty($config['prefix'])){
+                $config['database']['prefix'] = $table_prefix;
+            }
+        }else{
+            $config['database']=[
+                'prefix'=>$table_prefix
+            ];
+        }
+        ConfigProvider::set($config);
+    }
+
+    /**
+     * @param null $routeFun
+     */
+    static public function registerApidocRoutes($routeFun=null){
+        $routes = static::$routes;
+        $controller_namespace = '\hg\apidoc\Controller@';
+        $route_prefix = "/apidoc/";
+        foreach ($routes as $item) {
+            $route = [
+                'uri'=>$route_prefix.$item['rule'],
+                'callback'=>$controller_namespace.$item['route'],
+                'route'=>$item['route'],
+                'method'=>$item['method']
+            ];
+            if (!empty($routeFun)){
+                $routeFun($route);
+            }else{
+                self::registerRoute($route);
+            }
+        }
+    }
+
+}

+ 56 - 0
vendor/hg/apidoc/src/providers/CommonService.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace hg\apidoc\providers;
+
+use hg\apidoc\utils\ConfigProvider;
+
+class CommonService
+{
+    use BaseService;
+
+    static function getApidocConfig()
+    {
+        // TODO: Implement getApidocConfig() method.
+    }
+
+    static function registerRoute($route)
+    {
+        // TODO: Implement registerRoute() method.
+    }
+
+    static function databaseQuery($sql)
+    {
+        // TODO: Implement databaseQuery() method.
+    }
+
+    static function getRootPath()
+    {
+        // TODO: Implement getRootPath() method.
+    }
+
+    static function getRuntimePath()
+    {
+        // TODO: Implement getRuntimePath() method.
+    }
+
+    static function setLang($locale)
+    {
+        // TODO: Implement setLang() method.
+    }
+
+    static function getLang($lang)
+    {
+        // TODO: Implement getLang() method.
+    }
+
+    static function handleResponseJson($res)
+    {
+        // TODO: Implement handleResponseJson() method.
+    }
+
+    static function getTablePrefix()
+    {
+        // TODO: Implement getTablePrefix() method.
+    }
+
+}

+ 80 - 0
vendor/hg/apidoc/src/providers/HyperfService.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace hg\apidoc\providers;
+
+use hg\apidoc\utils\ConfigProvider;
+use Hyperf\DbConnection\Db;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+class HyperfService
+{
+    use BaseService;
+
+    static $langLocale="zh_CN";
+
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        $this->initConfig();
+
+        if ($request->getMethod() == "GET"){
+            $params = $request->getQueryParams();
+        }else{
+            $params = $request->getParsedBody();
+        }
+        $config =  ConfigProvider::get();
+        $config['request_params'] = $params;
+        ConfigProvider::set($config);
+        return $handler->handle($request);
+    }
+
+    static function getApidocConfig()
+    {
+        $config = config("apidoc");
+        if (!(!empty($config['auto_url']) && !empty($config['auto_url']['filter_keys']))){
+            $config['auto_url']['filter_keys'] = ['App','Controller'];
+        }
+        return $config;
+    }
+
+    static function registerRoute($route)
+    {
+        // TODO: Implement registerRoute() method.
+    }
+
+    static function databaseQuery($sql)
+    {
+        return Db::select($sql);
+    }
+
+    static function getRootPath()
+    {
+        return BASE_PATH."/";
+    }
+
+    static function getRuntimePath()
+    {
+        return BASE_PATH."/runtime/";
+    }
+
+    static function setLang($locale)
+    {
+        static::$langLocale = $locale;
+    }
+
+    static function getLang($lang): string
+    {
+        return trans($lang);
+    }
+
+    static function handleResponseJson($res)
+    {
+        return $res;
+    }
+
+    static function getTablePrefix(){
+        return config('databases.default.prefix','');
+    }
+}

+ 84 - 0
vendor/hg/apidoc/src/providers/LaravelService.php

@@ -0,0 +1,84 @@
+<?php
+
+namespace hg\apidoc\providers;
+
+use hg\apidoc\middleware\LaravelMiddleware;
+use Illuminate\Support\Facades\Lang;
+use Illuminate\Support\Facades\Request;
+use Illuminate\Support\ServiceProvider;
+use Illuminate\Support\Facades\Route;
+use Exception;
+use hg\apidoc\utils\ApiCrossDomain;
+use Illuminate\Support\Facades\DB;
+
+class LaravelService extends ServiceProvider
+{
+
+    use BaseService;
+
+    public function boot()
+    {
+        $this->publishes([
+            __DIR__.'/../config.php' => config_path('apidoc.php'),
+        ]);
+    }
+
+    public function register()
+    {
+        $config = static::getApidocConfig();
+        $this->initConfig();
+        self::registerApidocRoutes();
+    }
+
+    static function getApidocConfig()
+    {
+        $config = config("apidoc");
+        if (!(!empty($config['auto_url']) && !empty($config['auto_url']['filter_keys']))){
+            $config['auto_url']['filter_keys'] = ['App','Http','Controllers'];
+        }
+        return $config;
+    }
+
+    static function registerRoute($route){
+        $config = self::getApidocConfig();
+        $registerRoute =  Route::match([$route['method']],$route['uri'], $route['callback']);
+        $registerRoute->middleware([LaravelMiddleware::class]);
+        if (!empty($config['allowCrossDomain'])) {
+            $registerRoute->middleware([ApiCrossDomain::class]);
+        }
+    }
+
+    static function databaseQuery($sql){
+        return DB::select($sql);
+    }
+
+    static function getTablePrefix(){
+        $driver = config('database.default');
+        $table_prefix=config('database.connections.'.$driver.'.prefix');
+        return $table_prefix;
+    }
+
+    static function getRootPath()
+    {
+        return base_path()."/";
+    }
+
+    static function getRuntimePath()
+    {
+        return storage_path()."/";
+    }
+
+    static function setLang($locale){
+        Lang::setLocale($locale);
+    }
+
+    static function getLang($lang){
+        return trans($lang);
+    }
+
+    static function handleResponseJson($res){
+        return $res;
+    }
+
+
+}

Some files were not shown because too many files changed in this diff