Explorar el Código

feat():pos提交

geek hace 4 años
padre
commit
cdf0e1fbe3
Se han modificado 100 ficheros con 10250 adiciones y 107 borrados
  1. 5 3
      application/api/BaseController.php
  2. 183 44
      application/api/controller/Admin.php
  3. 111 0
      application/api/controller/Brand.php
  4. 143 0
      application/api/controller/Company.php
  5. 86 0
      application/api/controller/Group.php
  6. 7 32
      application/api/controller/Index.php
  7. 161 0
      application/api/controller/Pay.php
  8. 155 0
      application/api/controller/Staff.php
  9. 179 0
      application/api/controller/Store.php
  10. 101 2
      application/api/controller/User.php
  11. 72 1
      application/api/model/AdminModel.php
  12. 28 6
      application/api/model/BaseModel.php
  13. 24 0
      application/api/model/BrandModel.php
  14. 36 0
      application/api/model/CompanyModel.php
  15. 13 0
      application/api/model/GroupModel.php
  16. 33 0
      application/api/model/PayModel.php
  17. 36 0
      application/api/model/StaffModel.php
  18. 13 0
      application/api/model/StaffTitleModel.php
  19. 41 0
      application/api/model/StoreModel.php
  20. 3 3
      application/api/model/UserModel.php
  21. 7 6
      application/common/until/Token.php
  22. 45 6
      application/common/until/Until.php
  23. 2 1
      composer.json
  24. 297 1
      composer.lock
  25. 1 1
      config/app.php
  26. 1 1
      config/database.php
  27. 9 0
      public/swagger.php
  28. 1 0
      vendor/bin/openapi
  29. 1 0
      vendor/composer/autoload_files.php
  30. 4 0
      vendor/composer/autoload_psr4.php
  31. 27 0
      vendor/composer/autoload_static.php
  32. 304 0
      vendor/composer/installed.json
  33. 49 0
      vendor/doctrine/annotations/.doctrine-project.json
  34. 39 0
      vendor/doctrine/annotations/.github/workflows/coding-standards.yml
  35. 61 0
      vendor/doctrine/annotations/.github/workflows/continuous-integration.yml
  36. 55 0
      vendor/doctrine/annotations/.github/workflows/release-on-milestone-closed.yml
  37. 45 0
      vendor/doctrine/annotations/.github/workflows/static-analysis.yml
  38. 162 0
      vendor/doctrine/annotations/CHANGELOG.md
  39. 19 0
      vendor/doctrine/annotations/LICENSE
  40. 22 0
      vendor/doctrine/annotations/README.md
  41. 46 0
      vendor/doctrine/annotations/composer.json
  42. 271 0
      vendor/doctrine/annotations/docs/en/annotations.rst
  43. 399 0
      vendor/doctrine/annotations/docs/en/custom.rst
  44. 100 0
      vendor/doctrine/annotations/docs/en/index.rst
  45. 6 0
      vendor/doctrine/annotations/docs/en/sidebar.rst
  46. 59 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php
  47. 21 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php
  48. 15 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php
  49. 69 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php
  50. 43 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php
  51. 13 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php
  52. 99 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php
  53. 171 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php
  54. 342 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php
  55. 190 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php
  56. 264 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php
  57. 129 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php
  58. 1387 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php
  59. 315 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php
  60. 165 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php
  61. 100 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php
  62. 11 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/NamedArgumentConstructorAnnotation.php
  63. 77 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php
  64. 80 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php
  65. 114 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php
  66. 208 0
      vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php
  67. 4 0
      vendor/doctrine/annotations/phpbench.json.dist
  68. 154 0
      vendor/doctrine/annotations/phpcs.xml.dist
  69. 13 0
      vendor/doctrine/annotations/phpstan.neon
  70. 19 0
      vendor/doctrine/lexer/LICENSE
  71. 9 0
      vendor/doctrine/lexer/README.md
  72. 41 0
      vendor/doctrine/lexer/composer.json
  73. 328 0
      vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php
  74. 79 0
      vendor/symfony/finder/CHANGELOG.md
  75. 91 0
      vendor/symfony/finder/Comparator/Comparator.php
  76. 51 0
      vendor/symfony/finder/Comparator/DateComparator.php
  77. 79 0
      vendor/symfony/finder/Comparator/NumberComparator.php
  78. 19 0
      vendor/symfony/finder/Exception/AccessDeniedException.php
  79. 19 0
      vendor/symfony/finder/Exception/DirectoryNotFoundException.php
  80. 797 0
      vendor/symfony/finder/Finder.php
  81. 133 0
      vendor/symfony/finder/Gitignore.php
  82. 111 0
      vendor/symfony/finder/Glob.php
  83. 61 0
      vendor/symfony/finder/Iterator/CustomFilterIterator.php
  84. 58 0
      vendor/symfony/finder/Iterator/DateRangeFilterIterator.php
  85. 45 0
      vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php
  86. 87 0
      vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php
  87. 53 0
      vendor/symfony/finder/Iterator/FileTypeFilterIterator.php
  88. 58 0
      vendor/symfony/finder/Iterator/FilecontentFilterIterator.php
  89. 47 0
      vendor/symfony/finder/Iterator/FilenameFilterIterator.php
  90. 106 0
      vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php
  91. 56 0
      vendor/symfony/finder/Iterator/PathFilterIterator.php
  92. 144 0
      vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php
  93. 57 0
      vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php
  94. 101 0
      vendor/symfony/finder/Iterator/SortableIterator.php
  95. 19 0
      vendor/symfony/finder/LICENSE
  96. 14 0
      vendor/symfony/finder/README.md
  97. 85 0
      vendor/symfony/finder/SplFileInfo.php
  98. 28 0
      vendor/symfony/finder/composer.json
  99. 9 0
      vendor/zircote/swagger-php/.gitignore
  100. 0 0
      vendor/zircote/swagger-php/.php_cs.dist

+ 5 - 3
application/api/BaseController.php

@@ -40,6 +40,8 @@ abstract class BaseController
     protected $middleware = [];
 
     public $userId = 0;
+
+    public $isAdmin = 0;
     
     /**
      * 构造方法
@@ -57,8 +59,6 @@ abstract class BaseController
             '/api/index',
             '/api/user/login',
             '/api/admin/login',
-            '/api/setting/read',
-            '/api/user/clearSession'
         ];
         if (!in_array($this->request->baseUrl(),$route)){
             if (empty($this->request->header('token'))) {
@@ -67,10 +67,12 @@ abstract class BaseController
                 $token = new Token();
                 $decodeToken = $token->decodeToken();
                 $this->userId = $decodeToken['userId'];
+                $this->isAdmin = $decodeToken['isAdmin'];
             }
 //            $token = new Token();
 //            $decodeToken = $token->decodeToken();
 //            $this->userId = $decodeToken['userId'];
+//            $this->isAdmin = $decodeToken['isAdmin'];
         }
         // 控制器初始化
         $this->initialize();
@@ -81,7 +83,7 @@ abstract class BaseController
     {}
 
     protected function isAdmin() {
-        if (request()->header('flag') === 'admin'){
+        if ($this->isAdmin == 1){
             return true;
         }
         return false;

+ 183 - 44
application/api/controller/Admin.php

@@ -1,93 +1,232 @@
 <?php
-declare (strict_types = 1);
+declare (strict_types=1);
 
 namespace app\api\controller;
 
 use app\api\BaseController;
 use app\api\exception\ApiException;
 use app\api\model\AdminModel;
+use app\api\model\GroupModel;
+use app\api\model\StoreModel;
 use app\common\until\Until;
+use think\Db;
+use think\Exception;
 use think\Request;
 
-class Admin extends BaseController
-{
+class Admin extends BaseController {
+
     /**
-     * 显示资源列表
-     *
-     * @return \think\Response
+     * @OA\GET(path="/api/Admin/index",
+     *   tags={"管理员管理"},
+     *   summary="管理员列表",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="page", in="query", description="页码", @OA\Schema(type="ineger",default="1")),
+     *   @OA\Parameter(name="pageSize", in="query", description="页尺寸", @OA\Schema(type="integer",default="10")),
+     *   @OA\Parameter(name="status", in="query", description="状态 1正常 2删除", @OA\Schema(type="integer",default="1")),
+     *   @OA\Parameter(name="name", in="query", description="名称", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
      */
-    public function index()
-    {
-        //
+    public function index() {
+        $input = request()->get();
+        $model = new AdminModel();
+        $model->setPage($input['page'] ?? 1);
+        $model->setPageSize($input['pageSize'] ?? 10);
+        if ($this->isAdmin()) {
+            $where = [];
+        } else {
+            $where[] = ['a.status', '=', $model::NORMAL];
+        }
+
+        if (!empty($input['name'])) {
+            $where[] = ['a.name', 'like', "%{$input['name']}%"];
+        }
+
+        if (!empty($input['mobile'])) {
+            $where[] = ['a.mobile', 'like', "%{$input['mobile']}%"];
+        }
+        $model->setWhere($where);
+        $data = $model->getAdminList();
+        Until::output($data);
     }
 
     /**
-     * 保存新建的资源
-     *
-     * @param  \think\Request  $request
-     * @return \think\Response
+     * @OA\Post(path="/api/Admin/save",
+     *   tags={"管理员管理"},
+     *   summary="保存管理员信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     @OA\MediaType(
+     *       mediaType="multipart/form-data",
+     *         @OA\Schema(
+     *           @OA\Property(description="管理员名称", property="name", type="string", default="jack"),
+     *           @OA\Property(description="登入账号", property="account", type="string", default="admin01"),
+     *           @OA\Property(description="登入密码", property="password", type="string", default="123465"),
+     *           @OA\Property(description="手机号", property="mobile", type="string", default="12367897654"),
+     *           @OA\Property(description="角色id-单选", property="roleId", type="integer", default="1"),
+     *           @OA\Property(description="集团id-多选", property="groupIds", type="string", default="1,2"),
+     *           @OA\Property(description="门店id-多选", property="storeIds", type="string", default="1,2"),
+     *           @OA\Property(description="mac地址", property="macAdress", type="string", default="1,2"),
+     *           @OA\Property(description="管理员id", property="id", type="string", default="0"),
+     *           required={"name","account","mobile","roleId","groupIds","storeIds"})
+     *       )
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
      */
-    public function save(Request $request)
-    {
-        //
-    }
+    public function save() {
+        $input = Until::getInput();
+        $rule = [
+            'name|管理员名称'    => 'require',
+            'account|登入账号'  => 'require',
+            'mobile|手机号'    => 'require',
+            'roleId|角色id'   => 'require',
+            'storeIds|门店id' => 'require',
+            'groupIds|集团id' => 'require',
+        ];
+        Until::check($rule, $input);
+        $model = new AdminModel();
+        if (!empty($input['id'])) {
+            $id = (int)$input['id'];
+            try {
+                $model->startTrans();
+                $model::where(['id' => $id])->update([
+                    'name'     => $input['name'],
+                    'account'  => $input['account'],
+                    'role_id'  => $input['roleId'],
+                    'mobile'   => $input['mobile'],
+                    'status'   => $input['status'] ?? 1,
+                ]);
+                $model->saveStoreRole($input['storeIds'], $id, true);
+                $model->saveGroupRole($input['groupIds'], $id,true);
+            }catch (\Exception $e){
+                $model->rollback();
+                throw new ApiException($e->getMessage());
+            }
 
+        } else {
+            if (empty($input['password'])) {
+                throw new ApiException('密码不为空');
+            }
+            try {
+                $model->startTrans();
+                $id = $model->insertGetId([
+                    'name'        => $input['name'],
+                    'account'     => $input['account'],
+                    'role_id'     => $input['roleId'],
+                    'password'    => md5($input['password'] . '-Bjx14Nb3Le9ghOmM'),
+                    'mobile'      => $input['mobile'],
+                    'status'      => $input['status'] ?? 1,
+                    'mac_address' => $input['macAddress'] ?? ''
+                ]);
+                $model->saveStoreRole($input['storeIds'], (int)$id);
+                $model->saveGroupRole($input['groupIds'], (int)$id);
+                $model->commit();
+            } catch (Exception $e) {
+                $model->rollback();
+                throw new ApiException($e->getMessage());
+            }
+
+        }
+        $where[] = ['a.id', '=', (int)$id];
+        $model->setWhere($where);
+        $info = $model->getAdminInfo();
+        Until::output(['info' => $info]);
+    }
 
+    /**
+     * @OA\Post(path="/api/Admin/login",
+     *   tags={"管理员管理"},
+     *   summary="管理员登入",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     @OA\MediaType(
+     *       mediaType="multipart/form-data",
+     *         @OA\Schema(
+     *           @OA\Property(description="登入账号", property="account", type="string", default="admin01"),
+     *           @OA\Property(description="登入密码", property="password", type="string", default="123465"),
+     *           required={"account","password"})
+     *       )
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
     public function login() {
         $input = Until::getInput();
         $rule = [
-            'username|用户名'   => 'require',
-            'password|内容'   => 'require',
+            'account|用户名' => 'require',
+            'password|内容'  => 'require',
         ];
         Until::check($rule, $input);
-        $info = (new AdminModel())::where(['account' => $input['username'], 'passwd' => $input['password']])->find();
-        if ($info === null) {
+        $model = (new AdminModel());
+        $where[] = ['a.account', '=', $input['account']];
+        $where[] = ['a.password', '=', $input['password']];
+        $model->setWhere($where);
+        $info = $model->getAdminInfo();
+        if (empty($info)) {
             throw new ApiException('账号或密码错误');
         }
         $tokenService = new \app\common\until\Token();
-        $token = $tokenService->getToken($info['id']);
-        Until::output(['token' => $token,'id' => $info['id']]);
+        $token = $tokenService->getToken($info['id'],'',true);
+
+        Until::output(['token' => $token, 'info' => $info]);
     }
 
 
     public function logout() {
         Until::output(['name' => 'tom']);
     }
+
     /**
-     * 显示指定的资源
-     *
-     * @return \think\Response
+     * @OA\GET(path="/api/Admin/read",
+     *   tags={"管理员管理"},
+     *   summary="查看管理员个人信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="id", in="query", description="管理员id", @OA\Schema(type="ineger",default="1")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
      */
-    public function read()
-    {
-        $info = (new AdminModel())::where(['id' => $this->userId])->field('account,avatar')->find();
-        if ($info === null) {
-            throw new ApiException('无此用户');
-        }
-        Until::output(['username' => $info['account'],'avatar' => $info['avatar']]);
+    public function read($id) {
+        $model = new AdminModel();
+        $where[] = ['a.id', '=', (int)$id];
+        $model->setWhere($where);
+        $info = $model->getAdminInfo();
+        Until::output(['info' => $info]);
     }
 
     /**
      * 保存更新的资源
      *
-     * @param  \think\Request  $request
-     * @param  int  $id
+     * @param \think\Request $request
+     * @param int $id
      * @return \think\Response
      */
-    public function update(Request $request, $id)
-    {
+    public function update(Request $request, $id) {
         //
     }
 
     /**
-     * 删除指定资源
-     *
-     * @param  int  $id
-     * @return \think\Response
+     * @OA\GET(path="/api/Admin/delete",
+     *   tags={"管理员管理"},
+     *   summary="删除管理员信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="id", in="query", description="管理员id", @OA\Schema(type="ineger",default="1")),
+     *   @OA\Parameter(name="status", in="query", description="1正常 2删除", @OA\Schema(type="ineger",default="1")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
      */
-    public function delete($id)
-    {
-        //
+    public function delete($id,$status) {
+        $model = new AdminModel();
+        $where[] = ['id', '=', (int)$id];
+        $data = ['status' => (int)$status];
+        $isSuccess = $model::where($where)->update($data);
+        Until::output(['isSuccess' => $isSuccess]);
     }
 
 }

+ 111 - 0
application/api/controller/Brand.php

@@ -0,0 +1,111 @@
+<?php
+/**
+ * Author: luzheng.liu
+ * Time: 2020/12/16 23:06
+ */
+
+namespace app\api\controller;
+
+
+use app\api\BaseController;
+use app\api\model\BrandModel;
+use app\api\model\GroupModel;
+use app\common\until\Until;
+
+class Brand extends BaseController {
+
+    /**
+     * @OA\Post(path="/api/Brand/index",
+     *   tags={"品牌管理"},
+     *   summary="品牌列表",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="page", in="query", description="页码", @OA\Schema(type="ineger",default="1")),
+     *   @OA\Parameter(name="pageSize", in="query", description="页尺寸", @OA\Schema(type="integer",default="10")),
+     *   @OA\Parameter(name="status", in="query", description="状态 1正常 2删除", @OA\Schema(type="integer",default="1")),
+     *   @OA\Parameter(name="name", in="query", description="品牌名", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function index() {
+        $input = request()->get();
+        $model = new BrandModel();
+        $model->setPage($input['page'] ?? 1);
+        $model->setPageSize($input['pageSize'] ?? 10);
+        if ($this->isAdmin()) {
+            $where = [];
+        } else {
+            $where[] = ['b.status', '=', $model::NORMAL];
+        }
+
+        if (!empty($input['name'])) {
+            $where[] = ['b.brand_name', 'like', "%{$input['name']}%"];
+        }
+        $model->setWhere($where);
+        $data = $model->getBrandList();
+        Until::output($data);
+    }
+
+
+    /**
+     * @OA\Post(path="/api/Brand/save",
+     *   tags={"品牌管理"},
+     *   summary="保存品牌信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     @OA\MediaType(
+     *       mediaType="multipart/form-data",
+     *         @OA\Schema(
+     *           @OA\Property(description="品牌名称", property="name", type="string", default="大大品牌"),
+     *           @OA\Property(description="集团id", property="groupId", type="string", default="1"),
+     *           required={"name","groupId"})
+     *       )
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function save() {
+        $input = Until::getInput();
+        $rule = [
+            'name|品牌名称'    => 'require',
+            'groupId|集团id' => 'require',
+        ];
+        Until::check($rule, $input);
+        $model = new BrandModel();
+        if (!empty($input['id'])) {
+            $id = (int)$input['id'];
+            $model::where(['id' => $id])->update([
+                'brand_name' => $input['name'],
+                'group_id'   => $input['groupId']
+            ]);
+        } else {
+            $id = $model->insertGetId([
+                'brand_name' => $input['name'],
+                'group_id'   => $input['groupId']
+            ]);
+        }
+        $info = $model::get($id);
+        Until::output(['info' => Until::modelToArray($info)]);
+    }
+
+    /**
+     * @OA\GET(path="/api/Brand/delete",
+     *   tags={"品牌管理"},
+     *   summary="删除品牌信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="id", in="query", description="品牌id", @OA\Schema(type="ineger",default="1")),
+     *   @OA\Parameter(name="status", in="query", description="1正常 2删除", @OA\Schema(type="ineger",default="1")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function delete($id,$status) {
+        $model = new BrandModel();
+        $where[] = ['id', '=', (int)$id];
+        $data = ['status' => (int)$status];
+        $isSuccess = $model::where($where)->update($data);
+        Until::output(['isSuccess' => $isSuccess]);
+    }
+}

+ 143 - 0
application/api/controller/Company.php

@@ -0,0 +1,143 @@
+<?php
+/**
+ * Author: luzheng.liu
+ * Time: 2020/12/16 23:06
+ */
+
+namespace app\api\controller;
+
+
+use app\api\BaseController;
+use app\api\model\AdminModel;
+use app\api\model\BrandModel;
+use app\api\model\CompanyModel;
+use app\api\model\GroupModel;
+use app\common\until\Until;
+
+class Company extends BaseController {
+
+    /**
+     * @OA\Post(path="/api/Company/index",
+     *   tags={"公司管理"},
+     *   summary="公司列表",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="page", in="query", description="页码", @OA\Schema(type="ineger",default="1")),
+     *   @OA\Parameter(name="pageSize", in="query", description="页尺寸", @OA\Schema(type="integer",default="10")),
+     *   @OA\Parameter(name="status", in="query", description="状态 1正常 2删除", @OA\Schema(type="integer",default="1")),
+     *   @OA\Parameter(name="name", in="query", description="公司名", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function index() {
+        $input = request()->get();
+        $model = new CompanyModel();
+        $model->setPage($input['page'] ?? 1);
+        $model->setPageSize($input['pageSize'] ?? 10);
+        if ($this->isAdmin()) {
+            $where = [];
+        } else {
+            $where[] = ['c.status', '=', $model::NORMAL];
+        }
+
+        if (!empty($input['name'])) {
+            $where[] = ['c.company_name', 'like', "%{$input['name']}%"];
+        }
+        $model->setWhere($where);
+        $data = $model->getCompanyList();
+        Until::output($data);
+    }
+
+
+    /**
+     * @OA\Post(path="/api/Company/save",
+     *   tags={"公司管理"},
+     *   summary="保存公司信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     @OA\MediaType(
+     *       mediaType="multipart/form-data",
+     *         @OA\Schema(
+     *           @OA\Property(description="公司名称", property="name", type="string", default="jj公司"),
+     *           @OA\Property(description="集团id", property="groupId", type="integer", default="1"),
+     *           @OA\Property(description="公司code", property="code", type="string", default="a01"),
+     *           @OA\Property(description="支付id", property="payId", type="integer", default="1"),
+     *           @OA\Property(description="状态 1正常  2删除", property="status", type="integer", default="1"),
+     *           required={"name","groupId","code","payId"})
+     *       )
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function save() {
+        $input = Until::getInput();
+        $rule = [
+            'name|公司名称'    => 'require',
+            'code|公司code'  => 'require',
+            'groupId|集团id' => 'require',
+            'payId|支付id'   => 'require',
+        ];
+        Until::check($rule, $input);
+        $model = new CompanyModel();
+        if (!empty($input['id'])) {
+            $id = (int)$input['id'];
+            $model::where(['id' => $id])->update([
+                'company_name' => $input['name'],
+                'company_code' => $input['code'],
+                'group_id'     => $input['groupId'],
+                'pay_id'       => $input['payId'],
+                'status'       => $input['status'] ?? 1
+            ]);
+        } else {
+            $id = $model->insertGetId([
+                'company_name' => $input['name'],
+                'company_code' => $input['code'],
+                'group_id'     => $input['groupId'],
+                'pay_id'       => $input['payId'],
+            ]);
+        }
+        $info = $model::get($id);
+        Until::output(['info' => Until::modelToArray($info)]);
+    }
+
+    /**
+     * @OA\GET(path="/api/Company/read",
+     *   tags={"公司管理"},
+     *   summary="查看公司信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="id", in="query", description="公司id", @OA\Schema(type="ineger",default="1")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function read($id) {
+        $model = new CompanyModel();
+        $where[] = ['c.id', '=', (int)$id];
+        $model->setWhere($where);
+        $info = $model->getCompanyInfo();
+        Until::output(['info' => $info]);
+    }
+
+    /**
+     * @OA\GET(path="/api/Company/delete",
+     *   tags={"公司管理"},
+     *   summary="删除公司信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="id", in="query", description="公司id", @OA\Schema(type="ineger",default="1")),
+     *   @OA\Parameter(name="status", in="query", description="1正常 2删除", @OA\Schema(type="ineger",default="1")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function delete($id,$status) {
+        $model = new CompanyModel();
+        $where[] = ['id', '=', (int)$id];
+        $data = ['status' => (int)$status];
+        $isSuccess = $model::where($where)->update($data);
+        Until::output(['isSuccess' => $isSuccess]);
+    }
+
+}

+ 86 - 0
application/api/controller/Group.php

@@ -0,0 +1,86 @@
+<?php
+/**
+ * Author: luzheng.liu
+ * Time: 2020/12/16 23:06
+ */
+
+namespace app\api\controller;
+
+
+use app\api\BaseController;
+use app\api\model\GroupModel;
+use app\common\until\Until;
+
+class Group extends BaseController {
+
+    /**
+     * @OA\Post(path="/api/Group/index",
+     *   tags={"集团管理"},
+     *   summary="集团列表",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="page", in="query", description="页码", @OA\Schema(type="ineger",default="1")),
+     *   @OA\Parameter(name="pageSize", in="query", description="页尺寸", @OA\Schema(type="integer",default="10")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function index() {
+        $input = request()->get();
+        $model = new GroupModel();
+        $model->setPage($input['page'] ?? 1);
+        $model->setPageSize($input['pageSize'] ?? 10);
+        $where = [];
+
+        $model->setWhere($where);
+        $data = $model->getPageList($model);
+        Until::output($data);
+    }
+
+
+    /**
+     * @OA\Post(path="/api/Group/save",
+     *   tags={"集团管理"},
+     *   summary="保存集团信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     @OA\MediaType(
+     *       mediaType="multipart/form-data",
+     *         @OA\Schema(
+     *           @OA\Property(description="集团名称", property="name", type="string", default="四海集团"),
+     *           required={"name"})
+     *       )
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function save() {
+        $input = Until::getInput();
+        $rule = [
+            'name|集团名称'        => 'require',
+        ];
+        Until::check($rule, $input);
+        $model = new GroupModel();
+        if (!empty($input['id']) ) {
+            $id = (int)$input['id'];
+            $model::where(['id' => $id])->update([
+                'group_name' => $input['name']
+            ]);
+        } else {
+            $id = $model->insertGetId([
+                'group_name' => $input['name']
+            ]);
+        }
+        $info = $model::get($id);
+        Until::output(['info' => Until::modelToArray($info)]);
+    }
+
+
+    public function delete($id,$status) {
+        $model = new GroupModel();
+        $where[] = ['id', '=', (int)$id];
+        $data = ['status' => (int)$status];
+        $isSuccess = $model::where($where)->update($data);
+        Until::output(['isSuccess' => $isSuccess]);
+    }
+}

+ 7 - 32
application/api/controller/Index.php

@@ -11,43 +11,18 @@ use app\common\service\CommonService;
 use app\api\exception\ApiException;
 use app\api\model\UserModel;
 use app\api\model\VisitorModel;
+use app\common\until\Until;
 use think\console\command\make\Model;
 use think\facade\Cache;
 use think\facade\Session;
-
+/**
+ * @OA\Info(title="POS系统api", version="0.1")
+ */
 class Index {
 
     public function index() {
-        $visitorId = input('visitorId');
-        $notifyUrl = input('notify_url');
-        $channelId = input('channelId');
-        if (empty($channelId)){
-            throw new ApiException('渠道id不为空');
-        }
-        $data = CommonService::getSetData($channelId);
-        $info = (new UserModel())::where(['unionid' => Session::get('wxId'), 'channel_id' => $channelId])
-            ->order(['id' => 'desc'])->find();
-        if (empty(Session::get('wxId')) || empty($info)){
-            header("Location:".CommonService::getAuthUrl($channelId));
-            die();
-        }
-        $params = [
-            'id'     => $info['channel_id'],
-            'userid' => $info['unionid'],
-            'name'   => $info['name'],
-            'avatar' => $info['avatar'],
-            'key'    => $info['key']
-        ];
-        header('Location:http://webcasting.bizconfstreaming.com/activity.php?a=userAssign&'.http_build_query($params));
-        die();
-        //增加客户自己的验证逻辑,例如登录,关注,付费,填手机号等
-        $key = md5($visitorId.$data['channel_auth_code']);
-        if(strpos($notifyUrl,'?') !== false){//url参数处理,将key加到url参数中
-            $returnUrl = $notifyUrl."&key=".$key."&expire=1";
-        }else{
-            $returnUrl = $notifyUrl."?key=".$key."&expire=1";
-        }
-        header("Location:".$returnUrl);//跳转到直播观看页
-        //        redirect($returnUrl);
+        $openapi = \OpenApi\scan('/Users/liuluzheng/PhpstormProjects/tp5.1/application/api/controller');
+        header('Content-Type: application/x-yaml');
+        echo $openapi->toYaml();
     }
 }

+ 161 - 0
application/api/controller/Pay.php

@@ -0,0 +1,161 @@
+<?php
+/**
+ * Author: luzheng.liu
+ * Time: 2020/12/16 23:06
+ */
+
+namespace app\api\controller;
+
+
+use app\api\BaseController;
+use app\api\model\AdminModel;
+use app\api\model\BrandModel;
+use app\api\model\CompanyModel;
+use app\api\model\GroupModel;
+use app\api\model\PayModel;
+use app\common\until\Until;
+
+class Pay extends BaseController {
+
+    /**
+     * @OA\Post(path="/api/Pay/index",
+     *   tags={"支付管理"},
+     *   summary="支付配置列表",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="page", in="query", description="页码", @OA\Schema(type="ineger",default="1")),
+     *   @OA\Parameter(name="pageSize", in="query", description="页尺寸", @OA\Schema(type="integer",default="10")),
+     *   @OA\Parameter(name="status", in="query", description="状态 1正常 2删除", @OA\Schema(type="integer",default="1")),
+     *   @OA\Parameter(name="code", in="query", description="支付code", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function index() {
+        $input = request()->get();
+        $model = new PayModel();
+        $model->setPage($input['page'] ?? 1);
+        $model->setPageSize($input['pageSize'] ?? 10);
+        if ($this->isAdmin()) {
+            $where = [];
+        } else {
+            $where[] = ['p.status', '=', $model::NORMAL];
+        }
+
+        if (!empty($input['code'])) {
+            $where[] = ['p.pay_code', 'like', "%{$input['code']}%"];
+        }
+        $model->setWhere($where);
+        $data = $model->getPayList();
+        Until::output($data);
+    }
+
+
+    /**
+     * @OA\Post(path="/api/Pay/save",
+     *   tags={"支付管理"},
+     *   summary="保存支付配置信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     @OA\MediaType(
+     *       mediaType="multipart/form-data",
+     *         @OA\Schema(
+     *           @OA\Property(description="支付code", property="code", type="string", default="jj公司"),
+     *           @OA\Property(description="集团id", property="groupId", type="integer", default="1"),
+     *           @OA\Property(description="主商户号", property="masterPayId", type="string", default="a01"),
+     *           @OA\Property(description="子商户号", property="subPayId", type="string", default="1"),
+     *           @OA\Property(description="主appId", property="masterAppId", type="string", default="1"),
+     *           @OA\Property(description="子appId", property="subAppId", type="string", default="1"),
+     *           @OA\Property(description="秘钥", property="xcxSecret", type="string", default="1"),
+     *           @OA\Property(description="状态 1正常  2删除", property="status", type="integer", default="1"),
+     *           @OA\Property(description="备注", property="remark", type="dtring", default="xx支付"),
+     *           required={"code","groupId","masterPayId","masterAppId","xcxSecret"})
+     *       )
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function save() {
+        $input = Until::getInput();
+        $rule = [
+            'code|支付code'        => 'require',
+            'masterPayId|主商户号'   => 'require',
+            //'subPayId|子商户号'  => 'require',
+            'masterAppId|主appId' => 'require',
+            //'subAppId|子appId'  => 'require',
+            'groupId|集团id'       => 'require',
+            'xcxSecret|秘钥'       => 'require',
+        ];
+        Until::check($rule, $input);
+        $model = new PayModel();
+        if (!empty($input['id'])) {
+            $id = (int)$input['id'];
+            $model::where(['id' => $id])->update([
+                'pay_code'      => $input['code'],
+                'master_pay_id' => $input['masterPayId'],
+                'sub_pay_id'    => $input['subPayId'],
+                'master_app_id' => $input['masterAppId'],
+                'sub_app_id'    => $input['subAppId'],
+                'xcx_secret'    => $input['xcxSecret'],
+                'group_id'      => $input['groupId'],
+                'status'        => $input['status'] ?? 1,
+                'remark'        => $input['remark']
+            ]);
+        } else {
+            $id = $model->insertGetId([
+                'pay_code'      => $input['code'],
+                'master_pay_id' => $input['masterPayId'],
+                'sub_pay_id'    => $input['subPayId'] ?? "",
+                'master_app_id' => $input['masterAppId'],
+                'sub_app_id'    => $input['subAppId'] ?? "",
+                'xcx_secret'    => $input['xcxSecret'],
+                'group_id'      => $input['groupId'],
+                'status'        => $input['status'] ?? 1,
+                'remark'        => $input['remark'] ?? ''
+            ]);
+        }
+        $model->setWhere([['p.id' ,'=', (int)$id]]);
+        $info = $model->getPayInfo();
+        Until::output(['info' => $info]);
+    }
+
+    /**
+     * @OA\GET(path="/api/Pay/read",
+     *   tags={"支付管理"},
+     *   summary="查看支付信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="id", in="query", description="支付id", @OA\Schema(type="ineger",default="1")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function read($id) {
+        $model = new PayModel();
+        $where[] = ['p.id', '=', (int)$id];
+        $model->setWhere($where);
+        $info = $model->getPayInfo();
+        Until::output(['info' => $info]);
+    }
+
+    /**
+     * @OA\GET(path="/api/Pay/delete",
+     *   tags={"支付管理"},
+     *   summary="删除支付配置信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="id", in="query", description="支付id", @OA\Schema(type="ineger",default="1")),
+     *   @OA\Parameter(name="status", in="query", description="1正常 2删除", @OA\Schema(type="ineger",default="1")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function delete($id, $status) {
+        $model = new PayModel();
+        $where[] = ['id', '=', (int)$id];
+        $data = ['status' => (int)$status];
+        $isSuccess = $model::where($where)->update($data);
+        Until::output(['isSuccess' => $isSuccess]);
+    }
+
+}

+ 155 - 0
application/api/controller/Staff.php

@@ -0,0 +1,155 @@
+<?php
+/**
+ * Author: luzheng.liu
+ * Time: 2020/12/16 23:06
+ */
+
+namespace app\api\controller;
+
+
+use app\api\BaseController;
+use app\api\model\BrandModel;
+use app\api\model\GroupModel;
+use app\api\model\StaffModel;
+use app\common\until\Until;
+
+class Staff extends BaseController {
+
+    /**
+     * @OA\Post(path="/api/Staff/index",
+     *   tags={"职员管理"},
+     *   summary="职员列表",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="page", in="query", description="页码", @OA\Schema(type="ineger",default="1")),
+     *   @OA\Parameter(name="pageSize", in="query", description="页尺寸", @OA\Schema(type="integer",default="10")),
+     *   @OA\Parameter(name="status", in="query", description="状态 1正常 2删除", @OA\Schema(type="integer",default="1")),
+     *   @OA\Parameter(name="name", in="query", description="职员名", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="mobile", in="query", description="手机号", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function index() {
+        $input = request()->get();
+        $model = new StaffModel();
+        $model->setPage($input['page'] ?? 1);
+        $model->setPageSize($input['pageSize'] ?? 10);
+        if ($this->isAdmin()) {
+            $where = [];
+        } else {
+            $where[] = ['s.status', '=', $model::NORMAL];
+        }
+
+        if (!empty($input['name'])) {
+            $where[] = ['s.staff_name', 'like', "%{$input['name']}%"];
+        }
+
+        if (!empty($input['mobile'])) {
+            $where[] = ['s.mobile', 'like', "%{$input['mobile']}%"];
+        }
+        $model->setWhere($where);
+        $data = $model->getStaffList();
+        Until::output($data);
+    }
+
+
+    /**
+     * @OA\Post(path="/api/Staff/save",
+     *   tags={"职员管理"},
+     *   summary="保存职员信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     @OA\MediaType(
+     *       mediaType="multipart/form-data",
+     *         @OA\Schema(
+     *           @OA\Property(description="职员名称", property="name", type="string", default="tony"),
+     *           @OA\Property(description="职员工号", property="code", type="string", default="A9527"),
+     *           @OA\Property(description="职员手机号", property="mobile", type="string", default="12367897654"),
+     *           @OA\Property(description="入职日期", property="joinTime", type="string", default="2020-01-04"),
+     *           @OA\Property(description="职称id", property="staffTitleId", type="integer", default="1"),
+     *           @OA\Property(description="storeId", property="storeId", type="integer", default="1"),
+     *           required={"name","code","mobile","joinTime","staffTitleId","storeId"})
+     *       )
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function save() {
+        $input = Until::getInput();
+        $rule = [
+            'name|职员名称'         => 'require',
+            'code|职员工号'         => 'require',
+            'mobile|职员手机号'      => 'require',
+            'joinTime|入职日期'     => 'require',
+            'staffTitleId|职称id' => 'require',
+            'storeId|storeId'      => 'require',
+        ];
+        Until::check($rule, $input);
+        $model = new StaffModel();
+        if (!empty($input['id'])) {
+            $id = (int)$input['id'];
+            $model::where(['id' => $id])->update([
+                'staff_name'     => $input['name'],
+                'staff_code'     => $input['code'],
+                'mobile'         => $input['mobile'],
+                'join_time'      => $input['joinTime'],
+                'staff_title_id' => $input['staffTitleId'],
+                'store_id'       => $input['storeId'],
+                'status'         => $input['status'] ?? $model::NORMAL
+            ]);
+        } else {
+            $id = $model->insertGetId([
+                'staff_name'     => $input['name'],
+                'staff_code'     => $input['code'],
+                'mobile'         => $input['mobile'],
+                'join_time'      => $input['joinTime'],
+                'staff_title_id' => $input['staffTitleId'],
+                'store_id'       => $input['storeId']
+            ]);
+        }
+        $where[] = ['s.id', '=', (int)$id];
+        $model->setWhere($where);
+        $info = $model->getStaffInfo();
+        Until::output(['info' => $info]);
+    }
+
+    /**
+     * @OA\GET(path="/api/Staff/read",
+     *   tags={"职员管理"},
+     *   summary="查看职员信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="id", in="query", description="职员id", @OA\Schema(type="ineger",default="1")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function read($id) {
+        $model = new StaffModel();
+        $where[] = ['s.id', '=', (int)$id];
+        $model->setWhere($where);
+        $info = $model->getStaffInfo();
+        Until::output(['info' => $info]);
+    }
+
+    /**
+     * @OA\GET(path="/api/Staff/delete",
+     *   tags={"职员管理"},
+     *   summary="删除职员信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="id", in="query", description="职员id", @OA\Schema(type="ineger",default="1")),
+     *   @OA\Parameter(name="status", in="query", description="1正常 2删除", @OA\Schema(type="ineger",default="1")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function delete($id,$status) {
+        $model = new StaffModel();
+        $where[] = ['id', '=', (int)$id];
+        $data = ['status' => (int)$status];
+        $isSuccess = $model::where($where)->update($data);
+        Until::output(['isSuccess' => $isSuccess]);
+    }
+}

+ 179 - 0
application/api/controller/Store.php

@@ -0,0 +1,179 @@
+<?php
+/**
+ * Author: luzheng.liu
+ * Time: 2020/12/16 23:03
+ */
+
+namespace app\api\controller;
+
+
+use app\api\BaseController;
+use app\api\model\AdminModel;
+use app\api\model\StoreModel;
+use app\common\until\Until;
+
+class Store extends BaseController {
+
+    /**
+     * @OA\Post(path="/api/Store/index",
+     *   tags={"门店管理"},
+     *   summary="门店列表",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="page", in="query", description="页码", @OA\Schema(type="ineger",default="1")),
+     *   @OA\Parameter(name="pageSize", in="query", description="页尺寸", @OA\Schema(type="integer",default="10")),
+     *   @OA\Parameter(name="status", in="query", description="状态 1正常 2删除", @OA\Schema(type="integer",default="1")),
+     *   @OA\Parameter(name="name", in="query", description="门店名称", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function index() {
+        $input = request()->get();
+        $model = new StoreModel();
+        $model->setPage($input['page'] ?? 1);
+        $model->setPageSize($input['pageSize'] ?? 10);
+        if ($this->isAdmin()) {
+            $where = [];
+        } else {
+            $where[] = ['s.status', '=', $model::NORMAL];
+        }
+        if (!empty($input['name'])){
+            $where[] = ['s.store_name', 'like', "%{$input['name']}%"];
+        }
+
+        $model->setWhere($where);
+        $data = $model->getStoreList();
+        Until::output($data);
+    }
+
+
+    /**
+     * @OA\Post(path="/api/Store/save",
+     *   tags={"门店管理"},
+     *   summary="保存门店信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     @OA\MediaType(
+     *       mediaType="multipart/form-data",
+     *         @OA\Schema(
+     *           @OA\Property(description="门店名称", property="name", type="string", default="测试门店1"),
+     *           @OA\Property(description="门店code", property="code", type="string", default="A001"),
+     *           @OA\Property(description="营业时间", property="openTime", type="string", default="06:00"),
+     *           @OA\Property(description="闭店时间", property="closeTime", type="string", default="22:00"),
+     *           @OA\Property(description="支付标识", property="payCode", type="string", default="paycode1"),
+     *           @OA\Property(description="所属集团id", property="groupId", type="integer", default="1"),
+     *           @OA\Property(description="所属公司id", property="companyId", type="integer", default="1"),
+     *           @OA\Property(description="所属品牌id", property="brandId", type="integer", default="1"),
+     *           @OA\Property(description="logo的url", property="logo", type="string", default="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1608146390523&di=02b955a1fa80d1c43c6289f846ddc42c&imgtype=0&src=http%3A%2F%2Fimg.sccnn.com%2Fbimg%2F338%2F38706.jpg"),
+     *           @OA\Property(description="纬度", property="latitude", type="string", default="31.241510099342623"),
+     *           @OA\Property(description="经度", property="longitude", type="string", default="121.32174958203123"),
+     *           @OA\Property(description="地址", property="address", type="string", default="上海市普陀区真北路"),
+     *           @OA\Property(description="联系电话", property="mobile", type="string", default="15656789876"),
+     *           @OA\Property(description="门店id", property="id", type="string", default=""),
+     *           @OA\Property(description="门店状态 1正常 2闭店 3暂歇", property="status", type="0"),
+     *           required={"name","code","openTime","closeTime","payCode","groupId","companyId","brandId","logo",
+     *     "latitude","longitude","address","mobile"})
+     *       )
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function save() {
+        $input = Until::getInput();
+        $rule = [
+            'name|门店名称'        => 'require',
+            'code|门店code'      => 'require',
+            'openTime|营业时间'    => 'require',
+            'closeTime|闭店时间'   => 'require',
+            'payCode|支付标识'    => 'require',
+            'groupId|所属集团id'   => 'require',
+            'companyId|所属公司id' => 'require',
+            'brandId|所属品牌id'   => 'require',
+            'logo|logo的url'    => 'require',
+            'latitude|纬度'      => 'require',
+            'longitude|经度'     => 'require',
+            'address|地址'       => 'require',
+            'mobile|联系电话'      => 'require',
+        ];
+        Until::check($rule, $input);
+        $model = new StoreModel();
+        if (!empty($input['id']) ) {
+            $id = (int)$input['id'];
+            $model::where(['id' => $id])->update([
+                'store_name' => $input['name'],
+                'store_code' => $input['code'],
+                'open_time'  => $input['openTime'],
+                'close_time' => $input['closeTime'],
+                'pay_code'  => $input['payCode'],
+                'group_id'   => $input['groupId'],
+                'company_id' => $input['companyId'],
+                'brand_id'   => $input['brandId'],
+                'logo'       => $input['logo'],
+                'latitude'   => $input['latitude'],
+                'longitude'  => $input['longitude'],
+                'address'    => $input['address'],
+                'mobile'     => $input['mobile'],
+                'status'     => $input['status']
+            ]);
+        } else {
+            $id = $model->insertGetId([
+                'store_name' => $input['name'],
+                'store_code' => $input['code'],
+                'open_time'  => $input['openTime'],
+                'close_time' => $input['closeTime'],
+                'pay_code'  => $input['payCode'],
+                'group_id'   => $input['groupId'],
+                'company_id' => $input['companyId'],
+                'brand_id'   => $input['brandId'],
+                'logo'       => $input['logo'],
+                'latitude'   => $input['latitude'],
+                'longitude'  => $input['longitude'],
+                'address'    => $input['address'],
+                'mobile'     => $input['mobile'],
+            ]);
+        }
+        $info = $model::get($id);
+        Until::output(['info' => Until::modelToArray($info)]);
+    }
+
+    /**
+     * @OA\GET(path="/api/Store/read",
+     *   tags={"门店管理"},
+     *   summary="查看门店信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="id", in="query", description="门店id", @OA\Schema(type="ineger",default="1")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function read($id) {
+        $model = new StoreModel();
+        $where[] = ['s.id', '=', (int)$id];
+        $model->setWhere($where);
+        $info = $model->getStoreInfo();
+        Until::output(['info' => $info]);
+    }
+
+    /**
+     * @OA\GET(path="/api/Store/delete",
+     *   tags={"门店管理"},
+     *   summary="删除门店信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\Parameter(name="id", in="query", description="门店id", @OA\Schema(type="ineger",default="1")),
+     *   @OA\Parameter(name="status", in="query", description="1正常 2删除", @OA\Schema(type="ineger",default="1")),
+     *   @OA\RequestBody(
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function delete($id,$status) {
+        $model = new StoreModel();
+        $where[] = ['id', '=', (int)$id];
+        $data = ['status' => (int)$status];
+        $isSuccess = $model::where($where)->update($data);
+        Until::output(['isSuccess' => $isSuccess]);
+    }
+
+}

+ 101 - 2
application/api/controller/User.php

@@ -7,21 +7,120 @@
 namespace app\api\controller;
 
 
+use app\api\model\HomeModel;
 use app\common\service\CommonService;
 use app\api\BaseController;
 use app\api\exception\ApiException;
 use app\api\model\UserModel;
 use app\api\model\VisitorModel;
+use app\common\until\Until;
 use think\facade\Cache;
 use think\facade\Session;
 
 class User extends BaseController {
 
+    /**
+     * @OA\Post(path="/api/User",
+     *   tags={"用户管理"},
+     *   summary="用户列表",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     @OA\MediaType(
+     *       mediaType="application/json",
+     *         @OA\Schema(
+     *           @OA\Property(description="页码", property="page", type="integer", default="1"),
+     *           @OA\Property(description="页尺寸", property="pageSize", type="integer", default="10"),
+     *           @OA\Property(description="状态 1正常 2删除", property="status", type="integer"),
+     *           @OA\Property(description="手机号", property="mobile", type="string"),
+     *           required={})
+     *       )
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
     public function index() {
+        $input = request()->get();
+        $model = new UserModel();
+        $model->setPage($input['page'] ?? 1);
+        $model->setPageSize($input['pageSize'] ?? 10);
+        if($this->isAdmin()){
+            $where = [];
+        }else {
+            $where[] = ['status', '=', $model::NORMAL];
+        }
+        if (!empty($input['mobile']) || (isset($input['mobile']) && $input['mobile'] == '0')) {
+            $where[] = ['mobile', 'like', '%' . $input['mobile'] . '%'];
+        }
+        $model->setWhere($where);
+        $data = $model->getPageList($model);
+        Until::output($data);
+    }
 
+    /**
+     * @OA\Post(path="/api/user/save",
+     *   tags={"用户管理"},
+     *   summary="保存用户(有id就更新,没id就新增)",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     @OA\MediaType(
+     *       mediaType="application/json",
+     *         @OA\Schema(
+     *           @OA\Property(description="文章名称", property="title", type="string", default="dd"),
+     *           @OA\Property(description="文章内容", property="content", type="string"),
+     *           required={"title", "content"})
+     *       )
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function save() {
+        $input = Until::getInput();
+        $rule = [
+            'img_url|图片' => 'require',
+            'type|类型'    => 'require',
+        ];
+        Until::check($rule, $input);
+        $model  = new UserModel();
+        if (empty($input['id'])){
+            $id = (int)$input['id'];
+            $model::where(['id' => $id])->update([
+                'img_url' => $input['img_url'] ?? '',
+                'sort'    => $input['sort'] ?? 0,
+                'status'  => $input['status'] ?? 1,
+                'type'    => $input['type']
+            ]);
+        }else {
+            $id = $model->insertGetId([
+                'img_url' => $input['img_url'] ?? '',
+                'sort'    => $input['sort'] ?? 0,
+                'status'  => $input['status'] ?? 1,
+                'type'    => $input['type']
+            ]);
+        }
+        $info = $model::get($id);
+        Until::output(['info' => Until::modelToArray($info)]);
     }
 
-    public function clearSession() {
-        Session::clear();
+
+    /**
+     * @OA\Post(path="/api/User/read",
+     *   tags={"用户管理"},
+     *   summary="读取单个用户信息",
+     *   @OA\Parameter(name="token", in="header", description="token", @OA\Schema(type="string")),
+     *   @OA\RequestBody(
+     *     @OA\MediaType(
+     *       mediaType="application/json",
+     *         @OA\Schema(
+     *           @OA\Property(description="用户id", property="id", type="int"),
+     *           required={"id"})
+     *       )
+     *     ),
+     *   @OA\Response(response="200", description="请求成功")
+     * )
+     */
+    public function read(int $id) {
+        $info = (new UserModel())::where(['id' => $id])->find();
+        Until::output(['info' => Until::modelToArray($info)]);
     }
+
 }

+ 72 - 1
application/api/model/AdminModel.php

@@ -7,9 +7,80 @@
 namespace app\api\model;
 
 
+use app\common\until\Until;
+use think\Db;
 use think\Model;
 
-class AdminModel  extends Model {
+class AdminModel  extends BaseModel {
 
     protected $table = 'admin';
+
+    public function getAdminList() {
+        $countModel = $this->alias('a')
+            ->join('role r','a.role_id = r.id')
+            ->join('store_role sr','sr.admin_id = a.id','left')
+            ->join('store st','st.id = sr.store_id','left')
+            ->join('group_role gr','gr.admin_id = a.id','left')
+            ->join('group g','g.id = gr.group_id','left')
+            ->group('a.id');
+
+
+        $selectModel = $this->alias('a')
+            ->field('a.*,r.auth_name,GROUP_CONCAT(DISTINCT st.store_name) as storeName,
+            GROUP_CONCAT(DISTINCT g.group_name) as groupName')
+            ->join('role r','a.role_id = r.id')
+            ->join('store_role sr','sr.admin_id = a.id','left')
+            ->join('store st','st.id = sr.store_id','left')
+            ->join('group_role gr','gr.admin_id = a.id','left')
+            ->join('group g','g.id = gr.group_id','left')
+            ->group('a.id');
+        return $this->joinModelPageList($countModel, $selectModel);
+    }
+
+    public function saveStoreRole(string $storeIds, int $id,$isUpdate = false) {
+        $storeIdArr = explode(',', $storeIds);
+        $storeData = [];
+        foreach ($storeIdArr as $storeId) {
+            $storeData[] = [
+                'store_id' => (int)$storeId,
+                'admin_id' => $id
+            ];
+        }
+        if ($isUpdate) {
+            Db::table('store_role')->where([['admin_id','=',$id]])->delete(true);
+        }
+        Db::table('store_role')->insertAll($storeData);
+    }
+
+    public function saveGroupRole($groupIds, $id, $isUpdate = false) {
+        $idArr = explode(',', $groupIds);
+        $groupData = [];
+        foreach ($idArr as $subId) {
+            $groupData[] = [
+                'group_id' => (int)$subId,
+                'admin_id' => $id
+            ];
+        }
+        if ($isUpdate) {
+            Db::table('group_role')->where([['admin_id','=',$id]])->delete(true);
+        }
+        Db::table('group_role')->insertAll($groupData);
+    }
+
+    public function getAdminInfo() {
+        $selectModel = $this->alias('a')
+            ->field('a.id,a.account,a.name,a.mobile,a.last_login_time,a.status,
+            r.auth_name,
+            GROUP_CONCAT(DISTINCT st.store_name) as storeName,
+            GROUP_CONCAT(DISTINCT g.group_name) as groupName')
+            ->join('role r', 'a.role_id = r.id')
+            ->join('store_role sr', 'sr.admin_id = a.id', 'left')
+            ->join('store st', 'st.id = sr.store_id', 'left')
+            ->join('group_role gr', 'gr.admin_id = a.id', 'left')
+            ->join('group g', 'g.id = gr.group_id', 'left')
+            ->where(['a.id' => 14])
+            ->group('a.id')
+            ->find();
+        return Until::modelToArray($selectModel);
+    }
 }

+ 28 - 6
application/api/model/BaseModel.php

@@ -8,10 +8,13 @@ namespace app\api\model;
 
 
 use app\common\until\Until;
+use think\db\Query;
 use think\Model;
 
 class BaseModel extends Model {
 
+    const NORMAL = 1;
+
     private $where = [];
     private $page = 1;
     private $pageSize = 10;
@@ -52,17 +55,36 @@ class BaseModel extends Model {
         ];
     }
 
+    public function joinModelPageList( $countModel, $selectModel) {
+        $count = $countModel
+            ->where($this->where)
+            ->count('*');
+        if ($count > 0){
+            $list = $selectModel
+                ->where($this->where)
+                ->page($this->getPage(), $this->getPageSize())
+                ->select();
+            $list = Until::modelToArray($list);
+        }
+        return [
+            'count'     => $count,
+            'pageCount' => $count ? ceil($count / $this->getPageSize()) : 0,
+            'page'      => $this->getPage(),
+            'pageSize'  => $this->getPageSize(),
+            'list'      => $count ? $list : []
+        ];
+    }
     /**
      * @return array
      */
-    public function getWhere(): array {
+    public function getWhere() {
         return $this->where;
     }
 
     /**
      * @param array $where
      */
-    public function setWhere(array $where) {
+    public function setWhere($where) {
         $this->where = $where;
     }
 
@@ -76,8 +98,8 @@ class BaseModel extends Model {
     /**
      * @param int $page
      */
-    public function setPage(int $page) {
-        $this->page = $page;
+    public function setPage( $page) {
+        $this->page = (int)$page;
     }
 
     /**
@@ -90,8 +112,8 @@ class BaseModel extends Model {
     /**
      * @param int $pageSize
      */
-    public function setPageSize(int $pageSize) {
-        $this->pageSize = $pageSize;
+    public function setPageSize( $pageSize) {
+        $this->pageSize = (int)$pageSize;
     }
 
     /**

+ 24 - 0
application/api/model/BrandModel.php

@@ -0,0 +1,24 @@
+<?php
+/**
+ * Author: luzheng.liu
+ * Time: 2020/12/5 19:52
+ */
+
+namespace app\api\model;
+
+use app\common\until\Until;
+
+class BrandModel  extends BaseModel {
+
+    protected $table = 'brand';
+
+    public function getBrandList() {
+        $countModel = $this->alias('b')
+            ->join('group g', 'b.group_id = g.id');
+        $selectModel = $this->alias('b')
+            ->field('b.*,g.group_name')
+            ->join('group g', 'b.group_id = g.id');
+        return $this->joinModelPageList($countModel, $selectModel);
+
+    }
+}

+ 36 - 0
application/api/model/CompanyModel.php

@@ -0,0 +1,36 @@
+<?php
+/**
+ * Author: luzheng.liu
+ * Time: 2020/12/5 19:52
+ */
+
+namespace app\api\model;
+
+use app\common\until\Until;
+
+class CompanyModel  extends BaseModel {
+
+    protected $table = 'company';
+
+    public function getCompanyList() {
+        $countModel = $this->alias('c')
+            ->join('group g','g.id = c.group_id','left')
+            ->join('pay_config p','p.id = c.pay_id','left');
+
+        $selectModel = $this->alias('c')
+            ->field('c.*,g.group_name,p.pay_code')
+            ->join('group g','g.id = c.group_id','left')
+            ->join('pay_config p','p.id = c.pay_id','left');
+        return $this->joinModelPageList($countModel, $selectModel);
+    }
+
+    public function getCompanyInfo() {
+        $selectModel = $this->alias('c')
+            ->field('c.*,g.group_name,p.pay_code')
+            ->join('group g','g.id = c.group_id','left')
+            ->join('pay_config p','p.id = c.pay_id','left')
+            ->where($this->getWhere())
+            ->find();
+        return Until::modelToArray($selectModel);
+    }
+}

+ 13 - 0
application/api/model/GroupModel.php

@@ -0,0 +1,13 @@
+<?php
+/**
+ * Author: luzheng.liu
+ * Time: 2020/12/5 19:52
+ */
+
+namespace app\api\model;
+
+class GroupModel  extends BaseModel {
+
+    protected $table = 'group';
+
+}

+ 33 - 0
application/api/model/PayModel.php

@@ -0,0 +1,33 @@
+<?php
+/**
+ * Author: luzheng.liu
+ * Time: 2020/12/5 19:52
+ */
+
+namespace app\api\model;
+
+use app\common\until\Until;
+
+class PayModel  extends BaseModel {
+
+    protected $table = 'pay_config';
+
+    public function getPayList() {
+        $countModel = $this->alias('p')
+            ->join('group g','g.id = p.group_id');
+
+        $selectModel = $this->alias('p')
+            ->field('p.*,g.group_name')
+            ->join('group g','g.id = p.group_id');
+        return $this->joinModelPageList($countModel, $selectModel);
+    }
+
+    public function getPayInfo() {
+        $selectModel = $this->alias('p')
+            ->field('p.*,g.group_name')
+            ->join('group g','g.id = p.group_id')
+            ->where($this->getWhere())
+            ->find();
+        return Until::modelToArray($selectModel);
+    }
+}

+ 36 - 0
application/api/model/StaffModel.php

@@ -0,0 +1,36 @@
+<?php
+/**
+ * Author: luzheng.liu
+ * Time: 2020/12/5 19:52
+ */
+
+namespace app\api\model;
+
+use app\common\until\Until;
+
+class StaffModel  extends BaseModel {
+
+    protected $table = 'staff';
+
+    public function getStaffList() {
+        $countModel = $this->alias('s')
+            ->join('store sto','sto.id = s.store_id')
+            ->join('staff_title st','st.id = s.staff_title_id');
+
+        $selectModel = $this->alias('s')
+            ->field('s.*,sto.store_name,st.staff_title')
+            ->join('store sto','sto.id = s.store_id')
+            ->join('staff_title st','st.id = s.staff_title_id');
+        return $this->joinModelPageList($countModel, $selectModel);
+    }
+
+    public function getStaffInfo() {
+        $selectModel = $this->alias('s')
+            ->field('s.*,sto.store_name,st.staff_title')
+            ->join('store sto', 'sto.id = s.store_id')
+            ->join('staff_title st', 'st.id = s.staff_title_id')
+            ->where($this->getWhere())
+            ->find();
+        return Until::modelToArray($selectModel);
+    }
+}

+ 13 - 0
application/api/model/StaffTitleModel.php

@@ -0,0 +1,13 @@
+<?php
+/**
+ * Author: luzheng.liu
+ * Time: 2020/12/5 19:52
+ */
+
+namespace app\api\model;
+
+class StaffTitleModel  extends BaseModel {
+
+    protected $table = 'staff_title';
+
+}

+ 41 - 0
application/api/model/StoreModel.php

@@ -0,0 +1,41 @@
+<?php
+/**
+ * Author: luzheng.liu
+ * Time: 2020/12/5 19:52
+ */
+
+namespace app\api\model;
+
+use app\common\until\Until;
+use think\Db;
+
+class StoreModel  extends BaseModel {
+
+    protected $table = 'store';
+
+    public function getStoreList() {
+        $count = $this->alias('s')
+            ->join('company c', 'c.id = s.company_id','left')
+            ->join('group g', 'g.id = s.group_id','left')
+            ->join('brand b', 'b.id = s.brand_id','left');
+
+        $select = $this->alias('s')
+            ->field('s.*,c.company_name,b.brand_name,g.group_name')
+            ->join('company c', 'c.id = s.company_id','left')
+            ->join('group g', 'g.id = s.group_id','left')
+            ->join('brand b', 'b.id = s.brand_id','left');
+
+        return $this->joinModelPageList($count, $select);
+    }
+
+    public function getStoreInfo() {
+        $select = $this->alias('s')
+            ->field('s.*,c.company_name,b.brand_name,g.group_name')
+            ->join('company c', 'c.id = s.company_id', 'left')
+            ->join('group g', 'g.id = s.group_id', 'left')
+            ->join('brand b', 'b.id = s.brand_id', 'left')
+            ->where($this->getWhere())
+            ->find();
+        return Until::modelToArray($select);
+    }
+}

+ 3 - 3
application/api/model/UserModel.php

@@ -6,10 +6,10 @@
 
 namespace app\api\model;
 
+class UserModel  extends BaseModel {
 
-use think\Model;
-
-class UserModel  extends Model {
+    //正常
+    const NORMAL = 1;
 
     protected $table = 'user';
 }

+ 7 - 6
application/common/until/Token.php

@@ -19,16 +19,17 @@ class Token {
     public $expTime = 3600;
 
     public function __construct() {
-        $this->jwtKey = env('app.jwt_key','Rn4zNAX9e3li5dfI6mBuWLvbacTZqUr3');
+        $this->jwtKey = env('app.jwt_key', 'Rn4zNAX9e3li5dfI6mBuWLvbacTZq123');
     }
 
 
-    public function getToken(int $userId,string $visitor = '') {
+    public function getToken(int $userId, string $visitor = '', $isAdmin = true) {
         $payload = [
-            "iat"    => time(),
-            "exp"    => time() + (3600 * 24 * 7),
-            "userId" => $userId,
-            "visitor"=> $visitor
+            "iat"     => time(),
+            "exp"     => time() + (3600 * 24 * 7),
+            "userId"  => $userId,
+            "visitor" => $visitor,
+            'isAdmin' => $isAdmin ? 1 : 0
         ];
         $token = JWT::encode($payload, $this->jwtKey);
         return $token;

+ 45 - 6
application/common/until/Until.php

@@ -17,11 +17,12 @@ class Until {
      * @param int $code
      * @param string $message
      */
-    public static function output(array $data = [],string $message = 'success',int $code = Enum::SUCCESS_CODE) {
+    public static function output(array $data = [], string $message = 'success', int $code = Enum::SUCCESS_CODE) {
+        $data = self::convertUnderlineArray($data);
         $re = [
-            'code' => $code,
+            'code'    => $code,
             'message' => $message,
-            'data' => $data
+            'data'    => $data
         ];
         header('Content-Type: application/json; charset=utf-8');
         echo json_encode($re);
@@ -34,14 +35,14 @@ class Until {
      * @param array $data
      * @param int $code
      */
-    public static function outputSystemError(string $showMsg = '',string $systemErrorMsg = '', $data = [], $code = Enum::THROW_ERR_CODE) {
+    public static function outputSystemError(string $showMsg = '', string $systemErrorMsg = '', $data = [], $code = Enum::THROW_ERR_CODE) {
         $output = ['code' => $code, 'msg' => $showMsg, 'systemErrorMsg' => $systemErrorMsg, 'data' => $data];
         header('Content-Type: application/json; charset=utf-8');
         echo json_encode($output);
         die;
     }
 
-    public static function modelToArray($data):array {
+    public static function modelToArray($data): array {
         if (empty($data)) {
             return [];
         }
@@ -51,13 +52,51 @@ class Until {
     public static function check(array $rule, array $data) {
         $validate = new Validate();
         if (!$validate->check($data, $rule)) {
-            self::output([],(string)$validate->getError(),Enum::THROW_ERR_CODE);
+            self::output([], (string)$validate->getError(), Enum::THROW_ERR_CODE);
         }
     }
 
     public static function getInput(): array {
         $input = file_get_contents("php://input");
+        $data = json_decode($input, true);
+        if (empty($data)) {
+            return input();
+        }
         return json_decode($input, true);
     }
 
+    /**
+     * 公共下划线转驼峰(新)
+     *   由于convertUnderlineArray会默认把字段值的类型全部转成string,再转回来以后,会造成类型的丢失.
+     *   所以这个新的转换方法,默认不转换.需要的时候才会去转换. 这样就不会有来回转换造成类型丢失的问题了.
+     * @param array $arr
+     * @param bool $ucFirst 首字母大写
+     * @param bool $strictMode 严格模式: 严格区分int和字符串,默认为严格模式
+     * @return array
+     */
+    public static function convertUnderlineArray($arr = [], $ucFirst = false, $strictMode = true): array {
+        if (empty($arr)) {
+            return [];
+        }
+        $newArray = [];
+        foreach ($arr as $key => $value) {
+            $str = ucwords(str_replace('_', ' ', $key));
+            $str = str_replace(' ', '', lcfirst($str));
+            $str = $ucFirst ? ucfirst($str) : $str;
+
+            //如果是数组重构这个数组用递归的方法
+            if (is_array($value)) {
+                $newArray[$str] = self::convertUnderlineArray($value, $ucFirst, $strictMode);
+                continue;
+            }
+            // 非严格模式下全部转成字符串
+            if ($strictMode === false) {
+                $newArray[$str] = (string)$value;
+                continue;
+            }
+            $newArray[$str] = $value ?? '';
+        }
+        return $newArray;
+    }
+
 }

+ 2 - 1
composer.json

@@ -22,7 +22,8 @@
         "ext-curl": "*",
         "ext-json": "*",
         "firebase/php-jwt": "^5.2",
-        "guzzlehttp/guzzle": "~6.0"
+        "guzzlehttp/guzzle": "~6.0",
+        "zircote/swagger-php": "^3.1"
     }
     ,
     "require-dev": {

+ 297 - 1
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "2a5eaa75d85c83e39069e4eebb2b1ebc",
+    "content-hash": "7a392cde7baec35d94efbca236b944c9",
     "packages": [
         {
             "name": "cakephp/cache",
@@ -385,6 +385,165 @@
             "time": "2019-03-14T14:41:29+00:00"
         },
         {
+            "name": "doctrine/annotations",
+            "version": "1.11.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/annotations.git",
+                "reference": "ce77a7ba1770462cd705a91a151b6c3746f9c6ad"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/annotations/zipball/ce77a7ba1770462cd705a91a151b6c3746f9c6ad",
+                "reference": "ce77a7ba1770462cd705a91a151b6c3746f9c6ad",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "doctrine/lexer": "1.*",
+                "ext-tokenizer": "*",
+                "php": "^7.1 || ^8.0"
+            },
+            "require-dev": {
+                "doctrine/cache": "1.*",
+                "doctrine/coding-standard": "^6.0 || ^8.1",
+                "phpstan/phpstan": "^0.12.20",
+                "phpunit/phpunit": "^7.5 || ^9.1.5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.11.x-dev"
+                }
+            },
+            "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"
+            ],
+            "time": "2020-10-26T10:28:16+00:00"
+        },
+        {
+            "name": "doctrine/lexer",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/lexer.git",
+                "reference": "e864bbf5904cb8f5bb334f99209b48018522f042"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042",
+                "reference": "e864bbf5904cb8f5bb334f99209b48018522f042",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^6.0",
+                "phpstan/phpstan": "^0.11.8",
+                "phpunit/phpunit": "^8.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer"
+                }
+            },
+            "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"
+            ],
+            "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"
+                }
+            ],
+            "time": "2020-05-25T17:44:05+00:00"
+        },
+        {
             "name": "firebase/php-jwt",
             "version": "v5.2.0",
             "source": {
@@ -1217,6 +1376,70 @@
             "time": "2019-02-07T11:40:08+00:00"
         },
         {
+            "name": "symfony/finder",
+            "version": "v5.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/finder.git",
+                "reference": "fd8305521692f27eae3263895d1ef1571c71a78d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/fd8305521692f27eae3263895d1ef1571c71a78d",
+                "reference": "fd8305521692f27eae3263895d1ef1571c71a78d",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Finder\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Finder Component",
+            "homepage": "https://symfony.com",
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-11-18T09:42:36+00:00"
+        },
+        {
             "name": "symfony/polyfill-ctype",
             "version": "v1.11.0",
             "source": {
@@ -1769,6 +1992,79 @@
                 }
             ],
             "time": "2018-05-11T06:45:42+00:00"
+        },
+        {
+            "name": "zircote/swagger-php",
+            "version": "3.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/zircote/swagger-php.git",
+                "reference": "9d172471e56433b5c7061006b9a766f262a3edfd"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/zircote/swagger-php/zipball/9d172471e56433b5c7061006b9a766f262a3edfd",
+                "reference": "9d172471e56433b5c7061006b9a766f262a3edfd",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "doctrine/annotations": "*",
+                "ext-json": "*",
+                "php": ">=7.2",
+                "symfony/finder": ">=2.2",
+                "symfony/yaml": ">=3.3"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^2.16",
+                "phpunit/phpunit": ">=8"
+            },
+            "bin": [
+                "bin/openapi"
+            ],
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "OpenApi\\": "src"
+                },
+                "files": [
+                    "src/functions.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Robert Allen",
+                    "email": "zircote@gmail.com"
+                },
+                {
+                    "name": "Bob Fanger",
+                    "email": "bfanger@gmail.com",
+                    "homepage": "https://bfanger.nl"
+                },
+                {
+                    "name": "Martin Rademacher",
+                    "email": "mano@radebatz.net",
+                    "homepage": "https://radebatz.net"
+                }
+            ],
+            "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations",
+            "homepage": "https://github.com/zircote/swagger-php/",
+            "keywords": [
+                "api",
+                "json",
+                "rest",
+                "service discovery"
+            ],
+            "time": "2020-09-03T20:18:43+00:00"
         }
     ],
     "packages-dev": [

+ 1 - 1
config/app.php

@@ -141,6 +141,6 @@ return [
     // 显示错误信息
     'show_error_msg'         => false,
     // 异常处理handle类 留空使用 \think\exception\Handle
-    'exception_handle'       => 'app\index\exception\ExceptionHandel',
+    'exception_handle'       => 'app\api\exception\ExceptionHandel',
 
 ];

+ 1 - 1
config/database.php

@@ -15,7 +15,7 @@ return [
     // 服务器地址
     'hostname'        => 'llzlovesh.top',
     // 数据库名
-    'database'        => 'hc_live',
+    'database'        => 'kd_massage',
     // 用户名
     'username'        => 'root',
     // 密码

+ 9 - 0
public/swagger.php

@@ -0,0 +1,9 @@
+<?php
+/**
+ * Author: luzheng.liu
+ * Time: 2020/12/15 15:36
+ */
+require("vendor/autoload.php");
+$openapi = \OpenApi\scan('/Users/liuluzheng/PhpstormProjects/tp5.1/application/api/controller');
+header('Content-Type: application/x-yaml');
+echo $openapi->toYaml();

+ 1 - 0
vendor/bin/openapi

@@ -0,0 +1 @@
+../zircote/swagger-php/bin/openapi

+ 1 - 0
vendor/composer/autoload_files.php

@@ -18,4 +18,5 @@ return array(
     'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
     'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
     '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
+    '0ccdf99b8f62f02c52cba55802e0c2e7' => $vendorDir . '/zircote/swagger-php/src/functions.php',
 );

+ 4 - 0
vendor/composer/autoload_psr4.php

@@ -15,6 +15,7 @@ return array(
     'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
     'Symfony\\Contracts\\' => array($vendorDir . '/symfony/contracts'),
     'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
+    'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
     'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'),
     'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
     'Symfony\\Component\\Config\\' => array($vendorDir . '/symfony/config'),
@@ -23,10 +24,13 @@ return array(
     'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
     'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
     'Phinx\\' => array($vendorDir . '/robmorgan/phinx/src/Phinx'),
+    'OpenApi\\' => array($vendorDir . '/zircote/swagger-php/src'),
     'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
     'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
     'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
     'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
+    'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'),
+    'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'),
     'Cake\\Utility\\' => array($vendorDir . '/cakephp/utility'),
     'Cake\\Log\\' => array($vendorDir . '/cakephp/log'),
     'Cake\\Datasource\\' => array($vendorDir . '/cakephp/datasource'),

+ 27 - 0
vendor/composer/autoload_static.php

@@ -19,6 +19,7 @@ class ComposerStaticInit74794bfe79cbabcac146f5fbfe3de9d7
         'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
         'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
         '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
+        '0ccdf99b8f62f02c52cba55802e0c2e7' => __DIR__ . '/..' . '/zircote/swagger-php/src/functions.php',
     );
 
     public static $prefixLengthsPsr4 = array (
@@ -39,6 +40,7 @@ class ComposerStaticInit74794bfe79cbabcac146f5fbfe3de9d7
             'Symfony\\Polyfill\\Ctype\\' => 23,
             'Symfony\\Contracts\\' => 18,
             'Symfony\\Component\\Yaml\\' => 23,
+            'Symfony\\Component\\Finder\\' => 25,
             'Symfony\\Component\\Filesystem\\' => 29,
             'Symfony\\Component\\Console\\' => 26,
             'Symfony\\Component\\Config\\' => 25,
@@ -51,6 +53,10 @@ class ComposerStaticInit74794bfe79cbabcac146f5fbfe3de9d7
             'Psr\\Http\\Client\\' => 16,
             'Phinx\\' => 6,
         ),
+        'O' => 
+        array (
+            'OpenApi\\' => 8,
+        ),
         'G' => 
         array (
             'GuzzleHttp\\Psr7\\' => 16,
@@ -61,6 +67,11 @@ class ComposerStaticInit74794bfe79cbabcac146f5fbfe3de9d7
         array (
             'Firebase\\JWT\\' => 13,
         ),
+        'D' => 
+        array (
+            'Doctrine\\Common\\Lexer\\' => 22,
+            'Doctrine\\Common\\Annotations\\' => 28,
+        ),
         'C' => 
         array (
             'Cake\\Utility\\' => 13,
@@ -110,6 +121,10 @@ class ComposerStaticInit74794bfe79cbabcac146f5fbfe3de9d7
         array (
             0 => __DIR__ . '/..' . '/symfony/yaml',
         ),
+        'Symfony\\Component\\Finder\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/finder',
+        ),
         'Symfony\\Component\\Filesystem\\' => 
         array (
             0 => __DIR__ . '/..' . '/symfony/filesystem',
@@ -142,6 +157,10 @@ class ComposerStaticInit74794bfe79cbabcac146f5fbfe3de9d7
         array (
             0 => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx',
         ),
+        'OpenApi\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/zircote/swagger-php/src',
+        ),
         'GuzzleHttp\\Psr7\\' => 
         array (
             0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
@@ -158,6 +177,14 @@ class ComposerStaticInit74794bfe79cbabcac146f5fbfe3de9d7
         array (
             0 => __DIR__ . '/..' . '/firebase/php-jwt/src',
         ),
+        'Doctrine\\Common\\Lexer\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer',
+        ),
+        'Doctrine\\Common\\Annotations\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/doctrine/annotations/lib/Doctrine/Common/Annotations',
+        ),
         'Cake\\Utility\\' => 
         array (
             0 => __DIR__ . '/..' . '/cakephp/utility',

+ 304 - 0
vendor/composer/installed.json

@@ -392,6 +392,169 @@
         ]
     },
     {
+        "name": "doctrine/annotations",
+        "version": "1.11.1",
+        "version_normalized": "1.11.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/doctrine/annotations.git",
+            "reference": "ce77a7ba1770462cd705a91a151b6c3746f9c6ad"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/doctrine/annotations/zipball/ce77a7ba1770462cd705a91a151b6c3746f9c6ad",
+            "reference": "ce77a7ba1770462cd705a91a151b6c3746f9c6ad",
+            "shasum": "",
+            "mirrors": [
+                {
+                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                    "preferred": true
+                }
+            ]
+        },
+        "require": {
+            "doctrine/lexer": "1.*",
+            "ext-tokenizer": "*",
+            "php": "^7.1 || ^8.0"
+        },
+        "require-dev": {
+            "doctrine/cache": "1.*",
+            "doctrine/coding-standard": "^6.0 || ^8.1",
+            "phpstan/phpstan": "^0.12.20",
+            "phpunit/phpunit": "^7.5 || ^9.1.5"
+        },
+        "time": "2020-10-26T10:28:16+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.11.x-dev"
+            }
+        },
+        "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"
+        ]
+    },
+    {
+        "name": "doctrine/lexer",
+        "version": "1.2.1",
+        "version_normalized": "1.2.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/doctrine/lexer.git",
+            "reference": "e864bbf5904cb8f5bb334f99209b48018522f042"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042",
+            "reference": "e864bbf5904cb8f5bb334f99209b48018522f042",
+            "shasum": "",
+            "mirrors": [
+                {
+                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                    "preferred": true
+                }
+            ]
+        },
+        "require": {
+            "php": "^7.2 || ^8.0"
+        },
+        "require-dev": {
+            "doctrine/coding-standard": "^6.0",
+            "phpstan/phpstan": "^0.11.8",
+            "phpunit/phpunit": "^8.2"
+        },
+        "time": "2020-05-25T17:44:05+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.2.x-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer"
+            }
+        },
+        "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"
+        ],
+        "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"
+            }
+        ]
+    },
+    {
         "name": "firebase/php-jwt",
         "version": "v5.2.0",
         "version_normalized": "5.2.0.0",
@@ -1629,6 +1792,72 @@
         "homepage": "https://symfony.com"
     },
     {
+        "name": "symfony/finder",
+        "version": "v5.2.0",
+        "version_normalized": "5.2.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/finder.git",
+            "reference": "fd8305521692f27eae3263895d1ef1571c71a78d"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/symfony/finder/zipball/fd8305521692f27eae3263895d1ef1571c71a78d",
+            "reference": "fd8305521692f27eae3263895d1ef1571c71a78d",
+            "shasum": "",
+            "mirrors": [
+                {
+                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                    "preferred": true
+                }
+            ]
+        },
+        "require": {
+            "php": ">=7.2.5"
+        },
+        "time": "2020-11-18T09:42:36+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Symfony\\Component\\Finder\\": ""
+            },
+            "exclude-from-classmap": [
+                "/Tests/"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Fabien Potencier",
+                "email": "fabien@symfony.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors"
+            }
+        ],
+        "description": "Symfony Finder Component",
+        "homepage": "https://symfony.com",
+        "funding": [
+            {
+                "url": "https://symfony.com/sponsor",
+                "type": "custom"
+            },
+            {
+                "url": "https://github.com/fabpot",
+                "type": "github"
+            },
+            {
+                "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                "type": "tidelift"
+            }
+        ]
+    },
+    {
         "name": "symfony/polyfill-ctype",
         "version": "v1.11.0",
         "version_normalized": "1.11.0.0",
@@ -2197,5 +2426,80 @@
                 "email": "448901948@qq.com"
             }
         ]
+    },
+    {
+        "name": "zircote/swagger-php",
+        "version": "3.1.0",
+        "version_normalized": "3.1.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/zircote/swagger-php.git",
+            "reference": "9d172471e56433b5c7061006b9a766f262a3edfd"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/zircote/swagger-php/zipball/9d172471e56433b5c7061006b9a766f262a3edfd",
+            "reference": "9d172471e56433b5c7061006b9a766f262a3edfd",
+            "shasum": "",
+            "mirrors": [
+                {
+                    "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                    "preferred": true
+                }
+            ]
+        },
+        "require": {
+            "doctrine/annotations": "*",
+            "ext-json": "*",
+            "php": ">=7.2",
+            "symfony/finder": ">=2.2",
+            "symfony/yaml": ">=3.3"
+        },
+        "require-dev": {
+            "friendsofphp/php-cs-fixer": "^2.16",
+            "phpunit/phpunit": ">=8"
+        },
+        "time": "2020-09-03T20:18:43+00:00",
+        "bin": [
+            "bin/openapi"
+        ],
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "OpenApi\\": "src"
+            },
+            "files": [
+                "src/functions.php"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "Apache-2.0"
+        ],
+        "authors": [
+            {
+                "name": "Robert Allen",
+                "email": "zircote@gmail.com"
+            },
+            {
+                "name": "Bob Fanger",
+                "email": "bfanger@gmail.com",
+                "homepage": "https://bfanger.nl"
+            },
+            {
+                "name": "Martin Rademacher",
+                "email": "mano@radebatz.net",
+                "homepage": "https://radebatz.net"
+            }
+        ],
+        "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations",
+        "homepage": "https://github.com/zircote/swagger-php/",
+        "keywords": [
+            "api",
+            "json",
+            "rest",
+            "service discovery"
+        ]
     }
 ]

+ 49 - 0
vendor/doctrine/annotations/.doctrine-project.json

@@ -0,0 +1,49 @@
+{
+    "active": true,
+    "name": "Annotations",
+    "slug": "annotations",
+    "docsSlug": "doctrine-annotations",
+    "versions": [
+        {
+            "name": "1.11",
+            "branchName": "master",
+            "slug": "latest",
+            "upcoming": true
+        },
+        {
+            "name": "1.10",
+            "branchName": "1.10.x",
+            "slug": "1.10",
+            "aliases": [
+                "current",
+                "stable"
+            ],
+            "current": true,
+            "maintained": true
+        },
+        {
+            "name": "1.9",
+            "branchName": "1.9.x",
+            "slug": "1.9",
+            "maintained": false
+        },
+        {
+            "name": "1.8",
+            "branchName": "1.8",
+            "slug": "1.8",
+            "maintained": false
+        },
+        {
+            "name": "1.7",
+            "branchName": "1.7",
+            "slug": "1.7",
+            "maintained": false
+        },
+        {
+            "name": "1.6",
+            "branchName": "1.6",
+            "slug": "1.6",
+            "maintained": false
+        }
+    ]
+}

+ 39 - 0
vendor/doctrine/annotations/.github/workflows/coding-standards.yml

@@ -0,0 +1,39 @@
+
+name: "Coding Standards"
+
+on: ["pull_request", "push"]
+
+jobs:
+  coding-standards:
+    name: "Coding Standards"
+    runs-on: "ubuntu-20.04"
+
+    strategy:
+      matrix:
+        php-version:
+          - "7.4"
+
+    steps:
+      - name: "Checkout"
+        uses: "actions/checkout@v2"
+
+      - name: "Install PHP"
+        uses: "shivammathur/setup-php@v2"
+        with:
+          coverage: "none"
+          php-version: "${{ matrix.php-version }}"
+          tools: "cs2pr"
+
+      - name: "Cache dependencies installed with Composer"
+        uses: "actions/cache@v2"
+        with:
+          path: "~/.composer/cache"
+          key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
+          restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
+
+      - name: "Install dependencies with Composer"
+        run: "composer install --no-interaction --no-progress --no-suggest"
+
+      # https://github.com/doctrine/.github/issues/3
+      - name: "Run PHP_CodeSniffer"
+        run: "vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr"

+ 61 - 0
vendor/doctrine/annotations/.github/workflows/continuous-integration.yml

@@ -0,0 +1,61 @@
+name: "Continuous Integration"
+
+on:
+  pull_request:
+    branches:
+      - "*.x"
+      - "master"
+  push:
+    branches:
+      - "*.x"
+      - "master"
+
+jobs:
+  phpunit:
+    name: "PHPUnit"
+    runs-on: "ubuntu-20.04"
+
+    strategy:
+      matrix:
+        php-version:
+          - "7.1"
+          - "7.2"
+          - "7.3"
+          - "7.4"
+          - "8.0"
+        deps:
+          - "normal"
+        include:
+          - deps: "low"
+            php-version: "7.2"
+
+    steps:
+      - name: "Checkout"
+        uses: "actions/checkout@v2"
+        with:
+          fetch-depth: 2
+
+      - name: "Install PHP"
+        uses: "shivammathur/setup-php@v2"
+        with:
+          php-version: "${{ matrix.php-version }}"
+          coverage: "pcov"
+          ini-values: "zend.assertions=1"
+
+      - name: "Cache dependencies installed with composer"
+        uses: "actions/cache@v2"
+        with:
+          path: "~/.composer/cache"
+          key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
+          restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
+
+      - name: "Update dependencies with composer"
+        run: "composer update --no-interaction --no-progress --no-suggest"
+        if: "${{ matrix.deps == 'normal' }}"
+
+      - name: "Install lowest possible dependencies with composer"
+        run: "composer update --no-interaction --no-progress --no-suggest --prefer-dist --prefer-lowest"
+        if: "${{ matrix.deps == 'low' }}"
+
+      - name: "Run PHPUnit"
+        run: "vendor/bin/phpunit"

+ 55 - 0
vendor/doctrine/annotations/.github/workflows/release-on-milestone-closed.yml

@@ -0,0 +1,55 @@
+name: "Automatic Releases"
+
+on:
+  milestone:
+    types:
+      - "closed"
+
+jobs:
+  release:
+    name: "Git tag, release & create merge-up PR"
+    runs-on: "ubuntu-20.04"
+
+    steps:
+      - name: "Checkout"
+        uses: "actions/checkout@v2"
+
+      - name: "Release"
+        uses: "laminas/automatic-releases@v1"
+        with:
+          command-name: "laminas:automatic-releases:release"
+        env:
+          "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
+          "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
+          "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
+          "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
+
+      - name: "Create Merge-Up Pull Request"
+        uses: "laminas/automatic-releases@v1"
+        with:
+          command-name: "laminas:automatic-releases:create-merge-up-pull-request"
+        env:
+          "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
+          "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
+          "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
+          "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
+
+      - name: "Create and/or Switch to new Release Branch"
+        uses: "laminas/automatic-releases@v1"
+        with:
+          command-name: "laminas:automatic-releases:switch-default-branch-to-next-minor"
+        env:
+          "GITHUB_TOKEN": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
+          "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
+          "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
+          "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
+
+      - name: "Create new milestones"
+        uses: "laminas/automatic-releases@v1"
+        with:
+          command-name: "laminas:automatic-releases:create-milestones"
+        env:
+          "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
+          "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
+          "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
+          "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}

+ 45 - 0
vendor/doctrine/annotations/.github/workflows/static-analysis.yml

@@ -0,0 +1,45 @@
+name: "Static Analysis"
+
+on:
+  pull_request:
+    branches:
+      - "*.x"
+      - "master"
+  push:
+    branches:
+      - "*.x"
+      - "master"
+
+jobs:
+  static-analysis-phpstan:
+    name: "Static Analysis with PHPStan"
+    runs-on: "ubuntu-20.04"
+
+    strategy:
+      matrix:
+        php-version:
+          - "7.4"
+
+    steps:
+      - name: "Checkout code"
+        uses: "actions/checkout@v2"
+
+      - name: "Install PHP"
+        uses: "shivammathur/setup-php@v2"
+        with:
+          coverage: "none"
+          php-version: "${{ matrix.php-version }}"
+          tools: "cs2pr"
+
+      - name: "Cache dependencies installed with composer"
+        uses: "actions/cache@v2"
+        with:
+          path: "~/.composer/cache"
+          key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
+          restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
+
+      - name: "Install dependencies with composer"
+        run: "composer install --no-interaction --no-progress --no-suggest"
+
+      - name: "Run a static analysis with phpstan/phpstan"
+        run: "vendor/bin/phpstan analyse -l 3 -c phpstan.neon --error-format=checkstyle lib/ tests/ | cs2pr"

+ 162 - 0
vendor/doctrine/annotations/CHANGELOG.md

@@ -0,0 +1,162 @@
+## Changelog
+
+### 1.6.1
+
+This release fixes an issue in which annotations such as `@foo-bar`
+and `@foo-` were incorrectly recognised as valid, and both erroneously
+parsed as `@foo`.
+
+Any annotation with `@name-*` format will now silently be ignored,
+allowing vendor-specific annotations to be prefixed with the tool
+name.
+
+Total issues resolved: **3**
+
+- [165: Update the composer branch alias](https://github.com/doctrine/annotations/pull/165) thanks to @mikeSimonson
+- [209: Change Annotation::value typehint to mixed](https://github.com/doctrine/annotations/pull/209) thanks to @malarzm
+- [257: Skip parsing annotations containing dashes, such as `@Foo-bar`, or `@Foo-`](https://github.com/doctrine/annotations/pull/257) thanks to @Ocramius
+
+### 1.6.0
+
+This release brings a new endpoint that make sure that you can't shoot yourself in the foot by calling ```registerLoader``` multiple times and a few tests improvements.
+
+Total issues resolved: **7**
+
+- [145: Memory leak in AnnotationRegistry::registerLoader() when called multiple times](https://github.com/doctrine/annotations/issues/145) thanks to @TriAnMan
+- [146: Import error on @experimental Annotation](https://github.com/doctrine/annotations/issues/146) thanks to @aturki
+- [147: Ignoring @experimental annotation used by Symfony 3.3 CacheAdapter](https://github.com/doctrine/annotations/pull/147) thanks to @aturki
+- [151: Remove duplicate code in `DCOM58Test`](https://github.com/doctrine/annotations/pull/151) thanks to @tuanphpvn
+- [161: Prevent loading class&#95;exists multiple times](https://github.com/doctrine/annotations/pull/161) thanks to @jrjohnson
+- [162: Add registerUniqueLoader to AnnotationRegistry](https://github.com/doctrine/annotations/pull/162) thanks to @jrjohnson
+- [163: Use assertDirectoryExists and assertDirectoryNotExists](https://github.com/doctrine/annotations/pull/163) thanks to @carusogabriel
+
+Thanks to everyone involved in this release.
+
+### 1.5.0
+
+This release increments the minimum supported PHP version to 7.1.0.
+
+Also, HHVM official support has been dropped.
+
+Some noticeable performance improvements to annotation autoloading
+have been applied, making failed annotation autoloading less heavy
+on the filesystem access.
+
+- [133: Add @throws annotation in AnnotationReader#__construct()](https://github.com/doctrine/annotations/issues/133) thanks to @SenseException
+- [134: Require PHP 7.1, drop HHVM support](https://github.com/doctrine/annotations/issues/134) thanks to @lcobucci
+- [135: Prevent the same loader from being registered twice](https://github.com/doctrine/annotations/issues/135)  thanks to @jrjohnson
+- [137: #135 optimise multiple class load attempts in AnnotationRegistry](https://github.com/doctrine/annotations/issues/137)  thanks to @Ocramius
+
+
+### 1.4.0
+
+This release fix an issue were some annotations could be not loaded if the namespace in the use statement started with a backslash.
+It also update the tests and drop the support for php 5.X
+
+- [115: Missing annotations with the latest composer version](https://github.com/doctrine/annotations/issues/115) thanks to @pascalporedda
+- [120: Missing annotations with the latest composer version](https://github.com/doctrine/annotations/pull/120) thanks to @gnat42
+- [121: Adding a more detailed explanation of the test](https://github.com/doctrine/annotations/pull/121) thanks to @mikeSimonson
+- [101: Test annotation parameters containing space](https://github.com/doctrine/annotations/pull/101) thanks to @mikeSimonson
+- [111: Cleanup: move to correct phpunit assertions](https://github.com/doctrine/annotations/pull/111) thanks to @Ocramius
+- [112: Removes support for PHP 5.x](https://github.com/doctrine/annotations/pull/112) thanks to @railto
+- [113: bumped phpunit version to 5.7](https://github.com/doctrine/annotations/pull/113) thanks to @gabbydgab
+- [114: Enhancement: Use SVG Travis build badge](https://github.com/doctrine/annotations/pull/114) thanks to @localheinz
+- [118: Integrating PHPStan](https://github.com/doctrine/annotations/pull/118) thanks to @ondrejmirtes
+
+### 1.3.1 - 2016-12-30
+
+This release fixes an issue with ignored annotations that were already
+autoloaded, causing the `SimpleAnnotationReader` to pick them up
+anyway. [#110](https://github.com/doctrine/annotations/pull/110)
+
+Additionally, an issue was fixed in the `CachedReader`, which was
+not correctly checking the freshness of cached annotations when
+traits were defined on a class. [#105](https://github.com/doctrine/annotations/pull/105)
+
+Total issues resolved: **2**
+
+- [105: Return single max timestamp](https://github.com/doctrine/annotations/pull/105)
+- [110: setIgnoreNotImportedAnnotations(true) didn&rsquo;t work for existing classes](https://github.com/doctrine/annotations/pull/110)
+
+### 1.3.0
+
+This release introduces a PHP version bump. `doctrine/annotations` now requires PHP
+5.6 or later to be installed.
+
+A series of additional improvements have been introduced:
+
+ * support for PHP 7 "grouped use statements"
+ * support for ignoring entire namespace names
+   via `Doctrine\Common\Annotations\AnnotationReader::addGlobalIgnoredNamespace()` and
+   `Doctrine\Common\Annotations\DocParser::setIgnoredAnnotationNamespaces()`. This will
+   allow you to ignore annotations from namespaces that you cannot autoload
+ * testing all parent classes and interfaces when checking if the annotation cache
+   in the `CachedReader` is fresh
+ * simplifying the cache keys used by the `CachedReader`: keys are no longer artificially
+   namespaced, since `Doctrine\Common\Cache` already supports that
+ * corrected parsing of multibyte strings when `mbstring.func_overload` is enabled
+ * corrected parsing of annotations when `"\t"` is put before the first annotation
+   in a docblock
+ * allow skipping non-imported annotations when a custom `DocParser` is passed to
+   the `AnnotationReader` constructor
+
+Total issues resolved: **15**
+
+- [45: DocParser can now ignore whole namespaces](https://github.com/doctrine/annotations/pull/45)
+- [57: Switch to the docker-based infrastructure on Travis](https://github.com/doctrine/annotations/pull/57)
+- [59: opcache.load&#95;comments has been removed from PHP 7](https://github.com/doctrine/annotations/pull/59)
+- [62: &#91;CachedReader&#92; Test traits and parent class to see if cache is fresh](https://github.com/doctrine/annotations/pull/62)
+- [65: Remove cache salt making key unnecessarily long](https://github.com/doctrine/annotations/pull/65)
+- [66: Fix of incorrect parsing multibyte strings](https://github.com/doctrine/annotations/pull/66)
+- [68: Annotations that are indented by tab are not processed.](https://github.com/doctrine/annotations/issues/68)
+- [69: Support for Group Use Statements](https://github.com/doctrine/annotations/pull/69)
+- [70: Allow tab character before first annotation in DocBlock](https://github.com/doctrine/annotations/pull/70)
+- [74: Ignore not registered annotations fix](https://github.com/doctrine/annotations/pull/74)
+- [92: Added tests for AnnotationRegistry class.](https://github.com/doctrine/annotations/pull/92)
+- [96: Fix/#62 check trait and parent class ttl in annotations](https://github.com/doctrine/annotations/pull/96)
+- [97: Feature - #45 - allow ignoring entire namespaces](https://github.com/doctrine/annotations/pull/97)
+- [98: Enhancement/#65 remove cache salt from cached reader](https://github.com/doctrine/annotations/pull/98)
+- [99: Fix - #70 - allow tab character before first annotation in docblock](https://github.com/doctrine/annotations/pull/99)
+
+### 1.2.4
+
+Total issues resolved: **1**
+
+- [51: FileCacheReader::saveCacheFile::unlink fix](https://github.com/doctrine/annotations/pull/51)
+
+### 1.2.3
+
+Total issues resolved: [**2**](https://github.com/doctrine/annotations/milestones/v1.2.3)
+
+- [49: #46 - applying correct `chmod()` to generated cache file](https://github.com/doctrine/annotations/pull/49)
+- [50: Hotfix: match escaped quotes (revert #44)](https://github.com/doctrine/annotations/pull/50)
+
+### 1.2.2
+
+Total issues resolved: **4**
+
+- [43: Exclude files from distribution with .gitattributes](https://github.com/doctrine/annotations/pull/43)
+- [44: Update DocLexer.php](https://github.com/doctrine/annotations/pull/44)
+- [46: A plain &quot;file&#95;put&#95;contents&quot; can cause havoc](https://github.com/doctrine/annotations/pull/46)
+- [48: Deprecating the `FileCacheReader` in 1.2.2: will be removed in 2.0.0](https://github.com/doctrine/annotations/pull/48)
+
+### 1.2.1
+
+Total issues resolved: **4**
+
+- [38: fixes doctrine/common#326](https://github.com/doctrine/annotations/pull/38)
+- [39: Remove superfluous NS](https://github.com/doctrine/annotations/pull/39)
+- [41: Warn if load_comments is not enabled.](https://github.com/doctrine/annotations/pull/41)
+- [42: Clean up unused uses](https://github.com/doctrine/annotations/pull/42)
+
+### 1.2.0
+
+ * HHVM support
+ * Allowing dangling comma in annotations
+ * Excluded annotations are no longer autoloaded
+ * Importing namespaces also in traits
+ * Added support for `::class` 5.5-style constant, works also in 5.3 and 5.4
+
+### 1.1.0
+
+ * Add Exception when ZendOptimizer+ or Opcache is configured to drop comments

+ 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.

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

@@ -0,0 +1,22 @@
+# Doctrine Annotations
+
+[![Build Status](https://travis-ci.org/doctrine/annotations.svg?branch=master)](https://travis-ci.org/doctrine/annotations)
+[![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://poser.pugx.org/doctrine/annotations/v/stable.png)](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/latest/reference/index.html#introduction).
+
+## Changelog
+
+See [CHANGELOG.md](CHANGELOG.md).

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

@@ -0,0 +1,46 @@
+{
+    "name": "doctrine/annotations",
+    "type": "library",
+    "description": "Docblock Annotations Parser",
+    "keywords": ["annotations", "docblock", "parser"],
+    "homepage": "https://www.doctrine-project.org/projects/annotations.html",
+    "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"}
+    ],
+    "require": {
+        "php": "^7.1 || ^8.0",
+        "ext-tokenizer": "*",
+        "doctrine/lexer": "1.*"
+    },
+    "require-dev": {
+        "doctrine/cache": "1.*",
+        "doctrine/coding-standard": "^6.0 || ^8.1",
+        "phpstan/phpstan": "^0.12.20",
+        "phpunit/phpunit": "^7.5 || ^9.1.5"
+    },
+    "config": {
+        "sort-packages": true
+    },
+    "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/SingleClassLOC1000.php"
+        ]
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.11.x-dev"
+        }
+    }
+}

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

@@ -0,0 +1,271 @@
+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.
+
+You can use a file caching reader, but please note it is deprecated to
+do so:
+
+.. code-block:: php
+
+    use Doctrine\Common\Annotations\FileCacheReader;
+    use Doctrine\Common\Annotations\AnnotationReader;
+
+    $reader = new FileCacheReader(
+        new AnnotationReader(),
+        "/path/to/cache",
+        $debug = true
+    );
+
+If you set the ``debug`` flag to ``true`` the cache reader will check
+for changes in the original files, which is very important during
+development. If you don't set it to ``true`` you have to delete the
+directory to clear the cache. This gives faster performance, however
+should only be used in production, because of its inconvenience during
+development.
+
+You can also use one of the ``Doctrine\Common\Cache\Cache`` cache
+implementations to cache the annotations:
+
+.. code-block:: php
+
+    use Doctrine\Common\Annotations\AnnotationReader;
+    use Doctrine\Common\Annotations\CachedReader;
+    use Doctrine\Common\Cache\ApcCache;
+
+    $reader = new CachedReader(
+        new AnnotationReader(),
+        new ApcCache(),
+        $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);

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

@@ -0,0 +1,399 @@
+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 can implement the
+``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` interface
+and then declare a constructor with regular parameter names that are matched
+from the named arguments in the annotation syntax.
+
+.. code-block:: php
+
+    namespace MyCompany\Annotations;
+
+    use Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation;
+
+    /** @Annotation */
+    class Bar implements NamedArgumentConstructorAnnotation
+    {
+        private $foo;
+
+        public function __construct(string $foo)
+        {
+            $this->foo = $foo;
+        }
+    }
+
+    /** Useable with @Bar(foo="baz") */
+
+In combination with PHP 8's constructor property promotion feature
+you can simplify this to:
+
+.. code-block:: php
+
+    namespace MyCompany\Annotations;
+
+    use Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation;
+
+    /** @Annotation */
+    class Bar implements NamedArgumentConstructorAnnotation
+    {
+        public function __construct(private string $foo) {}
+    }
+
+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
+-  ``ALL`` Allowed in class, property and method 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);

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

@@ -0,0 +1,100 @@
+Introduction
+============
+
+Doctrine Annotations allows to implement custom annotation
+functionality for PHP classes.
+
+.. 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
+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.
+
+:ref:`Read more about handling annotations. <annotations>`
+
+IDE Support
+-----------
+
+Some IDEs already provide support for annotations:
+
+- Eclipse via the `Symfony2 Plugin <http://symfony.dubture.com/>`_
+- 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

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

@@ -0,0 +1,59 @@
+<?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;
+
+    /**
+     * @throws InvalidArgumentException
+     *
+     * @phpstan-param array{literal?: mixed[], value: list<scalar>} $values
+     */
+    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;
+
+    /**
+     * @throws RuntimeException
+     *
+     * @phpstan-param array{value: string|list<string>} $values
+     */
+    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/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
+{
+}

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

@@ -0,0 +1,99 @@
+<?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_ALL        = 15;
+
+    /** @var array<string, int> */
+    private static $map = [
+        'ALL'        => self::TARGET_ALL,
+        'CLASS'      => self::TARGET_CLASS,
+        'METHOD'     => self::TARGET_METHOD,
+        'PROPERTY'   => self::TARGET_PROPERTY,
+        'ANNOTATION' => self::TARGET_ANNOTATION,
+    ];
+
+    /** @phpstan-var list<string> */
+    public $value;
+
+    /**
+     * Targets as bitmask.
+     *
+     * @var int
+     */
+    public $targets;
+
+    /**
+     * Literal target declaration.
+     *
+     * @var string
+     */
+    public $literal;
+
+    /**
+     * @throws InvalidArgumentException
+     *
+     * @phpstan-param array{value?: string|list<string>} $values
+     */
+    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);
+    }
+}

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

@@ -0,0 +1,171 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use Exception;
+
+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)
+    {
+        return new self('[Creation Error] ' . $message);
+    }
+
+    /**
+     * 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 object|class-string $given
+     *
+     * @return AnnotationException
+     *
+     * @phpstan-param list<string>        $available
+     */
+    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.'
+        );
+    }
+}

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

@@ -0,0 +1,342 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use Doctrine\Common\Annotations\Annotation\IgnoreAnnotation;
+use Doctrine\Common\Annotations\Annotation\Target;
+use ReflectionClass;
+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.
+     *
+     * @var array<string, array<string, class-string>>
+     */
+    private $imports = [];
+
+    /**
+     * In-memory cache mechanism to store ignored annotations per class.
+     *
+     * @var 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->getClassImports($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;
+    }
+
+    /**
+     * Returns the ignored annotations for the given class.
+     *
+     * @return array<string, true>
+     */
+    private function getIgnoredAnnotationNames(ReflectionClass $class)
+    {
+        $name = $class->getName();
+        if (isset($this->ignoredAnnotationNames[$name])) {
+            return $this->ignoredAnnotationNames[$name];
+        }
+
+        $this->collectParsingMetadata($class);
+
+        return $this->ignoredAnnotationNames[$name];
+    }
+
+    /**
+     * Retrieves imports.
+     *
+     * @return array<string, class-string>
+     */
+    private function getClassImports(ReflectionClass $class)
+    {
+        $name = $class->getName();
+        if (isset($this->imports[$name])) {
+            return $this->imports[$name];
+        }
+
+        $this->collectParsingMetadata($class);
+
+        return $this->imports[$name];
+    }
+
+    /**
+     * Retrieves imports for methods.
+     *
+     * @return array<string, class-string>
+     */
+    private function getMethodImports(ReflectionMethod $method)
+    {
+        $class        = $method->getDeclaringClass();
+        $classImports = $this->getClassImports($class);
+
+        $traitImports = [];
+
+        foreach ($class->getTraits() as $trait) {
+            if (
+                ! $trait->hasMethod($method->getName())
+                || $trait->getFileName() !== $method->getFileName()
+            ) {
+                continue;
+            }
+
+            $traitImports = array_merge($traitImports, $this->phpParser->parseClass($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->getClassImports($class);
+
+        $traitImports = [];
+
+        foreach ($class->getTraits() as $trait) {
+            if (! $trait->hasProperty($property->getName())) {
+                continue;
+            }
+
+            $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
+        }
+
+        return array_merge($classImports, $traitImports);
+    }
+
+    /**
+     * Collects parsing metadata for a given class.
+     */
+    private function collectParsingMetadata(ReflectionClass $class)
+    {
+        $ignoredAnnotationNames = self::$globalIgnoredNames;
+        $annotations            = $this->preParser->parse($class->getDocComment(), 'class ' . $class->name);
+
+        foreach ($annotations as $annotation) {
+            if (! ($annotation instanceof IgnoreAnnotation)) {
+                continue;
+            }
+
+            foreach ($annotation->names as $annot) {
+                $ignoredAnnotationNames[$annot] = true;
+            }
+        }
+
+        $name = $class->getName();
+
+        $this->imports[$name] = array_merge(
+            self::$globalImports,
+            $this->phpParser->parseClass($class),
+            [
+                '__NAMESPACE__' => $class->getNamespaceName(),
+                'self' => $name,
+            ]
+        );
+
+        $this->ignoredAnnotationNames[$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;
+    }
+}

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

@@ -0,0 +1,264 @@
+<?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.
+ */
+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;
+    }
+}

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

@@ -0,0 +1,129 @@
+<?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.
+ */
+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, int> */
+    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, int> */
+    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;
+    }
+}

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1387 - 0
vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php


+ 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\CachedReader} 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 = [];
+    }
+}

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

@@ -0,0 +1,165 @@
+<?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,
+    ];
+
+    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 PhpStan = [
+        'extends' => true,
+        'implements' => true,
+        'template' => true,
+        'use' => 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::PhpStan;
+
+    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, $annotation)
+    {
+        return $this->delegate->getClassAnnotation($class, $annotation);
+    }
+
+    /**
+     * {@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, $annotation)
+    {
+        return $this->delegate->getMethodAnnotation($method, $annotation);
+    }
+
+    /**
+     * {@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, $annotation)
+    {
+        return $this->delegate->getPropertyAnnotation($property, $annotation);
+    }
+
+    /**
+     * 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);
+    }
+}

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

@@ -0,0 +1,11 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+/**
+ * Marker interface for PHP7/PHP8 compatible support
+ * for named arguments (and constructor property promotion).
+ */
+interface NamedArgumentConstructorAnnotation
+{
+}

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

@@ -0,0 +1,77 @@
+<?php
+
+namespace Doctrine\Common\Annotations;
+
+use ReflectionClass;
+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.
+     *
+     * @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)
+    {
+        if (method_exists($class, 'getUseStatements')) {
+            return $class->getUseStatements();
+        }
+
+        $filename = $class->getFileName();
+
+        if ($filename === false) {
+            return [];
+        }
+
+        $content = $this->getFileContent($filename, $class->getStartLine());
+
+        if ($content === null) {
+            return [];
+        }
+
+        $namespace = preg_quote($class->getNamespaceName());
+        $content   = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content);
+        $tokenizer = new TokenParser('<?php ' . $content);
+
+        return $tokenizer->parseUseStatements($class->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;
+    }
+}

+ 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;
+    }
+}

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

@@ -0,0 +1,208 @@
+<?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();
+    }
+}

+ 4 - 0
vendor/doctrine/annotations/phpbench.json.dist

@@ -0,0 +1,4 @@
+{
+    "bootstrap": "tests/Doctrine/Performance/Common/bootstrap.php",
+    "path": "tests/Doctrine/Performance/Common/Annotations"
+}

+ 154 - 0
vendor/doctrine/annotations/phpcs.xml.dist

@@ -0,0 +1,154 @@
+<?xml version="1.0"?>
+<ruleset
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd"
+>
+    <arg name="basepath" value="."/>
+    <arg name="extensions" value="php"/>
+    <arg name="parallel" value="80"/>
+    <arg name="cache" value=".phpcs-cache"/>
+    <arg name="colors"/>
+
+    <!-- Show progress of the run and show sniff names -->
+    <arg value="ps"/>
+
+    <file>lib</file>
+    <file>tests</file>
+
+    <rule ref="Doctrine">
+        <exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint"/>
+        <exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint"/>
+        <exclude name="SlevomatCodingStandard.TypeHints.DeclareStrictTypes"/>
+        <exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming"/>
+        <exclude name="SlevomatCodingStandard.Classes.SuperfluousExceptionNaming"/>
+        <exclude name="SlevomatCodingStandard.Classes.SuperfluousTraitNaming.SuperfluousSuffix"/>
+        <exclude name="SlevomatCodingStandard.Classes.DisallowLateStaticBindingForConstants.DisallowedLateStaticBindingForConstant"/>
+        <exclude name="SlevomatCodingStandard.ControlStructures.ControlStructureSpacing.IncorrectLinesCountAfterLastControlStructure"/>
+
+        <exclude name="PSR2.Methods.MethodDeclaration.Underscore"/>
+        <!-- https://github.com/slevomat/coding-standard/issues/867 -->
+        <exclude name="SlevomatCodingStandard.ControlStructures.JumpStatementsSpacing.IncorrectLinesCountAfterLastControlStructure"/>
+        <!-- See https://github.com/squizlabs/PHP_CodeSniffer/issues/2937 -->
+        <exclude name="Squiz.Arrays.ArrayDeclaration.ValueNoNewline"/>
+        <exclude name="Squiz.NamingConventions.ValidVariableName.PublicHasUnderscore"/>
+    </rule>
+
+    <!-- Disable the rules that will require PHP 7.4 -->
+    <rule ref="SlevomatCodingStandard.TypeHints.PropertyTypeHint">
+        <properties>
+            <property name="enableNativeTypeHint" value="false"/>
+        </properties>
+    </rule>
+
+    <rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps">
+        <exclude-pattern>*/lib/Doctrine/Common/Annotations/DocParser.php</exclude-pattern>
+    </rule>
+    <rule ref="Squiz.Classes.ValidClassName.NotCamelCaps">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/ClassNoNamespaceNoComment.php</exclude-pattern>
+    </rule>
+
+    <rule ref="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/*</exclude-pattern>
+    </rule>
+    <rule ref="SlevomatCodingStandard.Commenting.ForbiddenAnnotations.AnnotationForbidden">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/*</exclude-pattern>
+    </rule>
+    <rule ref="SlevomatCodingStandard.Namespaces.UseDoesNotStartWithBackslash.UseStartsWithBackslash">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/*</exclude-pattern>
+    </rule>
+    <rule ref="PSR12.Files.ImportStatement.LeadingSlash">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/*</exclude-pattern>
+    </rule>
+    <rule ref="SlevomatCodingStandard.TypeHints.LongTypeHints.UsedLongTypeHint">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/*</exclude-pattern>
+    </rule>
+    <rule ref="SlevomatCodingStandard.Namespaces.UseFromSameNamespace.UseFromSameNamespace">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/*</exclude-pattern>
+    </rule>
+    <rule ref="SlevomatCodingStandard.Namespaces.UseDoesNotStartWithBackslash.UseStartsWithBackslash">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/*</exclude-pattern>
+    </rule>
+    <rule ref="SlevomatCodingStandard.Namespaces.UnusedUses.UnusedUse">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/*</exclude-pattern>
+    </rule>
+    <rule ref="SlevomatCodingStandard.Namespaces.UseSpacing.IncorrectLinesCountBetweenSameTypeOfUse">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/*</exclude-pattern>
+    </rule>
+    <rule ref="SlevomatCodingStandard.Namespaces.MultipleUsesPerLine.MultipleUsesPerLine">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/*</exclude-pattern>
+    </rule>
+
+    <!-- https://github.com/slevomat/coding-standard/issues/1066 -->
+    <rule ref="SlevomatCodingStandard.PHP.UselessParentheses">
+        <exclude-pattern>*/lib/Doctrine/Common/Annotations/DocParser.php</exclude-pattern>
+    </rule>
+
+    <rule ref="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFullyQualifiedName">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/PhpParserTest.php</exclude-pattern>
+    </rule>
+
+    <!-- It is easier to understand tests that involve annotations if you can
+        declare several dummy classes with annotations in the same file -->
+    <rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses">
+        <exclude-pattern>*/tests/*</exclude-pattern>
+    </rule>
+    <rule ref="Squiz.Classes.ClassFileName.NoMatch">
+        <exclude-pattern>*/tests/*</exclude-pattern>
+    </rule>
+
+    <rule ref="PSR1.Files.SideEffects.FoundWithSymbols">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Ticket/DCOM58Test.php</exclude-pattern>
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/AbstractReaderTest.php</exclude-pattern>
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/PhpParserTest.php</exclude-pattern>
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/NamespaceWithClosureDeclaration.php</exclude-pattern>
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/ClassWithRequire.php</exclude-pattern>
+    </rule>
+
+    <!-- these classes have unused properties, and is unused in a benchmark for the parser -->
+    <rule ref="SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedProperty">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/SingleClassLOC1000.php</exclude-pattern>
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/NamespacedSingleClassLOC1000.php</exclude-pattern>
+    </rule>
+
+    <rule ref="SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedMethod">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/Controller.php</exclude-pattern>
+    </rule>
+
+    <!-- these classes do not have a namespace on purpose -->
+    <rule ref="PSR1.Classes.ClassDeclaration.MissingNamespace">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/SingleClassLOC1000.php</exclude-pattern>
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/NonNamespacedClass.php</exclude-pattern>
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Ticket/DCOM58Entity.php</exclude-pattern>
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/ClassNoNamespaceNoComment.php</exclude-pattern>
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/TopLevelAnnotation.php</exclude-pattern>
+    </rule>
+
+    <rule ref="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingAnyTypeHint">
+        <!-- there is a class property with an empty var annotation on purpose -->
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/AbstractReaderTest.php</exclude-pattern>
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/ClassWithValidAnnotationTarget.php</exclude-pattern>
+    </rule>
+
+    <rule ref="SlevomatCodingStandard.Namespaces.DisallowGroupUse.DisallowedGroupUse">
+        <!-- The name of the file alone explains this -->
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/GroupUseStatement.php</exclude-pattern>
+    </rule>
+    <rule ref="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingTraversableTypeHintSpecification">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/AnnotationWithVarType.php</exclude-pattern>
+    </rule>
+
+    <rule ref="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming.SuperfluousSuffix">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/EmptyInterface.php</exclude-pattern>
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/InterfaceThatExtendsAnInterface.php</exclude-pattern>
+    </rule>
+
+    <rule ref="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming.SuperfluousPrefix">
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/InterfaceThatExtendsAnInterface.php</exclude-pattern>
+        <exclude-pattern>*/tests/Doctrine/Tests/Common/Annotations/Fixtures/InterfaceWithConstants.php</exclude-pattern>
+    </rule>
+
+    <rule ref="Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase">
+        <!-- Usage of mixed case constants seems pretty deliberate here -->
+        <exclude-pattern>*/lib/Doctrine/Common/Annotations/ImplicitlyIgnoredAnnotationNames.php</exclude-pattern>
+    </rule>
+</ruleset>

+ 13 - 0
vendor/doctrine/annotations/phpstan.neon

@@ -0,0 +1,13 @@
+parameters:
+    excludes_analyse:
+        - %currentWorkingDirectory%/tests/*/Fixtures/*
+        - %currentWorkingDirectory%/tests/Doctrine/Tests/Common/Annotations/ReservedKeywordsClasses.php
+        - %currentWorkingDirectory%/tests/Doctrine/Tests/Common/Annotations/Ticket/DCOM58Entity.php
+        - %currentWorkingDirectory%/tests/Doctrine/Tests/DoctrineTestCase.php
+    polluteScopeWithLoopInitialAssignments: true
+    ignoreErrors:
+        - '#Instantiated class Doctrine_Tests_Common_Annotations_Fixtures_ClassNoNamespaceNoComment not found#'
+        - '#Property Doctrine\\Tests\\Common\\Annotations\\DummyClassNonAnnotationProblem::\$foo has unknown class#'
+
+        # That tag is empty on purpose
+        - '#PHPDoc tag @var has invalid value \(\)\: Unexpected token "\*/", expected type at offset 9#'

+ 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: [![Build Status](https://travis-ci.org/doctrine/lexer.svg?branch=master)](https://travis-ci.org/doctrine/lexer)
+
+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

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

@@ -0,0 +1,41 @@
+{
+    "name": "doctrine/lexer",
+    "type": "library",
+    "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
+    "keywords": [
+        "php",
+        "parser",
+        "lexer",
+        "annotations",
+        "docblock"
+    ],
+    "homepage": "https://www.doctrine-project.org/projects/lexer.html",
+    "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"}
+    ],
+    "require": {
+        "php": "^7.2 || ^8.0"
+    },
+    "require-dev": {
+        "doctrine/coding-standard": "^6.0",
+        "phpstan/phpstan": "^0.11.8",
+        "phpunit/phpunit": "^8.2"
+    },
+    "autoload": {
+        "psr-4": { "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" }
+    },
+    "autoload-dev": {
+        "psr-4": { "Doctrine\\Tests\\": "tests/Doctrine" }
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.2.x-dev"
+        }
+    },
+    "config": {
+        "sort-packages": true
+    }
+}

+ 328 - 0
vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php

@@ -0,0 +1,328 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\Common\Lexer;
+
+use ReflectionClass;
+use const PREG_SPLIT_DELIM_CAPTURE;
+use const PREG_SPLIT_NO_EMPTY;
+use const PREG_SPLIT_OFFSET_CAPTURE;
+use function implode;
+use function in_array;
+use function preg_split;
+use function sprintf;
+use function substr;
+
+/**
+ * Base class for writing simple lexers, i.e. for creating small DSLs.
+ */
+abstract class AbstractLexer
+{
+    /**
+     * Lexer original input string.
+     *
+     * @var string
+     */
+    private $input;
+
+    /**
+     * Array of scanned tokens.
+     *
+     * Each token is an associative array containing three items:
+     *  - 'value'    : the string value of the token in the input string
+     *  - 'type'     : the type of the token (identifier, numeric, string, input
+     *                 parameter, none)
+     *  - 'position' : the position of the token in the input string
+     *
+     * @var array
+     */
+    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 array|null
+     */
+    public $lookahead;
+
+    /**
+     * The last matched/seen token.
+     *
+     * @var array|null
+     */
+    public $token;
+
+    /**
+     * Composed regex for input parsing.
+     *
+     * @var string
+     */
+    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 int|string $token
+     *
+     * @return bool
+     */
+    public function isNextToken($token)
+    {
+        return $this->lookahead !== null && $this->lookahead['type'] === $token;
+    }
+
+    /**
+     * Checks whether any of the given tokens matches the current lookahead.
+     *
+     * @param array $tokens
+     *
+     * @return bool
+     */
+    public function isNextTokenAny(array $tokens)
+    {
+        return $this->lookahead !== null && in_array($this->lookahead['type'], $tokens, true);
+    }
+
+    /**
+     * 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 string $type The token type to skip until.
+     *
+     * @return void
+     */
+    public function skipUntil($type)
+    {
+        while ($this->lookahead !== null && $this->lookahead['type'] !== $type) {
+            $this->moveNext();
+        }
+    }
+
+    /**
+     * Checks if given value is identical to the given token.
+     *
+     * @param mixed      $value
+     * @param int|string $token
+     *
+     * @return bool
+     */
+    public function isA($value, $token)
+    {
+        return $this->getType($value) === $token;
+    }
+
+    /**
+     * Moves the lookahead token forward.
+     *
+     * @return array|null The next token or NULL if there are no more tokens ahead.
+     */
+    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 array|null The next token or NULL if there are no more tokens ahead.
+     */
+    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[] = [
+                'value' => $match[0],
+                'type'  => $type,
+                'position' => $match[1],
+            ];
+        }
+    }
+
+    /**
+     * Gets the literal for a given token.
+     *
+     * @param int|string $token
+     *
+     * @return int|string
+     */
+    public function getLiteral($token)
+    {
+        $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 array
+     */
+    abstract protected function getCatchablePatterns();
+
+    /**
+     * Lexical non-catchable patterns.
+     *
+     * @return array
+     */
+    abstract protected function getNonCatchablePatterns();
+
+    /**
+     * Retrieve token type. Also processes the token value if necessary.
+     *
+     * @param string $value
+     *
+     * @return int|string|null
+     */
+    abstract protected function getType(&$value);
+}

+ 79 - 0
vendor/symfony/finder/CHANGELOG.md

@@ -0,0 +1,79 @@
+CHANGELOG
+=========
+
+5.0.0
+-----
+
+ * added `$useNaturalSort` argument to `Finder::sortByName()`
+
+4.3.0
+-----
+
+ * added Finder::ignoreVCSIgnored() to ignore files based on rules listed in .gitignore
+
+4.2.0
+-----
+
+ * added $useNaturalSort option to Finder::sortByName() method
+ * the `Finder::sortByName()` method will have a new `$useNaturalSort`
+   argument in version 5.0, not defining it is deprecated
+ * added `Finder::reverseSorting()` to reverse the sorting
+
+4.0.0
+-----
+
+ * removed `ExceptionInterface`
+ * removed `Symfony\Component\Finder\Iterator\FilterIterator`
+
+3.4.0
+-----
+
+ * deprecated `Symfony\Component\Finder\Iterator\FilterIterator`
+ * added Finder::hasResults() method to check if any results were found
+
+3.3.0
+-----
+
+ * added double-star matching to Glob::toRegex()
+
+3.0.0
+-----
+
+ * removed deprecated classes
+
+2.8.0
+-----
+
+ * deprecated adapters and related classes
+
+2.5.0
+-----
+ * added support for GLOB_BRACE in the paths passed to Finder::in()
+
+2.3.0
+-----
+
+ * added a way to ignore unreadable directories (via Finder::ignoreUnreadableDirs())
+ * unified the way subfolders that are not executable are handled by always throwing an AccessDeniedException exception
+
+2.2.0
+-----
+
+ * added Finder::path() and Finder::notPath() methods
+ * added finder adapters to improve performance on specific platforms
+ * added support for wildcard characters (glob patterns) in the paths passed
+   to Finder::in()
+
+2.1.0
+-----
+
+ * added Finder::sortByAccessedTime(), Finder::sortByChangedTime(), and
+   Finder::sortByModifiedTime()
+ * added Countable to Finder
+ * added support for an array of directories as an argument to
+   Finder::exclude()
+ * added searching based on the file content via Finder::contains() and
+   Finder::notContains()
+ * added support for the != operator in the Comparator
+ * [BC BREAK] filter expressions (used for file name and content) are no more
+   considered as regexps but glob patterns when they are enclosed in '*' or '?'

+ 91 - 0
vendor/symfony/finder/Comparator/Comparator.php

@@ -0,0 +1,91 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Comparator;
+
+/**
+ * Comparator.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class Comparator
+{
+    private $target;
+    private $operator = '==';
+
+    /**
+     * Gets the target value.
+     *
+     * @return string The target value
+     */
+    public function getTarget()
+    {
+        return $this->target;
+    }
+
+    public function setTarget(string $target)
+    {
+        $this->target = $target;
+    }
+
+    /**
+     * Gets the comparison operator.
+     *
+     * @return string The operator
+     */
+    public function getOperator()
+    {
+        return $this->operator;
+    }
+
+    /**
+     * Sets the comparison operator.
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function setOperator(string $operator)
+    {
+        if ('' === $operator) {
+            $operator = '==';
+        }
+
+        if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) {
+            throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator));
+        }
+
+        $this->operator = $operator;
+    }
+
+    /**
+     * Tests against the target.
+     *
+     * @param mixed $test A test value
+     *
+     * @return bool
+     */
+    public function test($test)
+    {
+        switch ($this->operator) {
+            case '>':
+                return $test > $this->target;
+            case '>=':
+                return $test >= $this->target;
+            case '<':
+                return $test < $this->target;
+            case '<=':
+                return $test <= $this->target;
+            case '!=':
+                return $test != $this->target;
+        }
+
+        return $test == $this->target;
+    }
+}

+ 51 - 0
vendor/symfony/finder/Comparator/DateComparator.php

@@ -0,0 +1,51 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Comparator;
+
+/**
+ * DateCompare compiles date comparisons.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class DateComparator extends Comparator
+{
+    /**
+     * @param string $test A comparison string
+     *
+     * @throws \InvalidArgumentException If the test is not understood
+     */
+    public function __construct(string $test)
+    {
+        if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) {
+            throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test));
+        }
+
+        try {
+            $date = new \DateTime($matches[2]);
+            $target = $date->format('U');
+        } catch (\Exception $e) {
+            throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2]));
+        }
+
+        $operator = isset($matches[1]) ? $matches[1] : '==';
+        if ('since' === $operator || 'after' === $operator) {
+            $operator = '>';
+        }
+
+        if ('until' === $operator || 'before' === $operator) {
+            $operator = '<';
+        }
+
+        $this->setOperator($operator);
+        $this->setTarget($target);
+    }
+}

+ 79 - 0
vendor/symfony/finder/Comparator/NumberComparator.php

@@ -0,0 +1,79 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Comparator;
+
+/**
+ * NumberComparator compiles a simple comparison to an anonymous
+ * subroutine, which you can call with a value to be tested again.
+ *
+ * Now this would be very pointless, if NumberCompare didn't understand
+ * magnitudes.
+ *
+ * The target value may use magnitudes of kilobytes (k, ki),
+ * megabytes (m, mi), or gigabytes (g, gi).  Those suffixed
+ * with an i use the appropriate 2**n version in accordance with the
+ * IEC standard: http://physics.nist.gov/cuu/Units/binary.html
+ *
+ * Based on the Perl Number::Compare module.
+ *
+ * @author    Fabien Potencier <fabien@symfony.com> PHP port
+ * @author    Richard Clamp <richardc@unixbeard.net> Perl version
+ * @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
+ * @copyright 2002 Richard Clamp <richardc@unixbeard.net>
+ *
+ * @see http://physics.nist.gov/cuu/Units/binary.html
+ */
+class NumberComparator extends Comparator
+{
+    /**
+     * @param string|int $test A comparison string or an integer
+     *
+     * @throws \InvalidArgumentException If the test is not understood
+     */
+    public function __construct(?string $test)
+    {
+        if (!preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) {
+            throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test));
+        }
+
+        $target = $matches[2];
+        if (!is_numeric($target)) {
+            throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target));
+        }
+        if (isset($matches[3])) {
+            // magnitude
+            switch (strtolower($matches[3])) {
+                case 'k':
+                    $target *= 1000;
+                    break;
+                case 'ki':
+                    $target *= 1024;
+                    break;
+                case 'm':
+                    $target *= 1000000;
+                    break;
+                case 'mi':
+                    $target *= 1024 * 1024;
+                    break;
+                case 'g':
+                    $target *= 1000000000;
+                    break;
+                case 'gi':
+                    $target *= 1024 * 1024 * 1024;
+                    break;
+            }
+        }
+
+        $this->setTarget($target);
+        $this->setOperator(isset($matches[1]) ? $matches[1] : '==');
+    }
+}

+ 19 - 0
vendor/symfony/finder/Exception/AccessDeniedException.php

@@ -0,0 +1,19 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Exception;
+
+/**
+ * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
+ */
+class AccessDeniedException extends \UnexpectedValueException
+{
+}

+ 19 - 0
vendor/symfony/finder/Exception/DirectoryNotFoundException.php

@@ -0,0 +1,19 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Exception;
+
+/**
+ * @author Andreas Erhard <andreas.erhard@i-med.ac.at>
+ */
+class DirectoryNotFoundException extends \InvalidArgumentException
+{
+}

+ 797 - 0
vendor/symfony/finder/Finder.php

@@ -0,0 +1,797 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder;
+
+use Symfony\Component\Finder\Comparator\DateComparator;
+use Symfony\Component\Finder\Comparator\NumberComparator;
+use Symfony\Component\Finder\Exception\DirectoryNotFoundException;
+use Symfony\Component\Finder\Iterator\CustomFilterIterator;
+use Symfony\Component\Finder\Iterator\DateRangeFilterIterator;
+use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator;
+use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator;
+use Symfony\Component\Finder\Iterator\FilecontentFilterIterator;
+use Symfony\Component\Finder\Iterator\FilenameFilterIterator;
+use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator;
+use Symfony\Component\Finder\Iterator\SortableIterator;
+
+/**
+ * Finder allows to build rules to find files and directories.
+ *
+ * It is a thin wrapper around several specialized iterator classes.
+ *
+ * All rules may be invoked several times.
+ *
+ * All methods return the current Finder object to allow chaining:
+ *
+ *     $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class Finder implements \IteratorAggregate, \Countable
+{
+    const IGNORE_VCS_FILES = 1;
+    const IGNORE_DOT_FILES = 2;
+    const IGNORE_VCS_IGNORED_FILES = 4;
+
+    private $mode = 0;
+    private $names = [];
+    private $notNames = [];
+    private $exclude = [];
+    private $filters = [];
+    private $depths = [];
+    private $sizes = [];
+    private $followLinks = false;
+    private $reverseSorting = false;
+    private $sort = false;
+    private $ignore = 0;
+    private $dirs = [];
+    private $dates = [];
+    private $iterators = [];
+    private $contains = [];
+    private $notContains = [];
+    private $paths = [];
+    private $notPaths = [];
+    private $ignoreUnreadableDirs = false;
+
+    private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'];
+
+    public function __construct()
+    {
+        $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
+    }
+
+    /**
+     * Creates a new Finder.
+     *
+     * @return static
+     */
+    public static function create()
+    {
+        return new static();
+    }
+
+    /**
+     * Restricts the matching to directories only.
+     *
+     * @return $this
+     */
+    public function directories()
+    {
+        $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;
+
+        return $this;
+    }
+
+    /**
+     * Restricts the matching to files only.
+     *
+     * @return $this
+     */
+    public function files()
+    {
+        $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;
+
+        return $this;
+    }
+
+    /**
+     * Adds tests for the directory depth.
+     *
+     * Usage:
+     *
+     *     $finder->depth('> 1') // the Finder will start matching at level 1.
+     *     $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
+     *     $finder->depth(['>= 1', '< 3'])
+     *
+     * @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels
+     *
+     * @return $this
+     *
+     * @see DepthRangeFilterIterator
+     * @see NumberComparator
+     */
+    public function depth($levels)
+    {
+        foreach ((array) $levels as $level) {
+            $this->depths[] = new Comparator\NumberComparator($level);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Adds tests for file dates (last modified).
+     *
+     * The date must be something that strtotime() is able to parse:
+     *
+     *     $finder->date('since yesterday');
+     *     $finder->date('until 2 days ago');
+     *     $finder->date('> now - 2 hours');
+     *     $finder->date('>= 2005-10-15');
+     *     $finder->date(['>= 2005-10-15', '<= 2006-05-27']);
+     *
+     * @param string|string[] $dates A date range string or an array of date ranges
+     *
+     * @return $this
+     *
+     * @see strtotime
+     * @see DateRangeFilterIterator
+     * @see DateComparator
+     */
+    public function date($dates)
+    {
+        foreach ((array) $dates as $date) {
+            $this->dates[] = new Comparator\DateComparator($date);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Adds rules that files must match.
+     *
+     * You can use patterns (delimited with / sign), globs or simple strings.
+     *
+     *     $finder->name('*.php')
+     *     $finder->name('/\.php$/') // same as above
+     *     $finder->name('test.php')
+     *     $finder->name(['test.py', 'test.php'])
+     *
+     * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
+     *
+     * @return $this
+     *
+     * @see FilenameFilterIterator
+     */
+    public function name($patterns)
+    {
+        $this->names = array_merge($this->names, (array) $patterns);
+
+        return $this;
+    }
+
+    /**
+     * Adds rules that files must not match.
+     *
+     * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
+     *
+     * @return $this
+     *
+     * @see FilenameFilterIterator
+     */
+    public function notName($patterns)
+    {
+        $this->notNames = array_merge($this->notNames, (array) $patterns);
+
+        return $this;
+    }
+
+    /**
+     * Adds tests that file contents must match.
+     *
+     * Strings or PCRE patterns can be used:
+     *
+     *     $finder->contains('Lorem ipsum')
+     *     $finder->contains('/Lorem ipsum/i')
+     *     $finder->contains(['dolor', '/ipsum/i'])
+     *
+     * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
+     *
+     * @return $this
+     *
+     * @see FilecontentFilterIterator
+     */
+    public function contains($patterns)
+    {
+        $this->contains = array_merge($this->contains, (array) $patterns);
+
+        return $this;
+    }
+
+    /**
+     * Adds tests that file contents must not match.
+     *
+     * Strings or PCRE patterns can be used:
+     *
+     *     $finder->notContains('Lorem ipsum')
+     *     $finder->notContains('/Lorem ipsum/i')
+     *     $finder->notContains(['lorem', '/dolor/i'])
+     *
+     * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
+     *
+     * @return $this
+     *
+     * @see FilecontentFilterIterator
+     */
+    public function notContains($patterns)
+    {
+        $this->notContains = array_merge($this->notContains, (array) $patterns);
+
+        return $this;
+    }
+
+    /**
+     * Adds rules that filenames must match.
+     *
+     * You can use patterns (delimited with / sign) or simple strings.
+     *
+     *     $finder->path('some/special/dir')
+     *     $finder->path('/some\/special\/dir/') // same as above
+     *     $finder->path(['some dir', 'another/dir'])
+     *
+     * Use only / as dirname separator.
+     *
+     * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
+     *
+     * @return $this
+     *
+     * @see FilenameFilterIterator
+     */
+    public function path($patterns)
+    {
+        $this->paths = array_merge($this->paths, (array) $patterns);
+
+        return $this;
+    }
+
+    /**
+     * Adds rules that filenames must not match.
+     *
+     * You can use patterns (delimited with / sign) or simple strings.
+     *
+     *     $finder->notPath('some/special/dir')
+     *     $finder->notPath('/some\/special\/dir/') // same as above
+     *     $finder->notPath(['some/file.txt', 'another/file.log'])
+     *
+     * Use only / as dirname separator.
+     *
+     * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
+     *
+     * @return $this
+     *
+     * @see FilenameFilterIterator
+     */
+    public function notPath($patterns)
+    {
+        $this->notPaths = array_merge($this->notPaths, (array) $patterns);
+
+        return $this;
+    }
+
+    /**
+     * Adds tests for file sizes.
+     *
+     *     $finder->size('> 10K');
+     *     $finder->size('<= 1Ki');
+     *     $finder->size(4);
+     *     $finder->size(['> 10K', '< 20K'])
+     *
+     * @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges
+     *
+     * @return $this
+     *
+     * @see SizeRangeFilterIterator
+     * @see NumberComparator
+     */
+    public function size($sizes)
+    {
+        foreach ((array) $sizes as $size) {
+            $this->sizes[] = new Comparator\NumberComparator($size);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Excludes directories.
+     *
+     * Directories passed as argument must be relative to the ones defined with the `in()` method. For example:
+     *
+     *     $finder->in(__DIR__)->exclude('ruby');
+     *
+     * @param string|array $dirs A directory path or an array of directories
+     *
+     * @return $this
+     *
+     * @see ExcludeDirectoryFilterIterator
+     */
+    public function exclude($dirs)
+    {
+        $this->exclude = array_merge($this->exclude, (array) $dirs);
+
+        return $this;
+    }
+
+    /**
+     * Excludes "hidden" directories and files (starting with a dot).
+     *
+     * This option is enabled by default.
+     *
+     * @return $this
+     *
+     * @see ExcludeDirectoryFilterIterator
+     */
+    public function ignoreDotFiles(bool $ignoreDotFiles)
+    {
+        if ($ignoreDotFiles) {
+            $this->ignore |= static::IGNORE_DOT_FILES;
+        } else {
+            $this->ignore &= ~static::IGNORE_DOT_FILES;
+        }
+
+        return $this;
+    }
+
+    /**
+     * Forces the finder to ignore version control directories.
+     *
+     * This option is enabled by default.
+     *
+     * @return $this
+     *
+     * @see ExcludeDirectoryFilterIterator
+     */
+    public function ignoreVCS(bool $ignoreVCS)
+    {
+        if ($ignoreVCS) {
+            $this->ignore |= static::IGNORE_VCS_FILES;
+        } else {
+            $this->ignore &= ~static::IGNORE_VCS_FILES;
+        }
+
+        return $this;
+    }
+
+    /**
+     * Forces Finder to obey .gitignore and ignore files based on rules listed there.
+     *
+     * This option is disabled by default.
+     *
+     * @return $this
+     */
+    public function ignoreVCSIgnored(bool $ignoreVCSIgnored)
+    {
+        if ($ignoreVCSIgnored) {
+            $this->ignore |= static::IGNORE_VCS_IGNORED_FILES;
+        } else {
+            $this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES;
+        }
+
+        return $this;
+    }
+
+    /**
+     * Adds VCS patterns.
+     *
+     * @see ignoreVCS()
+     *
+     * @param string|string[] $pattern VCS patterns to ignore
+     */
+    public static function addVCSPattern($pattern)
+    {
+        foreach ((array) $pattern as $p) {
+            self::$vcsPatterns[] = $p;
+        }
+
+        self::$vcsPatterns = array_unique(self::$vcsPatterns);
+    }
+
+    /**
+     * Sorts files and directories by an anonymous function.
+     *
+     * The anonymous function receives two \SplFileInfo instances to compare.
+     *
+     * This can be slow as all the matching files and directories must be retrieved for comparison.
+     *
+     * @return $this
+     *
+     * @see SortableIterator
+     */
+    public function sort(\Closure $closure)
+    {
+        $this->sort = $closure;
+
+        return $this;
+    }
+
+    /**
+     * Sorts files and directories by name.
+     *
+     * This can be slow as all the matching files and directories must be retrieved for comparison.
+     *
+     * @return $this
+     *
+     * @see SortableIterator
+     */
+    public function sortByName(bool $useNaturalSort = false)
+    {
+        $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME;
+
+        return $this;
+    }
+
+    /**
+     * Sorts files and directories by type (directories before files), then by name.
+     *
+     * This can be slow as all the matching files and directories must be retrieved for comparison.
+     *
+     * @return $this
+     *
+     * @see SortableIterator
+     */
+    public function sortByType()
+    {
+        $this->sort = Iterator\SortableIterator::SORT_BY_TYPE;
+
+        return $this;
+    }
+
+    /**
+     * Sorts files and directories by the last accessed time.
+     *
+     * This is the time that the file was last accessed, read or written to.
+     *
+     * This can be slow as all the matching files and directories must be retrieved for comparison.
+     *
+     * @return $this
+     *
+     * @see SortableIterator
+     */
+    public function sortByAccessedTime()
+    {
+        $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME;
+
+        return $this;
+    }
+
+    /**
+     * Reverses the sorting.
+     *
+     * @return $this
+     */
+    public function reverseSorting()
+    {
+        $this->reverseSorting = true;
+
+        return $this;
+    }
+
+    /**
+     * Sorts files and directories by the last inode changed time.
+     *
+     * This is the time that the inode information was last modified (permissions, owner, group or other metadata).
+     *
+     * On Windows, since inode is not available, changed time is actually the file creation time.
+     *
+     * This can be slow as all the matching files and directories must be retrieved for comparison.
+     *
+     * @return $this
+     *
+     * @see SortableIterator
+     */
+    public function sortByChangedTime()
+    {
+        $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME;
+
+        return $this;
+    }
+
+    /**
+     * Sorts files and directories by the last modified time.
+     *
+     * This is the last time the actual contents of the file were last modified.
+     *
+     * This can be slow as all the matching files and directories must be retrieved for comparison.
+     *
+     * @return $this
+     *
+     * @see SortableIterator
+     */
+    public function sortByModifiedTime()
+    {
+        $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME;
+
+        return $this;
+    }
+
+    /**
+     * Filters the iterator with an anonymous function.
+     *
+     * The anonymous function receives a \SplFileInfo and must return false
+     * to remove files.
+     *
+     * @return $this
+     *
+     * @see CustomFilterIterator
+     */
+    public function filter(\Closure $closure)
+    {
+        $this->filters[] = $closure;
+
+        return $this;
+    }
+
+    /**
+     * Forces the following of symlinks.
+     *
+     * @return $this
+     */
+    public function followLinks()
+    {
+        $this->followLinks = true;
+
+        return $this;
+    }
+
+    /**
+     * Tells finder to ignore unreadable directories.
+     *
+     * By default, scanning unreadable directories content throws an AccessDeniedException.
+     *
+     * @return $this
+     */
+    public function ignoreUnreadableDirs(bool $ignore = true)
+    {
+        $this->ignoreUnreadableDirs = $ignore;
+
+        return $this;
+    }
+
+    /**
+     * Searches files and directories which match defined rules.
+     *
+     * @param string|string[] $dirs A directory path or an array of directories
+     *
+     * @return $this
+     *
+     * @throws DirectoryNotFoundException if one of the directories does not exist
+     */
+    public function in($dirs)
+    {
+        $resolvedDirs = [];
+
+        foreach ((array) $dirs as $dir) {
+            if (is_dir($dir)) {
+                $resolvedDirs[] = $this->normalizeDir($dir);
+            } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) {
+                sort($glob);
+                $resolvedDirs = array_merge($resolvedDirs, array_map([$this, 'normalizeDir'], $glob));
+            } else {
+                throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir));
+            }
+        }
+
+        $this->dirs = array_merge($this->dirs, $resolvedDirs);
+
+        return $this;
+    }
+
+    /**
+     * Returns an Iterator for the current Finder configuration.
+     *
+     * This method implements the IteratorAggregate interface.
+     *
+     * @return \Iterator|SplFileInfo[] An iterator
+     *
+     * @throws \LogicException if the in() method has not been called
+     */
+    public function getIterator()
+    {
+        if (0 === \count($this->dirs) && 0 === \count($this->iterators)) {
+            throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
+        }
+
+        if (1 === \count($this->dirs) && 0 === \count($this->iterators)) {
+            return $this->searchInDirectory($this->dirs[0]);
+        }
+
+        $iterator = new \AppendIterator();
+        foreach ($this->dirs as $dir) {
+            $iterator->append($this->searchInDirectory($dir));
+        }
+
+        foreach ($this->iterators as $it) {
+            $iterator->append($it);
+        }
+
+        return $iterator;
+    }
+
+    /**
+     * Appends an existing set of files/directories to the finder.
+     *
+     * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
+     *
+     * @return $this
+     *
+     * @throws \InvalidArgumentException when the given argument is not iterable
+     */
+    public function append(iterable $iterator)
+    {
+        if ($iterator instanceof \IteratorAggregate) {
+            $this->iterators[] = $iterator->getIterator();
+        } elseif ($iterator instanceof \Iterator) {
+            $this->iterators[] = $iterator;
+        } elseif ($iterator instanceof \Traversable || \is_array($iterator)) {
+            $it = new \ArrayIterator();
+            foreach ($iterator as $file) {
+                $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file));
+            }
+            $this->iterators[] = $it;
+        } else {
+            throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
+        }
+
+        return $this;
+    }
+
+    /**
+     * Check if any results were found.
+     *
+     * @return bool
+     */
+    public function hasResults()
+    {
+        foreach ($this->getIterator() as $_) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Counts all the results collected by the iterators.
+     *
+     * @return int
+     */
+    public function count()
+    {
+        return iterator_count($this->getIterator());
+    }
+
+    private function searchInDirectory(string $dir): \Iterator
+    {
+        $exclude = $this->exclude;
+        $notPaths = $this->notPaths;
+
+        if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
+            $exclude = array_merge($exclude, self::$vcsPatterns);
+        }
+
+        if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
+            $notPaths[] = '#(^|/)\..+(/|$)#';
+        }
+
+        if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
+            $gitignoreFilePath = sprintf('%s/.gitignore', $dir);
+            if (!is_readable($gitignoreFilePath)) {
+                throw new \RuntimeException(sprintf('The "ignoreVCSIgnored" option cannot be used by the Finder as the "%s" file is not readable.', $gitignoreFilePath));
+            }
+            $notPaths = array_merge($notPaths, [Gitignore::toRegex(file_get_contents($gitignoreFilePath))]);
+        }
+
+        $minDepth = 0;
+        $maxDepth = \PHP_INT_MAX;
+
+        foreach ($this->depths as $comparator) {
+            switch ($comparator->getOperator()) {
+                case '>':
+                    $minDepth = $comparator->getTarget() + 1;
+                    break;
+                case '>=':
+                    $minDepth = $comparator->getTarget();
+                    break;
+                case '<':
+                    $maxDepth = $comparator->getTarget() - 1;
+                    break;
+                case '<=':
+                    $maxDepth = $comparator->getTarget();
+                    break;
+                default:
+                    $minDepth = $maxDepth = $comparator->getTarget();
+            }
+        }
+
+        $flags = \RecursiveDirectoryIterator::SKIP_DOTS;
+
+        if ($this->followLinks) {
+            $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
+        }
+
+        $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);
+
+        if ($exclude) {
+            $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude);
+        }
+
+        $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);
+
+        if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) {
+            $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth);
+        }
+
+        if ($this->mode) {
+            $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
+        }
+
+        if ($this->names || $this->notNames) {
+            $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames);
+        }
+
+        if ($this->contains || $this->notContains) {
+            $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
+        }
+
+        if ($this->sizes) {
+            $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes);
+        }
+
+        if ($this->dates) {
+            $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates);
+        }
+
+        if ($this->filters) {
+            $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
+        }
+
+        if ($this->paths || $notPaths) {
+            $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths);
+        }
+
+        if ($this->sort || $this->reverseSorting) {
+            $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting);
+            $iterator = $iteratorAggregate->getIterator();
+        }
+
+        return $iterator;
+    }
+
+    /**
+     * Normalizes given directory names by removing trailing slashes.
+     *
+     * Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper
+     */
+    private function normalizeDir(string $dir): string
+    {
+        if ('/' === $dir) {
+            return $dir;
+        }
+
+        $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR);
+
+        if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) {
+            $dir .= '/';
+        }
+
+        return $dir;
+    }
+}

+ 133 - 0
vendor/symfony/finder/Gitignore.php

@@ -0,0 +1,133 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder;
+
+/**
+ * Gitignore matches against text.
+ *
+ * @author Ahmed Abdou <mail@ahmd.io>
+ */
+class Gitignore
+{
+    /**
+     * Returns a regexp which is the equivalent of the gitignore pattern.
+     *
+     * @return string The regexp
+     */
+    public static function toRegex(string $gitignoreFileContent): string
+    {
+        $gitignoreFileContent = preg_replace('/^[^\\\r\n]*#.*/m', '', $gitignoreFileContent);
+        $gitignoreLines = preg_split('/\r\n|\r|\n/', $gitignoreFileContent);
+
+        $positives = [];
+        $negatives = [];
+        foreach ($gitignoreLines as $i => $line) {
+            $line = trim($line);
+            if ('' === $line) {
+                continue;
+            }
+
+            if (1 === preg_match('/^!/', $line)) {
+                $positives[$i] = null;
+                $negatives[$i] = self::getRegexFromGitignore(preg_replace('/^!(.*)/', '${1}', $line), true);
+
+                continue;
+            }
+            $negatives[$i] = null;
+            $positives[$i] = self::getRegexFromGitignore($line);
+        }
+
+        $index = 0;
+        $patterns = [];
+        foreach ($positives as $pattern) {
+            if (null === $pattern) {
+                continue;
+            }
+
+            $negativesAfter = array_filter(\array_slice($negatives, ++$index));
+            if ([] !== $negativesAfter) {
+                $pattern .= sprintf('(?<!%s)', implode('|', $negativesAfter));
+            }
+
+            $patterns[] = $pattern;
+        }
+
+        return sprintf('/^((%s))$/', implode(')|(', $patterns));
+    }
+
+    private static function getRegexFromGitignore(string $gitignorePattern, bool $negative = false): string
+    {
+        $regex = '';
+        $isRelativePath = false;
+        // If there is a separator at the beginning or middle (or both) of the pattern, then the pattern is relative to the directory level of the particular .gitignore file itself
+        $slashPosition = strpos($gitignorePattern, '/');
+        if (false !== $slashPosition && \strlen($gitignorePattern) - 1 !== $slashPosition) {
+            if (0 === $slashPosition) {
+                $gitignorePattern = substr($gitignorePattern, 1);
+            }
+
+            $isRelativePath = true;
+            $regex .= '^';
+        }
+
+        if ('/' === $gitignorePattern[\strlen($gitignorePattern) - 1]) {
+            $gitignorePattern = substr($gitignorePattern, 0, -1);
+        }
+
+        $iMax = \strlen($gitignorePattern);
+        for ($i = 0; $i < $iMax; ++$i) {
+            $tripleChars = substr($gitignorePattern, $i, 3);
+            if ('**/' === $tripleChars || '/**' === $tripleChars) {
+                $regex .= '.*';
+                $i += 2;
+                continue;
+            }
+
+            $doubleChars = substr($gitignorePattern, $i, 2);
+            if ('**' === $doubleChars) {
+                $regex .= '.*';
+                ++$i;
+                continue;
+            }
+            if ('*/' === $doubleChars) {
+                $regex .= '[^\/]*\/?[^\/]*';
+                ++$i;
+                continue;
+            }
+
+            $c = $gitignorePattern[$i];
+            switch ($c) {
+                case '*':
+                    $regex .= $isRelativePath ? '[^\/]*' : '[^\/]*\/?[^\/]*';
+                    break;
+                case '/':
+                case '.':
+                case ':':
+                case '(':
+                case ')':
+                case '{':
+                case '}':
+                    $regex .= '\\'.$c;
+                    break;
+                default:
+                    $regex .= $c;
+            }
+        }
+
+        if ($negative) {
+            // a lookbehind assertion has to be a fixed width (it can not have nested '|' statements)
+            return sprintf('%s$|%s\/$', $regex, $regex);
+        }
+
+        return '(?>'.$regex.'($|\/.*))';
+    }
+}

+ 111 - 0
vendor/symfony/finder/Glob.php

@@ -0,0 +1,111 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder;
+
+/**
+ * Glob matches globbing patterns against text.
+ *
+ *     if match_glob("foo.*", "foo.bar") echo "matched\n";
+ *
+ *     // prints foo.bar and foo.baz
+ *     $regex = glob_to_regex("foo.*");
+ *     for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t)
+ *     {
+ *         if (/$regex/) echo "matched: $car\n";
+ *     }
+ *
+ * Glob implements glob(3) style matching that can be used to match
+ * against text, rather than fetching names from a filesystem.
+ *
+ * Based on the Perl Text::Glob module.
+ *
+ * @author Fabien Potencier <fabien@symfony.com> PHP port
+ * @author     Richard Clamp <richardc@unixbeard.net> Perl version
+ * @copyright  2004-2005 Fabien Potencier <fabien@symfony.com>
+ * @copyright  2002 Richard Clamp <richardc@unixbeard.net>
+ */
+class Glob
+{
+    /**
+     * Returns a regexp which is the equivalent of the glob pattern.
+     *
+     * @return string
+     */
+    public static function toRegex(string $glob, bool $strictLeadingDot = true, bool $strictWildcardSlash = true, string $delimiter = '#')
+    {
+        $firstByte = true;
+        $escaping = false;
+        $inCurlies = 0;
+        $regex = '';
+        $sizeGlob = \strlen($glob);
+        for ($i = 0; $i < $sizeGlob; ++$i) {
+            $car = $glob[$i];
+            if ($firstByte && $strictLeadingDot && '.' !== $car) {
+                $regex .= '(?=[^\.])';
+            }
+
+            $firstByte = '/' === $car;
+
+            if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) {
+                $car = '[^/]++/';
+                if (!isset($glob[$i + 3])) {
+                    $car .= '?';
+                }
+
+                if ($strictLeadingDot) {
+                    $car = '(?=[^\.])'.$car;
+                }
+
+                $car = '/(?:'.$car.')*';
+                $i += 2 + isset($glob[$i + 3]);
+
+                if ('/' === $delimiter) {
+                    $car = str_replace('/', '\\/', $car);
+                }
+            }
+
+            if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) {
+                $regex .= "\\$car";
+            } elseif ('*' === $car) {
+                $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*');
+            } elseif ('?' === $car) {
+                $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.');
+            } elseif ('{' === $car) {
+                $regex .= $escaping ? '\\{' : '(';
+                if (!$escaping) {
+                    ++$inCurlies;
+                }
+            } elseif ('}' === $car && $inCurlies) {
+                $regex .= $escaping ? '}' : ')';
+                if (!$escaping) {
+                    --$inCurlies;
+                }
+            } elseif (',' === $car && $inCurlies) {
+                $regex .= $escaping ? ',' : '|';
+            } elseif ('\\' === $car) {
+                if ($escaping) {
+                    $regex .= '\\\\';
+                    $escaping = false;
+                } else {
+                    $escaping = true;
+                }
+
+                continue;
+            } else {
+                $regex .= $car;
+            }
+            $escaping = false;
+        }
+
+        return $delimiter.'^'.$regex.'$'.$delimiter;
+    }
+}

+ 61 - 0
vendor/symfony/finder/Iterator/CustomFilterIterator.php

@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * CustomFilterIterator filters files by applying anonymous functions.
+ *
+ * The anonymous function receives a \SplFileInfo and must return false
+ * to remove files.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class CustomFilterIterator extends \FilterIterator
+{
+    private $filters = [];
+
+    /**
+     * @param \Iterator  $iterator The Iterator to filter
+     * @param callable[] $filters  An array of PHP callbacks
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function __construct(\Iterator $iterator, array $filters)
+    {
+        foreach ($filters as $filter) {
+            if (!\is_callable($filter)) {
+                throw new \InvalidArgumentException('Invalid PHP callback.');
+            }
+        }
+        $this->filters = $filters;
+
+        parent::__construct($iterator);
+    }
+
+    /**
+     * Filters the iterator values.
+     *
+     * @return bool true if the value should be kept, false otherwise
+     */
+    public function accept()
+    {
+        $fileinfo = $this->current();
+
+        foreach ($this->filters as $filter) {
+            if (false === $filter($fileinfo)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}

+ 58 - 0
vendor/symfony/finder/Iterator/DateRangeFilterIterator.php

@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+use Symfony\Component\Finder\Comparator\DateComparator;
+
+/**
+ * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates).
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class DateRangeFilterIterator extends \FilterIterator
+{
+    private $comparators = [];
+
+    /**
+     * @param \Iterator        $iterator    The Iterator to filter
+     * @param DateComparator[] $comparators An array of DateComparator instances
+     */
+    public function __construct(\Iterator $iterator, array $comparators)
+    {
+        $this->comparators = $comparators;
+
+        parent::__construct($iterator);
+    }
+
+    /**
+     * Filters the iterator values.
+     *
+     * @return bool true if the value should be kept, false otherwise
+     */
+    public function accept()
+    {
+        $fileinfo = $this->current();
+
+        if (!file_exists($fileinfo->getPathname())) {
+            return false;
+        }
+
+        $filedate = $fileinfo->getMTime();
+        foreach ($this->comparators as $compare) {
+            if (!$compare->test($filedate)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}

+ 45 - 0
vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php

@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * DepthRangeFilterIterator limits the directory depth.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class DepthRangeFilterIterator extends \FilterIterator
+{
+    private $minDepth = 0;
+
+    /**
+     * @param \RecursiveIteratorIterator $iterator The Iterator to filter
+     * @param int                        $minDepth The min depth
+     * @param int                        $maxDepth The max depth
+     */
+    public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX)
+    {
+        $this->minDepth = $minDepth;
+        $iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth);
+
+        parent::__construct($iterator);
+    }
+
+    /**
+     * Filters the iterator values.
+     *
+     * @return bool true if the value should be kept, false otherwise
+     */
+    public function accept()
+    {
+        return $this->getInnerIterator()->getDepth() >= $this->minDepth;
+    }
+}

+ 87 - 0
vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php

@@ -0,0 +1,87 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * ExcludeDirectoryFilterIterator filters out directories.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator
+{
+    private $iterator;
+    private $isRecursive;
+    private $excludedDirs = [];
+    private $excludedPattern;
+
+    /**
+     * @param \Iterator $iterator    The Iterator to filter
+     * @param string[]  $directories An array of directories to exclude
+     */
+    public function __construct(\Iterator $iterator, array $directories)
+    {
+        $this->iterator = $iterator;
+        $this->isRecursive = $iterator instanceof \RecursiveIterator;
+        $patterns = [];
+        foreach ($directories as $directory) {
+            $directory = rtrim($directory, '/');
+            if (!$this->isRecursive || false !== strpos($directory, '/')) {
+                $patterns[] = preg_quote($directory, '#');
+            } else {
+                $this->excludedDirs[$directory] = true;
+            }
+        }
+        if ($patterns) {
+            $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#';
+        }
+
+        parent::__construct($iterator);
+    }
+
+    /**
+     * Filters the iterator values.
+     *
+     * @return bool True if the value should be kept, false otherwise
+     */
+    public function accept()
+    {
+        if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) {
+            return false;
+        }
+
+        if ($this->excludedPattern) {
+            $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath();
+            $path = str_replace('\\', '/', $path);
+
+            return !preg_match($this->excludedPattern, $path);
+        }
+
+        return true;
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasChildren()
+    {
+        return $this->isRecursive && $this->iterator->hasChildren();
+    }
+
+    public function getChildren()
+    {
+        $children = new self($this->iterator->getChildren(), []);
+        $children->excludedDirs = $this->excludedDirs;
+        $children->excludedPattern = $this->excludedPattern;
+
+        return $children;
+    }
+}

+ 53 - 0
vendor/symfony/finder/Iterator/FileTypeFilterIterator.php

@@ -0,0 +1,53 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * FileTypeFilterIterator only keeps files, directories, or both.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class FileTypeFilterIterator extends \FilterIterator
+{
+    const ONLY_FILES = 1;
+    const ONLY_DIRECTORIES = 2;
+
+    private $mode;
+
+    /**
+     * @param \Iterator $iterator The Iterator to filter
+     * @param int       $mode     The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES)
+     */
+    public function __construct(\Iterator $iterator, int $mode)
+    {
+        $this->mode = $mode;
+
+        parent::__construct($iterator);
+    }
+
+    /**
+     * Filters the iterator values.
+     *
+     * @return bool true if the value should be kept, false otherwise
+     */
+    public function accept()
+    {
+        $fileinfo = $this->current();
+        if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) {
+            return false;
+        } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) {
+            return false;
+        }
+
+        return true;
+    }
+}

+ 58 - 0
vendor/symfony/finder/Iterator/FilecontentFilterIterator.php

@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings).
+ *
+ * @author Fabien Potencier  <fabien@symfony.com>
+ * @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
+ */
+class FilecontentFilterIterator extends MultiplePcreFilterIterator
+{
+    /**
+     * Filters the iterator values.
+     *
+     * @return bool true if the value should be kept, false otherwise
+     */
+    public function accept()
+    {
+        if (!$this->matchRegexps && !$this->noMatchRegexps) {
+            return true;
+        }
+
+        $fileinfo = $this->current();
+
+        if ($fileinfo->isDir() || !$fileinfo->isReadable()) {
+            return false;
+        }
+
+        $content = $fileinfo->getContents();
+        if (!$content) {
+            return false;
+        }
+
+        return $this->isAccepted($content);
+    }
+
+    /**
+     * Converts string to regexp if necessary.
+     *
+     * @param string $str Pattern: string or regexp
+     *
+     * @return string regexp corresponding to a given string or regexp
+     */
+    protected function toRegex(string $str)
+    {
+        return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
+    }
+}

+ 47 - 0
vendor/symfony/finder/Iterator/FilenameFilterIterator.php

@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+use Symfony\Component\Finder\Glob;
+
+/**
+ * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string).
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class FilenameFilterIterator extends MultiplePcreFilterIterator
+{
+    /**
+     * Filters the iterator values.
+     *
+     * @return bool true if the value should be kept, false otherwise
+     */
+    public function accept()
+    {
+        return $this->isAccepted($this->current()->getFilename());
+    }
+
+    /**
+     * Converts glob to regexp.
+     *
+     * PCRE patterns are left unchanged.
+     * Glob strings are transformed with Glob::toRegex().
+     *
+     * @param string $str Pattern: glob or regexp
+     *
+     * @return string regexp corresponding to a given glob or regexp
+     */
+    protected function toRegex(string $str)
+    {
+        return $this->isRegex($str) ? $str : Glob::toRegex($str);
+    }
+}

+ 106 - 0
vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php

@@ -0,0 +1,106 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings).
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+abstract class MultiplePcreFilterIterator extends \FilterIterator
+{
+    protected $matchRegexps = [];
+    protected $noMatchRegexps = [];
+
+    /**
+     * @param \Iterator $iterator        The Iterator to filter
+     * @param string[]  $matchPatterns   An array of patterns that need to match
+     * @param string[]  $noMatchPatterns An array of patterns that need to not match
+     */
+    public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns)
+    {
+        foreach ($matchPatterns as $pattern) {
+            $this->matchRegexps[] = $this->toRegex($pattern);
+        }
+
+        foreach ($noMatchPatterns as $pattern) {
+            $this->noMatchRegexps[] = $this->toRegex($pattern);
+        }
+
+        parent::__construct($iterator);
+    }
+
+    /**
+     * Checks whether the string is accepted by the regex filters.
+     *
+     * If there is no regexps defined in the class, this method will accept the string.
+     * Such case can be handled by child classes before calling the method if they want to
+     * apply a different behavior.
+     *
+     * @return bool
+     */
+    protected function isAccepted(string $string)
+    {
+        // should at least not match one rule to exclude
+        foreach ($this->noMatchRegexps as $regex) {
+            if (preg_match($regex, $string)) {
+                return false;
+            }
+        }
+
+        // should at least match one rule
+        if ($this->matchRegexps) {
+            foreach ($this->matchRegexps as $regex) {
+                if (preg_match($regex, $string)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        // If there is no match rules, the file is accepted
+        return true;
+    }
+
+    /**
+     * Checks whether the string is a regex.
+     *
+     * @return bool
+     */
+    protected function isRegex(string $str)
+    {
+        if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) {
+            $start = substr($m[1], 0, 1);
+            $end = substr($m[1], -1);
+
+            if ($start === $end) {
+                return !preg_match('/[*?[:alnum:] \\\\]/', $start);
+            }
+
+            foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) {
+                if ($start === $delimiters[0] && $end === $delimiters[1]) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Converts string into regexp.
+     *
+     * @return string
+     */
+    abstract protected function toRegex(string $str);
+}

+ 56 - 0
vendor/symfony/finder/Iterator/PathFilterIterator.php

@@ -0,0 +1,56 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * PathFilterIterator filters files by path patterns (e.g. some/special/dir).
+ *
+ * @author Fabien Potencier  <fabien@symfony.com>
+ * @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
+ */
+class PathFilterIterator extends MultiplePcreFilterIterator
+{
+    /**
+     * Filters the iterator values.
+     *
+     * @return bool true if the value should be kept, false otherwise
+     */
+    public function accept()
+    {
+        $filename = $this->current()->getRelativePathname();
+
+        if ('\\' === \DIRECTORY_SEPARATOR) {
+            $filename = str_replace('\\', '/', $filename);
+        }
+
+        return $this->isAccepted($filename);
+    }
+
+    /**
+     * Converts strings to regexp.
+     *
+     * PCRE patterns are left unchanged.
+     *
+     * Default conversion:
+     *     'lorem/ipsum/dolor' ==>  'lorem\/ipsum\/dolor/'
+     *
+     * Use only / as directory separator (on Windows also).
+     *
+     * @param string $str Pattern: regexp or dirname
+     *
+     * @return string regexp corresponding to a given string or regexp
+     */
+    protected function toRegex(string $str)
+    {
+        return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
+    }
+}

+ 144 - 0
vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php

@@ -0,0 +1,144 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+use Symfony\Component\Finder\Exception\AccessDeniedException;
+use Symfony\Component\Finder\SplFileInfo;
+
+/**
+ * Extends the \RecursiveDirectoryIterator to support relative paths.
+ *
+ * @author Victor Berchet <victor@suumit.com>
+ */
+class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
+{
+    /**
+     * @var bool
+     */
+    private $ignoreUnreadableDirs;
+
+    /**
+     * @var bool
+     */
+    private $rewindable;
+
+    // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations
+    private $rootPath;
+    private $subPath;
+    private $directorySeparator = '/';
+
+    /**
+     * @throws \RuntimeException
+     */
+    public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false)
+    {
+        if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) {
+            throw new \RuntimeException('This iterator only support returning current as fileinfo.');
+        }
+
+        parent::__construct($path, $flags);
+        $this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
+        $this->rootPath = $path;
+        if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) {
+            $this->directorySeparator = \DIRECTORY_SEPARATOR;
+        }
+    }
+
+    /**
+     * Return an instance of SplFileInfo with support for relative paths.
+     *
+     * @return SplFileInfo File information
+     */
+    public function current()
+    {
+        // the logic here avoids redoing the same work in all iterations
+
+        if (null === $subPathname = $this->subPath) {
+            $subPathname = $this->subPath = (string) $this->getSubPath();
+        }
+        if ('' !== $subPathname) {
+            $subPathname .= $this->directorySeparator;
+        }
+        $subPathname .= $this->getFilename();
+
+        if ('/' !== $basePath = $this->rootPath) {
+            $basePath .= $this->directorySeparator;
+        }
+
+        return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname);
+    }
+
+    /**
+     * @return \RecursiveIterator
+     *
+     * @throws AccessDeniedException
+     */
+    public function getChildren()
+    {
+        try {
+            $children = parent::getChildren();
+
+            if ($children instanceof self) {
+                // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
+                $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;
+
+                // performance optimization to avoid redoing the same work in all children
+                $children->rewindable = &$this->rewindable;
+                $children->rootPath = $this->rootPath;
+            }
+
+            return $children;
+        } catch (\UnexpectedValueException $e) {
+            if ($this->ignoreUnreadableDirs) {
+                // If directory is unreadable and finder is set to ignore it, a fake empty content is returned.
+                return new \RecursiveArrayIterator([]);
+            } else {
+                throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
+            }
+        }
+    }
+
+    /**
+     * Do nothing for non rewindable stream.
+     */
+    public function rewind()
+    {
+        if (false === $this->isRewindable()) {
+            return;
+        }
+
+        parent::rewind();
+    }
+
+    /**
+     * Checks if the stream is rewindable.
+     *
+     * @return bool true when the stream is rewindable, false otherwise
+     */
+    public function isRewindable()
+    {
+        if (null !== $this->rewindable) {
+            return $this->rewindable;
+        }
+
+        if (false !== $stream = @opendir($this->getPath())) {
+            $infos = stream_get_meta_data($stream);
+            closedir($stream);
+
+            if ($infos['seekable']) {
+                return $this->rewindable = true;
+            }
+        }
+
+        return $this->rewindable = false;
+    }
+}

+ 57 - 0
vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php

@@ -0,0 +1,57 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+use Symfony\Component\Finder\Comparator\NumberComparator;
+
+/**
+ * SizeRangeFilterIterator filters out files that are not in the given size range.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class SizeRangeFilterIterator extends \FilterIterator
+{
+    private $comparators = [];
+
+    /**
+     * @param \Iterator          $iterator    The Iterator to filter
+     * @param NumberComparator[] $comparators An array of NumberComparator instances
+     */
+    public function __construct(\Iterator $iterator, array $comparators)
+    {
+        $this->comparators = $comparators;
+
+        parent::__construct($iterator);
+    }
+
+    /**
+     * Filters the iterator values.
+     *
+     * @return bool true if the value should be kept, false otherwise
+     */
+    public function accept()
+    {
+        $fileinfo = $this->current();
+        if (!$fileinfo->isFile()) {
+            return true;
+        }
+
+        $filesize = $fileinfo->getSize();
+        foreach ($this->comparators as $compare) {
+            if (!$compare->test($filesize)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}

+ 101 - 0
vendor/symfony/finder/Iterator/SortableIterator.php

@@ -0,0 +1,101 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Iterator;
+
+/**
+ * SortableIterator applies a sort on a given Iterator.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class SortableIterator implements \IteratorAggregate
+{
+    const SORT_BY_NONE = 0;
+    const SORT_BY_NAME = 1;
+    const SORT_BY_TYPE = 2;
+    const SORT_BY_ACCESSED_TIME = 3;
+    const SORT_BY_CHANGED_TIME = 4;
+    const SORT_BY_MODIFIED_TIME = 5;
+    const SORT_BY_NAME_NATURAL = 6;
+
+    private $iterator;
+    private $sort;
+
+    /**
+     * @param \Traversable $iterator The Iterator to filter
+     * @param int|callable $sort     The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback)
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false)
+    {
+        $this->iterator = $iterator;
+        $order = $reverseOrder ? -1 : 1;
+
+        if (self::SORT_BY_NAME === $sort) {
+            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
+                return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
+            };
+        } elseif (self::SORT_BY_NAME_NATURAL === $sort) {
+            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
+                return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
+            };
+        } elseif (self::SORT_BY_TYPE === $sort) {
+            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
+                if ($a->isDir() && $b->isFile()) {
+                    return -$order;
+                } elseif ($a->isFile() && $b->isDir()) {
+                    return $order;
+                }
+
+                return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
+            };
+        } elseif (self::SORT_BY_ACCESSED_TIME === $sort) {
+            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
+                return $order * ($a->getATime() - $b->getATime());
+            };
+        } elseif (self::SORT_BY_CHANGED_TIME === $sort) {
+            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
+                return $order * ($a->getCTime() - $b->getCTime());
+            };
+        } elseif (self::SORT_BY_MODIFIED_TIME === $sort) {
+            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
+                return $order * ($a->getMTime() - $b->getMTime());
+            };
+        } elseif (self::SORT_BY_NONE === $sort) {
+            $this->sort = $order;
+        } elseif (\is_callable($sort)) {
+            $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort;
+        } else {
+            throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.');
+        }
+    }
+
+    /**
+     * @return \Traversable
+     */
+    public function getIterator()
+    {
+        if (1 === $this->sort) {
+            return $this->iterator;
+        }
+
+        $array = iterator_to_array($this->iterator, true);
+
+        if (-1 === $this->sort) {
+            $array = array_reverse($array);
+        } else {
+            uasort($array, $this->sort);
+        }
+
+        return new \ArrayIterator($array);
+    }
+}

+ 19 - 0
vendor/symfony/finder/LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2004-2020 Fabien Potencier
+
+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.

+ 14 - 0
vendor/symfony/finder/README.md

@@ -0,0 +1,14 @@
+Finder Component
+================
+
+The Finder component finds files and directories via an intuitive fluent
+interface.
+
+Resources
+---------
+
+  * [Documentation](https://symfony.com/doc/current/components/finder.html)
+  * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+  * [Report issues](https://github.com/symfony/symfony/issues) and
+    [send Pull Requests](https://github.com/symfony/symfony/pulls)
+    in the [main Symfony repository](https://github.com/symfony/symfony)

+ 85 - 0
vendor/symfony/finder/SplFileInfo.php

@@ -0,0 +1,85 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder;
+
+/**
+ * Extends \SplFileInfo to support relative paths.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class SplFileInfo extends \SplFileInfo
+{
+    private $relativePath;
+    private $relativePathname;
+
+    /**
+     * @param string $file             The file name
+     * @param string $relativePath     The relative path
+     * @param string $relativePathname The relative path name
+     */
+    public function __construct(string $file, string $relativePath, string $relativePathname)
+    {
+        parent::__construct($file);
+        $this->relativePath = $relativePath;
+        $this->relativePathname = $relativePathname;
+    }
+
+    /**
+     * Returns the relative path.
+     *
+     * This path does not contain the file name.
+     *
+     * @return string the relative path
+     */
+    public function getRelativePath()
+    {
+        return $this->relativePath;
+    }
+
+    /**
+     * Returns the relative path name.
+     *
+     * This path contains the file name.
+     *
+     * @return string the relative path name
+     */
+    public function getRelativePathname()
+    {
+        return $this->relativePathname;
+    }
+
+    public function getFilenameWithoutExtension(): string
+    {
+        $filename = $this->getFilename();
+
+        return pathinfo($filename, \PATHINFO_FILENAME);
+    }
+
+    /**
+     * Returns the contents of the file.
+     *
+     * @return string the contents of the file
+     *
+     * @throws \RuntimeException
+     */
+    public function getContents()
+    {
+        set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
+        $content = file_get_contents($this->getPathname());
+        restore_error_handler();
+        if (false === $content) {
+            throw new \RuntimeException($error);
+        }
+
+        return $content;
+    }
+}

+ 28 - 0
vendor/symfony/finder/composer.json

@@ -0,0 +1,28 @@
+{
+    "name": "symfony/finder",
+    "type": "library",
+    "description": "Symfony Finder Component",
+    "keywords": [],
+    "homepage": "https://symfony.com",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Fabien Potencier",
+            "email": "fabien@symfony.com"
+        },
+        {
+            "name": "Symfony Community",
+            "homepage": "https://symfony.com/contributors"
+        }
+    ],
+    "require": {
+        "php": ">=7.2.5"
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Component\\Finder\\": "" },
+        "exclude-from-classmap": [
+            "/Tests/"
+        ]
+    },
+    "minimum-stability": "dev"
+}

+ 9 - 0
vendor/zircote/swagger-php/.gitignore

@@ -0,0 +1,9 @@
+/vendor
+/composer.lock
+/bin/*
+!/bin/openapi
+/openapi.json
+/openapi.yaml
+/docs/.vuepress/dist
+.phpunit.result.cache
+/.php_cs.cache

+ 0 - 0
vendor/zircote/swagger-php/.php_cs.dist


Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio